It’s been quite a while since I’ve posted here, I hope everyone is doing well.
Been busy with school ever since it started, so I haven’t had much time to make decent writeups. Thankfully, with exams on the horizon, I’ll finally have time to write some posts.
Anyway, first things first, I think I’ll be moving away from Hugo in the upcoming months. I’ve been looking at eleventy Astro recently and it looks pretty promising. Hopefully the shift doesn’t break too many of my old posts, but if they do just know I won’t be fixing them. cuz im lazy
In terms of cybersecurity, I finally got a job! Hooray. Still looking for something for the summer but at least I get some working experience.
As for CTFs, I’ve been doing them like normal. Unfortunately I’ll probably never be able to go back and make writeups for most of them, but some fun ones I did recently include UDCTF, LakeCTF Quals, and HITCON.
At HITCON, we managed to qualify for the finals and got 2nd place! Really fun CTF even if I didn’t mess with too much of the A/D side (was farming koth xd).
I also qualified and went to CSAW Finals, but unfortunately I spent my entire time tunnel-visioned on one challenge so I didn’t do too well. Still pretty fun, and the food in NYC was great. (Unfortunately I did lose my phone on the way back, but that’s a story for another time.)
With those updates out of the way, let’s get to the teaser.
Teaser
In the upcoming months with the transition to eleventy Astro, I also plan on making a series of blog posts where I dive into different programming languages and discuss their features, mainly discussing how I would approach reversing them for CTFs.
Note that my advice may not be the best, but this is mainly more for me to force myself to learn these languages rather than as an actual guide.
As a teaser for the first post, if you played in HITCON Finals recently, you may recall the KOTH challenge expansion
, where you were given 5 different “binaries” all compiled with ELVM. I’ll be going over one of the esolangs from that challenge, Befunge, so as a gift here’s a basic (and possibly slightly broken) interpreter that I’ve written.
from random import randintimport os.pathimport sys
class BefungeGrid: grid = {} def __init__(self): self.grid = {} self.row_bound = 0 self.col_bound = 0
def __getitem__(self, key): if type(key) == tuple: # parse as row, col return self.grid.get(key[0], {}).get(key[1], " ")
return self.grid.get(key, {}) # ? may be incomplete row, due to being sparse
def __setitem__(self, key, value): if type(key) == tuple: # parse as row, col self.grid.setdefault(key[0], {})[key[1]] = value else: self.grid[key] = value
def read_file(self, filename): data = open(filename).readlines() self.col_bound = len(data) for row, line in enumerate(data): line = line.strip('\n') # remove trailing newline self.row_bound = max(self.row_bound, len(line)) for col, char in enumerate(line): if char != ' ': self.grid.setdefault(row, {})[col] = char
# for bounds, we assume only the original grid matters # THIS IS IMPT IF YOU OVERWRITE AN AREA NEAR THE EDGE def get_row_bound(self): return self.row_bound
def get_col_bound(self): return self.col_bound
class Stack: def __init__(self): self.stack = []
def pop(self, value=None): if value: return self.stack.pop(value) if self.stack else 0 else: return self.stack.pop() if self.stack else 0
def append(self, value): self.stack.append(value)
def __getitem__(self, key): if not self.stack: # empty stack return 0 return self.stack[key]
# GLOBAL VARIABLESgrid = BefungeGrid() # BEFUNGE GRIDx = 0 # X COORDINATE OF POINTERy = 0 # Y COORDINATE OF POINTERdirection = 0 # CURRENT DIRECTION OF MOTIONstack = Stack() # STACK OF POINTERinQuotes = False # WHETHER WE'RE IN STRING MODEglobalPC = 0 # GLOBAL PROGRAM COUNTER
def befunge_main(filename): global grid, x, y, direction, stack, inQuotes global globalPC, runFail, flag_i grid = BefungeGrid() # BEFUNGE GRID x, y = 0, 0 # POINTER direction = 0 # CURRENT DIRECTION OF MOTION stack = Stack() # LIFO NUMBER STORAGE inQuotes = False # WHETHER WE'RE IN STRING MODE globalPC = 0 # GLOBAL PROGRAM COUNTER
grid.read_file(filename) # printGrid(grid)
while grid[y, x] != "@" and not runFail: globalPC += 1 step()
def printGrid(grid: BefungeGrid, MAX_X=100, MAX_Y=100): """Print the grid to the console""" for row in range(MAX_Y): for col in range(grid.get_row_bound()): print(grid[row, col], end="") print()
def step(): # print(f"{globalPC} ({y}, {x}) {grid[y, x]}") processInstruction(grid[y, x]) move()
def processInstruction(inst: str): global direction, inQuotes
# IN STRING MODE if inQuotes and inst != '"': stack.append(ord(inst)) return
# NOP if inst == " ": return
# TOGGLE STRING MODE elif inst == '"': inQuotes = not inQuotes
# MOVEMENT elif inst in '>v<^': direction = '>v<^'.index(inst)
elif inst == '#': # skip move()
# LITERAL elif inst.isdigit(): stack.append(int(inst))
# ARITHMETIC elif inst in '+-*%': a, b = stack.pop(), stack.pop() stack.append(eval(f'{b}{inst}{a}')) elif inst == '/': # special case a, b = stack.pop(), stack.pop() stack.append(b // a)
# BOOLEAN elif inst == '!': # logical not stack.append(not stack.pop())
elif inst == '`': # greater than a, b = stack.pop(), stack.pop() stack.append(int(b > a))
# RANDOM elif inst == '?': # random direction direction = randint(0, 3)
# CONDITIONALS elif inst == '_': # horizontal if if not stack.pop(): direction = 0 else: direction = 2
elif inst == '|': if not stack.pop(): direction = 1 else: direction = 3
# STACK OPS elif inst == ':': # dup stack.append(stack[-1])
elif inst == '\\': # swap a, b = stack.pop(), stack.pop() stack.append(a) stack.append(b)
elif inst == '$': # pop stack.pop()
# OUTPUT elif inst == '.': # output int print(stack.pop(), end=" ")
elif inst == ',': # output char print(chr(stack.pop()), end="")
# MODIFY # THESE HAVE SPECIAL BEHAVIOR WHEN DATA IS NOT ASCII elif inst == 'p': # put y, x, v = stack.pop(), stack.pop(), stack.pop() if 32 <= v <= 126: grid[y, x] = chr(v) else: grid[y, x] = v
elif inst == 'g': # get y, x = stack.pop(), stack.pop() v = grid[y, x] if type(v) == str: stack.append(ord(v)) else: stack.append(v)
# INPUT elif inst == '&': # input int stack.append(int(input("(n) > ")))
elif inst == '~': # input char stack.append(ord(input("(c) > ")))
def move(): global x, y # right, down, left, up dxy = [(0, 1), (1, 0), (0, -1), (-1, 0)] dy, dx = dxy[direction] x += dx y += dy
# WRAP AROUND IF NECESSARY x %= grid.get_row_bound() y %= grid.get_col_bound()
def main(): global globalPC if len(sys.argv) < 2: print("ERROR: No input file specified!") sys.exit()
filename = sys.argv[1] if not os.path.isfile(filename): print("ERROR: Specified input file does not exist!") sys.exit()
befunge_main(filename)
if __name__ == "__main__": main()
And of course, I can’t end the teaser without a challenge. Here’s a small Befunge program that I wrote, can you figure out what it does?
>92+2*1+3*99*99*vv *99 p<&\ >:00p -, v \ >\:|:\ +1 g00< - @ 3 0 ^"Vmt ekg$nz("0< | < @,,"NO"<>99*g 55+::**67**+ -!^
That’s all for now, see you soon!