线下赛patch工具介绍

因为要去参加某比赛的线下赛,所以准备了一下线下patch相关的东西(然而参加了才发现根本没pwn选手什么事,全程在帮web大佬运维,各种删马、各种杀进程)。本文主要参考的是P4nda师傅写的patch相关的文章,在此基础上自己patch了一番。

使用IDA直接patch

这种方式适合于较简单的修改,不能修改文件结构,直接在Edit–>Patch program–>Assemble中进行修改即可:

例如这里存在off-by-null,直接将该指令nop掉即可:

如下图所示:

此时查看反编译的结果可以发现已经没有off-by-null漏洞了,最终还需要将修改的结果保存至文件中:
Edit–>Patch program–>Apply patches to input file

使用LIEF

项目的地址:
https://github.com/lief-project/LIEF

安装如下:

1
2
3
pip install setuptools --upgrade

sudo pip install lief

工具的使用参考官方文档:
https://lief.quarkslab.com/doc/latest/index.html

使用LIEF增加段来patch

程序的源代码如下:

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char** argv) {
printf("/bin/sh%d",102);
puts("let's go\n");
printf("/bin/sh%d",102);
puts("let's gogo\n");
return EXIT_SUCCESS;
}

目标是修改其中的printf函数为我们自己的函数

hook程序中的导入函数

编写hook函数

首先要先编写我们的hook函数,编写hook函数有几个要求:

  • 汇编代码必须是位置独立的(也就是要使用-fPIC或-pie / -fPIE标志编译)
  • 不要使用libc.so等外部库(使用:-nostdlib -nodefaultlibs flags)

根据上面的限制条件,我们编译hook程序时使用的编译指令如下所示:

gcc -nostdlib -nodefaultlibs -fPIC -Wl,-shared hook.c -o hook

我们编写的hook函数my_printf如下:

1
2
3
4
5
6
7
8
9
10
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"
);
}

将hook函数注入到程序并修改got表

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

binary = lief.parse("./vulner")
hook = lief.parse('./hook')
# inject hook program to binary
segment_added = binary.add(hook.segments[0])

# hook got
my_printf = hook.get_symbol("myprintf")
my_printf_addr = segment_added.virtual_address + my_printf.value

binary.patch_pltgot('printf', my_printf_addr)
binary.write('vulner.patched')

运行patch后的程序可以发现patch成功:

hook指定地址的函数调用

使用下面的代码可以完成hook程序中指定地址的call函数调用:

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

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

binary = lief.parse("./vulner")
hook = lief.parse('./hook')
# inject hook program to binary
segment_added = binary.add(hook.segments[0])
hook_fun = hook.get_symbol("myprintf")


dstaddr = segment_added.virtual_address + hook_fun.value
srcaddr = 0x400584

patch_call(binary,srcaddr,dstaddr)

binary.write('vulner.patched')

修改.eh_frame段实现patch

eh_frame段在执行的时候对程序的影响不大,所以可以把hook代码添加到该段中,通过修改函数跳转的方式来执行hook代码

对section的操作参考官方文档-section部分

section对象中的content属性就是该section中的内容,所以要对待patch程序的.eh_frame段进行修改,直接将hook程序中的.text段的内容赋值到.eh_frame段的内容就行。赋值完成之后,在通过与前面一致的方法修改函数跳转地址,使其跳转到.eh_frame段来执行我们的hook代码

具体的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import lief
from pwn import *

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

binary = lief.parse("./vulner")
hook = lief.parse('./hook')

# write hook's .text content to binary's .eh_frame content
sec_ehrame = binary.get_section('.eh_frame')
print sec_ehrame.content
sec_text = hook.get_section('.text')
print sec_text.content
sec_ehrame.content = sec_text.content
print binary.get_section('.eh_frame').content

# hook target call
dstaddr = sec_ehrame.virtual_address
srcaddr = 0x400584

patch_call(binary,srcaddr,dstaddr)

binary.write('vulner.patched')

直接将hook程序中的.text段的content赋值到binary程序中的.eh_frame段的content段得到的效果如下图所示,内容确实是我们的hook函数:

修改指定的函数调用,使其跳转到我们修改后的.eh_frame段来执行,效果如下图所示:

参考链接

http://p4nda.top/2018/07/02/patch-in-pwn/

https://lief.quarkslab.com/doc/latest/index.html

https://blog.csdn.net/gwzz1228/article/details/16862911