PwnCollege - RE - Storage and Retrieval

Storage and Retrieval

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

Wikipedia

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// ...
struct cimg_sprite
{
uint8_t height;
uint8_t width;
pixel_bw_t *data;
};

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

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

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

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

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

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

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

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

initialize_framebuffer(&cimg);

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

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

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

if (total_data > 400) won = 0;

if (won) win();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
handle_1 (0x0001): Basic 直接按全局宽高的 pixel_t(每个 4 bytes: R, G, B, ASCII)读取全图数据。
# ...
handle_2 (0x0002): Patch 读取 base_x, base_y, width, height (共 4 bytes),然后再读取对应区域的完整 pixel_t 数组。
# ...
handle_3 (0x0003): 定义 Sprite。
参数: sprite_id (1 byte), width (1 byte), height (1 byte)。
数据流: malloc 并读取 width * height bytes 的单字节 ASCII 数据(在 asm 的 0x401bde 处有校验,只允许 printable chars 0x20 到 0x5e。
不带任何颜色信息 一个 10x10 的纯字符图案只需要 2 (code) + 3 (args) + 100 (data) = 105 bytes。
;-- handle_3:
0x00401ae1 f30f1efa endbr64
0x00401ae5 4154 push r12
0x00401ae7 4183c8ff or r8d, 0xffffffff ; -1
0x00401aeb ba01000000 mov edx, 1
0x00401af0 488d0df706.. lea rcx, str.ERROR:_Failed_to_read_sprite_id_ ; 0x4021ee ; "ERROR: Failed to read &sprite_id!"
0x00401af7 55 push rbp
0x00401af8 4889fd mov rbp, rdi
0x00401afb 31ff xor edi, edi
0x00401afd 53 push rbx
0x00401afe 4883ec10 sub rsp, 0x10
0x00401b02 64488b0425.. mov rax, qword fs:[0x28]
0x00401b0b 4889442408 mov qword [rsp + 8], rax
0x00401b10 31c0 xor eax, eax
0x00401b12 488d742405 lea rsi, [rsp + 5]
0x00401b17 e8dffbffff call sym.read_exact ;[1]
0x00401b1c 488d742406 lea rsi, [rsp + 6]
0x00401b21 4183c8ff or r8d, 0xffffffff ; -1
0x00401b25 31ff xor edi, edi
0x00401b27 488d0d8306.. lea rcx, str.ERROR:_Failed_to_read_width_ ; 0x4021b1 ; "ERROR: Failed to read &width!"
0x00401b2e ba01000000 mov edx, 1
0x00401b33 e8c3fbffff call sym.read_exact ;[1]
0x00401b38 ba01000000 mov edx, 1
0x00401b3d 31ff xor edi, edi
0x00401b3f 4183c8ff or r8d, 0xffffffff ; -1
0x00401b43 488d742407 lea rsi, [rsp + 7]
0x00401b48 488d0d8006.. lea rcx, str.ERROR:_Failed_to_read_height_ ; 0x4021cf ; "ERROR: Failed to read &height!"
0x00401b4f e8a7fbffff call sym.read_exact ;[1]
0x00401b54 0fb6442405 movzx eax, byte [rsp + 5]
0x00401b59 8a542406 mov dl, byte [rsp + 6]
0x00401b5d 48c1e004 shl rax, 4
0x00401b61 4801e8 add rax, rbp
0x00401b64 885019 mov byte [rax + 0x19], dl
0x00401b67 488b7820 mov rdi, qword [rax + 0x20]
0x00401b6b 8a542407 mov dl, byte [rsp + 7]
0x00401b6f 885018 mov byte [rax + 0x18], dl
0x00401b72 4885ff test rdi, rdi
┌─< 0x00401b75 7405 je 0x401b7c
│ 0x00401b77 e804f6ffff call sym.imp.free ;[2]
└─> 0x00401b7c 440fb6642406 movzx r12d, byte [rsp + 6]
0x00401b82 0fb6542407 movzx edx, byte [rsp + 7]
0x00401b87 440fafe2 imul r12d, edx
0x00401b8b 4963fc movsxd rdi, r12d
0x00401b8e e8adf6ffff call sym.imp.malloc ;[3]
0x00401b93 4889c3 mov rbx, rax
0x00401b96 4885c0 test rax, rax
┌─< 0x00401b99 750e jne 0x401ba9
│ 0x00401b9b 488d3d3105.. lea rdi, str.ERROR:_Failed_to_allocate_memory_for_the_image_data_ ; 0x4020d3 ; "ERROR: Failed to allocate memory for the image data!"
│ 0x00401ba2 e8f9f5ffff call sym.imp.puts ;[4]
┌──< 0x00401ba7 eb55 jmp 0x401bfe
│└─> 0x00401ba9 4489e2 mov edx, r12d
│ 0x00401bac 4889c6 mov rsi, rax
│ 0x00401baf 4183c8ff or r8d, 0xffffffff ; -1
│ 0x00401bb3 31ff xor edi, edi
│ 0x00401bb5 488d0d4c05.. lea rcx, str.ERROR:_Failed_to_read_data_ ; 0x402108 ; "ERROR: Failed to read data!"
│ 0x00401bbc e83afbffff call sym.read_exact ;[1]
│ 0x00401bc1 0fb6442407 movzx eax, byte [rsp + 7]
│ 0x00401bc6 0fb6542406 movzx edx, byte [rsp + 6]
│ 0x00401bcb 0fafd0 imul edx, eax
│ 0x00401bce 31c0 xor eax, eax
│┌─> 0x00401bd0 39c2 cmp edx, eax
┌───< 0x00401bd2 7e32 jle 0x401c06
││╎ 0x00401bd4 0fb60c03 movzx ecx, byte [rbx + rax]
││╎ 0x00401bd8 48ffc0 inc rax
││╎ 0x00401bdb 8d71e0 lea esi, [rcx - 0x20]
││╎ 0x00401bde 4080fe5e cmp sil, 0x5e ; '^' ; 94
││└─< 0x00401be2 76ec jbe 0x401bd0
││ 0x00401be4 488b3d75cf.. mov rdi, qword [obj.stderr] ; obj.stderr__GLIBC_2.2.5
││ ; [0x40eb60:8]=0
││ 0x00401beb 488d153205.. lea rdx, str.ERROR:_Invalid_character_0x_x_in_the_image_data__n ; str.ERROR:_Invalid_character_0x_x_in_the_image_data__n
││ ; 0x402124 ; "ERROR: Invalid character 0x%x in the image data!\n"
││ 0x00401bf2 be01000000 mov esi, 1
││ 0x00401bf7 31c0 xor eax, eax
││ 0x00401bf9 e892f6ffff call sym.imp.__fprintf_chk ;[5]
│└──> 0x00401bfe 83cfff or edi, 0xffffffff ; -1
│ 0x00401c01 e87af6ffff call sym.imp.exit ;[6]
└───> 0x00401c06 0fb6442405 movzx eax, byte [rsp + 5]
0x00401c0b 48c1e004 shl rax, 4
0x00401c0f 48895c2820 mov qword [rax + rbp + 0x20], rbx
0x00401c14 488b442408 mov rax, qword [rsp + 8]
0x00401c19 6448330425.. xor rax, qword fs:[0x28]
┌─< 0x00401c22 7405 je 0x401c29
│ 0x00401c24 e897f5ffff call sym.imp.__stack_chk_fail ;[7]
└─> 0x00401c29 4883c410 add rsp, 0x10
0x00401c2d 5b pop rbx
0x00401c2e 5d pop rbp
0x00401c2f 415c pop r12
0x00401c31 c3 ret

handle_4 (0x0004): 渲染 Sprite。
参数: 读取 6 bytes: sprite_id (1 byte), R (1 byte), G (1 byte), B (1 byte), base_x (1 byte), base_y (1 byte)。
逻辑: 将你在 handle_3 中注册的裸字符 sprite,刷上统一的 RGB 颜色,并按指定的坐标渲染到 framebuffer 上。
整个指令只需要 2 (code) + 6 (args) = 8 bytes。
;-- handle_4:
0x00401c32 f30f1efa endbr64
0x00401c36 4157 push r15
0x00401c38 4156 push r14
0x00401c3a 4155 push r13
0x00401c3c 4154 push r12
0x00401c3e 55 push rbp
0x00401c3f 53 push rbx
0x00401c40 4c8d9c2400.. lea r11, [rsp - 0x40000]
┌─> 0x00401c48 4881ec0010.. sub rsp, 0x1000
╎ 0x00401c4f 830c2400 or dword [rsp], 0
╎ 0x00401c53 4c39dc cmp rsp, r11
└─< 0x00401c56 75f0 jne 0x401c48
0x00401c58 4883ec38 sub rsp, 0x38
0x00401c5c 488d0dad05.. lea rcx, str.ERROR:_Failed_to_read_sprite_render_record_ ; 0x402210 ; "ERROR: Failed to read &sprite_render_record!"
0x00401c63 ba06000000 mov edx, 6
0x00401c68 4183c8ff or r8d, 0xffffffff ; -1
0x00401c6c 64488b0425.. mov rax, qword fs:[0x28]
0x00401c75 4889842428.. mov qword [rsp + 0x40028], rax ; [0x40028:8]=-1
0x00401c7d 31c0 xor eax, eax
0x00401c7f 4889fb mov rbx, rdi
0x00401c82 488d742409 lea rsi, [rsp + 9]
0x00401c87 31ff xor edi, edi
0x00401c89 e86dfaffff call sym.read_exact ;[2]
0x00401c8e 488d7c240f lea rdi, [rsp + 0xf]
0x00401c93 b900000100 mov ecx, 0x10000
0x00401c98 31c0 xor eax, eax
0x00401c9a 0fb6542409 movzx edx, byte [rsp + 9]
0x00401c9f 448a54240a mov r10b, byte [rsp + 0xa]
0x00401ca4 488d74240f lea rsi, [rsp + 0xf]
0x00401ca9 f3ab rep stosd dword [rdi], eax
0x00401cab 448a5c240b mov r11b, byte [rsp + 0xb]
0x00401cb0 408a6c240c mov bpl, byte [rsp + 0xc]
0x00401cb5 48c1e204 shl rdx, 4
0x00401cb9 4801da add rdx, rbx
0x00401cbc 440fb66218 movzx r12d, byte [rdx + 0x18]
┌─> 0x00401cc1 4139cc cmp r12d, ecx
┌──< 0x00401cc4 7e58 jle 0x401d1e
│╎ 0x00401cc6 440fb64219 movzx r8d, byte [rdx + 0x19]
│╎ 0x00401ccb 31ff xor edi, edi
│╎ 0x00401ccd 4489c0 mov eax, r8d
│╎ 0x00401cd0 0fafc1 imul eax, ecx
┌───> 0x00401cd3 4139f8 cmp r8d, edi
┌────< 0x00401cd6 7e42 jle 0x401d1a
│╎│╎ 0x00401cd8 4c8b4a20 mov r9, qword [rdx + 0x20]
│╎│╎ 0x00401cdc 44881486 mov byte [rsi + rax*4], r10b
│╎│╎ 0x00401ce0 44885c8601 mov byte [rsi + rax*4 + 1], r11b
│╎│╎ 0x00401ce5 40886c8602 mov byte [rsi + rax*4 + 2], bpl
│╎│╎ 0x00401cea 4d85c9 test r9, r9
┌─────< 0x00401ced 751b jne 0x401d0a
││╎│╎ 0x00401cef 488b356ace.. mov rsi, qword [obj.stderr] ; obj.stderr__GLIBC_2.2.5
││╎│╎ ; [0x40eb60:8]=0
││╎│╎ 0x00401cf6 488d3d4005.. lea rdi, str.ERROR:_attempted_to_render_uninitialized_sprite__n ; 0x40223d ; "ERROR: attempted to render uninitialized sprite!\n"
││╎│╎ 0x00401cfd e8def4ffff call sym.imp.fputs ;[3]
││╎│╎ 0x00401d02 83cfff or edi, 0xffffffff ; -1
││╎│╎ 0x00401d05 e876f5ffff call sym.imp.exit ;[4]
└─────> 0x00401d0a 458a0c01 mov r9b, byte [r9 + rax]
│╎│╎ 0x00401d0e ffc7 inc edi
│╎│╎ 0x00401d10 44884c8603 mov byte [rsi + rax*4 + 3], r9b
│╎│╎ 0x00401d15 48ffc0 inc rax
│└───< 0x00401d18 ebb9 jmp 0x401cd3
└────> 0x00401d1a ffc1 inc ecx
│└─< 0x00401d1c eba3 jmp 0x401cc1
└──> 0x00401d1e 440fb674240e movzx r14d, byte [rsp + 0xe]
0x00401d24 440fb67c240d movzx r15d, byte [rsp + 0xd]
0x00401d2a 4531ed xor r13d, r13d
┌─> 0x00401d2d 0fb6442409 movzx eax, byte [rsp + 9]
╎ 0x00401d32 48c1e004 shl rax, 4
╎ 0x00401d36 0fb6441818 movzx eax, byte [rax + rbx + 0x18]
╎ 0x00401d3b 4439e8 cmp eax, r13d
┌──< 0x00401d3e 0f8eaf000000 jle 0x401df3
│╎ 0x00401d44 31ed xor ebp, ebp
┌───> 0x00401d46 0fb6442409 movzx eax, byte [rsp + 9]
╎│╎ 0x00401d4b 48c1e004 shl rax, 4
╎│╎ 0x00401d4f 0fb64c1819 movzx ecx, byte [rax + rbx + 0x19]
╎│╎ 0x00401d54 39e9 cmp ecx, ebp
┌────< 0x00401d56 0f8e8c000000 jle 0x401de8
│╎│╎ 0x00401d5c 410fafcd imul ecx, r13d
│╎│╎ 0x00401d60 488dbc240f.. lea rdi, [rsp + 0x4000f]
│╎│╎ 0x00401d68 50 push rax
│╎│╎ 0x00401d69 ba01000000 mov edx, 1
│╎│╎ 0x00401d6e 4c8d05e103.. lea r8, str.e_38_2__03d__03d__03dm_ce_0m ; 0x402156
│╎│╎ 0x00401d75 be19000000 mov esi, 0x19 ; 25
│╎│╎ 0x00401d7a 440fb66306 movzx r12d, byte [rbx + 6]
│╎│╎ 0x00401d7f 01e9 add ecx, ebp
│╎│╎ 0x00401d81 4863c9 movsxd rcx, ecx
│╎│╎ 0x00401d84 0fb6448c1a movzx eax, byte [rsp + rcx*4 + 0x1a]
│╎│╎ 0x00401d89 50 push rax
│╎│╎ 0x00401d8a 0fb6448c21 movzx eax, byte [rsp + rcx*4 + 0x21]
│╎│╎ 0x00401d8f 50 push rax
│╎│╎ 0x00401d90 0fb6448c28 movzx eax, byte [rsp + rcx*4 + 0x28]
│╎│╎ 0x00401d95 50 push rax
│╎│╎ 0x00401d96 440fb64c8c2f movzx r9d, byte [rsp + rcx*4 + 0x2f]
│╎│╎ 0x00401d9c 31c0 xor eax, eax
│╎│╎ 0x00401d9e b919000000 mov ecx, 0x19 ; 25
│╎│╎ 0x00401da3 e8c8f3ffff call sym.imp.__snprintf_chk ;[5]
│╎│╎ 0x00401da8 428d443d00 lea eax, [rbp + r15]
│╎│╎ 0x00401dad 4883c420 add rsp, 0x20
│╎│╎ 0x00401db1 ffc5 inc ebp
│╎│╎ 0x00401db3 99 cdq
│╎│╎ 0x00401db4 0f1084240f.. movups xmm0, xmmword [rsp + 0x4000f]
│╎│╎ 0x00401dbc 41f7fc idiv r12d
│╎│╎ 0x00401dbf 450fafe6 imul r12d, r14d
│╎│╎ 0x00401dc3 428d0422 lea eax, [rdx + r12]
│╎│╎ 0x00401dc7 31d2 xor edx, edx
│╎│╎ 0x00401dc9 f7730c div dword [rbx + 0xc]
│╎│╎ 0x00401dcc 486bd218 imul rdx, rdx, 0x18
│╎│╎ 0x00401dd0 48035310 add rdx, qword [rbx + 0x10]
│╎│╎ 0x00401dd4 0f1102 movups xmmword [rdx], xmm0
│╎│╎ 0x00401dd7 488b84241f.. mov rax, qword [rsp + 0x4001f]
│╎│╎ 0x00401ddf 48894210 mov qword [rdx + 0x10], rax
│└───< 0x00401de3 e95effffff jmp 0x401d46
└────> 0x00401de8 41ffc5 inc r13d
│╎ 0x00401deb 41ffc6 inc r14d
│└─< 0x00401dee e93affffff jmp 0x401d2d
└──> 0x00401df3 488b842428.. mov rax, qword [rsp + 0x40028]
0x00401dfb 6448330425.. xor rax, qword fs:[0x28]
┌─< 0x00401e04 7405 je 0x401e0b
│ 0x00401e06 e8b5f3ffff call sym.imp.__stack_chk_fail ;[1]
└─> 0x00401e0b 4881c43800.. add rsp, 0x40038
0x00401e12 5b pop rbx
0x00401e13 5d pop rbp
0x00401e14 415c pop r12
0x00401e16 415d pop r13
0x00401e18 415e pop r14
0x00401e1a 415f pop r15
0x00401e1c c3 ret
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
import struct

from pwn import *

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

sprites = {}
renders = []

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

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

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

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

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

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

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

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

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

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


if __name__ == "__main__":
build_payload()

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