Fading Coder

One Final Commit for the Last Sprint

Home > Tools > Content

Solving Hackpack 2023 Reverse Engineering Challenges with Automated Tools

Tools 1

Competition Overview

The Hackpack 2023 CTF featured several reverse engineering challenges available at https://ctf2023.hackpack.club/challenges. This writeup covers two main challenge categories: the Speed-Rev series and a WebAssembly challenge.

Speed-Rev Challenge Analysis

The Speed-Rev challenge required solving 6 reverse engineering problems within a 3-minute timeframe. The challenges progressively increased in complexity:

  • Challenges 1-3: Direct string extraction and simple string splitting operations
  • Challenges 4-6: More complex character relationship analysis requiring pattern matching

Network Communication Setup

The challenges utilized network-based flag submission, requiring automated solutions. The nclib library proved essential for handling TCP connections:

import nclib
import base64
import string

TIMEOUT = 2
connection = nclib.Netcat(('challenge.hackpack.club', 41702))

Solving Early Challenges

The first challenge involved extracting base64-encoded data from server responses:

response = connection.recv_all(timeout=TIMEOUT).decode()
encoded_data = response[response.find('b\'')+2:-20].encode()
decoded = base64.decodebytes(encoded_data)
flag_part = decoded[8196:8212]
print(flag_part.decode())
connection.send(flag_part + b'\n')

Challenges 2 and 3 used similar extraction patterns with additional string manipulation:

response = connection.recv_all(timeout=TIMEOUT).decode()
data = base64.decodebytes(response[response.find('b\'')+2:-20].encode())
fragments = data[4436:8212].split(b'<')
result = b''
for index in range(1, 17):
    result += fragments[index][:1]
print(result.decode())
connection.send(result + b'\n')

Handling Ambiguous Solutions

Challenges 4-6 could produce multiple valid flags. The solution involved filtering results to ensure only alphanumeric characters:

def is_alphanumeric(candidate: str) -> bool:
    return all(char in string.ascii_letters + string.digits for char in candidate)

response = connection.recv_all(timeout=TIMEOUT).decode()
data = base64.decodebytes(response[response.find('b\'')+2:-20].encode())
fragments = data[4448:5204].split(b't')

values = []
for segment in fragments:
    if segment[-1] == 0 and segment[-5] == ord('='):
        values.append(segment[-4])
    else:
        if segment[-1] != 233:
            values.append(segment[-1])

candidates = []
for char in string.ascii_letters + string.digits:
    attempt = [char]
    valid = True
    for idx in range(15):
        if values[idx] - ord(attempt[idx]) > 0:
            attempt.append(chr(values[idx] - ord(attempt[idx])))
        else:
            valid = False
            break
    if valid:
        candidates.append(''.join(attempt))

for candidate in candidates:
    if is_alphanumeric(candidate):
        print(candidate)
        connection.send(candidate.encode() + b'\n')
        break

Automated Solution Using Angr

For a more robust approach, the angr symbolic execution framework can automatically solve binary validation functions:

#!/usr/bin/env python3
from angr import Project, SimState
from claripy import *
from pwn import remote
from base64 import b64decode

def extract_response(sock, lines=1):
    data = b''
    for _ in range(lines):
        data += sock.recvline(keepends=True)
    return data

def submit_solution(sock, payload):
    if isinstance(payload, int):
        payload = str(payload)
    if isinstance(payload, str):
        payload = payload.encode('utf-8')
    sock.sendline(payload)

def solve_binary(filepath):
    project = Project(filepath, auto_load_libs=False, main_opts={'base_addr': 0})
    
    validation_func = project.loader.main_object.get_symbol('validate')
    assert validation_func.is_function
    func_address = validation_func.linked_addr
    
    flag_buffer = BVS('flag', 128)
    state = project.factory.call_state(func_address, flag_buffer)
    
    for position in range(16):
        state.solver.add(Or(
            And(flag_buffer.get_byte(position) >= ord('A'), 
                flag_buffer.get_byte(position) <= ord('Z')),
            And(flag_buffer.get_byte(position) >= ord('a'), 
                flag_buffer.get_byte(position) <= ord('z')),
            And(flag_buffer.get_byte(position) >= ord('0'), 
                flag_buffer.get_byte(position) <= ord('9')),
        ))
    
    state.memory.store(state.regs.rsp, flag_buffer)
    state.regs.rdi = state.regs.rsp
    state.stack_push(0)
    state.stack_push(0)
    
    simgr = project.factory.simulation_manager(state)
    simgr.run()
    
    result = b'1' * 16
    for ended_state in simgr.deadended:
        ended_state.solver.add(ended_state.regs.eax == 0)
        if ended_state.satisfiable():
            result = ended_state.solver.eval(flag_buffer, cast_to=bytes)
    
    return result

def main():
    socket = remote('challenge.hackpack.club', 41702)
    extract_response(socket, 2)
    
    for challenge_num in range(6):
        while True:
            response = extract_response(socket, echo=False)
            if response.startswith(b'b\''):
                break
        
        challenge_data = b64decode(response[2:-2])
        extract_response(socket)
        
        filename = f'challenge_{challenge_num + 1}'
        with open(filename, 'wb') as f:
            f.write(challenge_data)
        
        solution = solve_binary(filename)
        submit_solution(socket, solution)
        
        if extract_response(socket) == b'Wrong!\n':
            break
    else:
        extract_response(socket)

if __name__ == '__main__':
    main()

This script automatically downloads each challenge binary, uses angr to symbolically execute the validation function, and extracts the correct flag by constraining the input to alphanumeric characters and checking for successful validation (return value of 0).

WASM-Safe Challenge

The WebAssembly challenge required decompiling and analyzing WASM binaries. Tools like Jeb or dedicated WASM disassemblers can be used for this purpose. While not fully solved, working with this challenge provided valuable experience with WebAssembly reverse engineering techniques.

Key Takeaways

  1. Symbolic Execution: Using angr eliminates the need for manual binary analysis by automatically exploring execution paths
  2. Network Automation: The nclib libray simplifies TCP communication for CTF challenges
  3. Pattern Recognition: Identifying special byte sequences (like the 't' delimiter in this challenge) helps locate relevant data
  4. Solution Filtering: When multiple valid solutions exist, filtering by character constraints helps identify the correct flag

Related Articles

Efficient Usage of HTTP Client in IntelliJ IDEA

IntelliJ IDEA incorporates a versatile HTTP client tool, enabling developres to interact with RESTful services and APIs effectively with in the editor. This functionality streamlines workflows, replac...

Installing CocoaPods on macOS Catalina (10.15) Using a User-Managed Ruby

System Ruby on macOS 10.15 frequently fails to build native gems required by CocoaPods (for example, ffi), leading to errors like: ERROR: Failed to build gem native extension checking for ffi.h... no...

Resolve PhpStorm "Interpreter is not specified or invalid" on WAMP (Windows)

Symptom PhpStorm displays: "Interpreter is not specified or invalid. Press ‘Fix’ to edit your project configuration." This occurs when the IDE cannot locate a valid PHP CLI executable or when the debu...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.