PwnCollege - RE - Patch Directive

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