Great write up! As a programmer interested in auditing for vulnerabilities like this and others, what does the community suggest as a direction to start honing my skills with proper learning material?
What do you want to get good at? 'Memory corruption' is the term of art for these vulns. There are a lot of areas you can learn "enough" of.
Start with the Micro Corruption CTF. See which part you enjoy the most (finding, analyzing, exploiting). Each use similar but different skills.
You need to get good at assembly/machine code. You have to learn the patterns of what compiled code translates back to (structs, exception handlers, logic, etc). Machine code is the one constant. Notice how the author had mapped C structs in his hex editor? That is the sort of thing that gets you back to a conceptual hacking level instead of being down in the weeds. Get good at doing those things.
Besides what's been suggested already, there are many small open source programs that have never had an audit. You could follow the example of defuse.ca and just start doing it.
The point of this article should be the technical parts of memory corruption. Jailbreaks often exploit multiple bugs that could have the same title. The last wide Jailbreak for 9.3.3 I did entirely in the browser. It was nice of them to ask before exploiting my system :)
When this happens, you can override the stack. As variables are pushed on the stack, you can influence the execution of the program, for example return address. This can be exploited using ROP (Return Oriented Programming) and then you've got full control.
One choice people sometimes have is the language to implement parsers in.
Among other languages, I think Go is fantastic language for parsers. Great basic types to work with, no need to typedef uint64 long long, easy to parallelize parsing logic and use up multiple cores. Race detector and other static analysis will help verify there's no easy to spot issues, and good code review for the rest. Built in testing facilities make it trivial to create tests anyone can run and reproduce.
Finally, if you're still worried about a programmer error leading to a panic, can use recover at the top level to ensure that it would get caught. The HTTP server does that for user handlers, so that a panic in a user-written handler fails that single HTTP request, rather than taking down entire server. But need to do this in all goroutines that are at risk, not just the main one.
I would feel so much more comfortable and confident implementing or reviewing code for parsers in Go than C/C++. Because Go is so short on features and highly explicit, it's just so much easier to read the code and be fully confident it does the right thing, and nothing else.
It's unfair to compare a 2017 re-write in Go vs. C++ that's festered for 20 years. If you want to do apples-to-apples Go vs. C++, first you'll need to compare it to well written C++11 (no need to crop C in this.) C++ without raw pointers and references is pretty safe, just like Go with the unsafe package can be, well, unsafe.
I wasn't comparing with a 20-year-old existing C++. I was comparing how I felt about doing an implementation or code review of a parser in 2017 in both languages.
> Among other languages, I think Go is fantastic language for parsers.
While that may be true in a self-contained environment, a library simply can't drag an invasive runtime around: no one will use a format whose only implementation has that requirement (e.g. imagine libjpeg was written in Go - no one would bother with it).
So for general purpose libraries, like these mentioned by GP, there are much fewer options. One can try to write safe C (ideally using analyzers and only a subset of C), but we know how that usually turns out. One can try to write safe C++, which can be easier, but usually kinda isn't [because a lot of code is older than five years; I'd wager most C++ code is much older than both Go and Rust]. At this point in this HN comment we pretty much narrowed it down to Rust or perhaps D.
23 comments
[ 5.5 ms ] story [ 973 ms ] threadStart with the Micro Corruption CTF. See which part you enjoy the most (finding, analyzing, exploiting). Each use similar but different skills.
You need to get good at assembly/machine code. You have to learn the patterns of what compiled code translates back to (structs, exception handlers, logic, etc). Machine code is the one constant. Notice how the author had mapped C structs in his hex editor? That is the sort of thing that gets you back to a conceptual hacking level instead of being down in the weeds. Get good at doing those things.
Still this is a fine bug.
[1] 210 CVEs containing JPEG https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=JPEG, 158 CVEs containing GIF, 127 CVEs containing PNG, and this covers just the common web picture formats.
Among other languages, I think Go is fantastic language for parsers. Great basic types to work with, no need to typedef uint64 long long, easy to parallelize parsing logic and use up multiple cores. Race detector and other static analysis will help verify there's no easy to spot issues, and good code review for the rest. Built in testing facilities make it trivial to create tests anyone can run and reproduce.
Finally, if you're still worried about a programmer error leading to a panic, can use recover at the top level to ensure that it would get caught. The HTTP server does that for user handlers, so that a panic in a user-written handler fails that single HTTP request, rather than taking down entire server. But need to do this in all goroutines that are at risk, not just the main one.
I would feel so much more comfortable and confident implementing or reviewing code for parsers in Go than C/C++. Because Go is so short on features and highly explicit, it's just so much easier to read the code and be fully confident it does the right thing, and nothing else.
While that may be true in a self-contained environment, a library simply can't drag an invasive runtime around: no one will use a format whose only implementation has that requirement (e.g. imagine libjpeg was written in Go - no one would bother with it).
So for general purpose libraries, like these mentioned by GP, there are much fewer options. One can try to write safe C (ideally using analyzers and only a subset of C), but we know how that usually turns out. One can try to write safe C++, which can be easier, but usually kinda isn't [because a lot of code is older than five years; I'd wager most C++ code is much older than both Go and Rust]. At this point in this HN comment we pretty much narrowed it down to Rust or perhaps D.
Probably introduced in macOS 10.11 or 10.12 then.