PwnCollege - RE - Animations

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