{
	"id": "942ae5f5-f06a-483d-9bb4-fcd82953c232",
	"created_at": "2026-04-06T00:15:57.576678Z",
	"updated_at": "2026-04-10T13:12:12.949668Z",
	"deleted_at": null,
	"sha1_hash": "84808bc2593e63402721d2301f246eb842b1305c",
	"title": "Unpacking a JsonPacker-packed sample",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 1812129,
	"plain_text": "Unpacking a JsonPacker-packed sample\r\nBy @cryptax\r\nPublished: 2022-06-27 · Archived: 2026-04-05 22:53:51 UTC\r\n5 min read\r\nJun 27, 2022\r\nWith students of mine, we built a static unpacker (short of public name, I named it “JsonPacker” in APKiD).\r\nUnfortunately, the static unpacker doesn’t work for the sample and the students are in for a quick patch before\r\ntheir defense in a few days. Lol. I’m sure they hate me 😁 The sha256 of the sample is\r\n2877b27f1b6c7db466351618dda4f05d6a15e9a26028f3fc064fa144ec3a1850 , and it dates back to February 2022.\r\nQuickly spot the encrypted json filename in the code\r\nThere are many classes, and obfuscated names, so at first it could be a bit disorientating to find the right spot. But\r\nI’ve unpacked such samples dozens of time: just search for the class with attachBaseContext (which is a method\r\nfound in classes which derive from Application and which is called at “the very beginning”).\r\nHead to class CToKhLqQwJbTrQrKg :)\r\nIn there, head to the object fields which get their initial value in the constructor. Spot the json file hq.json .\r\nPress enter or click to view image in full size\r\nhttps://cryptax.medium.com/unpacking-a-jsonpacker-packed-sample-4038e12119f5\r\nPage 1 of 8\n\nThe encrypted payload is inside hq.json. I like to rename the field to something more meaningful :)\r\nSpot the place where the file is dynamically loaded\r\nFor such samples, just look where DexClassLoader is used. I like to use the detailed report of DroidLysis for that.\r\nPress enter or click to view image in full size\r\nDexClassLoader is used in a single place:\r\nABeJgOnNtJpIcNgRxUkDwXcIwNyTzCyFxXhUsZsWxQuShDpLkUiRyWn\r\nGo to that class, and search for DexClassLoader , you find method rigidmiddle .\r\nPress enter or click to view image in full size\r\nhttps://cryptax.medium.com/unpacking-a-jsonpacker-packed-sample-4038e12119f5\r\nPage 2 of 8\n\nThis method loads dynamically the decrypted payload stored on the filesystem in “filename”\r\nNow, work your way up to who calls this method, using cross-references:\r\ntailcreek\r\nguessextra\r\naerobicneutral\r\nattachBaseContext : the call to aeronicneutral is at the end of the image below.\r\nPress enter or click to view image in full size\r\nParts of code of attachBaseContext. There is lots of junk code. The payload filename is used 3 times\r\nin this screenshots: loadover, ceilingnice, aerobicneutral\r\nSpot where payload decryption occurs\r\nloadover constructs the absolute path of the filename.\r\nceilingnice decrypts the file\r\naerobicneutral loads the decrypted file.\r\nIn ceilingnice , let’s follow the calls to the decryption algorithm:\r\nallrather\r\nhttps://cryptax.medium.com/unpacking-a-jsonpacker-packed-sample-4038e12119f5\r\nPage 3 of 8\n\norcharddecide\r\nMethod orcharddecide loads the asset:\r\nPress enter or click to view image in full size\r\nFirst, the code retrieves an AssetManager. Then it opens the encrypted payload asset. The input\r\nstream is the encrypted payload, the output stream will be stored in the location of absolutepath\r\nThen it reads the asset, decrypts it (this happens inside futureinherit ), unzips the result and writes it to the\r\noutput stream.\r\nPress enter or click to view image in full size\r\nThis part of code (inside orcharddecide) decrypts the assets and unzips the result.\r\nfutureinherit calls ratherbanana . It takes an encrypted byte array as input and returns a decrypted byte array.\r\nUnderstanding the preparation of the key\r\nratherbanana reads a fixed string (“Ianj” for this sample), and I assume it is the key. It converts the string to a\r\nbyte array, then converts it to an integer array in nomineesign .\r\nPress enter or click to view image in full size\r\nStill lots of junk code. Prepare the decryption key.\r\nhttps://cryptax.medium.com/unpacking-a-jsonpacker-packed-sample-4038e12119f5\r\nPage 4 of 8\n\nThe code of nomineesign is not very long but requires close attention to remove junk code (but not too much:\r\nthe loop initializing the convertedkey table is not junk!), and de-obfuscate code.\r\nPress enter or click to view image in full size\r\nThe line with StrictMath.hypot is obfuscated.\r\nFor example, the lign with hypot is interesting:\r\nint cv = (int)StrictMath.hypot(this.timeone('d', 0x1E681L, convertedkey, index), 0.0);\r\ntimeone actually returns the index-th byte of convertedkey , i.e convertedkey[index] .\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\nhypot computes square root of ( convertedkey[index]² + 0² ). As 0² = 0, the variable cv will simply receive the\r\nvalue of convertedkey[index] .\r\nIn the end, the algorithm boils down to this:\r\nprivate void swap(int a, int b, int[] array) {\r\n int tmp = array[a];\r\n array[a]=array[b];\r\n array[b]=tmp;\r\n}\r\n private int[] convert_key(byte[] key) {\r\n int[] convertedkey = new int[0x100];\r\n int i;\r\n for(i = 0; i \u003c 256; ++i) {\r\n convertedkey[i] = i; // init\r\n }\r\n int j = 0;\r\n int k = 0;\r\nhttps://cryptax.medium.com/unpacking-a-jsonpacker-packed-sample-4038e12119f5\r\nPage 5 of 8\n\nwhile(j \u003c 0x100) {\r\n int cv = convertedkey[j];\r\n k = (k+cv+key[j%key.length]+0x100) % 0x100;\r\n swap(j, k, convertedkey); // swap values\r\n ++j;\r\n }\r\n return convertedkey;\r\n}\r\nDecryption algorithm\r\nThe next step is to understand the decryption algorithm in itself. Actually, there is lots of junk code that can be\r\nremoved. To start, I focus on where the encrypted input byte array is used.\r\ndecrypted[i] = this.motionavoid(Math.round(v0_6) ^ encrypted[i]);\r\nThe method motionavoid is there just for obfuscation: it merely returns its argument. Also, obviously, we only\r\nhave integers, so Math.round is useless. So, we have decrypted[i] = v0_6 ^ encrypted[i]; . A few lines above,\r\nwe have v0_6 : int v0_6 = ckey[(v15 + v0_5) % 0x100]; . A few lines above, we have v15 and v0_5 :\r\nint v15 = this.timeone('b', 5222L, ckey, HMoEsEkXySsLhTyCkZlChSoBfFlPk.counter); // ckey[counter]\r\nthis.GfnxRHLRQuDY_713808 = this.KfYicpzIQMgk_598597 * 0x12FC3 + this.RMSmhfBNuxnA_506561 - 50009; //\r\nint v0_5 = this.timeone('z', 0x179161L, ckey, v14); // ckey[v14]\r\nThe method timeone only uses the last two arguments: a table and an index, and returns table[index] value.\r\nQuite strangely, v15 uses a static integer that I have renamed counter . I search where this counter is used:\r\nPress enter or click to view image in full size\r\nThe first use basically increments the counter, making sure it remains below 0x100. Then, counter is\r\nput in v2 and swapped with another value (energyalmost is a method that performs byte swap).\r\nFinally, v15 gets the value of the ckey[counter]\r\nI work out that int v15 = ckey[counter]; .\r\nhttps://cryptax.medium.com/unpacking-a-jsonpacker-packed-sample-4038e12119f5\r\nPage 6 of 8\n\nAs for v0_5 , we have v0_5 = ckey[v14] and v14 is yet another static counter: int v14 =\r\nHMoEsEkXySsLhTyCkZlChSoBfFlPk.other_counter; . Same, I search where this other counter is used, and it’s\r\nbasically the same: an increment modulus 0x100, a swap and ckey[other_counter] . That’s it! We have all\r\nelements to decrypt! The algorithm boils down to this:\r\nPress enter or click to view image in full size\r\nSimplified decryption method. For this sample, the initial key is “Ianj”. The encrypted byte array is\r\nthe contents of hq.json. I added a length argument because actually in my code the hq.json is read\r\ninto a bigger array, and we only need to decrypt up to the length of hq.json file.\r\nNote: the code above uses static variables counter and other_counter, but actually it works fine with local\r\nvariables, and probably would be easier to read with local ones.\r\nDecrypt the payload\r\nTo the key + decryption algorithm, we just need to add something to read hq.json and write to another file.\r\nThen, we can unpack!\r\nPress enter or click to view image in full size\r\nStatic unpacker works fine :) Hurray!\r\nThe decrypted file is a Zip file (this was expected: remember that orcharddecide unzips the result): inside, there\r\nis a classes.dex (sha256: dae52bbee7f709fae9d91e06229c35b46d4559677f26152d4327fc1601d181be ). It is the\r\npayload of the Xenomorph malware.\r\nWhich class/method does the malware load dynamically?\r\nBefore we decompile this payload, we need to know which method is called. The manifest shows the main activity\r\nis com.sniff.sibling.MainActivity . This class is not present in the wrapping apk… so it must be in the\r\npayload! This will be automatically called by Android as soon as it’s time to launch the main activity.\r\nPress enter or click to view image in full size\r\nhttps://cryptax.medium.com/unpacking-a-jsonpacker-packed-sample-4038e12119f5\r\nPage 7 of 8\n\nThe main activity is indeed found in the payload.\r\nWe’ve had enough for a single blog post, but the payload, similarly to many Android botnets, uses the\r\nAccessibility Services API to overlay windows of given applications.\r\n— Cryptax\r\nSource: https://cryptax.medium.com/unpacking-a-jsonpacker-packed-sample-4038e12119f5\r\nhttps://cryptax.medium.com/unpacking-a-jsonpacker-packed-sample-4038e12119f5\r\nPage 8 of 8",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://cryptax.medium.com/unpacking-a-jsonpacker-packed-sample-4038e12119f5"
	],
	"report_names": [
		"unpacking-a-jsonpacker-packed-sample-4038e12119f5"
	],
	"threat_actors": [],
	"ts_created_at": 1775434557,
	"ts_updated_at": 1775826732,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/84808bc2593e63402721d2301f246eb842b1305c.pdf",
		"text": "https://archive.orkl.eu/84808bc2593e63402721d2301f246eb842b1305c.txt",
		"img": "https://archive.orkl.eu/84808bc2593e63402721d2301f246eb842b1305c.jpg"
	}
}