Don’t you hate it when CTFs happen faster than you can write them up?
This is probably the only PlaidCTF challenge I get to, unfortunately.1
Web is out, retro is in. Play your favorite word game from the
comfort of your terminal!
It’s a terminal Wordle client!
I only solved the first half of this challenge. The two halves seem
to be unrelated though. (Nobody solved the second half during the CTF.)
The challenge was quite big code-wise, with more than a dozen files, so
it’s hard to replicate the experience in a post like this, but here’s an
attempt.
If I had a nickel for every CTF
challenge I’ve done that involves understanding the internal
structure of a QR code, I would have two nickels. Which isn’t a lot, etc
etc. That previous challenge probably helped me get first blood on
this.
The source code is wonderfully short:
import io, qrcode, stringflag_contents = [REDACTED]assertall(i in string.ascii_lowercase +'_'for i in flag_contents)flag =b"actf{"+ flag_contents.encode() +b"}"print("flag is %d characters"%len(flag))qr = qrcode.QRCode(version=1, error_correction = qrcode.constants.ERROR_CORRECT_L, box_size=1, border=0)while1:try: inp =bytes.fromhex(input("give input (in hex): "))assertlen(inp) ==len(flag)except:print("bad input, exiting")break qr.clear() qr.add_data(bytes([i^j for i,j inzip(inp, flag)])) f = io.StringIO() qr.print_ascii(out=f) f.seek(0)print('\n'.join(i[:11] for i in f))
Now that kmh is gone, clam’s been going through pickle withdrawal. To
help him cope, he wrote his own pickle pyjail. It’s nothing like kmh’s,
but maybe it’s enough.
Language jails are rapidly becoming one of my CTF areas of expertise.
Not sure how I feel about that.
#!/usr/local/bin/python3import pickleimport ioimport sysmodule =type(__builtins__)empty = module("empty")empty.empty = emptysys.modules["empty"] = emptyclass SafeUnpickler(pickle.Unpickler):def find_class(self, module, name):if module =="empty"and name.count(".") <=1:returnsuper().find_class(module, name)raise pickle.UnpicklingError("e-legal")lepickle =bytes.fromhex(input("Enter hex-encoded pickle: "))iflen(lepickle) >400:print("your pickle is too large for my taste >:(")else: SafeUnpickler(io.BytesIO(lepickle)).load()
pickle
is a Python object serialization format. As the docs page loudly
proclaims, it is not secure. Roughly the simplest possible code to pop a
shell (adapted from David
Hamann, who constructs a more realistic RCE) looks like:
It’s clam’s newest javascript Calculator-as-a-Service: the CaaSio
Please Stop Edition! no but actually please stop I hate jsjails js isn’t
a good language stop putting one in every ctf I don’t want to look at
another jsjail because if I do I might vomit from how much I hate js and
js quirks aren’t even cool or funny or quirky they’re just painful
because why would you design a language like this
ahhhhhhhhhhhhhhhhhhhhh
It’s just a JavaScript eval jail.
#!/usr/local/bin/node// flag in ./flag.txtconst vm =require("vm");const readline =require("readline");constinterface= readline.createInterface({input:process.stdin,output:process.stdout,});interface.question("Welcome to CaaSio: Please Stop Edition! Enter your calculation:\n",function (input) {interface.close();if ( input.length<215&&/^[\x20-\x7e]+$/.test(input) &&!/[.\[\]{}\s;`'"\\_<>?:]/.test(input) &&!input.toLowerCase().includes("import") ) {try {const val = vm.runInNewContext(input, {});console.log("Result:");console.log(val);console.log("See, isn't the calculator so much nicer when you're not trying to hack it?" ); } catch (e) {console.log("your tried"); } } else {console.log("Third time really is the charm! I've finally created an unhackable system!" ); } });
Last weekend Galhacktic
Trendsetters sort of spontaneously decided to do DiceCTF 2022, months or years after
most of us had done another CTF. It was a lot of fun and we placed
6th!
Back in the day the silver edition was the top of the line Texas
Instruments calculator, but now the security is looking a little
obsolete. Can you break it?
It’s yet another Python jail. We input a string and, after it makes
it through a gauntlet of checks and processing, it gets
exec’d.
#!/usr/bin/env python3import disimport sysbanned = ["MAKE_FUNCTION", "CALL_FUNCTION", "CALL_FUNCTION_KW", "CALL_FUNCTION_EX"]used_gift =Falsedef gift(target, name, value):global used_giftif used_gift: sys.exit(1) used_gift =Truesetattr(target, name, value)print("Welcome to the TI-1337 Silver Edition. Enter your calculations below:")math =input("> ")iflen(math) >1337:print("Nobody needs that much math!") sys.exit(1)code =compile(math, "<math>", "exec")bytecode =list(code.co_code)instructions =list(dis.get_instructions(code))for i, inst inenumerate(instructions):if inst.is_jump_target:print("Math doesn't need control flow!") sys.exit(1) nextoffset = instructions[i+1].offset if i+1<len(instructions) elselen(bytecode)if inst.opname in banned: bytecode[inst.offset:instructions[i+1].offset] = [-1]*(instructions[i+1].offset-inst.offset)names =list(code.co_names)for i, name inenumerate(code.co_names):if"__"in name: names[i] ="$INVALID$"code = code.replace(co_code=bytes(b for b in bytecode if b >=0), co_names=tuple(names), co_stacksize=2**20)v = {}exec(code, {"__builtins__": {"gift": gift}}, v)if v: print("\n".join(f"{name} = {val}"for name, val in v.items()))else: print("No results stored.")
Last weekend Galhacktic
Trendsetters sort of spontaneously decided to do DiceCTF 2022, months or years after
most of us had done another CTF. It was a lot of fun and we placed
6th!
I made a blazing fast MoCkInG CaSe converter!
blazingfast.mc.ax
We’re presented with a website that converts text to AlTeRnAtInG
CaSe. The core converter is written in WASM, and also checks that its
input doesn’t have any of the characters <>&".
The JavaScript wrapper takes an input from the URL, converts it to
uppercase, feeds it to the converter, and if the check passes, injects
the output into an innerHTML. The goal is to compose a URL
that, when visited by an admin bot, leaks the flag from
localStorage.
The converter is compiled from this C code:
int length, ptr =0;char buf[1000];void init(int size){ length = size; ptr =0;}char read(){return buf[ptr++];}void write(char c){ buf[ptr++]= c;}int mock(){for(int i =0; i < length; i ++){if(i %2==1&& buf[i]>=65&& buf[i]<=90){ buf[i]+=32;}if(buf[i]=='<'|| buf[i]=='>'|| buf[i]=='&'|| buf[i]=='"'){return1;}} ptr =0;return0;}
This was a web challenge with a few pages. The “User” page displayed
some user information:
Name: Alice
Email: [email protected]
Group: CSAW2019
Intro: Alice is cool
Name: Bob
Email: [email protected]
Group: CSAW2019
Intro: Bob is cool too
The “About” page simply told us, “Flag is located at /flag.txt, come
get it”. The most interesting page was “Upload”, where we could view an
example users XML file:
Ahhh, CSAW CTF. Amidst all the other CTFs where we’re competing with
security professionals who probably have decades of experience and who
follow security developments for a living or whatever, there remains a
competition where scrubs like me can apply our extremely basic CTF
skills and still feel kinda smart by earning points. Now that I’ve
graduated and am no longer eligible, our team was pretty small and I
didn’t dedicate the full weekend to the CTF, but it means I got to do
the really easy challenges in the categories that I was the worst at, by
which I mean pwn.
baby_boi is pretty much the simplest possible modern ROP
(the modern security protections NX and ASLR are not artificially
disabled, but you get everything you need to work around them). We even
get source code.
So there’s nothing novel here for experienced pwners, but I feel like
there is a shortage of tutorials that walk you through how to solve a
textbook ROP the way you’d want to solve it in a CTF, so here is a
writeup.
In this challenge, we get a gzipped file called
perf.data and a minimal description of an environment.
Googling this reveals that perf.data is a record format of
the perf tool, a Linux profiler. Installing
perf allows us to read perf.data and see some
pretty interactive tables of statistics in our terminal describing the
profiling results, from which we can see some libraries and addresses
being called, but they don’t reveal much about what’s going on. One
hacky way to see more of the underlying data in a more human-readable
way (and to see just how much of it there is) is
perf report -D, which dumps the raw data in an ASCII
format, but this is still not that useful. (One might hope that one
could simply grep for the flag in this big text dump, but it’s nowhere
to be seen.) Still, from this file, we can definitely read off all the
exact library versions that the perf record was run
against.