关于"栈迁移"的介绍,网上已经有很多文章,个人觉得这篇写的比较详细:a13K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2j5%4y4V1L8W2)9J5k6h3&6W2N6q4)9J5c8Y4q4I4i4K6g2X3x3K6R3I4y4e0b7^5x3U0m8Q4x3V1k6S2M7Y4c8A6j5$3I4W2i4K6u0r3k6r3g2@1j5h3W2D9M7#2)9J5c8U0p5H3y4U0x3K6x3o6t1K6z5l9`.`.
不过,我在看链接文章时,还是遇到一些疑惑(即以下黑休+斜体部分,也是本篇帖子要在后半部分统一解释的内容)。
漏洞程序:
1. 程序启动后,等待用户的输入数据;
2. 脚本程序构造payload_1并输入,利用main()函数的栈溢出漏洞,将ebp0指向的内容、返回地址,分别覆盖为bbs+0x500、read_plt;
3. main()函数接收到输入数据,会继续执行到自己尾部的leave、ret指令;
read()函数将stack_1中的0、bss+0x500、0x100作为参数,即read(0, bss+0x500, 0x100),再次等待用户的输入数据(fd参数为0);
4. 脚本程序构造payload_2并输入,由read()函数写到bss+0x500(即ebp1位置);
5. read()函数接收到输入数据后,会继续执行到自己尾部的ret指令,"返回"到esp(当前为eps1)指向的leave_ret处执行;
puts()函数将stack_2中的puts_got作为参数,即puts(puts_got),打印出puts_got所指位置中的内容(即puts()函数的加载地址),然后根据固定偏移,脚本程序就可以推算出,后继要使用的system()函数加载地址、"/bin/sh"字符串地址(这个过程需要理解动态链接的重定位原理:https://bbs.pediy.com/thread-246373.htm);
6. puts()函数继续执行到自己尾部的ret指令,返回到esp(当前为esp2)指向的pop_ebx_ret处执行;
read()函数将stack_2中的0、bss+0x400、0x100作为参数,即read(0, bss+0x400, 0x100),再次等待用户的输入数据(fd参数为0);
7. 脚本构造payload_3并输入,由read()函数写到bss+0x400(即ebp2位置);
8. read()函数接收到输入数据后,会继续执行到自己尾部的ret指令,"返回"到esp(当前为eps4)指向的leave_ret处执行;
最终,system()函数将stack_3中的binsh_addr作为参数,即system("/bin/sh"),拿到shell。
先把注意力集中到第一次执行read()函数的过程:
1. read()函数执行时,为什么会将stack_1中的0、bbs+0x500、0x100作为参数?
2. read()函数执行完,为什么会返回到esp1指向的代码地址?
比如:
反汇编结果:
[培训]科锐逆向工程师培训第53期2025年7月8日开班!