main调用puts输出
看vuln
这里应该是个read写入到var_38h位置 共可以写入0x40个(我的Cutter有点问题不显示具体函数名。
var_38一共长度0x38 ,因为上面read读0x40个,所以只可以溢出到rbp的下一个8位,也就是正好覆盖返回地址。
因为溢出长度不够干啥,所以这里考虑栈迁移到bss。
首先溢出覆盖到rbp给个bss的地址,然后返回地址再给个vuln的地址
这么做是为了,使得rbp直接在初次执行到最后的经过leave也就是mov rsp,rbp
pop rbp
,之后rbp pop到我们指定的bss的位置,然后再次执行vuln为了往rbp跳过去的bss的var_38部分写东西,方便再跳一次。
1 | from pwn import * |
执行完初次vuln的leave大约是这个样子,参数本身就是由rbp做定位的,所以也就跟着跑了到了bss,就像这里的var_38(Cutter里反汇编图表中的变量是+rbp的8个字节的,所以给的0x38和ida我记得有点区别,具体的偏移还是要看汇编,这里其实还是rbp-0x30)
可以看到当前的rbp已经飞到指定的bss部分
此时再进入vuln,我们开始再次进行写入,此时读入的就写到了跟着rbp,跑到了bss上的var_38那边此刻所内存的部分了。
因为vuln执行到最后的部分会执行leave所以rsp在这次mov rsp,rbp
pop rbp
会直接飞到rbp的位置也就是bss部分
(下图执行完mov rsp,rbp
)
但是这里有个问题就是我们第二次在read部分写入的rop没法执行,因为rsp在这里而指令在var_38的部分,如果此时正常跑pop rbp
的话rbp就跑飞了,同时rsp也移动不到指令的位置
所以我们要把此时rbp的部分覆盖到var_38当前的地址,使得他在执行完第二次之后可以飞到我们刚刚用read写入到bss的部分,也就是var_38的地址,至于为啥
rbp飞过去的目的是为了让rsp能够过去,所以我们在溢出的8字节再给一个leave,使得rsp再去找rbp
但是这里pop rbp的时候他又会因为我们填入的东西他再跑飞了,我们一会还要用它写入,所以再给个别的bss地址,让她一会再次调用vuln的时候又会在我们此刻指定的另一个地址进行写入,一会这里泄露完libc还要再用到他重复一遍同样的操作。
1 | from pwn import * |
他在执行完read之后,在还没执行leave
ret
之前是这个样子的,为了方便看我把leava
拆解开了
然后开始执行mov rsp,rbp
,执行之后如下,可见rsp去找rbp了
然后pop rbp
rbp当前地址我们给他放的rbp-0x30的地址,所以她pop rbp
就跳转到我们rbp-0x30的位置了,此时leave
执行结束,同时rbp此时所处的就是我们利用read()写入的数据的开始位置。
此时即便你笨如我也可以想到,如果此时要想让rsp移动到我们写入的,想要执行的栈位置,只需要在ret
之后,让溢出的返回地址的8位覆盖为leave
就可以rsp
再一次去找rbp
,等于通过控制rbp
的返回到我们想要去的地址,在pop rbp
后rbp去了那边后,再leave_ret
就可以rsp
移动到rbp
目前所在的位置了,结尾的ret
从而让rip
也移动到rsp
,让其开始正常执行我们写入的部分。
注:(需要注意的是pop rbp
会让rsp+8,ret
也是可以看作pop rip
,可能大火都知道,但是还是提一下,也就是说ret
过去的时候rip不会指向bss的地址,在leave中的pop rbp
的时候就会越过,所以基础还是挺重要的..当时我刚学时候纠结好久,最后拆开来看leave才看懂为啥rsp+8的)。
这里的话可以看到执行完leave中的mov rsp,rbp
后rsp
就过去了,因为写入的栈空间只有0x40个字节,换言之只够我们写8个8字节,并且其中有俩需要分配给一个rbp
跳转以及一个leave
还有一个再一次的vuln
函数调用的
(因为我这一轮执行空间只够泄露libc拿不到shell,所以还需要利用vuln他再来一轮)
留给我们执行的只有8-3=5
个地址空间可以利用,于是我拿它来做libc泄露了,接下来我们利用leave
的pop rbp
,让rbp跳去下一个地址,让他在那边准备等待第二次写入。
接下来的剧情就是获取puts.got的地址泄露,然后再一次执行到vuln,再另一个bss-0x500的地方再一次写入,因为拿到了基地址所以这次可以写入pop rdi
/bin/sh
system
一条龙,然后vuln
再一次执行完他里面最后的leave_ret
,rsp
就又去找rbp
了
嘻嘻这里很简单,简单思考一下是什么呐