绕行和GCC内联汇编(Linux)

Ell*_*ink 8 linux assembly hook gcc detours

我正在为一个为(我们)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
}
Run Code Online (Sandbox Code Playgroud)

现在这是交易; 我没有任何关于功能挂钩的经验,而且我对内联汇编(仅限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);
Run Code Online (Sandbox Code Playgroud)

我明白在这里我无法得到一个完全令人满意的答案,但我不仅仅对这些方向的帮助表示感谢,因为我在这里处于薄弱的地方......我读过有关绕行的内容,但几乎没有任何文档(特别是对于linux),我想我想实现所谓的"蹦床",但我似乎找不到如何获取这些知识的方法.

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

kub*_*uba 12

这个项目是否开发了一个"框架",允许其他人在不同的二进制文件中挂钩不同的功能?或者只是需要挂钩你有这个特定的程序?

首先,让我们假设你想要第二件事,你只需要一个二进制函数,你想要挂钩,编程和可靠.普遍做到这一点的主要问题是,可靠地做到这一点是一场非常艰难的比赛,但如果你愿意做出一些妥协,那么它肯定是可行的.另外我们假设这是x86的事情.

如果要挂钩函数,有几个选项可以执行此操作.什么弯路确实是内联补丁.他们对研究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]
...
...
Run Code Online (Sandbox Code Playgroud)

现在用函数CALL或JMP替换函数的开头,并保存你用补丁覆盖的原始字节:

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

(注意我覆盖了5个字节.)现在使用与原始函数相同的参数和相同的调用约定来调用函数.如果你的函数想要调用原始函数(但它没有),你创建一个"trampoline",1)运行被覆盖的原始指令2)jmps到原始函数的其余部分:

Trampoline:
    MOV EDI,EDI
    PUSH EBP
    MOV EBP,ESP
    JMP 00E32BD3
Run Code Online (Sandbox Code Playgroud)

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

大多数当前的编译器/汇编器产生一个5字节长的函数序列,正是为了这个目的,挂钩.看到了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
}
Run Code Online (Sandbox Code Playgroud)

似乎比我描述的更糟(以及Detours的作用),例如你可能想要"不调用原始"但返回一些不同的值.或者两次调用原始功能.相反,让你的钩子处理程序决定它是否以及在何处调用原始函数.此外,您不需要两个钩子处理函数.

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

无论如何,如果你的任务是挂钩特定程序中的某些功能,那么这是可行的,如果你遇到任何麻烦,请再次询问.基本上你可以做很多假设(如函数prologs或使用的约定),这将使你的任务更容易.

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

另请注意,此技术不是特定于操作系统,在所有x86平台上都是相同的,它可以在Linux和Windows上运行.什么 OS特定的是你可能不得不改变代码的内存保护("解锁"它,所以你可以写它),这是mprotect在Linux和VirtualProtectWindows上完成的.调用约定也是不同的,这就是你可以通过在编译器中使用正确的语法来解决的问题.

另一个麻烦是"DLL注入"(在Linux上它可能被称为"共享库注入",但术语DLL注入是众所周知的).您需要将代码(执行挂钩)放入程序中.我的建议是,如果可能,只需使用LD_PRELOAD环境变量,您可以在其中指定一个库,该库将在运行之前加载到程序中.这已经在很多时候被描述过了,就像这里:什么是LD_PRELOAD技巧?.如果你必须在运行时这样做,我担心你需要使用gdb或ptrace,我认为这很难(至少是ptrace的事情).但是,您可以阅读有关codeproject此ptrace教程的本文.

我也找到了一些不错的资源:

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