对于32位的elf来说,pwntools有现成的函数fmt_str可以使用,但是对于64位的elf,就需要自己手动构建。
程序分析
通过对main函数的逆向,可以很直接地找到fmt地洞:
然后查看程序地保护机制:
发现程序开启了pie,所以在gdb动态调试的时候,会稍微麻烦一点。
开启gdb动态调试之前,先要找到函数下断点的地方,在IDA中鼠标光标选中
1 | print(format); |
注意不是双击哦!然后按tab,之后会出现graph界面:
然后按空格,即可跳转至以下界面:
左边红框中地即为函数地偏移,先记下。
GDB动态调试
打开gdb,进行动态调试:
先让程序跑起来,但是注意到这里先不要输入,直接ctrl+c打断程序运行:
然后输入n,这时候程序会让你输入,这个时候输入一串字符,回车。
发现了偏移为0x883的函数,直接n单步调试至改函数:
输入命令:
1 | stack 30 |
查看栈中的值:
发现可以通过格式化字符串获取到__libc_start_main的值,使用fmtarg获取格式化字符串的位置:
继续观察stack中的值,发现了一条ebp链:
使用fmtarg查找出这两处的偏移:
由于程序开启了Full RELRO保护,所以不能修改got表的值,这边选择使用one_gadget去完成getshell。
准备工作
泄露libc基址
首先通过泄露__libc_start_main函数的地址获取libc的基址:
1 | libc_base = __libc_start_main - libc.sym["__libc_start_main"] |
但是要注意,泄露出来的地址还得减去240:
泄露return地址
泄露了libc的基址之后,进一步就是泄露出函数的return地址,这边可以先要确定函数return地址的偏移:
通过计算这两个地址的差值可得到偏移:
1 | offset = 0x7fffffffdd98 - 0x7fffffffdcb8 = 224 = 0xe0 |
获取one_gadget
攻击思路
总体来说,攻击需要将return地址改成onegadget的地址。
这边把地址拆成了两份,因为每个地址只有后面的八位不同,所以以四位为一份。
第一步将ebp链指向return地址
1 | payload = "%" + str(low_retn) + "c%9$hnstep1" |
动态结果:
首先vmmap查看函数起始地址
然后加上0x883的偏移,下断点:
输入c继续运行至断点处:
发现已经修改成功。
第二步将onegadget的第一部分写入
1 | payload = "%" + str(low_onegadget) + "c%35$hnstep2" |
动态结果:
已经修改成功。这一步的修改是为了将返回地址指向one_gadget
第三步将更改one_gadget的第二部分写入
1 | hign_retn = (retn_addr + 2)&0xffff |
这一步需要重点讲解以下,根据stack中的显示,很容易将其理解为是0x7ffd6388f448这个地址指向了0x7f71c98ad226这个值,但是其实不然,
其实0x7ffd6388f448这个地址是指向0x26这个值(linux中数据存储为小端序,这里不展开讲)。更直观可以使用命令x/b 0x7ffd6388f448查看0x7ffd6388f448指向的单个字节(由于重新运行了程序,地址不一样,但是本质是一样的):
如果理解了这一点,也就一定可以理解为什么payload中要讲return的地址加二,目的是为了指向高字节,修改one_gadget的高字节:
第四步调用one_gadget完成getshell
1 | payload = "%" + str(hign_onegadget) + "c%35$hnstep4" |
动态结果:
修改成功。
最后输入61happy跳出循环,到达return地址,完成getshell:
最后附上完整脚本,完整脚本稍作调整:
1 | # coding=utf-8 |