本教程的目的是对LIEF中解析和处理格式的API进行一个总体的介绍
首先从ELF
格式开始。要创建一个ELF.Binary
对象,我们只需将ELF文件的路径传给lief.parse()或是lief.ELF.parse()函数。
注意:在Python API中,这两个函数的功能是相同的,但在C++的API中,LIEF:Parser:parse()
返回值是一个指向LIEF::Binary
对象的指针,而LIEF::ELF::Parser::parse()
返回的是一个LIEF::ELF::Binary
对象。
在解析完ELF后,就可以访问它的Header
:
header = binary.header
修改入口点和目标架构(ARCH
)。
然后重新生成它。
我们也可以像下面这样对Section
进行遍历:
修改.text
区段的内容:
像ELF
一样,可以用lief.parse()
或lief.PE.parse()
函数创建PE.Binary
要访问不同的PE头(DosHeader
,Header
和OptionalHeader
):
你可以用两种方法访问导入函数。其中比较抽象的方法是使用LIEF的抽象层。
如果你想要使用精度更细的方法以确定函数在哪个动态库中,或是访问PE导入表中的其它字段:
LIEF可以修改Import
和ImportEntry
的所有属性,但是要进行这个操作的话,必须先配置Builder
:
在这篇教程中,我们将学习怎么从零开始创建一个PE可执行文件。
脚本和资源可以在这里找到:materials
LIEF支持从零开始创建一个简单的PE文件,在这篇文章会介绍怎么创建一个可以用MessageBoxA
弹出"Hello Word"的PE文件。
首先需要构造一个Binary
:
第一个参数是二进制文件的名字,第二个参数是PE文件的类型:PE32
或是PE64
(见PE_TYPE
)。Binary
的构造器可以自动创建DosHeader
,Header
,OptionalHeader
以及一个空的DataDirectory
。
现在我们已经有了一个最小的二进制程序,我们需要添加区段。第一个区段(.text
)用来保存汇编代码,第二个区段用来保存字符串(.data
):
MessageBoxA
由title和message组成。这两个字符串将存储在.data
段中:
.text
段的伪汇编代码如下:
事实上我们push的不是字符串,而是字符串所在的虚拟地址。在PE格式中,虚拟地址表示的是相对虚拟地址(如果ASLR不开启的话,是相对于Optional.imagebase
)。Binary
构造器会默认把imagebase
设为0x400000
。
字符串的虚拟地址计算如下的如下:
因为代码中使用了MessageBoxA
,我们需要将user32.dll
放进Import
s表中,并将MessageBoxA
放进ImportEntry
中。我们可以使用add_library()
和add_entry()
来实现这一操作。
ExitProcess
(kernel32.dll
)的导入也是这样操作:
在动态库和函数导入进来之后,我们需要确定它们的地址(Import Address Table)。
要实现这个功能可以使用predict_funciton_rva()
方法,它会返回由Builder
设置的IAT
地址:
Binary.predict_function_rva(self: _pylief.PE.Binary, library: str, function: str) → int
尝试预测在指定动态库中某函数的RVA
MessageBoxA
和ExitProcess
的绝对虚拟地址是:
相关的汇编代码为:
将Binary
对象转化为可执行文件的操作是由Builder
类来实现的。
默认情况下,导入表不会被重建,所以我们需要手动配置:
现在你可以试试这个新创建的二进制文件了。
在这篇教程我们将学习如何在可执行文件和动态库中修改动态符号。
脚本和资源可以在这里找到:materials
当一个动态库被链接到可执行文件中,这个动态库会被存储到动态表的DT_NEEDED
条目中,所需要的函数会在动态符号表中以下列属性注册:
类似的,如果一个库要导出函数,就会在动态表中添加一条DT_SONAME
条目,导出的函数会在动态符号表中以下列属性注册:
当逆向分析一个程序时,导入函数名是非常有用的。要防止这种逆向分析,一种做法是将二进制程序和库静态链接起来(译者注:静态链接之后导入表中就不再需要导入函数名,对函数的调用直接通过地址进行)。另一种做法是把某些函数名进行交换,以迷惑逆向工程师。
看下列代码:
这个程序的功能基本上就是接收一个整数,并对这个值进行计算。
pow
和log
函数位于libm.so.6
这个库中。一个有趣的小技巧就是用LIEF把当前的函数名和另一个函数名进行互换。在这篇文章中,我们把它们分别替换为cos
和sin
函数。
首先需要载入可执行文件和动态库:
接下来我们需要改变binary
中两个导入函数的函数名:
最后我们把log
替换为sin
,把pow
替换为cos
,并重建这两个对象:
在这个脚本中,我们在当前目录下构建了一个修改过后的libm
,接下来需要在执行binary.obf
的时候强制让Linux加载器加载我们修改过后的这个库。要实现这个功能,我们需要把LD_LIBRARY_PATH
导出到当前目录:
(译者注:修改过后的动态库中cos对应的地址是pow函数的地址,sin的对应地址是log函数的地址,而修改后的binary
中调用的分别是cos和sin函数。调用的是cos函数,而实际执行的是pow函数的功能,这就会对逆向分析人员造成困扰)
如果不这样做,它就会使用默认的libm
中的sin
和cos
来做hash计算。
这个功能的一个实际应用是在加密库OpenSSL中交换两个符号。比如,EVP_DecryptInit
和EVP_EncryptInit
拥有相同的原型,我们可以交换它们。
原文链接:https://lief.quarkslab.com/doc/latest/tutorials/01_play_with_formats.html
编译:看雪翻译小组 梦野间
校对:看雪翻译小组 lumou
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2018-9-18 21:07
被梦野间编辑
,原因: