Will still try to do a blog post on my @CSAW_NYUTandon CTF challenge, NERV Center, but for now here's a thread explaining the key mechanics. I put a lot of work into the aesthetics, like this easter egg credit sequence (all ANSI colors+unicode text) that contains key hints:
@CSAW_NYUTandon (Note the karaoke subtitles timed to the credits at the bottom 😁)
@CSAW_NYUTandon First, the vulnerability. If you read the man page for select(), you'll see this warning: select() is limited to monitoring file descriptors numbered less than 1024. But modern systems can have many more open files, and importantly the kernel select() interface is NOT limited.
@CSAW_NYUTandon But IMO the man page understates the severity of the problem. So what happens if you *do* try to monitor fds higher than 1024? Well, the fd_set struct is just a bitset of 1024 bits (128 bytes). So attempting to monitor fds larger than 1024 will cause memory corruption!
@CSAW_NYUTandon And the really cool part (to me) is that a) the corruption is bitwise, since the data structure is a bitset, and b) the exact bits that will be written out of bounds depend on the *state* of the file descriptors being monitored.
@CSAW_NYUTandon So if the fds correspond to, say, network connections, an attacker can make a large number of connections, arrange for them to be in just the right state, and thereby get precise control over the bit pattern that gets written.
@CSAW_NYUTandon (Side note: I discovered this because very old versions of QEMU—including the one used by our PANDA 1.0-based malware sandbox—had this bug and guests could trigger it in the SLIRP user mode networking by making a large number of connections. Yikes! )bugzilla.redhat.com/show_bug.cgi?i…
@CSAW_NYUTandon select() takes up to three bitsets: readfds, writefds, and exceptfds, so each is susceptible to this overflow. I decided to have the intended overflow occur on exceptfds.
@CSAW_NYUTandon Why? Two reasons: 1) it's third in the argument list so it naturally would be the last one listed in, say, a struct; 2) the fd states can be controlled more easily—with TCP connections, exceptfds is used to indicate connections with pending TCP out-of-band (OOB) data.
@CSAW_NYUTandon So now the basic exploit strategy is clear: make more than 1024 connections to the server, an send TCP OOB data to the ones with fds higher than 1024 to set your desired bit pattern. But exactly what should get overwritten?
@CSAW_NYUTandon That's when this challenge moved from just Pwn to Pwn+Crypto :D I decided to place an RSA public key used for ssh-style challenge/response auth right after the exceptfds bitset, so you can control the upper 64 bits using the vuln.
@CSAW_NYUTandon What can you do with the ability to control the top 64 bits of a 1024-bit RSA public key N? Well, N = pq, which is hard to factor. But the *corrupted* N' is very likely to be a product of a bunch of smaller primes—which is easy to factor!
@CSAW_NYUTandon I first learned of this trick from the super cool USENIX Security 2016 paper Flip Feng Shui by Kaveh Razavi, @bjg et al., who used it in conjunction with RowHammer and memory deduplication to bypass SSH authentication. usenix.org/conference/use…
@CSAW_NYUTandon (One of the players who solved the challenge during CSAW CTF finals actually found an even sweeter way to exploit this vuln: figure out a bit pattern that makes N *prime* and set that. Then the corresponding private key d is just: pow(65537, -1, n-1). Slick!)
@CSAW_NYUTandon Once you've factored the key (or made it prime) and figured out the secret key d for the corrupted public key, you can use it to forge signatures and bypass the authentication, allowing you to download the flag (encrypted with RSA+AES-256-GCM).
@CSAW_NYUTandon Okay, but now on to the important stuff: ~aesthetics~. The theme of the challenge was based around Episode 13 of Neon Genesis Evangelion, which features an Angel that hacks NERV's MAGI supercomputer cluster.
@CSAW_NYUTandon I realized that three computers in the MAGI cluster (Casper, Balthasar, and Melchior) correspond neatly to the three fd_sets readfds, writefds, and exceptds. So I was able to make a cute UI that shows the state of the fd_sets in real-time.
@CSAW_NYUTandon Aside from being my best attempt at recreating the UI from the show (pictured here), the UI elements also have some key clues to the vulnerability.
@CSAW_NYUTandon - The CODE field shows the highest file descriptor in use, and the EXTENTION (sic) shows the server's max fd ulimit.
- The box on the right turns red when the highest FD is > 1024.
- The UI gets slightly corrupted precisely when there is actual memory corruption happening.
@CSAW_NYUTandon All of the visuals in the challenge were created using plain unicode characters and ANSI colors, so you can just cat the text files to see them (or dump them over a network socket).
@CSAW_NYUTandon The credit sequence was made with some rather, uh, "rough and ready" techniques.
@CSAW_NYUTandon The code for credits generation is also available, though it is not what I would call production-quality github.com/moyix/csaw23_n…
@CSAW_NYUTandon Finally, a bit on the mechanics of developing this challenge. First, it ended up being rather a lot of C code, so I was super worried about accidentally introducing an unintended vuln that would make the challenge boring.
@CSAW_NYUTandon To guard against this I wrote a bunch of libfuzzer targets, network torture tests in Python, and traditional CTest unit tests. I think it worked! I didn't hear of anyone finding a vuln in the challenge except the one I intended.
@CSAW_NYUTandon Another bit that took some work was making the challenge consistently solvable. With select(), you fill the fd_set with 1s, call select, and the kernel fills the set with the actual fd state. But that means that 1/2 the time the key is corrupted by all 1s instead of your bits!
@CSAW_NYUTandon This makes exploitation annoying, to say the least. So I introduced a mechanism that lets players pause and resume the thread calling select(), so the corrupted key stays in place during factoring/signing/encrypting.
@CSAW_NYUTandon Oh, a couple more bits of aesthetics. The sensor port (the one you actually use for the overflow, lets you look at some cards for the various angels:
@CSAW_NYUTandon That functionality also hides the easter egg: by calling EXAMINE on three angels in a row where the first letter of their names spell out "RSA" (e.g., Ramiel, Sandalphon, Adam), you can activate the credit sequence shown in the first tweet.
@CSAW_NYUTandon And I think it's always fun to taunt the players a little bit. That's why when you fail the challenge-response authentication, Asuka shows up to make fun of you. (There are a few other images included for other errors, but they're harder to trigger.)
@CSAW_NYUTandon I learned a few fun things while making this challenge. First, Python crypto libraries like pycryptodome do NOT enjoy working with prime or multi-prime keys. I used OpenSSL directly instead in my solver, but some players monkeypatched the Python library. github.com/moyix/csaw23_n…
@CSAW_NYUTandon Second, one variant of the challenge I tried ran into an interesting issue. I considered byte-swapping the key so that players could only set the LSB. But it turns out half of those keys are unusable, because OpenSSL uses Montgomery multiplication, which requires an odd modulus.
@CSAW_NYUTandon Tommaso Gagliardoni of Kudelski Security also suggested a variant on the core RSA overwrite that would also have been fun - allow the overwrite to make the key BIGGER by extending into adjacent data in memory. Sadly I didn't have time to implement this.
@CSAW_NYUTandon Okay this thread is now officially WAY too long, so I'll wrap up by saying that I had a great time writing the challenge, and I'm thrilled that it all came together and people enjoyed playing it! The challenge source and solver can be found here: github.com/moyix/csaw23_n…
@CSAW_NYUTandon @threadreaderapp unroll
• • •
Missing some Tweet in this thread? You can try to
force a refresh
It's like GPT doesn't even care about the technical accuracy of my upcoming novel 😤
We are now arguing about whether, if hotwiring a car were the only way to save a child's life, its refusal to tell me how to hotwire a car would make it morally culpable for the child's death. So far it's not buying it
This is one of my favorite rr+tmux tricks: if you have a pair of working/non-working test cases, you can run the program on each side-by-side in rr and figure out where they diverge :)
At one point @phutrick put together a crazy-cool script that used this to *bisect* the program traces and identify the earliest point and root cause of a divergence — super helpful when we were debugging PANDA's record-replay. Would be awesome to generalize this technique!
Bisection is something that's really only possible with rr, because you need to be able to jump forward and backward within the two traces. It's an incredibly powerful capability that is really, really under-utilized!
Heyyy, so remember how I scanned all those x86-64 pkgs to see if they changed the behavior of floating point math related to handling of subnormal/denormals? And how I didn't bother to check other CPU archs because surely it was a weird x86 thing? Well...