{
	"id": "090511ab-fdc9-482d-bd44-143ca62e802d",
	"created_at": "2026-04-06T00:19:13.157064Z",
	"updated_at": "2026-04-10T03:30:33.081085Z",
	"deleted_at": null,
	"sha1_hash": "7756592bc690a96ea505bb5c44e3fbb43f3df0fe",
	"title": "Disect Android APKs like a Pro - Static code analysis - blog.dornea.nu",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 1403609,
	"plain_text": "Disect Android APKs like a Pro - Static code analysis - blog.dornea.nu\r\nPublished: 2014-07-07 · Archived: 2026-04-05 17:48:06 UTC\r\nI’ve started writing this IPython notebook in order to make myself more comfortable with Android and its SDK. Due to\r\nsome personal interests I thought I could also have a look at the available RE tools and learn more about their pros \u0026 cos. In\r\nparticular I had a closer look at AndroGuard which seems to be good at:\r\nReverse engineering, Malware and goodware analysis of Android applications … and more (ninja !)\r\nI was charmed but its capabilities and the pythonic art of handling with APKs. In the 2nd step I’ve needed a malware to play\r\nit, so I had a look at Contagio Mobile. There I’ve randomly chosen a malware and got stucked with Fake Banker. There are\r\nsome technical details about the malware itself gained during automated tests which can be read here.\r\nThis article will only deal with the static source code analysis of the malware. A 2nd part dedicated to the dynamic\r\nanalysis is planed as well.\r\nStart Kali Linux #\r\nStay safe and run the stuff isolated:\r\n 1\r\n 2\r\n 3\r\n 4\r\n 5\r\n 6\r\n 7\r\n 8\r\n 9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n➜ ~ virsh -c qemu:///system\r\nWelcome to virsh, the virtualization interactive terminal.\r\nType: 'help' for help with commands\r\n 'quit' to quit\r\nvirsh #\r\nvirsh # list --all\r\n Id Name State\r\n----------------------------------------------------\r\n 2 Ubuntu.GitLab running\r\n - Linux.Kali shut off\r\n - Ubuntu.Tracks shut off\r\n - Windows7 shut off\r\nvirsh # start Linux.Kali\r\nDomain Linux.Kali started\r\nNow we’re ready to login:\r\n 1\r\n 2\r\n 3\r\n 4\r\n 5\r\n 6\r\n 7\r\n 8\r\n 9\r\n10\r\n➜ ~ ssh kali.local\r\nvictor@kali.local's password:\r\nLinux kali 3.7-trunk-amd64 #1 SMP Debian 3.7.2-0+kali8 x86_64\r\nThe programs included with the Kali GNU/Linux system are free software;\r\nthe exact distribution terms for each program are described in the\r\nindividual files in /usr/share/doc/*/copyright.\r\nKali GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent\r\npermitted by applicable law.\r\nInstall SDK #\r\n1\r\n2\r\n3\r\n4\r\n(env)root@kali:~/work/apk/SDK# wget http://dl.google.com/android/android-sdk_r22.6.2-linux.tgz\r\n(env)root@kali:~/work/apk/SDK# tar -zxf android-sdk_r22.6.2-linux.tgz\r\n(env)root@kali:~/work/apk/SDK# export PATH=$PATH:/root/work/apk/SDK/android-sdk-linux/tools(env)root@kali:~/work/apk/SDK# which monitor\r\n/root/work/apk/SDK/android-sdk-linux/tools/monitor\r\nMake sure you have the ia32-libs installed.\r\nhttp://blog.dornea.nu/2014/07/07/disect-android-apks-like-a-pro-static-code-analysis/\r\nPage 1 of 19\n\nSetup PATH #\r\n 1\r\n 2\r\n 3\r\n 4\r\n 5\r\n 6\r\n 7\r\n 8\r\n 9\r\n10\r\n11\r\n12\r\n13\r\nimport os\r\nimport sys\r\n# Adjust PYTHONPATH\r\nsys.path.append(os.path.expanduser('~/work/bin/androguard'))\r\n# Setup new PATH\r\nold_path = os.environ['PATH']\r\nnew_path = old_path + \":\" + \"/root/work/apk/SDK/android-sdk-linux/tools:/root/work/apk/SDK/android-sdk-linux/platform-tools:/root/work/ap\r\nos.environ['PATH'] = new_path\r\n# Change working directory\r\nos.chdir(\"/root/work/apk/\")\r\nSetup IPython settings #\r\n 1\r\n 2\r\n 3\r\n 4\r\n 5\r\n 6\r\n 7\r\n 8\r\n 9\r\n10\r\n%pylab inline\r\nimport pandas as pd\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\nimport networkx as nx\r\nfrom IPython.display import display_pretty, display_html, display_jpeg, display_png, display_json, display_latex, display_svg\r\n# Androguard stuff\r\nimport androlyze as anz\r\n#import corae.bytecodes.dvm as dvm\r\nPopulating the interactive namespace from numpy and matplotlib\r\nGet malicious APKs #\r\nNow that you got everything running it’s time to get some malicious APKs to play with. On contagio mobile you’ll get tons\r\nof malicious files to look at. I’ve decided to look at the Fake Banker:\r\n1 (env)root@kali:~/work/apk/DroidBox/APK# wget http://www.mediafire.com/download/e938k6t3y6ul1yy/FakeBankerAPKs.zip\r\nNow you’ll have to extract the archive. As mentioned on the site you’ll have to contact the sites maintainer in order to get\r\nthe password for the files. Thanks to @snowfl0w for providing me the password.\r\nNOTE: The ordinary unzip command will fail to extract the files. You should install p7zip.\r\n 1\r\n 2\r\n 3\r\n 4\r\n 5\r\n 6\r\n 7\r\n 8\r\n 9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n(env)root@kali:~/work/apk/DroidBox/APK# 7z e FakeBankerAPKs.zip\r\n7-Zip [64] 9.20 Copyright (c) 1999-2010 Igor Pavlov 2010-11-18\r\np7zip Version 9.20 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,1 CPU)\r\nProcessing archive: FakeBankerAPKs.zip\r\nExtracting 7276e76298c50d2ee78271cf5114a176\r\nEnter password (will not be echoed) :\r\nExtracting a15b704743f53d3edb9cdd1182ca78d1\r\nExtracting aac4d15741abe0ee9b4afe78be090599\r\nEverything is Ok\r\nFiles: 3\r\nhttp://blog.dornea.nu/2014/07/07/disect-android-apks-like-a-pro-static-code-analysis/\r\nPage 2 of 19\n\n17\r\n18\r\nSize: 629877\r\nCompressed: 622336\r\nScratch the surface #\r\nIn this section we’ll have a brief look at the APK(s):\r\nWhich files does the APK contain?\r\nHow is the APK built?\r\nCan we find some vital information e.g. permissions the APK will have when installed on the device?\r\nWhat about other ressources?\r\n1\r\n2\r\n# Change CWD\r\nos.chdir(\"/root/work/apk/DroidBox/APK\")\r\nCheck APKs contents #\r\n1\r\n2\r\n%%bash\r\nfor i in *; do file $i; done\r\n7276e76298c50d2ee78271cf5114a176: Zip archive data, at least v2.0 to extract\r\na15b704743f53d3edb9cdd1182ca78d1: Zip archive data, at least v2.0 to extract\r\naac4d15741abe0ee9b4afe78be090599: Zip archive data, at least v2.0 to extract\r\nZippped files #\r\n1\r\n2\r\n%%bash\r\nunzip -l 7276e76298c50d2ee78271cf5114a176\r\nArchive: 7276e76298c50d2ee78271cf5114a176\r\nsigned by SignApk\r\n Length Date Time Name\r\n--------- ---------- ----- ----\r\n 1119 2008-02-29 05:33 META-INF/MANIFEST.MF\r\n 1172 2008-02-29 05:33 META-INF/CERT.SF\r\n 1714 2008-02-29 05:33 META-INF/CERT.RSA\r\n 5004 2008-02-29 05:33 AndroidManifest.xml\r\n 394740 2008-02-29 05:33 classes.dex\r\n 6426 2008-02-29 05:33 res/drawable-hdpi/ic_launcher1.png\r\n 14738 2008-02-29 05:33 res/drawable-hdpi/logo.png\r\n 2052 2008-02-29 05:33 res/drawable-ldpi/ic_launcher1.png\r\n 3231 2008-02-29 05:33 res/drawable-mdpi/ic_launcher1.png\r\n 8824 2008-02-29 05:33 res/drawable-xhdpi/ic_launcher1.png\r\n 1012 2008-02-29 05:33 res/layout/actup.xml\r\n 620 2008-02-29 05:33 res/layout/main.xml\r\n 4200 2008-02-29 05:33 res/layout/main2.xml\r\n 432 2008-02-29 05:33 res/menu/main.xml\r\n 56 2008-02-29 05:33 res/raw/blfs.key\r\n 1048 2008-02-29 05:33 res/raw/config.cfg\r\n 3196 2008-02-29 05:33 resources.arsc\r\n--------- -------\r\n 449584 17 files\r\nDump APKs content with apktool #\r\n1\r\n2\r\n%%bash\r\ncp 7276e76298c50d2ee78271cf5114a176 FakeBanker.apk\r\nhttp://blog.dornea.nu/2014/07/07/disect-android-apks-like-a-pro-static-code-analysis/\r\nPage 3 of 19\n\n3 java -jar /root/work/bin/apktool1.5.2/apktool.jar d 7276e76298c50d2ee78271cf5114a176 output\nDestination directory (/root/work/apk/DroidBox/APK/output) already exists. Use -f switch if you want to overwrite it.\nAndroidManifest.xml #\n1\n2\n%%bash\ncat output/AndroidManifest.xml\nCheck for media #\n1\n2\n3\n4\nmedia_paths = !find output -regextype posix-egrep -regex \"^.*\\.(png|jpg|jpeg|gif|bmp)$\"\ndisplay(media_paths)\nfor p in media_paths:\n display(Image(filename=p))\nhttp://blog.dornea.nu/2014/07/07/disect-android-apks-like-a-pro-static-code-analysis/\nPage 4 of 19\n\n['output/res/drawable-ldpi/ic_launcher1.png',\r\n 'output/res/drawable-mdpi/ic_launcher1.png',\r\n 'output/res/drawable-xhdpi/ic_launcher1.png',\r\n 'output/res/drawable-hdpi/logo.png',\r\n 'output/res/drawable-hdpi/ic_launcher1.png']\r\nDirectory structure #\r\n1\r\n2\r\n3\r\n%%bash\r\n# Ignore smali directory\r\ntree -f -I \"smali\" output/\r\noutput\r\n├── output/AndroidManifest.xml\r\n├── output/apktool.yml\r\n└── output/res\r\n ├── output/res/drawable-hdpi\r\n │ ├── output/res/drawable-hdpi/ic_launcher1.png\r\n │ └── output/res/drawable-hdpi/logo.png\r\n ├── output/res/drawable-ldpi\r\n │ └── output/res/drawable-ldpi/ic_launcher1.png\r\n ├── output/res/drawable-mdpi\r\n │ └── output/res/drawable-mdpi/ic_launcher1.png\r\n ├── output/res/drawable-xhdpi\r\n │ └── output/res/drawable-xhdpi/ic_launcher1.png\r\n ├── output/res/layout\r\n │ ├── output/res/layout/actup.xml\r\n │ ├── output/res/layout/main2.xml\r\n │ └── output/res/layout/main.xml\r\n ├── output/res/menu\r\n │ └── output/res/menu/main.xml\r\n ├── output/res/raw\r\n │ ├── output/res/raw/blfs.key\r\n │ └── output/res/raw/config.cfg\r\n └── output/res/values\r\n ├── output/res/values/ids.xml\r\n ├── output/res/values/public.xml\r\n ├── output/res/values/strings.xml\r\n └── output/res/values/styles.xml\r\n9 directories, 17 files\r\nhttp://blog.dornea.nu/2014/07/07/disect-android-apks-like-a-pro-static-code-analysis/\r\nPage 5 of 19\n\nFirst findings #\nHaving a look at the AndroidManifest.xml file itself you can see that we have a MessageReceiver with a quite high priority:\n1\n2\n3\n4\n5 That looks very suspicious as well. What about the main entry point:\n1\n2\n3\n4\n5\n6 So obviously the class com.gmail.xpack.MainActivity contains the main entry point. In the next steps we will have a closer\nlook at the code. Besides that there are 2 files which might be interesting:\noutput/res/raw/blfs.key\noutput/res/raw/config.cfg\nStatic code analysis using AndroGuard #\n 1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9\n10\n11\n# Use AndroGuard to static analysis\n# Have a look at https://code.google.com/p/androguard/wiki/RE for some introduction\n#a = anz.APK('KC.apk')\na, d, dx = anz.AnalyzeAPK('FakeBanker.apk',decompiler='dex2jar')\n\"\"\"\nd = anz.DalvikVMFormat(a.get_dex())\ndx = anz.VMAnalysis( d )\ngx = anz.GVMAnalysis( dx, None )\nd.set_vmanalysis( dx )\nd.set_gvmanalysis( gx )\n\"\"\"\n'\\nd = anz.DalvikVMFormat(a.get_dex())\\ndx = anz.VMAnalysis( d )\\ngx = anz.GVMAnalysis( dx, None )\\nd.set_vmanalysis( dx\nAnalyze the manifest file #\n{'AndroidManifest.xml': 'Unknown',\n 'META-INF/CERT.RSA': 'Unknown',\n 'META-INF/CERT.SF': 'Unknown',\n 'META-INF/MANIFEST.MF': 'Unknown',\n 'classes.dex': 'Unknown',\n 'res/drawable-hdpi/ic_launcher1.png': 'Unknown',\n 'res/drawable-hdpi/logo.png': 'Unknown',\n 'res/drawable-ldpi/ic_launcher1.png': 'Unknown',\n 'res/drawable-mdpi/ic_launcher1.png': 'Unknown',\n 'res/drawable-xhdpi/ic_launcher1.png': 'Unknown',\n 'res/layout/actup.xml': 'Unknown',\n 'res/layout/main.xml': 'Unknown',\n 'res/layout/main2.xml': 'Unknown',\n 'res/menu/main.xml': 'Unknown',\n 'res/raw/blfs.key': 'Unknown',\n 'res/raw/config.cfg': 'Unknown',\n 'resources.arsc': 'Unknown'}\nhttp://blog.dornea.nu/2014/07/07/disect-android-apks-like-a-pro-static-code-analysis/\nPage 6 of 19\n\n['android.permission.RECEIVE_BOOT_COMPLETED',\r\n 'android.permission.READ_SMS',\r\n 'android.permission.RECEIVE_SMS',\r\n 'android.permission.INTERNET',\r\n 'android.permission.READ_PHONE_STATE',\r\n 'android.permission.ACCESS_COARSE_LOCATION',\r\n 'android.permission.ACCESS_NETWORK_STATE']\r\n['com.gmail.xpack.MainActivity', 'com.gmail.xpack.ActUpdate']\r\n['com.gmail.xservices.XService', 'com.gmail.xservices.XSmsIncom']\r\n['com.gmail.xbroadcast.OnBootReceiver',\r\n 'com.gmail.xbroadcast.MessageReceiver',\r\n 'com.gmail.xservices.XRepeat',\r\n 'com.gmail.xservices.XUpdate']\r\nu'com.gmail.xpack.MainActivity'\r\n1 d.CLASS_Lcom_gmail_xpack_MainActivity.METHOD_onCreate.source()\r\n protected void onCreate(android.os.Bundle p5)\r\n {\r\n super.onCreate(p5);\r\n this.setContentView(1.741289080126432e+38);\r\n this.show_hide(Integer.valueOf(com.gmail.xlibs.myFunctions.getVar(\"PASSADDED\", 0, this)));\r\n com.gmail.xlibs.myFunctions.sendLog(\"START\", \"Service started\", this);\r\n this.startService(new android.content.Intent(\"XMainProcessStart\").putExtra(\"name\", \"value\"));\r\n return;\r\n }\r\nOk let’s have a look at com.gmail.xlibs.myFunctions:\r\n1\r\n2\r\nmethods = d.CLASS_Lcom_gmail_xlibs_myFunctions.get_methods()\r\nfor m in methods: print(m.get_name())\r\n\u003cinit\u003e\r\nIntToStr\r\nStrToInt\r\ncheckPhone\r\ndeviceInfo\r\nfoundCodeInSms\r\ngetCheckedURL\r\ngetFirst\r\ngetRawData\r\ngetSecond\r\ngetVar\r\ngetVar\r\nin_array\r\nisOnline\r\nloadNumFromPreferences\r\nparseXml\r\nsendFromDb\r\nsendLog\r\nsendMessge\r\nsetK12\r\nsetVar\r\nsetVar\r\nhttp://blog.dornea.nu/2014/07/07/disect-android-apks-like-a-pro-static-code-analysis/\r\nPage 7 of 19\n\nsetVarsList\r\ntypeInternetConnection\r\nHaving a look at the whole class you can see that there are a lot of methods. But this one looks very interesting:\r\n1 d.CLASS_Lcom_gmail_xlibs_myFunctions.METHOD_setK12.source()\r\n public static String setK12(String p4, android.content.Context p5)\r\n {\r\n String v2;\r\n String v1 = com.gmail.xlibs.myFunctions.getVar(\"BLFSK\", \"\", p5);\r\n if (v1.length() \u003c= 0) {\r\n v2 = \"\";\r\n } else {\r\n v2 = new com.gmail.xlibs.Blowfish(v1, \"base64\", \"12345678\").encrypt(p4);\r\n }\r\n return v2;\r\n }\r\nDecrypting stuff #\r\nApparently there is an encryption routine (Blowfish) used for some stuff. In this case a new class v2 is initialized. The\r\nconstructor gets several parameters:\r\ncontent of BLFSK ( v1 )\r\nbase64 (I thing this sort of flag)\r\n“1234578” (Looks like some IV)\r\nLet’s have a look at the Blowfish class:\r\n1 d.CLASS_Lcom_gmail_xlibs_Blowfish.METHOD_init.source()\r\n public Blowfish(String p2, String p3, String p4)\r\n {\r\n this.IV = \"12345678\";\r\n this.in_out_format = \"clear\";\r\n this.IV = p4;\r\n this.strkey = p2;\r\n this.in_out_format = p3;\r\n return;\r\n }\r\np2 (the first argument) is the key. Looking one step before let’s find out what’s inside the BLFSK variable. First let’s see\r\nwhere the variable is beeing used:\r\n1\r\n2\r\nz = dx.tainted_variables.get_string(\"BLFSK\")\r\nif z: z.show_paths(d)\r\nR 62 Lcom/gmail/xlibs/myFunctions;-\u003egetFirst (Landroid/content/Context;)V\r\nR 17c Lcom/gmail/xlibs/myFunctions;-\u003egetFirst (Landroid/content/Context;)V\r\nR e8 Lcom/gmail/xlibs/myFunctions;-\u003egetSecond (Landroid/content/Context;)V\r\nR 0 Lcom/gmail/xlibs/myFunctions;-\u003esetK12 (Ljava/lang/String; Landroid/content/Context;)Ljava/lang/String;\r\nLet’s search for some content/files:\r\n1\r\n2\r\n%%bash\r\nfind output -name \"blfs*\"\r\nhttp://blog.dornea.nu/2014/07/07/disect-android-apks-like-a-pro-static-code-analysis/\r\nPage 8 of 19\n\noutput/res/raw/blfs.key\r\n1\r\n2\r\n%%bash\r\ncat output/res/raw/blfs.key\r\nNfvnkjlnvkjKCNXKDKLFHSKD:LJmdklsXKLNDS:\u003cXObcniuaebkjxbcz\r\nWell that looks like a key to me :). What else can we find inside output/res/raw :\r\n1\r\n2\r\n%%bash\r\nls -l output/res/raw\r\ntotal 8\r\n-rw-r--r-- 1 root root 56 Jun 25 13:26 blfs.key\r\n-rw-r--r-- 1 root root 1048 Jun 25 13:26 config.cfg\r\n1\r\n2\r\n%%bash\r\ncat output/res/raw/config.cfg\r\nHoBbgAt+xT9vXJUlyhYYAVOx5Oy4XSMLyc7JC+ly5a1tbUtWvFMny2yqavP9D9GT0ogg2U4LN5FZE8/0Y2duLgE7dfXLPcaeXKoIuTmJ4LBiUjS0OokxUPMOr\r\nI’ve checked that content and it’s base64:\r\n1\r\n2\r\n3\r\n%%bash\r\nbase64 -d output/res/raw/config.cfg \u003e output/res/raw/config.cfg.raw\r\nfile output/res/raw/config.cfg.raw\r\noutput/res/raw/config.cfg.raw: data\r\nOk. Now let’s decrypt that file. But first let’s have a look at the used encryption parameters:\r\n1 d.CLASS_Lcom_gmail_xlibs_Blowfish.METHOD_encrypt.source()\r\n public String encrypt(String p10)\r\n {\r\n try {\r\n String v7;\r\n javax.crypto.spec.SecretKeySpec v6 = new javax.crypto.spec.SecretKeySpec(this.strkey.getBytes(), \"Blowfish\");\r\n javax.crypto.Cipher v0 = javax.crypto.Cipher.getInstance(\"Blowfish/CBC/PKCS5Padding\");\r\n v0.init(1, v6, new javax.crypto.spec.IvParameterSpec(this.IV.getBytes()));\r\n byte[] v3 = v0.doFinal(p10.getBytes());\r\n } catch (Exception v1) {\r\n v7 = 0;\r\n return v7;\r\n }\r\n if (this.in_out_format != \"base64\") {\r\n if (this.in_out_format != \"hex\") {\r\n v7 = new String(v3, \"UTF8\");\r\n } else {\r\n v7 = com.gmail.xlibs.Blowfish.byte2hex(v3);\r\n }\r\n } else {\r\n v7 = android.util.Base64.encodeToString(v3, 0);\r\nhttp://blog.dornea.nu/2014/07/07/disect-android-apks-like-a-pro-static-code-analysis/\r\nPage 9 of 19\n\n}\r\n return v7;\r\n }\r\nSo we have Blowfish with CBC. The decryption method:\r\n1 d.CLASS_Lcom_gmail_xlibs_Blowfish.METHOD_decrypt.source()\r\n public String decrypt(String p9)\r\n {\r\n try {\r\n byte[] v1;\r\n javax.crypto.spec.SecretKeySpec v5 = new javax.crypto.spec.SecretKeySpec(this.strkey.getBytes(), \"Blowfish\");\r\n javax.crypto.Cipher v0 = javax.crypto.Cipher.getInstance(\"Blowfish/CBC/PKCS5Padding\");\r\n v0.init(2, v5, new javax.crypto.spec.IvParameterSpec(this.IV.getBytes()));\r\n } catch (Exception v2) {\r\n byte[] v6 = 0;\r\n return v6;\r\n }\r\n if (this.in_out_format != \"base64\") {\r\n if (this.in_out_format != \"hex\") {\r\n v1 = v0.doFinal(p9.getBytes(\"UTF8\"));\r\n } else {\r\n v1 = v0.doFinal(com.gmail.xlibs.Blowfish.hex2byte(p9));\r\n }\r\n } else {\r\n v1 = v0.doFinal(android.util.Base64.decode(p9, 0));\r\n }\r\n v6 = new String(v1);\r\n return v6;\r\n }\r\nAs I’ve looked into the code I’ve noticed that a new Blowfish class is initiated in com/xlibs/myFunctions.class.\r\n1 d.CLASS_Lcom_gmail_xlibs_myFunctions.METHOD_getFirst.source()\r\n public static void getFirst(android.content.Context p14)\r\n {\r\n if (com.gmail.xlibs.myFunctions.getVar(\"RID\", 0, p14) == 0) {\r\n String v5 = com.gmail.xlibs.myFunctions.getRawData(1.754580954436089e+38, 0, p14);\r\n com.gmail.xlibs.myFunctions.parseXml(new com.gmail.xlibs.Blowfish(v5, \"base64\", \"12345678\").decrypt(com.gmail.\r\n int v0 = com.gmail.xlibs.myFunctions.getVar(\"RID\", 0, p14);\r\n if (v0 != 0) {\r\n com.gmail.xlibs.myFunctions.setVar(\"BLFSK\", v5, p14);\r\n com.gmail.xlibs.myFunctions.setVar(\"RID\", v0, p14);\r\n String v9 = com.gmail.xlibs.myFunctions.getVar(\"USE_URL_MAIN\", \"\", p14);\r\n if (v9.trim().length() \u003e 0) {\r\n String[] v7 = new String[3];\r\n v7[0] = \"data\";\r\n v7[1] = \"rid\";\r\n v7[2] = \"first\";\r\n String[] v10 = new String[3];\r\n v10[0] = com.gmail.xlibs.Blowfish.base64_encode(com.gmail.xlibs.myFunctions.deviceInfo(p14));\r\n v10[1] = com.gmail.xlibs.myFunctions.IntToStr(com.gmail.xlibs.myFunctions.getVar(\"RID\", 0, p14));\r\n v10[2] = \"true\";\r\n try {\r\n String v8 = com.gmail.xlibs.SimpleCurl.httpPost(v9, com.gmail.xlibs.SimpleCurl.prepareVars(v7, v10\r\n } catch (java.io.IOException v4) {\r\n com.gmail.xlibs.myFunctions.setVar(\"RID\", 0, p14);\r\n v4.printStackTrace();\r\n } catch (java.io.IOException v4) {\r\n com.gmail.xlibs.myFunctions.setVar(\"RID\", 0, p14);\r\n v4.printStackTrace();\r\n }\r\nhttp://blog.dornea.nu/2014/07/07/disect-android-apks-like-a-pro-static-code-analysis/\r\nPage 10 of 19\n\nv8 = v8.trim();\r\n if ((v8 != null) \u0026\u0026 (v8.length() != 0)) {\r\n com.gmail.xlibs.myFunctions.setVar(\"BLFSK\", v8, p14);\r\n } else {\r\n com.gmail.xlibs.myFunctions.setVar(\"RID\", 0, p14);\r\n android.util.Log.d(\"HTTP\", \"Obnilim rid\");\r\n }\r\n }\r\n }\r\n }\r\n return;\r\n }\r\nEspecially this line is very interesting:\r\n1 com.gmail.xlibs.myFunctions.parseXml(new com.gmail.xlibs.Blowfish(v5, \"base64\", \"12345678\").decrypt(com.gmail.xlibs.myFunctions.getRawDa\r\nObviously the encrypted file has some XML content which has to be parsed first. The parseXML function requires a String\r\nparameter containing the XML code. The XML code has to be first decrypted:\r\n1 new com.gmail.xlibs.Blowfish(v5, \"base64\", \"12345678\")\r\nThis will create a new Blowfish object where the parameters are set as shown below:\r\nv5\r\nString v5 = com.gmail.xlibs.myFunctions.getRawData(1.754580954436089e+38, 0, p14);\r\nbase64\r\nFormat of ciphertext to be decrypted\r\n12345678\r\nThe IV used for the Blowfish cipher\r\nv5 contains the encryption key. This is have to be loaded first using getRawData . Let’s have a look at it:\r\n1 d.CLASS_Lcom_gmail_xlibs_myFunctions.METHOD_getRawData.source()\r\n public static String getRawData(int p9, int p10, android.content.Context p11)\r\n {\r\n java.io.InputStream v4 = p11.getResources().openRawResource(p9);\r\n java.io.ByteArrayOutputStream v0 = new java.io.ByteArrayOutputStream();\r\n try {\r\n int v3 = v4.read();\r\n } catch (java.io.IOException v2) {\r\n v2.printStackTrace();\r\n String v6;\r\n String v5 = v0.toString();\r\n if (p10 != 0) {\r\n v6 = v5;\r\n } else {\r\n v6 = com.gmail.xlibs.Blowfish.byte2hex(v5.getBytes()).substring(0, 50);\r\n }\r\n return v6;\r\n }\r\n while (v3 != -1) {\r\n v0.write(v3);\r\n v3 = v4.read();\r\n }\r\n v4.close();\r\n }\r\nThe first argument p9 is a shared preference and contains the name of the file the content should be read from. Using\r\nanother decompiler l I had a look at the getFirst method and found this:\r\nhttp://blog.dornea.nu/2014/07/07/disect-android-apks-like-a-pro-static-code-analysis/\r\nPage 11 of 19\n\n1\n2\n3\n4\n5\n...\nString str1 = getRawData(2130968576, 0, paramContext);\nString str2 = getRawData(2130968577, 1, paramContext);\nparseXml(new Blowfish(str1, \"base64\", \"12345678\").decrypt(str2), paramContext);\n...\nNow what are those numbers: 2130968576 and 2130968577? Those are ressource identifier. If we hex them we’ll have:\nhex(2130968576) = 7f040000 and\nhex(2130968577) = 7f040001\nLet’s search the files for this pattern:\n1\n2\n%%bash\ngrep -r \"7f040000\" output/* \u0026\u0026 grep -r \"7f040001\" output/*\noutput/res/values/public.xml: output/smali/com/gmail/xpack/R$raw.smali:.field public static final blfs:I = 0x7f040000\noutput/res/values/public.xml: output/smali/com/gmail/xlibs/myFunctions.smali: const v11, 0x7f040001\noutput/smali/com/gmail/xpack/R$raw.smali:.field public static final config:I = 0x7f040001\nSo those numbers are indeed ressources. What kind of?\n1\n2\n%%bash\ngrep \"7f04000\" output/smali/com/gmail/xpack/R\\$raw.smali\n.field public static final blfs:I = 0x7f040000\n.field public static final config:I = 0x7f040001\nBingo! Now we know that:\n2130968576 -\u003e 7f040000 -\u003e blfs.key\n2130968577 -\u003e 7f040000 -\u003e blfs.cfg\nSo let’s summarize some things:\ngetFirst() calls getRawData twice\n1. getRawData(\"blfs.key\", 0, paramContext);\n2. getRawData(\"config.cg\", 1, paramContext);\nIn getRawData() there is no magic happing: Some file stream is created and then the conten is read. BUT: There is one catch\nabout it:\n1\n2\n3\n4\n5\nif (p10 != 0) {\n v6 = v5;\n} else {\n v6 = com.gmail.xlibs.Blowfish.byte2hex(v5.getBytes()).substring(0, 50);\n}\np10 is the 2nd argument given to getRawData. If you pay attention you may notice that if p10 == 1 nothing special will\nhappen. Otherwise (content of blfs.key is read out) there are some string manipulations taking place. This took me a while to\nnotice it and was the reason I couldn’t decrypt the config.cfg. This is what it does with the content of blfs.key :\nconvert string to byte array\nconvert byte array to hex string\ntake only the first 50 bytes\nhttp://blog.dornea.nu/2014/07/07/disect-android-apks-like-a-pro-static-code-analysis/\nPage 12 of 19\n\nIn the end v6 will contain the decryption key. Using PyCrypto we’ll try to decrypt the content in Python:\r\n 1\r\n 2\r\n 3\r\n 4\r\n 5\r\n 6\r\n 7\r\n 8\r\n 9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\n22\r\n23\r\n24\r\n25\r\nfrom Crypto.Cipher import Blowfish\r\nfrom Crypto import Random\r\nfrom struct import pack\r\nfrom binascii import hexlify, unhexlify\r\n# Read content from files\r\nblfs_key = !cat output/res/raw/blfs.key\r\nciphertext_base64 = !cat output/res/raw/config.cfg\r\nciphertext_raw = ciphertext_base64[0].decode(\"base64\")\r\n# Some settings\r\nIV = \"12345678\"\r\n_KEY = blfs_key[0]\r\nciphertext = ciphertext_raw\r\n# As seen in the source code:\r\n# * hex-encode the blfs key\r\n# * take only the substring[0:50]\r\nKEY = hexlify(_KEY)[:50]\r\n# Do the decryption\r\ncipher = Blowfish.new(KEY, Blowfish.MODE_CBC, IV)\r\nmessage = cipher.decrypt(ciphertext)\r\nmessage\r\n'\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\\n \u003cconfig\u003e\\n \u003cdata rid=\"25\" \\n shnum10=\"\" sh\r\nYeay! Now a more structured look at the XML:\r\n 1\r\n 2\r\n 3\r\n 4\r\n 5\r\n 6\r\n 7\r\n 8\r\n 9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\nfrom lxml import etree\r\nimport xml.etree.ElementTree as ET\r\n# Remove dirty characters\r\nxml = message.replace(\"\\x05\", \"\").replace(\"\\n\", \"\")\r\n# Create XML tree from string\r\nroot = etree.XML(xml)\r\ndata = root.xpath(\"/config//data\")\r\nframe = []\r\n# Get data\r\nfor sample in data:\r\n for attr_name, attr_value in sample.items():\r\n values = attr_value.split(\";\")\r\n for v in values:\r\n frame.append((attr_name, v))\r\n# Show attributes found in /config/data\r\ndf = pd.DataFrame(frame, columns=['Attribute', 'Value'])\r\ndf\r\nAttribute Value\r\n0 rid 25\r\n1 shnum10\r\n2 shtext10\r\n3 shnum5\r\n4 shtext5\r\nhttp://blog.dornea.nu/2014/07/07/disect-android-apks-like-a-pro-static-code-analysis/\r\nPage 13 of 19\n\nAttribute Value\r\n5 shnum3\r\n6 shtext3\r\n7 shnum1\r\n8 shtext1\r\n9 del_dev 0\r\n10 url_main http://best-invest-int.com/gallery/3.php\r\n11 url_main http://citroen-club.ch/images/3.php\r\n12 url_data http://best-invest-int.com/gallery/1.php\r\n13 url_data http://citroen-club.ch/images/1.php\r\n14 url_sms http://best-invest-int.com/gallery/2.php\r\n15 url_sms http://citroen-club.ch/images/2.php\r\n16 url_log http://best-invest-int.com/gallery/4.php\r\n17 url_log http://citroen-club.ch/images/4.php\r\n18 download_domain certificates-security.com\r\n19 ready_to_bind 0\r\nInspect malwares config #\r\nLooks interesting. Are those URLs still available?\r\n 1\r\n 2\r\n 3\r\n 4\r\n 5\r\n 6\r\n 7\r\n 8\r\n 9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\n22\r\n23\r\n24\r\n25\r\n26\r\n27\r\n28\r\nimport urllib2\r\n \r\n# Get URLs from DataFrame (only the valid ones)\r\nurls = df['Value'][10:19].tolist()\r\n#urls = [\"http://google.de/\", \"http://blog.dornea.nu/about\"]\r\nresp = {}\r\ndef get_status_code(host, path=\"/\"):\r\n \"\"\" This function retreives the status code of a website by requesting\r\n HEAD data from the host. This means that it only requests the headers.\r\n If the host cannot be reached or something else goes wrong, it returns\r\n None instead.\r\n \"\"\"\r\n try:\r\n conn = httplib.HTTPConnection(host)\r\n conn.request(\"HEAD\", path)\r\n return conn.getresponse().getheaders()\r\n except StandardError:\r\n return None\r\n# Iterate through URLs\r\nfor u in urls:\r\n p = '(?:http.*://)?(?P\u003chost\u003e[^:/ ]+).?(?P\u003cport\u003e[0-9]*)/(?P\u003cpath\u003e.*)'\r\n m = re.search(p, u)\r\n if m:\r\n status_code = get_status_code(m.group('host'), \"/\" + m.group('path'))\r\n resp[u] = status_code\r\nresp\r\n{'http://best-invest-int.com/gallery/1.php': None,\r\n 'http://best-invest-int.com/gallery/2.php': None,\r\n 'http://best-invest-int.com/gallery/3.php': None,\r\n 'http://best-invest-int.com/gallery/4.php': None,\r\n 'http://citroen-club.ch/images/1.php': [('date',\r\nhttp://blog.dornea.nu/2014/07/07/disect-android-apks-like-a-pro-static-code-analysis/\r\nPage 14 of 19\n\n'Fri, 04 Jul 2014 08:31:12 GMT'),\r\n ('content-type', 'text/html; charset=iso-8859-1'),\r\n ('server', 'Apache')],\r\n 'http://citroen-club.ch/images/2.php': [('date',\r\n 'Fri, 04 Jul 2014 08:31:12 GMT'),\r\n ('content-type', 'text/html; charset=iso-8859-1'),\r\n ('server', 'Apache')],\r\n 'http://citroen-club.ch/images/3.php': [('date',\r\n 'Fri, 04 Jul 2014 08:31:11 GMT'),\r\n ('content-type', 'text/html; charset=iso-8859-1'),\r\n ('server', 'Apache')],\r\n 'http://citroen-club.ch/images/4.php': [('date',\r\n 'Fri, 04 Jul 2014 08:31:12 GMT'),\r\n ('content-type', 'text/html; charset=iso-8859-1'),\r\n ('server', 'Apache')]}\r\nHmmm.. Nothing special. The servers might have been patched meanwhile against this malware. I hope we’re going to see\r\nmore when doing the dynamic analysis.\r\nControl Flow Graph (CFG) #\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n%%bash\r\nmkdir DEX\r\ncp 7276e76298c50d2ee78271cf5114a176 DEX\r\ncd DEX\r\nunzip 7276e76298c50d2ee78271cf5114a176\r\ncd ..\r\nArchive: 7276e76298c50d2ee78271cf5114a176\r\nsigned by SignApk\r\n inflating: META-INF/MANIFEST.MF\r\n inflating: META-INF/CERT.SF\r\n inflating: META-INF/CERT.RSA\r\n inflating: AndroidManifest.xml\r\n inflating: classes.dex\r\n extracting: res/drawable-hdpi/ic_launcher1.png\r\n extracting: res/drawable-hdpi/logo.png\r\n extracting: res/drawable-ldpi/ic_launcher1.png\r\n extracting: res/drawable-mdpi/ic_launcher1.png\r\n extracting: res/drawable-xhdpi/ic_launcher1.png\r\n inflating: res/layout/actup.xml\r\n inflating: res/layout/main.xml\r\n inflating: res/layout/main2.xml\r\n inflating: res/menu/main.xml\r\n extracting: res/raw/blfs.key\r\n inflating: res/raw/config.cfg\r\n inflating: resources.arsc\r\n 1\r\n 2\r\n 3\r\n 4\r\n 5\r\n 6\r\n 7\r\n 8\r\n 9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\nimport hashlib\r\nimport StringIO, pydot\r\nfrom IPython.display import Image\r\nfrom androguard.core.bytecodes.dvm import *\r\nfrom androguard.core.analysis.analysis import VMAnalysis\r\nfrom androguard.core.bytecode import method2dot, method2format, method2png\r\nd = DalvikVMFormat(open(\"DEX/classes.dex\").read())\r\nx = VMAnalysis(d)\r\nd.set_vmanalysis(x)\r\n# Utilities\r\ndef create_graph(data, output):\r\n # Stolen from androguard/core/bytecode.py\r\n buff = \"digraph {\\n\"\r\nhttp://blog.dornea.nu/2014/07/07/disect-android-apks-like-a-pro-static-code-analysis/\r\nPage 15 of 19\n\n18\r\n19\r\n20\r\n21\r\n22\r\n23\r\n24\r\n25\r\n26\r\n27\r\n28\r\n29\r\n30\r\n31\r\n32\r\n buff += \"graph [rankdir=TB]\\n\"\r\n buff += \"node [shape=plaintext]\\n\"\r\n \r\n # subgraphs cluster\r\n buff += \"subgraph cluster_\" + hashlib.md5(output).hexdigest() + \" {\\nlabel=\\\"%s\\\"\\n\" % data['name']\r\n buff += data['nodes']\r\n buff += \"}\\n\"\r\n # subgraphs edges\r\n buff += data['edges']\r\n buff += \"}\\n\"\r\n \r\n graph = pydot.graph_from_dot_data(buff)\r\n \r\n return graph\r\ncom.gmail.xlibs.myFunctions: getFirst() #\r\n 1\r\n 2\r\n 3\r\n 4\r\n 5\r\n 6\r\n 7\r\n 8\r\n 9\r\n10\r\n11\r\n12\r\n13\r\n# Definition: d.get_method_descriptor(self, class_name, method_name, descriptor)\r\n#\r\n# Examples for method descriptors:\r\n# R 62 Lcom/gmail/xlibs/myFunctions;-\u003egetFirst (Landroid/content/Context;)V\r\n# R 17c Lcom/gmail/xlibs/myFunctions;-\u003egetFirst (Landroid/content/Context;)V\r\n# R e8 Lcom/gmail/xlibs/myFunctions;-\u003egetSecond (Landroid/content/Context;)V\r\n# R 0 Lcom/gmail/xlibs/myFunctions;-\u003esetK12 (Ljava/lang/String; Landroid/content/Context;)Ljava/lang/String;\r\nm = d.get_method_descriptor(\"Lcom/gmail/xlibs/myFunctions;\", \"getFirst\", \"(Landroid/content/Context;)V\")\r\nbuff_dot = method2dot(x.get_method(m))\r\ngraph = create_graph(buff_dot, \"png\")\r\nImage(graph.create_png())\r\nhttp://blog.dornea.nu/2014/07/07/disect-android-apks-like-a-pro-static-code-analysis/\r\nPage 16 of 19\n\ncom.gmail.xlibs.myFunctions: getSecond() #\r\n1\r\n2\r\n3\r\n4\r\n5\r\nm = d.get_method_descriptor(\"Lcom/gmail/xlibs/myFunctions;\", \"getSecond\", \"(Landroid/content/Context;)V\")\r\nbuff_dot = method2dot(x.get_method(m))\r\ngraph = create_graph(buff_dot, \"png\")\r\nImage(graph.create_png())\r\nhttp://blog.dornea.nu/2014/07/07/disect-android-apks-like-a-pro-static-code-analysis/\r\nPage 17 of 19\n\ncom.gmail.xservices: XRepeat #\r\ngetFirst and getSecond are both called from the class com.gmail.xservices.XRepeat in the method doInBackground .\r\nLet’s search for the appropriate method descriptor:\r\n1\r\n2\r\n3\r\n# We should have a list of PathP objects which represent where a specific method is called:\r\npaths = x.tainted_packages.search_methods(\".\", \"onReceive\", \".\")\r\npaths\r\n[\u003candroguard.core.analysis.analysis.PathP instance at 0x101a5290\u003e]\r\n 1\r\n 2\r\n 3\r\n 4\r\n 5\r\n 6\r\n 7\r\n 8\r\n 9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n# Get the class manager from the VM\r\ncm = d.get_class_manager()\r\nsrc = []\r\ndst = []\r\n# Iterate through paths\r\nfor p in paths:\r\n src.append(p.get_src(cm))\r\n dst.append(p.get_dst(cm))\r\ndf_src = pd.DataFrame(src, columns=['From', 'Method', 'Type'])\r\ndf_dst = pd.DataFrame(dst, columns=['To', 'Method', 'Type'])\r\ndisplay_html(df_src)\r\ndisplay_html(df_dst)\r\nFrom Method Type\r\n0 Landroid/support/v4/content/LocalBroadcastMana... executePendingBroadcasts ()V\r\nhttp://blog.dornea.nu/2014/07/07/disect-android-apks-like-a-pro-static-code-analysis/\r\nPage 18 of 19\n\nTo Method Type\r\n0 Landroid/content/BroadcastReceiver; onReceive (Landroid/content/Context; Landroid/content/In...\r\n1\r\n2\r\n3\r\n4\r\n5\r\nm = d.get_method_descriptor(\"Lcom/gmail/xservices/XRepeat;\", \"onReceive\", \"(Landroid/content/Context; Landroid/content/Intent;)V\")\r\nbuff_dot = method2dot(x.get_method(m))\r\ngraph = create_graph(buff_dot, \"png\")\r\nImage(graph.create_png())\r\nConclusion #\r\nI’ve found cool new ways how to analyze an APK using python tools. AndroGuard seems to be a quite good framework to\r\nwork it. Although I’ve managed it to get almost everything working, I must say that the project itself (and its components!)\r\naren’t well documented. Then I had several errors like this one:\r\n 1\r\n 2\r\n 3\r\n 4\r\n 5\r\n 6\r\n 7\r\n 8\r\n 9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n# ~/work/bin/androguard/androdd.py -i FakeBanker.apk -f PNG -o neu\r\nDump information FakeBanker.apk in neu\r\nClean directory neu\r\nAnalysis ... End\r\nDecompilation ... End\r\nERROR: module pydot not found\r\nDump Landroid/support/v4/app/FragmentManager; \u003cinit\u003e ()V ... PNG ...\r\nTraceback (most recent call last):\r\n File \"/root/work/bin/androguard/androdd.py\", line 222, in \u003cmodule\u003e\r\n main(options, arguments)\r\n File \"/root/work/bin/androguard/androdd.py\", line 207, in main~~~~\r\n export_apps_to_format(options.input, a, options.output, options.limit, options.jar, options.decompiler, options.format)\r\n File \"/root/work/bin/androguard/androdd.py\", line 180, in export_apps_to_format\r\n method2format(filename + \".\" + format, format, None, buff)\r\n File \"/root/work/bin/androguard/androguard/core/bytecode.py\", line 338, in method2format\r\n error(\"module pydot not found\")\r\n File \"/root/work/bin/androguard/androguard/core/androconf.py\", line 270, in error\r\n raise()\r\nTypeError: exceptions must be old-style classes or derived from BaseException, not tuple\r\nBut I don’t want to complain about the project. In fact I think its the most comprehensive tool bundle out there for analytical\r\npurposes. If you know better alternatives just let me know about it. In the next part I’ll be writing about dynamic code\r\nanalysis. So stay tuned :)\r\nSource: http://blog.dornea.nu/2014/07/07/disect-android-apks-like-a-pro-static-code-analysis/\r\nhttp://blog.dornea.nu/2014/07/07/disect-android-apks-like-a-pro-static-code-analysis/\r\nPage 19 of 19",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"ETDA",
		"Malpedia"
	],
	"references": [
		"http://blog.dornea.nu/2014/07/07/disect-android-apks-like-a-pro-static-code-analysis/"
	],
	"report_names": [
		"disect-android-apks-like-a-pro-static-code-analysis"
	],
	"threat_actors": [
		{
			"id": "aa73cd6a-868c-4ae4-a5b2-7cb2c5ad1e9d",
			"created_at": "2022-10-25T16:07:24.139848Z",
			"updated_at": "2026-04-10T02:00:04.878798Z",
			"deleted_at": null,
			"main_name": "Safe",
			"aliases": [],
			"source_name": "ETDA:Safe",
			"tools": [
				"DebugView",
				"LZ77",
				"OpenDoc",
				"SafeDisk",
				"TypeConfig",
				"UPXShell",
				"UsbDoc",
				"UsbExe"
			],
			"source_id": "ETDA",
			"reports": null
		},
		{
			"id": "75108fc1-7f6a-450e-b024-10284f3f62bb",
			"created_at": "2024-11-01T02:00:52.756877Z",
			"updated_at": "2026-04-10T02:00:05.273746Z",
			"deleted_at": null,
			"main_name": "Play",
			"aliases": null,
			"source_name": "MITRE:Play",
			"tools": [
				"Nltest",
				"AdFind",
				"PsExec",
				"Wevtutil",
				"Cobalt Strike",
				"Playcrypt",
				"Mimikatz"
			],
			"source_id": "MITRE",
			"reports": null
		}
	],
	"ts_created_at": 1775434753,
	"ts_updated_at": 1775791833,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/7756592bc690a96ea505bb5c44e3fbb43f3df0fe.pdf",
		"text": "https://archive.orkl.eu/7756592bc690a96ea505bb5c44e3fbb43f3df0fe.txt",
		"img": "https://archive.orkl.eu/7756592bc690a96ea505bb5c44e3fbb43f3df0fe.jpg"
	}
}