{
	"id": "0739131d-d88d-4318-b6fb-b998014e50b2",
	"created_at": "2026-04-06T00:16:33.373461Z",
	"updated_at": "2026-04-10T03:22:02.345777Z",
	"deleted_at": null,
	"sha1_hash": "18690360326dfda8d6f208c6121daf145004e6bd",
	"title": "Stantinko’s new cryptominer features unique obfuscation techniques",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 701225,
	"plain_text": "Stantinko’s new cryptominer features unique obfuscation\r\ntechniques\r\nBy Vladislav Hrčka\r\nArchived: 2026-04-05 17:10:23 UTC\r\nIn the new cryptomining module we discovered and described in our previous article, the cybercriminals behind\r\nthe Stantinko botnet introduced several obfuscation techniques, some of which have not yet been publicly\r\ndescribed. In this article, we dissect these techniques and describe possible countermeasures against some of them.\r\nTo thwart the analysis and avoid detection, Stantinko’s new module uses various obfuscation techniques:\r\nObfuscation of strings - meaningful strings are constructed and only present in memory when they are to be\r\nused\r\nControl-flow obfuscation - transformation of the control flow to a form that is hard to read and the\r\nexecution order of basic blocks is unpredictable without extensive analysis\r\nDead code - addition of code that is never executed; it also contains exports that are never called. Its\r\npurpose is to make the files look more legitimate to prevent detection\r\nDo-nothing code – addition of code that is executed, but that has no material effect on the overall\r\nfunctionality. It is meant to bypass behavioral detections\r\nDead strings and resources - addition of resources and strings with no impact on the functionality\r\nOut of these techniques, the most notable are obfuscation of strings and control-flow obfuscation; we will describe\r\nthem in detail in the following sections.\r\nObfuscation of strings\r\nAll the strings embedded in the module are unrelated to the real functionality. Their source is unknown and they\r\neither serve as building blocks for constructing the strings that are actually used or they are not used at all.\r\nThe actual strings used by the malware are generated in memory in order to avoid file-based detection and thwart\r\nanalysis. They are formed by rearranging bytes of the decoy strings – those embedded in the module – and using\r\nstandard functions for string manipulation, such as strcpy(), strcat(), strncat(), strncpy(), sprintf(), memmove() and\r\ntheir Unicode versions.\r\nSince all the strings to be used in a particular function are always assembled sequentially at the beginning of the\r\nfunction, one can emulate the entry points of the functions and extract the sequences of printable characters that\r\narise to reveal the strings.\r\nhttps://www.welivesecurity.com/2020/03/19/stantinko-new-cryptominer-unique-obfuscation-techniques/\r\nPage 1 of 15\n\nFigure 1. Example of string obfuscation. There are 7 highlighted decoy strings in the image. For example, the one\r\nmarked in red generates the string “NameService”.\r\nControl-flow flattening\r\nControl-flow flattening is an obfuscation technique used to thwart analysis and avoid detection.\r\nCommon control-flow flattening is achieved by splitting a single function into basic blocks. These blocks are then\r\nplaced as dispatches into a switch statement inside of a loop (i.e. each dispatch consists of exactly one basic\r\nhttps://www.welivesecurity.com/2020/03/19/stantinko-new-cryptominer-unique-obfuscation-techniques/\r\nPage 2 of 15\n\nblock). There is a control variable to determine which basic block should be executed in the switch statement; its\r\ninitial value is assigned before the loop.\r\nThe basic blocks are all assigned an ID and the control variable always holds the ID of the basic block to be\r\nexecuted.\r\nAll the basic blocks set the value of the control variable to the ID of its successor (a basic block can have multiple\r\npossible successors; in that case the immediate successor can be chosen in a condition).\r\nFigure 2. Structure of common control-flow-flattening loop\r\nThere are various approaches to resolving this obfuscation, such as using IDA’s microcode API. Rolf Rolles used\r\nthis method to identify these loops heuristically, extract the control variable from each flattened block and\r\nrearrange them in accordance with the control variables.\r\nThis – and similar – approaches would not work on Stantinko’s obfuscation, because it has some unique features\r\ncompared to common control-flow-flattening obfuscations:\r\nCode is flattened on the source code level, which also means the compiler can introduce some anomalies\r\ninto the resulting binary\r\nThe control variable is incremented in a control block (to be explained later), not in basic blocks\r\nDispatches contain multiple basic blocks (the division may be disjunctive, i.e. each basic block belongs to\r\nexactly one dispatch, but sometimes the dispatches intertwine, meaning that they share some basic blocks)\r\nFlattening loops can be nested and successive\r\nMultiple functions are merged\r\nThese features show that Stantinko has introduced new obstacles to this technique that must be overcome in order\r\nto analyze its final payload.\r\nControl-flow flattening in Stantinko\r\nIn most of Stantinko’s functions, the code is split into several dispatches (described above) and two control blocks\r\n— a head and a tail — that control the flow of the function.\r\nhttps://www.welivesecurity.com/2020/03/19/stantinko-new-cryptominer-unique-obfuscation-techniques/\r\nPage 3 of 15\n\nThe head decides which dispatch should be executed by checking the control variable. The tail increases the\r\ncontrol variable by a fixed constant and either goes back to the head or exits the flattening loop:\r\nFigure 3. Regular structure of Stantinko’s control-flow-flattening loop\r\nStantinko appears to be flattening code of all functions and bodies of high-level constructs (such as a for loop), but\r\nsometimes it also tends to choose seemingly random blocks of code. Since it applies the control-flow-flattening\r\nloops on both functions and high-level constructs, they can be naturally nested and there happen to be multiple\r\nconsecutive loops too.\r\nWhen a control-flow-flattening loop is created by merging code of multiple functions, the control variable in the\r\nresulting merged function is initialized with different values, based on which of the original functions is called.\r\nThe value of the control variable is passed to the resulting function as a parameter.\r\nWe overcame this obfuscation technique by rearranging the blocks in the binary; our approach is described in the\r\nnext section.\r\nIt’s important to note that we observed multiple anomalies in some of the flattening loops that make it harder to\r\nautomate the deobfuscation process. The majority of them seem to be generated by the compiler; this leads us to\r\nbelieve that the control-flow-flattening obfuscation is applied prior to compilation.\r\nWe witnessed the following anomalies; they can appear separately or in combination:\r\n1. Some dispatches can be just dead code – they will never be executed. (Examples in the section “Dead code\r\ninside the control-flow-flattening loop” below.)\r\nhttps://www.welivesecurity.com/2020/03/19/stantinko-new-cryptominer-unique-obfuscation-techniques/\r\nPage 4 of 15\n\n2. Basic blocks inside of dispatches may intertwine, this means that they can contain joint code.\r\nFigure 4. Structure of a flattening loop with dispatches sharing joint code\r\n3. There are direct jumps from dispatches to a block outside the flattening loop, right behind the tail, and to\r\nblocks that return from the function.\r\nhttps://www.welivesecurity.com/2020/03/19/stantinko-new-cryptominer-unique-obfuscation-techniques/\r\nPage 5 of 15\n\nFigure 5. Structure of a flattening loop whose dispatch breaks directly out of the loop. Only one of the dashed\r\nlines occurs.\r\n4. There can be multiple tails, or no tail at all – in the latter case, the control variable is increased at the end of\r\neach dispatch.\r\nFigure 6. Structure of a flattening loop without any tail (left) and with multiple tails (right)\r\n5. The head doesn’t contain a jump table right away. Instead, there can be multiple jump tables and there’s a\r\nsequence of branches, prior to the jump tables, binary-searching for the correct dispatch.\r\nhttps://www.welivesecurity.com/2020/03/19/stantinko-new-cryptominer-unique-obfuscation-techniques/\r\nPage 6 of 15\n\n6. The value of the control variable might be used inside of the dispatches; this means that the control value\r\nhas to be preserved/computed even in the deobfuscated code.\r\nFigure 7. The EDI register contains the control variable that is passed to EAX and used inside the dispatch. The\r\ndispatch is highlighted with red.\r\n7. Sometimes, the tail contains instructions that are crucial to restoring the correct values of registers and\r\nlocal variables. During deobfuscation, we remove the tail, so we must make sure these instructions are\r\nexecuted after each dispatch, even if they are not part of it.\r\n8. There are cases where there is no dispatch whose ID is, at that moment, equal to the current value of the\r\ncontrol variable.\r\nDeobfuscation\r\nOur goal is to build a deobfuscation function able to rearrange the code on the binary level to make it easily\r\nreadable for a reverse engineer, while keeping the resulting code executable. It has to be able to recognize all basic\r\nblocks belonging to each dispatch and to copy and move them arbitrarily.\r\nDuring basic block manipulation one has to make sure to recalculate relative addresses of branch targets and\r\naddresses forming legitimate jump tables correctly.\r\nOur solution doesn’t take relocations into account; hence one always needs to make sure that the sample is loaded\r\nat the same base address.\r\nWe used a reverse-engineering framework that provides us with some useful features, such as assembly\r\nmanipulation and a symbolic execution engine.\r\nThe core parameters of the function are the addresses of the control blocks (head and tails), range and step of the\r\ncontrol variable, names of the registers, and the memory locations containing the control variable,\r\ncontrol_locations, and, lastly, the address of the first basic block following the loop, which we define as\r\nnext_block. It obviously also requires the address of the function to be deobfuscated and the address where the\r\ndeobfuscated function should be placed.\r\nhttps://www.welivesecurity.com/2020/03/19/stantinko-new-cryptominer-unique-obfuscation-techniques/\r\nPage 7 of 15\n\nWe expect multiple tails due to anomaly 4 above.\r\nThe deobfuscation function iterates through the range of the control variable by its step value to simulate the real\r\ncontrol-flow-flattening loop; in each iteration, the function starts by generating a context to deal with anomalies 6\r\nand 7. The context is to be placed before the respective dispatch.\r\nThe context is a basic block containing instructions assigning registers and memory addresses and keeping\r\ncontrol_locations updated. The context of the first iteration just preserves the value of the control variable. (Note:\r\nno context is required to deal with anomaly number 4.)\r\nThe last basic blocks of the previous dispatch (or, in case of the first dispatch, the basic blocks right before the\r\nhead) are redirected to the created context.\r\nThe initial basic block of a dispatch that is to be executed (in each of the iterations) is determined by the current\r\nvalue of the control variable (dispatch ID).\r\nThe actual basic block is found by symbolically executing the binary-search algorithm, which searches for a basic\r\nblock with the current ID. The initial state of the symbolic execution contains control_locations assigned to the\r\ncurrent value of the control variable.\r\nWe stop the symbolic execution at the first basic block that (i) contains an unconditional branch, or, (ii) has a\r\ndestination that cannot be determined by the control variable.\r\nOne could also emulate this part or use a framework that would be able to simplify the binary-search algorithm\r\ninto a jump table and then convert that into a switch statement instead. These methods deal with anomaly 5.\r\nIn case there’s no dispatch for a particular ID, the loop just continues and increases the control variable due to\r\nanomaly 8.\r\nThe whole dispatch (i.e., each basic block that is reachable from its initial basic block to its head, tail(s) or\r\nnext_block) is then copied after the preceding context block (as described above). It cannot be just moved due to\r\nanomaly 2.\r\nThere are currently two uncommon cases that can occur due to anomaly 3; both result in premature termination of\r\nthe iteration. The cases happen when a dispatch:\r\nReturns from the function\r\nPoints to next_block\r\nFinally, when the iteration ends, the last basic blocks of the previous dispatch (or basic blocks right before the\r\nhead, in case of the first dispatch), are redirected to the first basic block outside the flattening loop.\r\nThis method solves anomaly 1 automatically, since the dead dispatches won’t be copied into the resulting code.\r\nhttps://www.welivesecurity.com/2020/03/19/stantinko-new-cryptominer-unique-obfuscation-techniques/\r\nPage 8 of 15\n\nFigure 8. Example of an obfuscated function (left) and its deobfuscated counterpart (right). The dispatches are\r\nexecuted in this order: dispatch1 → dispatch2 → dispatch3.\r\nThese changes are then written to the virtual address where the deobfuscated function should be placed.\r\nIn case we are dealing with flattening of merged functions, we point references to the target function having the\r\nidentical initial value of the control variable in the parameter, to the address of the new deobfuscated function.\r\nFigure 9. Example of obfuscated (right) and deobfuscated (left) control flow graph\r\nPossible improvements\r\nThe approach described above operates exclusively at the assembly level, which isn’t sufficient to make the\r\ndeobfuscation fully automated.\r\nhttps://www.welivesecurity.com/2020/03/19/stantinko-new-cryptominer-unique-obfuscation-techniques/\r\nPage 9 of 15\n\nThe reason is that accurate recognition of all patterns is rather difficult, mostly due to various compiler\r\noptimizations present in the source code level obfuscations. The pattern recognition is necessary in our case, for\r\nexample, to automatically fill in the parameters of the core deobfuscation function.\r\nThe advantage of this approach is that the resulting code can be executed right away and one can use arbitrary\r\nreverse-engineering tools for further analysis.\r\nThis approach could be further improved by the use of a progressive intermediate representation (IR), which\r\nprovides optimization techniques that would, among other things, get rid of most of the anomalies generated by\r\ncompilers, and thus allow automated recognition of the parameters required by the deobfuscation function.\r\nOne could also use the selected IR for both recognition and the deobfuscation of which the latter, in our case,\r\nconsists of rearranging of basic blocks.\r\nThe drawback of this option is that the resulting code would also be in the IR, which means that the consecutive\r\nanalysis would have to be done with the IR as well. The number of tools working with the IR and their\r\nfunctionality could be rather limited, especially when it comes to visualization. Due to this, it’d be hard to analyze\r\na more complex sample, especially when there are additional layers of obfuscation. We wouldn’t be able to\r\nexecute the resulting code either.\r\nDead code\r\nBy “dead code” we mean code that either is never executed, or has no overall impact on the functionality. The\r\nmalware contains dead code mostly in the flattened loops (effectively removed by our above-explained\r\ndeobfuscation function), but there are also, for example, unused exports and there’s no way to distinguish the\r\nunused exports from the legitimate ones.\r\nAs for dead code in the flattened loop: for Stantinko, it is always inside the dispatches that are never executed. It\r\nmay contain modified parts of legitimate software such as WinSpy++ (see the example below) that was obfuscated\r\nin the same way.\r\nhttps://www.welivesecurity.com/2020/03/19/stantinko-new-cryptominer-unique-obfuscation-techniques/\r\nPage 10 of 15\n\nFigure 10. Deobfuscated part of dead code inside a dispatch containing legitimate WinSpy++ code\r\nhttps://www.welivesecurity.com/2020/03/19/stantinko-new-cryptominer-unique-obfuscation-techniques/\r\nPage 11 of 15\n\nFigure 11. The equivalent part of code (as in Figure 10) in the official release of WinSpy++\r\nDo-nothing code\r\nEven after the unflattening operation, there are parts of code that have no purpose at all, intermingled with the\r\nlines of the “real code”. This is probably meant to obscure the analysis even more or to bypass behavioral\r\ndetection.\r\nhttps://www.welivesecurity.com/2020/03/19/stantinko-new-cryptominer-unique-obfuscation-techniques/\r\nPage 12 of 15\n\nhttps://www.welivesecurity.com/2020/03/19/stantinko-new-cryptominer-unique-obfuscation-techniques/\r\nPage 13 of 15\n\nFigure 12. Marked parts are redundant code that iterates through the first two disk volume names and then does\r\nnothing with the returned values\r\nSince the code isn’t much harder to read, we decided not to take any actions and analyzed the code at this point.\r\nTo optimize out this do-nothing code in general: we’d have to, for example, generate disjunct slices containing all\r\nthe Windows API calls that are present. The slicing criterion would consist of all the parameters of the calls in\r\neach disjunct slice.\r\nSubsequently we’d execute the slices with a prepared call stack in a controlled environment and we’d consider a\r\nslice to be functional if it does at least one of the following:\r\nmake some changes to the underlying OS\r\nrequire an initial value of a function parameter or a global variable to be known\r\nassign a value of a function parameter or a global variable\r\ndirectly affect overall control flow of the function\r\nConclusion\r\nThe criminals behind the Stantinko botnet are constantly improving and developing new modules that often\r\ncontain non-standard and interesting techniques.\r\nhttps://www.welivesecurity.com/2020/03/19/stantinko-new-cryptominer-unique-obfuscation-techniques/\r\nPage 14 of 15\n\nWe have described their new cryptomining module previously; for the module's functional analysis, refer to our\r\nNovember 2019 blogpost. This module displays several obfuscation techniques aimed at protecting against\r\ndetection and thwarting analysis. We analyzed the techniques and described a possible approach to deobfuscating\r\nsome of these techniques.\r\nNote: For IoCs and the list of techniques mapped to the MITRE ATT\u0026CK taxonomy, please refer to our previous\r\narticle describing this cryptominer’s functionality.\r\nSource: https://www.welivesecurity.com/2020/03/19/stantinko-new-cryptominer-unique-obfuscation-techniques/\r\nhttps://www.welivesecurity.com/2020/03/19/stantinko-new-cryptominer-unique-obfuscation-techniques/\r\nPage 15 of 15",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://www.welivesecurity.com/2020/03/19/stantinko-new-cryptominer-unique-obfuscation-techniques/"
	],
	"report_names": [
		"stantinko-new-cryptominer-unique-obfuscation-techniques"
	],
	"threat_actors": [],
	"ts_created_at": 1775434593,
	"ts_updated_at": 1775791322,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/18690360326dfda8d6f208c6121daf145004e6bd.pdf",
		"text": "https://archive.orkl.eu/18690360326dfda8d6f208c6121daf145004e6bd.txt",
		"img": "https://archive.orkl.eu/18690360326dfda8d6f208c6121daf145004e6bd.jpg"
	}
}