I had all the time needed to learn to reverse engineer Android apps. There have been folks on the market who knew tips on how to navigate and modify the internals of an APK file and I wasn’t certainly one of them. This needed to be modified however it took a very long time for that to occur. On this publish, I’ll present you ways I used to be in a position to reverse engineer an Android app, add some debug statements, and work out how sure question parameters for API calls have been being generated. It ought to offer you a reasonably good concept 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 making ready for my superior maths examination. I had just lately realized 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 price and that was it. I had a whole lot of questions on how the app labored below the hood:
- Was the processing taking place on the cellphone?
- Have been they making any API calls?
- In that case then what have 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 come back again to this mission.
I made a decision to have a contemporary begin on the drawback and work out if I even must go so far as decompiling the APK. Possibly only a easy MITM assault could 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. Each time I made a question, this API name was made:
Thus far so good. No want for studying tips on how to reverse-engineer the app. Certainly I can work out what these question parameters are? Because it seems it was extraordinarily exhausting to determine how the sig
parameter was being generated. Every part else appeared generic sufficient however sig
was dynamic and altered 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 form of hash however I wasn’t certain what type or the way it was being generated and now this required a bit little bit of reverse engineering.
Observe: Whereas making an attempt to proxy the android emulator requests through mitmproxy, you would possibly see the error: Shopper connection killed by block_global
. To repair this, be sure to 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 visitors. Observe these directions to do this.
Downloading and unpacking the APK
Disclaimer: I don’t condone piracy. That is merely an train to show you ways one thing like this works. The identical information 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 information.
The very first step is to get your arms on the APK file. There are a number of methods to do this. You may both use ADB to get an APK from an Android system (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 you must be capable to discover a solution to obtain APKs fairly simply.
Let’s suppose our APK known as utility.apk
. Now we have to work out tips on how to unpack the APK right into a folder with all of the sources and Dalvik bytecode recordsdata. We will simply try this utilizing the apktool
. You may 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 be sure to 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
Candy!
What about JADX?
On the time of writing this text, probably the most well-known instrument for decompiling an APK might be JADX. It converts an APK file right into a human-readable Java output. Although the decompilation of an APK utilizing JADX normally provides 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 previously as effectively. I used to imagine that you would be able to merely recompile a decompiled APK and it could work. If solely APK reverse-engineering was this straightforward…
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 accessible even when it isn’t in a practical state. It can 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 may entry it right here. Simply give it the APK and it provides you with a zipper file containing the decompiled supply.
Seeing the next string in a number of locations within the decompiled output gave me a great 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 while 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 may learn extra about the place smali matches 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
methodology 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 methodology name to the v2
register.
It in all probability gained’t make a whole lot of sense proper now however I’ll clarify the required bits in a bit. That is simply to offer you an concept of what to anticipate. In case you are , I extremely suggest you to try this excellent presentation about Android code injection. It provides some helpful particulars about smali code as effectively.
There may be 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 similar so we are able to discover the output of JADX to assist us work out the place to insert the debug statements in smali.
After exploring the Java output for some time I discovered the tactic that was producing the signature. The signature was simply an MD5 hash of the remainder of the question parameters which have been being despatched to the server:
non-public 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 subject was I didn’t know what question parameters have been being handed to this methodology. 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
methodology was:
.methodology 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/Checklist;
move-result-object p1
# ..snipped for brevity
.finish methodology
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 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 care for if I have been to make use of this snippet. I needed to be sure that v8
was really a register declared for native use within the methodology (undergo this PDF if you happen to 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 methodology 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 tough to change however 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 might safely use for my customized code, why not permit the perform entry to a brand new register? That means we are able to ensure that no authentic code within the methodology is utilizing the brand new register.
Then I inserted the next traces:
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 exhausting 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 own home folder) and run:
apktool b utility
It will generate an utility.apk
file within the ~/utility/dist
folder.
Android doesn’t can 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
It will use the debug.keystore
file within the ~/.android
folder to signal the APK.
You must ensure to align your APK recordsdata utilizing a instrument referred to as zipalign
. It comes as a part of the Android SDK. In keeping with the Android docs:
zipalign is an archive alignment instrument that gives necessary 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 use zipalign like this:
zipalign -v 4 ~/utility/dist/utility.apk ~/utility/dist/application-aligned.apk
It will 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 necessary as a result of in any other case in later steps ADB provides you with 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 you have to be sure that the unique APK isn’t put in on the system. It’s pretty straightforward to uninstall an APK utilizing adb
. If the package deal identify for the app is com.yasoob.app
, you possibly can uninstall it utilizing the next command:
adb uninstall com.yasoob.app
As soon as it’s uninstalled, you possibly can 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 a whole lot 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 regarded 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 discovered the right smali code for logging an inventory) and disappointing as a result of Java outputted the references to Strings and never the string values themselves. However hey! No less than we made some progress and our repacked APK isn’t crashing!
At this level I merged all the APK constructing and set up instructions into one big command in order that I don’t must constantly 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 however it didn’t give us the knowledge we wanted. I regarded on the smali code once more and noticed the next statements:
.methodology 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 methodology
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 regarded like:
.methodology 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 methodology
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 methodology that was calling this getMd5Digest
methodology 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 superb! 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 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",
"system":"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 finished my exploration. The unique goals have been to determine how the sig
hash was being generated and tips on how to 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 happy.
I positioned the code in an “outdated tasks” folder, regarded on the clock and sighed. I had promised myself that I’d sleep at midnight however the clock was displaying 4:30 am. Nonetheless, it was time for some hard-earned relaxation.
Helpful Suggestions
-
When 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 is no such thing as a higher solution to study smali than really seeing what your individual Java (or Kotlin) code compiles to. -
There may be conditions the place a perform/methodology is utilizing the utmost allowed registers and you don’t have any concept tips on how to output a debug log. In these eventualities, it’s important to be a bit extra artistic and do some register shifting. The next instructions can be tremendous helpful in these instances:
move-object vx,vy move-object vy,vx
That is transferring the thing reference from vy to vx and again. It will can help you quickly 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. It will be sure 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 outgrep
when debugging points.
Conclusion
That’s all for at the moment. When you realized 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. 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! 👋