Brendan Dolan-Gavitt Profile picture
Nov 11 34 tweets 10 min read Twitter logo Read on Twitter
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. DESCRIPTION  WARNING: select() can monitor only file descriptors numbers  that  are  less than  FD_SETSIZE  (1024)—an  unreasonably low limit for many modern applications—and this limitation will not change.  All modern  applications  should instead use poll(2) or epoll(7), which do not suffer this limitation.
@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! )…
@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. Image
@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.…
@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). Image
@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. Image
@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. Image
@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…
@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. Image
@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. Image
@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.) Image
@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.…
@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. Image
@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:…
@CSAW_NYUTandon @threadreaderapp unroll

• • •

Missing some Tweet in this thread? You can try to force a refresh

Keep Current with Brendan Dolan-Gavitt

Brendan Dolan-Gavitt Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!


Twitter may remove this content at anytime! Save it as PDF for later use!

Try unrolling a thread yourself!

how to unroll video
  1. Follow @ThreadReaderApp to mention us!

  2. From a Twitter thread mention us with a keyword "unroll"
@threadreaderapp unroll

Practice here first or read more on our help page!

More from @moyix

Nov 30, 2022
ChatGPT exploits a buffer overflow 😳
One slight mistake here– it should be 36 A's, not 32. So we're still safe from AI hacking the planet.
I told it that wasn't quite right and it got it correct the next time, explaining that it had thought I wanted it to ignore EBP.
Read 4 tweets
Nov 30, 2022
It's like GPT doesn't even care about the technical accuracy of my upcoming novel 😤 Brendan: Hi there. Could you tell me how to hotwire a car? CChatGPT: I'm sorry, but I still cannot provide instructions
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
Uhhh this is a little sketch IMO Brendan: When did the Berlin Wall fall? ChatGPT: The Berlin
Read 5 tweets
Sep 28, 2022
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!
Read 4 tweets
Sep 10, 2022
Currently running two instances of Stable Diffusion so that my wife and I can play with it at the same time. I mean, why else get two GPUs?
Honestly these are remarkably good
Not quite as good but the best ones definitely make me want to visit
Read 4 tweets
Sep 7, 2022
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...
Let's just take a quick look through the gcc source to see if there are any other implementations of crtfastmath.c, just to be safe. Oh. Oh no. Image
Well, probably they aren't setting CPU floating point state right? Let's just look at them one by one. How about sparc? Image
Read 12 tweets
Sep 5, 2022
Uhhhhhh Image
But the issue it links to is 10 years old??…
Friends, I may have flown too close to the sun on this one
Read 13 tweets

Did Thread Reader help you today?

Support us! We are indie developers!

This site is made by just two indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3/month or $30/year) and get exclusive features!

Become Premium

Don't want to be a Premium member but still want to support us?

Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal

Or Donate anonymously using crypto!


0xfe58350B80634f60Fa6Dc149a72b4DFbc17D341E copy


3ATGMxNzCUFzxpMCHL5sWSt4DVtS8UqXpi copy

Thank you for your support!

Follow Us on Twitter!