{
	"id": "1a4c889a-d572-4cd9-b98a-4190de0a7a32",
	"created_at": "2026-04-06T01:30:53.019761Z",
	"updated_at": "2026-04-10T03:21:33.401452Z",
	"deleted_at": null,
	"sha1_hash": "2c6986202e0901bb10e745893cf22e5a34c5fbc6",
	"title": "Automation in Reverse Engineering: String Decryption",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 80569,
	"plain_text": "Automation in Reverse Engineering: String Decryption\r\nBy Tim Blazytko\r\nPublished: 2021-06-29 · Archived: 2026-04-06 00:25:19 UTC\r\nAutomation plays a crucial rule in reverse engineering, no matter whether we search for vulnerabilities in\r\nsoftware, analyze malware or remove obfuscated layers from code. Once we manually identify repeating patterns,\r\nwe try to automate the process as far as possible. For automation, it often doesn’t matter if you use Binary Ninja,\r\nIDA Pro or Ghidra, as long as you have the knowledge how to realize it in your tool of choice. As you will see,\r\nyou don’t have to be an expert to automate tedious reverse engineering tasks; sometimes it just takes a few lines of\r\ncode to improve your understanding a lot.\r\nToday, we take a closer look at this process and automate the decryption of strings for a malware sample from the\r\nMirai botnet. Mirai is a malware family that hijacks embedded systems such as IP cameras or home routers by\r\nscanning for devices that accept default login credentials. To impede analysis, Mirai samples store those\r\ncredentials in an encoded form and decode them at runtime using a simple XOR with a constant. In the following,\r\nwe first manually analyze the string obfuscation. Afterward, we use Binary Ninja’s high-level intermediate\r\nlanguage (HLIL) API to get all string references and decrypt them.\r\nIf you would like to try it on your own, you’ll find the code and the used malware sample on GitHub. To better\r\nunderstand Mirai, you can also have a look at its leaked source code.\r\nManual Analysis\r\nIn static malware analysis, one of the first things to do is to have a closer look at the identified strings, since they\r\noften reveal a lot of context. In this sample, however, we mostly see strings like PMMV , CFOKL , QWRRMPV and\r\nothers. At first glance, they don’t make much sense. However, if we have a closer look at how they are used in the\r\ncode, we notice something interesting: They are repeatedly used as function parameters for the function\r\nsub_10778 . (The corresponding function calls can be found here in the leaked source code.)\r\nsub_10778(\"PMMV\", \u0026data_1616c, 0xa)\r\nsub_10778(\"PMMV\", \"TKXZT\", 9)\r\nsub_10778(\"PMMV\", \"CFOKL\", 8)\r\nsub_10778(\"CFOKL\", \"CFOKL\", 7)\r\nsub_10778(\"PMMV\", \u0026data_16184, 6)\r\nsub_10778(\"PMMV\", \"ZOJFKRA\", 5)\r\nsub_10778(\"PMMV\", \"FGDCWNV\", 5)\r\nsub_10778(\"PMMV\", 0x1619c, 5) {\"HWCLVGAJ\"}\r\nsub_10778(\"PMMV\", \u0026data_161a8, 5)\r\nsub_10778(\"PMMV\", \u0026data_161b0, 5)\r\nsub_10778(\"QWRRMPV\", \"QWRRMPV\", 5)\r\nhttps://synthesis.to/2021/06/30/automating_string_decryption.html\r\nPage 1 of 5\n\nBased on this, we can assume that the passed strings are decoded and further processed in the called function. If\r\nwe inspect the decompiled code of the function, we identify the following snippet that operates on the first\r\nfunction parameter arg1 . For the second parameter arg2 , we can find a similar snippet.\r\nuint32_t r0_3 = sub_12c90(arg1)\r\nvoid* r0_5 = sub_14100(r0_3 + 1)\r\nsub_12d0c(r0_5, arg1, r0_3 + 1)\r\nif (r0_3 s\u003e 0) {\r\n char* r2_3 = nullptr\r\n do {\r\n *(r2_3 + r0_5) = *(r2_3 + r0_5) ^ 0x22\r\n r2_3 = \u0026r2_3[1]\r\n } while (r0_3 != r2_3)\r\n}\r\nThe code first performs some function calls using arg1 , goes into a loop and increments a counter until the\r\ncondition r0_3 != r2_3 no longer holds. Within the loop, we notice an XOR operation *(r2_3 + r0_5) ^\r\n0x22 , where *(r2_3 + r0_5) seems to be an array-like memory access that is xored with the constant 0x22 .\r\nAfter performing a deeper analysis, we can clean up the code by assigning some reasonable variable and function\r\nnames.\r\nuint32_t length = strlen(arg1)\r\nvoid* ptr = malloc(length + 1)\r\nstrcpy(ptr, arg1, length + 1)\r\nif (length s\u003e 0) {\r\n char* index = nullptr\r\n do {\r\n *(index + ptr) = *(index + ptr) ^ 0x22\r\n index = \u0026index[1]\r\n } while (length != index)\r\n}\r\nNow, we have a better understanding of what the code does: It first calculates the length of the provided string,\r\nallocates memory for a new string and copies the encrypted string into the allocated buffer. Afterward, it walks\r\nover the copied string and decrypts it bytewise by xoring each byte with 0x22 . This is also in line with the\r\ndecryption routine of the original source code.\r\nIn other words, strings are encoded using a bytewise XOR with the constant value 0x22 . If we want to decode\r\nthe string PMMV in Python, we can do this with the following one-liner.\r\n\u003e\u003e\u003e ''.join([chr(ord(c) ^ 0x22) for c in \"PMMV\"])\r\n'root'\r\nhttps://synthesis.to/2021/06/30/automating_string_decryption.html\r\nPage 2 of 5\n\nWe walk over each byte of the string, get its corresponding ASCII value via ord , xor it with 0x22 and\r\ntransform it back into a character using chr . In a final step, we join all characters into a single string.\r\nAfter we manually analyzed how strings can be decrypted, we will now automate this with Binary Ninja.\r\nAutomated Decryption\r\nTo automate the decryption, we first have to find a way to identify all encoded strings. In particular, we have to\r\nknow where they start and where they end; in other words, we aim to identify all encrypted bytes. In the second\r\nstep, we can decrypt each byte individually.\r\nBeforehand, we noticed that the encoded strings are passed as the first two parameters to the function sub_10778 .\r\nTo obtain the encoded strings, we can exploit this characteristic by searching for all function calls and parse all\r\npassed parameters. Using Binary Ninja’s high-level intermediate language (HLIL) API, we can realize this within\r\na few lines of code.\r\n# get function instance of target function\r\ntarget_function = bv.get_function_at(0x10778)\r\n# set of already decrypted bytes\r\nalready_decrypted = set()\r\n# 1: walk over all callers\r\nfor caller_function in set(target_function.callers):\r\n \r\n # 2: walk over high-level IL instructions\r\n for instruction in caller_function.hlil.instructions:\r\n \r\n # 3: if IL instruction is a call\r\n # and call goes to target function\r\n if (instruction.operation == HighLevelILOperation.HLIL_CALL and\r\n instruction.dest.constant == target_function.start):\r\n \r\n # 4: fetch pointer to encrypted strings\r\n p1 = instruction.params[0]\r\n p2 = instruction.params[1]\r\n \r\n # 5: decrypt strings\r\n decrypt(p1.value.value, already_decrypted)\r\n decrypt(p2.value.value, already_decrypted)\r\nAfter fetching the function object of the targeted function sub_10778 , we walk over all functions calling\r\nsub_10778 . For each of these calling functions (referred to as callers), we need to identify the instruction that\r\nperforms the call to sub_10778 . In order to do this, we walk over the caller’s HLIL instructions; for each\r\ninstruction, we then check if its operation is a call and if the call destination is the targeted function. If so, we\r\naccess its first two parameters (the pointers to the encoded strings) and pass them to the decryption function. Since\r\nhttps://synthesis.to/2021/06/30/automating_string_decryption.html\r\nPage 3 of 5\n\nsome strings—such as PMMV —are used as parameters multiple times, we ensure that we only decrypt them once.\r\nTherefore, we collect the addresses of all bytes that we already have decrypted in a set called\r\nalready_decrypted .\r\nUp until now, we identified all parameters that flow into the decryption routine. The only thing left to do is to\r\nidentify all encrypted bytes and decrypt them. Since each parameter is a pointer to a string, we can consider it as\r\nthe string’s start address. Similarly, we can determine the string’s end by scanning for terminating null bytes.\r\ndef decrypt(address, already_decrypted):\r\n # walk over string bytes until termination\r\n while True:\r\n # read a single byte from database\r\n encrypted_byte = bv.read(address, 1)\r\n # return if null byte or already decrypted\r\n if encrypted_byte == b'\\x00' or address in already_decrypted:\r\n return\r\n # decrypt byte\r\n decrypted_byte = chr(int(encrypted_byte[0]) ^ 0x22)\r\n \r\n # write decrypted byte to database\r\n bv.write(address, decrypted_byte)\r\n # add to set of decrypted addresses\r\n already_decrypted.add(address)\r\n # increment address\r\n address += 1\r\nTaking the string’s start address as input, we sequentially walk over the string until we reach a byte that terminates\r\nthe string or that was already decrypted. For each byte, we then transform it into an integer, xor it with 0x22 ,\r\nencode it as a character and write it back to the database. Afterward, we add the current address to the set\r\nalready_decrypted and increment the address.\r\nFinally, we have all parts together: We walk over all function calls of the string decryption function, parse the\r\nparameters for each call and decrypt all the strings in Binary Ninja’s database. If we put everything into a Python\r\nscript and execute it, the decompiled code from above contains all strings in plain text.\r\nsub_10778(\"root\", \"xc3511\", 0xa)\r\nsub_10778(\"root\", \"vizxv\", 9)\r\nsub_10778(\"root\", \"admin\", 8)\r\nsub_10778(\"admin\", \"admin\", 7)\r\nsub_10778(\"root\", \"888888\", 6)\r\nsub_10778(\"root\", \"xmhdipc\", 5)\r\nhttps://synthesis.to/2021/06/30/automating_string_decryption.html\r\nPage 4 of 5\n\nsub_10778(\"root\", \"default\", 5)\r\nsub_10778(\"root\", 0x1619c, 5) {\"juantech\"}\r\nsub_10778(\"root\", \"123456\", 5)\r\nsub_10778(\"root\", \"54321\", 5)\r\nsub_10778(\"support\", \"support\", 5)\r\nAs a result, the decompilation reveals much more context information. By googling some of the strings, we learn\r\nthat the parameters are username/password tuples of default login credentials.\r\nSetting the Scene\r\nAutomation allows us to spend less time with tedious and repetitive reverse engineering tasks. In this post, I tried\r\nto emphasize the thought process behind automation on the example of decrypting strings in malware. Starting\r\nwith manual analysis, we first pinpointed interesting behavior: encrypted strings used as function parameters.\r\nThen, we put it into context by digging into the function, and learned that the strings are decrypted inside. By\r\nnoticing a recurring pattern—that the function is called several times with different parameters—we developed an\r\nidea of how to automate the decryption. By using Binary Ninja’s decompiler API, we walked over all relevant\r\nfunction calls, parsed the parameters and decrypted the strings. In the end, 20 lines of code sufficed to improve the\r\ndecompilation and achieve a much better understanding of the malware sample.\r\nEven if you are just starting out, I encourage you to get familiar with the API that your tool of choice exposes, and\r\nto automate some of the tedious tasks you encounter during your day-to-day reversing. It is not only fun; reverse\r\nengineering also becomes so much easier.\r\nQuestions and feedback are welcome—feel free to reach out via email or any of the channels linked above.\r\nSource: https://synthesis.to/2021/06/30/automating_string_decryption.html\r\nhttps://synthesis.to/2021/06/30/automating_string_decryption.html\r\nPage 5 of 5",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://synthesis.to/2021/06/30/automating_string_decryption.html"
	],
	"report_names": [
		"automating_string_decryption.html"
	],
	"threat_actors": [],
	"ts_created_at": 1775439053,
	"ts_updated_at": 1775791293,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/2c6986202e0901bb10e745893cf22e5a34c5fbc6.pdf",
		"text": "https://archive.orkl.eu/2c6986202e0901bb10e745893cf22e5a34c5fbc6.txt",
		"img": "https://archive.orkl.eu/2c6986202e0901bb10e745893cf22e5a34c5fbc6.jpg"
	}
}