{
	"id": "12fafcce-61dc-4ce3-b271-dbab5848c085",
	"created_at": "2026-04-06T00:07:46.974447Z",
	"updated_at": "2026-04-10T13:11:37.553742Z",
	"deleted_at": null,
	"sha1_hash": "7b6f65310703ab41c15b5b7dadc665d52298b935",
	"title": "Knowledge Fragment: Unwrapping Fobber",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 423109,
	"plain_text": "Knowledge Fragment: Unwrapping Fobber\r\nArchived: 2026-04-05 15:18:03 UTC\r\nAbout two weeks ago I came across an interesting sample using an interesting anti-analysis pattern.\r\nThe anti-analysis technique can be best described as \"runtime-only code decryption\". This means prior to\r\nexecution of a function, the code is decrypted, then executed and finally encrypted again, but with a different key.\r\nMalwarebytes has already published an analysis on this family they called \"Fobber\".\r\nHowever, in this blog post I wanted to share how to \"unwrap\" the sample's encrypted functions for easier analysis.\r\nThere is also another blog post detailing how to work around the string encryption.\r\nThe sample and code related to this blog post can be found on bitbucket.\r\nFobber's Function Encryption Scheme\r\nFirst off, let's have a look how Fobber looks in memory, visualized by IDA's function analysis:\r\nhttp://byte-atlas.blogspot.ch/2015/08/knowledge-fragment-unwrapping-fobber.html\r\nPage 1 of 9\n\nIDA's first view on Fobber.\r\nIDA only recognizes a handful of functions. Among these is the actual code decryption routine, as well as some\r\ncode handling translating relevant addresses of the position independent code into absolute offsets.\r\nNext, a closer look at how the on-demand decryption/encryption of functions works:\r\nhttp://byte-atlas.blogspot.ch/2015/08/knowledge-fragment-unwrapping-fobber.html\r\nPage 2 of 9\n\nThe Fobber-encrypted function sub_95112A, starting with call to decryptFunctionCode.\r\nWe can see that function sub_95112A starts with a call to what I renamed \"decryptFunctionCode\":\r\nhttp://byte-atlas.blogspot.ch/2015/08/knowledge-fragment-unwrapping-fobber.html\r\nPage 3 of 9\n\nFobber's on-demand decryption code for functions, revealing the parameter offsets neccessary for decryption.\r\nThis function does not make use of the stack, thus it is surrounded by a simple pushad/popad prologue/epilogue.\r\nWe can see that some references are made relative to the return address (initially put into esi by copying from\r\n[esp+20h]):\r\nField [esi-7] contains a flag indicating whether or not the function is already decrypted. \r\nField [esi-8h] contains the single byte key for encryption, while \r\nfield [esi-Ah] contains the length of the encrypted function, stored xor'ed with 0x461F.\r\nThe actual cryptXorCode takes those values as parameters and then loops over the encrypted function body,\r\nxor'ing with the current key and then updating the key by rotating 3bit and adding 0x53.\r\nhttp://byte-atlas.blogspot.ch/2015/08/knowledge-fragment-unwrapping-fobber.html\r\nPage 4 of 9\n\nFunction for decrypting one function, given the neccessary parameters.\r\nAfter decryption, our function makes a lot more sense and we can see the default function prologue (push ebp;\r\nmov ebp, esp) among other things.\r\nhttp://byte-atlas.blogspot.ch/2015/08/knowledge-fragment-unwrapping-fobber.html\r\nPage 5 of 9\n\nThe decrypted equivalent of function sub_95112A, revealing some \"real\" code.\r\nAlso note the parameters:\r\n0x951125 - key: 0x7B\r\n0x951126 - length: 0x4629^0x461F -\u003e 0x36 bytes\r\n0x951128 - encryption flag: 0x01\r\nSo far so good. Now let's decrypt all of those functions automatically.\r\nDecrypt All The Things\r\nFirst, we want to find our decryption function. For all Fobber samples I looked at, the regex\r\nr\"\\x60\\x8B.\\x24\\x20\\x66\" was delivering unique results for locating the decryption function.\r\nNext, we want to find all calls to this decryption function. For this we can use the regex r\"\\xE8\" to find all\r\npotential \"call rel_offset\" instructions.\r\nThen we just need to do some address math and check if the call destination (calculated as: image_base +\r\ncall_origin + relative_call_offset + 5) is equal to the address of our decryption function.\r\nShould this be the case, we can extract the parameters as described above and decrypt the code.\r\nWe then only need to exchange the respective bytes in our binary with the decrypted bytes. In the following code I\r\nalso set the decryption flag and fix the function ending with a \"retn\" (0xC3) instruction to ease IDA's job of\r\nidentifying functions afterwards. Otherwise, rinse/repeat until all functions are decrypted.\r\nCode:\r\nhttp://byte-atlas.blogspot.ch/2015/08/knowledge-fragment-unwrapping-fobber.html\r\nPage 6 of 9\n\n#!/usr/bin/env python\r\nimport re\r\nimport struct\r\ndef decrypt(buf, key):\r\n    decrypted = \"\"\r\n    for char in buf:\r\n        decrypted += chr(ord(char) ^ key)\r\n        # rotate 3 bits\r\n        key = ((key \u003e\u003e 3) | (key \u003c\u003c (8 - 3))) \u0026 0xFF\r\n        key = (key + 0x53) \u0026 0xFF\r\n    return decrypted\r\ndef replace_bytes(buf, offset, bytes):\r\n    return buf[:offset] + bytes + buf[offset + len(bytes):]\r\ndef decrypt_all(binary, image_base):\r\n    # locate decryption function\r\n    decrypt_function_offset = re.search(r\"\\x60\\x8B.\\x24\\x20\\x66\", binary).start()\r\n    # locate all calls to decryption function\r\n    regex_call = r\"\\xe8(?P\u003crel_call\u003e.{4})\"\r\n    for match in re.finditer(regex_call, binary):\r\n        call_origin = match.start()\r\n        packed_call = binary[call_origin + 1:call_origin + 1 + 4]\r\n        rel_call = struct.unpack(\"I\", packed_call)[0]\r\n        call_destination = (image_base + call_origin + rel_call + 5) \u0026 0xFFFFFFFF\r\n        if call_destination == image_base + decrypt_function_offset:\r\n            # decrypt function and replace/fix\r\n            decrypted_flag = ord(binary[call_origin - 0x2])\r\n            if decrypted_flag == 0x0:\r\n                key = ord(binary[call_origin - 0x3])\r\n                size = struct.unpack(\"H\", binary[call_origin - 0x5:call_origin - 0x3])[0] ^ 0x461F\r\n                buf = binary[call_origin + 0x5:call_origin + 0x5 + size]\r\n                decrypted_function = decrypt(buf, key)\r\n                binary = replace_bytes(binary, call_origin + 0x5, decrypted_function)\r\n                binary = replace_bytes(binary, call_origin + len(decrypted_function), \"\\xC3\")\r\n                binary = replace_bytes(binary, call_origin - 0x2, \"\\x01\")\r\n    return binary\r\n[...]\r\nhttp://byte-atlas.blogspot.ch/2015/08/knowledge-fragment-unwrapping-fobber.html\r\nPage 7 of 9\n\nIDA likes this this already better:\r\nIDA's view on a code-decrypted Fobber sample.\r\nHowever, we are not quite done yet, as IDA still barfs on a couple of functions.\r\nConclusion\r\nAfter decrypting all functions, we can already start analyzing the sample effectively.\r\nBut we are not quite done yet, and the second post looks closer at the inline usage of encrypted strings.\r\nsample used:\r\n  md5: 49974f869f8f5d32620685bc1818c957\r\n  sha256: 93508580e84d3291f55a1f2cb15f27666238add9831fd20736a3c5e6a73a2cb4\r\nhttp://byte-atlas.blogspot.ch/2015/08/knowledge-fragment-unwrapping-fobber.html\r\nPage 8 of 9\n\nRepository with memdump + extraction code\r\nSource: http://byte-atlas.blogspot.ch/2015/08/knowledge-fragment-unwrapping-fobber.html\r\nhttp://byte-atlas.blogspot.ch/2015/08/knowledge-fragment-unwrapping-fobber.html\r\nPage 9 of 9",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"ETDA",
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"http://byte-atlas.blogspot.ch/2015/08/knowledge-fragment-unwrapping-fobber.html"
	],
	"report_names": [
		"knowledge-fragment-unwrapping-fobber.html"
	],
	"threat_actors": [],
	"ts_created_at": 1775434066,
	"ts_updated_at": 1775826697,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/7b6f65310703ab41c15b5b7dadc665d52298b935.pdf",
		"text": "https://archive.orkl.eu/7b6f65310703ab41c15b5b7dadc665d52298b935.txt",
		"img": "https://archive.orkl.eu/7b6f65310703ab41c15b5b7dadc665d52298b935.jpg"
	}
}