国赛半决赛的时候需要fix,因为没有做太多准备,第一天直接用的ida,第二天用的keypatch插件,但是效果都不是太好,感觉很水,所以单独学习一下

Tools

keypatch

说到patch容易看到的一款ida的插件了,用法倒是很简单,
https://github.com/keystone-engine/keypatch 下载完毕后放到ida的插件目录下重启ida就ok了

lief

刚发现的一款神器,可以对elf,pe等多种格式的文件进行修改
python 使用直接pip安装即可

Method

这里主要使用lief

直接修改库的函数

如果程序使用了system来getshell,如果是已经动态链接的话,实际上是可以将它给改为其他函数的

测试

1
2
3
4
5
6
7
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv) {
system("/bin/sh");
return EXIT_SUCCESS;
}

如果是正常的程序,运行就可以直接拿到shell

我们编写一份patch.py,就是简单的将system换为puts

1
2
3
4
5
6
import lief

bin = lief.parse('main')
system_off = filter(lambda e:e.name == "system",bin.imported_symbols)[0]
system_off.name = "puts"
bin.write("main_patch")

效果展示:

1
2
3
4
5
6
7
8
9
sirius@ubuntu:~/tikool/test/test2$ ./main
$ ls
main main.c main_patch patch.py
$
sirius@ubuntu:~/tikool/test/test2$ python patch.py
done
sirius@ubuntu:~/tikool/test/test2$ ./main_patch
/bin/sh
sirius@ubuntu:~/tikool/test/test2$

修改导入库的函数

这里就跟着lief的官方文档来进行实验

主程序,会调用exp函数求e的n次幂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//gcc main.c -o main -lm
#include <stdio.h>
#include <stdlib.h>
#include <math.h>


int main(int argc, char **argv) {
if (argc != 2) {
printf("Usage: %s <a> \n", argv[0]);
exit(-1);
}
double a = atoi(argv[1]);
printf("exp(%lf) = %lf\n", a, exp(a));
return 0;
}

因为该函数是在libm.so.6中,我们将其复制到当前目录,编写hook,生成库

这里hook函数用来计算平方值。

1
2
3
4
//gcc -Os -nostdlib -nodefaultlibs -fPIC -Wl,-shared hook.c -o hook
double hook(double x){
return x*x;
}

编写patch.py,这里是在libm中新添加了一个段来添加后面新加的exp函数,最后将其写入libm.so.6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import lief

main = lief.parse('main')
libm = lief.parse('libm.so.6')
hook = lief.parse('hook')

segment_add = libm.add(hook.segments[0])
print "hook inserted at VA: 0x{:06x}".format(segment_add.virtual_address)

exp_symbol = libm.get_symbol("exp")
hook_symbol = hook.get_symbol("hook")
exp_symbol.value = segment_add.virtual_address + hook_symbol.value

libm.write("libm.so.6")

查看效果,因为默认使用的是系统库,所以正常,优先使用当前目录的库时,原来的exp变为平方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sirius@ubuntu:~/tikool/test/test3$ ldd main
linux-vdso.so.1 => (0x00007ffff7ffa000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007ffff7ace000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff7704000)
/lib64/ld-linux-x86-64.so.2 (0x00007ffff7dd7000)
sirius@ubuntu:~/tikool/test/test3$ ./main 2
expl(2.000000) = 7.389056
sirius@ubuntu:~/tikool/test/test3$ LD_LIBRARY_PATH=. ldd main
linux-vdso.so.1 => (0x00007ffff7ffa000)
libm.so.6 => ./libm.so.6 (0x00007fffb7cbb000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fffb78f1000)
/lib64/ld-linux-x86-64.so.2 (0x00007ffff7dd7000)
sirius@ubuntu:~/tikool/test/test3$ LD_LIBRARY_PATH=. ./main 2
expl(2.000000) = 4.000000

特定地址函数patch

例如UAF漏洞经常会结合其他漏洞一块存在,如果可以对free进行修补则可以在一定程度上对其进行维护

这里使用别的函数进行演示

主程序,就是两次输出

1
2
3
4
5
6
7
8
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char** argv) {
int a=2;
printf("%d\n",a);
printf("%d\n",a);
return EXIT_SUCCESS;
}

目标是将第一个printf修改成我们自己写的比如说write函数,将其使用汇编进行编写为hook

1
2
3
4
5
6
7
8
9
10
//gcc -Os -nostdlib -nodefaultlibs -fPIC -Wl,-shared hook.c -o hook
void myprintf(char *a,int b){
asm(
"mov %rdi,%rsi\n"
"mov $0,%rdi\n"
"mov $0x20,%rdx\n"
"mov $0x1,%rax\n"
"syscall\n"
);
}

反汇编查看第一次printf的调用地址

1
2
3
4
5
6
7
8
9
40053f:	89 c6                	mov    esi,eax
400541: bf f4 05 40 00 mov edi,0x4005f4
400546: b8 00 00 00 00 mov eax,0x0
40054b: e8 b0 fe ff ff call 400400 <printf@plt>
400550: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
400553: 89 c6 mov esi,eax
400555: bf f4 05 40 00 mov edi,0x4005f4
40055a: b8 00 00 00 00 mov eax,0x0
40055f: e8 9c fe ff ff call 400400 <printf@plt>

编写patch.py 这里是先将myprintf添加到main程序中,再修改函数调用部分为我们的myprintf_addr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
import lief

def patch_call(file,where,end,arch='amd64'):
length = p32((end-(where+5))&0xffffffff)
order = '\xe8'+length
print disasm(order,arch=arch)
file.patch_address(where,[ord(i) for i in order])
file.write("main_patch")

binary = lief.parse('main')
hook = lief.parse('hook')
segment_add = binary.add(hook.segments[0])

myprintf = hook.get_symbol("myprintf")
myprintf_addr = myprintf.value + segment_add.virtual_address

patch_addr = 0x400584
patch_call(binary,patch_addr,myprintf_addr)
binary.write("main_patch")

测试

1
2
3
4
5
6
7
8
9
sirius@ubuntu:~/tikool/test/test1$ ./main
2
2
sirius@ubuntu:~/tikool/test/test1$ python patch.py
0x802000
0: e8 8a 1d 40 00 call 0x401d8f
sirius@ubuntu:~/tikool/test/test1$ ./main_patch
%d
;4�����8���P2

效果很明显,但是看不出来第二个printf是否被影响了,查看反汇编确定修改成功

1
2
3
4
5
6
7
8
9
40053f:	89 c6                	mov    esi,eax
400541: bf f4 05 40 00 mov edi,0x4005f4
400546: b8 00 00 00 00 mov eax,0x0
40054b: e8 8a 1d 40 00 call 8022da <_end+0x20129a>
400550: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
400553: 89 c6 mov esi,eax
400555: bf f4 05 40 00 mov edi,0x4005f4
40055a: b8 00 00 00 00 mov eax,0x0
40055f: e8 9c fe ff ff call 400400 <printf@plt>

直接修改got表

这里我还不太确定其内部实际上与第一种是否相同

仍然使用第三次的程序,直接上patch.py

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
import lief

binary = lief.parse('main')
hook = lief.parse('hook')
segment_add = binary.add(hook.segments[0])

myprintf = hook.get_symbol("myprintf")
myprintf_addr = myprintf.value + segment_add.virtual_address
binary.patch_pltgot('printf',myprintf_addr)

binary.write("main_patch")

这里就是对printf的调用修改成了自定义的myprintf

测试

1
2
3
4
sirius@ubuntu:~/tikool/test/test1$ ./main_patch 
%d
;4�����8���P%d
;4�����8���P

查看反汇编发现与修改前一致,应该是直接对got表进行了修改

Notice

这个lief比较好用,就写了个小工具来方便patch,现在只有一个简单点指定地址长度的nop与针对UAF漏洞的free函数的patch2
如果需要的话看这里https://github.com/siriuswhiter/ELF-Patcher

需要提醒的是UAF的patch2是不能直接将其patch完成的,因为函数调用的姿势经常不尽相同,需要根据call free之前参数的传递方式来进行修改判断是否需要迁移代码。
如果运气比较好像这样

1
2
3
4
5
6
.text:0000000000000E64                 lea     rdx, ds:0[rax*8]
.text:0000000000000E6C lea rax, unk_202060
.text:0000000000000E73 mov rax, [rdx+rax]
.text:0000000000000E77 mov rax, [rax]
.text:0000000000000E7A mov rdi, rax ; ptr
.text:0000000000000E7D call _free

就完全不需要迁移,将0xE77 nop 掉就可以将指向ptr的指针传参进去了

而像下面这种,就需要进行部分迁移了

1
2
3
4
5
6
.text:000000000040094A                 push    rbp
.text:000000000040094B mov rbp, rsp
.text:000000000040094E mov rax, cs:buf
.text:0000000000400955 mov rdi, rax ; ptr
.text:0000000000400958 call _free
.text:000000000040095D lea rdi, aDone ; "Done!"

在使用时选择migration,指定起始地址为0x400955,结束地址为0x40095d,就可以将这两行进行迁移,
之后在IDA中将 mov rax, cs:buf 改为 lea rax, cs:buf 取地址即可

ps: 然后最后在比赛的时候完全没用着,14k的文件添加一个free的patch后会到达25k,然后因为过大而判定失败……,所以像国赛这样的patch可能就与LIEF无缘了