balsnCTF

Happy Farm

Challenge Description:

Have you ever played happy farm on FaceBook? It is a savior to me when I suffered from creepy crypto challenge.

chal.py:

’’’ #!/usr/bin/env python3.8 import os import time

from Cryptodome.Cipher import AES from Cryptodome.Util.number import *

from fertilizers import Fertilizer1, Fertilizer2, Fertilizer3 from utils import Drawer, banner1, banner2, banner3, flag

TARGET_LAYER = 9000 TARGET_BLOCKS = 16 BLOCK_SIZE = 16

def level1(): print(banner1) my_seed = os.urandom(BLOCK_SIZE * TARGET_BLOCKS) print(f”My seed:”) drawer.draw_seed(my_seed) my_start_date = os.urandom(BLOCK_SIZE) print(f”My start date: {my_start_date.hex()}”)

for _ in range(2):
    ## read request pair (start_date, seed, layer)
    start_date = bytes.fromhex(input("start date: "))
    if len(start_date) != BLOCK_SIZE:
        raise Exception
    seed = bytes.fromhex(input("seed: "))
    if len(seed) % BLOCK_SIZE != 0 or my_seed in seed:
        raise Exception
    layer = int(input("layer: "))
    if layer < 0 or layer >= TARGET_LAYER:
        raise Exception

    # encrypt for "layer" times and then send back the result
    fertilizer = Fertilizer1(start_date)
    onion = fertilizer.grow(seed, layer)
    print(f"Your onion")
    drawer.draw_onion(onion)

guessed_onion = bytes.fromhex(input("How would my onion looks like? "))
fertilizer = Fertilizer1(my_start_date)
onion = fertilizer.grow(my_seed, TARGET_LAYER)
if guessed_onion == onion:
    print(f"What a prophet!")
    return True
else:
    print(f"...?")
    return False

def level2(): print(banner2)

fertilizer = Fertilizer2()
my_seed = fertilizer.seed
print(f"My seed is")
drawer.draw_seed(my_seed)
print(f"You should use my seed first!")

## read request (layer)
layer = int(input("layer: "))
if layer < 0 or layer >= TARGET_LAYER:
    raise Exception

highest_layer = 8998
if layer >= 2:
    print(f"The layer you request is too high!")
    print(f"You cannot request layer higher than this!")
    highest_layer = layer

## encrypt for "layer" times and then send back the result
onion = fertilizer.grow(my_seed, layer)
print(f"your onion")
drawer.draw_onion(onion)

print(f"You can now use your seed")
seed = bytes.fromhex(input("seed: "))
layer = int(input("layer: "))
if layer < 0 or layer >= TARGET_LAYER or layer > highest_layer:
    raise Exception

onion = fertilizer.grow(seed, layer)
time.sleep(1)
print(
    f"Oops! It seems that some naughty rats sneak a taste on your onion while I'm napping!"
)
print(f"Here you go")
drawer.draw_eaten_onion(onion)

guessed_onion = bytes.fromhex(input("How would my onion looks like? "))
onion = fertilizer.grow(my_seed, TARGET_LAYER)
if guessed_onion == onion:
    print(f"What a prophet!")
    return True
else:
    print(f"...?")
    return False

def level3(): print(banner3)

seed_length = 15 * TARGET_BLOCKS
my_seed = os.urandom(seed_length)

for _ in range(4):
    fertilizer = Fertilizer3()

    ## read request (layer)
    layer = int(input("layer: "))
    if layer < 0 or layer >= TARGET_LAYER:
        raise Exception

    ## encrypt for "layer" times and then send back the result
    onion = fertilizer.grow(my_seed, layer)
    print(f"your onion")
    drawer.draw_onion(onion)

print(f"To pass the last challenge, your power should be over 9000!!!")
guessed_onion = bytes.fromhex(input("How would my onion looks like? "))
fertilizer = Fertilizer3()
try:
    onion = fertilizer.doctor_Balsn(my_seed, TARGET_LAYER ** 3)
except NotImplementedError:
    onion = fertilizer.grow(my_seed, TARGET_LAYER ** 3)

if guessed_onion == onion:
    print(f"What a prophet!")
    return True
else:
    print(f"...?")
    return False

return True

def main(): if not level1(): return

if not level2():
    return

if not level3():
    return

print(flag)

drawer = Drawer() if name == “main”: try: main() except Exception as e: print(“Something went wrong…”)

’’’

fertilizers.py

’’’ import os import sys

from Cryptodome.Cipher import AES from Cryptodome.Util.number import (bytes_to_long, getStrongPrime, inverse, long_to_bytes) class Fertilizer1: start_date = None key = None

def __init__(self, start_date=None):
    if Fertilizer1.start_date is None:
        Fertilizer1.start_date = os.urandom(16)

    if Fertilizer1.key is None:
        Fertilizer1.key = os.urandom(16)

    self.key = Fertilizer1.key
    self.start_date = Fertilizer1.start_date
    if not start_date is None:
        self.start_date = start_date

    self.fertilizer = AES.new(mode=AES.MODE_CBC, key=self.key, iv=self.start_date)

def grow(self, seed, layer):
    for _ in range(layer):
        seed = self.fertilizer.encrypt(seed)
    return seed

class Fertilizer2: def init(self): self.e = 3 self.p = getStrongPrime(512, e=self.e) self.q = getStrongPrime(512, e=self.e) self.n = self.p * self.q self.phi = (self.p - 1) * (self.q - 1) self.d = inverse(self.e, self.phi)

    ## generate secret seed
    self.seed = pow(1 << 1023, self.e, self.n)
    self.seed = long_to_bytes(self.seed)

def grow(self, seed, layer):
    exp = pow(self.d, layer, self.phi)
    seed = bytes_to_long(seed)
    if seed >= self.n or seed < 0:
        raise Exception

    onion = pow(seed, exp, self.n)
    return long_to_bytes(onion)

class Fertilizer3: key = None

def __init__(self):
    if Fertilizer3.key is None:
        Fertilizer3.key = os.urandom(32)
    self.key = Fertilizer3.key
    self.rc4_init()

def rc4_init(self):
    self.i = 0
    self.j = 0

    s = []
    for i in range(256):
        s.append(i)

    j = 0
    for i in range(256):
        j += s[i] + self.key[i % 16]
        j %= 256

        s[i], s[j] = s[j], s[i]

    self.s = s

def swap(self, a, b):
    a, b = b, a

def bytes_xor(self, a, b):
    return bytes([_a ^ _b for _a, _b in zip(a, b)])

def rc4_encrypt(self, inputs):
    output = []
    i = self.i
    j = self.j
    s = self.s
    for _ in range(len(inputs)):
        i = (i + 1) % 256
        j = (j + s[i]) % 256
        self.swap(s[i], s[j])
        output.append(s[(s[i] + s[j]) % 256])

    self.i = i
    self.j = j
    self.s = s
    return self.bytes_xor(inputs, output)

def encrypt(self, L, R):
    next_L = R
    next_R = self.bytes_xor(L, self.rc4_encrypt(R))
    return next_L, next_R

def grow(self, seed, layer):
    length = len(seed) // 2
    L, R = seed[:length], seed[length:]
    for _ in range(layer):
        L, R = self.encrypt(L, R)
    return L + R

def doctor_Balsn(self, seed, layer):
    r"""
    Dr. Balsn is our secret think tank!
    It can help us to grow onions incredibly fast on our server.
    We won't let you to access it!!!
    """
    raise NotImplementedError '''

To pass the first check in level 1, we need to pass in a 16 byte string for start date, to match BLOCK_SIZE. I used: ‘12345678901234567890123456789012’ Then, we need to pass in a number whose byte size equals 0 with modulus BLOCK_SIZE. I used: ‘12345678901234567890123456789012’, again, for the seed. Then, when specifying the layer, the number inputted has to be greater than 0 and less than TARGET_SIZE. So I used: ‘7000’. This part of level 1 runs twice. Next, is guessing the “onion”, or the hex that matches onion produced by passing in my_seed and TARGET_LAYER. my_seed is produced with this line: my_seed = os.urandom(BLOCK_SIZE * TARGET_BLOCKS), and then draw on the screen. So, I need to get the seed, I’d need to reverse the draw function used to draw the seed.

After level1 passes, I’d move to level 2. For level 2, I’d need to follow the same layer rules as in level 1 and input that layer size. I’d use: ‘1’, since the next check is checking whether or not the layer would be greater than or equal to 2.

After discussing this challenge with another person working on it, I realized that a larger issue to deal with is to figure out how to pass in the whole plaintext, which needs to be passed in block by block, keeping block order because of the use of CBC mode. We were still working on a solution, when the CTF ended unfortuantely. Will update soon with the writeup.

aeshash

Challenge Description:

Golang, the c++ for the 21st century, has a great hash function using AES. It should be super secure. Hint: Although I’m not good at finding papers, Wikipedia is all you need AFAIK. The scramble function has a partial inverse, but you need to get its internal state first.

hash.go:

’’’ package main

import “unsafe”

//go:linkname memhash runtime.memhash func memhash(p unsafe.Pointer, h, s uintptr) uintptr

//go:linkname useAeshash runtime.useAeshash var useAeshash bool

//go:linkname aeskeysched runtime.aeskeysched var aeskeysched [512]byte

type stringStruct struct { str unsafe.Pointer len int }

func MemHash(data []byte) uint64 { ss := (*stringStruct)(unsafe.Pointer(&data)) return uint64(memhash(ss.str, 0xdeadbeef, uintptr(ss.len))) }

’’’

main.go

’’’ package main

import ( “encoding/base64” “fmt” “io/ioutil” “os” )

const flagPath = “./flag”

func assert(x bool) { if !x { fmt.Println(“ .————————————–. “) fmt.Println(“/ You can’t understand what the cow said \ “) fmt.Println(“\ How about a penguin? / “) fmt.Println(“ ‘————————————–’ “) fmt.Println(“ \ “) fmt.Println(“ \ “) fmt.Println(“ .–. “) fmt.Println(“ |o_o | “) fmt.Println(“ |:/ | “) fmt.Println(“ // \ \ “) fmt.Println(“ (| | ) “) fmt.Println(“ /’\ /`\ “) fmt.Println(“ \)=(_/ “) os.Exit(-1) } }

func main() { assert( useAeshash );

fmt.Println(" .--------------------------.            ")
fmt.Println("/ Gimme some base64 messages \\          ")
fmt.Println("\\ I'll hash it for you       /          ")
fmt.Println(" '--------------------------'            ")
fmt.Println("                 \\   ^__^               ")
fmt.Println("                  \\  (oo)\\_______      ")
fmt.Println("                     (__)\\       )\\/\\ ")
fmt.Println("                         ||----w |       ")
fmt.Println("                         ||     ||       ")

for i := 0; i < 25; i++ {
	var b64Input string
	fmt.Printf("[>] Input (base64): ")
	_, err := fmt.Scanf("%1024s", &b64Input)
	assert(err == nil)
	input, err := base64.StdEncoding.DecodeString(b64Input)
	assert(err == nil)
	fmt.Printf("[<] %016x\n", MemHash(input))
}

fmt.Println(" .------------------.              ")
fmt.Println("/ Want the flag?     \\            ")
fmt.Println("\\ Kill me if you can /            ")
fmt.Println(" '------------------'              ")
fmt.Println("           \\   ^__^               ")
fmt.Println("            \\  (oo)\\_______      ")
fmt.Println("               (__)\\       )\\/\\ ")
fmt.Println("                   ||----w |       ")
fmt.Println("                   ||     ||       ")

var b64Input string
fmt.Printf("[>] Input (base64): ")
_, err := fmt.Scanf("%1024s", &b64Input)
assert(err == nil)
input, err := base64.StdEncoding.DecodeString(b64Input)
assert(err == nil)
if MemHash(input) == 0xdeadbeef01231337 {
	fmt.Println("[+] Flag:")
	flag, err := ioutil.ReadFile(flagPath)
	assert(err == nil)
	fmt.Println(string(flag))
} else {
	fmt.Println("[-] Moooooooooooooo")
} }

’’’

run.sh: ‘’’ #!/bin/bash exec 2>/dev/null

cd /home/aeshash/ timeout 300 ./chall

’’’

Looking at main.go, it looks like I need to input a base64 encoded message and ensure that when MemHash is run, it matches “0xdeadbeef01231337”. In order to do that I need to take a look at how the MemHash function is working in hash.go. It looks like it’s just a wrapper for the go language memhash function, just passing the string in as unsafe.Pointer. The go language memhash function is an AES-based hash implementation, but I wasn’t able to find the exact implementation of this function. Not entirely sure of next steps, will update with writeup.

Written on November 14, 2020