Hello Navi

Tech, Security & Personal Notes

The Patch Directive

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
struct cimg_header
{
char magic_number[4];
uint16_t version;
uint8_t width;
uint8_t height;
uint32_t remaining_directives;
} __attribute__((packed));

typedef struct
{
uint8_t ascii;
} pixel_bw_t;
#define COLOR_PIXEL_FMT "\x1b[38;2;%03d;%03d;%03dm%c\x1b[0m"
typedef struct
{
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t ascii;
} pixel_color_t;
typedef pixel_color_t pixel_t;

typedef struct
{
union
{
char data[24];
struct term_str_st
{
char color_set[7]; // \x1b[38;2;
char r[3]; // 255
char s1; // ;
char g[3]; // 255
char s2; // ;
char b[3]; // 255
char m; // m
char c; // X
char color_reset[4]; // \x1b[0m
} str;
};
} term_pixel_t;

struct cimg
{
struct cimg_header header;
unsigned num_pixels;
term_pixel_t *framebuffer;
};

#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 8564:
handle_8564(&cimg);
break;
case 45626:
handle_45626(&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 > 1340) 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
            ;-- handle_45626:
0x0040186c f30f1efa endbr64
0x00401870 4157 push r15
0x00401872 4183c8ff or r8d, 0xffffffff ; -1
0x00401876 ba01000000 mov edx, 1
0x0040187b 488d0df108.. lea rcx, str.ERROR:_Failed_to_read_base_x_ ; 0x402173 ; "ERROR: Failed to read &base_x!"
0x00401882 4156 push r14
0x00401884 4155 push r13
0x00401886 4154 push r12
0x00401888 4989fc mov r12, rdi
0x0040188b 31ff xor edi, edi
0x0040188d 55 push rbp
0x0040188e 53 push rbx
0x0040188f 4883ec38 sub rsp, 0x38
0x00401893 64488b0425.. mov rax, qword fs:[0x28]
0x0040189c 4889442428 mov qword [rsp + 0x28], rax
0x004018a1 31c0 xor eax, eax
0x004018a3 488d74240d lea rsi, [rsp + 0xd]
0x004018a8 e8defdffff call sym.read_exact ;[3]
0x004018ad 4183c8ff or r8d, 0xffffffff ; -1
0x004018b1 31ff xor edi, edi
0x004018b3 488d74240e lea rsi, [rsp + 0xe]
0x004018b8 488d0dd308.. lea rcx, str.ERROR:_Failed_to_read_base_y_ ; 0x402192 ; "ERROR: Failed to read &base_y!"
0x004018bf ba01000000 mov edx, 1
0x004018c4 e8c2fdffff call sym.read_exact ;[3]
0x004018c9 4183c8ff or r8d, 0xffffffff ; -1
0x004018cd 31ff xor edi, edi
0x004018cf 488d74240b lea rsi, [rsp + 0xb]
0x004018d4 488d0dd608.. lea rcx, str.ERROR:_Failed_to_read_width_ ; 0x4021b1 ; "ERROR: Failed to read &width!"
0x004018db ba01000000 mov edx, 1
0x004018e0 e8a6fdffff call sym.read_exact ;[3]
0x004018e5 31ff xor edi, edi
0x004018e7 4183c8ff or r8d, 0xffffffff ; -1
0x004018eb ba01000000 mov edx, 1
0x004018f0 488d74240c lea rsi, [rsp + 0xc]
0x004018f5 488d0dd308.. lea rcx, str.ERROR:_Failed_to_read_height_ ; 0x4021cf ; "ERROR: Failed to read &height!"
0x004018fc e88afdffff call sym.read_exact ;[3]
0x00401901 0fb65c240b movzx ebx, byte [rsp + 0xb]
0x00401906 0fb654240c movzx edx, byte [rsp + 0xc]
0x0040190b 0fafda imul ebx, edx
0x0040190e 4863db movsxd rbx, ebx
0x00401911 48c1e302 shl rbx, 2
0x00401915 4889df mov rdi, rbx
0x00401918 e8e3f8ffff call sym.imp.malloc ;[4]
0x0040191d 4885c0 test rax, rax
┌─< 0x00401920 750e jne 0x401930
│ 0x00401922 488d3daa07.. lea rdi, str.ERROR:_Failed_to_allocate_memory_for_the_image_data_ ; 0x4020d3 ; "ERROR: Failed to allocate memory for the image data!"
│ 0x00401929 e842f8ffff call sym.imp.puts ;[5]
┌──< 0x0040192e eb58 jmp 0x401988
│└─> 0x00401930 89da mov edx, ebx
│ 0x00401932 4889c6 mov rsi, rax
│ 0x00401935 4183c8ff or r8d, 0xffffffff ; -1
│ 0x00401939 31ff xor edi, edi
│ 0x0040193b 488d0dc607.. lea rcx, str.ERROR:_Failed_to_read_data_ ; 0x402108 ; "ERROR: Failed to read data!"
│ 0x00401942 4889c5 mov rbp, rax
│ 0x00401945 e841fdffff call sym.read_exact ;[3]
│ 0x0040194a 0fb644240c movzx eax, byte [rsp + 0xc]
│ 0x0040194f 0fb654240b movzx edx, byte [rsp + 0xb]
│ 0x00401954 0fafd0 imul edx, eax
│ 0x00401957 31c0 xor eax, eax
│┌─> 0x00401959 39c2 cmp edx, eax
┌───< 0x0040195b 7e33 jle 0x401990
││╎ 0x0040195d 0fb64c8503 movzx ecx, byte [rbp + rax*4 + 3]
││╎ 0x00401962 48ffc0 inc rax
││╎ 0x00401965 8d71e0 lea esi, [rcx - 0x20]
││╎ 0x00401968 4080fe5e cmp sil, 0x5e ; '^' ; 94
││└─< 0x0040196c 76eb jbe 0x401959
││ 0x0040196e 488b3d0ba2.. mov rdi, qword [obj.stderr] ; obj.stderr__GLIBC_2.2.5
││ ; [0x40bb80:8]=0
││ 0x00401975 488d15a807.. 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"
││ 0x0040197c be01000000 mov esi, 1
││ 0x00401981 31c0 xor eax, eax
││ 0x00401983 e8c8f8ffff call sym.imp.__fprintf_chk ;[6]
│└──> 0x00401988 83cfff or edi, 0xffffffff ; -1
│ 0x0040198b e8b0f8ffff call sym.imp.exit ;[7]
└───> 0x00401990 4531ed xor r13d, r13d
0x00401993 4c8d7c240f lea r15, [rsp + 0xf]
┌─> 0x00401998 0fb644240c movzx eax, byte [rsp + 0xc]
╎ 0x0040199d 4439e8 cmp eax, r13d
┌──< 0x004019a0 0f8ea7000000 jle 0x401a4d
│╎ 0x004019a6 4531f6 xor r14d, r14d
┌───> 0x004019a9 0fb64c240b movzx ecx, byte [rsp + 0xb]
╎│╎ 0x004019ae 4439f1 cmp ecx, r14d
┌────< 0x004019b1 0f8e8e000000 jle 0x401a45
│╎│╎ 0x004019b7 0fb644240d movzx eax, byte [rsp + 0xd]
│╎│╎ 0x004019bc 0fb65c240e movzx ebx, byte [rsp + 0xe]
│╎│╎ 0x004019c1 410fafcd imul ecx, r13d
│╎│╎ 0x004019c5 4c89ff mov rdi, r15
│╎│╎ 0x004019c8 410fb6742406 movzx esi, byte [r12 + 6]
│╎│╎ 0x004019ce 4c8d058107.. lea r8, str.e_38_2__03d__03d__03dm_ce_0m ; 0x402156
│╎│╎ 0x004019d5 4401f0 add eax, r14d
│╎│╎ 0x004019d8 4401eb add ebx, r13d
│╎│╎ 0x004019db 99 cdq
│╎│╎ 0x004019dc 0fafde imul ebx, esi
│╎│╎ 0x004019df 4401f1 add ecx, r14d
│╎│╎ 0x004019e2 41ffc6 inc r14d
│╎│╎ 0x004019e5 f7fe idiv esi
│╎│╎ 0x004019e7 4863c9 movsxd rcx, ecx
│╎│╎ 0x004019ea be19000000 mov esi, 0x19 ; 25
│╎│╎ 0x004019ef 488d448d00 lea rax, [rbp + rcx*4]
│╎│╎ 0x004019f4 b919000000 mov ecx, 0x19 ; 25
│╎│╎ 0x004019f9 01d3 add ebx, edx
│╎│╎ 0x004019fb 52 push rdx
│╎│╎ 0x004019fc 0fb65003 movzx edx, byte [rax + 3]
│╎│╎ 0x00401a00 52 push rdx
│╎│╎ 0x00401a01 0fb65002 movzx edx, byte [rax + 2]
│╎│╎ 0x00401a05 52 push rdx
│╎│╎ 0x00401a06 0fb65001 movzx edx, byte [rax + 1]
│╎│╎ 0x00401a0a 52 push rdx
│╎│╎ 0x00401a0b 440fb608 movzx r9d, byte [rax]
│╎│╎ 0x00401a0f ba01000000 mov edx, 1
│╎│╎ 0x00401a14 31c0 xor eax, eax
│╎│╎ 0x00401a16 e835f7ffff call sym.imp.__snprintf_chk ;[1]
│╎│╎ 0x00401a1b 89d8 mov eax, ebx
│╎│╎ 0x00401a1d 31d2 xor edx, edx
│╎│╎ 0x00401a1f 410f1007 movups xmm0, xmmword [r15]
│╎│╎ 0x00401a23 41f774240c div dword [r12 + 0xc]
│╎│╎ 0x00401a28 4883c420 add rsp, 0x20
│╎│╎ 0x00401a2c 486bd218 imul rdx, rdx, 0x18
│╎│╎ 0x00401a30 4903542410 add rdx, qword [r12 + 0x10]
│╎│╎ 0x00401a35 0f1102 movups xmmword [rdx], xmm0
│╎│╎ 0x00401a38 498b4710 mov rax, qword [r15 + 0x10]
│╎│╎ 0x00401a3c 48894210 mov qword [rdx + 0x10], rax
│└───< 0x00401a40 e964ffffff jmp 0x4019a9
└────> 0x00401a45 41ffc5 inc r13d
│└─< 0x00401a48 e94bffffff jmp 0x401998
└──> 0x00401a4d 488b442428 mov rax, qword [rsp + 0x28]
0x00401a52 6448330425.. xor rax, qword fs:[0x28]
┌─< 0x00401a5b 7405 je 0x401a62
│ 0x00401a5d e82ef7ffff call sym.imp.__stack_chk_fail ;[2]
└─> 0x00401a62 4883c438 add rsp, 0x38
0x00401a66 5b pop rbx
0x00401a67 5d pop rbp
0x00401a68 415c pop r12
0x00401a6a 415d pop r13
0x00401a6c 415e pop r14
0x00401a6e 415f pop r15
0x00401a70 c3 ret

允许你在全局 Framebuffer 的任意位置 (base_x, base_y),绘制一个尺寸为 (width, height) 的矩形色块。
0x004019d5 add eax, r14d ; 全局 X = base_x + 局部 x
0x004019d8 add ebx, r13d ; 全局 Y = base_y + 局部 y
...
0x004019e5 idiv esi ; 涉及坐标映射,计算在全局 framebuffer 中的 index
...
0x00401a16 call sym.imp.__snprintf_chk ; 将局部像素格式化为 ANSI
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
;-- handle_8564:
...
获取坐标参数
0x004016ff movzx ebp, byte [rdi + 6] ; 从传入的 cimg 结构体获取宽度
0x00401703 movzx edx, byte [rdi + 7] ; 从传入的 cimg 结构体获取高度
...
0x00401717 imul ebp, edx ; 计算总像素 ebp = width * height
...
数据读取
0x00401741 or r8d, 0xffffffff ; -1 (exit code)
0x00401747 lea rcx, str.ERROR:_Failed_to_read_data_
0x00401751 call sym.read_exact ;[6]
rdi (fd) = 0
rsi (dst) = rax (malloc 的地址)
rdx (size) = ebp (即 width * height)

0x004017eb movzx edx, byte [rax + 3]
0x004017ef push rdx
0x004017f0 movzx edx, byte [rax + 2]
...
0x00401805 call sym.imp.__snprintf_chk ;[7]
...
0x00401830 movups xmmword [rdx], xmm0
它在用相同的几个像素数据(来源于刚刚读进来的 data)不断地格式化成 ANSI 字符串,并循环写入到整个 Framebuffer 中。

回顾 45626 指令。它需要 base_x, base_y, width, height,然后跟随这块区域的像素数据。我们可以用几个的 45626 矩形指令拼接出目标图像,就能绕过 1340 字节。

[绘制像素的指令代码] + [x坐标, y坐标] + [R, G, B, A] -> [绘制像素的指令代码] + [x, y, w, h] + ? * [R, G, B, A]

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
import struct
from collections import Counter

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:
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:
pass
return pixels

def build_payload():
binary_path = "/challenge/cimg"
num_pixels = 1314
width = 73
height = 18

pixels = extract_pixels_from_elf(binary_path, num_pixels)
if len(pixels) != num_pixels:
log.error("Failed to extract full image from binary.")
return

# 1. 寻找主导背景色
color_counts = Counter(pixels)
bg_pixel = color_counts.most_common(1)[0][0]
log.info(f"Dominant background pixel detected: {bg_pixel}")

# 2. 贪心二维矩形合并
directives_payload = bytearray()
directive_count = 0
visited = set()

# 扫描全局 Framebuffer
for y in range(height):
for x in range(width):
# 如果是需要绘制的前景像素,且未被合并过
if pixels[y * width + x] != bg_pixel and (x, y) not in visited:
# a. 横向贪心寻找最大宽度
w = 0
while (
x + w < width
and pixels[y * width + x + w] != bg_pixel
and (x + w, y) not in visited
):
w += 1

# b. 纵向贪心寻找最大高度 (整行必须完全匹配该宽度且未访问)
h = 1
while y + h < height:
row_valid = True
for i in range(w):
if (
pixels[(y + h) * width + x + i] == bg_pixel
or (x + i, y + h) in visited
):
row_valid = False
break
if row_valid:
h += 1
else:
break

# c. 标记该矩形区域内的所有像素为已访问
for dy in range(h):
for dx in range(w):
visited.add((x + dx, y + dy))

# d. 构造 45626 区块写入指令 (仅花费 6 Bytes 的 Overhead)
directives_payload += struct.pack("<H", 45626)
directives_payload += struct.pack("<BBBB", x, y, w, h)

# e. 填入该矩形的真实像素数据
for dy in range(h):
for dx in range(w):
p = pixels[(y + dy) * width + x + dx]
directives_payload += struct.pack(
"<BBBB", p[0], p[1], p[2], p[3]
)

directive_count += 1

log.info(f"Greedy algorithm optimized image into {directive_count} solid blocks.")
total_size = len(directives_payload) + 12

if total_size > 1340:
log.error(f"Bandwidth limit exceeded! Current size: {total_size} bytes.")
return

magic = b"cIMG"
version = 3
remaining_directives = 1
file_header = struct.pack(
"<4sHBBI", magic, version, width, height, remaining_directives
)

file_header = struct.pack("<4sHBBI", magic, version, width, height, directive_count)

# 4. 组装并运行
payload = file_header + directives_payload
file_name = "payload.cimg"
with open(file_name, "wb") as f:
f.write(payload)

log.success(f"Payload generated: {len(payload)} bytes.")
p = process([binary_path, file_name], stdin=process.PTY, stdout=process.PTY)
print(p.recvall().decode(errors="ignore"))


if __name__ == "__main__":
build_payload()

File Formats: Directives (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
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
// ...

char desired_output[] = ""; // too large

struct cimg_header
{
char magic_number[4];
uint16_t version;
uint8_t width;
uint8_t height;
uint32_t remaining_directives; // 4 bytes directives
} __attribute__((packed)); // gcc attribute no padding

typedef struct
{
uint8_t ascii;
} pixel_bw_t;
#define COLOR_PIXEL_FMT "\x1b[38;2;%03d;%03d;%03dm%c\x1b[0m"
typedef struct
{
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t ascii;
} pixel_color_t;
typedef pixel_color_t pixel_t;

typedef struct
{
union
{
char data[24];
struct term_str_st
{
char color_set[7]; // \x1b[38;2;
char r[3]; // 255
char s1; // ;
char g[3]; // 255
char s2; // ;
char b[3]; // 255
char m; // m
char c; // X
char color_reset[4]; // \x1b[0m
} str;
};
} term_pixel_t;

struct cimg
{
struct cimg_header header;
unsigned num_pixels;
term_pixel_t *framebuffer;
};

#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))
// read pixel data and cpy to framebuffer
void handle_24740(struct cimg *cimg)
{
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);
}
}

int idx = 0;
for (int y = 0; y < cimg->header.height; y++)
{
for (int x = 0; x < cimg->header.width; x++)
{
idx = (0+y)*((cimg)->header.width) + ((0+x)%((cimg)->header.width));
char emit_tmp[24+1];
snprintf(emit_tmp, sizeof(emit_tmp), "\x1b[38;2;%03d;%03d;%03dm%c\x1b[0m", data[y * cimg->header.width + x].r, data[y * cimg->header.width + x].g, data[y * cimg->header.width + x].b, data[y * cimg->header.width + x].ascii);
memcpy((cimg)->framebuffer[idx%(cimg)->num_pixels].data, emit_tmp, 24);

}
}
}
// ...
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);
}

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

initialize_framebuffer(&cimg);

// remaining directives count the loop times
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 24740:
handle_24740(&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 (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
import struct

from pwn import *

def build_payload():
binary_path = "/challenge/cimg"
try:
elf = ELF(binary_path, checksec=False)
except Exception as e:
log.error(f"Failed to load ELF: {e}")
return

# radare2 btw
# desired_output -> 0x404020
try:
desired_output_addr = elf.symbols["desired_output"]
log.info(f"Resolved 'desired_output' symbol at: {hex(desired_output_addr)}")
except KeyError:
desired_output_addr = 0x404020 # Fallback 地址
log.warning("Symbol not found, using fallback address.")

# pixel -> 930
num_pixels = 930
pixel_len = 24
total_bytes = num_pixels * pixel_len

log.info(
f"Extracting {total_bytes} bytes from address {hex(desired_output_addr)}..."
)

# 3. 从 ELF 文件中读取这些字节
try:
raw_ansi_data = elf.read(desired_output_addr, total_bytes)
except Exception as e:
log.error(f"Failed to read from ELF memory: {e}")
return

# 4. 提取颜色和字符数据 (<BBBB)
pixel_data = bytearray()
for i in range(num_pixels):
chunk = raw_ansi_data[i * pixel_len : (i + 1) * pixel_len]

# 典型的 chunk: b'\x1b[38;2;255;255;255m.\x1b[0m'
# 索引提取: R (7:10), G (11:14), B (15:18), ASCII (19)
try:
r = int(chunk[7:10])
g = int(chunk[11:14])
b = int(chunk[15:18])
char_byte = chunk[19]

pixel_data += struct.pack("<BBBB", r, g, b, char_byte)
except ValueError as e:
log.error(f"Failed to parse chunk at index {i}: {chunk}. Error: {e}")
return

# factor num_pixels
width = 30
height = 31
assert width * height == num_pixels, "Dimension logic error!"

# 6. 打包 Header (<4sHBBI)
magic = b"cIMG"
version = 3
# loop times
remaining_directives = 1
file_header = struct.pack(
"<4sHBBI", magic, version, width, height, remaining_directives
)

# 5. 构造 Directive Code (<H)
directive_code = 24740
directive_header = struct.pack("<H", directive_code)

# 7. 组装并保存 Payload
payload = file_header + directive_header + pixel_data

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

log.success(f"Payload generated: {len(payload)} bytes.")

# 8. 直接运行目标拿到 Flag
p = process([binary_path, file_name], stdin=process.PTY, stdout=process.PTY)
print(p.recvall().decode(errors="ignore"))


if __name__ == "__main__":
build_payload()

File Formats: Directives (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
  ╎└─< 0x00401374      75e4           jne 0x40135a
╎ 0x00401376 4889ef mov rdi, rbp
╎ 0x00401379 488d5c240e lea rbx, [rsp + 0xe]
╎ 0x0040137e e80b050000 call sym.initialize_framebuffer ;[3]
╎┌─> 0x00401383 8b442418 mov eax, dword [rsp + 0x18]
╎╎ 0x00401387 8d50ff lea edx, [rax - 1]
╎╎ 0x0040138a 89542418 mov dword [rsp + 0x18], edx
╎╎ 0x0040138e 85c0 test eax, eax
┌───< 0x00401390 744c je 0x4013de
│╎╎ 0x00401392 488d0da50e.. lea rcx, str.ERROR:_Failed_to_read_directive_code_ ; 0x40223e ; "ERROR: Failed to read &directive_code"
│╎╎ 0x00401399 4183c8ff or r8d, 0xffffffff ; -1
│╎╎ 0x0040139d 4889de mov rsi, rbx
│╎╎ 0x004013a0 31ff xor edi, edi
│╎╎ 0x004013a2 ba02000000 mov edx, 2
│╎╎ 0x004013a7 e8af020000 call sym.read_exact ;[4]

# 上一题我们用的 Directive Code 是 24740。但在这里,程序从 Payload 里读了两个字节(word),并与 0x79d6 进行对比。0x79d6 转换成十进制就是 31190。
│╎╎ 0x004013ac 0fb74c240e movzx ecx, word [rsp + 0xe]
│╎╎ 0x004013b1 6681f9d679 cmp cx, 0x79d6
┌────< 0x004013b6 750a jne 0x4013c2
││╎╎ 0x004013b8 4889ef mov rdi, rbp
││╎╎ 0x004013bb e8eb020000 call sym.handle_31190 ;[5] ; go to check the logic in handle_31190
││╎└─< 0x004013c0 ebc1 jmp 0x401383
└────> 0x004013c2 488b3d77ac.. mov rdi, qword [obj.stderr] ; obj.stderr__GLIBC_2.2.5
│╎ ; [0x40c040:8]=0
│╎ 0x004013c9 488d15950e.. lea rdx, str.ERROR:_invalid_directive_code__ux_n ; 0x402265 ; "ERROR: invalid directive_code %ux\n"
│╎ 0x004013d0 be01000000 mov esi, 1
│╎ 0x004013d5 31c0 xor eax, eax
│╎ 0x004013d7 e874feffff call sym.imp.__fprintf_chk ;[6]
│└──< 0x004013dc eb81 jmp 0x40135f
└───> 0x004013de 31f6 xor esi, esi
0x004013e0 4889ef mov rdi, rbp
0x004013e3 4c8d25362c.. lea r12, obj.desired_output ; 0x404020 ; old place
0x004013ea 31db xor ebx, ebx
0x004013ec e83c040000 call sym.display ;[7]
0x004013f1 448b74241c mov r14d, dword [rsp + 0x1c]
0x004013f6 4c8b6c2420 mov r13, qword [rsp + 0x20]
0x004013fb 4181fe5505.. cmp r14d, 0x555 ; 1365
0x00401402 0f94c3 sete bl
0x00401405 31ed xor ebp, ebp
0x00401407 4531ff xor r15d, r15d
┌─> 0x0040140a 4439f5 cmp ebp, r14d
┌──< 0x0040140d 733f jae 0x40144e
│╎ 0x0040140f 81fd55050000 cmp ebp, 0x555 ; 1365 ; new frame size
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
import struct

from pwn import *

def build_payload():
binary_path = "/challenge/cimg"
try:
elf = ELF(binary_path, checksec=False)
except Exception as e:
log.error(f"Failed to load ELF: {e}")
return

try:
desired_output_addr = elf.symbols["desired_output"]
log.info(f"Resolved 'desired_output' symbol at: {hex(desired_output_addr)}")
except KeyError:
desired_output_addr = 0x404020 # Fallback 地址
log.warning("Symbol not found, using fallback address.")

# search from radare2
num_pixels = 1365
pixel_len = 24
total_bytes = num_pixels * pixel_len

log.info(
f"Extracting {total_bytes} bytes from address {hex(desired_output_addr)}..."
)

# 3. 从 ELF 文件中读取这些字节
try:
raw_ansi_data = elf.read(desired_output_addr, total_bytes)
except Exception as e:
log.error(f"Failed to read from ELF memory: {e}")
return

# 4. 提取颜色和字符数据 (<BBBB)
pixel_data = bytearray()
for i in range(num_pixels):
chunk = raw_ansi_data[i * pixel_len : (i + 1) * pixel_len]
try:
r = int(chunk[7:10])
g = int(chunk[11:14])
b = int(chunk[15:18])
char_byte = chunk[19]

pixel_data += struct.pack("<BBBB", r, g, b, char_byte)
except ValueError as e:
log.error(f"Failed to parse chunk at index {i}: {chunk}. Error: {e}")
return

# factor num_pixels
width = 35
height = 39
assert width * height == num_pixels, "Dimension logic error!"

magic = b"cIMG"
version = 3
remaining_directives = 1
file_header = struct.pack(
"<4sHBBI", magic, version, width, height, remaining_directives
)

# 5. 构造 Directive Code (<H)
directive_code = 31190
directive_header = struct.pack("<H", directive_code)

# 7. 组装并保存 Payload
payload = file_header + directive_header + pixel_data

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

log.success(f"Payload generated: {len(payload)} bytes.")

# 8. 直接运行目标拿到 Flag
p = process([binary_path, file_name], stdin=process.PTY, stdout=process.PTY)
print(p.recvall().decode(errors="ignore"))


if __name__ == "__main__":
build_payload()

Internal State (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
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
// ...
char desired_output[] = ""; // too large
// ...
#define COLOR_PIXEL_FMT "\x1b[38;2;%03d;%03d;%03dm%c\x1b[0m"
// ...
typedef struct
{
union
{
char data[24];
struct term_str_st
{
char color_set[7]; // \x1b[38;2;
char r[3]; // 255
char s1; // ;
char g[3]; // 255
char s2; // ;
char b[3]; // 255
char m; // m
char c; // X
char color_reset[4]; // \x1b[0m
} str;
};
} term_pixel_t;

struct cimg
{
struct cimg_header header;
unsigned num_pixels;
term_pixel_t *framebuffer;
};

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

void display(struct cimg *cimg, pixel_t *data)
{
int idx = 0;
for (int y = 0; y < cimg->header.height; y++)
{
for (int x = 0; x < cimg->header.width; x++)
{
idx = (0+y)*((cimg)->header.width) + ((0+x)%((cimg)->header.width));
char emit_tmp[24+1];
snprintf(emit_tmp, sizeof(emit_tmp), "\x1b[38;2;%03d;%03d;%03dm%c\x1b[0m", data[y * cimg->header.width + x].r, data[y * cimg->header.width + x].g, data[y * cimg->header.width + x].b, data[y * cimg->header.width + x].ascii);
memcpy((cimg)->framebuffer[idx%(cimg)->num_pixels].data, emit_tmp, 24);

}
}

for (int i = 0; i < cimg->header.height; i++)
{
write(1, cimg->framebuffer+i*cimg->header.width, sizeof(term_pixel_t)*cimg->header.width);
write(1, "\x1b[38;2;000;000;000m\n\x1b[0m", 24);
}
}

struct cimg *initialize_framebuffer(struct cimg *cimg)
{
cimg->num_pixels = CIMG_FRAMEBUFFER_PIXELS(cimg);
cimg->framebuffer = malloc(CIMG_FRAMEBUFFER_SIZE(cimg)+1);
if (cimg->framebuffer == NULL)
{
puts("ERROR: Failed to allocate memory for the framebuffer!");
exit(-1);
}
for (int idx = 0; idx < cimg->num_pixels; idx += 1)
{
char emit_tmp[24+1];
snprintf(emit_tmp, sizeof(emit_tmp), "\x1b[38;2;%03d;%03d;%03dm%c\x1b[0m", 255, 255, 255, ' ');
memcpy(cimg->framebuffer[idx].data, emit_tmp, 24);

}

return cimg;
}

void __attribute__ ((constructor)) disable_buffering() {
//...
}

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 != 2)
{
puts("ERROR: Unsupported version!");
exit(-1);
}

initialize_framebuffer(&cimg);

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);
}
}

display(&cimg, data);

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 (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
import re
import struct

from pwn import *

def build_payload():
# 1. 直接读取目标 C 源码文件
try:
with open("/challenge/cimg.c", "r") as f:
src = f.read()
except FileNotFoundError:
return

# 2. 使用正则提取 desired_output 字符串
match = re.search(r'char desired_output\[\] = "(.*?)";', src, re.DOTALL)
if not match:
return

raw_c_str = match.group(1)

# 3. 解除 C 语言的字符串转义 (\x1b -> 真实 ESC 字节, \\ -> \)
decoded_bytes = bytes(raw_c_str, "utf-8").decode("unicode_escape").encode("latin1")

# 去除末尾的 C 语言 null terminator (\x00)
if decoded_bytes.endswith(b"\x00"):
decoded_bytes = decoded_bytes[:-1]

# 4. 计算总像素数 (每个终端像素刚好 24 字节)
# \x1b[38;2;RRR;GGG;BBBmc\x1b[0m
PIXEL_LEN = 24
num_pixels = len(decoded_bytes) // PIXEL_LEN

# 5. 因式分解,找出合法的 width 和 height ( 0~255 )
width, height = 0, 0
for w in range(1, 256):
if num_pixels % w == 0:
h = num_pixels // w
if h <= 255:
width, height = w, h
break

if width == 0:
log.error("Could not factorize num_pixels into valid uint8_t bounds.")
return

log.info(f"Calculated valid constraints: Width = {width}, Height = {height}")

# 6. 构造 8 Bytes Header (<4sHBB)
magic = b"cIMG"
version = 2
file_header = struct.pack("<4sHBB", magic, version, width, height)

# 7. 提取每个像素的 R, G, B, ASCII 构建数据段
pixel_data = bytearray()
for i in range(num_pixels):
chunk = decoded_bytes[i * PIXEL_LEN : (i + 1) * PIXEL_LEN]

# 精确内存对齐切片:
# chunk[7:10] R 3 bytes
# chunk[11:14] G 3 bytes
# chunk[15:18] = B 3 bytes
# chunk[19] = ASCII 1 byte
r = int(chunk[7:10])
g = int(chunk[11:14])
b = int(chunk[15:18])
char_byte = chunk[19]

pixel_data += struct.pack("<BBBB", r, g, b, char_byte)

# 8. 组装 Payload 并写入文件
payload = file_header + pixel_data
file_name = "payload.cimg"
with open(file_name, "wb") as f:
f.write(payload)

log.success(f"Payload generated: {len(payload)} bytes.")

# 9. 运行二进制并拿 Flag
p = process(["/challenge/cimg", file_name], stdin=process.PTY, stdout=process.PTY)
print(p.recvall().decode(errors="ignore"))

if __name__ == "__main__":
build_payload()

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

Internal State (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
51
52
            0x00401405      4c8d25142c..   lea r12, obj.desired_output    ; 0x404020 // go to check desired_output
0x0040140c 31db xor ebx, ebx
0x0040140e e8c8020000 call sym.display ;[3]
0x00401413 448b742408 mov r14d, dword [rsp + 8]
0x00401418 4c8b6c2410 mov r13, qword [rsp + 0x10]
0x0040141d 4181fee401.. cmp r14d, 0x1e4 ; 484 ; pixel count
0x00401424 0f94c3 sete bl
0x00401427 31ed xor ebp, ebp
0x00401429 4531ff xor r15d, r15d
┌─> 0x0040142c 81fde4010000 cmp ebp, 0x1e4 ; 484
┌──< 0x00401432 743c je 0x401470
│╎ 0x00401434 4139ee cmp r14d, ebp
┌───< 0x00401437 7637 jbe 0x401470
││╎ 0x00401439 418a4513 mov al, byte [r13 + 0x13]
││╎ 0x0040143d 413a442413 cmp al, byte [r12 + 0x13]
││╎ 0x00401442 410f45df cmovne ebx, r15d
││╎ 0x00401446 3c20 cmp al, 0x20 ; 32
┌────< 0x00401448 741a je 0x401464
│││╎ 0x0040144a 3c0a cmp al, 0xa ; 10
┌─────< 0x0040144c 7416 je 0x401464
││││╎ 0x0040144e ba18000000 mov edx, 0x18 ; 24
││││╎ 0x00401453 4c89e6 mov rsi, r12
││││╎ 0x00401456 4c89ef mov rdi, r13
││││╎ 0x00401459 e882fdffff call sym.imp.memcmp ;[4]
││││╎ 0x0040145e 85c0 test eax, eax
││││╎ 0x00401460 410f45df cmovne ebx, r15d
└└────> 0x00401464 ffc5 inc ebp
││╎ 0x00401466 4983c518 add r13, 0x18 ; 24
││╎ 0x0040146a 4983c418 add r12, 0x18 ; 24
││└─< 0x0040146e ebbc jmp 0x40142c
└└──> 0x00401470 85db test ebx, ebx
┌─< 0x00401472 7407 je 0x40147b
│ 0x00401474 31c0 xor eax, eax
│ 0x00401476 e81b010000 call sym.win ;[5]
└─> 0x0040147b 488b442418 mov rax, qword [rsp + 0x18]
0x00401480 6448330425.. xor rax, qword fs:[0x28]
┌─< 0x00401489 7405 je 0x401490
│ 0x0040148b e800fdffff call sym.imp.__stack_chk_fail ;[6]
└─> 0x00401490 4883c428 add rsp, 0x28
0x00401494 31c0 xor eax, eax
0x00401496 5b pop rbx
0x00401497 5d pop rbp
> px 200 @ 0x404020
- offset - 2021 2223 2425 2627 2829 2A2B 2C2D 2E2F 0123456789ABCDEF
0x00404020 1b5b 3338 3b32 3b32 3535 3b32 3535 3b32 .[38;2;255;255;2
0x00404030 3535 6d2e 1b5b 306d 1b5b 3338 3b32 3b32 55m..[0m.[38;2;2
0x00404040 3535 3b32 3535 3b32 3535 6d2d 1b5b 306d 55;255;255m-.[0m
0x00404050 1b5b 3338 3b32 3b32 3535 3b32 3535 3b32 .[38;2;255;255;2
0x00404060 3535 6d2d 1b5b 306d 1b5b 3338 3b32 3b32 55m-.[0m.[38;2;2
0x00404070 3535 3b32 3535 3b32 3535 6d2d 1b5b 306d 55;255;255m-.[0m
0x00404080 1b5b 3338 3b32 3b32 3535 3b32 3535 3b32 .[38;2;255;255;2
# ...
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
import struct

from pwn import *

def build_payload():
binary_path = "/challenge/cimg"
try:
elf = ELF(binary_path, checksec=False)
except Exception as e:
log.error(f"Failed to load ELF: {e}")
return

# 2. 我们通过 radare2 分析已知 desired_output 的地址是 0x404020
# 从汇编 cmp r14d, 0x1e4 得知,总共 484 个像素
desired_output_addr = 0x404020
num_pixels = 0x1E4 # 484
pixel_len = 24
total_bytes = num_pixels * pixel_len

log.info(
f"Extracting {total_bytes} bytes from address {hex(desired_output_addr)}..."
)

# 3. 从 ELF 文件中读取这些字节
try:
raw_ansi_data = elf.read(desired_output_addr, total_bytes)
except Exception as e:
log.error(f"Failed to read from ELF memory: {e}")
return

# 4. 提取颜色和字符数据 (<BBBB)
pixel_data = bytearray()
for i in range(num_pixels):
chunk = raw_ansi_data[i * pixel_len : (i + 1) * pixel_len]

# 典型的 chunk: b'\x1b[38;2;255;255;255m.\x1b[0m'
# 索引提取: R (7:10), G (11:14), B (15:18), ASCII (19)
try:
r = int(chunk[7:10])
g = int(chunk[11:14])
b = int(chunk[15:18])
char_byte = chunk[19]

pixel_data += struct.pack("<BBBB", r, g, b, char_byte)
except ValueError as e:
log.error(f"Failed to parse chunk at index {i}: {chunk}. Error: {e}")
return

# 5. 484 = 22 x 22
width = 22
height = 22
assert width * height == num_pixels, "Dimension logic error!"

# 6. 打包 Header (<4sHBB)
magic = b"cIMG"
version = 2
file_header = struct.pack("<4sHBB", magic, version, width, height)

# 7. 组装并保存 Payload
payload = file_header + pixel_data
file_name = "payload.cimg"
with open(file_name, "wb") as f:
f.write(payload)

log.success(f"Payload generated: {len(payload)} bytes.")

# 8. 直接运行目标拿到 Flag
p = process([binary_path, file_name], stdin=process.PTY, stdout=process.PTY)
print(p.recvall().decode(errors="ignore"))


if __name__ == "__main__":
build_payload()

Internal State Mini (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
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
char desired_output[] = "\x1b[38;2;200;040;131mc\x1b[0m\x1b[38;2;001;019;165mI\x1b[0m\x1b[38;2;160;134;059mM\x1b[0m\x1b[38;2;195;046;079mG\x1b[0m\x00";

// 4 + 2 + 1 + 1 = 8 bytes header
struct cimg_header
{
char magic_number[4];
uint16_t version;
uint8_t width;
uint8_t height;
} __attribute__((packed));

// 1 byte ascii
typedef struct
{
uint8_t ascii;
} pixel_bw_t;
#define COLOR_PIXEL_FMT "\x1b[38;2;%03d;%03d;%03dm%c\x1b[0m"

// 3 rgb + 1 ascii
typedef struct
{
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t ascii;
} pixel_color_t;
typedef pixel_color_t pixel_t;

// text ansi escape sequence (24 bytes)
typedef struct
{
union
{
char data[24];
struct term_str_st
{
char color_set[7]; // \x1b[38;2;
char r[3]; // 255
char s1; // ;
char g[3]; // 255
char s2; // ;
char b[3]; // 255
char m; // m
char c; // X
char color_reset[4]; // \x1b[0m
} str;
};
} term_pixel_t;
struct cimg
{
struct cimg_header header;
unsigned num_pixels;
term_pixel_t *framebuffer;
};

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

// ...

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

// magic_number
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);
}

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

initialize_framebuffer(&cimg);

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++)
{
// ascii
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);
}
}

display(&cimg, data);

// char desired_output[] = "\x1b[38;2;200;040;131mc\x1b[0m\x1b[38;2;001;019;165mI\x1b[0m\x1b[38;2;160;134;059mM\x1b[0m\x1b[38;2;195;046;079mG\x1b[0m\x00";
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 (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
from pwn import *
from pwn import process

header = b"cIMG"
version = 2
width = 4
height = 1
total_pixels = width * height

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

p1 = struct.pack("<BBBB", 200, 40, 131, ord('c'))
p2 = struct.pack("<BBBB", 1, 19, 165, ord('I'))
p3 = struct.pack("<BBBB", 160, 134, 59, ord('M'))
p4 = struct.pack("<BBBB", 195, 46, 79, ord('G'))

pixel_data = p1 + p2 + p3 + p4

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

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

Internal State Mini (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
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
       ╎╎   0x00401387      4863db         movsxd rbx, ebx
╎╎ 0x0040138a 48c1e302 shl rbx, 2
╎╎ 0x0040138e 4889df mov rdi, rbx
╎╎ 0x00401391 e86afeffff call sym.imp.malloc ;[1]
╎╎ 0x00401396 488d3d1e0e.. lea rdi, str.ERROR:_Failed_to_allocate_memory_for_the_image_data_ ; 0x4021bb ; "ERROR: Failed to all
╎╎ 0x0040139d 4889c5 mov rbp, rax
╎╎ 0x004013a0 4885c0 test rax, rax
╎└─< 0x004013a3 74b1 je 0x401356
╎ 0x004013a5 89da mov edx, ebx
╎ 0x004013a7 4889c6 mov rsi, rax
╎ 0x004013aa 4183c8ff or r8d, 0xffffffff ; -1
╎ 0x004013ae 31ff xor edi, edi
╎ 0x004013b0 488d0d390e.. lea rcx, str.ERROR:_Failed_to_read_data_ ; 0x4021f0 ; "ERROR: Failed to read data!"
╎ 0x004013b7 e8bf020000 call sym.read_exact ;[2]
╎ 0x004013bc 0fb6442407 movzx eax, byte [rsp + 7]
╎ 0x004013c1 0fb6542406 movzx edx, byte [rsp + 6]
╎ 0x004013c6 0fafd0 imul edx, eax
╎ 0x004013c9 31c0 xor eax, eax
╎┌─> 0x004013cb 39c2 cmp edx, eax
┌───< 0x004013cd 7e30 jle 0x4013ff
│╎╎ 0x004013cf 0fb64c8503 movzx ecx, byte [rbp + rax*4 + 3]
│╎╎ 0x004013d4 48ffc0 inc rax
│╎╎ 0x004013d7 8d71e0 lea esi, [rcx - 0x20]
│╎╎ 0x004013da 4080fe5e cmp sil, 0x5e ; '^' ; 94
│╎└─< 0x004013de 76eb jbe 0x4013cb
│╎ 0x004013e0 488b3dd92c.. mov rdi, qword [obj.stderr] ; obj.stderr__GLIBC_2.2.5
│╎ ; [0x4040c0:8]=0
│╎ 0x004013e7 488d151e0e.. lea rdx, str.ERROR:_Invalid_character_0x_x_in_the_image_data__n ; str.ERROR:_Invalid_character_0x_x_
│╎ ; 0x40220c ; "ERROR: Invalid character 0x%x in the image data!\n"
│╎ 0x004013ee be01000000 mov esi, 1
│╎ 0x004013f3 31c0 xor eax, eax
│╎ 0x004013f5 e856feffff call sym.imp.__fprintf_chk ;[3]
│└──< 0x004013fa e95cffffff jmp 0x40135b
└───> 0x004013ff 4889ee mov rsi, rbp
0x00401402 4c89e7 mov rdi, r12
0x00401405 4c8d25142c.. lea r12, obj.desired_output ; 0x404020 ; color ; go to check desired_output
0x0040140c 31db xor ebx, ebx
0x0040140e e8b8020000 call sym.display ;[4]
0x00401413 448b6c2408 mov r13d, dword [rsp + 8]
0x00401418 4c8b742410 mov r14, qword [rsp + 0x10]
0x0040141d 4183fd04 cmp r13d, 4 ; 4 ; pixel size
0x00401421 0f94c3 sete bl
0x00401424 31ed xor ebp, ebp
0x00401426 4531ff xor r15d, r15d
┌─> 0x00401429 83fd04 cmp ebp, 4 ; 4
┌──< 0x0040142c 743e je 0x40146c
│╎ 0x0040142e 4139ed cmp r13d, ebp
┌───< 0x00401431 7639 jbe 0x40146c
││╎ 0x00401433 486bfd18 imul rdi, rbp, 0x18
││╎ 0x00401437 418a443e13 mov al, byte [r14 + rdi + 0x13]
││╎ 0x0040143c 413a442413 cmp al, byte [r12 + 0x13]
││╎ 0x00401441 410f45df cmovne ebx, r15d
││╎ 0x00401445 3c20 cmp al, 0x20 ; 32
┌────< 0x00401447 741a je 0x401463
│││╎ 0x00401449 3c0a cmp al, 0xa ; 10
┌─────< 0x0040144b 7416 je 0x401463
││││╎ 0x0040144d 4c01f7 add rdi, r14
││││╎ 0x00401450 ba18000000 mov edx, 0x18 ; 24
││││╎ 0x00401455 4c89e6 mov rsi, r12
││││╎ 0x00401458 e883fdffff call sym.imp.memcmp ;[5]
││││╎ 0x0040145d 85c0 test eax, eax
││││╎ 0x0040145f 410f45df cmovne ebx, r15d
└└────> 0x00401463 48ffc5 inc rbp
││╎ 0x00401466 4983c418 add r12, 0x18 ; 24
││└─< 0x0040146a ebbd jmp 0x401429
└└──> 0x0040146c 85db test ebx, ebx
┌─< 0x0040146e 7407 je 0x401477
│ 0x00401470 31c0 xor eax, eax
│ 0x00401472 e80f010000 call sym.win ;[6]
└─> 0x00401477 488b442418 mov rax, qword [rsp + 0x18]
0x0040147c 6448330425.. xor rax, qword fs:[0x28]
┌─< 0x00401485 7405 je 0x40148c
│ 0x00401487 e804fdffff call sym.imp.__stack_chk_fail ;[7]
└─> 0x0040148c 4883c428 add rsp, 0x28

> px 96 @ 0x404020
- offset - 2021 2223 2425 2627 2829 2A2B 2C2D 2E2F 0123456789ABCDEF
0x00404020 1b5b 3338 3b32 3b31 3534 3b31 3732 3b30 .[38;2;154;172;0
0x00404030 3130 6d63 1b5b 306d 1b5b 3338 3b32 3b30 10mc.[0m.[38;2;0
0x00404040 3533 3b30 3935 3b32 3235 6d49 1b5b 306d 53;095;225mI.[0m
0x00404050 1b5b 3338 3b32 3b31 3332 3b30 3934 3b30 .[38;2;132;094;0
0x00404060 3637 6d4d 1b5b 306d 1b5b 3338 3b32 3b32 67mM.[0m.[38;2;2
0x00404070 3035 3b30 3336 3b30 3836 6d47 1b5b 306d 05;036;086mG.[0m
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
from pwn import *
from pwn import process

header = b"cIMG"
version = 2
width = 4
height = 1
total_pixels = width * height

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

p1 = struct.pack("<BBBB", 154, 172, 10, ord('c'))
p2 = struct.pack("<BBBB", 53, 95, 225, ord('I'))
p3 = struct.pack("<BBBB", 132, 94, 67, ord('M'))
p4 = struct.pack("<BBBB", 205, 36, 86, ord('G'))

pixel_data = p1 + p2 + p3 + p4

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

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

A Basic cIMG (Python)

ANSI escape sequences are a standard for in-band signaling to control cursor location, color, font styling, and other options on video text terminals and terminal emulators. Certain sequences of bytes, most starting with an ASCII escape character and a bracket character, are embedded into text. The terminal interprets these sequences as commands, rather than text to display verbatim.

Wikipedia

控制光标位置、颜色、字体样式的带内信号

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
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(10)
assert len(header) == 10, "ERROR: Failed to read header!"

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

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

width = int.from_bytes(header[6:8], "little")
assert width == 51, "ERROR: Incorrect width!"

height = int.from_bytes(header[8:10], "little")
assert height == 20, "ERROR: Incorrect height!"

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

pixels = [Pixel(*data[i : i + 4]) for i in range(0, len(data), 4)]

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!"

ansii_escape = lambda pixel: f"\x1b[38;2;{pixel.r:03};{pixel.g:03};{pixel.b:03}m{chr(pixel.ascii)}\x1b[0m"
framebuffer = "".join(
"".join(ansii_escape(pixel) for pixel in pixels[row_start : row_start + width])
+ ansii_escape(Pixel(0, 0, 0, ord("\n")))
for row_start in range(0, len(pixels), width)
)
print(framebuffer)

nonspace_count = sum(1 for pixel in pixels if chr(pixel.ascii) != " ")
if nonspace_count != 1020:
return

asu_maroon = (0x8C, 0x1D, 0x40) # #8C1D40
if any((pixel.r, pixel.g, pixel.b) != asu_maroon for pixel in pixels):
return

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)

脚本优先从命令行参数 (sys.argv) 读取 .cimg 文件;如果不传参,就从标准输入 sys.stdin.buffer 读管道流。

  • 它读取了 51 * 20 * 4 = 4080 字节的 payload 数据。
  • 每 4 个字节被打包成一个 Pixel 结构( R, G, B 值和 ASCII 码)。
  • 确保 ASCII 值都在可打印字符范围内 (0x200x7E)。
  • 利用 ansii_escape,把这些像素转换成终端带色字符,拼接成 framebuffer 字符串并 print

获取 Flag 的条件

  • 条件一: nonspace_count != 1020 则退出。由于画面一共 51 * 20 = 1020 个像素,这意味着你的图片里不能包含任何一个空格 (0x20),必须全部被非空格的可打印字符填满。
  • 条件二: 所有像素的颜色必须完全是 ASU Maroon (亚利桑那州立大学紫红色),即 #8C1D40(红:0x8C, 绿:0x1D, 蓝:0x40)。

ansii_escape (ansi? mispelled?) 用来生成 24-bit 真彩色 (Truecolor) 的终端输出字符串。

  • \x1b[:ESC 控制符(Escape character,ASCII 码是 27 或 0x1B),标志着转义序列的开始。
  • 38;2;:设置前景(文字)颜色,并且使用 24-bit 的 RGB 模式。
  • {pixel.r:03};{pixel.g:03};{pixel.b:03}m:把解析出的像素 R、G、B 值填进去(不足三位补零),m 代表颜色/格式设置结束。
  • {chr(pixel.ascii)}:打印到屏幕上的字符。
  • \x1b[0m:重置符。打印完这个字符后,立刻清除颜色设置,防止颜色溢出污染 CLI 界面。

try try?

1
printf "\x1b[31m\n"

not work in fish shell btw

Color codes

Most terminals support 8 and 16 colors, as well as 256 (8-bit) colors. These colors are set by the user, but have commonly defined meanings.

8-16 Colors

Color Name Foreground Color Code Background Color Code
Black 30 40
Red 31 41
Green 32 42
Yellow 33 43
Blue 34 44
Magenta 35 45
Cyan 36 46
White 37 47
Default 39 49

Most terminals, apart from the basic set of 8 colors, also support the "bright" or "bold" colors. These have their own set of codes, mirroring the normal colors, but with an additional ;1 in their codes:

1
2
3
4
# Set style to bold, red foreground.
\x1b[1;31mHello
# Set style to dimmed white foreground with red background.
\x1b[2;37;41mWorld

Terminals that support the aixterm specification provides bright versions of the ISO colors, without the need to use the bold modifier:

Color Name Foreground Color Code Background Color Code
Bright Black 90 100
Bright Red 91 101
Bright Green 92 102
Bright Yellow 93 103
Bright Blue 94 104
Bright Magenta 95 105
Bright Cyan 96 106
Bright White 97 107

256 Colors

The following escape codes tells the terminal to use the given color ID:

ESC Code Sequence Description
ESC[38;5;{ID}m Set foreground color.
ESC[48;5;{ID}m Set background color.

Where {ID} should be replaced with the color index from 0 to 255 of the following color table:

256 Color table
  • 0-7: standard colors (as in ESC [ 30–37 m)
  • 8–15: high intensity colors (as in ESC [ 90–97 m)
  • 16-231: 6 × 6 × 6 cube (216 colors): 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5) > Some emulators interpret these steps as linear increments (256 / 24) on all three channels while others may explicitly define these values.
  • 232-255: grayscale from dark to light in 24 steps.

RGB Colors

More modern terminals supports Truecolor (24-bit RGB), which allows you to set foreground and background colors using RGB.

These escape sequences are usually not well documented.

ESC Code Sequence Description
ESC[38;2;{r};{g};{b}m Set foreground color as RGB.
ESC[48;2;{r};{g};{b}m Set background color as RGB.

Note that ;38 and ;48 corresponds to the 16 color sequence and is interpreted by the terminal to set the foreground and background color respectively. Where as ;2 and ;5 sets the color format.

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 *

header = b"cIMG"
version = 2
width = 51
height = 20
total_pixels = width * height

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

ascii_char = ord('A')

r, g, b = 0x8C, 0x1D, 0x40
single_pixel = struct.pack("<BBBB", r, g, b, ascii_char)

pixel_data = single_pixel * total_pixels

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

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

A Basic 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
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
// ...
struct cimg_header
{
char magic_number[4];
uint64_t version;
uint16_t width;
uint16_t height;
} __attribute__((packed));

typedef struct
{
uint8_t ascii;
} pixel_bw_t;
#define COLOR_PIXEL_FMT "\x1b[38;2;%03d;%03d;%03dm%c\x1b[0m"
typedef struct
{
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t ascii;
} pixel_color_t;
typedef pixel_color_t pixel_t;

struct cimg
{
struct cimg_header header;
};

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

void display(struct cimg *cimg, pixel_t *data)
{
int idx = 0;
for (int y = 0; y < cimg->header.height; y++)
{
for (int x = 0; x < cimg->header.width; x++)
{
idx = (0+y)*((cimg)->header.width) + ((0+x)%((cimg)->header.width));
printf("\x1b[38;2;%03d;%03d;%03dm%c\x1b[0m", data[y * cimg->header.width + x].r, data[y * cimg->header.width + x].g, data[y * cimg->header.width + x].b, data[y * cimg->header.width + x].ascii);

}
puts("");
}

}
// ...
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 != 2)
{
puts("ERROR: Unsupported version!");
exit(-1);
}

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

if (cimg.header.height != 23)
{
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);
}
}

display(&cimg, data);

for (int i = 0; i < cimg.header.width * cimg.header.height; i++)
{
if (data[i].r != 0x8c || data[i].g != 0x1d || data[i].b != 0x40) won = 0;
}

int num_nonspace = 0;
for (int i = 0; i < cimg.header.width * cimg.header.height; i++)
{
if (data[i].ascii != ' ') num_nonspace++;
}
if (num_nonspace != 1058) 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
from pwn import *
from pwn import process

header = b"cIMG"
version = 2
width = 46
height = 23
total_pixels = width * height

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

ascii_char = ord('A')

r, g, b = 0x8C, 0x1D, 0x40
single_pixel = struct.pack("<BBBB", r, g, b, ascii_char)

pixel_data = single_pixel * total_pixels

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

pwn.crllege{**********************************************}

A Basic cIMG (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
51
52
53
54
      │   0x004012f5      4889ee         mov rsi, rbp
│ 0x004012f8 ba0b000000 mov edx, 0xb ; 11
│ 0x004012fd e839030000 call sym.read_exact ;[3]
│ 0x00401302 807c240d63 cmp byte [rsp + 0xd], 0x63 ; 'c'
┌──< 0x00401307 7515 jne 0x40131e
││ 0x00401309 807c240e49 cmp byte [rsp + 0xe], 0x49 ; 'I'
┌───< 0x0040130e 750e jne 0x40131e
│││ 0x00401310 807c240f4d cmp byte [rsp + 0xf], 0x4d ; 'M'
┌────< 0x00401315 7507 jne 0x40131e
││││ 0x00401317 807c241047 cmp byte [rsp + 0x10], 0x47 ; 'G'
┌─────< 0x0040131c 7414 je 0x401332
│└└└──> 0x0040131e 488d3d0e0e.. lea rdi, str.ERROR:_Invalid_magic_number_ ; 0x402133 ; "ERROR: Invalid magic number!"
┌─┌┌┌──> 0x00401325 e816feffff call sym.imp.puts ;[4]
┌─────└─> 0x0040132a 83cfff or edi, 0xffffffff ; -1
╎╎│╎╎╎ 0x0040132d e8cefeffff call sym.imp.exit ;[5]
╎╎└─────> 0x00401332 66837c241102 cmp word [rsp + 0x11], 2
╎╎ ╎╎╎ 0x00401338 488d3d110e.. lea rdi, str.ERROR:_Unsupported_version_ ; 0x402150 ; "ERROR: Unsupported version!"
╎└──────< 0x0040133f 75e4 jne 0x401325
╎ ╎╎╎ 0x00401341 837c24131a cmp dword [rsp + 0x13], 0x1a
╎ ╎╎╎ 0x00401346 488d3d1f0e.. lea rdi, str.ERROR:_Incorrect_width_ ; 0x40216c ; "ERROR: Incorrect width!"
╎ └────< 0x0040134d 75d6 jne 0x401325
╎ ╎╎ 0x0040134f 807c241717 cmp byte [rsp + 0x17], 0x17
╎ ╎╎ 0x00401354 488d3d290e.. lea rdi, str.ERROR:_Incorrect_height_ ; 0x402184 ; "ERROR: Incorrect height!"
╎ └───< 0x0040135b 75c8 jne 0x401325
╎ ╎ 0x0040135d bf58090000 mov edi, 0x958 ; 2392
╎ ╎ 0x00401362 e859feffff call sym.imp.malloc ;[6]
╎ ╎ 0x00401367 488d3d2f0e.. lea rdi, str.ERROR:_Failed_to_allocate_memory_for_the_image_data_ ; 0x40219d ; "ERROR: Failed to all
╎ ╎ 0x0040136e 4889c3 mov rbx, rax
╎ ╎ 0x00401371 4885c0 test rax, rax
╎ └──< 0x00401374 74af je 0x401325
╎ 0x00401376 ba58090000 mov edx, 0x958 ; 2392
╎ 0x0040137b 4889c6 mov rsi, rax
╎ 0x0040137e 4183c8ff or r8d, 0xffffffff ; -1
╎ 0x00401382 31ff xor edi, edi
╎ 0x00401384 488d0d470e.. lea rcx, str.ERROR:_Failed_to_read_data_ ; 0x4021d2 ; "ERROR: Failed to read data!"
╎ 0x0040138b e8ab020000 call sym.read_exact ;[3]
╎ 0x00401390 0fb6542417 movzx edx, byte [rsp + 0x17]
╎ 0x00401395 0faf542413 imul edx, dword [rsp + 0x13]
╎ 0x0040139a 31c0 xor eax, eax
╎ ┌─> 0x0040139c 39c2 cmp edx, eax
╎ ┌──< 0x0040139e 7630 jbe 0x4013d0
╎ │╎ 0x004013a0 0fb64c8303 movzx ecx, byte [rbx + rax*4 + 3]
╎ │╎ 0x004013a5 48ffc0 inc rax
╎ │╎ 0x004013a8 8d71e0 lea esi, [rcx - 0x20]
╎ │╎ 0x004013ab 4080fe5e cmp sil, 0x5e ; '^' ; 94
╎ │└─< 0x004013af 76eb jbe 0x40139c
╎ │ 0x004013b1 488b3d882c.. mov rdi, qword [obj.stderr] ; obj.stderr__GLIBC_2.2.5
╎ │ ; [0x404040:8]=0
╎ │ 0x004013b8 488d152f0e.. lea rdx, str.ERROR:_Invalid_character_0x_x_in_the_image_data__n ; str.ERROR:_Invalid_character_0x_x_
╎ │ ; 0x4021ee ; "ERROR: Invalid character 0x%x in the image data!\n"
╎ │ 0x004013bf be01000000 mov esi, 1
╎ │ 0x004013c4 31c0 xor eax, eax
╎ │ 0x004013c6 e845feffff call sym.imp.__fprintf_chk ;[7]
└───────< 0x004013cb e95affffff jmp 0x40132a
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 *

header = b"cIMG"
version = 2
width = 26
height = 23
total_pixels = width * height

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

ascii_char = ord('A')

r, g, b = 0x8C, 0x1D, 0x40
single_pixel = struct.pack("<BBBB", r, g, b, ascii_char)

pixel_data = single_pixel * total_pixels

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

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

Behold the cIMG! (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
41
42
43
44
45
46
47
48
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(28)
assert len(header) == 28, "ERROR: Failed to read header!"

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

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

width = int.from_bytes(header[12:20], "little")

height = int.from_bytes(header[20:28], "little")

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!"

framebuffer = "".join(
bytes(pixel.ascii for pixel in pixels[row_start : row_start + width]).decode() + "\n"
for row_start in range(0, len(pixels), width)
)
print(framebuffer)

nonspace_count = sum(1 for pixel in pixels if chr(pixel.ascii) != " ")
if nonspace_count != 275: # frame size
return

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

header = b"cIMG"
version = 1
width = 25
height = 11
data_length = width * height

file_header = struct.pack("<4sQQQ", 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())

Behold the 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
// ...
#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);
}

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);
}
}

display(&cimg, data);

int num_nonspace = 0;
for (int i = 0; i < cimg.header.width * cimg.header.height; i++)
{
if (data[i].ascii != ' ') num_nonspace++;
}
if (num_nonspace != 275) won = 0; //frame size

if (won) win();
}
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 = 25
height = 11
data_length = width * height

file_header = struct.pack("<4sQHH", 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())

Behold the cIMG! (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
51
52
53
54
55
│ ┌┌─> 0x00401345      e816feffff     call sym.imp.puts           ;[2]
│┌───> 0x0040134a 83cfff or edi, 0xffffffff ; -1
│╎╎╎ 0x0040134d e8cefeffff call sym.imp.exit ;[3]
└────> 0x00401352 837c240e01 cmp dword [rsp + 0xe], 1 ; version -> 1 dword -> 4 bytes
╎╎╎ 0x00401357 488d3dd50d.. lea rdi, str.ERROR:_Unsupported_version_ ; 0x402133 ; "ERROR: Unsupported version!"
╎└──< 0x0040135e 75e5 jne 0x401345
╎ ╎ 0x00401360 440fb7642412 movzx r12d, word [rsp + 0x12]
╎ ╎ 0x00401366 440faf642414 imul r12d, dword [rsp + 0x14]
╎ ╎ 0x0040136c 4489e7 mov edi, r12d
╎ ╎ 0x0040136f e86cfeffff call sym.imp.malloc ;[4]
╎ ╎ 0x00401374 488d3dd40d.. lea rdi, str.ERROR:_Failed_to_allocate_memory_for_the_image_data_ ; 0x40214f ; "ERROR: Failed to all
╎ ╎ 0x0040137b 4889c3 mov rbx, rax
╎ ╎ 0x0040137e 4885c0 test rax, rax
╎ └─< 0x00401381 74c2 je 0x401345
╎ 0x00401383 4489e2 mov edx, r12d
╎ 0x00401386 4889c6 mov rsi, rax
╎ 0x00401389 4183c8ff or r8d, 0xffffffff ; -1
╎ 0x0040138d 31ff xor edi, edi
╎ 0x0040138f 488d0dee0d.. lea rcx, str.ERROR:_Failed_to_read_data_ ; 0x402184 ; "ERROR: Failed to read data!"
╎ 0x00401396 e880020000 call sym.read_exact ;[1]
╎ 0x0040139b 0fb7542412 movzx edx, word [rsp + 0x12] ; 2 bytes
╎ 0x004013a0 0faf542414 imul edx, dword [rsp + 0x14] ; 4 bytes
╎ 0x004013a5 31c0 xor eax, eax
╎ ┌─> 0x004013a7 39c2 cmp edx, eax
╎┌──< 0x004013a9 762f jbe 0x4013da
╎│╎ 0x004013ab 0fb60c03 movzx ecx, byte [rbx + rax]
╎│╎ 0x004013af 48ffc0 inc rax
╎│╎ 0x004013b2 8d71e0 lea esi, [rcx - 0x20]
╎│╎ 0x004013b5 4080fe5e cmp sil, 0x5e ; '^' ; 94
╎│└─< 0x004013b9 76ec jbe 0x4013a7
╎│ 0x004013bb 488b3d7e2c.. mov rdi, qword [obj.stderr] ; obj.stderr__GLIBC_2.2.5
╎│ ; [0x404040:8]=0
╎│ 0x004013c2 488d15d70d.. lea rdx, str.ERROR:_Invalid_character_0x_x_in_the_image_data__n ; str.ERROR:_Invalid_character_0x_x_
╎│ ; 0x4021a0 ; "ERROR: Invalid character 0x%x in the image data!\n"
╎│ 0x004013c9 be01000000 mov esi, 1
╎│ 0x004013ce 31c0 xor eax, eax
╎│ 0x004013d0 e85bfeffff call sym.imp.__fprintf_chk ;[5]
└───< 0x004013d5 e970ffffff jmp 0x40134a
└──> 0x004013da 4889de mov rsi, rbx
0x004013dd 4889ef mov rdi, rbp
0x004013e0 e886020000 call sym.display ;[6]
0x004013e5 0fb74c2412 movzx ecx, word [rsp + 0x12]
0x004013ea 31c0 xor eax, eax
0x004013ec 31d2 xor edx, edx
0x004013ee 0faf4c2414 imul ecx, dword [rsp + 0x14]
┌─> 0x004013f3 39c1 cmp ecx, eax
┌──< 0x004013f5 760d jbe 0x401404
│╎ 0x004013f7 803c0320 cmp byte [rbx + rax], 0x20
┌───< 0x004013fb 7402 je 0x4013ff
││╎ 0x004013fd ffc2 inc edx
└───> 0x004013ff 48ffc0 inc rax
│└─< 0x00401402 ebef jmp 0x4013f3
└──> 0x00401404 81fa13010000 cmp edx, 0x113 ; 275
┌─< 0x0040140a 7507 jne 0x401413
│ 0x0040140c 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 = 25
height = 11
data_length = width * height

file_header = struct.pack("<4sIHI", 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())

Metadata and Data (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
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(10)
assert len(header) == 10, "ERROR: Failed to read header!"

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

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

width = int.from_bytes(header[6:8], "little")
assert width == 66, "ERROR: Incorrect width!"

height = int.from_bytes(header[8:10], "little")
assert height == 17, "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]

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

header = b"CNmG"
version = 1
width = 66
height = 17
data_length = width * height

file_header = struct.pack("<4sHHH", 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())

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

Metadata and Data (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
// ...
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] != 'm' || cimg.header.magic_number[2] != '@' || 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);

if (won) win();
}
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"Cm@g"
version = 1
width = 71
height = 21
data_length = width * height

file_header = struct.pack("<4sHBH", 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{**********************************************}'

Metadata and Data (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
# ...
│ 0x004012fc 807c240d5b cmp byte [rsp + 0xd], 0x5b ; '['
│ ┌─< 0x00401301 7515 jne 0x401318
│ │ 0x00401303 807c240e4d cmp byte [rsp + 0xe], 0x4d ; 'M'
│┌──< 0x00401308 750e jne 0x401318
│││ 0x0040130a 807c240f61 cmp byte [rsp + 0xf], 0x61 ; 'a'
┌────< 0x0040130f 7507 jne 0x401318
││││ 0x00401311 807c241067 cmp byte [rsp + 0x10], 0x67 ; 'g'
┌─────< 0x00401316 7414 je 0x40132c
│└─└└─> 0x00401318 488d3df70d.. lea rdi, str.ERROR:_Invalid_magic_number_ ; 0x402116 ; "ERROR: Invalid magic number!"
┌─┌─┌┌─> 0x0040131f e81cfeffff call sym.imp.puts ;[6]
╎│╎└───> 0x00401324 83cfff or edi, 0xffffffff ; -1
╎│╎ ╎╎ 0x00401327 e8d4feffff call sym.imp.exit ;[7]
╎└─────> 0x0040132c 837c241101 cmp dword [rsp + 0x11], 1 # dword -> 4 bytes
╎ ╎ ╎╎ 0x00401331 488d3dfb0d.. lea rdi, str.ERROR:_Unsupported_version_ ; 0x402133 ; "ERROR: Unsupported version!"
└──────< 0x00401338 75e5 jne 0x40131f
╎ ╎╎ 0x0040133a 66837c241544 cmp word [rsp + 0x15], 0x44 ; 'D' word -> 2 bytes
╎ ╎╎ 0x00401340 488d3d080e.. lea rdi, str.ERROR:_Incorrect_width_ ; 0x40214f ; "ERROR: Incorrect width!"
└────< 0x00401347 75d6 jne 0x40131f
╎╎ 0x00401349 807c24170f cmp byte [rsp + 0x17], 0xf
╎╎ 0x0040134e 488d3d120e.. lea rdi, str.ERROR:_Incorrect_height_ ; 0x402167 ; "ERROR: Incorrect height!"
└──< 0x00401355 75c8 jne 0x40131f
╎ 0x00401357 bffc030000 mov edi, 0x3fc ; 1020
╎ 0x0040135c e85ffeffff call sym.imp.malloc ;[8]
╎ 0x00401361 488d3d180e.. lea rdi, str.ERROR:_Failed_to_allocate_memory_for_the_image_data_ ; 0x402180 ; "ERROR: Failed to all
╎ 0x00401368 4889c6 mov rsi, rax
╎ 0x0040136b 4885c0 test rax, rax
└─< 0x0040136e 74af je 0x40131f
0x00401370 4183c8ff or r8d, 0xffffffff ; -1
0x00401374 31ff xor edi, edi
0x00401376 bafc030000 mov edx, 0x3fc ; 1020
0x0040137b 488d0d330e.. lea rcx, str.ERROR:_Failed_to_read_data_ ; 0x4021b5 ; "ERROR: Failed to read data!"
0x00401382 e804020000 call sym.read_exact ;[5]
# ...
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"[Mag"
version = 1
width = 0x44
height = 0xf
data_length = width * height

file_header = struct.pack("<4sIHB", 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{**********************************************}'

Version Information (Python)

Programs that parse evolving file formats must be able to tell what version of the format it must parse. This is, often, stored right near the magic number.

在现代 x86 架构下,内存和二进制文件通常使用 Little-Endian (小端序)。简单来说,就是低位字节存放在低地址(前面)

  • 十进制的 1337,转换成十六进制是 0x0539
  • 如果是 2-byte (Short, <H) 存储,它不是 \x05\x39,而是反过来的 \x39\x05
  • 如果是 4-byte (Integer, <I) 存储,高位要补零,所以变成了 \x39\x05\x00\x00

Python 的 struct 模块就是用来做这种转换的完美工具。struct.pack("<I", 1337) 里的 < 代表 Little-Endian,I 代表 4 字节的无符号整数。

前缀 含义 (Byte Order) 适用场景
< Little-endian (小端序) 最常用的格式。现代 x86/x64 架构的标准。低位字节在低地址。
> Big-endian (大端序) 高位字节在低地址。常见于一些非主流或老旧的 RISC 架构。
! Network byte order (网络字节序) 实际上就是大端序。 所有网络包头部都用这个。
@ Native (本机原生) 默认值,使用本机的字节序和 C 编译器的内存对齐方式(会产生 padding 填充)。
Format (字符) C 语言对应类型 Python 对应类型 标准大小 (Bytes)
x pad byte (无) 1 用于手动填充空字节以实现内存对齐。
b signed char integer 1 8-bit 有符号整数 (-128 到 127)。
B unsigned char integer 1 8-bit 无符号整数 (0 到 255)。处理单字节 flags 时最常用。
h short integer 2 16-bit 有符号整数。
H unsigned short integer 2 16-bit 无符号整数。比如网络端口号就用这个 (!H)。
i int integer 4 32-bit 有符号整数。
I unsigned int integer 4 32-bit 无符号整数。CTF 中最常用的内存地址偏移量大小(32位系统)。
q long long integer 8 64-bit 有符号整数。
Q unsigned long long integer 8 64-bit 无符号整数。现代 64 位系统。
f float float 4 IEEE 754 单精度浮点数。
d double float 8 IEEE 754 双精度浮点数。
s char[] bytes 变长 字符串/字节数组。需要在前面加数字,比如 4s 代表 4 个字节的 bytes。

假设你需要给 /challenge/cimg 一个文件头,格式要求如下:

  1. Magic Number: "CIMG" (4 个字节)
  2. Version: 1 (16-bit 无符号整数,小端序)
  3. Payload Size: 1024 (64-bit 无符号整数,小端序)
  4. Flag: 0xFF (单字节无符号整数)
1
2
3
4
5
6
7
8
9
10
11
import struct

# < : Little-endian
# 4s : 4 bytes string ("CIMG")
# H : unsigned short (Version 1)
# Q : unsigned long long (Size 1024)
# B : unsigned char (Flag 0xFF)

file_header = struct.pack("<4sHQB", b"CIMG", 1, 1024, 0xFF)
with open("arch_crafted_payload.bin", "wb") as f:
f.write(file_header)
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
Pixel = namedtuple("Pixel", ["ascii"])

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(6)
assert len(header) == 6, "ERROR: Failed to read header!"

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

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

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


file_header = struct.pack("<4sH", b"c:MG", 51 )

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

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

b'pwn.college{**********************************************}'

Version Information (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
//...
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] != '{' || cimg.header.magic_number[1] != 'M' || cimg.header.magic_number[2] != 'A' || cimg.header.magic_number[3] != 'G')
{
puts("ERROR: Invalid magic number!");
exit(-1);
}

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

if (won) win();

}
1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
from pwn import process


file_header = struct.pack("<4sQ", b"{MAG", 168 )

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

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

b'pwn.college{**********************************************}'

Version Information (x86)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
            ;-- main:
# ...
│ 0x004012d0 488d0d210e.. lea rcx, str.ERROR:_Failed_to_read_header_ ; 0x4020f8 ; "ERROR: Failed to read header!"
│ 0x004012d7 e83f020000 call sym.read_exact ;[5]
│ 0x004012dc 807c240343 cmp byte [rsp + 3], 0x43 ; 'C'
│ ┌─< 0x004012e1 7515 jne 0x4012f8
│ │ 0x004012e3 807c24044d cmp byte [rsp + 4], 0x4d ; 'M'
│┌──< 0x004012e8 750e jne 0x4012f8
│││ 0x004012ea 807c240561 cmp byte [rsp + 5], 0x61 ; 'a'
┌────< 0x004012ef 7507 jne 0x4012f8
││││ 0x004012f1 807c240667 cmp byte [rsp + 6], 0x67 ; 'g'
┌─────< 0x004012f6 7414 je 0x40130c
│└─└└─> 0x004012f8 488d3d170e.. lea rdi, str.ERROR:_Invalid_magic_number_ ; 0x402116 ; "ERROR: Invalid magic number!"
│ │ ┌─> 0x004012ff e82cfeffff call sym.imp.puts ;[6]
│ └───> 0x00401304 83cfff or edi, 0xffffffff ; -1
│ ╎ 0x00401307 e8d4feffff call sym.imp.exit ;[7]
└─────> 0x0040130c 807c240764 cmp byte [rsp + 7], 0x64 ; 'd'
╎ 0x00401311 488d3d1b0e.. lea rdi, str.ERROR:_Unsupported_version_ ; 0x402133 ; "ERROR: Unsupported version!"
#...
1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
from pwn import process


file_header = struct.pack("<4sB", b"CMag", 0x64 )

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

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

b'pwn.college{**********************************************}'

Reading Endianness (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
Pixel = namedtuple("Pixel", ["ascii"])

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(4)
assert len(header) == 4, "ERROR: Failed to read header!"

assert int.from_bytes(header[:4], "little") == 0x72254F3C, "ERROR: Invalid magic number!"

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
hacker@reverse-engineering~reading-endianness-python:~$ xxd payload.cimg
00000000: 3c4f 2572 0000 0000 0000 0000 0000 0000 <O%r............
hacker@reverse-engineering~reading-endianness-python:~$ /challenge/cimg payload.cimg
pwn.college{**********************************************}

Reading Endianness (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
//...
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 != 1198345851)
{
puts("ERROR: Invalid magic number!");
exit(-1);
}

if (won) win();
1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
from pwn import process

magic_number = 1198345851
# or 4 bytes
magic_number = magic_number.to_bytes(256, "little")
file = open("payload.cimg", "wb")
file.write(magic_number)
file.close()

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

b'pwn.college{**********************************************}'

Reading Endianness (x86)

radare2 btw

1
2
3
4
5
6
7
8
9
10
11
12
13
14
│     0x004012be      488d742404     lea rsi, [rsp + 4]
│ 0x004012c3 ba04000000 mov edx, 4
│ 0x004012c8 488d0d290e.. lea rcx, str.ERROR:_Failed_to_read_header_ ; 0x4020f8 ; "ERROR: Failed to read header!"
│ 0x004012cf e827020000 call sym.read_exact ;[5]
│ 0x004012d4 817c24043c.. cmp dword [rsp + 4], 0x72254f3c ; '<O%r'
│ ┌─< 0x004012dc 7414 je 0x4012f2
│ │ 0x004012de 488d3d310e.. lea rdi, str.ERROR:_Invalid_magic_number_ ; 0x402116 ; "ERROR: Invalid magic number!"
│ │ 0x004012e5 e846feffff call sym.imp.puts ;[6]
└───> 0x004012ea 83cfff or edi, 0xffffffff ; -1
│ 0x004012ed e8eefeffff call sym.imp.exit ;[7]
└─> 0x004012f2 31c0 xor eax, eax
0x004012f4 e80d010000 call sym.win ;[8]
0x004012f9 488b442408 mov rax, qword [rsp + 8]
0x004012fe 6448330425.. xor rax, qword fs:[0x28]
1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
from pwn import process

magic_number = 0x72254f3c
# or 4 bytes
magic_number = magic_number.to_bytes(256, "little")
file = open("payload.cimg", "wb")
file.write(magic_number)
file.close()

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

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

File Formats: Magic Numbers(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
#!/usr/bin/exec-suid -- /usr/bin/python3 -I

import os
import sys
from collections import namedtuple

Pixel = namedtuple("Pixel", ["ascii"])

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(4)
assert len(header) == 4, "ERROR: Failed to read header!"

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

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
# -n option -> no newline
hacker@reverse-engineering~file-formats-magic-numbers-python:~$ echo -n "{:m6" > payload.cimg
hacker@reverse-engineering~file-formats-magic-numbers-python:~$ /challenge/cimg payload.cimg
pwn.college{**********************************************}

File Formats: Magic Numbers(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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
void win()
{
char flag[256];
int flag_fd;
int flag_length;

flag_fd = open("/flag", 0);
if (flag_fd < 0)
{
printf("\n ERROR: Failed to open the flag -- %s!\n", strerror(errno));
if (geteuid() != 0)
{
printf(" Your effective user id is not 0!\n");
printf(" You must directly run the suid binary in order to have the correct permissions!\n");
}
exit(-1);
}
flag_length = read(flag_fd, flag, sizeof(flag));
if (flag_length <= 0)
{
printf("\n ERROR: Failed to read the flag -- %s!\n", strerror(errno));
exit(-1);
}
write(1, flag, flag_length);
printf("\n\n");
}

void read_exact(int fd, void *dst, int size, char *msg, int exitcode)
{
int n = read(fd, dst, size);
if (n != size)
{
fprintf(stderr, msg);
fprintf(stderr, "\n");
exit(exitcode);
}
}

struct cimg_header
{
char magic_number[4];
} __attribute__((packed));

typedef struct
{
uint8_t ascii;
} pixel_bw_t;
typedef pixel_bw_t pixel_t;

struct cimg
{
struct cimg_header header;
};

void __attribute__ ((constructor)) disable_buffering()
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 1);
}

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] != '<' || cimg.header.magic_number[1] != 'O' || cimg.header.magic_number[2] != '%' || cimg.header.magic_number[3] != 'r')
{
puts("ERROR: Invalid magic number!");
exit(-1);
}

if (won) win();

}
1
2
3
4
echo -n "<O%r" > payload.cimg

hacker@reverse-engineering~file-formats-magic-numbers-c:~$ /challenge/cimg payload.cimg
pwn.college{U8Qj1nr1Y8CCrAcigFixhqFtuxF.QX2ATN2EDL4cjM1gzW}

File Formats: Magic Numbers (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
0x00401546      488b45d0       mov rax, qword [rbp - 0x30]
0x0040154a 4883c008 add rax, 8
0x0040154e 488b00 mov rax, qword [rax]
0x00401551 4889c7 mov rdi, rax
0x00401554 e807fcffff call sym.imp.strlen ;[1]
0x00401559 4883e805 sub rax, 5
0x0040155d 4801d8 add rax, rbx
0x00401560 488d35850b.. lea rsi, str..cimg ; 0x4020ec ; ".cimg"
0x00401567 4889c7 mov rdi, rax
0x0040156a e861fcffff call sym.imp.strcmp ;[2]
0x0040156f 85c0 test eax, eax
0x00401571 741b je 0x40158e
0x00401573 488d3d7e0b.. lea rdi, str.ERROR:_Invalid_file_extension_ ; 0x4020f8 ; "ERROR: Invalid file extension!"
0x0040157a b800000000 mov eax, 0
0x0040157f e80cfcffff call sym.imp.printf ;[3]
0x00401584 bfffffffff mov edi, 0xffffffff ; -1
0x00401589 e882fcffff call sym.imp.exit ;[4]
0x0040158e 488b45d0 mov rax, qword [rbp - 0x30]
0x00401592 4883c008 add rax, 8
0x00401596 488b00 mov rax, qword [rax]
0x00401599 be00000000 mov esi, 0
0x0040159e 4889c7 mov rdi, rax
0x004015a1 b800000000 mov eax, 0
0x004015a6 e855fcffff call sym.imp.open ;[5]
0x004015ab be00000000 mov esi, 0
0x004015b0 89c7 mov edi, eax
0x004015b2 e8c9fbffff call sym.imp.dup2 ;[6]
0x004015b7 488d45e4 lea rax, [rbp - 0x1c]
0x004015bb 41b8ffffffff mov r8d, 0xffffffff ; -1
0x004015c1 488d0d4f0b.. lea rcx, str.ERROR:_Failed_to_read_header_ ; 0x402117 ; "ERROR: Failed to read header!"
0x004015c8 ba04000000 mov edx, 4
0x004015cd 4889c6 mov rsi, rax
0x004015d0 bf00000000 mov edi, 0
0x004015d5 e863feffff call sym.read_exact ;[7]
0x004015da 0fb645e4 movzx eax, byte [rbp - 0x1c]
0x004015de 3c7b cmp al, 0x7b ; '{' ; 123
0x004015e0 7518 jne 0x4015fa
0x004015e2 0fb645e5 movzx eax, byte [rbp - 0x1b]
0x004015e6 3c6e cmp al, 0x6e ; 'n' ; 110
0x004015e8 7510 jne 0x4015fa
0x004015ea 0fb645e6 movzx eax, byte [rbp - 0x1a]
0x004015ee 3c6d cmp al, 0x6d ; 'm' ; 109
0x004015f0 7508 jne 0x4015fa
0x004015f2 0fb645e7 movzx eax, byte [rbp - 0x19]
0x004015f6 3c36 cmp al, 0x36 ; '6' ; 54
0x004015f8 7416 je 0x401610
0x004015fa 488d3d340b.. lea rdi, str.ERROR:_Invalid_magic_number_ ; 0x402135 ; "ERROR: Invalid magic number!"
0x00401601 e83afbffff call sym.imp.puts ;[8]
0x00401606 bfffffffff mov edi, 0xffffffff ; -1
0x0040160b e800fcffff call sym.imp.exit ;[4]
1
2
3
echo -n "{nm6" > payload.cimg
hacker@reverse-engineering~file-formats-magic-numbers-x86:~$ /challenge/cimg payload.cimg
pwn.college{**********************************************}
+ + +
SYSTEM STATUS: ACTIVE ENCRYPTED SECTOR 7 PRTS_TERMINAL_V2.0 PROTOCOL: 0x2A ENCRYPTED DATA STREAM SYSTEM: ONLINE