I love a good challenge. When I saw a post on the For Hire subreddit announcing a $100,000/year remote job would go to anyone who could solve a reverse engineering test, I was intrigued. I wasn’t actively searching for a job, but I always like to keep my options open in case my client work dries up.

Several people had commented, asking to take the test. I figured many more people had directly messaged the poster. The challenge remained up, signaling to me it could be quite a task. I checked the poster’s history. They’d been looking for a reverse engineer for over a month. Previously they’d offered $80,000/year, but it seems they’d had no luck. I messaged them for the test.

They replied with the challenge. It was a bit vague, but it involved reversing Instagram’s API key.

I downloaded the latest version of Instagram and decrypted it on my jailbroken iPhone.

I recalled seeing a PHP project on GitHub implementing the Instagram REST API. I figured it was an excellent place to start. I wanted to know what header parameter or post body parameter the API key was sent in. I could use the parameter name in IDA to check for cross-references. I searched for “key” inside the GitHub project and found a constant. I traced its usage to a parameter sent in REST requests called signed_body. Just what I expected to find.

The reason I did it this way instead of intercepting Instagram’s requests to its API is that Instagram uses SSL pinning. Usually, pinning is easy to defeat with a beautiful library named SSL Kill Switch 2. Unfortunately, Instagram performs pinning internally using a statically compiled version of OpenSSL. While there’s a publicly available byte pattern for patching, there’s not a readily available library for defeating it. This way was much easier and faster. The only risk was that if Instagram had changed to using a newer API, they could be using a different parameter name or an additional API key somewhere else.

I opened up the main Instagram binary in IDA and searched for signature_body in the strings, and there were no hits. I thought maybe they had changed the API. I ran a search through the entire contents of the app, and I found a hit in FBSharedFramework. Opening the framework up in IDA, I found one function referencing the signature_body string. The function's signature is "+ [IGAPIRequestSigning signedPOSTParametersFromDictionary:(NSDictionary *)]"

No part of this code jumped out at me. I decided to look into the anonymous function calls. Looking through each one, inside sub_3608C I found a call to an internal category method, "- [NSString HMACWithSecret:(NSString *)]"

Decompiling the category method, and reading through it, I spotted a call to CCHmacInit. Looking at the documentation for the function makes everything pretty clear. The signature is "void CCHmacInit(CCHmacContext *ctx, CCHmacAlgorithm algorithm, const void *key, size_t keyLength)." It's an encryption function. This function is used to cryptographically sign the request body with the API key.

sub_3608C contains the key, and then the key is passed to CCHmacInit from HMACWithSecret: to create the signed_body parameter. I wrote up an explanation as had been requested of me, and sent it off.

A few hours later I received a reply saying I was wrong. Wait. What? How was I wrong? Was there a new API parameter? There’s no way I missed something. It’s super simple. There’s barely any code. I asked for a further explanation.

Taking a closer look, I had missed something. A call to sub_7CD38 passes a variable which passes to CCHmacInit as the "key" variable. What? What’s the point of the method having a key as an argument? It seems to be a decoy since it remains unused. This technique is new to me. I’m not a malware reverser. I don't see code meant to deceive.

I figured sub_7CD38 must be the hidden API key algorithm they were referencing.

Once decompiled, it's pretty easy to read. It iterates over 64 characters stored in aR09, which is a pointer to the data segment, and the switch acts as a lookup table for replacement of characters, putting each replacement into the passed pointer.

Looking at the data aR09 points to, we can see it's a standard byte array.

Recreating this function in Swift is pretty straightforward.

I sent over an explanation of how it all works, addresses for the functions, screenshots, and the implementation in Swift as well as the output.

Pretty soon I received a reply.

I had been hoping the job would be project-based. It turned out to be fixed at 40 hours a week. Unfortunately, I couldn't commit to quite so many added hours. I was forced to decline.

Overall I felt it was a great challenge, and it was fun to figure it out. I'll be looking out for more real-world reversing challenges in the future.