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.