For the past week, I have been slowly and steadily enjoying a new CTF website, TargetPractice. I finally completed their Android crypto/reverse-engineering challenge, Dead Drop, and wanted to share my workflow and experience with this fun exercise.
Warning: This is a complete write-up and will contain spoilers and solutions.
To start, I did a dry run of the application to see what its all about.
It is a simple 6-digit pin code blocking entry to whatever the next component of the application is. Its likely that we can just circumvent this and see whats next, so lets decompile the apk file and start poking around.
The first thing I like to do with decompiled Android code is pull all of the strings out and search for interesting tidbits. I found a URL when searching for http connections.
I decided to monitor the applications network traffic and see if that connection gets made before or after entering a pin.
> emulator @Pixel_2_XL_API_28 -tcpdump C:/dev/TargetPractice/emulator_capture.cap
Sure enough, there's an exchange. The application sends an RSA public key and the server responds with an encrypted PIN and encrypted flag.
Okay, lets take a look at where this public key is being generated. To do this, I want to see the flow of code when the application is launched, and also when a pin is attempted. I used my personally developed live debugger to monitor which java methods were being called while the application was being run. The Dead Drop app appears to have been coded in kotlin, but both java and kotlin are converted to the same dalvik instructions. The application launch looks like this:
and the PIN attempt looks like this:
There's a lot going on here... but one thing that immediately caught my eye was the repeated writePrivateFile() method. I took a look at the code for it and it takes two strings as parameters. One can assume that they'd be file name and data. I used my debugger to output the arguments and restarted the app.
Sure enough, it writes a public key and a private key that it must have generated as well as the encrypted pin and the encrypted flag that it received from the server. At this point, it might be possible to just decrypt the pin or flag and be done with it but I tried for a long time and was unsuccessful. I decided to return to my original goal of just bypassing the pin screen. When further examining the method trace of entering a pin, I noticed that something was being decrypted via AsymmetricEncryption.decrypt(). Lets log the result of that function every time it is run:
Well what do you know.... There's the pin! And using it brings you straight to a screen with the unencrypted flag! Success!
Big thanks to TargetPractice for this challenge. It was definitely a fun CTF and I look forwarding to completing their others!