{
	"id": "ce7df45c-f57b-49dd-a7dd-bcb7f256adaf",
	"created_at": "2026-04-06T00:15:54.799213Z",
	"updated_at": "2026-04-10T13:12:07.738575Z",
	"deleted_at": null,
	"sha1_hash": "353f6db7fef247557c83a0c34399fc285d182f88",
	"title": "The tangle of WiryJMPer’s obfuscation",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 1763487,
	"plain_text": "The tangle of WiryJMPer’s obfuscation\r\nBy Threat Research TeamThreat Research Team\r\nArchived: 2026-04-05 20:55:34 UTC\r\nThe story of how we conquered WiryJMPer’s obfuscation begins with a simple binary file posing as an ABBC\r\nCoin wallet. We found the file suspicious, as the file size was three-times as big as it should be, and the strings in\r\nthe file corresponded to other software WinBin2Iso (version 3.16) from SoftwareOK. ABBC Coin (originally\r\nAlibaba Coin, not affiliated with Alibaba Group) is an altcoin, one of many blockchain-based cryptocurrencies.\r\nWinBin2Iso, on the other hand, is software that converts various binary images of CD/DVD/Blu-ray media into\r\nthe ISO format. Behavioral analysis revealed that the binary, posing as an ABBC Coin wallet, is a dropper, which\r\nwe will, from now on, refer to as WiryJMPer. WiryJMPer hides a Netwire payload between two benign binaries.\r\nThe first stage of the payload innocently appears as a regular WinBin2Iso binary with a suspiciously large .rsrc\r\nsection. The JMP instruction, which is normally part of a loop handling window messages, jumps into the .rsrc\r\nsection where a roller-coaster of control flow begins. This causes an unresponsive WinBin2Iso window to appear\r\nbriefly before being replaced by a ABBC Coin wallet window. This window is always shown at startup and thus it\r\nis a good indicator of infection.\r\nWhile this functionality isn’t novel in any sense and no sandbox evasion was utilized, the obfuscation was\r\nuncommon enough to gain our attention. The combination of control flow obfuscation and low level code\r\nabstraction made the analysis of the malware’s workflow rather tedious. This, in combination with the low\r\ndetection rate on VirusTotal (6 out of 66 as of 7/8/2019), provided us a great excuse to rummage through our\r\ntoolbox to perform the analysis. Moreover, during the analysis, we found that the obfuscated loader also utilises a\r\n(possibly) custom stack-based virtual machine during the RC4 key schedule, which aroused our interest even\r\nmore.\r\nDue to the aforementioned reasons and low overall prevalence, this analysis will focus on the obfuscation itself.\r\nResulting side-effects and the malware’s functionality, will be mostly mentioned as side-notes.\r\nHigh-level overview\r\nThe malware starts in the WinBin2Iso binary that has a patched jump. This jump leads to the .rsrc section, where a\r\nloader is decrypted, loaded into memory and relocations are made.\r\nhttps://decoded.avast.io/adolfstreda/the-tangle-of-wiryjmpers-obfuscation/\r\nPage 1 of 11\n\nOriginal WinBin2Iso binary, note that the jump leads back to GetMessageA\r\nPatched WinBin2Iso binary, notice that the jump leads to totally different address range\r\nThis loader then handles everything until the control flow is redirected to Netwire. It loads ntdll.dll into the\r\nmemory, decrypts some auxiliary data such as LNK filename or RC4 decryption password. Afterwards, it decrypts\r\nNetwire, which is also loaded into the memory, and the “decoy” binary (ABBC Coin wallet in this case), which is\r\nsaved onto the disk.\r\nhttps://decoded.avast.io/adolfstreda/the-tangle-of-wiryjmpers-obfuscation/\r\nPage 2 of 11\n\nHigh-level overview of WiryJMPer’s workflow\r\nSubsequently, the control flow is redirected into Netwire. Netwire is a pretty much standard remote access tool, no\r\nsignificant modifications were made. The Netwire C\u0026C lies at 46.166.160[.]158 , this address was\r\nunfortunately unresponsive at the time we performed our  analysis.\r\nThe loader also tries to achieve persistence by copying the original binary to %APPDATA%\\abbcdriver.exe and by\r\ncreating an LNK file, leading to this binary, in the startup folder.\r\nAccessing the loader\r\nThe code following the patched jump consists of many small code blocks connected by a network of jumps. This\r\nmakes the binary rather hard to (statically) analyze without any preprocessing. With the help of an emulator, we\r\nare able to reconstruct the call graph and concatenate some of these blocks. While this approach still yields rather\r\nunpleasant results, it allowed us to get rid of some dummy instructions and simplify the control flow. Note that\r\nthis obfuscation also utilises so called opaque predicates, i.e. conditional jumps with one branch that will never be\r\nreached. If we wanted to keep the analysis static, we would have to employ e.g. symbolic execution or other\r\nheuristics to resolve these jumps. However, for the purpose of our analysis, a simple heuristic, where the code\r\nfollowing the conditional branch should lead to “reasonable” instructions, was good enough.\r\nhttps://decoded.avast.io/adolfstreda/the-tangle-of-wiryjmpers-obfuscation/\r\nPage 3 of 11\n\nSeveral concatenated consecutive blocks, note the conditional jump that has predetermined result.\r\nBrief inspection of the sandbox behavioral log reveals two calls of RtlDecompressBuffer , the second one\r\ndecompressing our Netwire payload. Unfortunately, addresses of the compressed buffers lead to addresses\r\nallocated during the execution, meaning that we cannot access them directly during static analysis, and thus we\r\nwill have to dive deeper in order to retrieve the payload.\r\nFinding content of ESI is easy, we just have to dig under ESP. Note that superfluous jumps were removed.\r\nThe first RtlDecompressBuffer call takes data loaded by rep movsb at 0x01e6d44f . This instruction loads the data\r\nfrom the binary itself into an allocated memory (VirtualAlloc). Since user-space debuggers were probably detected\r\nby the sample, we decided to use a kernel debugger that got us to the offset that’s under ESI ( 0x004f9000 ),\r\nwhich leads to data in the .rsrc section. A brief check whether this data matches the input to RtlDecompressBuffer\r\nreveals that the data is encrypted. Fortunately, the decryption loop is located right after the block with rep\r\nmovsb .\r\nhttps://decoded.avast.io/adolfstreda/the-tangle-of-wiryjmpers-obfuscation/\r\nPage 4 of 11\n\nDecryption routine.\r\nLooking at the XORing at 0x1e75ad6 we see that our key is hidden in EBX which is coincidentally loaded just\r\nbefore our rep movsb at 0x01e727a5 . Furthermore, we can see that the key is static and thus the whole buffer is\r\nXORed by 0xca81c398 . This buffer is then decompressed by the aforementioned RtlDecompressedBuffer.\r\nThis resolved our first question: where is the compressed loader located? Now two questions remain unresolved:\r\nwhere can we find Netwire payload and where is the “decoy” (ABBC Coin wallet) binary that is launched at the\r\nend?\r\nLoader relocation\r\nBefore the execution flow moves into the loader from the .rsrc section, the relocation has to be made due to the\r\nundeterministic nature of VirtualAlloc (and the presence of absolute jumps in the loader). The relocation is\r\nimplemented in the same way as standard relocations in PE files. The relocation table has the following structure:\r\nThe offset of the instruction to be patched is calculated in the following way:\r\nreloc_high + reloc_low[i] \u0026 0xfff + allocated_memory_base\r\nUnsurprisingly, the difference between the original base ( 0x10000000 ) and the base of the VirtualAlloc-ed\r\nmemory is added to the residing 32-bit value. The list of reloc_low is iterated until the iterator points to the next\r\nrow of the relocation table.\r\nAccessing the payloads\r\nSince Netwire payload is unpacked using RtlDecompressBuffer, it should be easily trackable using the very same\r\ntricks we used before. However, since there is other stuff being dropped or extracted, we used VirtualAlloc for our\r\nbreakpoints instead.\r\nhttps://decoded.avast.io/adolfstreda/the-tangle-of-wiryjmpers-obfuscation/\r\nPage 5 of 11\n\nThe first VirtualAlloc, coming after the unpacked loader, prepares space for ntdll.dll that is loaded with\r\nNtReadFile. This is becoming a rather common anti-debugging trick as debuggers mostly do not recognize calls\r\ninto this manually copied DLL.\r\nNow this is where it gets more interesting. The second VirtualAlloc is made for some internal configuration (LNK\r\nfile name, RC4 key that will be used later on) that is located in the .rsrc section ( 0x01e632ac ). Again, this binary\r\nblob is encrypted by a simple XOR cipher with a key 0x98c381ca . From now on, we’ll start with an assumption\r\nthat everything is encrypted by the XOR cipher with a hardcoded key, this may simplify the analysis as it is\r\nstraightforward to recover the key from the plaintext-ciphertext pair.\r\nOur assumption failed immediately on the next payload ( 0x004899B6 ), its decryption loop contained the\r\nfollowing instructions (note that we have removed superfluous jumps):\r\nThis is a keystream generation loop of the RC4 cipher. This brings us to another problem – we would like to find\r\nthe key that was used to instantiate the table at 0x09458248 . Unfortunately, the key schedule for the RC4 cipher\r\nis obfuscated by a stack-based virtual machine and thus we started to debug again. To illustrate the following\r\nprocess, we will recall the RC4 key schedule algorithm (see Python implementation below).\r\nhttps://decoded.avast.io/adolfstreda/the-tangle-of-wiryjmpers-obfuscation/\r\nPage 6 of 11\n\nRC4 key schedule in Python\r\nThis time, we have set a breakpoint on the virtual machine’s instruction that is used to write to the table which\r\nallows us to recover the state of registers and thus access addresses of S[i] and S[j] . By subtracting the RC4\r\ntable’s base address, we obtained the respective indices i and j . Now, it’s trivial to step through the RC4 key\r\nschedule and recover bytes of the key from the index j . More specifically, we obtain a sequence:\r\n105, 120, 89, 105, 77, 70, 82, 88, 56, 83, 78, 70, 68, 74, 112, 72, 104, 85, 82, 121, 105, 120, 89,\r\n105, 77, 70, 82, 88, 56, 83, 78, 70, 68, 74, 112, 72, 104, 85, 82, 121, 105, 120, 89, 105, 77, 70, 82,\r\n88, 56, 83, 78, 70, 68, 74, 112, 72, 104, ...\r\nThis sequence obviously has repetitions (highlighted in bold) and these values seem to fall into the printable\r\nASCII range. Using these observations, we recover the RC4 key: ixYiMFRX8SNFDJpHhURy . This key was loaded\r\nfrom the buffer, located in the second VirtualAlloc-ed memory (containing the internal configuration), into the\r\nloader. The decryption of this blob yields the ABBC Coin wallet binary.\r\nInterestingly, the extracted binary matches the real ABBC Coin wallet (version 3.9.1). Later on, this binary will be\r\nextracted into the temporary directory under a name matching the following regular expression: [A-Za-z]\r\n{5}\\.exe , and executed right away. We suppose that this is intended to mask the real purpose of the original\r\nbinary. This payload will not be executed if the original binary is already in the %APPDATA% directory.\r\nIncidentally, this is the location where the original binary is copied to and thus it won’t launch the ABBC Coin\r\nwallet if ran at startup (through the aforementioned LNK file).\r\nWhile we will discuss the virtual machine itself in the Virtual Machine section, we will have a brief look at its\r\npart, which is called a dispatcher (actually WiryJMPer has three more similar dispatchers). You may notice one\r\ndetail:\r\nOne of the four VM dispatchers that are used during RC4 key-schedule. The correspondence between highlighted\r\nparts is not accidental.\r\nRecall that the memory, where the loader resides, had been allocated by VirtualAlloc before calling the\r\nRtlDecompressBuffer and the virtual machine is in the loader. Therefore, bytes at the address 0x0034AF8C\r\ncouldn’t have been set with this offset from the beginning and had to be patched during runtime as the address\r\nrange for the loader is not known beforehand. This also indicates the need for relocations.\r\nhttps://decoded.avast.io/adolfstreda/the-tangle-of-wiryjmpers-obfuscation/\r\nPage 7 of 11\n\nNow, we just need to find Netwire in the sample. We used the same approach to find VirtualAlloc that allocates\r\nmemory for Netwire, we set a breakpoint on the write and then found a location from which Netwire is copied\r\n(offset 0x01e585b6 in .rsrc section). Luckily for us, it was encrypted using RC4 with the same key. The\r\ndecryption yields a UPX packed Netwire, thus concluding the payload extraction and confirming its presence\r\nhinted by the sandbox behavioral log.\r\nVirtual machine\r\nDuring the analysis, we discovered that the RC4 key schedule was implemented in a custom stack-based virtual\r\nmachine.\r\nSchematic of common stack-based virtual machine\r\nThe first part of every virtual machine is a dispatcher. WiryJMPer’s virtual machine uses four distinct switch\r\ndispatchers. In general, switch dispatchers jump to the code corresponding to the desired instruction via switch\r\nstatements or similar constructions. The instruction is translated into a specific address or offset via a jump table,\r\npushed onto the stack and the instruction ret is called, although other constructions are also possible. The\r\ntypical setting is shown on the diagram below, but note that WiryJMPer’s virtual machine is more complex as it\r\nhas more dispatchers and the stack-overflow check does not follow all instructions.\r\nhttps://decoded.avast.io/adolfstreda/the-tangle-of-wiryjmpers-obfuscation/\r\nPage 8 of 11\n\nAnother dispatcher of the virtual machine.\r\nSince every virtual instruction has to deterministically reach either the dispatcher or exit the virtual machine, we\r\ntried to reach these instructions by tracking references leading to these dispatchers. As this virtual machine is\r\nstack-based, arguments are passed through stack instead of registers. General purpose registers are mostly used\r\nlocally, although some registers, such ESI (instruction pointer), ESP (stack frame base) or EBP (stack top), have a\r\nglobal effect on the virtual machine.\r\nInstructions are rather similar to, for instance, the WProtect virtual machine – arithmetic operations, jumps,\r\nmemory/stack writes and reads, etc. We assumed a typical setting where registers are put onto specific positions in\r\nthe stack during the initialization and then loaded back from these positions on exit. Due to the amount of\r\ninstructions (and some duplicities), we will only provide a few examples of these instructions.\r\nRead DWORD from stack\r\nhttps://decoded.avast.io/adolfstreda/the-tangle-of-wiryjmpers-obfuscation/\r\nPage 9 of 11\n\nJump\r\nWrite DWORD to memory\r\nSimilar files\r\nWe  found files utilising the same scheme – WinBin2Iso binary patched to unpack Netwire and another binary. For\r\nexample,  the decoy payload led to a different, yet legitimate, installer of Bitcoin Core (version 0.18.0). Others led\r\nto the Yoroi wallet, Neon wallet, ZecWallet, DigiByte Core, OWallet, Verge core wallet and others. The common\r\ndenominator seems to be cryptocurrency wallets.\r\nConclusion\r\nWhile the malware’s functionality isn’t very innovative, it has managed to pass under the radar for some time,\r\nprobably due to obfuscation and rather low prevalence. The utilised obfuscation was easily overcome by\r\nbehavioral analysis, nevertheless it served well in obfuscating details of the malware’s operation. Rather slow\r\nsetup of the decoy showing multiple windows with unrelated titles may be suspicious enough for power-users, on\r\nthe other hand, providing the “decoy” binary might be comforting enough for ordinary users.\r\nIndicators of Compromise (IoC)\r\nRepository: https://github.com/avast/ioc/tree/master/WiryJMPer\r\nList of SHA-256: https://github.com/avast/ioc/blob/master/WiryJMPer/samples.sha256\r\nAnalyzed sample\r\nAnalyzed sample\r\nNetwire payload C\u0026C: 46.166.160[.]158\r\nSimilar samples\r\nhttps://decoded.avast.io/adolfstreda/the-tangle-of-wiryjmpers-obfuscation/\r\nPage 10 of 11\n\nSimilar samples\r\nThreat Research Team\r\nThreat Research Team\r\nA group of elite researchers who like to stay under the radar.\r\nSource: https://decoded.avast.io/adolfstreda/the-tangle-of-wiryjmpers-obfuscation/\r\nhttps://decoded.avast.io/adolfstreda/the-tangle-of-wiryjmpers-obfuscation/\r\nPage 11 of 11",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://decoded.avast.io/adolfstreda/the-tangle-of-wiryjmpers-obfuscation/"
	],
	"report_names": [
		"the-tangle-of-wiryjmpers-obfuscation"
	],
	"threat_actors": [
		{
			"id": "3fff98c9-ad02-401d-9d4b-f78b5b634f31",
			"created_at": "2023-01-06T13:46:38.376868Z",
			"updated_at": "2026-04-10T02:00:02.949077Z",
			"deleted_at": null,
			"main_name": "Cleaver",
			"aliases": [
				"G0003",
				"Operation Cleaver",
				"Op Cleaver",
				"Tarh Andishan",
				"Alibaba",
				"TG-2889",
				"Cobalt Gypsy"
			],
			"source_name": "MISPGALAXY:Cleaver",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		}
	],
	"ts_created_at": 1775434554,
	"ts_updated_at": 1775826727,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/353f6db7fef247557c83a0c34399fc285d182f88.pdf",
		"text": "https://archive.orkl.eu/353f6db7fef247557c83a0c34399fc285d182f88.txt",
		"img": "https://archive.orkl.eu/353f6db7fef247557c83a0c34399fc285d182f88.jpg"
	}
}