patch
转自peda
IDA
IDA Pro是一个非常强大的工具,其中包含了对汇编指令修改的功能。
以国赛华北赛区的半决赛为例,其中有一道PWN2是一个栈溢出,代码是这样的。
很显然,在read这里有一个明显的栈溢出,修复漏洞的方法也和容易,将这个值改小成0x138就好了,下面的write也一样的改法。
这里使用IDA默认的修改插件来改,在Edit-Patch Program目录下,首先切换到IDA View-A这个汇编指令界面,并选中要改的汇编指令行:
选择Assemble/Change byte/Change word都可以,以Assemble为例在Instruction窗口,将mov edx, 1cch改为mov edx, 138h。
此时,切换到类C语言窗口可以看到该行已经被修改为了read(a1, &s, 0x138uLL);
但并没有完,这仅仅修改了IDA对于该文件的数据库,并没有应用到文件中去,同样在Edit-Patch Program目录下,选择Apply patches into file…,将修改写入文件,就完成了一道简单题目的patch。
这种方法完全依靠手动,而且不能修改文件结构,可以供手动修改的位置也很少,一旦出现如UAF等悬垂指针的问题基本就很难解决了,还得依靠其他更有力的方法来解决。
lief
lief是一个开源的跨平台的可执行文件修改工具,链接如下:
1 | https://github.com/lief-project/LIEF |
对外提供了Python、C++、C的接口。
对于Python库安装可以使用pip,如
1 | sudo pip install lief |
对于lief的API和用法就不介绍了,RTFM。
1 | https://lief.quarkslab.com/doc/latest/api/python/index.html |
以下是几种可行的patch方法
增加segment
这个方法的目的是增加一个程序段,在这个程序段中加入一个修复漏洞的程序代码,一般程序会在call某个函数时触发漏洞,一般语句为call 0x8041234,可以劫持这句话的逻辑,改成call我们定义的修复函数。
首先我们的代码程序如下:
1 | #include <stdio.h> |
我们想把第一处printf修改掉,改成我们自己的逻辑,首先需要编译一个包含实现patch函数的静态库,比如:
1 | void myprintf(char *a,int b){ |
如上,将printf改成了write(0,”/bin/sh%d”,0x20),利用注释的gcc命令将其编译。
patch程序的流程是首先将代码段加入到binary程序中,然后修改跳转逻辑,将call printf@plt,改成call myprintf。
lief中提供了add参数可以用于为二进制文件增加段:
1 | binary = lief.parse(binary_name) |
在修改跳转语句部分,由程序的call执行寻址方法是相对寻址的,即call addr = EIP + addr
因此需要计算写入的新函数距离要修改指令的偏移,计算方法如下:
1 | call xxx =(addr of new segment + offset function ) - (addr of order + 5 /*length of call xx*/) |
由于偏移地址是补码表示的,因此在用python计算时需要对结果异或0xffffffff,最终patch计算函数如下:
1 | def patch_call(file,where,end,arch = "amd64"): |
执行之后可以看到patch成功了,
但是一个重大的问题是patch前后文件大小改动很大:
1 | ┌─[p4nda@p4nda-virtual-machine] - [~/Desktop/pwn/patch] - [一 7月 02, 20:36] |
这样在一些线下赛中很容易由于修改过大和被判定为通防或者宕机。
增加library
这是借鉴LD_preload的一种思路,当程序中加载两个库时,在调用某一函数在两个库内同名存在时,是有一定查找顺序的,也就是可以实现,在不修改程序正常代码的前提下,对全部libc函数进行hook。如下例:
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
编译一个动态链接库
1 | //#include "/home/p4nda/linux-4.17.3/lib/syscall.c" |
编译命令在注释中,则每次printf时都会先执行上述库中的函数,达到hook的目的。
优势很明显,可以执行任意libc内函数代码,让编程更容易。
不过缺点也很明显,首先程序变得巨大,并且当不存在这个静态链接库的时候,程序跑不起来… 有些线下赛都是本地check的,比如*网杯,很容易就判断宕机了…
修改程序.eh_frame段
在TSCTF 2018 Final时,我在NeSE战队的binary文件中找到了通防工具,但是程序改动并没有特别大,当时感觉很好奇,在赛后调试了一下,发现他们把通防的shellcode写在了一个叫.eh_frame的段中,这个段会加载到程序中来,并且自身带有可执行权限,在查找这个段用处时,发现该段对程序执行影响不大,故可以将patch代码写在这个段中,再用跳转的方法将程序逻辑劫持到这里来。
可以看到在patch前后,程序大小保持不变。
缺点同样明显,.eh_frame的大小是有限的…
综上,似乎没有比较简洁的通用方法,综合着来用吧….