首页 \ 问答 \ 迂回和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

相关问答

更多

如何在给出字符串的开始和结束部分时使用正则表达式提取字符串的一部分(How to extract a part of a string using regex, when starting and ending portion of the string is given)

这是sed的正则表达式: "sed -i 's,/home/portweb/software/nodejs/\(.*\)/bin,/home/portweb/software/nodejs/'" + nodeversion + "'/bin,g'" 请注意,您可以使用除/之外的分隔符以便于识别,所以我们在这里使用, Here is the regex for sed: "sed -i 's,/home/portweb/software/nodejs/\(.*\)/bin,/home/portw

Python:Spyder在未停靠的编辑器窗口和spyder控制台之间切换(Python: Spyder switching between undocked editor window and spyder console)

( Spyder维护者在这里 )你需要去编辑器右边的小齿轮菜单,并选择新窗口选项来获得一个合适的窗口。 这将在Spyder 4中得到改进,允许我们所有的窗格在未连接时生成一个新窗口(注意:这已经在Spyder master分支中实现,以防您想尝试)。 (Spyder maintainer here) You need to go to the little cog menu on the right of the Editor and select the option New window t

我可以使用带有params关键字的lambda表达式吗?(Can I use a lambda expression with params keyword?)

这有用吗? 编辑对不起,用一只眼睛做这个,让我改一下。 static int myMethod (int n, params MyDel[] lambdas) { Does this work? EDIT Sorry, was doing this with one eye, let me rephrase that. static int myMethod (int n, params MyDel[] lambdas) {

以编程方式在swift中格式化字符串(Programmatically formatting string in swift)

您可以使用正则表达式: let string = "813 - Crying Flute.mp3" let regex = try! NSRegularExpression(pattern: "^(.*) - (.*)\\.(.*)$") if let match = regex.firstMatch(in: string, range: string.nsRange) { let title = string[match.range(at: 1)] let name

如何根据现有因素在新列中添加因子(How to add a factor in a new column according to the existing factors)

我认为这样的事情: df$new <- with(df,ave(sequence(nrow(df)),list(statenum,casenum),FUN=length)) > df statenum casenum vnumber pnumber numfatal new 1 48 3081 1 1 1 1 2 48 3080 5 1 1 5 3 4

yii CHtml ::按钮和POST请求到控制器(yii CHtml::button and POST request to controller)

尝试这个: echo CHtml::button('Delete', array( 'submit'=>array('controllername/actionname',array('id'=>$id)), 'confirm' => 'Are you sure?' // or you can use 'params'=>array('id'=>$id) ) ); 正如你将会看到的, button还clientChange特殊的h

android ListIterator,类转换bug?(android ListIterator, class casting bug? please check my log)

您需要在Math.random()*100周围添加括号以覆盖优先级。 list.add(""+ ((int) (Math.random()*100))); 否则, (int)只是将random()返回的float 0.0为1 (不包括random()为0 ,当乘以100时再次给出0 。 You need to put parentheses around Math.random()*100 to override precedence. list.add(""+ ((int) (Math.ra

当我使用函数创建属性时,如何向R data.frame添加属性?(How do I add an attribute to an R data.frame while I'm making it with a function?)

请注意,R中的数据库函数通常会返回一个数据框,因此您不必填充空的现有数据框。 下面我们使用sqldf包来保持示例自包含且可重现,但您可以替换正在使用的任何类型的数据库访问。 (通常,您需要创建一个数据库连接并将其传递给answer_maker但在此示例中,因为我们使用的是sqldf,所以不需要它。) library(sqldf) name_table <- data.frame(names = letters, age = 1:26) # test data answer_maker <

相关文章

更多

最新问答

更多
  • 如何在给出字符串的开始和结束部分时使用正则表达式提取字符串的一部分(How to extract a part of a string using regex, when starting and ending portion of the string is given)
  • 使用从下拉列表派生的变量作为select语句中的列名... Access DB(Using a variable derived from a drop-down list as the column name in a select statement … Access DB)
  • 禁用付款选项 - 仅限特定产品 - magento的货到付款(Disable payment options-only cash on delivery for particular product-magento)
  • Python:Spyder在未停靠的编辑器窗口和spyder控制台之间切换(Python: Spyder switching between undocked editor window and spyder console)
  • 使用proxy_pass会影响letsencrypt的安装吗?(Will using proxy_pass affect letsencrypt installation?)
  • 即使confirm()方法为false,jquery AJAX也会提交表单(Jquery AJAX submits the form even if confirm() method is false)
  • 我可以使用带有params关键字的lambda表达式吗?(Can I use a lambda expression with params keyword?)
  • 以编程方式在swift中格式化字符串(Programmatically formatting string in swift)
  • 如何根据现有因素在新列中添加因子(How to add a factor in a new column according to the existing factors)
  • android快速加载来自网址的图片(android fast load image from url)
  • yii CHtml ::按钮和POST请求到控制器(yii CHtml::button and POST request to controller)
  • 无法更改工具栏的颜色(Can't change the color of toolbar)
  • android ListIterator,类转换bug?(android ListIterator, class casting bug? please check my log)
  • 当我使用函数创建属性时,如何向R data.frame添加属性?(How do I add an attribute to an R data.frame while I'm making it with a function?)
  • 如何将我的数据存储到Activity类中的一个对象中,并将该对象发送到另一个Activity类(How to store my data into one object in my Activity class and send that object to another Activity class)
  • 无法在wamp服务器上的joomla安装中配置数据库(Unable to configure database in joomla installation on wamp server)
  • 捕获所有重复的组(Capture all repeated groups)
  • 为宏提供状态更新,直到完成后进入无响应状态(Providing status updates for macro that goes into not responding state until completion)
  • 如何让PDO Fetch()以字符串形式返回(How to get a PDO Fetch( ) to return as string)
  • 无法访问SDK工具来安装NDK(Can't access SDK Tools to install NDK)
  • 获取CGPoint与应用程序窗口进行比较(Get CGPoint in comparison to app Window)
  • 如何在css中对齐图像和文本?(How to align images and text in css?)
  • BinaryWriter将脏字符放在AppendMode的开头写入[重复](BinaryWriter puts dirty chars at the begin writing in AppendMode [duplicate])
  • Jquery垂直内容滚动条(Jquery Vertical content scroller)
  • 是否有可能在android studio中更改.so文件代码并进行构建?(Is it possible to change the .so file code in android studio itself and make a build?)
  • prolog避免重复谓词(prolog avoiding duplicate predicates)
  • 在Swift中使用AlamoFire创建通用方法(Creating a generic method with AlamoFire in Swift)
  • 如何获得给定的JSON值?(how to get given JSON value?)
  • 访问当前对象的类(Access class of current object)
  • 如何延迟jQuery中的文本功能(How to delay the text function in jQuery)