ret2libc3类型题思路总结
本文最后更新于:2023年3月10日 晚上
注:作者会常用io声明为process类的类名,elf声明为ELF文件对应的ELF类的类名,libc声明为libc文件对应的ELF类的类名。
ret2libc3
题目来源:CTF-wiki
题目环境:ELF i386 未知libc版本
本题保护如下:
1 |
|
本题可能是大多数人第一次接触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 |
|
最终再次构造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地址为