哎~就这题我一看名字我就知道这个是栈迁移,然后ida一看果然是栈迁移
一个小白能有什么坏心思呢,只是想做题提升一下罢了 哎~
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊(来自一个小白的无能狂吼)
那就先看看这个程序吧
32位的,没开别的东西
直接看ida有一个vuln函数。
int vuln()
{
int buf[11]; // [esp+8h] [ebp-30h] BYREF
memset(buf, 0, 40);
read(0, buf, 0x38u);
printf("%s", (const char *)buf);
read(0, buf, 0x38u);
return printf("%s", (const char *)buf);
}
分析一下,
1. read函数输入buf,可以溢出8个字节,然后printf函数输出buf
2. 再一次read函数输入buf,然后再输出buf
就没了。。。。
那我们应该怎么办
思路
由于题里没有'/bin/sh',所以我们就把/bin/sh写入栈里,然后通过地址去调用/bin/sh
由于可以溢出的字节太少了,所以我们就要就要换一个地方去当作我们的栈,其实按照我的理解啊,有esp的地方才是栈,所以我们要把esp的位置改以一下。
所以我们可以以ebp的地址作为基准(因为这个地址是可以获得的),然后我们可以通过ebp的地址找到buf在栈上的地址,那么我们就把栈迁移到这个位置,因为这个地方够大而且也可以容易找到。
那么我们来实际操作一下,来找ebp_addr, buf_addr
ebp
在第一次输入输出的时候,ida显示buf距离栈底是0x30字节。那我们把buf的0x30字节覆盖之后的下一个位置就是ebp(这个应该都知道吧),然后printf(“%s")输出,会把buf的整体都输出,一直输出到'\0',但是ebp是有值的,不是'\0',所以会把ebp连带着一起输出,那我们就借着这个输出把ebp输出就好了,所以我们先用'a' 填充0x2f 个字节 (距离0x30还剩一个字节),然后再输入'b'(弄个和之前输入不一样的), 因为我们printf会把0x30个数据都会输出,然后才会输出ebp,那我们先接收0x30个数据,(用b作为分割),就会方便很多
payload = b'a' * 0x2f + b'b'
io.send(payload)
io.recvuntil("b")
ebp_addr = u32(io.recv(4))
buf_addr
buf的地址咱们用gdb看
咱们下断点到read函数那里 b *0x08048545
然后 r 运行然后 n 下一步
输入 aaaaaaaa
然后我们看栈 stack 30
在这里我们要注意一下,buf的值就是框框的地址,但是ebp的值就需要注意,ebp那一行有俩地址,其中左边第一个是ebp的位置,框框里的是我们之前泄露的地址,所以我们要使用泄露的地址去相减,所以是0xffffd108 - 0xffffd0c8 = 0x40
所以buf_addr = ebp_addr - 0x40
所以现在我们就知道了ebp_addr, buf_addr。这些都是准备工作
然后我们还需要寻找一个汇编指令 ” leave ret “
ROPgadget --binary pwn --only "leave|ret"
找到leave;ret的地址,那这个指令有什么用呢?
leave:分两步
1. mov esp, ebp //把ebp的值给esp,,也就是让esp指向ebp的位置
2. pop ebp //把esp指向的内容给ebp
ret :就一步
pop eip //把esp指向的内容给eip,让程序执行
我们先构造payload
payload = (b'aaaa' + p32(system_addr) + p32(1) + p32(buf_addr + 0x10) + b'/bin/sh')
payload = payload.ljust(0x30, b'\x00') + p32(buf_addr) + p32(leave_ret)
然后我们看栈上
这个就应该是发送payload之后栈上的内容,然后我们来还原一下这个payload是如何运行的
payload是在当前函数运行结束之后才会执行,但是看vuln函数的汇编代码能看到最后有一个leave retn.
所以函数本身的leave ret加上我们构造的leave ret,一共有两组leave ret。
leave的第一步之后就是把esp挪到ebp的位置
leave的第二步过后,是pop ebp,就是把esp指向的值给ebp,并且esp会向上移动一位
到现在为止,ebp的位置就移动了移动到了buf的位置,函数本身的leave指令结束了,开始进行函数本身的ret指令。 然后esp到了我们payload里构造的leave ret的位置
ret:pop rip,把esp指向的内容给eip,然后esp向上一个。
然后eip执行我们payload的leave ret,
leave第一步,把esp移动到ebp的位置
leave第二步:把esp指向的东西给ebp,然后esp向上一位
就变成了这样,因为之前esp指向的位置的值是aaaa,所以执行pop ebp的时候,aaaa给了ebp,所以ebp现在的位置就在0x0000aaaa那里,我也不知道是哪里,爱在哪在哪。现在已经用不上了
,因为我们的目的已经达到了,有esp的地方就是栈,ebp管他干嘛,现在我们已经成功的栈迁移了。所以到现在leave就结束了,
接下来就是ret:pop eip 把esp的内容给eip去执行,自己向上一格
现在执行eip,system函数就运行了,然后就是32位程序函数system寻找参数,32位的程序寻找参数是要隔一位的
参数就是buf_addr + 0x10的位置的值,那么这个位置是什么呢,buf_addr + 0的位置是buf本身
栈上每一个格子是4字节,那么buf_addr + 0x10就是在buf开始的第5个位置
所以buf_addr + 0x10刚好是/bin/sh的位置,那么system函数调用就是/bin/sh。
所以啊,就组成了system("/bin/sh")直接提权
听懂掌声!!!!!!!!!!!!!!!!
这道题就完事了,好像基本大概可能也许栈迁移都是这个思路,可能我写的有一点乱,各位师傅凑活凑活看哈,如果有哪里写的不对或者不足,欢迎指正
这个题在 https://www.polarctf.com/#/page/challengesPolarD&N CTF靶场是一个CTF综合练习平台,题目难易结合,简题居多,适合CTF初学者。靶场所有题目均为原创,全部免费使用。https://www.polarctf.com/#/page/challenges 的pwn区,想要会栈迁移的话还是自己再尝试一下吧,反正我是理解了好久
加油哦~~~~