Share
Embed
How to reverse engineer Android applications—using a popular word game as an example
Short introduction to the basic methods and techniques used in reverse engineering Android applications. A popular word game is used as an example app.
The slides describe obtaining the application code, decompiling it, debugging Android applications, using a proxy server (Man-in-the-Middle) to extract communication protocols and automating Android applications.
Published under CC BY-NC-SA 3.0
Christoph Matthies
August 27, 2014
Transcript
38 slides
-
How to reverse engineer Android applications Finding Vulnerabilities through Reverse
Engineering Hasso Plattner Institute, Potsdam Hubert Hesse, Lukas Pirl, Christoph Matthies, Conrad Calmez using a popular word game as an example ? ? Images: “Freepik” on flaticon.com (CC BY 3.0), Google (CC BY 3.0) -
1 Get the .apk 2 3 4 Extract the .apk
5 Decompilation to Smali Debugging 6 Putting it together 7 8 Automation Proxy Decompilation to Java -
Our Example—a word game • Top 10 word game in
145 countries (as of July 2014) • More than 10.000.000 installs • Over 50 million players • Play online (with friends) • 14 languages • Free and premium version -
1:58 0 points S N B I L U S
F E I T T E R P A -
1:58 15 points S N B I L U S
F E I T T E R P A FLUT +15 -
• APK (application package file), archive file, based on JAR
format • Similar to Deb packages (in Ubuntu) or MSI packages (in Windows) • Contains program code, resources, assets, certificates, and manifest file • Can’t be directly downloaded from App Store 1 Get the .apk Download using online “APK Downloader” (http://apps.evozi.com/apk-downloader/) - or - Install on device and download using SDK tools (adb pull <app_path> downloaded.apk) -
2 Extract the .apk • Normal decompression using unzip fails
• Special tool: APKTool ◦ Standard is APKTool 1.5.2. (not able to recompress correctly) (https: //code.google.com/p/android-apktool/downloads/list) ◦ APKTool 2.0.0 Beta 9 works (http://connortumbleson.com/2014/02/apktool-2-0-0-beta-9-released/) Decrompressing: apktool d -d game.apk -o outdir -
2 Modifying resources • Change arbitrary resources • Repack into
.apk file and install Recrompressing: apktool b -d outdir -o com.company.game.free_patch.apk • Recompression works, Android fails with “can’t install”, wrong certificate ◦ APKTool tries to reuse as much as possible, doesn’t recompute signature -
2 Manually sign repacked apk: • Create custom CA •
Java JAR Signing and Verification Tool (http://docs.oracle.com/javase/7/docs/technotes/tools/windows/jarsigner.html) jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my- release-key.keystore com.company.game.free_patch.apk alias_name Modifying resources -
.apk contains compiled code • Dalvik bytecode interpreted by the
Dalvik Process virtual machine • Stored in .dex (Dalvik EXecutable) files APKTool translates this to “smali” (https://code.google.com/p/smali/) • Abstraction of bytecode, closer to Java • Dalvik opcodes (http://s.android.com/tech/dalvik/dalvik-bytecode.html) • Can be edited directly 3 Decompilation to Smali -
.class public LHelloWorld; .super Ljava/lang/Object; .method public static main([Ljava/lang/String;)V .registers
2 sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream; const-string v1, "Hello World!" invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V return-void .end method 3 Smali Hello World -
Interactive debugging • Set debuggable=”true” in AndroidManifest.xml ◦ Repack using
APKTool • Need to connect smali sources to binary • Workaround: pretend we have valid Java code 4 Debugging <application android:allowBackup="true" android:hardwareAccelerated="true" android:icon="@drawable/launcher_icon" android:label="@string/app_name" android:name="com.company.game.core.GameApplication" android:theme=" @style/Theme.GameTheme" android:debuggable="true"> -
a=0;// .class public abstract La; a=0;// .super Ljava/lang/Object; a=0;// a=0;//
a=0;// # instance fields a=0;// .field protected final a:Ljava/lang/Object; a=0;// a=0;// .field private final b:Landroid/os/Handler; a=0;// 4 Debugging Smali code in comments Placeholder Java -
Two ways to obtain java code • Convert .dex files
to .jar ◦ Use standard java bytecode decompilers • Disassemble .dex directly to .java 5 Decompilation to Java -
Using dex files • Androguard (https://code.google.com/p/androguard/) ◦ Maps DEX format
into full Python objects ◦ Works in memory (My 4GB machine wasn’t enough) ◦ Doesn’t immediately dump code into Java files 5 Decompilation to Java -
Using jar files • dex2jar (https://code.google.com/p/dex2jar/) ◦ dex2jar, jar2dex, apk-sign
◦ Supports recreating .dex from Java • JD-GUI (http://jd.benow.ca/) ◦ Popular jar-decompiler ◦ Works 100% with “Hello World” app 5 Decompilation to Java -
Combining Java decompilation and Smali • Java more readable than
Smali • Unfortunately Java decompilation not 100% perfect ◦ Invalid Java constructs or only method signatures ◦ Cannot recompile from Java sources 6 Putting it together -
private void fixSpecialChars() { int i; char ac[]; int j;
int k; i = 0; ac = tiles; j = ac.length; k = 0; _L9: if(k >= j) break MISSING_BLOCK_LABEL_161; ac[k]; JVM INSTR lookupswitch 6: default 80 // 40: 125 // 41: 137 // 47: 149 // 91: 89 // 92: 101 // 93: 113; goto _L1 _L2 _L3 _L4 _L5 _L6 _L7 _L4: break MISSING_BLOCK_LABEL_149; _L1: break; /* Loop/switch isn't completed */ _L5: break; /* Loop/switch isn't completed */ _L10: i++; k++; if(true) goto _L9; else goto _L8 _L8: 6 When Decompilation fails an example Goto not supported in Java Bare JVM instructions -
Combining Java decompilation and Smali • Approach: Use multiple Java
decompilers ◦ They tend to fail in different places 6 Putting it together 1. Find interesting parts in Java source 2. Check corresponding smali sources 3. Edit those -
protected void roundEnd(boolean paramBoolean) { // … this.resultData.setTotalScore(this.totalScore); // …
startRoundSummary(); if (!this.isPractice) { this.currentRound.setWordsInRound(this.resultData.getMoves().size()); // … this.currentRound.setPlayer1Moves(GameHelper.encodeMoves(this.resultData. getMoves())); this.currentRound.setPlayer1Score(this.totalScore); // … 6 Manipulating the score Opportunities for manipulation • Server validation disallows this -
a=0;// sget-boolean v0, Lcom/company/game/core/statics/Statics;->DEBUGGING:Z a=0;// a=0;// #v0=(Boolean); -a=0;// if-eqz v0,
:cond_0 +a=0;// #if-eqz v0, :cond_0 a=0;// 6 Enable Logging public class Toolkit { // … public static void Logw(String s, String s1) { if(Statics.DEBUGGING) Log.w(s, s1); } // … -
a=0;// # static fields a=0;// .field public static ROUND_DURATION_IN_SECONDS_FOR_NORMAL_GAME:I a=0;//
.field public static ROUND_DURATION_IN_SECONDS_FOR_TUTORIAL:I a=0;// a=0;// .method static constructor <clinit>()V a=0;// .locals 1 … -a=0;// const/16 v0, 0x78 +a=0;// const/16 v0, 0x12c a=0;// a=0;// #v0=(PosByte); a=0;// sput v0, Lcom/company/game/core/statics/GameStatics;->ROUND_DURATION_IN_SECONDS_FOR_NORMAL_GAME:I 6 More time per round 120s 300s -
public static boolean allowPremiumContent(PremiumType premiumtype, Context context) { if(premiumIsPurchased(context)) return
true; synchronized(lock) { if(!isLicensed(context)) break MISSING_BLOCK_LABEL_31; } return true; 6 Getting Premium a=0;// .line 129 -a=0;// invoke-static {p0}, Lcom/company/game/util/PremiumCampaignHelper;->premiumIsPurchased(…;)Z +a=0;// # invoke-static {p0}, Lcom/company/game/util/PremiumCampaignHelper;->premiumIsPurchased(…;)Z a=0;// -a=0;// move-result v0 +a=0;// # move-result v0 a=0;// -a=0;// #v0=(Boolean); -a=0;// if-eqz v0, :cond_0 +a=0;// #v0=(One); +a=0;// # if-eqz v0, :cond_0 -
7 Proxy Route all app traffic through custom proxy •
Used MitMProxy (https://github.com/mitmproxy/mitmproxy) • Retrieve real server URL via Wireshark • Redirect app traffic via /etc/hosts on device • Custom SSL certificate ◦ Install own CA in device ◦ No certificate pinning • Avoid compressed responses via HTTP header ◦ Accept-Encoding: gzip;q=0,deflate,sdch -
7 Proxy AES encryption • Shared key in decompiled code
• No key derivation function • AES initialization vector in HTTP header ◦ Payload-session: 2e2f6a61642f7372… ◦ Unencrypted // file APIConnector.java private static byte sharedKey[] = { 57, -116, 126, 39, 116, -25, -95, -106, -81, 48, -33, -19, 120, 118, 35, 40, 66, 126, 31, 30, -83, 76, 31, 93, 13, -122, -50, 68, -108, -114, 28, -80 }; -
SSL MitM Proxy SSL HTTP Server by “aLf “, thenounproject.com
(CC BY 3.0 US) Spy by “Hopstarter ”, iconarchive.com (CC BY-NC-ND 4.0) #! python #decrypt AES #using IV 7 Proxy Header: AES IV AES payload HTTP # /etc/hosts # redirect # to proxy -
7 Proxy { "cacheTimestamp": "1405377910521", "userId": "0", "conversationId": "-1", "player1MostWordsInRound":
"32", "id": "6602198229545556683", "player1Score": "214", "player1LongestWord": "HEAPS", "player1User": { "username": "username", "ranking": "0", "premium": "false", "recruits": "0", "deleted": "false", "newUser": "false", "bestScoreInMatch": "0", "userId": "3005807464", "bestScoreInRound": "0", "online": "false", "facebookConnected": "false", "avatarId": "0", "matchesPlayed": "0", "useFacebookImage": "false", "mostWordsInRound": "0" }, {"rounds": [ { "seed3": "14657688", "player2MoveErrors": "0", "gameId": "6602198229545556683", "player2SwipeDistance": "681", "player2Moves": "1AB2BAE2EAB216227612AEF2DA73840127652567354013DAB723673 B7654EAB72", "player1MoveErrors": "19", "player2Done": "true", "seed1": "2073207065", "seed2": "680974433", "player1SwipeDistance": "1608", "board": { "bonus": [" ", " ", " ", " ", " ", " ", "D", " ", " ", " ", " ", " ", " ", " ", " ", "T" ], "board": ["A", "T", "E", "H", "E", "P", "O", "T", "H", "S", "A", "S", "T", "F", "T", "E" ], "words": [ "TATE", "SOTS", "HOST", "SAPS", "FATSOS", … Server response request size up to 100kB -
8 Automation Play the game automatically • Generic external approach
◦ No modification of binary necessary ◦ Works for any app Monkeyrunner (http://developer.android.com/tools/help/monkeyrunner_concepts.html) • Test apps at the functional/framework level • Able to simulate keystrokes, take screenshots • Python bindings -
8 Obtain all possible words to play correctly • apk
contains .jet “dictionary” for each language • Btw, also a wordlist (probably) used to check for cheaters Automation -
8 Automation Ruzzle .jet files • Binary files • Trie
/ Radix tree structure • Optimal for the way the game is played • No duplicate encoding of characters • List of all excepted words constructable G GA GAM GAME GO GOD GOT G O D T A M E -
8 Automation Achieving the highscore • Get all 16 letters
◦ Input by hand / screenshot + OCR • Find all valid words using the extracted dictionary • Simulate keystrokes for found words ◦ Actually not enough time to enter all valid words -
Achievements Found possibilities to: ✓ Enable logging ✓ Unlock premium
features ✓ Achieve insanely high score through automation ✓ Extract protocol via man-in-the-middle attack -
Pinned certificate (installed at dev. time) App Server Get current
server certificate 1 Compare current and pinned certificates 2 if identical: establish connection else: reject 3 Certificate Pinning