HKCERT CTF 2022 – C++harming Website
A harming website? Hope it doesn't harm my sleep!
Description
350 points. 4/5 ⭐️. 4/311 solves.
Seems someone encrypt their flag with some weird online website. And seems the website is written in C++...
Does anyone even use C++ to write their web server? I guess C++ is still charm but it must be easy to reverse.... right?
Analysis
We’re provided with the server binary written in C++. No source code. 😟 We’re also provided with a link to a website (presumably hosted by the server).
Hmm, I wonder what the website has in store for us. Let’s check it out!
How disappointing. Oh well, perhaps the binary is more helpful. Maybe we can find out how to work the website. Might be important. Might not be important. Who knows?1
Firing up Ghidra and loading the binary, we start by going to main
(okay so far!). main
doesn't seem to do much, besides calling init
, run
, and std::cout
. Things get a lot more interesting when we look at run
:
It’s easy to be intimidated by such a large application. And it’s in C++, so there’s a ton of garbage (std
, templates, constructors, destructors, etc.).2
After a bit of digging, we uncover quite a bit of info:
The server uses a library called oatpp.
- It’s useful to look at some oatpp examples, as this gives us a general idea of the application flow and structure.
- For example, an endpoint could be defined by using
oatpp::web::server::HttpRouter::route
(example) or with theENDPOINT
macro (example). It appears our charming website was using the latter. - Now that we know what library is used, can we find out what the endpoint is?
- Yes. In the examples, we see that the endpoints are hardcoded. Chances are, the endpoints in the charming website are also hardcoded, and thus stored in static memory.
Let’s look at some strings!
Ghidra has a “Defined Strings” tool for browsing strings...
But I ended up using the
strings
command along withgrep
:With this, we find out that the endpoint is
/encrypt
, and the MIME type isapplication/json
. No other MIME type appears, so it's probably using JSON for both request and response.There’s also some interesting strings such as “charm.c”. But I thought this was a C++ application? Perhaps a third-party library? Maybe we can use this later on.
The gold can be found in
MyController::Encrypt::encrypt
. This is where all the juicy stuff takes place. You can arrive here through a number of ways (e.g. following XREFs ofuc_encrypt
).The function begins by generating a random Initialisation Vector (IV).
It then initialises some state using
uc_state_init
with a key.Fortunately, the key is stored in static memory. In plain sight. This is very blursed: blessed, because (from a CTF POV) we don't need much work; and cursed, because (from a dev vs. exploiter POV) we don't need much work.
The message is then encrypted using
uc_encrypt
.I have no idea what
puVar[-0x227] = X
does, and apparently it's not important.Finally,
encrypt
encodes the message, tag, and IV in Base64; then sends out a JSON response.
Now how do we go about reversing this encryption?
- It's probably not trivial—most encryptions aren't.
- What cryptographic algorithms use a tag and IV? Google suggested AES-GCM.
- Oh, but wait—there’s a
uc_decrypt
function...
Pikachu used charm! It’s not very effective.
To make our life easier (and also because of curiosity), let’s see if the encryption library is open-source. OSINT time! Googling “charm.c uc_encrypt site:github.com” leads us to dsvpn, which links us to charm. Both are GitHub repositories using the same charm.c as the challenge.
The source gives us obvious clues we might’ve missed in our initial analysis. For example, the key should be 32 bytes long. This was quite helpful, as Ghidra for some reason grouped the 32nd byte apart from the first 31 bytes (took me a while to figure out what went wrong).
Now that we have the source, we can use it directly for our solve script!
Since the state is initialised inside the endpoint, it is refreshed for each encryption. As long as we have the key and IV, we can recover the state. Finally, we decrypt the message and get the flag. That's all there is to it!
Final Remarks
This was a rather nice, relaxing C++ challenge. And yes, C++ is still charm.
With C++ reverse challenges (and looking at large applications in general), it’s difficult to know what’s important because there are so many things to look at. But! It’s really helpful to know what’s not important, because then you can filter those out and pay attention to things that matter.
For example, if you see templates (the ever so familiar, pointy friends of ours), you can usually ignore everything in between. Normally they're the default anyway.
Also, if there’s something to learn from this challenge, it’s that application developers should secure their secrets (e.g. with environment variables or config loaders). 😛
Solve Scripts
Flag
Footnotes
It wasn’t. ↩︎
To be fair, one of the reasons C++ is powerful is because it’s both performant and expressive. And it’s expressive, because there can be a lot of hidden control flow. You can write one line of code which could be syntax sugar for twenty lines of code, and even more assembly. With C, it’s more straightforward (and simple). ↩︎
Comments are back! Privacy-focused, without ads, bloatware, and trackers. Be one of the first to contribute to the discussion — I'd love to hear your thoughts.