2a1
程序分析
程序的主要功能有3个:
程序首先会给你alarm的地址,用它可以求出libc的基址
程序会讲你指定的地址上的八个字节输出出来
你先制定一个地址,程序申请0x30的堆栈空间,你可以向里面输入东西,然后程序通过30行的*v8=v9将堆地址写到你制定的地址上
利用思路
一开始想伪造vtable,利用IO_overflow来执行gadget,后来发现没发饶过执行IO_overflow的条件。请教了学长之后才有了思路。
程序正常退出时或exit时会执行__run_exit_handlers函数,这个函数里面有东西可以利用。查看glibc的源码,可以看到struct exit_function_list cur = listp;,我们可以通过劫持这个指针,因为这个指针指向的结构体里面有函数指针并且之后的步骤会执行case ef_cxa:cxafct = f->func.cxa.fn;,我们可以利用此来执行system(‘/bin/sh’)获得shell。
1 | void |
看一下这个结构体的结构,我们劫持了cur指针就能够指向我们伪造的exit_function_list,由于可以想任意的地址写上堆地址,我们可以覆盖cur指针为堆地址,将fn设置为system,将arg设置为/bin/sh,然后执行cxafct (f->func.cxa.arg, status);就够获得shell了。
1 | struct exit_function |
接下来我们进行调试,在__run_exit_handlers设置下断点,看看这个函数的执行情况。
参考源码
1 | void |
函数的第二个参数即rsi是&__exit_funcs,则rbp是固定的值即&__exit_funcs,而r13的值是通过rbp取址的,从而如果我们能够改写__exit_funcs的内容则就能偶控制r13。
从这里可以看出r13存储的就是lisp,我们利用程序的在任意地址写上我们申请的堆地址的功能,将堆地址写到&__exit_funcs处,从而就能够劫持cur。
由于我们可以往堆内写任意的内容,我们按照exit_function_list结构体将idx设置为0,将fns[0].flavor设置为4,函数就会跳过去执行一下内容
1 | case ef_cxa: |
我们再设置f->func.cxa.fn和f->func.cxa.arg就能够完成劫持,但是这里的函数地址是要经过一些操作才能获得的,进入gdb我们看看具体是什么
在call之前,函数地址会现经过循环右移0x11,再和fs:[0x30]异或。想要控制调用的函数就知道fs:[0x30]的值。可以利用之前读任意地址的操作获得,现在的问题是fs[0x30]指向哪里?参考这篇回答,在gdb中输入call arch_prctl(0x1003, $rsp - 0x8)然后再 x /gx $rsp - 0x8打印出结果,计算出fs:[0x30]和libc的相对偏移;
根据相对偏移利用之前的任意读读出fs:[0x30]中的内容guard,要想再经过ror和xor操作后得到正确system地址,只需要将system地址先进行xor然后再进行循rol循环左移就行了。
1 | ROL((gadget+libc)^guard,0x11) |
EXP
1 | from pwn import * |