{
	"id": "66c51d72-c150-4476-8193-7daade69cdd8",
	"created_at": "2026-04-06T00:14:13.279226Z",
	"updated_at": "2026-04-10T03:20:04.159586Z",
	"deleted_at": null,
	"sha1_hash": "9521411a20e3d5d7f795c7f99da47ae54a346503",
	"title": "Using Emulation Against Anti-Reverse Engineering Techniques",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 1092050,
	"plain_text": "Using Emulation Against Anti-Reverse Engineering Techniques\r\nBy Gergely Revay\r\nArchived: 2026-04-05 17:19:32 UTC\r\nThe life of a malware reverse-engineer is not always easy. You might think that it is all fun and games, but in\r\nreality, it is bits and assembly. And as it is true for the whole security industry, it is a cat and mouse game with\r\nmalware developers. As we continuously expose their malware, which leads us to learn more about the attackers\r\nand develop countermeasures, they keep trying to implement new ways of slowing down analysts and reverse\r\nengineering. In a recent blog, we analyzed the Pandora ransomware and discussed that it is filled with anti-reverse-engineering techniques, and has multiple layers of code obfuscation.\r\nIn this blog post, instead of talking about what we discovered in the Pandora ransomware, I would like to discuss\r\nhow we did that. More specifically, we will fight the function call obfuscation and string encryption in this\r\nsample, using emulation. We will implement an IDAPython script using the flare-emu framework to turn the\r\ndisassembly in IDA Pro more readable. This will be a great help in the static analysis of the sample.\r\nAffected Platforms: Windows\r\nChallenges in Pandora\r\nFor the detailed list of challenges we faced while reversing Pandora, read our analysis blog. In this post I will\r\ndiscuss two specific anti-reverse-engineering techniques that we’ve seen in Pandora:\r\n   -    Function call obfuscation with opaque predicates \r\n   -    Encrypted strings\r\nFirst, let’s discuss what these challenges are exactly.\r\nFunction Call Obfuscation with Opaque Predicates\r\nFigure 1 shows what a simple function call looks like in the Pandora ransomware after it was unpacked.\r\nFigure 1 - Standard function call in Pandora\r\nhttps://www.fortinet.com/blog/threat-research/Using-emulation-against-anti-reverse-engineering-techniques\r\nPage 1 of 12\n\nWe can see that the address of the function that is being called is calculated in runtime. cs:qword_7FF6B6FF9AB8\r\nseems to be the base address of some kind of function address table. Then we use hardcoded values to find the\r\nright function pointer in that table and that is what we load into rax before calling it. An opaque predicate\r\ngenerally means an expression in the program whose outcome is known to the programmer, but it still needs to be\r\nevaluated in runtime. It is used in many different ways as an obfuscation and anti-analysis technique. In this case,\r\nthe value that goes in rax is fixed, but because it still has to be calculated in runtime it disrupts static-analysis\r\ntools.\r\nIf we use Figure 1 as an example, the address in rax is calculated like this:\r\nrax = *(*address_table_base + 0x260BB2E4) + 0xFFFFFFFFAAF7CABC)\r\nor in decimal:\r\nrax = *(*address_table_base + 638300900) - 1426601284)\r\nThe trivial solution for such a problem is to run the malware in a debugger and get the address from there. But in\r\nthis sample all function calls were like this (except those in the statically linked libraries). That means that we\r\nwould have needed to break at every function call in the debugger just to have the slightest idea of what is\r\nhappening in the malware. This called for automation.\r\nEncrypted Strings\r\nAnother challenge of this particular ransomware sample was that all the interesting strings were encrypted. There\r\nwere a lot of plain text strings in the binary (shown in Figure 2), but they were mostly Windows API function\r\nnames and the strings in the embedded libraries. None of the strings that would help us understand what the\r\nmalware is doing are available in plain text. This is very common in modern malware, thus the solution to this\r\nchallenge could be used against a wide variety of malware.\r\nFigure 2 - Strings in the Pandora sample\r\nUsually when one encounters malware with encrypted strings, there are two approaches:\r\nhttps://www.fortinet.com/blog/threat-research/Using-emulation-against-anti-reverse-engineering-techniques\r\nPage 2 of 12\n\n-        Use a dynamic approach, such as debugging or emulation, and use the malware’s own string decryption\r\nfunctions to do the work.\r\n-        Understand the decryption function in such detail that one can reimplement it in a simple script. This is\r\nusually the way to go when the encryption is a simple one-byte XOR.\r\nIn the case of Pandora, there is not one, but at least 14 different string decryption functions, so reimplementing the\r\ndecryption algorithm might not always be feasible.\r\nEmulation\r\nEmulation allows us to pretend that the code is running on a CPU, but instead of running on a real CPU, the\r\nemulation software runs the code. Emulation is usually very slow compared to real execution. However, it allows\r\nus full control over what we want to run and a very high level of interaction with the emulated code. With an\r\nemulator, for instance, we can emulate just one function of the malware (or even just a couple of lines of code)\r\nand evaluate the state of the program at every instruction. A big advantage of emulation in this case is that we can\r\ndo it directly in IDA Pro.\r\nflare-emu\r\nflare-emu is an emulation framework created by the FLARE team at Mandiant. It builds on the well-known\r\nemulation engine called Unicorn Engine and IDAPython. One could use the Unicorn Engine directly but flare-emu hides some of its complexities. Essentially, one can define what (which part of the code) one wants to\r\nemulate and define callback functions for specific hooks that will be called when the emulation reaches that hook.\r\nA good example is the callHook parameter, which accepts a callback function that will be called every time a\r\nCALL instruction is about to be emulated. In this callback function we can implement whatever we want to do in\r\nthat situation, i.e., dump registers, change data, skip call, etc. flare-emu turned out to be very straightforward and\r\nrelatively easy to use (which does not mean that I did not need to look into its source code to figure out a few\r\nthings).\r\nSolving the Challenges\r\nAfter this extensive introduction, let’s finally write some code to solve these challenges with an IDAPython script.\r\nFunction Call Obfuscation\r\nFigure 3 shows again the problem we are trying to solve first. This is the first function call in the main()function in\r\nthe unpacked part of Pandora’s code. At this point we can be fairly sure that if we emulate the main()function and\r\ncheck rax’s value before it is called then we get the right result. When we are already there, we could read out the\r\narguments of the function call as well and add all this information to the assembly code as a comment in IDA Pro.\r\nhttps://www.fortinet.com/blog/threat-research/Using-emulation-against-anti-reverse-engineering-techniques\r\nPage 3 of 12\n\nFigure 3 - Function call obfuscation\r\nLet’s start to put together our IDAPython script. Figure 4 shows how we can initialize the emulation. When we\r\nlaunch the script, it should emulate the function in which the cursor stands at the moment in IDA (returned by\r\nget_screen_ea()).\r\nFigure 4 - Initialize the emulation\r\nTo initialize flare-emu, we just need to instantiate an EmuHelper. Flare-emu offers different ways to run our\r\nemulation. We use the emulateRange() function, which is used to specify a memory range we want to emulate. We\r\nset the start address to the beginning of the function and the end address can be omitted (None in python) which\r\nmeans that the emulation will run until a return type instruction is reached. Note that the iterateAllPaths() instead\r\nof the emulateRange() should also work, however that caused problems due to another obfuscation technique in\r\nPandora, which is not in the scope of this post. But in a less complex malware iterateAllPaths()could be a better\r\noption.\r\nWhen one of flare-emu’s emulation functions is called (emulateRange()in this case), then the emulation starts. The\r\nframework allows us to provide additional details for the emulation, such as processor state with registers and\r\nstack, or data for the callback functions, but we don’t need those at this point.\r\nemulateRange()allows us to define callback functions for different hooks:\r\n-        instruction hook: called before the emulation of every instruction. I used this to color every instruction that\r\nwas emulated in IDA to visualize the coverage of the emulation.\r\n-        call hook: called whenever a CALL type of instruction will be emulated. Note that the called function is not\r\nemulated by default.\r\n-        memory access hook: called whenever memory is accessed for reading or writing.\r\nhttps://www.fortinet.com/blog/threat-research/Using-emulation-against-anti-reverse-engineering-techniques\r\nPage 4 of 12\n\nFor our current task we only need the callHook. As you can see in line 9 in Figure 4, we already passed the\r\ncall_hook function name as the callHook parameter (might not be the cleverest naming). Next, we need to define\r\nthe callHook function, which can be seen in Figure 5.\r\nFigure 5 - First implementation of call_hook ()\r\nWe created the call_hook() function, that will be called by the emulator every time before a CALL instruction is\r\nemulated. In its current state, this function will log that it was executed, then uses the analysisHelper to check\r\nwhether the operand in the current CALL instruction is a register or not. If not, then we can return because only\r\nthe register case is interesting for us. Then we recover the register’s name (operand_name) and its value\r\n(operand_name) and log them for now. If we run the script against the main function, then we get the results in\r\nFigure 6. Note that due to the numerous other evil obfuscations in the Pandora code, this simple script won’t be\r\nable to emulate the whole function. But this can be done by extending the script.\r\nFigure 6 - Results of the first test\r\nhttps://www.fortinet.com/blog/threat-research/Using-emulation-against-anti-reverse-engineering-techniques\r\nPage 5 of 12\n\nThe emulation found three CALL instructions and printed the values of the operand registers. If we think about it,\r\nwe have mostly solved the problem of function call obfuscation, because we now know which addresses are called\r\nby the different CALL instructions. Now we just need to add this to the disassembly in IDA. These are the things\r\nwe want to do in IDA whenever we resolve a CALL instruction:\r\n-        Add a comment with the address of the function that is being called\r\n-        Add a comment with the arguments for that function call\r\n-        Add a cross-reference in IDA to the function that is called\r\nFigure 7 shows the updated code.\r\nFigure 7 - Adding comment and cross-reference\r\nWhen creating the comment we use a nice feature from flare-emu. It allows us to get the function parameters in an\r\narchitecture-independent way. This malware is x86_64 so we could just take rcx, rdx, r8, r9, and the stack, but this\r\nway we don’t have to deal with that. One of the arguments the call hook gets is the arguments variable and this\r\nhttps://www.fortinet.com/blog/threat-research/Using-emulation-against-anti-reverse-engineering-techniques\r\nPage 6 of 12\n\nwill contain the values flare-emu thinks are the parameters of this function call. Of course, without analyzing the\r\ncalled function, we won’t know how many parameters are expected so we will just print all of them.\r\nAt the end (line 23) we add an IDA cross-reference, which will be a great help further along in our analysis. If we\r\nrun this code again on the main function, we get the result in Figure 8.\r\nFigure 8 - Results of the function call resolution\r\nEncrypted String\r\nNow that we have the first problem out of the way, and have an emulation framework to work with, we can move\r\nonto our second challenge, decrypting the strings. To be able to know which function to emulate to get the strings\r\ndecrypted, our only requirement is that we need to know which functions are decryption functions. As always,\r\nreverse engineering is an iterative process. Once we run the script we wrote on the main function, then we can\r\nstart to analyze the called functions. So how do we figure out if a function is a decryption function? (Spoiler:\r\n0x7ff6b6f971e0 points to a decryption function.)\r\nA)    We see it in IDA. Without diving deep in the function at 0x7ff6b6f971e0, we can see in the graph view\r\n(Figure 10) that it is fairly simple and has some loops.\r\nFigure 9. - Graph view at the function at 0x7ff6b6f971e0\r\nIf we scroll through the code, we find the basic block in Figure 10, where we see that it iterates through some\r\nvalue and XORs it. This indicates that it might be an XOR-based encoding/encryption.\r\nhttps://www.fortinet.com/blog/threat-research/Using-emulation-against-anti-reverse-engineering-techniques\r\nPage 7 of 12\n\nFigure 10 - XOR indicates decoding/decryption\r\nB)    We see it in a debugger. Parallel to our static analysis, we of course can also debug the malware (in a safe\r\nenvironment). In the debugger when we see a function that gets some addresses as an input and returns a string,\r\nthat could mean that it is a decryption function. Figure 11 shows when the function at 0x7ff6b6f971e0 returns and\r\nindeed it returns the string “ThisIsMutexa” in rcx.\r\nFigure 11 - The decrypted string appears in rcx\r\nOnce we know that a function is a decryption function, we can rename it accordingly (we used mw_decrypt_str()).\r\nInterestingly, Pandora uses multiple decryption functions, which we discovered slowly as we dived deeper into the\r\ncode. At the end we identified 14 different decryption functions, however most of them looked very similar to\r\nFigure 9, which allowed us to quickly see if a function is just another decryption function.\r\nOnce we know (some) of the decryption functions, we can improve our IDAPython script to emulate the function\r\ncall whenever we see that a decryption function is called. This is actually very similar to one of the examples in\r\nflare-emu’s documentation, which shows how well such code can often be reused.\r\nFigure 12 shows the updated call_hook() function. Starting from line 23, we first check whether the function at the\r\naddress we are calling has a name that contains the string mw_decrypt_str. This is how we decide whether the\r\ncalled function is a decryption function. This is not a very scientific method, but we want to reverse malware, not\r\nget a PhD.\r\nhttps://www.fortinet.com/blog/threat-research/Using-emulation-against-anti-reverse-engineering-techniques\r\nPage 8 of 12\n\nFigure 12 - Adding decryption to the call_hook()\r\nIf it is a decryption function, then we call the decrypt()function in our script. This will return the decrypted plain\r\ntext string. Then we create a comment that will include the decrypted string as well.\r\nHow the decryption is emulated can be seen in Figure 13. We create a new EmuHelper instance and when starting\r\nemulateRange, we use the function name (fname) to get the function’s address as the start address. We also pass\r\nthe first four elements of the argv array as the argument registers. At the end we return the value in argv[0], which\r\nshould contain the address of the decrypted string.\r\nFigure 13 - Emulating the decryption\r\nAfter running the script in IDA, the results are shown in Figure 12. The decrypted string was ThisIsMutexa, which\r\nwas added to the comment and logged in the output.\r\nhttps://www.fortinet.com/blog/threat-research/Using-emulation-against-anti-reverse-engineering-techniques\r\nPage 9 of 12\n\nFigure 14 - String decryption is successful\r\nNow we can automatically decrypt strings. As we progress with the analysis of the code and more decryption\r\nfunctions are discovered, we can just rerun the script on the functions that call these decryption functions to\r\nrecover the plain text strings.\r\nConclusion\r\nThe Pandora ransomware is packed with obfuscation and anti-reverse-engineering techniques. In this post we\r\nlooked at two of these: function call obfuscation and string encryption. We used the flare-emu emulation\r\nframework to write an IDAPython script to resolve the addresses and arguments of the function calls as well as\r\nemulate the decryption functions to recover the strings as plain text. The final script can be developed further to\r\ndeal with the other anti-reverse-engineering challenges discussed in the deep dive analysis of the Pandora\r\nransomware.\r\nFortinet Protection\r\nThe analyzed Pandora ransomware sample is detected by the following (AV) signature:\r\nW64/Filecoder.EGYTYFD!tr.ransom\r\nFortiEDR also detects and mitigates execution of Pandora ransomware through the combination of behavioral\r\nanalysis, and integration with machine learning and threat intelligence feeds. Execution of the Pandora sample\r\nanalyzed as part of this blog triggers seven rules resulting in nine security events. Triggered rules were a result of\r\npre-execution analysis and post-execution behaviors. These security events can be observed below in Figure 15.\r\nhttps://www.fortinet.com/blog/threat-research/Using-emulation-against-anti-reverse-engineering-techniques\r\nPage 10 of 12\n\nFigure 15 - FortiEDR security events generated following execution of Pandora ransomware sample. Note that\r\nduring this execution, FortiEDR was set to only log events rather than mitigate, to properly demonstrate detections\r\npost-execution.\r\nPre-execution detections included: identifying the malicious file (hash based), detection of a suspicious packer,\r\nand presence of writeable code. Post-execution detections included: detection of each file encryption attempt,\r\ndetection of encrypted file rename attempt, dropping of the ransom note, and attempts to access SMB shares.\r\nIn Protect mode, FortiEDR will detect and mitigate detected behavior. In the case of Pandora, this will prevent\r\nexecution of the ransomware, mitigating malicious activity before it occurs, and will prevent subsequent file\r\nencryption attempts if the adversary is able to execute the sample. The post-exploitation detections are not\r\ndependent on signature, meaning they will effectively mitigate this activity for newer Pandora variants even with\r\nno prior knowledge of the samples.\r\nIOCs\r\nMutex: ThisIsMutexa\r\nRansom note: Restore_My_Files.txt\r\nSHA256 hash of hardcoded public key:\r\n7b2c21eea03a370737d2fe7c108a3ed822be848cce07da2ddc66a30bc558af6b\r\nSHA256 hash of sample: 5b56c5d86347e164c6e571c86dbf5b1535eae6b979fede6ed66b01e79ea33b7b\r\nATT\u0026CK TTPs\r\nTTP Name TTP ID Description\r\nhttps://www.fortinet.com/blog/threat-research/Using-emulation-against-anti-reverse-engineering-techniques\r\nPage 11 of 12\n\nObfuscated Files or Information: Software\r\nPacking\r\nT1027.002 Modified UPX packer\r\nImpair Defenses: Disable Windows Event\r\nLogging\r\nT1562.002 Disable event logging\r\nImpair Defenses: Disable or Modify Tools T1562.001 Bypass AMSI\r\nData from Local System T1005 Searches unmounted drives and partitions\r\nModify Registry T1112 Cryptographic keys are stored in the registry\r\nData Encrypted for Impact T1486 As a ransomware it encrypts files\r\nCommand and Scripting Interpreter T1059 Uses cmd.exe to remove the shadow copies\r\nSystem Information Discovery T1082\r\nCollects system information with\r\nGetSystemInfo()\r\nFile and Directory Discovery T1083 Discovers drives and enumerates filesystems\r\nInhibit System Recovery T1490 Deletes shadow copies\r\nService Stop T1489 Terminates processes if they lock a file\r\nLearn more about Fortinet’s FortiGuard Labs threat research and intelligence organization and the FortiGuard\r\nSecurity Subscriptions and Services portfolio.\r\nSource: https://www.fortinet.com/blog/threat-research/Using-emulation-against-anti-reverse-engineering-techniques\r\nhttps://www.fortinet.com/blog/threat-research/Using-emulation-against-anti-reverse-engineering-techniques\r\nPage 12 of 12",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia",
		"ETDA"
	],
	"references": [
		"https://www.fortinet.com/blog/threat-research/Using-emulation-against-anti-reverse-engineering-techniques"
	],
	"report_names": [
		"Using-emulation-against-anti-reverse-engineering-techniques"
	],
	"threat_actors": [],
	"ts_created_at": 1775434453,
	"ts_updated_at": 1775791204,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/9521411a20e3d5d7f795c7f99da47ae54a346503.pdf",
		"text": "https://archive.orkl.eu/9521411a20e3d5d7f795c7f99da47ae54a346503.txt",
		"img": "https://archive.orkl.eu/9521411a20e3d5d7f795c7f99da47ae54a346503.jpg"
	}
}