@


前言

关于上一章的ret2text 32位留下了几个问题,今日对以下问题进行补充
问题一:如果没有直接给system('/bin/sh')但给了system函数以及在别的地方给了'/bin/sh'或'sh'怎么办?
问题二:32位如何传参?
问题三:call与ROP 调用的差别

我知道32位会越来越少,但既然要pwn就从头到尾一点点学,打好基础。

下面采用的案例程序来自ctfshow中的pwn入门:pwn39


一、问题一

问:如果没有直接给system('/bin/sh')但给了system函数以及在别的地方给了'/bin/sh'或'sh'怎么办?
答:找到了'/bin/sh'或'sh'地址,给system传参。

二、问题二

问:32位如何传参?
答:通过栈进行传参。

这两个问题都很好回答,因为在执行system函数的时候,首先是找到了system地址,接着去找参数的地址,然后执行即可,下面我们拿一道题目来说明问题。

三、gdb调试答疑

ida分析题目

进入到main函数
在这里插入图片描述
发现有一个ctfshow函数,进去看一下(ctfshow题目一般漏洞点都是在ctfshow函数里)
在这里插入图片描述
发现有一个read函数,估计是栈溢出,下面可以找一下有没有后门函数,发现这里有个hint函数,估计是可以利用的后门函数。
在这里插入图片描述
在这里插入图片描述
此时可以看到这里有‘/bin/sh’和system函数,但‘/bin/sh‘并没有作为system的参数出现,现在我们可以根据一开始分析的结果进行做题了,现在目标很明确,把‘/bin/sh‘通过栈传到system里,作为system的参数即可。现在思路清晰:首先我们找到偏移,再偏移之后跳转到system函数,接着利用栈把’/bin/sh‘传给system函数即可拿到shell,下面开始我们的gdb调试。

gdb调试

首先我们先进入到漏洞函数(ctshow函数),再根据我们上几章中的测试方式计算偏移

在这里插入图片描述目前已经计算出偏移:22,如果不知道计算偏移的方式可以看一下gdb调试以及上一章那里详细的介绍了计算偏移,当然也可以使用ida pro进行计算,不过我还是喜欢动态调试这种方式进行计算。计算出来的这个结果也可以转为16进制,方式:p/x 22 这里我就不转了
第一步已经完成,开始第二步,寻找system地址,如果你们嫌麻烦的话可以利用pwntools中的ELF来寻找system函数,这里先不过多的讲脚本。
用ELF方式找system函数:
在这里插入图片描述
这样就可以通过pwntools工具找到system函数。也可以通过ida pro来找地址。这里我们先采用ELF方式来寻找地址(有伏笔)。
接着我们就可以寻找’/bin/sh‘的地址了,找到这个地址我们的payload就算完成了,这次我选择用ida pro去寻找,因为我们可以在hint函数里直接看到有一个’/bin/sh‘,直接双击就可以看到地址
在这里插入图片描述
当然有的题目不会这么明显,我们在ida pro里按shift+f12来查看

在这里插入图片描述
和我们刚刚找的地址是一模一样的,现在我们的payload就写好了。可以开始写payload了,我建议写脚本的时候尽量开启gdb调试,那样的话你就知道哪里错了。
我写的脚本如下:
在这里插入图片描述
现在执行这个脚本进行调试看看,为了方便我直接跳到ctfshow里ret的那里,先看看ret是否返回到了system地址。
这里可以看到在ctfshow函数执行之后是跳到了system函数
在这里插入图片描述
目前是没有错误的,再继续执行,你走到最后你会发现没通,虽然跳到了system,但没通,下面就要开始第一个重中之重的内容了,根据gdb调试,你会对函数调用会有更深的印象。本来我们理想的状态是system执行’/bin/sh‘,但目前的状态好像不对,那只能说明system没有执行’/bin/sh‘,我们直接看更深层的system函数,看看他到底做了什么,先设置断点,然后c执行,就进入到了libc system
在这里插入图片描述
可以看到汇编并不多,我们一步步来看
在这里插入图片描述
我们先看一下栈上的数据,esp指向了/bin/sh
在这里插入图片描述
我们感觉是对的,因为通过栈进行传参,就应该执行’/bin/sh‘,但我们可以看一下’/bin/sh‘下面的那个地址,现在可以先记录一下,我们继续执行汇编指令,在我们执行完sub esp, 0xc 指令的时候,我们来看一下栈变成了什么样子,你会发现他指向了更小的地址,’/bin/sh‘的地址在下面
在这里插入图片描述

我们继续执行一条汇编指令,查看一下栈的情况在这里插入图片描述
此时可以发现eax里存着’/bin/sh‘下面的那个地址,应该会变得很疑惑,不是用栈传参吗?怎么又出现了eax寄存器?现在要补充一个知识点:虽然 x86 的 CPU 可以做简单的内存对比,但在绝大多数复杂操作(比如检查参数、传递给底层系统调用)时,CPU 是极度排斥直接对着栈操作的。它必须先走到栈里,把参数放到eax 寄存器,然后才能对这个参数进行后续的高级操作。
我们看到这条指令mov eax, dword ptr [esp + 0x10]的时候,就标志着用栈传参了,说明已经找到参数并且传到eax里面了,然后判断test eax,eax看看eax里面是否为空。那么我们的 ‘/bin/sh’去哪了呢?看下面的图,由于我重新关闭了那个程序,导致地址变了,但操作以及内容是没错的,输入这条命令,锁死父进程

set follow-fork-mode parent

我们使用汇编,在ret上一个地址那里设置断点并跳过去
在这里插入图片描述

可以看到栈是这样的 esp还是之前我们刚才的那个参数,接着我们可以看看esp和eip有什么变化
在这里插入图片描述
有没有发现eip那里指向了‘bin/sh’,为什么是到eip那里了呢?不要忘记ret的本质,ret的本质就是pop eip 上一章有讲,现在是不是对函数调用更清晰了,那么我们现在就可以看到问题所在了,system把当参数的 /bin/sh 地址当成了返回地址,把’bin/sh‘下面的地址当成了参数。一切都变得明朗了吧。

总结

1:在 32 位程序里,通过栈溢出来调用任何一个函数,都必须严格遵守下面这个公式:
[执行动作的函数] + [执行完后要跳去的返回地址] + [给这个函数的参数]
2:call 相当于 push 返回地址 + jmp;而 ret 相当于 pop eip。
3:在 32 位系统中,函数寻找参数的规矩是:去读当前 esp+4 的位置

所以我们重新写payload就可以了
在这里插入图片描述
这个payload就可以打通了,只需要在 ‘/bin/sh’前面随便加个返回地址就可以,不一定是0xdeadbeef也可以是别的。如果这个问题没有讲清楚或者是哪里讲错了,欢迎各位师傅私信我讨论此问题。

call调用与ROP 调用的差别

其实这道题也可以不用0xdeadbeef来做出来,因为根据刚才总结的经验,在 32 位系统中,函数寻找参数的规矩是:去读当前 esp+4 的位置。因为在正常 call 调用的时候,底层第一步会自动 push 返回地址;但我们在 ROP 调用的时候,利用的是 ret 指令,并没有自动 push 压栈的动作,所以我们要自己手动写入一个 0xdeadbeef 或者是别的 4 字节来弥补这个栈空间的差异。如果不加 0xdeadbeef或其他,程序就会错把我们写的 ‘/bin/sh’ 地址当成返回地址,那样只会把紧随其后的未知垃圾数据当作参数读进去。
在没有加 0xdeadbeef 的截图中,程序错把我们写的‘/ bin/sh’ 地址当成了返回地址,而把 ‘/bin/sh’ 后面的那个未知垃圾数据(此程序里显示的是 0xf79eae0a 或者 0)当成了参数读了进去。
在这里插入图片描述
加了0xdeadbeef:,根据上面的结论可以发现程序把0xdeadbeef当作返回地址,并且把‘/bin/sh’当作参数了

在这里插入图片描述

我们可以把payload中的system的地址改为call system的地址,并且不加0xdeadbeef

在这里插入图片描述

再根据我们新的payload看一下会有什么变化。
下面是使用call system的变化,变化一:会有一个call system 并且会写好参数

在这里插入图片描述

变化二:在我们si进入到system函数后,我们可以看到esp是这样的:
在这里插入图片描述
他push了system结束后的下一条指令的返回地址,这样就明显的看到差别,我觉得也可以更好的去理解函数调用了。
使用call system函数的payload:
在这里插入图片描述

总结

作为一个刚学 PWN 的新手,目前我也只能根据我自己的机器情况,把遇到的问题和解法记录下来。可能不是每台机器都会遇到这些奇怪的报错,但如果在座的各位师傅也碰到了,希望这篇文章能帮你省掉几个小时的排错时间。

posted on 2026-02-22 14:42  0x4a4  阅读(2)  评论(0)    收藏  举报