{
	"id": "fd00942a-cb64-4efa-871c-b6f917ecf8d9",
	"created_at": "2026-04-06T00:11:45.605774Z",
	"updated_at": "2026-04-10T03:21:38.708705Z",
	"deleted_at": null,
	"sha1_hash": "5ca67141fe351e3f38035022a37c4172bca7bd4b",
	"title": "Inside KangaPack: the Kangaroo packer with native decryption",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 1155853,
	"plain_text": "Inside KangaPack: the Kangaroo packer with native decryption\nBy @cryptax\nPublished: 2023-06-23 · Archived: 2026-04-05 21:13:13 UTC\n6 min read\nJun 23, 2023\nIn this blog post, we unpack a malicious sample sha256:\n2c05efa757744cb01346fe6b39e9ef8ea2582d27481a441eb885c5c4dcd2b65b . The core decryption of the payload is\nimplemented at native level. I named the packer KangaPack (you’ll understand why when reading this article), it\nalso goes under the name Packed.57103, I am unaware of any other name.\nTeaser: from decompiled code, we’ll see exactly how the packer decrypts the payload, we’ll use JEB decompiler to\ndecompile an ARM library, we’ll use ImHex with a DEX pattern to understand where the payload is hidden.\nWhere to begin\nThe sample is packed: its main ( com.dsfdgfd.sdfsdf.MainActivity ) and receiver\n( com.shounakmulay.telephony.sms.IncomingSmsReceiver ) are not available from the wrapping APK: /app_JKnBkiqZbmLnxDv/HIFlFQ .\nprotected void attachBaseContext(Context base) {\n int i;\n super.attachBaseContext(base);\nhttps://cryptax.medium.com/inside-kangapack-the-kangaroo-packer-with-native-decryption-3e7e054679c4\nPage 1 of 8\n\nFile app_srcdir = new File(this.getApplicationInfo().sourceDir);\r\n File dir = this.getDir(\"JKnBkiqZbmLnxDv\", 0);\r\n \r\n File HIF_dir = new File(dir, \"HIFlFQ\");\r\n ArrayList list = new ArrayList();\r\n \r\n if(!HIF_dir.exists()) {\r\n Ysaplgfew9laf2lsdsa.unzip_metainf(app_srcdir, HIF_dir, true);\r\n }\r\n...\r\nThen, it parses each unzipped file:\r\nthis.dex_extension = \".dex\";\r\nthis.classes_dex_file = \"classes.dex\";\r\nFile[] arr_file = new File(dir, \"HIFlFQ\").listFiles();\r\nint v = 0;\r\nwhile(v \u003c arr_file.length) {\r\n ...\r\nWhenever it encounters a DEX file, it reads it, decrypts, parses the resulting ZIP and adds to a list each file of that\r\nZIP. By default, the code is obfuscated, I have edited the names to ease its understanding. We’ll look into\r\nwriteDex (real name: odgnstswehaxibqwemcbvdand ) do_decrypt_file (real name: bhwi8sma09d23ssva )\r\nafterwards.\r\nIn our case, there is a single DEX file: classes.dex .\r\nFile file = arr_file[v];\r\nString f = file.getName();\r\nif(f.equals(this.classes_dex_file)) {\r\n \r\n \r\n String the_dex_dex = file.getPath() + this.dex_extension;\r\n \r\n this.writeDex(BQddpmHvTsWgSIexmtrw.file2bytes(file), the_dex_dex);\r\n \r\n \r\n File[] arr_file1 = this.do_decrypt_file(new File(the_dex_dex), f).listFiles();\r\n \r\n int max = arr_file1.length;\r\n i = 0;\r\n while(true) {\r\nhttps://cryptax.medium.com/inside-kangapack-the-kangaroo-packer-with-native-decryption-3e7e054679c4\r\nPage 2 of 8\n\nlabel_20:\r\n if(i \u003e= max) {\r\n goto loop_finished;\r\n }\r\n \r\n list.add(arr_file1[i]);\r\n break;\r\nOnce this is done, the malware installs the listed payload files on the smartphone, thus making their code available\r\nto the malware. We’ll see that as well later.\r\nFinding the payload\r\nWe dig into writeDex (real name: odgnstswehaxibqwemcbvdand ). We notice the method\r\n1. Retrieves an integer from the last 4 bytes of the file\r\n2. Allocates a buffer whose size is represented by that integer\r\n3. Reads the required length before that integer and stores it in a resulting file.\r\nprivate void writeDex(byte[] bytearray, String filename) throws IOException {\r\n byte[] dexlen = new byte[4];\r\n \r\n System.arraycopy(bytearray, bytearray.length - 4, dexlen, 0, 4);\r\n \r\n int dex_length = new DataInputStream(new ByteArrayInputStream(dexlen)).readInt();\r\n System.out.println(Integer.toHexString(dex_length));\r\n byte[] newdex = new byte[dex_length];\r\n \r\n System.arraycopy(bytearray, bytearray.length - 4 - dex_length, newdex, 0, dex_length);\r\n File file = new File(filename);\r\n try {\r\n FileOutputStream localFileOutputStream = new FileOutputStream(file);\r\n localFileOutputStream.write(newdex);\r\n localFileOutputStream.close();\r\n return;\r\n }\r\n catch(IOException localIOException) {\r\n throw new RuntimeException(localIOException);\r\n }\r\n }\r\nThis means that the payload is actually embedded inside the wrapping classes.dex itself!\r\nTo start unpacking manually, we read the last bytes of classes.dex : 00 04 A5 D0. This corresponds to a length\r\nof 304592 bytes.\r\nhttps://cryptax.medium.com/inside-kangapack-the-kangaroo-packer-with-native-decryption-3e7e054679c4\r\nPage 3 of 8\n\n$ hexdump -C classes.dex | tail -n 3\r\n0005eae0 3e 15 97 d7 d5 f8 74 16 5f ed b5 8c 6c aa d0 af |\u003e.....t._...l...|\r\n0005eaf0 00 04 a5 d0 |....|\r\n0005eaf4\r\nThe original classes.dex file has a length of 387828 bytes. So, we need to copy bytes starting at 387828–\r\n304592–4=83232. Not surprisingly, the extract part has no known format: it is encrypted.\r\n$ dd if=classes.dex of=todecrypt skip=83232 count=304592 bs=1\r\n304592+0 records in\r\n304592+0 records out\r\n304592 bytes (305 kB, 297 KiB) copied, 0,610131 s, 499 kB/s\r\n$ file todecrypt\r\ntodecrypt: data\r\nLet’s come back to where the encrypted payload is. It is — except for the last 4 bytes — at the end of the original\r\n(packing) classes.dex . How is that possible? We load the DEX in ImHex and apply the DEX pattern.\r\nPress enter or click to view image in full size\r\nFile_size, inside the DEX header, is 387828.\r\nThe DEX header has the correct length of 387828. This means we do not have “a DEX, and then an encrypted\r\npayload”, but that the encrypted payload is included in the DEX format itself.\r\nPress enter or click to view image in full size\r\nhttps://cryptax.medium.com/inside-kangapack-the-kangaroo-packer-with-native-decryption-3e7e054679c4\r\nPage 4 of 8\n\nDEX data ends at 0x1451F included.\r\nA DEX normally ends after its link_data, which is just after its data section. Here, we have no link_data, so the\r\nDEX ends at the end the data section, i.e at 0x1451F. Notice that 0x14520 = 83232 which is exactly the offset for\r\nthe encrypted payload.\r\nGet @cryptax’s stories in your inbox\r\nJoin Medium for free to get updates from this writer.\r\nRemember me for faster sign in\r\nNB. For correct pattern detection of DEX files in ImHex, I added the following to struct Dex in dex.hexpat . I\r\nwill push the update soon, but meanwhile, you can do it for yourselves:\r\n u8 data[header.data_size] @header.data_off;\r\n map_list map_list @ header.map_off;\r\n u8 link_data[header.link_size] @ header.link_off;\r\nSo, the packer cleverly embeds the encrypted payload inside the DEX file format, fixes the size, signature and\r\nchecksum.\r\nhttps://cryptax.medium.com/inside-kangapack-the-kangaroo-packer-with-native-decryption-3e7e054679c4\r\nPage 5 of 8\n\nLayout of packer’s classes.dex\r\nUnderstanding decryption\r\nNow let’s go back to do_decrypt_file (real name: bhwi8sma09d23ssva ). The function calls abddesCrypt .\r\n byte[] arr_b = Molfwernpozxswfsg.abcdesCrypt(Fsolwtym0asmsaw.File2byte(file));\r\n FileOutputStream decrypted_file = new FileOutputStream(new File(file.getPath()));\r\n decrypted_file.write(arr_b);\r\n decrypted_file.flush();\r\n decrypted_file.close();\r\nAnd as I said in the introduction, this calls native code:\r\nimport android.content.Context;\r\npublic class Molfwernpozxswfsg {\r\n static {\r\n System.loadLibrary(\"tahagmaxss\");\r\n System.loadLibrary(\"apksadfsalkwes\");\r\n }\r\n public static native byte[] abcdesCrypt(byte[] arg0) {\r\n }\r\nhttps://cryptax.medium.com/inside-kangapack-the-kangaroo-packer-with-native-decryption-3e7e054679c4\r\nPage 6 of 8\n\nThe function is implemented in libapksadfsalkwes.so . We decompile it. The code is straight forward. The\r\nencrypted payload is retrieved from v5 and stored in ciphertext_ptr (the names have been edited for clarity). Then,\r\nthe code allocates buffers and initializes OpenSSL’s EVP API. AES decryption is initialized for CBC mode using a\r\nsymmetric key and an IV. The routine asks to decrypt the ciphertext ( EVP_DecryptUpdate ) and retrieves the result\r\n( EVP_DecryptFinal_ex )\r\nPress enter or click to view image in full size\r\nadcdesCrypt function decompiled by JEB\r\nTo decrypt, we need to find the key and IV. See they point to the same address!\r\nLOAD.data:0002E000 AES_SECRET_KEY dd 27C7Ch ; xref: Java_com_hwgapk\r\nLOAD.data:0002E004 AES_IV dd 27C7Ch\r\n...\r\n.rodata:00027C7C aJ2K10uXshMh9UGP db \"j2K10uXshMh9UGPS\",0\r\nWe have all we need to decrypt the payload.\r\nfrom Crypto.Cipher import AES\r\nciphertext = open('encrypted_payload','rb').read()\r\nkey=b'j2K10uXshMh9UGPS'\r\niv=key\r\ncipher=AES.new(key, AES.MODE_CBC, iv)\r\nplaintext=cipher.decrypt(ciphertext)\r\nf = open('decrypted.zip','wb')\r\nf.write(plaintext)\r\nf.close()\r\nThe resulting file is a ZIP. It contains the payload DEX.\r\nhttps://cryptax.medium.com/inside-kangapack-the-kangaroo-packer-with-native-decryption-3e7e054679c4\r\nPage 7 of 8\n\nLoading the payload\r\nOnce decrypted, the malware calls an install function (real name: ir68n8s9tdadlbjh ). The code is difficult to\r\nunderstand, but fortunately, we don’t have to: I have already seen this code, it is part of Android itself. Compare it\r\nwith this source code.\r\nprivate static void install(ClassLoader loader, List additionalClassPathEntries, File optimizedDirect\r\n IOException[] dexElementsSuppressedExceptions1;\r\n Object pathListField = BQddpmHvTsWgSIexmtrw.findField(loader, \"pathList\").get(loader);\r\n ArrayList suppressedExceptions = new ArrayList();\r\n Log.d(\"BQddpmHvTsWgSIexmtrw\", \"Build.VERSION.SDK_INT \" + Build.VERSION.SDK_INT);\r\n if(Build.VERSION.SDK_INT \u003e= 23) {\r\n BQddpmHvTsWgSIexmtrw.expandFieldArray(pathListField, \"dexElements\", BQddpmHvTsWgSIexm\r\n }\r\n — Cryptax\r\nIt implements installation of all files listed in additionalClassPathEntries argument. In that list, we’ll have the\r\nclasses_ogr.dex file. Thus, the file will be loaded and everything inside ( com.dsfdgfd.sdfsdf.MainActivity\r\nfor instance) becomes available.\r\nPacker naming\r\nThis packer isn’t recognized by APKiD (yet) and I don’t know where it is from. If you have any clue, please\r\ncontact me Twitter or Mastodon.social (handle: @cryptax). Meanwhile, I am going to name it. As it contains the\r\npayload inside the DEX file inside, I’ll call it KangaPack (kudos to Kangaroos).\r\n— Cryptax\r\nSource: https://cryptax.medium.com/inside-kangapack-the-kangaroo-packer-with-native-decryption-3e7e054679c4\r\nhttps://cryptax.medium.com/inside-kangapack-the-kangaroo-packer-with-native-decryption-3e7e054679c4\r\nPage 8 of 8",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://cryptax.medium.com/inside-kangapack-the-kangaroo-packer-with-native-decryption-3e7e054679c4"
	],
	"report_names": [
		"inside-kangapack-the-kangaroo-packer-with-native-decryption-3e7e054679c4"
	],
	"threat_actors": [],
	"ts_created_at": 1775434305,
	"ts_updated_at": 1775791298,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/5ca67141fe351e3f38035022a37c4172bca7bd4b.pdf",
		"text": "https://archive.orkl.eu/5ca67141fe351e3f38035022a37c4172bca7bd4b.txt",
		"img": "https://archive.orkl.eu/5ca67141fe351e3f38035022a37c4172bca7bd4b.jpg"
	}
}