{
	"id": "ee6bc6c0-81c9-420c-be22-d971da2233e9",
	"created_at": "2026-04-06T00:07:18.01505Z",
	"updated_at": "2026-04-10T03:21:08.987066Z",
	"deleted_at": null,
	"sha1_hash": "ec7be5576dc138cc55a7f52355a717fb40776828",
	"title": "Ghidra script to decrypt a string array in XOR DDoS – Max Kersten",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 104973,
	"plain_text": "Ghidra script to decrypt a string array in XOR DDoS – Max\r\nKersten\r\nArchived: 2026-04-05 20:14:19 UTC\r\nThis article was published on the 25th of July 2021. This article was updated on the 8th of December 2021.\r\nThe XOR DDoS bot, an ELF file for Linux distributions, is used to perform DDoS attacks. This article focuses on\r\na rather small segment of the malware family: the internally used encrypted string array, and its decryption. This\r\narticle will dive into arrays, the decryption loop, the decryption routine, and the creation of a Ghidra script in Java\r\nto automate this process.\r\nTable of contents\r\nThe sample\r\nUsed tooling\r\nArrays in theory\r\nUnderstanding the loop\r\nRemaking the decryption routine\r\nWriting the Ghidra script\r\nConclusion\r\nThe complete script\r\nThe sample\r\nThe sample can be downloaded from VirusBay, Malware Bazaar, or MalShare. The hashes are given below.\r\nMD5: 349456ecaa1380a142f15810a8260378\r\nSHA-1: 02dd15ecdeedefd7a2f82ba0df38703a74489af3\r\nSHA-256: 0f00c2e074c6284c556040012ef23357853ccac4ad1373d1dea683562dc24bca\r\nSize: 625889 bytes\r\nUsed tooling\r\nThe analysis in this article has been done with a self-built version of Ghidra. The used sources date back to the\r\nfirst of June 2021. The used for-loop that is seen later in this article is displayed as a while-loop in earlier Ghidra\r\nversions. Other than that, no significant changes are present between versions. All analysis options have been used\r\nwhen analysing the file.\r\nArrays in theory\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-a-string-array-in-xor-ddos/\r\nPage 1 of 9\n\nArrays, disregarding of the type, are structured the same way. The first element, which resides at index zero,\r\nmarks the start of the array, followed by the other elements if present. To obtain the element for a given index, the\r\nsize of each element is multiplied by the index number, which is then added to the address of the first element.\r\nThe code below provides an example in pseudo code, where T is the element’s type.\r\nlong elementAddress = arrayBase + (sizeof(T) * i);\r\nWhen dealing with strings, the length can be variable, as not all strings have the same length. In some cases,\r\nstrings with a fixed length are used, this uses more memory than is required, but makes it easy to find the element\r\nfor a given index. Alternatively, the string’s length can be calculated using strlen, which is then used instead of the\r\nsizeof function. This requires more CPU cycles, as the string length needs to be calculated, but it uses less\r\nmemory.\r\nUnderstanding the loop\r\nThe given sample contains symbols, which make the analysis easier. The main function is already called main, and\r\ncontains the decryption loop for the string array at 0804d12a. The complete loop’s assembly code is given below.\r\n MOV dword ptr [EBP + local_3c],0x0\r\n JMP LAB_0804d12e\r\n LAB_0804d108\r\n MOV EDX,dword ptr [EBP + local_3c]\r\n MOV EAX,EDX\r\n SHL EAX,0x2\r\n ADD EAX,EDX\r\n SHL EAX,0x2\r\n ADD EAX,daemonname\r\n MOV dword ptr [ESP + local_3dec],0x14\r\n MOV dword ptr [ESP]=\u003elocal_3df0,EAX\r\n CALL encrypt_code\r\n ADD dword ptr [EBP + local_3c],0x1\r\n LAB_0804d12e\r\n CMP dword ptr [EBP + local_3c],0x16\r\n JBE LAB_0804d108\r\nThe loop, like most loops in assembly languages, starts with the initialisation of a variable to store the count in.\r\nThis variable is commonly named i when named by programmers. In this case, the variable is named local_3c by\r\nGhidra. The naming scheme of Ghidra is based on the location. A jump is then made downwards, where the value\r\nof local_3c is compared to 0x16, or 22 in decimal. If the value of local_3c is below or equal, the jump upwards is\r\ntaken. The two labels, LAB_0804d108 and LAB_0804d12e, can be renamed to loop_body and loop_compare\r\nrespectively to increase the readability. The variable local_3c can be renamed to i for further clarification.\r\nTo understand the loop’s body, each instruction will be explained below, in the usual step-by-step manner. At first,\r\nthe value of i, which resides at EBP + i is moved into EDX, after which it is also moved into EAX.\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-a-string-array-in-xor-ddos/\r\nPage 2 of 9\n\nMOV EDX,dword ptr [EBP + i]\r\nMOV EAX,EDX\r\nNext, the value in EAX is shifted left by two bits. A left shift of N equals two to the power of N. In this case, it\r\nmeans that EAX is multiplied by four.\r\nThe value of i is then added to EAX, after which it is multiplied by four again.\r\nThe variable named daemonname refers to the string array, although Ghidra does not recognise the type due to the\r\nfact that it contains the encrypted strings. The base address of the array is then added to EAX.\r\nIn short, EAX contains the array’s base address and the offset based on i.\r\nThe next three instructions push two arguments on the stack, after which the decryption function (named\r\nencrypt_code) is called. The first argument, as they are read from the stack in the reverse order, is equal to EAX,\r\nwhich contains the current element’s address. The second argument is equal to 0x14, or 20 in decimal. As such, it\r\nbecomes apparent that this string array is based on strings with a fixed length, although this does not mean that\r\nevery string is 20 bytes in size. Rather, the length of each string is between 0 and 19, given that each string is\r\nterminated with a null byte.\r\nMOV dword ptr [ESP + local_3dec],0x14\r\nMOV dword ptr [ESP]=\u003elocal_3df0,EAX\r\nCALL encrypt_code\r\nAt last, the value of i is incremented with one.\r\nADD dword ptr [EBP + i],0x1\r\nThe calculation for the offset of the next element can be simplified as follows.\r\nfor (int i = 0; i \u003c= 0x16; i++)\r\n{\r\nint result = i;\r\nresult = result * 4;\r\nresult += i;\r\nresult = result * 4;\r\nSystem.out.println(result);\r\n}\r\nFurther simplified, one can rewrite the code above as follows.\r\nfor (int i = 0; i \u003c= 0x16; i++)\r\n{\r\nint result = ((i * 4) + i) * 4;\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-a-string-array-in-xor-ddos/\r\nPage 3 of 9\n\nSystem.out.println(result);\r\n}\r\nSince multiplication is the same as repeated addition, one can even further simplify the formula. At first, i is\r\nmultiplied by four, after which i is added. This can be simplified by stating that i is multiplied by five. The\r\noutcome of this is multiplied by four. As such, the original value is multiplied by four, after which its multiplied\r\nby five. In total, i is multiplied by (four times five) twenty. The code below shows the simplification in several\r\nsteps.\r\n((i * 4) + i) * 4;\r\n(i * 5) * 4\r\ni * 20\r\nThe likely reason as to why the code looks like this, is the efficiency of the shift instructions, when compared to\r\nthe multiplication instructions. The compiler likely chose to replace a single multiplication with less resource\r\nintensive instructions.\r\nWhen looking at the main function in Ghidra’s decompiler, one can find the string array decryption loop at line\r\n100, 101, and 102. The excerpt is given below. Note that the refactoring of the variables in the assembly code is\r\nreflected in this code.\r\nfor (i = 0; i \u003c 0x17; i = i + 1) {\r\n encrypt_code(daemonname + i * 0x14,0x14);\r\n}\r\nNote that this loop displays 0x17 with regards to the amount of iterations, rather than 0x16. The condition for the\r\nloop is less than, rather than less than or equal, meaning the value needs to be incremented with one.\r\nFully understanding the code, and how it is generated, will be useful when creating a Ghidra script later on.\r\nRemaking the decryption routine\r\nThe decryption function, named encrypt_code, is used to decrypt a given encrypted string with a given length. The\r\nfunction is given below.\r\nbyte * encrypt_code(byte *param_1,int param_2)\r\n{\r\n byte *local_10;\r\n int local_c;\r\n local_10 = param_1;\r\n for (local_c = 0; local_c \u003c param_2; local_c = local_c + 1) {\r\n *local_10 = *local_10 ^ xorkeys[local_c % 0x10];\r\n local_10 = local_10 + 1;\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-a-string-array-in-xor-ddos/\r\nPage 4 of 9\n\n}\r\n return param_1;\r\n}\r\nThe first argument (named param_1) can be renamed into input, whereas the second argument (named param_2)\r\ncan be renamed into length. The variable named local_10 is a copy of the given input, as it points to the same\r\nvalue. As such, it can be renamed into inputCopy. The loop uses local_c as its counter, which can be renamed into\r\ni. The refactored code is given below.\r\nbyte * encrypt_code(byte *input,int length)\r\n{\r\n byte *inputCopy;\r\n int i;\r\n inputCopy = input;\r\n for (i = 0; i \u003c length; i = i + 1) {\r\n *inputCopy = *inputCopy ^ xorkeys[i % 0x10];\r\n inputCopy = inputCopy + 1;\r\n }\r\n return input;\r\n}\r\nThe variable xorkeys is a string, although Ghidra does not recognise it as such. Changing the type, using T as a\r\nhotkey in the disassembly view, will display its content. Alternatively, one can also get the raw value of the bytes\r\ninstead. The key equals BB2FA36AAA9541F0.\r\nWhen rewriting the decryption function in Java, there is one more more thing to take into account. Strings in C\r\nend with a null byte, but when using a byte array to create a string in Java, this byte is to be omitted. As not all\r\nstrings are equal to the predefined length, a check is to be included to break the loop when the null byte is\r\nencountered. When breaking the loop, the bytes that have been decrypted thus far are to be used to create a new\r\nstring. The recreated function is given below.\r\nprivate String decrypt(byte[] input, char[] key) {\r\n byte[] output = new byte[input.length];\r\n for (int i = 0; i \u003c input.length; i++) {\r\n if(input[i] == 0) {\r\n break;\r\n }\r\n output[i] = (byte) (input[i] ^ key[i % 0x10]);\r\n }\r\n return new String(output);\r\n}\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-a-string-array-in-xor-ddos/\r\nPage 5 of 9\n\nNote that the key is passed as an argument to the function, as this will come in useful when creating the script. By\r\npassing the value as an argument to the function, one can keep all variables in a single place within the script.\r\nWriting the Ghidra script\r\nThe script itself uses the decryption function that was created in the previous step. To decrypt the string array,\r\nseveral variables need to be initialised first. The decryption key, as is required by the decryption function, as well\r\nas the location of the array (defined as arrayBase), the size of a single element (defined as elementSize), and the\r\namount of elements of the array (defined as arraySize).\r\nchar[] key = \"BB2FA36AAA9541F0\".toCharArray();\r\nint arrayBase = 0x080cf1c0;\r\nint elementSize = 0x14;\r\nint arraySize = 0x17;\r\nTo get the value of each element, one needs to multiply the loop count with the predefined element size, after\r\nwhich the array’s base address is added. To get the data from the sample, one can use the getBytes function, which\r\nrequires an Address to know where to start reading the bytes from, and an integer to know how many bytes should\r\nbe read. To convert an integer, long, or string to an Address object, one needs to use the toAddr function.\r\nThe obtained bytes are decrypted by the decryption function, along with the decryption key. The result is then\r\nprinted to Ghidra’s console.\r\ntry {\r\n for (int i = 0; i \u003c arraySize; i++) {\r\nint offset = i * elementSize;\r\nint location = arrayBase + offset;\r\nbyte[] input = getBytes(toAddr(location), elementSize);\r\nString decrypted = decrypt(input, key);\r\nprintln(decrypted);\r\n }\r\n} catch (MemoryAccessException e) {\r\n e.printStackTrace();\r\n println(\"\\nA memory access exception occurred, please refer to the stacktrace above for more inform\r\n}\r\nIn the case of an error with the getBytes function, a MemoryAccessException is thrown. The image below shows\r\nthe output of the script once its execution has finished.\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-a-string-array-in-xor-ddos/\r\nPage 6 of 9\n\nConclusion\r\nDecrypting content from a sample provides a lot more insight as to what the sample does, especially because these\r\nstrings are concealed for a reason. In some cases, bots within the same family reuse the encryption key. If the key\r\nchanges, it is easy to replace it in the script, or make use of a dialog in the script that requests the key once\r\nexecuted.\r\nUnderstanding how to easily access variables and memory in a script in Ghidra is helpful when analysing any\r\nsample. Given that some code segments are easy to reuse, it is useful to create scripts that are made up of easily\r\nreusable functions.\r\nThe complete script\r\nThe complete script, including documentation, is given below. Note the hardcoded key, element length, and array\r\nlength. For more information on how to avoid hardcoding values in a script, one can visit the Amadey string\r\ndecryption script article.\r\n//This script is used to decrypt a string array within the XOR DDoS bot. Note that it the array's loc\r\n//@author Max 'Libra' Kersten (https://maxkersten.nl, @Libranalysis)\r\n//@category string array decryption\r\n//@keybinding\r\n//@menupath\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-a-string-array-in-xor-ddos/\r\nPage 7 of 9\n\n//@toolbar\r\nimport ghidra.app.script.GhidraScript;\r\nimport ghidra.program.model.mem.MemoryAccessException;\r\npublic class xorddos_array_decryption extends GhidraScript {\r\n /**\r\n * This function is called by Ghidra, as such it is the entry into the script\r\n */\r\n @Override\r\n protected void run() throws Exception {\r\n //The hardcoded decryption key, works for numerous samples, but this script is based on 0f00c2e07\r\n char[] key = \"BB2FA36AAA9541F0\".toCharArray();\r\n //The location of the array's base address, which resides at index 0\r\n int arrayBase = 0x080cf1c0;\r\n //The size of each element within the array\r\n int elementSize = 0x14;\r\n //The amount of elements in the array\r\n int arraySize = 0x17;\r\n try {\r\n //Since the assembly code uses a Jump Below or Equal instruction to compare i to 0x16, the loop\r\n for (int i = 0; i \u003c arraySize; i++) {\r\n //Declare the offset from the array base (meaning the size of each element times the element\r\n int offset = i * elementSize;\r\n //Declare the location within the program\r\n int location = arrayBase + offset;\r\n //Get the bytes for one element at the given location\r\n byte[] input = getBytes(toAddr(location), elementSize);\r\n //Decrypt the given data using the given key\r\n String decrypted = decrypt(input, key);\r\n //Print the decrypted string\r\n println(decrypted);\r\n }\r\n } catch (MemoryAccessException e) {\r\n //Print the stacktrace, and provide further indication that an error occured\r\n e.printStackTrace();\r\n println(\"\\nA memory access exception occurred, please refer to the stacktrace above for more in\r\n }\r\n }\r\n /**\r\n * Decrypts the given input using the given key and returns a new string with the decrypted content\r\n * @param input the data to decrypt\r\n * @param key the key to decrypt the given input\r\n * @return the decrypted input in the form of a string\r\n */\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-a-string-array-in-xor-ddos/\r\nPage 8 of 9\n\nprivate String decrypt(byte[] input, char[] key) {\r\n //Create a new byte array to store the decrypted data in\r\n byte[] output = new byte[input.length];\r\n //Iterate over all bytes\r\n for (int i = 0; i \u003c input.length; i++) {\r\n //If the byte equals zero, the string is terminated, meaning the loop needs to be broken, which\r\n if(input[i] == 0) {\r\n break;\r\n }\r\n //Decrypt the current byte\r\n output[i] = (byte) (input[i] ^ key[i % 0x10]);\r\n }\r\n //Once the loop breaks, or all iterations have finished, a new string is returned\r\n return new String(output);\r\n }\r\n}\r\nTo contact me, you can e-mail me at [info][at][maxkersten][dot][nl], or DM me on BlueSky @maxkersten.nl.\r\nSource: https://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-a-string-array-in-xor-ddos/\r\nhttps://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-a-string-array-in-xor-ddos/\r\nPage 9 of 9",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://maxkersten.nl/binary-analysis-course/analysis-scripts/ghidra-script-to-decrypt-a-string-array-in-xor-ddos/"
	],
	"report_names": [
		"ghidra-script-to-decrypt-a-string-array-in-xor-ddos"
	],
	"threat_actors": [],
	"ts_created_at": 1775434038,
	"ts_updated_at": 1775791268,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/ec7be5576dc138cc55a7f52355a717fb40776828.pdf",
		"text": "https://archive.orkl.eu/ec7be5576dc138cc55a7f52355a717fb40776828.txt",
		"img": "https://archive.orkl.eu/ec7be5576dc138cc55a7f52355a717fb40776828.jpg"
	}
}