# Reversing Golang Developed Ransomware: SNAKE **[0ffset.net/reverse-engineering/analysing-snake-ransomware/](https://www.0ffset.net/reverse-engineering/analysing-snake-ransomware/)** 3 August 2022 [Gabriele Orini](https://www.0ffset.net/author/greenplan/) 3rd August 2022 No Comments ## Introduction Snake Ransomware (or EKANS Ransomware) is a Golang ransomware which in the past has affected several companies such as Enel and Honda. The MD5 hashing of the analyzed sample is [ED3C05BDE9F0EA0F1321355B03AC42D0. This sample in particular is](https://app.any.run/tasks/ad3d809f-e197-4091-9b93-b6d68368c05b/) [obfuscated with Gobfuscate, an open source obfuscation project available on Github.](https://github.com/unixpickle/gobfuscate) Let’s start by quickly summarizing the functionality of the malware: First, the sample checks the domain to which the infected host belongs to, before continuing execution Next, it checks whether the computer is a Backup Domain Controller or Primary Domain Controller, and if so will only drop the ransom note, rather than encrypting the machine SNAKE will then isolate the host machine from the network by leveraging the netsh tool The shadow copies on the system are deleted using WMI SNAKE attempts to terminate any running AV, EDR, and SIEM components ----- Finally, local files on the system are encrypted For each file, a unique AES encryption key is generated, which is later encrypted with an RSA-2048 public key and stored within the encrypted file. Let’s start reversing this sample with IDA! ## Static Analysis There are some differences of Go from other languages to keep in mind: Functions can return multiple values. Function parameters are passed on the stack. Strings are typically a sequence of bytes with a fixed length, that are not null terminated; the string is represented by a structure formed by a pointer to a byte array, and the length of the string. The constants are stored in one large buffer and sorted by length. Many stack manipulations are present within the binaries, that can make the analysis complex. ### Obfuscation Opening the sample with IDA we immediately notice that the function names are very obfuscated: Gobfuscate obfuscates almost all function names within the binary ----- [Performing a quick search, I found that the malware is obfuscated with Gobfuscate. This](https://github.com/unixpickle/gobfuscate) project performs obfuscation of several components: Global names, Package names, Struct methods, and Strings. Each string in the binary is replaced by a function call. Each function contains two arrays that are xored to get the original string. When implementing the decryption function, keep in mind that there are different ways in which these arrays are passed to the function **runtime.stringtoslicebyte – either via a variable, a pointer or a hardcoded value).** Analyzing the sample, you will usually find the call to a main function that contains a large number of other calls within it, where each subroutine performs decryption of only one string. Sometimes only the decryption operations are found within these subroutines, other times additional operations are performed on the decrypted string. String Decryption Functions String Decryption Functions As mentioned, the string encryption is fairly basic, XORing the contents of two arrays together to retrieve the final string. ----- Loading the two arrays for decryption XORing the arrays together to get the decrypted string Due to the simplicity of the algorithm, we can develop a simple Python script utilising some regular expressions to locate and decrypt 90% of the encrypted strings within the binary! The script can be found at the end of this post. ``` startDecryptFunction = b"\x64\x8B\x0D\x14\x00\x00\x00" sliceStr = b"(" + b"\x8D.....\x89.\$\x04\xC7\x44\$\b...." + b")" xorLoop = b"\x0F\xB6<\)1\xFE(\x83|\x81)\xFD" ``` With the majority of the strings decrypted, let’s continue the analysis! ### Check Environment One of the first operations I perform when analyzing malware in GO is to jump to the **main.init function.** The main.init is generated for each package by the compiler to initialise all other packages that this sample relies on, as well as the global variables; analysing this function is very important because it allows us to understand a large amount of the malware’s functionality and speed up subsequent analysis. ----- In the main.init function we can find, for example, references to encryption: AES, RSA, **SHA1, X509. In addition, there are several functions for decryption of strings and function** names. Initialisations performed within main.init function API function decryption and loading through NewProc Now we move on to analyze the main.main function. One of the first activities the malware performs is to check the environment before continuing with encryption. ----- Call to CheckEnvironment within main.main The function CheckEnvironment starts by attempting to resolve the hostname **mds.honda.com and compare the returned value with 172[.]108[.]71[.]153.This check is** used to confirm that the infected machine is part of the correct domain. In fact, it is important to remember that this ransomware is deployed at the end of the infection chain by other loaders, and thus it is likely custom-built for the victim. Resolving hostname via DNS ----- Comparing resolved address to hardcoded address After the first environment check, the malware executes the API calls CoInitializeEx, **CoInitializeSecurity and CoCreateInstance to instantiate an object of the SWbemLocator** interface. Using the SWbemLocator object, SNAKE then invokes the method **SWbemLocator::ConnectServer and obtains a pointer to an SWbemServices object.** Finally, with this object, it will execute ExecQuery with the following query: ``` select DomainRole from Win32_ComputerSystem ``` In an attempt to determine whether the infected computer is a server or a workstation. Decryption of object used to perform WMI query Decryption of WMI query to retrieve DomainRole After making the query, the malware only continues execution if the DomainRole value is equal to or less than 3. ----- Execution of WMI query and checking of result According to [Microsoft documentation, the integers returned by the call correspond to](https://docs.microsoft.com/en-us/dotnet/api/microsoft.powershell.commands.domainrole?view=powershellsdk-1.1.0) different values: **VALUE** **MEANING** 0 Standalone Workstation 1 Member Workstation 2 Standalone Server 3 Member Server 4 Backup Domain Controller 5 Primary Domain Controller Therefore, the malware performs the infection only if the role obtained of the computer is **Standalone Workstation, Member Workstation, Standalone Server, or Member Server.** If this check is successful, the mutex Global\EKANS is created, and presuming the mutex is created successfully, the sample continues executing. ----- Execution flow depending on the result of the DomainRole If, on the other hand, the computer role is either a backup domain controller or primary domain controller, a ransom note is dropped to C:\Users\Public\Desktop, and files are not encrypted. Within the ransom note is an email on how to contact the threat actors, with the email used in this sample being CarrolBidell@tutanota.com. ----- Ransom note When analyzing Go developed programs, two functions to pay attention to are NewLazyDll (essentially LoadLibrary), and NewProc (as you may have guessed, basically GetProcAddress). With the use of Gobfuscate to obfuscate this sample, the names of the libraries and API functions to be passed to the described functions. Pointers to the loaded libraries/functions are stored within DWORDs for later reference. For example, we can see in the sample we have the function that performs the decryption of a function name before calling LazyDLL.NewProc: API function decryption and loading via NewProc A pointer to the function is saved in a DWORD, so that we can trace to see where the function is called within the binary. ----- Cross references for API functions Execution of previously loaded API function ### Endpoint Isolation After the function CheckEnvironment has finished, the strings “netsh advfirewall set **allprofiles state on” and “netsh advfirewall set allprofiles firewallpolicy** **blockinbound,blockoutbound” are decrypted and executed via cmd.run. The first** command enables Windows Firewall for all network profiles, while the second blocks all incoming and outgoing connections. This is fairly unusual behaviour for ransomware, which typically performs lateral movement across a network to infect additional machines. ----- Decryption of netsh commands to alter the firewall Execution of above commands through os.exec ### Terminate Process and Services Prior to encryption, the ransomware terminates a number of processes, to reduce the amount of interference with the encryption (for example any open file handles), as well as disable any running EDR/SIEM software on the machine. ----- Decryption of target processes to be terminated Processes are terminated using syscall.OpenProcess and syscall.TerminateProcess calls. In order for SNAKE to retrieve the PIDs of the target processes, the usual calls of CreateToolhelp32Snapshot, Process32FirstW, and Process32NextW are performed. Terminating processes with OpenProcess and TerminateProcess ----- In addition to terminating processes, the ransomware stops more than 200 services related to EDR, SIEM, AV, etc. Decryption of target service names to be terminated In order to terminate services, the following API function calls are made: **OpenSCManagerA: gets a service control manager handle for subsequent calls.** **EnumServicesStatusEx: enumeration of services.** **OpenServiceW: gets a service handle for subsequent calls.** **QueryServiceStatusEx: check the status of services.** **ControlService: used to stop the service (flag SERVICE_CONTROL_STOP).** Termination of services using OpenService and ControlService ### Shadow Copy Deletion ----- The ransomware executes the WMI query **SELECT FROM Win32_ShadowCopy to get** the IDs of Shadow Copies and will always use WMI for deletion (remember that there are many ways to perform shadow copy deletion). In addition to the Wbemscripting.SWbemLocator object, **WbemScripting.SWbemNamedValueSet is also created.** For deletion, SNAKE uses the DeleteInstance method by passing the ID of previously obtained Shadow Copies. Decryption of WbemScripting.SWbemNamedValueSet Decryption of WMI Query Decryption of WMI namespace ### Encryption Process SNAKE first encrypts all the various files by initializing 8 go-routines (runtime.newproc), before beginning to rename the files. ----- The offset of the function that does the encryption is passed to runtime.newproc (OffsetStartEncryption). Initialisation of go-routines prior to file renaming function Before beginning encryption of the file, it’s checked that it has not already been encrypted by checking for the presence of the string EKANS at the end of the file. Checking if file is already encrypted If the file hasn’t yet been encrypted and the files are among those to be encrypted (there is an allowlist and a denylist), encryption is initiated, which takes care of: ----- Generating AES key for each file; this key is encrypted with an RSA public key in OAEP Mode. Encryption of file via AES in CTR mode, with Random Key (32 bytes) and Random IV (16 bytes). A random 5 character is appended to the file extension of encrypted files. Adds data to the end of the file: encrypted AES Key, IV and EKANS string. Key generation, encryption, and metadata being added to file After instantiating the CTR cipher with cipher.NewCTR, encryption is performed with the **XORKeyStream method of that class.** The function reads 0x19000 bytes at a time and after encryption the file is rewritten using **WriteAt.** Generating the buffer to hold bytes read from the file ----- Encrypting buffer data and overwriting file After finishing the encryption, three more writes are performed on the file: Adding metadata to the file It’s easy to see that in the last one the string EKANS is written (which is used to determine if the file has already been encrypted), while it is much more complex to figure out what is written in the first two. As a result, let’s jump over to a debugger. Observing metadata being written to end of file using a debugger The first write adds the following to the file: The encrypted AES Key ----- The random IV The path of encrypted file The AES Key for each file is encrypted with a public RSA key. After decryption, the public key is parsed with pem.decode and x509.ParsePKCS1PublicKey. Decryption of RSA public key ----- Parsing of the RSA key The first parameter of the EncryptOAEP function must be the hash function, which in this case is sha1: EncryptOAEP Function ----- Call to EncryptOAEP function Various extensions, file and folders are excluded for file encryption, also using a regex. **Partially excluded Files:** **Iconcache.db** **Ntuser.dat** **Desktop.ini** **Ntuser.ini** **Usrclass.dat** **Usrclass.dat.log1** **Usrclass.dat.log2** **Bootmgr** **Bootnxt** **Ntuser.dat.log1** **Ntuser.dat.log2** **Boot.ini** **ctfmon.exe** **bootsect.bak** **ntdlr** **Partially excluded extensions:** **Exe** **Dll** **Sys** **Mui** **Tmp** **Lnk** **config** **settingcontent-ms** **Tlb** **Olb** **Bfl** **ico** **regtrans-ms** **devicemetadata-ms** **Bat** **Cmd** **Ps1** **Excluded Paths:** **\ProgramData** **\Users\All Users** **\Temp\** **\AppData\** **\Boot** **\Local Settings** ----- **\Recovery** **\Program Files** **\System Volume Information** **\$Recycle.Bin** **.+\\Microsoft\\(User Account Pictures|Windows\\(Explorer|Caches)|Device** And that just about wraps up this post on the SNAKE Ransomware! ### Decryption Script ----- ``` #!/usr/bin/env python3 import re, struct, pefile, sys pe = None imageBase = None def GetRVA(va): return pe.get_offset_from_rva(va - imageBase) def GetVA(raw): return imageBase + pe.get_rva_from_offset(raw) def main(): global pe, imageBase filename = sys.argv[1] with open(filename, 'rb') as sample: data = bytearray(sample.read()) pe = pefile.PE(filename) imageBase = pe.OPTIONAL_HEADER.ImageBase startDecryptFunction = b"\x64\x8B\x0D\x14\x00\x00\x00" sliceStr = b"(" + b"\x8D.....\x89.\$\x04\xC7\x44\$\b...." + b")" xorLoop = b"\x0F\xB6<\)1\xFE(\x83|\x81)\xFD" regex = startDecryptFunction + b".{10,100}" + sliceStr + b".{10,100}" + sliceStr + b".{10,100}" + xorLoop pattern = re.compile(regex, re.MULTILINE|re.DOTALL) found = pattern.finditer(bytes(data)) for m in found: va = GetVA(m.start()) funcVA = GetVA(m.start()) str1VA = struct.unpack("