How to reverse engineer Android applications—using a popular word game as an example
-
Upload
christoph-matthies -
Category
Software
-
view
133 -
download
3
Transcript of How to reverse engineer Android applications—using a popular word game as an example
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