I had at all times needed to learn to reverse engineer Android apps. There have been folks on the market who knew navigate and modify the internals of an APK file and I wasn’t one among them. This needed to be modified however it took a very long time for that to occur. On this publish, I’ll present you the way I used to be capable of reverse engineer an Android app, add some debug statements, and determine how sure question parameters for API calls had been being generated. It ought to offer you a reasonably good thought of how APK reverse engineering usually works.
Backstory
You may be questioning what fueled this curiosity so let me clarify. I used to be in highschool and was getting ready for my superior maths examination. I had lately discovered that I might use a sure app to get step-by-step options to my issues. I used to be excited. I attempted the app and it labored! It solely required a one time buy charge and that was it. I had lots of questions on how the app labored below the hood:
- Was the processing taking place on the cellphone?
- Had been they making any API calls?
- In that case then what had been the calls?
Easy, harmless questions that led me right into a rabbit gap. I attempted reverse-engineering the app however couldn’t get far. I ultimately determined to place the venture on the again burner and are available again to it as soon as I had extra time and expertise. It solely took 3 years, an entire lot of studying, and a renewed curiosity in reverse-engineering for me to return again to this venture.
I made a decision to have a contemporary begin on the downside and determine if I even have to go so far as decompiling the APK. Possibly only a easy MITM assault can be sufficient to snoop the API calls and craft my very own?
I presently have an iPhone so I put in the Android Emulator on my Linux machine and set up the app on that. Then I launched mitmproxy and began intercepting the visitors from the emulator. Every time I made a question, this API name was made:
To date so good. No want for studying reverse-engineer the app. Certainly I can determine what these question parameters are? Because it seems it was extraordinarily arduous to determine how the sig
parameter was being generated. All the pieces else appeared generic sufficient however sig
was dynamic and adjusted with a change in enter
.
I attempted modifying the enter barely simply to examine if the API was even checking the sig
parameter. Because it seems, it was. The endpoint returned an invalid signature error even on the slightest change in enter:
sig
was some type of hash however I wasn’t certain what sort or the way it was being generated and now this required just a little little bit of reverse engineering.
Word: Whereas making an attempt to proxy the android emulator requests by way of mitmproxy, you may see the error: Shopper connection killed by block_global
. To repair this, be sure you run mitmproxy with the block_global flag set to false: mitmproxy --set block_global=false
. Additionally, you will have to put in the mitmproxy certificates on the gadget to decrypt the SSL visitors. Comply with these directions to do this.
Downloading and unpacking the APK
Disclaimer: I don’t condone piracy. That is merely an train to show you the way one thing like this works. The identical data is used to reverse malware apps and to disable certificates pinning in APKs. There will probably be locations all through the article the place I’ll censor the identify of the applying or the bundle I’m reversing. Don’t do something unlawful with this information.
The very first step is to get your arms on the APK file. There are a number of methods to do this. You possibly can both use ADB to get an APK from an Android gadget (emulated or actual) or you need to use an internet APK web site to obtain a working model of the app. I opted for the latter possibility. Search on Google and it is best to have the ability to discover a method to obtain APKs fairly simply.
Let’s suppose our APK is known as software.apk
. Now we have to determine unpack the APK right into a folder with all of the sources and Dalvik bytecode recordsdata. We are able to simply do this utilizing the apktool
. You possibly can simply obtain the apktool
from this hyperlink.
On the time of writing, the newest model was apktool_2.4.1.jar
. Put this file wherever you need (in my case ~/Dev
) and add an alias to it in your .bashrc
for ease of use:
alias apktool="java -jar ~/Dev/apktool_2.4.1.jar"
I needed to set up JDK to get it to work so be sure you have it put in.
Now we will use apktool
to really unpack the APK:
apktool d software.apk
This could offer you an software
folder in the identical listing the place software.apk
is positioned. The construction of the applying folder ought to look one thing like this:
$ ls software
AndroidManifest.xml property lib res smali_classes2
apktool.yml kotlin unique smali unknown
Candy!
What about JADX?
On the time of writing this text, essentially the most well-known device for decompiling an APK might be JADX. It converts an APK file right into a human-readable Java output. Though the decompilation of an APK utilizing JADX often offers you pretty readable Java code, the decompilation course of is lossy and you’ll’t merely recompile the app utilizing Android Studio.
That is the place I acquired caught prior to now as effectively. I used to imagine which you could merely recompile a decompiled APK and it might work. If solely APK reverse-engineering was this straightforward…
So wait! Does this imply we received’t be utilizing JADX in any respect? Fairly the opposite. It’s tremendous helpful to have the decompiled supply code out there even when it isn’t in a practical state. It would assist us in determining the internals of how the app works and which strategies we have to modify within the smali code.
That is the proper time to make use of JADX to decompile the APK. The hosted model of JADX is fairly neat. You possibly can entry it right here. Simply give it the APK and it offers you a zipper file containing the decompiled supply.
Seeing the next string in a number of locations within the decompiled output gave me chuckle:
"ModGuard - Shield Your Piracy v1.3 by ill420smoker"
Introducing smali
So what are our choices if JADX doesn’t work? We’re gonna do the following smartest thing and decompile the APK into smali
code. Consider smali as meeting language. Usually if you compile your Java code into an APK, you find yourself with .dex
recordsdata (contained in the APK) which aren’t human-readable. So we convert the .dex
code into .smali
code which is a human-readable illustration of the .dex
code. You possibly can learn extra about the place smali suits within the compilation life-cycle in this excellent reply by Antimony on StackOverflow.
That is what smali code seems like:
invoke-interface {p1}, Ljava/util/Iterator;->hasNext()Z
move-result v2
That is equal to calling the hasNext
technique of java.util.Iterator
. Z
tells us that this name returns a boolean. p1
is known as a parameter register and that’s what the hasNext()
is being referred to as on. move-result v2
strikes the return worth of this technique name to the v2
register.
It most likely received’t make lots of sense proper now however I’ll clarify the required bits in a bit. That is simply to present you an thought of what to anticipate. In case you are , I extremely suggest you to try this excellent presentation about Android code injection. It offers some helpful particulars about smali code as effectively.
There may be additionally a smali cheat-sheet that was tremendous useful for me to grasp the fundamentals of smali.
Discovering the signature location
I needed to discover out the place the &sig=
question parameter was being added to within the smali code. It was pretty easy to determine this out utilizing grep
.
$ grep -r 'sig=' ./smali/com/
./easy/SimpleApi.smali: const-string v2, "&sig="
./impl/WAQueryImpl.smali: const-string v1, "&sig="
I began my exploration from there. I used the output of JADX to discover the place this parameter was being populated. That is the place having the decompiled supply code was actually helpful. The file construction within the apktool
output and jadx
output is similar so we will discover the output of JADX to assist us determine the place to insert the debug statements in smali.
After exploring the Java output for some time I discovered the strategy that was producing the signature. The signature was simply an MD5 hash of the remainder of the question parameters which had been being despatched to the server:
personal String getMd5Digest(WAQueryParameters wAQueryParameters) {
// ...
StringBuilder sb = new StringBuilder(600);
sb.append("vFdeaRwBTVqdc5CL");
for (String[] strArr : parameters) {
sb.append(strArr[0]);
sb.append(strArr[1]);
}
strive {
MessageDigest occasion = MessageDigest.getInstance("MD5");
occasion.replace(sb.toString().getBytes());
return String.format("%1$032X", new Object[]{new BigInteger(1, occasion.digest())});
} catch (NoSuchAlgorithmException unused) {
return "";
}
}
Now the one concern was I didn’t know what question parameters had been being handed to this technique. I attempted producing an MD5 hash in Python primarily based on the parameters I noticed within the URL however I used to be failing. If solely I had a log assertion that confirmed me the worth of wAQueryParameters
…
Including logging in smali
The equal smali code for the getMd5Digest
technique was:
.technique personal getMd5Digest(Lcom.______/WAQueryParameters;)Ljava/lang/String;
.locals 6
const/4 v5, 0x1
.line 1196
const/4 v5, 0x2
invoke-interface {p1}, Lcom/____/_____/WAQueryParameters;->getParameters()Ljava/util/Record;
move-result-object p1
# ..snipped for brevity
.finish technique
The move-result-object
name was shifting the output of getParameters
to the p1
register. That is the place I wanted a log assertion (or so I assumed). I did some analysis and in accordance with StackOverflow I might do one thing like this:
const-string v8, "log-tag"
invoke-static {v8, v9}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
This is able to print a debug assertion within the logcat
output and print the worth of the v9
register. There have been a few issues to handle if I had been to make use of this snippet. I needed to ensure that v8
was really a register declared for native use within the technique (undergo this PDF for those who don’t know what I imply) and that I used to be not over-writing a price in that register that was going for use later within the technique by the unique code. And moreover, I needed to print the worth of p1
and it was not of the java.lang.String
sort.
The code wasn’t all that onerous to change however it took me an embarrassingly very long time to determine the proper statements to insert in smali.
Firstly, I modified .locals 6
to .locals 7
. That is helpful as a result of as an alternative of tracing which register I might safely use for my customized code, why not enable the operate entry to a brand new register? That means we will ensure that no unique code within the technique is utilizing the brand new register.
Then I inserted the next strains:
const-string v6, "YASOOB getMd5Digest"
invoke-static {v6, p1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
Repacking the unpacked APK
After the smali modifications, now we have to repack the APK. This isn’t terribly arduous in case you have the tooling already arrange. We’ll do that in steps.
If the output of apktool d software.apk
was ~/software
then merely go to ~
(your property folder) and run:
apktool b software
This can generate an software.apk
file within the ~/software/dist
folder.
Android doesn’t permit you to set up unsigned APKs. If in case you have the Android SDK put in then you have already got a debug keystore that you need to use to signal an APK. The command for doing that’s this:
jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore ~/.android/debug.keystore -storepass android ~/software/dist/software.apk androiddebugkey
This can use the debug.keystore
file within the ~/.android
folder to signal the APK.
It’s a must to make certain to align your APK recordsdata utilizing a device referred to as zipalign
. It comes as a part of the Android SDK. In response to the Android docs:
zipalign is an archive alignment device that gives essential optimization to Android software (APK) recordsdata. The aim is to make sure that all uncompressed knowledge begins with a selected alignment relative to the beginning of the file.
You should use zipalign like this:
zipalign -v 4 ~/software/dist/software.apk ~/software/dist/application-aligned.apk
This can generate an application-aligned.apk
file.
Putting in the modified APK & Logging
I used an Android Emulator for this step. Particularly a Pixel 3XL emulator picture with API 28 and Android Oreo. Just remember to use an emulator Android picture with out Google Play. That is extraordinarily essential as a result of in any other case in later steps ADB offers you an error saying adbd can not run as root in manufacturing builds
. You will discover an in depth answer on StackOverflow.
After getting an emulator arrange it’s essential to ensure that the unique APK isn’t put in on the gadget. It’s pretty straightforward to uninstall an APK utilizing adb
. If the bundle identify for the app is com.yasoob.app
, you may uninstall it utilizing the next command:
adb uninstall com.yasoob.app
As soon as it’s uninstalled, you may set up the modified model:
adb set up -r ~/software/dist/application-aligned.apk
Now run the put in app within the emulator and run logcat
in a terminal on the host machine:
adb logcat | grep 'YASOOB'
The output of logcat
is tremendous noisy and it outputs lots of stuff we don’t care about. That’s the reason we use grep
to solely output the debug statements we really care about.
In my case the output of logcat seemed one thing like this:
YASOOB getMd5Digest: [[Ljava.lang.String;@ea7d7a1, [Ljava.lang.String;@8b00dc6, [Ljava.lang.String;@44c6187, [Ljava.lang.String;@fa6d8b4, [Ljava.lang.String;@f494cdd, [Ljava.lang.String;@78f4052, [Ljava.lang.String;@f038f23, [Ljava.lang.String;@232cc20, [Ljava.lang.String;@a92d9d9, [Ljava.lang.String;@5bd0f9e, [Ljava.lang.String;@e75fa7f, [Ljava.lang.String;@7c88a4c, [Ljava.lang.String;@d4e3a95]
That is thrilling and disappointing on the similar time. Thrilling as a result of the apk didn’t crash (it crashed fairly just a few occasions earlier than I found out the proper smali code for logging a listing) and disappointing as a result of Java outputted the references to Strings and never the string values themselves. However hey! At the least we made some progress and our repacked APK isn’t crashing!
At this level I merged the entire APK constructing and set up instructions into one enormous command in order that I don’t need to constantly execute them one after the other:
apktool b software &&
jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore ~/.android/debug.keystore -storepass android ~/software/dist/software.apk androiddebugkey &&
rm ~/software/dist/application-aligned.apk &&
zipalign -v 4 ~/software/dist/software.apk ~/software/dist/application-aligned.apk &&
adb uninstall com.yasoob.app &&
adb set up -r ~/software/dist/application-aligned.apk &&
adb logcat | grep 'YASOOB'
Fixing the debug assertion in smali
Okay, we had a working debug assertion however it didn’t give us the data we wanted. I seemed on the smali code once more and noticed the next statements:
.technique personal getMd5Digest(Lcom.______/WAQueryParameters;)Ljava/lang/String;
#--snipped for brevity--
aget-object v4, v2, v1
invoke-virtual {v0, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
.line 1211
const/4 v5, 0x0
aget-object v2, v2, v3
invoke-virtual {v0, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
#--snipped for brevity--
.finish technique
That is the place the applying was appending the question parameters to a StringBuilder
which is finally used to generate the MD5 hash. Why didn’t I merely put a debug assertion right here? We all know that StringBuilder
expects a String
so hopefully Java will output the worth of String this time as an alternative of the reference.
That is how the modified code seemed like:
.technique personal getMd5Digest(Lcom.______/WAQueryParameters;)Ljava/lang/String;
#--snipped for brevity--
aget-object v4, v2, v1
invoke-virtual {v0, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
# Customized Code YASOOB
const-string v6, "YASOOB QueryTask->getMd5Digest::FirstAppend"
invoke-static {v6, v4}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
# Customized Code finish
.line 1211
const/4 v5, 0x0
aget-object v2, v2, v3
invoke-virtual {v0, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
# Customized Code YASOOB
const-string v6, "YASOOB QueryTask->getMd5Digest::SecondAppend"
invoke-static {v6, v2}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
# Customized Code finish
#--snipped for brevity--
.finish technique
Whereas I used to be at it, I added some extra debug statements in a few extra locations in the identical file however totally different strategies. There was one technique that was calling this getMd5Digest
technique and one other that outputted the precise API URL with the question parameters. I added a debug assertion in each of those.
I rebuilt the APK and began monitoring the logs in logcat
:
YASOOB QueryTask->getMd5Digest::FirstAppend: appid
YASOOB QueryTask->getMd5Digest::SecondAppend: ****-*********
YASOOB QueryTask->getMd5Digest::FirstAppend: async
YASOOB QueryTask->getMd5Digest::SecondAppend: 0.25
YASOOB QueryTask->getMd5Digest::FirstAppend: banners
YASOOB QueryTask->getMd5Digest::SecondAppend: picture
YASOOB QueryTask->getMd5Digest::FirstAppend: countrycode
YASOOB QueryTask->getMd5Digest::SecondAppend: US
YASOOB QueryTask->getMd5Digest::FirstAppend: gadget
YASOOB QueryTask->getMd5Digest::SecondAppend: Android
YASOOB QueryTask->getMd5Digest::FirstAppend: format
YASOOB QueryTask->getMd5Digest::SecondAppend: png,plaintext,imagemap,minput,sound
YASOOB QueryTask->getMd5Digest::FirstAppend: enter
YASOOB QueryTask->getMd5Digest::SecondAppend: 1
YASOOB QueryTask->getMd5Digest::FirstAppend: languagecode
YASOOB QueryTask->getMd5Digest::SecondAppend: en
YASOOB QueryTask->getMd5Digest::FirstAppend: magazine
YASOOB QueryTask->getMd5Digest::SecondAppend: 3.8500000000000005
YASOOB QueryTask->getMd5Digest::FirstAppend: maxwidth
YASOOB QueryTask->getMd5Digest::SecondAppend: 2509
YASOOB QueryTask->getMd5Digest::FirstAppend: reinterpret
YASOOB QueryTask->getMd5Digest::SecondAppend: true
YASOOB QueryTask->getMd5Digest::FirstAppend: scantimeout
YASOOB QueryTask->getMd5Digest::SecondAppend: 0.5
YASOOB QueryTask->getMd5Digest::FirstAppend: sidebarlinks
YASOOB QueryTask->getMd5Digest::SecondAppend: true
YASOOB QueryTask->getMd5Digest::FirstAppend: width
YASOOB QueryTask->getMd5Digest::SecondAppend: 1328
YASOOB QueryTask->setSignatureParameter: 7A1AE2AD7F5F81C85B8A4D0FC2723C8C
YASOOB WAQueryImpl->toString: &enter=1&banners=picture&format=png,plaintext,imagemap,minput,sound&async=0.25&scantimeout=0.5&countrycode=US&languagecode=en&sidebarlinks=true&reinterpret=true&width=1328&maxwidth=2509&magazine=3.8500000000000005&gadget=Android&sig=7A1AE2AD7F5F81C85B8A4D0FC2723C8C
That is superb! Now I knew which parameters, and in what order, are getting used to generate the MD5 hash. I shortly whipped out my trusty Visible Studio Code and wrote down a brilliant easy Python script for producing this hash for me primarily based on customized inputs. That is what I got here up with:
import hashlib
url = "https://api.********.com/v2/question.jsp?"
sb = hashlib.md5()
sb.replace("vFdeaRwBTVqdc5CL".encode())
enter = "4x^3+3x^2+2x"
knowledge = {
"appid":"*****-********",
"async":"0.25",
"banners":"picture",
"countrycode":"US",
"gadget":"Android",
"format":"png,plaintext,imagemap,minput,sound",
"enter": enter,
"languagecode":"en",
"magazine":"3.8500000000000005",
"maxwidth":"2509",
"reinterpret":"true",
"scantimeout":"0.5",
"sidebarlinks":"true",
"width":"1328"
}
for okay, v in knowledge.objects():
sb.replace(okay.encode())
sb.replace(v.encode())
base_url += f"&{okay}={v}"
url += f"&sig={sb.hexdigest()}"
print(url)
I ran this system and the ensuing URL was the identical one I used to be seeing in mitmproxy. I modified the question, ran the Python program once more and the ensuing URL labored!
That is the place I ended my exploration. The unique goals had been to determine how the sig
hash was being generated and reverse-engineer the API to make customized question calls. I used to be capable of accomplish each of these goals and my curiosity was glad.
I positioned the code in an “outdated initiatives” folder, seemed on the clock and sighed. I had promised myself that I’d sleep at midnight however the clock was displaying 4:30 am. However, it was time for some hard-earned relaxation.
Helpful Ideas
-
In case you don’t know what the smali output for some Java code is meant to be, create a brand new Android venture, write down the code in Java and see the ensuing smali utilizing
apktool
. There isn’t any higher method to be taught smali than really seeing what your personal Java (or Kotlin) code compiles to. -
There may be conditions the place a operate/technique is utilizing the utmost allowed registers and you don’t have any thought output a debug log. In these situations, you need to be a bit extra inventive and do some register shifting. The next instructions will probably be tremendous helpful in these circumstances:
move-object vx,vy move-object vy,vx
That is shifting the item reference from vy to vx and again. This can permit you to briefly shuffle register values, do your debugging, after which put the unique register values again.
- If for some cause your repacked APK is supplying you with an error and never working, attempt to repack the APK with none modifications first. This can ensure that your modifications aren’t the explanation why the repacked APK isn’t working. Additionally it is actually helpful to make use of
adb logcat
with outgrep
when debugging points.
Conclusion
That’s all for at the moment. In case you discovered one thing new on this publish please subscribe to my weekly publication (scroll a bit additional for the shape) or the RSS feed for the web site. It’s also possible to observe me on Twitter the place I often publish an replace about new articles.
Take care and I’ll see you in a future article! 👋