0x00 源码分析
很明显的栈溢出
1 | char *pwnme() |
来看看这个usefulFunction函数:
1 | void __noreturn usefulFunction() |
有三个函数:
callme_one ,callme_two,callme_three
callme_one:
1 | int __fastcall callme_one(int a1, int a2, int a3) |
callme_two:
1 | FILE *__fastcall callme_two(int a1, int a2, int a3) |
callme_three:
1 | void __fastcall __noreturn callme_three(int a1, int a2, int a3) |
开始看到usefulFunction
函数时,以为需要调用这个函数,但是你仔细看每一个callme_
函数,你会发现传入的参数必须是 1,2,3
,不然就会显示Incorrect parameters
,然后退出。
那么根据这个usefulFunction函数可以知道这三个函数的调用顺序是callme_one,callme_two,callme_three
思路有了,那么接下来就是调试
0x01 程序调试
gdb动起来,先生成300个随机字符串,用来填充缓冲区:
1 | mask@mask-virtual-machine:~$ gdb ./callme |
然后r运行起来,输入刚才那300个填充字符串
1 | Legend: code, data, rodata, value |
可以看到停在了pwnme函数处,这里有一点需要注意:
PC指针并没有指向类似于0x41414141那样地址,而是停在了vulnerable_function()函数中。这是为什么呢?原因就是我们之前提到过的程序使用的内存地址不能大于0x00007fffffffffff,否则会抛出异常。但是,虽然PC不能跳转到那个地址,我们依然可以通过栈来计算出溢出点。因为ret相当于“pop rip”指令,所以我们只要看一下栈顶的数值就能知道PC跳转的地址了。
这里引用了蒸米师傅一步一步学ROP的文章中的原话,解释的很清楚了,那么我们来看看rsp数值:
1 | gdb-peda$ x/gx $rsp |
然后计算距离溢出点的偏移:
1 | gdb-peda$ x/gx $rsp |
偏移为40
第二步,我们需要找到callme_one,callme_two,callme_three
这三个函数的地址,因为这里是动态链接,那么我们只需要知道他们的.plt地址即可,关于got和.plt的概念请参考这篇文章:
这里我们使用Linux下的objdump来找:
1 | mask@mask-virtual-machine:~$ objdump -d callme | grep plt |
那么这三个函数的地址我们知道了:
1 | callme_one_plt__got = 0x00401850 |
那么第三步,还差一个关键地方,再次引用蒸米师傅的原话
这个函数的参数有三个,所以我们需要一个pop pop pop ret的gadget用来保证栈平衡。这个gadget非常好找,用objdump就可以轻松找到。
那么来找咯:
1 | mask@mask-virtual-machine:~$ objdump -d callme | grep -A 3 pop |
那么地址就取pppr_addr = 0x00401ab0
那么所有的都准备好了,接下来就是编写exp
0x02 exp的编写
使用了pwntools工具
1 | from pwn import * |
运行:
1 | mask@mask-virtual-machine:~/x64Pwn/callme$ vim exp.py |
成功!
0x03 总结
学习了构造函数传参的payload以及GOT和.plt等延迟绑定技术