pwn中有这样一类题:程序中存在对system函数的调用(括号里的参数不是/bin/sh),并且程序中存在/bin/sh字符串。这种类型的题是ret2system类。这时候,我们需要通过一些手段来实现栈溢出,来执行自己拼凑出的system("/bin/sh");
先讲一下大概思路吧:
从程序运行角度,我们想要的/bin/sh字符串作为参数,在原程序给system函数传参之前,把函数参数替换成/bin/sh。
那么我们便有了构造payload的思路:
程序运行角度,我们先替换掉函数参数然后运行函数;
栈角度,我们按照上面的程序运行角度的思路,通过栈溢出构造一条栈,就按照我们给程序先换参后执行的顺序,先压函数地址,再压binsh字符串的地址,最后压pop rdi指令的地址;这样,在栈一点点往外执行时,最先执行的是cpu会运行的pop rdi指令,这条指令操作栈中在它下面的数据(我们会在这个地方插入binsh地址),这样存有函数参数的寄存器rdi就被我们换成了binsh字符串的地址,最后我们执行system函数,就执行的是system("/bin/sh")。
大概的构造公式如下:payload=b'A'*n+p64(pop_rdi_addr)+p64(binsh_addr)+p64(system_addr);
n为缓冲区的大小(记得加8替换掉栈里上面函数的栈底指针!!!);
pop_rdi_addr为我们可以通过ROPgadget搜索查看的,原程序汇编代码中就有的,poprdi指令的地址,我们拿它插入缓冲区执行,当作我们自己的指令;
binsh_addr是程序中存在的,特殊字符串binsh的地址。
system_addr没什么好说的,就是system函数的地址。
我们用bugku的easy int举例,看看这类题是怎么做的:
拿到这样一道题,我们还是依旧先把pwn文件用ida打开,看看它内部的构造是什么样的。


可以看出,main函数的主逻辑是让你输入一个数,如果小于零就进入if判断语句;然后取这个数的相反数,如果这个相反数依然小于零,那么就执行vuln函数。然后我们观察到vuln函数中存在栈溢出漏洞,我们就可以通过vuln函数的栈溢出,对system函数的参数进行修改。
大概有了思路:
先通过整数溢出到vuin函数,然后通过vuin函数中的system指令和binsh字符串地址,实现system(binsh)。
下面我们来一步步实现脚本编写;
先来看main函数:

我们知道64位系统存储int类型整数在内存中占四个字节,那么去掉符号位,最大正整数就是二的三十一次方减一,即为2147483647;想让程序发生整数溢出,只需要比这个数字大即可。
我们会在python脚本中写如下代码,来实现对main函数的交互:

在给目标程序sendline一个这样的整数之后,程序就会按照我们的预期进入vuln函数。
然后,在进入vuln函数之后,就可以按照我们前面所讲的公式进行payload链的构造。
首先我们在控制台内通过ROPgadget指令查询原程序中pop rdi的指令;

可以看到,原程序中pop rdi;ret指令的地址是0x401343;
我们再来看binsh字符串的地址:
在ida中按下shift+f12,可以看到binsh字符串。

这个页面右分栏的左侧就有着我们想要的binsh字符串地址。没有显示出来,我们可以复制一下:
binsh字符串的地址是0x403500。
最后我们来找_system函数的地址;

可以看到_system函数的地址为0x401090;
你或许会有疑问,两个红色区域内都有system函数啊,为什么要用上面的而不是下面的?


所以我们用上面_system函数的地址。
通过上面三个地址,以及vuln函数内为输入分配栈空间的大小,我们就可以构造出如下payload:
payload = b'A'*108 + p64(pop_rdi)+p64(binsh_addr)+p64(system_addr)
值得注意的是,在这一题中,binsh_addr后面需要加一个p64(ret指令)用来平衡栈。由于我们程序在调用函数前,栈指针(RSP)必须是 16 的倍数,否则程序会崩溃;所以我们用一个额外的ret指令使栈顶指针rsp上移八个字节,带上前面pop rdi时ret使栈顶指针上移的八个字节,加起来就是十六个字节,这样就对齐了,可以正常调用函数。
同样使用ROPgadget找一个纯ret地址,不再赘述。

脚本如上图,运行可得flag;

flag{913ab371b1c2b2cc2375500aad4e0407}



