{
	"id": "668c96c1-a50d-4cfa-973c-475247cc9844",
	"created_at": "2026-04-06T00:07:25.200415Z",
	"updated_at": "2026-04-10T03:20:26.537591Z",
	"deleted_at": null,
	"sha1_hash": "065288c152731341d18f3254a8e1a65385b1b65e",
	"title": "Fobber Code Decryption",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 141778,
	"plain_text": "Fobber Code Decryption\r\nArchived: 2026-04-02 10:53:29 UTC\r\nAfter reading the Malwarebytes blog post describing Fobber, a new variant of Tinba, I wanted to have a look at it\r\nmyself (MD5 691ce6807925fed03cb61f4add0e5ffd ).\r\nInstead of unpacking all the code at once in memory, Fobber uses a cleverer way to make the analysis much more\r\ndifficult. Before any code can be executed, Fobber performs the following:\r\nDecrypt the function to be executed\r\nExecute it\r\nRe-encrypt the function\r\nIn this post, let’s have a look on how the decryption routine works. On the screenshot below we see OllyDbg\r\nstopped before the decryption function at 0x009E2592 is getting called.\r\nThe decryption function will use specific bytes preceding this call to perform the decryption. Let’s describe them:\r\nPosition Description\r\n- 0xB Mutex: either 0x21 (free) or 0xC1 (occupied)\r\n- 0xA..0x9 Code length\r\n- 0x8 XOR key\r\n- 0x7 Either 1 (decrypted) or 0 (encrypted)\r\n- 0x6 Not used\r\n- 0x5..0x1 Call to decrypt function\r\n0x0 First byte of encrypted code\r\nIf we want to match these field to the above code we obtain:\r\nMutex: 0x21\r\nKey: 0x37\r\nSize 0x9c bytes\r\nEncrypted: 0x0\r\nhttp://blog.wizche.ch/fobber/malware/analysis/2015/08/10/fobber-encryption.html\r\nPage 1 of 4\n\nThe size is not directly used but it is first XORed with the constant 0x0E489 , so 0xE415 ^ 0x0E489 = 0x9C\r\nThe mutex is 0x21 meaning that no other thread is decrypting it.\r\nNow let’s have a look at the decryption function in IDA:\r\nIn loc_2597 the decrypt function waits until the mutex is free (value 0x21 ), then it adds 1 to the -7th byte and\r\nchecks that the the previous value was 0x0 (encrypted), if so, it continues by pushing the XOR key and the\r\ncomputed size to the stack.\r\nFunction xor_code performs the actual decryption. This function loops from 0 to size and for each byte code\r\nperforms the following:\r\nXOR the current code byte with the XOR key\r\nCompute a new XOR key for the next step by rotating the bits of the XOR key 3 position to the right\r\n(ROR) Once finished it foes back\r\nTo simplify this process I wrote a small python script performing the steps described above. You can either use it\r\ndirectly on a dump of the injected memory or adapt it to be loaded in IDA debugger. The script uses pwntools\r\nlibrary to dump assembly code to the console.\r\nimport re\r\nimport struct\r\nfrom pwnlib.asm import disasm\r\nfrom pwnlib.context import context\r\nhttp://blog.wizche.ch/fobber/malware/analysis/2015/08/10/fobber-encryption.html\r\nPage 2 of 4\n\ncontext(arch='i386', os='windows', endian='big', word_size=32)\r\n# Injected memory dump file\r\nf = open(\"./data/dump_009E0000.mem\", \"rb\")\r\nd = bytearray(f.read())\r\ndef decrypt_section(key, size, data, start, end):\r\n ror = lambda val, r_bits, max_bits: \\\r\n ((val \u0026 (2 ** max_bits - 1)) \u003e\u003e r_bits % max_bits) | \\\r\n (val \u003c\u003c (max_bits - (r_bits % max_bits)) \u0026 (2 ** max_bits - 1))\r\n tmpkey = key\r\n for i in range(start, end):\r\n b = data[i]\r\n b = ((b ^ tmpkey) \u0026 0xFF)\r\n tmpkey = ror(tmpkey, 0x3, 8)\r\n tmpkey += 0x53\r\n tmpkey \u0026= 0xFF\r\n data[i] = b\r\n # Write ASM code to the console\r\n print disasm(data)\r\ntotal_length = len(d)\r\nprint \"Total length %s bytes\" % hex(total_length)\r\ncount = 0\r\n# Match where encrypted functions begins using this regex\r\nfor m in re.finditer('\\x21[\\x00-\\xFF]{5}\\xe8[\\x00-\\xFF]{4}', d):\r\n print \"Encrypted code section found at %s\" % hex(m.start())\r\n size = struct.unpack('\u003cH', d[m.start() + 1:m.start() + 3])[0]\r\n size ^= 0x0e489\r\n if size \u003e total_length:\r\n print \"Wrong match, size bigger then binary data\"\r\n continue\r\n key = struct.unpack('B', d[m.start() + 3:m.start() + 4])[0]\r\n encrypted = struct.unpack('B', d[m.start() + 4:m.start() + 5])[0]\r\n if encrypted \u003e 0:\r\n print \"Wrong match, encrypted flag must be zero\"\r\n continue\r\n print \"Key: %s\" % hex(key)\r\n print \"Size: %s bytes\" % hex(size)\r\n print \"Encrypted: %s\" % hex(encrypted)\r\nhttp://blog.wizche.ch/fobber/malware/analysis/2015/08/10/fobber-encryption.html\r\nPage 3 of 4\n\ndecrypt_section(key, size, d, m.end(), m.end() + size)\r\n count += 1\r\nprint \"-\" * 30\r\nprint \"Found %d references!\" % count\r\n# Save the decrypted code to a file\r\nwf = file(\"./data/decrypted.bin\", \"wb\")\r\nwf.write(d)\r\nprint \"Written %d bytes to %s\" % (len(d), wf.name)\r\nSource: http://blog.wizche.ch/fobber/malware/analysis/2015/08/10/fobber-encryption.html\r\nhttp://blog.wizche.ch/fobber/malware/analysis/2015/08/10/fobber-encryption.html\r\nPage 4 of 4",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia",
		"ETDA"
	],
	"references": [
		"http://blog.wizche.ch/fobber/malware/analysis/2015/08/10/fobber-encryption.html"
	],
	"report_names": [
		"fobber-encryption.html"
	],
	"threat_actors": [],
	"ts_created_at": 1775434045,
	"ts_updated_at": 1775791226,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/065288c152731341d18f3254a8e1a65385b1b65e.pdf",
		"text": "https://archive.orkl.eu/065288c152731341d18f3254a8e1a65385b1b65e.txt",
		"img": "https://archive.orkl.eu/065288c152731341d18f3254a8e1a65385b1b65e.jpg"
	}
}