首页 \ 问答 \ 迂回和GCC内联汇编(Linux)(Detouring and GCC inline assembly (Linux))

迂回和GCC内联汇编(Linux)(Detouring and GCC inline assembly (Linux))

我正在为一个游戏编写扩展,该游戏为(我们)modders提供了一个API。 这个API提供了各种各样的东西,但它有一个限制。 API仅用于“引擎”,这意味着基于引擎发布的所有修改(mod)不提供/具有任何种类的(特定于mod的)API。 我创建了一个'签名扫描仪'(注意:我的插件是作为共享库加载的,使用-share&-fPIC编译)可以找到感兴趣的功能(这很容易,因为我在linux上)。 所以要解释一下,我将介绍一个具体的案例:我已经找到了一个感兴趣函数的地址,它的函数头非常简单int * InstallRules(void); 。 它接受一个无(void)并返回一个整型指针(指向我感兴趣的对象)。 现在,我想要做的是创建一个绕道(并记住我有函数的开始地址),我自己的函数,我想这样做:

void MyInstallRules(void)
{
    if(PreHook() == block) // <-- First a 'pre' hook which can block the function
        return;
    int * val = InstallRules(); // <-- Call original function
    PostHook(val); // <-- Call post hook, if interest of original functions return value
}

现在是这笔交易; 我没有经历过关于函数挂钩的经历,而且我对内联汇编知之甚少(仅限于AT&T)。 互联网上的预制迂回包适用于Windows或使用其他方法(即,预先加载dll来覆盖原有的方法)。 所以基本上; 我该怎么做才能走上正轨? 我应该阅读关于调用约定(本例中为cdecl)并了解内联汇编,或者该怎么做? 最好的可能是一个已经功能的Linux绕行类包装类。 最后,我希望这样简单的事情:

void * addressToFunction = SigScanner.FindBySig("Signature_ASfs&43"); // I've already done this part
void * original = PatchFunc(addressToFunction, addressToNewFunction); // This replaces the original function with a hook to mine, but returns a pointer to the original function (relocated ofcourse)
// I might wait for my hook to be called or whatever
// ....

// And then unpatch the patched function (optional)
UnpatchFunc(addressToFunction, addressToNewFunction);

我明白,我不能在这里得到一个完全令人满意的答案,但我会不胜感激一些帮助的方向,因为我在这里薄冰...我已经阅读了有关迂回,但几乎没有任何文档(特别是Linux),我想我想实现所谓的“蹦床”,但我似乎无法找到如何获得这些知识的方法。

注意:我也对_thiscall感兴趣,但是从我读过的内容来看,使用GNU调用约定(?)并不那么难。


I'm programming extensions for a game which offers an API for (us) modders. This API offers a wide variety of things, but it has one limitation. The API is for the 'engine' only, which means that all modifications (mods) that has been released based on the engine, does not offer/have any sort of (mod specific) API. I have created a 'signature scanner' (note: my plugin is loaded as a shared library, compiled with -share & -fPIC) which finds the functions of interest (which is easy since I'm on linux). So to explain, I'll take a specific case: I have found the address to a function of interest, its function header is very simpleint * InstallRules(void);. It takes a nothing (void) and returns an integer pointer (to an object of my interest). Now, what I want to do, is to create a detour (and remember that I have the start address of the function), to my own function, which I would like to behave something like this:

void MyInstallRules(void)
{
    if(PreHook() == block) // <-- First a 'pre' hook which can block the function
        return;
    int * val = InstallRules(); // <-- Call original function
    PostHook(val); // <-- Call post hook, if interest of original functions return value
}

Now here's the deal; I have no experience what so ever about function hooking, and I only have a thin knowledge of inline assembly (AT&T only). The pre-made detour packages on the Internet is only for windows or is using a whole other method (i.e preloads a dll to override the orignal one). So basically; what should I do to get on track? Should I read about call conventions (cdecl in this case) and learn about inline assembly, or what to do? The best would probably be a already functional wrapper class for linux detouring. In the end, I would like something as simple as this:

void * addressToFunction = SigScanner.FindBySig("Signature_ASfs&43"); // I've already done this part
void * original = PatchFunc(addressToFunction, addressToNewFunction); // This replaces the original function with a hook to mine, but returns a pointer to the original function (relocated ofcourse)
// I might wait for my hook to be called or whatever
// ....

// And then unpatch the patched function (optional)
UnpatchFunc(addressToFunction, addressToNewFunction);

I understand that I won't be able to get a completely satisfying answer here, but I would more than appreciate some help with the directions to take, because I am on thin ice here... I have read about detouring but there is barely any documentation at all (specifically for linux), and I guess I want to implement what's known as a 'trampoline' but I can't seem to find a way how to acquire this knowledge.

NOTE: I'm also interested in _thiscall, but from what I've read that isn't so hard to call with GNU calling convention(?)


原文:https://stackoverflow.com/questions/10420102
更新时间:2019-10-10 02:36

最满意答案

这个项目是否会开发一个“框架”,使其他人能够在不同的二进制文件中挂钩不同的功能? 还是仅仅是需要钩住这个特定的程序?

首先,让我们假设你想要第二件事,你只需要一个二进制代码中的函数,你想要以编程方式和可靠地挂钩。 普遍做到这一点的主要问题是,可靠地做到这一点是一场非常艰难的比赛,但如果你愿意做出一些妥协,那肯定是可行的。 也让我们假设这是x86的东西。

如果你想挂钩一个功能,有几个选择如何做到这一点。 Detours所做的是内联补丁 。 他们对Research PDF文档中的工作原理有很好的概述。 基本的想法是你有一个功能,例如

00E32BCE  /$ 8BFF           MOV EDI,EDI
00E32BD0  |. 55             PUSH EBP
00E32BD1  |. 8BEC           MOV EBP,ESP
00E32BD3  |. 83EC 10        SUB ESP,10
00E32BD6  |. A1 9849E300    MOV EAX,DWORD PTR DS:[E34998]
...
...

现在用CALL或JMP函数的开头替换函数的起始位置,并将修补符所覆盖的原始字节保存在某处:

00E32BCE  /$ E9 XXXXXXXX    JMP MyHook
00E32BD3  |. 83EC 10        SUB ESP,10
00E32BD6  |. A1 9849E300    MOV EAX,DWORD PTR DS:[E34998]

(请注意,我重写了5个字节。)现在,您的函数将使用与原始函数相同的参数和相同的调用约定进行调用。 如果您的函数想要调用原始函数(但不是必须的),则创建一个“蹦床”,该函数1)运行原始指令,该指令被原始函数的其余部分覆盖2)jmps:

Trampoline:
    MOV EDI,EDI
    PUSH EBP
    MOV EBP,ESP
    JMP 00E32BD3

就是这样,您只需在运行时通过发送处理器指令来构建蹦床功能。 这个过程的难点在于让它可靠地工作,适用于任何函数,任何调用约定和不同的操作系统/平台。 其中一个问题是,如果要覆盖的5个字节在指令中间结束。 要检测“指令的结束”,基本上需要包含一个反汇编程序,因为在函数的开头可以有任何指令。 或者当函数本身短于5个字节时(一个返回0的函数可以被写为XOR EAX,EAX; RETN ,它只是3个字节)。

大多数当前的编译器/汇编程序产生一个5字节长的函数prolog,正是为了这个目的,挂钩。 看到MOV EDI, EDI ? 如果你想知道,“他们为什么把edi移动到edi?那什么都不做!?” 你是绝对正确的,但这是prolog的目的,正好是5个字节长(不在指令的中间结束)。 请注意,反汇编示例不是我编写的,它是Windows Vista上的calc.exe。

钩子实现的其余部分仅仅是技术细节,但它们会给你带来许多小时的痛苦,因为这是最难的部分。 还有你在你的问题中描述的行为:

void MyInstallRules(void)
{
    if(PreHook() == block) // <-- First a 'pre' hook which can block the function
        return;
    int * val = InstallRules(); // <-- Call original function
    PostHook(val); // <-- Call post hook, if interest of original functions return value
}

似乎比我描述的更糟糕(以及Detours的做法),例如,您可能希望“不要调用原始”,但返回一些不同的值。 或者两次调用原始函数。 相反,让你的钩子处理程序决定是否以及它将在哪里调用原始函数。 此外,你不需要钩子的两个处理函数。

如果您对此技术(主要是装配)所需的技术知之甚少,或者不知道如何进行挂钩,我建议您研究Detours的工作。 钩住你自己的二进制文件,然后拿一个调试器(例如OllyDbg)在汇编级别看到它究竟做了什么,放置了什么指令以及在哪里。 本教程也可以派上用场。

无论如何,如果你的任务是钩住特定程序中的某些功能,那么这是可行的,如果你有任何问题,请再次询问。 基本上你可以做很多假设(比如函数序言或惯例),这会使你的任务变得更容易。

如果你想创建一些可靠的钩子框架,那么仍然是一个完全不同的故事,你应该首先为一些简单的应用程序创建简单的钩子。

还要注意,这种技术不是特定于操作系统的,在所有的x86平台上都是一样的,它可以在Linux和Windows上运行。 操作系统的具体内容是,您可能必须更改代码的内存保护(“解锁”它,以便您可以写入),这是通过Linux上的mprotect和Windows上的VirtualProtect完成的。 调用约定也不同,这就是你可以在编译器中使用正确的语法来解决的问题。

另一个麻烦是“DLL注入”(在Linux上它可能被称为“共享库注入”,但术语DLL注入是众所周知的)。 你需要把你的代码(执行钩子)放到程序中。 我的建议是,如果可能的话,只需使用LD_PRELOAD环境变量,在这个变量中你可以指定一个库,它将在程序运行之前加载到程序中。 这在很多时候都有描述,像这样: 什么是LD_PRELOAD技巧? 。 如果你必须在运行时执行此操作,恐怕你需要使用gdb或ptrace,在我看来这很难(至少是ptrace)。 不过,你可以阅读例如这篇关于codeproject这个ptrace教程的文章

我还发现了一些不错的资源:

还有一点:这种“内联修补”不是唯一的方法。 甚至有更简单的方法,例如,如果函数是虚拟的或者它是一个库导出函数,则可以跳过所有的装配/反汇编/ JMP事物,并简单地将指针替换为该函数(在虚函数表中或在导出符号表)。


Is this project to develop a "framework" that will allow others to hook different functions in different binaries? Or is it just that you need to hook this specific program that you have?

First, let's suppose you want the second thing, you just have a function in a binary that you want to hook, programmatically and reliably. The main problem with doing this universally is that doing this reliably is a very tough game, but if you are willing to make some compromises, then it's definitely doable. Also let's assume this is x86 thing.

If you want to hook a function, there are several options how to do it. What Detours does is inline patching. They have a nice overview of how it works in a Research PDF document. The basic idea is that you have a function, e.g.

00E32BCE  /$ 8BFF           MOV EDI,EDI
00E32BD0  |. 55             PUSH EBP
00E32BD1  |. 8BEC           MOV EBP,ESP
00E32BD3  |. 83EC 10        SUB ESP,10
00E32BD6  |. A1 9849E300    MOV EAX,DWORD PTR DS:[E34998]
...
...

Now you replace the beginning of the function with a CALL or JMP to your function and save the original bytes that you overwrote with the patch somewhere:

00E32BCE  /$ E9 XXXXXXXX    JMP MyHook
00E32BD3  |. 83EC 10        SUB ESP,10
00E32BD6  |. A1 9849E300    MOV EAX,DWORD PTR DS:[E34998]

(Note that I overwrote 5 bytes.) Now your function gets called with the same parameters and same calling convention as the original function. If your function wants to call the original one (but it doesn't have to), you create a "trampoline", that 1) runs the original instructions that were overwritten 2) jmps to the rest of the original function:

Trampoline:
    MOV EDI,EDI
    PUSH EBP
    MOV EBP,ESP
    JMP 00E32BD3

And that's it, you just need to construct the trampoline function in runtime by emitting processor instructions. The hard part of this process is to get it working reliably, for any function, for any calling convention and for different OS/platforms. One of the issues is that if the 5 bytes that you want to overwrite ends in a middle of an instruction. To detect "ends of instructions" you would basically need to include a disassembler, because there can be any instruction at the beginning of the function. Or when the function is itself shorter than 5 bytes (a function that always returns 0 can be written as XOR EAX,EAX; RETN which is just 3 bytes).

Most current compilers/assemblers produce a 5-byte long function prolog, exactly for this purpose, hooking. See that MOV EDI, EDI? If you wonder, "why the hell do they move edi to edi? that doesn't do anything!?" you are absolutely correct, but this is the purpose of the prolog, to be exactly 5-bytes long (not ending in a middle of an instruction). Note that the disassembly example is not something I made up, it's calc.exe on Windows Vista.

The rest of the hook implementation is just technical details, but they can bring you many hours of pain, because that's the hardest part. Also the behaviour you described in your question:

void MyInstallRules(void)
{
    if(PreHook() == block) // <-- First a 'pre' hook which can block the function
        return;
    int * val = InstallRules(); // <-- Call original function
    PostHook(val); // <-- Call post hook, if interest of original functions return value
}

seems worse than what I described (and what Detours does), for example you might want to "not call the original" but return some different value. Or call the original function twice. Instead, let your hook handler decide whether and where it will call the original function. Also then you don't need two handler functions for a hook.

If you don't have enough knowledge about the technologies you need for this (mostly assembly), or don't know how to do the hooking, I suggest you study what Detours does. Hook your own binary and take a debugger (OllyDbg for example) to see at assembly level what it exactly did, what instructions were placed and where. Also this tutorial might come in handy.

Anyway, if your task is to hook some functions in a specific program, then this is doable and if you have any trouble, just ask here again. Basically you can do a lot of assumptions (like the function prologs or used conventions) that will make your task much easier.

If you want to create some reliable hooking framework, then still is a completely different story and you should first begin by creating simple hooks for some simple apps.

Also note that this technique is not OS specific, it's the same on all x86 platforms, it will work on both Linux and Windows. What is OS specific is that you will probably have to change memory protection of the code ("unlock" it, so you can write to it), which is done with mprotect on Linux and with VirtualProtect on Windows. Also the calling conventions are different, that that's what you can solve by using the correct syntax in your compiler.

Another trouble is "DLL injection" (on Linux it will probably be called "shared library injection" but the term DLL injection is widely known). You need to put your code (that performs the hook) into the program. My suggestion is that if it's possible, just use LD_PRELOAD environment variable, in which you can specify a library that will be loaded into the program just before it's run. This has been described in SO many times, like here: What is the LD_PRELOAD trick?. If you must do this in runtime, I'm afraid you will need to get with gdb or ptrace, which in my opinion is quite hard (at least the ptrace thing) to do. However you can read for example this article on codeproject or this ptrace tutorial.

I also found some nice resources:

Also one other point: This "inline patching" is not the only way to do this. There are even simpler ways, e.g. if the function is virtual or if it's a library exported function, you can skip all the assembly/disassembly/JMP thing and simply replace the pointer to that function (either in the table of virtual functions or in the exported symbols table).

2017-05-23

相关问答

更多

使用gcc中的内联汇编从stdin扫描并打印到stdout(Scan from stdin and print to stdout using inline assembly in gcc)

这段代码完全基于我对linux引用的阅读。 我不是在Linux上,所以我无法测试它,但它应该非常接近。 我会使用重定向测试它:a.out <foo.txt #include <stdio.h> #define SYS_READ 3 int main() { char buff[10]; /* Declare a buff to hold the returned string. */ ssize_t charsread; /* The number of characters r

在Linux内核中使用修饰符“P”和约束“p”到“m”的gcc内联汇编(gcc inline assembly using modifier “P” and constraint “p” over “m” in Linux kernel)

你的示例代码不起作用的原因是因为"p"约束在内联汇编中只是非常有限的用途。 所有内联汇编操作数都要求它们可以用汇编语言表示为操作数。 如果操作数不可表示,那么编译器通过先将其移到一个寄存器并将其替换为操作数来实现。 "p"约束会增加一个限制:操作数必须是有效地址。 问题是一个寄存器不是一个有效的地址。 寄存器可以包含地址,但寄存器本身不是有效地址。 这意味着"p"约束的操作数必须具有有效的汇编表示,并且是有效的地址。 你正试图使用堆栈中变量的地址作为操作数。 虽然这是一个有效的地址,但它不是一个有 ...

GCC Inline Assembly中的“= w”是什么意思?(What does “=w” in GCC Inline Assembly mean?)

这是输出限制。 等号表示“将写入价值”。 字母w表示AVR“寄存器从r24到r31”。 在这里查找特定于机器的约束描述。 编译器在寄存器分配阶段使用约束本身,以便分配正确的寄存器(在这种情况下 - 正是这样的,可以参与sbiw操作)。 如果你要指定错误的约束,gcc可能会为sbiw分配错误的寄存器,这会导致错误的输出程序集等。 It is output constraint. Equal sign means "value will be written to". Letter w means

在内联GCC组件中使用C数组(Using C arrays in inline GCC assembly)

代码失败的原因不是因为数组,而是因为fld和fst指令的工作方式。 这是你想要的代码: float tmp; __asm__ ( "flds %1; fld %%st(0); fmulp; " : "=t" (tmp) : "m" (x[0]) ); __asm__ ( "flds %1; fadds %2;" : "=t" (A[0]) : "m" (A[0]), "m" (tmp) ); fld和fst指令需要一个内存操作数。 此外,您需要指定是否要加载float(flds),doubl

线程局部变量和内联汇编(Thread local variables and inline assembly)

一个简单的事情肯定是工作是获取一个指向线程局部变量的指针,然后写入它。 你的编译器肯定会正确执行long *saved_fp_p = &saved_fp ,而内联汇编只会处理saved_fp_p ,这是一个局部变量。 您还可以使用gcc的输入和输出语法: __asm__ __volatile__ ( "mov %%rsp, 0(%0)" : : "r" (&saved_sp) ); 这使编译器负责解析saved_fp的地址,汇编代码将其放入寄存器中。 我们发现这也有效, __asm__

如何在GCC x86内联汇编中使用地址常量(How to use address constants in GCC x86 inline assembly)

noprefix / prefix指令只控制寄存器是否需要%前缀(*)(至少看起来如此,这是文档提到的唯一区别)。 值文字在AT&T语法中总是需要$前缀,而从不在Intel语法中。 所以下面的作品: __asm__(".intel_syntax prefix"); __asm__("MOV [DWORD PTR 0xDEADBEEF], 0x1234"); 如果您确实倾向于在使用GCC编译的C代码中使用Intel语法内联汇编,并且使用GAS进行汇编,请不要忘记在汇编后添加以下内容,以便汇编程序

什么是GCC内联汇编语言中的r()和双重百分比%%?(What is r() and double percent %% in GCC inline assembly language?)

好的,这是gcc内联汇编程序,它非常强大但很难理解。 首先,%char是一个特殊字符。 它可以让你定义寄存器和数字占位符(稍后在此)。 不幸的是,%也被用作寄存器名称的一部分(例如%EAX),所以在gcc内联汇编器中,如果要命名寄存器,则必须使用两个百分比字符。 %0,%1和%2(例如..)是占位符输入和输出操作数。 这些在汇编程序字符串后面的列表中定义。 在你的例子中,%0成为y的占位符,%1成为x的占位符。 编译器将确保变量在asm-code被执行前保存在输入操作数的寄存器中,并确保输出操作

gcc - 2个版本,内联函数的不同处理(gcc - 2 versions, different treatment of inline functions)

是的,行为已经从gcc-4.3开始改变了。 gcc内联文档( http://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Inline.html )详细说明了这一点。 简短的说明:简单内联只用于告诉gcc(无论如何在旧版本中)内联调用来自同一文件范围。 但是,它并没有告诉gcc所有调用者都来自文件范围,因此gcc还保留了f()的可链接版本:这解释了上面的重复符号错误。 Gcc 4.3改变了这种行为以与c99兼容。 并且,回答您的具体问题: 现在问题是:我应该如何定义和

相关文章

更多

最新问答

更多
  • 使用javascript为用户延迟加载内容的更好方法是什么?(What is a better way to lazy load content for users with javascript?)
  • 如何修复无法将类型的值...转换为预期的参数类型inout _(How to fix Cannot convert value of type … to expected argument type inout _)
  • 从哪里开始解析JSON?(Where to start with parsing JSON?)
  • Mail.php不适用于.htaccess(Mail.php doesn't work with .htaccess)
  • 使用htaccess设置子域(Setting subdomains using htaccess)
  • mod_pagespeed和java应用服务器(mod_pagespeed and java app servers)
  • Win32:如何崩溃?(Win32: How to crash?)
  • FFmpeg中RGB到YUV转换的错误(Error in RGB to YUV conversion in FFmpeg)
  • 如何在Android中首次打开应用时创建弹出窗口?(How to create pop-up when first open app in Android?)
  • 通过动态传递表名和列名来反转COLUMN DATA(Reversing COLUMN DATA by dynamically passing table name and Column Name)
  • 如何计算C#中ICollection中有多少元素?(How can I count how many elements are in an ICollection in C#)
  • 从pg-promise查询中获得结果(get result out of a pg-promise query)
  • 使用管理器注册一个类,然后调用子类重写方法(Register a class with a manager, then call the sub classes overridden methods)
  • 传递&在查询字符串中(Passing & in query string)
  • 谷歌浏览器打印预览不会第一次加载页面(google chrome print preview does not load the page the first time)
  • FsLexYacc:Tests / MiniProject“在.fsproj中导入错误”(FsLexYacc : Tests/MiniProject “incorrect Import in .fsproj”)
  • 创建一个模型方法,计算用户的帖子并在rspec(Rails)中测试?(Create a model method that counts a user's posts and test in rspec (Rails)?)
  • 记录线程的奇怪行为(Strange behaviour of logging thread)
  • 移动数组中的索引(Moving indices in an array)
  • MVC 5 - > MVC 5.1迁移。(MVC 5 -> MVC 5.1 Migration. Intellisense issues)
  • 使用函数在z3中创建列表(Creating List in z3 using function)
  • 如何进行html随机重定向(How to make a html random redirect)
  • 角度js:使用角度过滤器分割日期,月份和时间(Angular js: spliting date,month and time using angular filter)
  • 将所有模块导入一个文件夹,并按其属性之一使用它们(Import all modules in one folder and use them by one of its attributes)
  • 只需要在git的当前分支上获得合并列表(Just get list of merge happened on my current branch in git)
  • React-Native导入库并在多个组件中可用(React-Native import a library and make available in multiple components)
  • 如何使用jQuery为固定(偏移)位置设置div动画?(How to animate div for a fixed (offset) position using jQuery?)
  • operator ==和vector confusion(operator== and vector confusion)
  • 如何获取所选图像映射alt值?(How to get selected image map alt value?)
  • 在角度,如何使用取消用户事件的美元间隔,如页面更改?(In angular, how to use cancel an $interval on user events, like page change?)