Taming Rust With C - Cracking Caido Cli
Caido-cli is a proxy like BurpSuite, but written in Rust - but your dear borrow checker won’t stop a well-aimed linkerscript. Let’s make it free (as in freedom) again with C.
TL;DR - patch json parsing code with C and linkerscript magic to enjoy premium features for free :))).
Patch on CodeBerg for Caido 0.54.1 here.
Analysis
caido-cli requires a caido account to use premium features
(like unlimited projects, access to plugins, etc.). It authenticates the user
with an oauth2 flow. Once authenticated, it gets the list of “entitlements”
that the user can access. In order to use paid premium features without an
account, all I have to do is intercept and change JSON response.
If only it were that simple
Cryptographic verification
Apart from SSL / TLS pinning (which we bypassed in a previous post), caido also does cryptographic verification of responses with a HMAC.
This meant modifying the json response alone was not enough, and I also needed to forge the HMAC - for which I would need the signing key stored on the server.
Attacking a layer where trust has already been established
To mitigate this protection, I started searching for a different attack surface. JSON parsing of the entitlements response is done AFTER verifying with json response. This means we can patch the serde json parser to return pro entitlements, regardless of the server’s response.
Hunting for the JSON parser was quite the task. But thanks to earlier (quite unsuccessful) work on modifying responses, I knew exactly where to find it (thanks to string error response references).
Dissecting the JSON parser
The json parser code was quite complicated. It has the following signature:
C
|
|
And produced the following effects on it’s parameters:
-
Construct and return a vector of
struct EntitlementThis will be free’d later on, so we can’t just have it sitting in the
.dataor.rodatasections. -
Mutate parser state to consume one json array worth of bytes.
Start of function: internal pointer at
[, end of function, pointer must be one past].
C
|
|
The lobotomy
The patch had to return a rust Vec<Entitlement> array, which will be free’d later on.
So, I needed access to rustc’s allocator. On x86_64-linux-gnu, it’s usually glibc malloc,
but I decided to use __rust_alloc from the caido binary’s GOT anyway. Same goes
to Entitlement objects in the output vector.
The placement of .code and .rodata of my patch was quite complicated. Since
I had two large code caves, I dedicated one to host my patch code and the other to
host my data (mainly bunch of strings)
The initial patch looked something like this:
C
|
|
I found a way to save a bunch of space in the output patch, by reusing the strings defined in the binary. With just a few entries in my linkerscript, I could remove all but three strings from my patch and save about a hundred bytes.
LINKERSCRIPT
/* plt addresses */
memcpy = 0x4f3ec0;
malloc = 0x4f3d18;
__rust_alloc = 0x000000005b62d0;
/* necessary strings */
feature_unlimited_projects = 77500389;
feature_automate_workflows = 77500497;
feature_unlimited_plugins = 77500598;
feature_replay_workflow = 77500548;
feature_export_unlimited_findings = 117245558;
feature_unlimited_filter_presets = 77500330;
feature_assistant = 77500217;
feature_unlimited_environments = 77500654;
feature_export_filtered_requests = 77500266;
feature_unlimited_workflows = 77500443;Final result
With just a free account, all premium features become accessible. Cracking a Rust application was expected to be challenging, but the generous code cave size made the process somewhat more straightforward.
The patch is available on CodeBerg, though instructions for applying it to your own Caido installation won’t be covered here - out of respect for a well-crafted tool with a small user base; mass piracy of indie software helps no one.