{
	"id": "5b3495e7-b492-444b-a75f-5868a6886b2b",
	"created_at": "2026-04-06T00:16:19.16726Z",
	"updated_at": "2026-04-10T13:11:35.36947Z",
	"deleted_at": null,
	"sha1_hash": "e95dfe764e6612bbf15e7434bea3627b77b5add1",
	"title": "Stadeo: Deobfuscating Stantinko and more",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 554036,
	"plain_text": "Stadeo: Deobfuscating Stantinko and more\r\nBy Vladislav Hrčka\r\nArchived: 2026-04-05 18:20:10 UTC\r\nStadeo is a set of tools primarily developed to facilitate analysis of Stantinko, which is a botnet performing click\r\nfraud, ad injection, social network fraud, password stealing attacks and cryptomining.\r\nStadeo was demonstrated for the first time at Black Hat USA 2020 and subsequently published for free use.\r\nThe scripts, written entirely in Python, deal with Stantinko’s unique control-flow-flattening (CFF) and string\r\nobfuscation techniques described in our March 2020 blogpost. Additionally, they can be utilized for other\r\npurposes: for example, we’ve already extended our approach to support deobfuscating the CFF featured in Emotet\r\n– a trojan that steals banking credentials and that downloads additional payloads such as ransomware.\r\nOur deobfuscation methods use IDA, which is a standard tool in the industry, and Miasm – an open source\r\nframework providing us with various data-flow analyses, a symbolic execution engine, a dynamic symbolic\r\nexecution engine and the means to reassemble modified functions.\r\nYou can find Stadeo at https://github.com/eset/stadeo.\r\nUsage examples\r\nTo work with Stadeo, we first need to set up a RPyC (Remote Python Call) server inside IDA, which allows us to\r\naccess the IDA API from an arbitrary Python interpreter. You can use this script to open an RPyC server in IDA.\r\nIn all the examples below, we set up an RPyC server listening on 10.1.40.164:4455 (4455 being the default port),\r\nand then communicate with the server from a Python console.\r\nWe will use two Stadeo classes:\r\nCFFStrategies for CFF deobfuscation\r\nStringRevealer for string deobfuscation\r\nBoth classes can be initialized for both 32- and 64-bit architecture.\r\nDeobfuscating a single function\r\nSHA-1 of the sample: 791ad58d9bb66ea08465aad4ea968656c81d0b8e\r\nThe code below deobfuscates the function at 0x1800158B0 with the parameter in the R9 register set to 0x567C\r\nand writes its deobfuscated version to the 0x18008D000 address.\r\nThe R9 parameter is a control variable for function-merging the CFF loop (merging variable) and it has to be\r\nspecified using Miasm expressions, which are documented here; note that one has to use RSP_init/ESP_init\r\nhttps://www.welivesecurity.com/2020/08/07/stadeo-deobfuscating-stantinko-and-more/\r\nPage 1 of 7\n\ninstead of RSP/ESP to refer to the stack parameters. For example, we would use ExprMem(ExprId(\"ESP_init\",\r\n32) + ExprInt(4, 32), 32) to target the first stack parameter on 32-bit architecture.\r\nfrom stadeo.cff.cff_strategies import CFFStrategies\r\nfrom miasm.expression.expression import ExprId, ExprInt\r\ncs = CFFStrategies(64)\r\ncs.solve_loop(0x1800158B0,\r\n 0x18008D000,\r\n context={ExprId(\"R9\", 64): ExprInt(0x567c, 64)},\r\n ip='10.1.40.164')\r\nFigure 1. Call to the obfuscated function\r\nhttps://www.welivesecurity.com/2020/08/07/stadeo-deobfuscating-stantinko-and-more/\r\nPage 2 of 7\n\nFigure 2. Obfuscated (left) and deobfuscated (right) CFG\r\nProcessing reachable functions\r\nSHA-1 of the sample: e0087a763929dee998deebbcfa707273380f05ca\r\nThe following code recognizes only obfuscated functions reachable from 0x1002DC50 and searches for\r\ncandidates for merging variables. Successfully recognized and deobfuscated functions are starting at 0x10098000.\r\nfrom stadeo.cff.cff_strategies import CFFStrategies\r\nstrat = CFFStrategies(32)\r\nres = strat.process_merging(0x1002DC50,\r\n 0x10098000,\r\n ip=\"10.1.40.164\")\r\nPartial output:\r\nskipping 0x1002dc50 with val None: 0xbadf00d\r\nmapping: 0x1001ffc0 -\u003e 0x10098000 with val @32[ESP_init + 0x8]: 0x6cef\r\nskipping 0x100010f0 with val None: 0xbadf00d\r\nmapping: 0x1000f0c0 -\u003e 0x100982b7 with val @32[ESP_init + 0x8]: 0x2012\r\nmapping: 0x1003f410 -\u003e 0x10098c8a with val @32[ESP_init + 0x4]: 0x21a4\r\nmapping: 0x1003f410 -\u003e 0x10098f3d with val @32[ESP_init + 0x4]: 0x772a\r\nskipping 0x1004ee79 (library func)\r\n...\r\nhttps://www.welivesecurity.com/2020/08/07/stadeo-deobfuscating-stantinko-and-more/\r\nPage 3 of 7\n\nMapped functions were deobfuscated properly and skipped ones weren’t considered to be obfuscated. The format\r\nof the mapping lines in the output is:\r\nmapping: %obfuscated_function_address% -\u003e %deobfuscated_function_address% with val %merging_variable%:\r\n% merging_variable_value%\r\nThe default value for merging_variable_value is 0x0BADF00D. The skipping lines follow the same pattern, but\r\nthere’s no deobfuscated_function_address. Library functions recognized by IDA are just skipped without further\r\nprocessing.\r\nProcessing all functions\r\nSHA-1 of the sample: e575f01d3df0b38fc9dc7549e6e762936b9cc3c3\r\nWe use the following code only to deal with CFF featured in Emotet, whose CFF implementation fits into the\r\ndescription of common control-flow flattening here.\r\nWe prefer this approach because the method CFFStrategies.process_all() does not attempt to recognize merging\r\nvariables that are not present in Emotet and searches only for one CFF loop per function; hence it is more\r\nefficient.\r\nSuccessfully recognized and deobfuscated functions are sequentially written from 0x0040B000. Format of the\r\noutput is the same as in the process_merging method used in the Processing reachable functions example, but\r\nnaturally there won’t be any merging variables.\r\nfrom stadeo.cff.cff_strategies import CFFStrategies\r\nstrat = CFFStrategies(32)\r\nres = strat.process_all(0x0040b000,\r\n ip=\"10.1.40.164\")\r\nPartial output:\r\nmapping: 0x00401020 -\u003e 0x0040b000 with val None: 0xbadf00d\r\nskipping 0x00401670 with val None: 0xbadf00d\r\nmapping: 0x00401730 -\u003e 0x0040b656 with val None: 0xbadf00d\r\n...\r\nhttps://www.welivesecurity.com/2020/08/07/stadeo-deobfuscating-stantinko-and-more/\r\nPage 4 of 7\n\nFigure 3. Example of obfuscated (left) and deobfuscated (right) function in Emotet\r\nRedirecting references to deobfuscated functions\r\nStadeo doesn't automatically update references to functions after their deobfuscation. In the example below, we\r\ndemonstrate how to patch a function call in Figure 1 from the Deobfuscating a single function example. The\r\npatched reference is shown in Figure 4.\r\nWe use the following code to patch calls, whose fourth parameter is 0x567C, to the obfuscated function at\r\n0x1800158B0 with the deobfuscated one at 0x18008D000. Note that one has to make sure that IDA has\r\nrecognized parameters of the function correctly and possibly fix them.\r\nfrom stadeo.utils.xref_patcher import patch_xrefs\r\npatch_xrefs(0x1800158B0,\r\n 0x18008D000,\r\n {3: 0x567c},\r\n ip='10.1.40.164')\r\nhttps://www.welivesecurity.com/2020/08/07/stadeo-deobfuscating-stantinko-and-more/\r\nPage 5 of 7\n\nFigure 4. Patched reference\r\nRevealing obfuscated strings in a function\r\nThe function StringRevealer.process_funcs() reveals obfuscated strings in the specified function and returns a map\r\nof deobfuscated strings and their addresses.\r\nNote that control flow of the target function has to have been deobfuscated already.\r\nIn the example below, we deobfuscate the strings of the function at 0x100982B7, shown in Figure 5. The function\r\nitself was deobfuscated in the previous Processing reachable functions example.\r\nfrom stadeo.string.string_revealer import StringRevealer\r\nsr = StringRevealer(32)\r\nstrings = sr.process_funcs([0x100982B7],\r\n ip=\"10.1.40.164\")\r\nhttps://www.welivesecurity.com/2020/08/07/stadeo-deobfuscating-stantinko-and-more/\r\nPage 6 of 7\n\nFigure 5. Part of the function at 0x100982B7 which clearly assembles a string\r\nContent of the strings variable after the execution is:\r\n{0x100982B7: {'SeLockMemoryPrivilege'}}\r\nWe hope that you find the Stadeo tools and this explanation of their use helpful. If you have any enquiries reach\r\nout to us at threatintel[at]eset.com or open an issue on https://github.com/eset/stadeo.\r\nSource: https://www.welivesecurity.com/2020/08/07/stadeo-deobfuscating-stantinko-and-more/\r\nhttps://www.welivesecurity.com/2020/08/07/stadeo-deobfuscating-stantinko-and-more/\r\nPage 7 of 7\n\nPartial output: ip=\"10.1.40.164\")  \nmapping: 0x00401020 -\u003e 0x0040b000 with val None: 0xbadf00d\nskipping 0x00401670 with val None: 0xbadf00d \nmapping: 0x00401730 -\u003e 0x0040b656 with val None: 0xbadf00d\n...   \n   Page 4 of 7",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://www.welivesecurity.com/2020/08/07/stadeo-deobfuscating-stantinko-and-more/"
	],
	"report_names": [
		"stadeo-deobfuscating-stantinko-and-more"
	],
	"threat_actors": [],
	"ts_created_at": 1775434579,
	"ts_updated_at": 1775826695,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/e95dfe764e6612bbf15e7434bea3627b77b5add1.pdf",
		"text": "https://archive.orkl.eu/e95dfe764e6612bbf15e7434bea3627b77b5add1.txt",
		"img": "https://archive.orkl.eu/e95dfe764e6612bbf15e7434bea3627b77b5add1.jpg"
	}
}