如何通过.fini_array劫持实现PWN疑问?

摘要:1. .fini_array劫持 上周打了一下金盾杯,算是第一次做非栈上格式化字符串的题,之前只在ctfwiki上看过一点。边学边做,比赛最后一个小时做出来了,爽了。 以金盾杯的题为例 1.1 2023金盾杯 sign_format 题目介
1. .fini_array劫持 上周打了一下金盾杯,算是第一次做非栈上格式化字符串的题,之前只在ctfwiki上看过一点。边学边做,比赛最后一个小时做出来了,爽了。 以金盾杯的题为例 1.1 2023金盾杯 sign_format 题目介绍:一道非栈上的格式化字符串,通过修改dl_fini数组里的偏移值,使函数在退出时执行我们写在bss段上的shellcode。 题目保护分析:没开PIE,其他防护都开了。其次开启了沙箱,需要ORW 题目主要流程: __int64 __fastcall main(int a1, char **a2, char **a3) { sub_40135D(); puts("Welcome here!"); puts("It's a simple sign-in question."); puts("Let's start!"); close(1); read(0, format, 0x100uLL); printf(format); return 0LL; } 有一个很长的输入,之后是格式化字符串漏洞,但是关闭了标准输出流。 其中format保存在bss段上,所以不能修改返回地址了。 格式化字符串%n解析的是地址,当格式化字符串不写在栈上,我们无法通过访问写在栈上的地址来实现任意地址写。而且不能控制返回地址了。 考虑使用.fini_array劫持 1.2 劫持.fini_array 实现功能:在执行exit函数的时候,执行任意地址上的程序 利用条件:需要修改一个在ld.so段上的值。 猜测这个值会在进行动态链接的时候过程中残留在栈上(存疑) 1.2.1 原理分析 下面是程序执行流程图: 根据上图流程,在函数退出执行的时候会调用exit函数,如果我们控制exit函数的finiarray,可以实现任意代码执行,下面我们具体分析一下finiarray这个参数是什么: 1.2.2 dl_fini函数 exit函数执行的时候会调用dl_fini函数。 本来l->l_addr为0,而l->l_info[DT_FINI_ARRAY]->d_un.d_ptr指针指向程序中的fini_array段的地址,也就是l->l_info[DT_FINI_ARRAY]->d_un.d_ptr的值为0x0000000000403D98 如果我们能控制linkmap->l_addr指针,就可以将程序偏移到我们写的位置,执行shellcode。 需要注意这里存的是一个程序地址:0x401200,所以我们伪造l_addr的时候将偏移后的值改为shellcode的地址。 那么如何通过格式化字符串控制l_addr呢?这个就需要用到在栈上留存的一个ld.so上的指针了 1.3 gdb动态分析利用过程 首先,在main函数执行完毕之后会跳转到exit函数执行: 然后我们步进到exit函数中: 可见exit函数会调用一个叫__run_exit_handlers的函数 继续步进,直到这个函数调用了__dl_fini函数,然后dl_fini函数会执行call rax地址: 具体查看rax地址是0x40406b,指向0x404073,其实这里已经是被我们修改了。原本rax应该是0x0403D98 -> 0x401200,原本是要去执行这个代码的: dl_fini函数: 当我们修改了l_addr之后,array[i]就可以被控制了。 而我们只需要计算0x40406B - 0x403D98 = 723,就知道我们应该把l_addr改成多少了。那么我们怎么改这个值呢? 1.4 修改ElfW中l->l_addr劫持fini_array执行 前面我们说过了,在栈上有一个ld.so的地址残留,猜测是在动态链接的过程中残留在栈上的,对应这道题: 就是那个末尾是0x2e0的粉色的地址。里面存的值0x2d3就是723,这里是执行完格式化字符串漏洞,已经被我们修改过了。原本里面存的应该是0,对应的就是l->l_addr = 0 具体在dl_fini函数中: 执行 add rax,qword ptr [r15] ,这个r15此时保存的就是栈上ld.so保存的那个数据,那么只要利用格式化字符串漏洞修改这个ld.so地址中存的值,就可以控制让rax中保存的值从0x401200+0x0 改为访问我们希望执行的shellcode的地址。
阅读全文