{
	"id": "d14b2f32-8c34-4225-86ec-61ec21db597e",
	"created_at": "2026-04-06T00:10:24.019303Z",
	"updated_at": "2026-04-10T03:23:51.777374Z",
	"deleted_at": null,
	"sha1_hash": "9d0a7694dfddb938e2fa5cc459f659628bdf8dcf",
	"title": "String Obfuscation in the Hamweq IRC-bot – nullteilerfrei",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 67109,
	"plain_text": "String Obfuscation in the Hamweq IRC-bot – nullteilerfrei\r\nBy born\r\nArchived: 2026-04-05 15:31:03 UTC\r\nmalware-analysis 2020-05-31\r\nIn this blog post, we will follow one of herrcore's awesome videos and re-implement the automation as a Ghidra\r\nscript in Java. The video in question is part of a series about a legacy malware family called Hamweq. CERT\r\nPolska published an extensive analysis of Hamweq: The malware implements a IRC-based botnet with worm-like\r\ncapabilities. In this post we will solely focus on the string deobfuscation functionality in the malware.\r\nIdentifying the String Deobfuscation Method\r\nInstantly after opening the sample 4eb33ce768def8f7db79ef935aabf1c712f78974237e96889e1be3ced0d7e619 in\r\nGhidra, you can see four calls to GetProcAddress . This method resolves an API function dynamically, basically\r\nturning a string referencing a name of an API function into a pointer to the corresponding function. According to\r\nthe documentation, the second argument to GetProcAddress is that string. Following the memory address in\r\nGhidra (by double-clicking) does not lead to any printable strings though. Hence before these four calls to\r\nGetProcAddress , these memory regions have to be modified during runtime. Otherwise, GetProcAddress\r\nwould return the null pointer and calling that pointer, would crash the program.\r\nThe only two functions that can do this deobufscation step are FUN_00402781 and FUN_004027e1 . The first of\r\nthe two seems to be doing something related to privileges, but since we want to focus on string obfuscation right\r\nnow, we will not waste any time reverse engineering it but take a look at the function FUN_004027e1 . This\r\nfunction accepts one string argument which is hard-coded to be I0L0v3Y0u0V1rUs at this call. This is probably a\r\nreference to the famous ILOVEYOU virus from 2000 left by the malware author to our amusement. Because we\r\nare feeling lucky, let's rename FUN_004027e1 to pr_StringDeobfusaction .\r\nOptimizing Crappy Crypto\r\npr_StringDeobfusaction references the data at 0x00405020 and interprets it as an array of pointers to strings.\r\nEach of these strings is then deobfuscated with a custom Xor-algorithm using the passed argument as a key. The\r\ndeobfuscation algorithm is called on each of the referenced strings separately: it first Xors each byte of the passed\r\nkey onto each byte of the obfuscated data and then inverts every byte of the result.\r\nSince the Xor-operation is associative, the key can be reduced to a single-byte Xor-key: For simplicity's sake, let\r\nus assume, the Xor key is not I0L0v3Y0u0V1rUs but the sequence of numbers 23, 42 and 36. Now let 𝑥 be a\r\nsingle byte to be deobfuscated and let ⊗ denote bit-wise Xor, then the following equation is true:\r\n((𝑥 ⊗ 23) ⊗ 42) ⊗ 36 = 𝑥 ⊗ (23 ⊗ 42 ⊗ 36) = 𝑥 ⊗ 25\r\nhttps://blag.nullteilerfrei.de/2020/05/31/string-obfuscation-in-the-hamweq-irc-bot/\r\nPage 1 of 3\n\nso instead of using the key 23, 42, 36 one could simply use the key 25. Similarly, the key I0L0v3Y0u0V1rUs can\r\nbe reduced to 95. The following Java function implements this key-reduction:\r\nprivate byte[] reduceKey(String key) {\r\n byte ret[] = new byte[1];\r\n for (byte b : key.getBytes()) {\r\n ret[0] ^= b;\r\n }\r\n return ret;\r\n}\r\nScripting\r\nWe now want to write a script where the user specifies the address of the array of pointers to the obfuscated\r\nstrings and Ghidra should then deobfuscate them all, print the result, patch the data in memory, set the correct\r\ndata-type and create bookmarks for all deobfuscated strings:\r\npublic void run() throws Exception {\r\n byte[] key = reduceKey(\"I0L0v3Y0u0V1rUs\");\r\n Address stringTable = askAddress(\"Enter Address\", \"Specify address of string table\");\r\n while (true) {\r\n Address stringAddress = unpackAddressLE(getOriginalBytes(stringTable, 4));\r\n if (stringAddress.getOffset() == 0)\r\n break;\r\n byte data[] = getOriginalBytes(stringAddress, 0x40);\r\n if (data == null) {\r\n break;\r\n }\r\n byte cypherText[] = readUntilZeroByte(data);\r\n byte plainText[] = cryptXorAndInvert(cypherText, key);\r\n println(String.format(\"0x%08X %s\", stringAddress.getOffset(), new String(plainText)));\r\n setBytes(stringAddress, plainText);\r\n clearListing(stringAddress, stringAddress.add(plainText.length - 1));\r\n createData(stringAddress, new ArrayDataType(CharDataType.dataType, plainText.length, 1));\r\n createBookmark(stringAddress, \"DeobfuscatedString\", new String(plainText));\r\n stringTable = toAddr(stringTable.getOffset() + 4);\r\n }\r\n}\r\nThe only missing part now is the actual decryption routine:\r\nprivate byte[] cryptXorAndInvert(byte[] data, byte[] key) {\r\n final byte[] ret = new byte[data.length];\r\nhttps://blag.nullteilerfrei.de/2020/05/31/string-obfuscation-in-the-hamweq-irc-bot/\r\nPage 2 of 3\n\nfor (int k = 0; k \u003c data.length; k++)\r\n ret[k] = (byte) (~(data[k] ^ key[k % key.length]));\r\n return ret;\r\n}\r\nAs always, the complete script to deobfuscate strings from a Hamweq sample can be found on GitHub.\r\nSource: https://blag.nullteilerfrei.de/2020/05/31/string-obfuscation-in-the-hamweq-irc-bot/\r\nhttps://blag.nullteilerfrei.de/2020/05/31/string-obfuscation-in-the-hamweq-irc-bot/\r\nPage 3 of 3",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://blag.nullteilerfrei.de/2020/05/31/string-obfuscation-in-the-hamweq-irc-bot/"
	],
	"report_names": [
		"string-obfuscation-in-the-hamweq-irc-bot"
	],
	"threat_actors": [
		{
			"id": "b740943a-da51-4133-855b-df29822531ea",
			"created_at": "2022-10-25T15:50:23.604126Z",
			"updated_at": "2026-04-10T02:00:05.259593Z",
			"deleted_at": null,
			"main_name": "Equation",
			"aliases": [
				"Equation"
			],
			"source_name": "MITRE:Equation",
			"tools": null,
			"source_id": "MITRE",
			"reports": null
		},
		{
			"id": "d90307b6-14a9-4d0b-9156-89e453d6eb13",
			"created_at": "2022-10-25T16:07:23.773944Z",
			"updated_at": "2026-04-10T02:00:04.746188Z",
			"deleted_at": null,
			"main_name": "Lead",
			"aliases": [
				"Casper",
				"TG-3279"
			],
			"source_name": "ETDA:Lead",
			"tools": [
				"Agentemis",
				"BleDoor",
				"Cobalt Strike",
				"CobaltStrike",
				"RbDoor",
				"RibDoor",
				"Winnti",
				"cobeacon"
			],
			"source_id": "ETDA",
			"reports": null
		}
	],
	"ts_created_at": 1775434224,
	"ts_updated_at": 1775791431,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/9d0a7694dfddb938e2fa5cc459f659628bdf8dcf.pdf",
		"text": "https://archive.orkl.eu/9d0a7694dfddb938e2fa5cc459f659628bdf8dcf.txt",
		"img": "https://archive.orkl.eu/9d0a7694dfddb938e2fa5cc459f659628bdf8dcf.jpg"
	}
}