PwnCollege - RE - Internal State

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