Future Fun

This reversing challenge dropped a nearly 10MB binary on us. Given that I had no intention of reversing this mess, I took another route to solve this problem.

The challenge description has interesting bits in it though:

Deep on the web, I discovered a secret key validation. It appeared to be from the future, and it only had one sentence: "Risk speed for security". Something seems fishy, you should try to break the key and find the secret inside!

The reference to "speed" and the presence of a waste_time function made me wonder if I was meant to execute a timing attack against the binary. Quick tests showed that yes, it was very likely.

Knowing that the flag format is flag{..., I timed the execution of the binary with different inputs:

justin@kali:~/swampctf/future_fun$ time ./future_fun > /dev/null <<< "a"

real    0m0.008s
user    0m0.007s
sys     0m0.000s
justin@kali:~/swampctf/future_fun$ time ./future_fun > /dev/null <<< "a"

real    0m0.008s
user    0m0.007s
sys     0m0.000s
justin@kali:~/swampctf/future_fun$ time ./future_fun > /dev/null <<< "f"

real    0m0.013s
user    0m0.012s
sys     0m0.000s
justin@kali:~/swampctf/future_fun$ time ./future_fun > /dev/null <<< "fl"

real    0m0.018s
user    0m0.017s
sys     0m0.000s
justin@kali:~/swampctf/future_fun$ time ./future_fun > /dev/null <<< "fa"

real    0m0.014s
user    0m0.004s
sys     0m0.008s
justin@kali:~/swampctf/future_fun$ time ./future_fun > /dev/null <<< "fq"

real    0m0.014s
user    0m0.013s
sys     0m0.000s
justin@kali:~/swampctf/future_fun$ time ./future_fun > /dev/null <<< "fl"

real    0m0.019s
user    0m0.018s
sys     0m0.000s

As you can see above, the inputs with the correct first few characters of flag take longer to run.

I then wrote the following script:

guess.sh:

for i in {1..10}
do
  ./future_fun <<< $1 > /dev/null
done
import re
import string
import subprocess

def guess(inp):
    result = subprocess.run(["/bin/bash", "-c", "time ./guess.sh {}".format(inp)], stderr=subprocess.PIPE)
    match = re.search(r'real\t0m(\d+\.\d{3})', result.stderr.decode("ascii"))
    if match:
        time = float(match.groups(1)[0])
        #time += float(re.search(r'sys\t0m(\d+\.\d{3})', result.stderr.decode("ascii")).groups(1)[0])
        return time
    else:
        print("{} resulted in {}".format(inp, result.stderr))
        return 0

val = "flag{g00d_th1ng5_f0r_w41ting"

guess(val)

chars = "_-" + string.digits + string.ascii_lowercase

for c in chars:
    time = sum([guess(val+c) for _ in range(5)]) / 5
    print("{} results in time {}".format(val+c, round(time, 2)))

that ran through each character one by one, spitting out the time taken to evaluate the input. Unfortunately, the time taken to run did not always produce a single clear outlier, leaving me to test the outliers manually once the script ran.

It took me over 6 hours to extract this flag. I'm quite sure there has to be a simpler way, but hey. I got the flag in the end :)

Cartographer's Capture

Given a list of "IP addresses":

65.236.181.168
194.164.163.71
65.236.181.221
194.164.163.71
...

and

we know that this particular warehouse is one of the main sources for location information.

What if they were coordinates instead?

data = """65.236.181.168
194.164.163.71
65.236.181.221
194.164.163.71
...
65.236.181.63
194.164.161.59""".splitlines()

import struct
from matplotlib import pyplot as plt

def do_dirty_thing(ip_address):
    # parse IP into float
    # horrible bodge don't look closely. or at all.
    a = [int(a) for a in ip_address.split(".")]
    return struct.unpack("f", struct.pack(">I", a[3] << 24 | a[2] << 16 | a[1] << 8 | a[0]))

i = 0
lats = [do_dirty_thing(a) for a in data[::2]]
longs = [do_dirty_thing(a) for a in data[1::2]]

plt.subplot(aspect='equal')
plt.plot(longs, lats, ".")
plt.savefig("carto.png")

Hey lookie lookie!

Needle-Eye's Apprentice

We were given a bunch of 1s and 0s in an array. After someone pointed out that it was probably a quadtree, it was just a matter of writing a script to piece it together

from PIL import Image, ImageDraw

tree = ((0,0,(0,0,(0,0,(0,0,0,((0,0,0,(0,0,0,(0,0,1,1))),0,0,((1,(1,0,1,1),1,1),((0,0,0,1),0,0,(1,0,0,1)),(1,0,0,1),((0,1,0,0),1,1,(0,1,0,0))))),(0,0,(0,(0,0,(0,0,(0,0,1,1),(0,0,1,1)),0),
...
,0),0,0),0,0))

# origin is top left corner

# LAYERS needs to be defined because i want to draw
# the end leafs at 1 pixel per leaf
LAYERS = 8
# size of output image will be a square of edge size IMAGE_SIZE
IMAGE_SIZE = 2**8

def calc_offset(base_x, base_y, size, index):
    if index == 0:
        return base_x, base_y
    elif index == 1:
        return base_x + size, base_y
    elif index == 2:
        return base_x + size, base_y + size
    elif index == 3:
        return base_x, base_y + size
    else:
        raise Exception("Unknown index {}".format(index))

def draw(image_draw, offset_x, offset_y, depth, index, data):
    # draws data at depth (how many tiers in) and index (which node of the depth)
    # if data is a number, draw it, else call draw recursively on it

    # calculate the size that each node of data represents
    size = 2**(LAYERS - depth) / 4

    for i, node in enumerate(data):
        node_offset_x, node_offset_y = calc_offset(offset_x, offset_y, size, i)
        if isinstance(node, int):
            # draw this at node_offset_x, node_offset_y with size and color node
            image_draw.rectangle(
                ((node_offset_x, node_offset_y), (node_offset_x + size, node_offset_y + size)),
                fill="black" if node else "white"
            )
        else:
            draw(image_draw, node_offset_x, node_offset_y, depth + 1, i, node)

img = Image.new("RGB", (IMAGE_SIZE, IMAGE_SIZE))
image_draw = ImageDraw.Draw(img)

for i, node in enumerate(tree):
    offset_x, offset_y = calc_offset(0, 0, IMAGE_SIZE / 2, i)
    draw(image_draw, offset_x, offset_y, 0, i, node)

img.save("needle.PNG")

That's pretty cool. So quadtrees are a more efficient way to store data of varying...precision? I always thought the answer was just to store each unit whether it was modified or not.

Hash Slinging Slasher

Given the following contract, with the goal of well, guessing the number:

pragma solidity ^0.4.24;

contract HashSlingingSlasher {
    
    bytes32 answer = 0xed2a0ca74e236c332625ad7f4db75b63d2a2ee7e3fe52c2c93c8dbc4e06906d1;
    
    constructor() public payable {
        require(msg.value == 0.5 ether, "Must send 0.5 Ether");        
    }

    /** Guess the hashed number. Refunds ether if guessed correctly
     */
    function guess(uint16 number) public payable {
        require(msg.value == 0.25 ether, "Must send 0.25 Ether");
        if(keccak256(abi.encodePacked(number)) == answer) {
            msg.sender.transfer(address(this).balance);
        }
    }
    
    /** Returns balance of this contract
     */
    function getBalance() public view returns (uint256) {
        return address(this).balance;
    }

    /** CTF helper function
     *  Used to check if challenge is complete
     */
    function isComplete() public view returns (bool) {
        return address(this).balance == 0;
    }
    
}

Who ever said anything about a hash being good security, especially when you know there are only 65536 possibilities?

from eth_utils import decode_hex
from eth_utils import keccak

hash = decode_hex("0xed2a0ca74e236c332625ad7f4db75b63d2a2ee7e3fe52c2c93c8dbc4e06906d1")

i = 0
while True:
	if hash == keccak(i):
		print(i)
		break
	i += 1

Running the above script spits out 13337, which can be submitted for the flag.