{
	"id": "a2631925-a9b6-4cbb-aa8d-8bc90b0743ad",
	"created_at": "2026-04-06T00:20:07.275677Z",
	"updated_at": "2026-04-10T03:23:38.816843Z",
	"deleted_at": null,
	"sha1_hash": "e4401a1c54bc8e93873f4dd81f79f6e2c41005f7",
	"title": "Automated string de-gobfuscation",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 1717688,
	"plain_text": "Automated string de-gobfuscation\r\nPublished: 2020-12-02 · Archived: 2026-04-05 20:12:08 UTC\r\nLast week the Network Security Research Lab at 360 released a blog post on an obfuscated backdoor written in\r\nGo named Blackrota. They claim that the Blackrota backdoor is available for both x86/x86-64 architectures which\r\nis no surprise given how capable Golang’s cross compilation is.\r\nFor the last 4 years we have been using Golang for our internal services, and I can definitely see the allure that\r\nGolang has for malware authors:\r\nStatically compiled binaries by default\r\nCross compilation is often as simple as setting two environment variables\r\nStrong package ecosystem allowing you to pull in code that you need from other sources\r\nNo runtime dependencies\r\nEsoteric runtime with a non-standard calling convention breaks most decompilation tools forcing reverse\r\nengineers to read assembly\r\nBlackrota uses gobfuscate to obfuscate their source code before it gets compiled by the Go toolchain. Gobfuscate\r\npresents a number of challenges to reverse engineers but the one I’ll be focusing on today is string obfuscation.\r\nString Obfuscation\r\nMalware has been using XOR encoded strings for years now, but Blackrota takes this a step further1. It generates a\r\nrandom XOR key per string and wraps the string in a function that XORs the string at runtime to return the correct\r\none.\r\ngobfuscate runs before the compilation process to produce an obfuscated version of your code which is then\r\ncompiled by the Go compiler:\r\nBefore:\r\npackage main\r\nimport \"fmt\"\r\nfunc main() {\r\nfmt.Println(\"Hello world!\")\r\n}\r\nAfter:\r\nhttps://www.kryptoslogic.com/blog/2020/12/automated-string-de-gobfuscation/\r\nPage 1 of 9\n\npackage main\r\nimport \"fmt\"\r\nfunc main() {\r\nfmt.Println((func() string {\r\nmask := []byte(\"\\x21\\x0f\\xc7\\xbb\\x81\\x86\\x39\\xac\\x48\\xa4\\xc6\\xaf\")\r\nmaskedStr := []byte(\"\\x69\\x6a\\xab\\xd7\\xee\\xa6\\x4e\\xc3\\x3a\\xc8\\xa2\\x8e\")\r\nres := make([]byte, 12)\r\nfor i, m := range mask {\r\nres[i] = m ^ maskedStr[i]\r\n}\r\nreturn string(res)\r\n}()))\r\n}\r\nYou can use a tool like GCHQ’s CyberChef to verify that the result of those 2 byte arrays XORed together is:\r\nHello world! .\r\nWhile you could go through the effort of manually XORing each string in a binary you’re reversing, it’ll get\r\ntedious very quickly.\r\nHow do I solve it at scale?\r\nSo I know that Golang has a very capable cross compiler and that I want to deal with these XORed strings across\r\ndifferent architectures, but how?\r\nBinary Ninja (aka Binja) has a very powerful intermediate language (IL), which allows us to operate on a\r\nrepresentation of a function regardless of the system architecture (assuming there is an architecture loader for it)2.\r\nWhen deciding on an approach here I specifically wanted to use Binary Ninja’s IL as a learning exercise. The\r\nsolution could certainly be implemented using any number of alternative tools (e.g. Unicorn), however Binja has\r\nthe benefit of being very easy to use on any of its supported platforms. Another benefit, of course, is that the IL\r\neliminates the need to understand the target architecture at all, which (depending on implementation) may not be\r\nthe case using something like Unicorn.\r\nSo the solution will need a way of identifying what functions we want to extract the strings from (Candidate\r\nIdentification) and a way to extract the correct string from Binary Ninja’s low level IL.\r\nCandidate Identification\r\nLuckily for us, the Go compiler doesn’t inline this function call so it’s seen as an entirely new function:\r\nAssuming the approach I take can avoid the Go runtime functions: _runtime.morestack_noctxt and\r\n_runtime.slicebytetostring it should be pretty easy to emulate.\r\nhttps://www.kryptoslogic.com/blog/2020/12/automated-string-de-gobfuscation/\r\nPage 2 of 9\n\nThe solution will need to find functions in the binary which call _runtime.slicebytetostring and make use of\r\nan xor instruction.\r\nIn my first attempt you’ll see that I’m not using the IL for this, but that’s trivially solved in the final solution.\r\nMy first attempt at writing this didn’t go very well… Binary Ninja implements multiple different ILs at varying\r\nlevels of readability based on how much lifting, transformation and control flow recovery takes place. You can see\r\nexamples of LLIL (low level intermediate language), MLIL (medium level intermediate language) and HLIL (high\r\nlevel intermediate language) ILs in the corresponding links.\r\nSo I made the assumption that HLIL would be the best choice because there was less text. I also didn’t really\r\nunderstand that an IL operation could consist of multiple different IL operations:\r\nExample of a XOR/assign operation in HLIL\r\nYou can see my first attempt here. I wish I had read Josh Watson’s blog post on Binja’s IL before I started working\r\non it as it details some of the non obvious details 😅\r\nI managed to get some results from this approach but it was not consistent and broke very easily.\r\nI started chatting to Jordan Wiens one of the founders of Vector35, the company who makes Binary Ninja – and he\r\nsuggested that I should rewrite it using LLIL and a full blown emulator rather than a state machine.\r\nAt first this was daunting, but I started to play around with it and thanks to some incredibly helpful plugins which\r\nI’ll detail later, as well as a project from Josh Watson which had the foundations of an LLIL emulator, I was able\r\nto start making headway.\r\nUsing a helpful snippet from Jordan I was able to work out what LLIL operations I needed to implement and in\r\ntotal there were around 28 of them:\r\nhttps://www.kryptoslogic.com/blog/2020/12/automated-string-de-gobfuscation/\r\nPage 3 of 9\n\nIL operation Description\r\nLLIL_TAILCALL Call another function without writing a return address to the stack\r\nLLIL_CALL Call another function and push a return address to the stack\r\nLLIL_RET Pop a return address from the stack and jump to it\r\nLLIL_PUSH Push a value onto the stack\r\nLLIL_POP Pop a value from the stack\r\nLLIL_XOR XOR 2 values\r\nLLIL_ZX Zero Extends\r\nLLIL_GOTO Set the instruction pointer\r\nLLIL_STORE Write some data to memory\r\nLLIL_READ Read some data from memory\r\nLLIL_SET_FLAG Sets a flag\r\nLLIL_FLAG Reads a flag\r\nLLIL_CMP_NE Is not equal\r\nLLIL_CMP_E Is equal\r\nLLIL_CMP_SLE Signed less than or equal\r\nLLIL_CMP_SGT Signed greater than\r\nLLIL_CMP_SGE Signed greater than or equal\r\nLLIL_CMP_UGE Unsigned greater than or equal\r\nLLIL_CMP_UGT Unsigned greater than\r\nLLIL_CMP_ULT Unsigned less than\r\nLLIL_CMP_SLT Signed less than comparison\r\nLLIL_CMP_ULE Unsigned less than or equal\r\nLLIL_IF Check a conditional and set the instruction pointer based on the outcome\r\nLLIL_SET_REG Set a register value\r\nLLIL_CONST Get a constant value\r\nLLIL_CONST_PTR Get a constant value that happens to be a pointer\r\nhttps://www.kryptoslogic.com/blog/2020/12/automated-string-de-gobfuscation/\r\nPage 4 of 9\n\nIL operation Description\r\nLLIL_REG Read a register value\r\nLLIL_SUB Subtract two values\r\nLLIL_ADD Add two values\r\nAlone none of these seem too complex right? But together they allow us to fully implement what is required to\r\ndecode the XOR obfuscation in less than 500 lines of code. There’s a huge amount of heavy lifting that Binary\r\nNinja does under the hood that helps us keep the code as simple as possible, and I’m sure I’m missing some tricks.\r\nOnce I had something that kinda worked Jordan jumped in and converted it into a functional Binary Ninja plugin\r\nand cleaned up some of the code smell that occurs when you’re hacking away on a problem.\r\nWhile developing the plugin I came across some incredibly useful Binary Ninja plugins which I feel need a\r\nmention:\r\nSENinja\r\nSENinja is a symbolic execution engine for Binja, built using the Z3 SMT solver. It implements a LLIL emulator\r\nthat builds and manipulates Z3 formulae. Although the intentions of SENinja are much more complex then what I\r\nwas using it for, it was really useful to have something I could compare with (I didn’t want to turn on a Linux VM\r\nand use a debugger and deal with possible anti debugging tricks).\r\nWhat SENinja looks like while emulating a function\r\nBNIL Instruction Graph\r\nhttps://www.kryptoslogic.com/blog/2020/12/automated-string-de-gobfuscation/\r\nPage 5 of 9\n\nBNIL Instruction Graph allows you to click on a line of IL and generate a graph of the IL operations that make up\r\nthat line.\r\nLLIL\r\nhttps://www.kryptoslogic.com/blog/2020/12/automated-string-de-gobfuscation/\r\nPage 6 of 9\n\nHLIL\r\nThe difference in complexity between a line of LLIL and HLIL is surprising. However it makes sense once you\r\nunderstand that HLIL exists to allow higher level control flows to be recovered with the intention of producing a\r\nsource code representation, typically in an emulator though you don’t need this kind of high level control flow\r\ndata.\r\nAnother powerful feature of BNIL Instruction Graph is the ability to generate specific IL matching templates that\r\nare convenient starting points for building IL code:\r\ndef match_LowLevelIL_10a6df6_0(insn):\r\n # ecx = ecx ^ edx\r\n if insn.operation != LowLevelILOperation.LLIL_SET_REG:\r\n return False\r\n if insn.dest.name != 'ecx':\r\n return False\r\n # ecx ^ edx\r\n if insn.src.operation != LowLevelILOperation.LLIL_XOR:\r\n return False\r\n # ecx\r\n if insn.src.left.operation != LowLevelILOperation.LLIL_REG:\r\n return False\r\n if insn.src.left.src.name != 'ecx':\r\nhttps://www.kryptoslogic.com/blog/2020/12/automated-string-de-gobfuscation/\r\nPage 7 of 9\n\nreturn False\r\n # edx\r\n if insn.src.right.operation != LowLevelILOperation.LLIL_REG:\r\n return False\r\n if insn.src.right.src.name != 'edx':\r\n return False\r\n return True\r\nCan haz please?\r\nAs the usage of Golang by malware authors increases, so will their understanding and capabilities within the Go\r\necosystem. Right now gobfuscate is the main obfuscator used by actors using Golang, but this won’t always be\r\ntrue. As a Hacker News comment puts it: this is fairly weak. Gobfuscator doesn’t implement control flow\r\nobfuscation, the runtime functions aren’t obfuscated and at most gobfuscate serves mostly as an annoyance and a\r\nthinly veiled layer of obscurity.\r\nSolver at work!\r\nYou can download the Binary Ninja plugin on the plugin manager or download the code from the GitHub\r\nrepository linked here. Additionally you can find the Blackrota 32-bit and 64-bit Binary Ninja databases to look at\r\non Binary Ninja cloud.\r\nThanks:\r\nA massive thanks to the people in the Binary Ninja Slack who answered some of my questions while I was\r\ngetting started.\r\nhttps://www.kryptoslogic.com/blog/2020/12/automated-string-de-gobfuscation/\r\nPage 8 of 9\n\nJosh Watson, for his permissibly licensed emulator that I was able to use to get an idea of what was\r\nrequired to achieve my goal.\r\nJordan Wiens, for providing assistance during development, cleaning up messy code, filing a couple of\r\nBinja bug reports for me and generally answering questions about Binary Ninja.\r\nSource: https://www.kryptoslogic.com/blog/2020/12/automated-string-de-gobfuscation/\r\nhttps://www.kryptoslogic.com/blog/2020/12/automated-string-de-gobfuscation/\r\nPage 9 of 9",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://www.kryptoslogic.com/blog/2020/12/automated-string-de-gobfuscation/"
	],
	"report_names": [
		"automated-string-de-gobfuscation"
	],
	"threat_actors": [
		{
			"id": "5d2bd376-fcdc-4c6a-bc2c-17ebbb5b81a4",
			"created_at": "2022-10-25T16:07:23.667223Z",
			"updated_at": "2026-04-10T02:00:04.705778Z",
			"deleted_at": null,
			"main_name": "GCHQ",
			"aliases": [
				"Government Communications Headquarters",
				"Operation Socialist"
			],
			"source_name": "ETDA:GCHQ",
			"tools": [
				"Prax",
				"Regin",
				"WarriorPride"
			],
			"source_id": "ETDA",
			"reports": null
		}
	],
	"ts_created_at": 1775434807,
	"ts_updated_at": 1775791418,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/e4401a1c54bc8e93873f4dd81f79f6e2c41005f7.pdf",
		"text": "https://archive.orkl.eu/e4401a1c54bc8e93873f4dd81f79f6e2c41005f7.txt",
		"img": "https://archive.orkl.eu/e4401a1c54bc8e93873f4dd81f79f6e2c41005f7.jpg"
	}
}