赤道企鹅的博客 | Eqqie Blog - 2021年2月
type: channel
link: https://eqqie.cn/index.php/2021/02/
description: Binary Security Researcher, IoT/NFV Sec, Static Program Analysis, Fuzzing Test, CTFer
items: [pwnable.tw] printable - 利用exit过程伪造.fini_array到可控内存

[pwnable.tw] printable - 利用exit过程伪造.fini_array到可控内存
type: item
date: 2021-02-10T23:57:00+08:00
description: 分析题目主要逻辑非常短：int __cdecl __noreturn main(int argc, const char **argv, const char **envp) { char s; // [rsp+0h] [rbp-90h] unsigned __int64 v4; // [rsp+88h] [rbp-8h] v4 = __readfsqword(0x28u); init_proc(); // set no buf memset(&s, 0, 0x80uLL); // 0x80栈上缓冲区 printf("Input :", 0LL); close(1); // 关闭输出流 // 需要想办法打开或绕过 read(0, &s, 0x80uLL); printf(&s, &s); // 裸的格式化串 exit(0); // 直接exit0，考虑利用fini_array } 在read之前调用了close(1)来关闭stdout流，然后有一个裸的格式化字符串漏洞。由于stderr没有关闭，可以通过半个字节的爆破将bss段上stdout指针指向和stderr同一个文件结构体，此时再调用printf函数时，就可以将内容从stderr流输出来。在调试过程中发现fini_array内存不可写，不能通过直接写指针来让执行流形成loop。这里是本题最关键的难点。通过查阅资料后发现，在最后exit(0)的执行过程中会出现如下图的一处指令，来调用fini_array[0]处保存的指针：其中r12为&fini_array[0]的值，而rdx固定为0，于是往前找可以找到如下控制r12的指令：r12初始值为&fini_array[0]，然后加上[rbx]的值。而rbx中保存的地址，正是第一次调用printf前，栈上第42个参数的值。于是想到，在格式化字符串利用时写&fini_array[0]与一个可写可控地址（如.bss_start）之间的偏移量到该指针指向的内存。只要提前将要返回的起始地址写到.bss_start上，那么最后在exit时就可以构造出call *.bss_start这样的跳转。修改完stdout并返回read片段后的主要任务就是泄露glibc和栈地址。但是由于栈发生了变动，泄露完后不能直接再次返回。很碰巧的是在第二次printf前，栈上第23个变量正好是储存第二次调用printf时保存函数返回地址的栈地址的指针。于是省去了部分写的麻烦，直接写该地址，这样当printf执行完后就会直接回到read片段，且栈不会发生大的变化。（这是第二个容易卡住的点）最后一步很明显是借助上面的方法去getshell，但是不知道为何，直接写printf返回地址为one_gadget地址时无法成功getshell。于是我想在写返回地址的基础上在payload后面布置一段执行system("/bin/sh");的rop链。然后把返回地址写为add rsp, 0x80; ret;这样的gadget，从而在printf返回时跳过payload，执行提前布置的rop链来getshell。需要注意的是，最后一次写返回地址需要写长达6个字节，于是我拆分成了3次，每次写2字节。为了保证从小到大地写入三个地址片段（格式化字符串基本概念），我在分出每个片段后做了一次排序。 虽然说修改了stdout指针，但是getshell之后直接cat文件会提示文件描述符错误，需要在指令中重定向一下：cat /home/printable/printable_fl4g 1>&2;。符号>左边为文件描述符，右边如果直接使用2会被认为是重定向到一个名为2的文件，所以需要用&修饰，表示这是一个文件描述符。 思路 改写stdout，改写fini_array偏移到bss_start位置； 泄露栈地址，glibc地址，修改printf返回地址； 修改printf返回地址为add rsp, 0x80; ret;gadget，在格式化串payload后面布置好system("/bin/sh"); rop链. EXP#/usr/bin/python3 from pwn import * import time local_dbg = 1 libc = ELF("./libc_64.so.6") elf = ELF("./printable") context.log_level = "debug" # const ret_addr = 0x400925 #ret->read->printf->... bss_addr = 0x601000 fini_array_0 = 0x600DB8 #fini_array[0] fini_array_1 = 0x600DB8+8 #fini_array[1] bs…