Updates and Teaser
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.
befunge.py
from random import randint
import os.path
import 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 VARIABLES
grid = BefungeGrid() # BEFUNGE GRID
x = 0 # X COORDINATE OF POINTER
y = 0 # Y COORDINATE OF POINTER
direction = 0 # CURRENT DIRECTION OF MOTION
stack = Stack() # STACK OF POINTER
inQuotes = False # WHETHER WE'RE IN STRING MODE
globalPC = 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?
challenge.bef
>92+2*1+3*99*99*v
v *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!