how to reverse engineer android applications
TRANSCRIPT
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
23 4Extract the .apk
5Decompilation to Smali
Debugging
6Putting it together7 8Automation
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 SF
E I T
T E RP
A
1:58 15 points
S N B I
L U SF
E I T
T E RP
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
1Get 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)
2Extract 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
2Extract the .apk
2Modifying 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
2Manually 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
3Decompilation 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
3Smali 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
4Debugging
<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 fieldsa=0;// .field protected final a:Ljava/lang/Object;a=0;// a=0;// .field private final b:Landroid/os/Handler;a=0;//
4Debugging
Smali code in commentsPlaceholder
Java
Two ways to obtain java code
● Convert .dex files to .jar○ Use standard java bytecode decompilers
● Disassemble .dex directly to .java
5Decompilation 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
5Decompilation 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
5Decompilation 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
6Putting 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:
6When Decompilation failsan 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
6Putting it together
1. Find interesting parts in Java source2. Check corresponding smali sources3. 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); // …
6Manipulating 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;//
6Enable Loggingpublic 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
6More 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;
6Getting 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
6Getting Premium
free version premium (stats unlocked, no ads)
7Proxy
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
7Proxy
AES encryption
● Shared key in decompiled code● No key derivation function● AES initialization vector in HTTP header
○ Payload-session: 2e2f6a61642f7372…○ Unencrypted// file APIConnector.javaprivate 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};
SSLMitMProxy SSLHTTP
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
7ProxyHeader: AES IV
AES payload
HTTP
# /etc/hosts
# redirect# to proxy
7Proxy{ "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": "1AB2BAE2EAB216227612AEF2DA73840127652567354013DAB723673B7654EAB72", "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 responserequest size up to 100kB
8Automation
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
8Obtain all possible words to play correctly
● apk contains .jet “dictionary” for each language
● Btw, also a wordlist (probably) used to check for cheaters
Automation
8Automation
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
8Automation
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
8Automation
DEMO
Achievements
Found possibilities to:
✓ Enable logging✓ Unlock premium features✓ Achieve insanely high score through automation✓ Extract protocol via man-in-the-middle attack
Backup slides
Pinned certificate(installed at dev. time)
AppServer
Get current server certificate
1
Compare current and pinned certificates
2
if identical: establishconnectionelse: reject
3
Certificate Pinning