Let’s Reverse Engineer an Android App!



I had all the time wished to learn to reverse engineer Android apps. There have been individuals on the market who knew how one can navigate and modify the internals of an APK file and I wasn’t one in all them. This needed to be modified nevertheless it took a very long time for that to occur. On this publish, I’ll present you the way I used to be in a position to 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 typically works.


You could 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 just lately discovered that I may 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 plenty of questions on how the app labored below the hood:

  • Was the processing occurring on the telephone?
  • Have 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 mission 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 mission.

I made a decision to have a recent begin on the downside and determine if I even must 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 site visitors from the emulator. At any time when I made a question, this API name was made:


Thus far so good. No want for studying how one can reverse-engineer the app. Absolutely I can determine what these question parameters are? Because it seems it was extraordinarily onerous to determine how the sig parameter was being generated. All the things else appeared generic sufficient however sig was dynamic and adjusted with a change in enter.

I attempted modifying the enter barely simply to test 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 form of hash however I wasn’t certain what sort or the way it was being generated and now this required slightly little bit of reverse engineering.

Notice: Whereas making an attempt to proxy the android emulator requests by way of mitmproxy, you would possibly see the error: Shopper connection killed by block_global. To repair this, ensure 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 system to decrypt the SSL site 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 can be locations all through the article the place I’ll censor the identify of the applying or the package deal I’m reversing. Don’t do something unlawful with this data.

The very first step is to get your fingers 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 system (emulated or actual) or you need to use a web-based APK web site to obtain a working model of the app. I opted for the latter choice. Search on Google and you need to be capable to discover a approach to obtain APKs fairly simply.

Let’s suppose our APK known as utility.apk. Now we have to determine how one can unpack the APK right into a folder with all of the sources and Dalvik bytecode recordsdata. We will simply do this utilizing the apktool. You possibly can simply obtain the apktool from this hyperlink.

On the time of writing, the latest 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 ensure you have it put in.

Now we are able to use apktool to truly unpack the APK:

apktool d utility.apk

This could offer you an utility folder in the identical listing the place utility.apk is situated. The construction of the applying folder ought to look one thing like this:

$ ls utility
AndroidManifest.xml  belongings  lib       res    smali_classes2
apktool.yml          kotlin  authentic  smali  unknown


What about JADX?

On the time of writing this text, probably 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 normally provides you pretty readable Java code, the decompilation course of is lossy and you may’t merely recompile the app utilizing Android Studio.

That is the place I acquired caught previously as properly. I used to imagine that you could merely recompile a decompiled APK and it will work. If solely APK reverse-engineering was this simple…

That’s too easy

So wait! Does this imply we gained’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 will provide you with a zipper file containing the decompiled supply.

Seeing the next string in a number of locations within the decompiled output gave me an excellent chuckle:

"ModGuard - Defend 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 glorious reply by Antimony on StackOverflow.

That is what smali code appears 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 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 in all probability gained’t make plenty 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 glorious presentation about Android code injection. It provides some helpful particulars about smali code as properly.

There’s additionally a smali cheat-sheet that was tremendous useful for me to know 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 identical so we are able to 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:

non-public String getMd5Digest(WAQueryParameters wAQueryParameters) {
    // ...
    StringBuilder sb = new StringBuilder(600);
    for (String[] strArr : parameters) {
    strive {
        MessageDigest occasion = MessageDigest.getInstance("MD5");
        return String.format("%1$032X", new Object[]{new BigInteger(1, occasion.digest())});
    } catch (NoSuchAlgorithmException unused) {
        return "";

Now the one situation was I didn’t know what question parameters had been being handed to this technique. I attempted producing an MD5 hash in Python based mostly 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 non-public 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/Listing;

    move-result-object p1
    # ..snipped for brevity
.finish technique

The move-result-object name was transferring the output of getParameters to the p1 register. That is the place I wanted a log assertion (or so I believed). I did some analysis and in line with StackOverflow I may 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 may 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 truly a register declared for native use within the technique (undergo this PDF in the event you don’t know what I imply) and that I used to be not over-writing a worth in that register that was going for use later within the technique by the unique code. And moreover, I wished to print the worth of p1 and it was not of the java.lang.String sort.

The code wasn’t all that tough to change nevertheless it took me an embarrassingly very long time to determine the right statements to insert in smali.

Firstly, I modified .locals 6 to .locals 7. That is helpful as a result of as a substitute of tracing which register I may safely use for my customized code, why not permit the perform entry to a brand new register? That means we are able to make sure that no authentic 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, we have now to repack the APK. This isn’t terribly onerous if in case you have the tooling already arrange. We’ll do that in steps.

If the output of apktool d utility.apk was ~/utility then merely go to ~ (your house folder) and run:

apktool b utility

This can generate an utility.apk file within the ~/utility/dist folder.

Android doesn’t help you set up unsigned APKs. You probably 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 ~/utility/dist/utility.apk androiddebugkey

This can use the debug.keystore file within the ~/.android folder to signal the APK.

It’s a must to be sure to align your APK recordsdata utilizing a device referred to as zipalign. It comes as a part of the Android SDK. In keeping with the Android docs:

zipalign is an archive alignment device that gives essential optimization to Android utility (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 utilize zipalign like this:

zipalign -v 4 ~/utility/dist/utility.apk ~/utility/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 be sure you 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 will provide you with an error saying adbd can not run as root in manufacturing builds. Yow will discover an in depth answer on StackOverflow.

Upon getting an emulator arrange you should ensure that the unique APK isn’t put in on the system. It’s pretty simple to uninstall an APK utilizing adb. If the package deal identify for the app is com.yasoob.app, you’ll be able to uninstall it utilizing the next command:

adb uninstall com.yasoob.app

As soon as it’s uninstalled, you’ll be able to set up the modified model:

adb set up -r ~/utility/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 plenty of stuff we don’t care about. That’s the reason we use grep to solely output the debug statements we truly care about.

In my case the output of logcat appeared 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 identical time. Thrilling as a result of the apk didn’t crash (it crashed fairly a couple of occasions earlier than I found out the right 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! A minimum of we made some progress and our repacked APK isn’t crashing!

We have made progress

At this level I merged all the APK constructing and set up instructions into one big command in order that I don’t must repeatedly execute them one after the other:

apktool b utility && 
jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore ~/.android/debug.keystore -storepass android ~/utility/dist/utility.apk androiddebugkey && 
rm ~/utility/dist/application-aligned.apk && 
zipalign -v 4 ~/utility/dist/utility.apk ~/utility/dist/application-aligned.apk && 
adb uninstall com.yasoob.app && 
adb set up -r ~/utility/dist/application-aligned.apk && 
adb logcat | grep 'YASOOB'

Fixing the debug assertion in smali

Okay, we had a working debug assertion nevertheless it didn’t give us the data we wanted. I appeared on the smali code once more and noticed the next statements:

.technique non-public 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 ultimately 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 a substitute of the reference.

That is how the modified code appeared like:

.technique non-public 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 completely 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: system
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&system=Android&sig=7A1AE2AD7F5F81C85B8A4D0FC2723C8C

That is wonderful! Now I knew which parameters, and in what order, are getting used to generate the MD5 hash. I rapidly whipped out my trusty Visible Studio Code and wrote down an excellent easy Python script for producing this hash for me based mostly on customized inputs. That is what I got here up with:

import hashlib

url = "https://api.********.com/v2/question.jsp?"
sb = hashlib.md5() 

enter = "4x^3+3x^2+2x"
knowledge = {
    "enter": enter,

for ok, v in knowledge.objects():
    base_url += f"&{ok}={v}"

url += f"&sig={sb.hexdigest()}"

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!

It’s working!

That is the place I finished my exploration. The unique goals had been to determine how the sig hash was being generated and how one can reverse-engineer the API to make customized question calls. I used to be in a position to accomplish each of these goals and my curiosity was glad.

I positioned the code in an “outdated initiatives” folder, appeared on the clock and sighed. I had promised myself that I might sleep at midnight however the clock was exhibiting 4:30 am. However, it was time for some hard-earned relaxation.


Helpful Suggestions

  • In case you don’t know what the smali output for some Java code is meant to be, create a brand new Android mission, write down the code in Java and see the ensuing smali utilizing apktool. There isn’t any higher approach to study smali than truly seeing what your individual Java (or Kotlin) code compiles to.

  • There could be conditions the place a perform/technique is utilizing the utmost allowed registers and you haven’t any thought how one can output a debug log. In these situations, you must be a bit extra inventive and do some register shifting. The next instructions can be tremendous helpful in these circumstances:

    move-object vx,vy
    move-object vy,vx

    That is transferring the article reference from vy to vx and again. This can help you briefly shuffle register values, do your debugging, after which put the unique register values again.

  • If for some purpose your repacked APK is providing 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. It’s also actually helpful to make use of adb logcat with out grep when debugging points.


That’s all for as we speak. In case you discovered one thing new on this publish please subscribe to my weekly e-newsletter (scroll a bit additional for the shape) or the RSS feed for the web site. You may also observe me on Twitter the place I normally publish an replace about new articles.

Take care and I’ll see you in a future article! ?