Here I share notes on what I’m learning form Maldev Academy. This place is (at the moment) a mess of notes from various notetaking applications and approaches. Will consolidate and structure soon.

Module 3 - Required Tools

x64dbg

Some notes on the x64dbg interface.

The main tab (‘CPU’) has 4 screens:

  1. Disassembly (top-left): displays assembly instructions being executed
  2. Dump (bottom-left): displays memory contents
  3. Registers (top-right): displays values of CPU registers
  4. Stack (bottom-right): displays stack contents

Module 4 - Coding Basics

Structures/structs are declared using typedef to give aliases. F.e.:

typedef struct _STRUCTURE_NAME {
 
  // structure elements
 
} STRUCTURE_NAME, *PSTRUCTURE_NAME;
  • STRUCTURE_NAME alias: refers to structure name
  • PSTRUCTURE_NAME alias: represents pointer to structure (P is a Microsoft convention)

Passing By Reference

Passing by reference is a method of passing arguments to a function where the argument is a pointer to the object, rather than a copy of the object’s value.

Module 5 - Windows Architecture

diagram of user and kernel mode

  1. User Processes - A program/application executed by the user such as Notepad, Google Chrome or Microsoft Word.
  2. Subsystem DLLs - DLLs that contain API functions that are called by user processes. An example of this would be kernel32.dll exporting the CreateFile Windows API (WinAPI) function, other common subsystem DLLs are ntldll.dll, advapi32.dll and user32.dll.
  3. Ntdll.dll - A system-wide DLL which is the lowest layer available in user mode. This is a special DLL that creates the transition from user mode to kernel mode. This is often referred to as the Native API or NTAPI.
  4. Executive Kernel - This is what is known as the Windows Kernel and it calls other drivers and modules available within kernel mode to complete tasks. The Windows kernel is partially stored in a file called ntoskrnl.exe under “C:\Windows\System32”.

Function Call Flow

The image below shows an example of an application that creates a file. It begins with the user application calling the CreateFile WinAPI function which is available in kernel32.dll. Kernel32.dll is a critical DLL that exposes applications to the WinAPI and is therefore can be seen loaded by most applications. Next, CreateFile calls its equivalent NTAPI function, NtCreateFile, which is provided through ntdll.dll. Ntdll.dll then executes an assembly sysenter (x86) or syscall (x64) instruction, which transfers execution to kernel mode. The kernel NtCreateFile function is then used which calls kernel drivers and modules to perform the requested task.

image

Module 6 - Windows Memory Management

Writing to memory example

Freeing allocated memory

Module 7 - Introduction to the Windows API

Windows Data Types

Data typefunction
DWORDrepresents values up to 2^32-1 (32-bit uint)
size_trepresents size of an object (32- or 64-bit uint)
VOIDindicates absence of specific data type
PVOIDpointer of any data type (32-bit or 64-bit pointer)
HANDLEspecifies a particular object that the operating system is managing (file, process, thread)
HMODULEhandle to a module (DLL, EXE). base address of module in memory
LPCSTR/PCSTRpointer to a constant null-terminated string of 8-bit Windows characters (ANSI). equivalent to const char*
PWSTR/LPWSTRsame as LPCSTR/PCSTR but does not point to a constant variable but to a readable and writable string (equivalent to wchar*)
wchar_tsame as wchar which is used to represent wide characters
ULONG_PTRrepresents uint that is same size as pointer (32-bit or 64-bit). Used with arithmetic expressions containing pointers.
Data typeexample
DWORDDWORD dwVariable = 42;
size_tSIZE_T = sVariable = sizeof(int);
VOIDvoid* pVariable = NULL; // This is the same as PVOID
PVOIDPVOID pVariable = &SomeData;
HANDLEHANDLE hFile = CreateFile(…);
HMODULEHMODULE hModule = GetModuleHandle(…);
LPCSTR/PCSTRLCPSTR lpcString = ‘Hello, world!’
PCSTR pcwString = L’Hello, world!’
PWSTR/LPWSTRLPWSTR lpwString = L’Hello, world!’
PWSTR pwString = L’Hello, world!‘
wchar_twchar_t wChar = L’A’;
ULONG_PTRPVOID Pointer = malloc(100);
//Pointer = pointer +10; //not allowed
Pointer = (ULONG_PTR)Pointer + 10;
// allowed

Ansi & Unicode Functions

  • CreateFileA ‘A’ indicates ‘ANSI’
    • first parameter for CreateFileA is a LPCSTR
  • CreateFileW ‘W’ indicates ‘Wide’ and represents Unicode
    • first parameter for CreateFileW is LPCWSTR

Tip

To run single-file or sample code without a VS Studio project:

  • Open the ‘Developer Command Prompt for VS 2022’
  • cd to the source code and output folder (or specify the output folder with the flags /Fe<output-dir> and Fo<output-dir>)
  • cl.exe single-file-or-sample-code.c
  • single-file-or-sample-code.exe

Windows API debugging errors

Learning to use the GetLastError function

#include <windows.h>
 
void ErrorExit() 
{ 
    // Retrieve the system error message for the last-error code
 
    LPVOID lpMsgBuf;
    DWORD dw = GetLastError(); 
 
    if (FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0, NULL) == 0) {
        MessageBox(NULL, TEXT("FormatMessage failed"), TEXT("Error"), MB_OK);
        ExitProcess(dw);
    }
 
    MessageBox(NULL, (LPCTSTR)lpMsgBuf, TEXT("Error"), MB_OK);
 
    LocalFree(lpMsgBuf);
    ExitProcess(dw); 
}
 
void main()
{
    // Generate an error
 
    if (!GetProcessId(NULL))
        ErrorExit();
}

Now, to explain how this code does what it does:

FunctionDocumentationApplication
FormatMessage()Requires a message definition as input and formats a message stringIf FormatMessage() receives an incorrect dw (dwMessageId in the function definition), MessageBox() ‘FormatMessage failed’

Module 8

Notepad++

Combining Notepad++ and the Visual Studio Developer Prompt Command Tools I have been able to compile sample/single file code, which is nice for this part of the course.

Build & run

In the future, I would like to incorporate the ‘build and run’ workflow into Notepad++, which is possible with NppExec and the following script:

// 1. Save the current file
NPP_SAVE

// 2. Move to the directory of the current file
CD $(CURRENT_DIRECTORY)

// 3. Setup VS environment, compile, and run
// /EHsc enables standard C++ exception handling
// /Fe: creates an output executable named after your file
cmd /c ""C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\VsDevCmd.bat" && cl.exe /EHsc "$(FILE_NAME)" /Fe:"$(NAME_PART).exe" && "$(NAME_PART).exe""

PE Structure

structure of a Portable Executable

DOS header data structure

typedef struct _IMAGE_DOS_HEADER {
	WORD e_magic;    // Magic number
	WORD e_cblp;     // Bytes on last page of file
	WORD e_cp;       // Pages in file
	WORD e_crcl;     // Relocations
	WORD e_cparhdr;  //Size of header in paragraphs
	WORD e_minalloc; // Minimum extra paragraphs needed
	WORD e_maxalloc; // Maximum extra paragraphs needed
	WORD e_ss;       // Initial (relative) SS value 
	WORD e_sp;       // Initial SP value
	WORD e_csum;     // Checksum
	WORD e_ip;       // Initial IP value
	WORD e_cs;       // Initial (relative) CS value
	WORD e_lfarlc;   // File address of relocation table
	WORD e_ovno;     // Overlay number
	WORD e_res[4];   // Reserved words
	WORD e_oemid;    // OEM identifier (for e_oeminfo)
	WORD e_oeminfo;  // OEM information; e_oemid specific
	WORD e_res2[10]; // Reserved words
	LONG e_lfanew;   // Offset to the NT header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

The most important members of the struct are e_magic and e_lfanew.

e_magic is 2 bytes with a fixed value of 0x5A4D or MZ.

e_lfanew is a 4-byte value that holds an offset to the start of the NT Header. Note that e_lfanew is always located at an offset of 0x3C.

NT Header (IMAGE_NT_HEADERS)

NT Header contains FileHeader and OptionalHeader. NT Header can be reached via the e_lfanew member inside the DOS Header.

typedef struct _IMAGE_NT_HEADERS {
	DWORD                      Signature;
	IMAGE_FILE_HEADER          FileHeader;
	IMAGE_OPTIONAL_HEADER32/64 OptionalHeader;
} IMAGE_NT_HEADERS32/64, *PIMAGE_NT_HEADERS32/64;

File Header (IMAGE_FILE_HEADER)

typedef struct _IMAGE_FILE_HEADER {
	WORD Machine;
	WORD NumberofSections;
	DWORD TimeDateStamp;
	DWORD PointerToSymbolTable;
	DWORD NumberOfSymbols;
	WORD SizeOfOptionalHeader;
	WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_OF_FILE_HEADER;

NumberOfSections = number of sections in the PE file. Characteristics = Flags that specify certain attributes about the executable file, such as whether it’s a dynamic-link library (DLL) or a console application. SizeOfOptionalHeader = The size of the following optional header.

Optional header (IMAGE_OPTIONAL_HEADER)

32-bit-version:

typedef struct _IMAGE_OPTIONAL_HEADER {
  WORD                 Magic;
  BYTE                 MajorLinkerVersion;
  BYTE                 MinorLinkerVersion;
  DWORD                SizeOfCode;
  DWORD                SizeOfInitializedData;
  DWORD                SizeOfUninitializedData;
  DWORD                AddressOfEntryPoint;
  DWORD                BaseOfCode;
  DWORD                BaseOfData;
  DWORD                ImageBase;
  DWORD                SectionAlignment;
  DWORD                FileAlignment;
  WORD                 MajorOperatingSystemVersion;
  WORD                 MinorOperatingSystemVersion;
  WORD                 MajorImageVersion;
  WORD                 MinorImageVersion;
  WORD                 MajorSubsystemVersion;
  WORD                 MinorSubsystemVersion;
  DWORD                Win32VersionValue;
  DWORD                SizeOfImage;
  DWORD                SizeOfHeaders;
  DWORD                CheckSum;
  WORD                 Subsystem;
  WORD                 DllCharacteristics;
  DWORD                SizeOfStackReserve;
  DWORD                SizeOfStackCommit;
  DWORD                SizeOfHeapReserve;
  DWORD                SizeOfHeapCommit;
  DWORD                LoaderFlags;
  DWORD                NumberOfRvaAndSizes;
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

64-bit version

typedef struct _IMAGE_OPTIONAL_HEADER64 {
  WORD                 Magic;
  BYTE                 MajorLinkerVersion;
  BYTE                 MinorLinkerVersion;
  DWORD                SizeOfCode;
  DWORD                SizeOfInitializedData;
  DWORD                SizeOfUninitializedData;
  DWORD                AddressOfEntryPoint;
  DWORD                BaseOfCode;
  ULONGLONG            ImageBase;
  DWORD                SectionAlignment;
  DWORD                FileAlignment;
  WORD                 MajorOperatingSystemVersion;
  WORD                 MinorOperatingSystemVersion;
  WORD                 MajorImageVersion;
  WORD                 MinorImageVersion;
  WORD                 MajorSubsystemVersion;
  WORD                 MinorSubsystemVersion;
  DWORD                Win32VersionValue;
  DWORD                SizeOfImage;
  DWORD                SizeOfHeaders;
  DWORD                CheckSum;
  WORD                 Subsystem;
  WORD                 DllCharacteristics;
  ULONGLONG            SizeOfStackReserve;
  ULONGLONG            SizeOfStackCommit;
  ULONGLONG            SizeOfHeapReserve;
  ULONGLONG            SizeOfHeapCommit;
  DWORD                LoaderFlags;
  DWORD                NumberOfRvaAndSizes;
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;

Important struct members of _IMAGE_OPTIONAL_HEADER

Magic - Describes the state of the image file (32- or 64-bit image) MajorOperatingSystemVersion - The major version number of the required operating system MinorOperatingSystemVersion - The minor version number of the required operating system SizeOfCode - The size of the .text section AddressOfEntryPoint - Offset to the entry point of the file (Typically the main function) BaseOfCode - Offset to the start of the .text section SizeOfImage - The size of the image file in bytes ImageBase - It specifies the preferred address at which the application is to be loaded into memory when it is executed. DataDirectory - Array of IMAGE_DATA_DIRECTORY which contains the directories in a PE files

Data Directory

Array of data type IMAGE_DATA_DIRECTORY with the following data structure (below). Data Directory array is of size IMAGE_NUMBEROF_DIRECTORY_ENTRIES, constant value of 16.

typedef struct _IMAGE_DATA_DIRECTORY {
	DWORD VirtualAddress;
	DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

Each element of the array represents a data directory containing data about a PE section or Data Table. SPecific data directories can be indexed:

#define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor

Export Directory

Contains addresses of exported functions and variables which can be used by other executable files to access the functions and data. Generally found in DLLs that export functions (like kernel32.dll exporting CreateFileA).

Import Address Table

Contains information about the addresses of functions imported from other executable files. Addresses are used to access the functions and data in other executables (like Application.exe importing CreateFileA from kernel32.dll.)

PE Sections

contains code and data used to create an executable program. PE secions have a unique name and contains (1) executable code, (2) data, or (3) resource information. Sections can be added later manually, the IMAGE_FILE_HEADER.NumberOfSections determine that number.

Important PE sections:

.text - executable code which is the written code .data - initialized data which are variables initialized in the code .rdata - read-only data which are constant variables prefixed with const .idata - contains import tables which relate to functions called using the code .reloc - information how to fix up memory addresses so the program can be loaded .rsrc - store resources like icons and bitmaps

Each PE section has an IMAGE_SECTION_HEADER data structure, saved under the NT headers and are stacked above each other, representing a section. IMAGE_SECTION_HEADERcontains:

typedef STRUCT _IMAGE_SECTION_HEADER {
  BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // name of the section (.text, .data, .rdata)
  union {
    DWORD PhysicalAddress;            // size of the section in memory
    DWORD VirtualSize;
  } Misc;
  DWORD VirtualAddress;               // offset of start of section in memory
  DWORD SizeOfRawData;
  DWORD PointerToRawData;
  DWORD PointerToRelocations;
  DWORD PointerToLinenumbers;
  WORD NumberOfRelocations;
  WORD NumberOfLinenumbers;
  DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

'NT_STATUS' errors:

I haven’t been able to get rid of the error ‘unresolved external symbol NT_ERROR referenced in function …’ until I manually defined NT_ERROR. Specifying /link kernel32.lib and #include <ntdef.h> or <ntstatus.h> haven’t worked as well. Moving on for now.

Objectives

Open any EXE file with PE-Bear, click ‘DOS Header’ and locate the bytes 0x4D and 0x5A

Find the value of ‘e_lfanew’ (Hint: Look for the offset 0x3C)

Under the ‘File Hdr’ tab in PE-Bear, look at the value of ‘Sections Count’. Verify that number with the number of sections in the ‘Sections Hdrs’ tab.

‘File Hdr’ ‘Sections Count’: 6 ‘Sections Hdrs’:

View the imported DLLs and Windows APIs under the ‘Imports’ tab

Module 9 - Dynamic-Link Library

Unlike EXE files, DLL files cannot execute code on their own: DLL libraries need to be invoked by other programs to execute the code.

Automatically loaded DLLs:

  • ntdll.dll
  • kernel32.dll
  • kernelbase.dll

Windows uses a system-wide DLL base address to load DLLs at the same base address in the virtual address space of all space of all running processes (f.e. kernel32.dll).

  • There are 4 possibilities for a DLL entry point to be called:
    • DLL_PROCESS_ATTACH - A process is loading the DLL.
    • DLL_THREAD_ATTACH - A process is creating a new thread.
    • DLL_THREAD_DETACH - A thread exits normally.
    • DLL_PROCESS_DETACH - A process unloads the DLL.

Code & findings

Mental note:

You can compile a DLL with cl.exe /LD, optionally using user32.lib as linker input, like so: cl.exe /LD .\<DLL-code>.c user32.lib

  • [-] Try to build and load the example DLL and accompanying exe.

Maldev Academy sampleDLL and exe invoking the HelloWorld function

sampleDLL.dll

////// sampleDLL.dll //////
 
#include <Windows.h>
 
// Exported function
extern __declspec(dllexport) void HelloWorld(){
    MessageBoxA(NULL, "Hello, World!", "DLL Message", MB_ICONINFORMATION);
}
 
// Entry point for the DLL
BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved) {
    switch (ul_reason_for_call) {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
    }
    return TRUE;
}

This works: screenshot on successfully compiling SampleDLL.dll

sample exe

#include <windows.h>
 
// Constructing a new data type that represents HelloWorld's function pointer 
typedef void (WINAPI* HelloWorldFunctionPointer)();
 
void call() {
    // Attempt to get the handle of the DLL
    HMODULE hModule = GetModuleHandleA("sampleDLL.dll");
 
    if (hModule == NULL) {
        // If the DLL is not loaded in memory, use LoadLibrary to load it
        hModule = LoadLibraryA("sampleDLL.dll");
    }
 
	// pHelloWorld stores HelloWorld's function address
    PVOID pHelloWorld = GetProcAddress(hModule, "HelloWorld"); 
 
 
    // Typecasting pHelloWorld to be of type HelloWorldFunctionPointer
    HelloWorldFunctionPointer HelloWorld = (HelloWorldFunctionPointer)pHelloWorld;
 
	// Invoke HelloWorld
    HelloWorld();
    
}
 
int main(){
 
  call(); // Invoke the call() function
 
  return 0;
 
}

:

The example sample.DLL and accompanying exe do compile, but the exported DLL function HelloWorld doesn’t work (also not with rundll32.exe) and while the library is correctly loaded, the pointer to the function is empty when the exe is ran.

:

For some reason, the code above does work when I compile both the DLL and the exe with cl (cl.exe /LD DLL-code.c userlib32.dll and cl.exe exe-code.c).

Screenshot Maldev Academy VM sample code exporting DLL functions

SampleDLL.cpp and SampleApp.cpp from Microsoft documentation

Code for sample exe

// SampleApp.cpp
//
#include "stdafx.h"
#include "sampleDLL.h"
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    HelloWorld();
    return 0;
}

Code for sample DLL

Code for sample DLL:
// SampleDLL.cpp
//
 
#include "stdafx.h"
#define EXPORTING_DLL
#include "sampleDLL.h"
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved
)
{
    return TRUE;
}
 
void HelloWorld()
{
    MessageBox( NULL, TEXT("Hello World"), TEXT("In a DLL"), MB_OK);
}
 
// File: SampleDLL.h
//
#ifndef INDLL_H
    #define INDLL_H
    #ifdef EXPORTING_DLL
        extern __declspec(dllexport) void HelloWorld();
    #else
        extern __declspec(dllimport) void HelloWorld();
    #endif
 
#endif

Objectives

Todo

Do the objectives below.

Review the 4 entry points for a DLL

Read the documentation on LoadLibrary, GetModuleHandle and GetProcAddress

Create a DLL that exports a function

See the DLL and exe code here.

See the DLL and exe in action below.

Screenshot Maldev Academy VM sample code exporting DLL functions

screenshot on successfully compiling SampleDLL.dll

Invoke the function using Rundll32.exe

Create an EXE program that loads a DLL. Verify the DLL was loaded using Process Hacker

Module 10

Diagram of how EDRs hook into API calls ![](Diagram-of-EDR-API-hooking.ti

How to bypass API hooking: f.e. DLL unhooking and direct syscalls.

Detection mechanisms using the Import Address Table (IAT): any function names used by the portable executable at runtime or the libraries (DLLs) that export these functions listed in the IAT can help identify what WinAPIs the executable is using.

Objectives

Familiarize yourself with how YARA rules work to detect malware

Example YARA rule from https://yara.readthedocs.io/en/stable/writingrules.html:

rule ExampleRule
{
    strings:
        $my_text_string = "text here"
        $my_hex_string = { E2 34 A1 C8 23 FB }
 
    condition:
        $my_text_string or $my_hex_string
}

Some more notes from this chapter of the documentation:

” Rules are generally composed of two sections: strings definition and condition.

The strings definition section can be omitted if the rule doesn’t rely on any string, but the condition section is always required. The strings definition section is where the strings that will be part of the rule are defined. Each string has an identifier consisting of a $ character followed by a sequence of alphanumeric characters and underscores, these identifiers can be used in the condition section to refer to the corresponding string. Strings can be defined in text or hexadecimal.(…) Text strings are enclosed in double quotes just like in the C language. Hex strings are enclosed by curly brackets, and they are composed by a sequence of hexadecimal numbers that can appear contiguously or separated by spaces. Decimal numbers are not allowed in hex strings.

The condition section is where the logic of the rule resides. This section must contain a boolean expression telling under which circumstances a file or process satisfies the rule or not. Generally, the condition will refer to previously defined strings by using their identifiers. In this context the string identifier acts as a boolean variable which evaluate to true if the string was found in the file or process memory, or false if otherwise. ”

Other important aspects of rules: ” Global rules give you the possibility of imposing restrictions in all your rules at once. (…) they will be evaluated before the rest of the rules, which in turn will be evaluated only if all global rules are satisifed. Private rules are (…) not reported by YARA when they match on a give file. Rule tags (…) can be used later to filter YARA’s output and show only the rules that you are interested in. “

Search the following file hash on VirusTotal: e8ac867e5f51bdcf5ab7b06a8bced131

Click the ‘Behavior’ tab on the file’s results in VirusTotal. What suspicious/malicious behavior does this file perform?

Below are some aspects of suspicious/malicious behavior this file performs.

Info

VirusTotal categorizes malware behavior in the ‘Behavior’ tab not with Mitre Att&ck-framework taxonomy (f.e. T1659 for ‘Content Injection’) but instead an unfamiliar ‘OB0001’ for ‘Anti-Behavior Analysis. These codes link back to the ‘MBC Project’ or ‘Malware Behavior Catalog (link to Github-repo. About the relationship ATT&CK-MBC: “As a publicly available framework, The Malware Behavior Catalog (MBC) aims to directly and explicitly define malware behaviors and code characteristics to support malware analysis-oriented use cases. MBC defines behaviors outside ATT&CK’s scope and enhances some ATT&CK techniques and sub-techniques to be malware-focused.”

  • Anti-Behavioral Analysis (OB0001)
    • Debugger Detection (B0001)
  • Collection (OB0003)
    • Keylogging (F0002)
  • Impact (OB0006)
    • Denial of Service (B0033)
  • Cryptography (OC0005)
    • Encrypt Data (C0027)

And many more, see the page here.

Click the ‘Details’ tab and scroll down to ‘Imports’. What DLLs are imported? What WinAPIs are imported from User32.dll?

  • KERNEL32.dll
  • USER32.dll
    • AdjustWindowRectEx
    • AppendMenuA
    • ArrangeIconicWindows
    • BeginDeferWindowPos
    • BeginPaint
    • BringWindowToTop
    • CallNextHookEx
    • CallWindowProcA
    • ChangeClipboardChain
    • CharNextA
  • GDI32.dll
  • COMDLG32.dll
  • WINSPOOL.DRV
  • ADVAPI32.dll
  • SHELL32.dll
  • COMCTL32.dll
  • SHLWAPI.dll
  • oledlg.dll
  • ole32.dll
  • OLEAUT32.dll

Module 11

Virtual memory allows the operating system to use more memory than what is physically available by creating a virtual address space that can be accessed by the applications. These virtual address spaces are divided into ‘pages’ which are then allocated to processes.

pages

Type of memoryDefinition
Private memorydedicated to a single process, stores specific data
Mapped memorycan be shared, stores shared libraries, memory segments, shared files
Image memorycontains code and data of executable, often related to DLL files loaded into process address space

Process Environment Block (PEB)

PEB is a Windows data structure containing process information like:

  • parameters
  • startup information
  • allocated head information
  • loaded DLLs
  • process ID (PID) and path to executable

PEB struct in C reserved members (can be ignored)):

typedef struct _PEB {
  BYTE                          Reserved1[2];
  BYTE                          BeingDebugged;
  BYTE                          Reserved2[1];
  PVOID                         Reserved3[2];
  PPEB_LDR_DATA                 Ldr;
  PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
  PVOID                         Reserved4[3];
  PVOID                         AtlThunkSListPtr;
  PVOID                         Reserved5;
  ULONG                         Reserved6;
  PVOID                         Reserved7;
  ULONG                         Reserved8;
  ULONG                         AtlThunkSListPtr32;
  PVOID                         Reserved9[45];
  BYTE                          Reserved10[96];
  PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoute;
  BYTE                          Reserved11[128];
  PVOID                         Reserved12[1];
  ULONG                         SessionId;
} PEB, *PPEB;

PEB struct reserved members:

  • BeingDebugged

    • indicates whether the process is being debugged, used by the Windows loader
  • Ldr

    • pointer to PEB_LDR_DATA struct containing information about process’s loaded DLLs
    • can be leveraged to find base address of particular DLL and functions
    • used for a custom version of GetModuleHandleA/W for added stealth
typedef struct _PEB_LDR_DATA {
  BYTE Reserved1[8];
  PVOID Reserved2[3];
  LIST_ENTRY InMemoryOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;
  • ProcessParameters
    • data structure containing command line parameters passed to process
    • used for command line spoofing
typedef struct _RTL_USER_PROCESS_PARAMETERS {
  BYTE Reserved1[16];
  PVOID Reserved2[10];
  UNICODE_STRING ImagePathName;
  UNICODE_STRING CommandLine;
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;
  • AtlTHunkSListPtr and AtlThunkSListPtr32

    • used by the ATL (Active Template Library) to store a pointer to linked list of thunking functions
      • thunking functions used to call functions implemented in different address space
      • often represent functions exported from a DLL
  • PostProcessInitRoutine

    • stores pointer to function called by OS after TLS (Thread Local Storage) initialization
  • SessionId

    • unique identifier assigned to single session

Thread Environment Block (TEB)

typedef struct _TEB {
  PVOID Reserved1[12];
  PPEB  ProcessEnvironmentBlock;
  PVOID Reserved2[399];
  BYTE  Reserved3[1952];
  PVOID TlsSlots[64];
  BYTE  Reserved4[8];
  PVOID Reserved5[26];
  PVOID ReservedForOle;
  PVOID Reserved6[4];
  PVOID TlsExpansionSlots;
} TEB, *PTEB;

ProcessEnvironmentBlock (PEB) is a pointer to the PEB structure, located inside the Thread Environment Block (TEB) and used to store information about the currently running process.

TlsSlots (Thread Local Storage) Slots are locations in the TEB used to store thread-specific data (thread-specific variables, handles, states).

TlsExpansionSlots are a set of pointers to store thead-local storage data for a thread, reserved for use by system DLLs.

Objectives

Open Process Hacker and double click on a process, select the ‘Threads’ tab and look at the number of threads running

Click the ‘Memory’ tab and view the different memory regions

Read Microsoft’s documentation for OpenProcess and OpenThread

OpenProcess

Source.

OpenProcess

Function (processthreadsapi.h) Opens an existing local process object. ”

HANDLE OpenProcess(
  [in] DWORD dwDesiredAccess,
  [in] BOOL  bInheritHandle,
  [in] DWORD dwProcessId
);

Parameters

[in] dwDesiredAccess The access to the process object. This access right is checked against the security descriptor for the process. This parameter can be one or more of the process access rights. If the caller has enabled the SeDebugPrivilege privilege, the requested access is granted regardless of the contents of the security descriptor. [in] bInheritHandle If this value is TRUE, processes created by this process will inherit the handle. Otherwise, the processes do not inherit this handle. [in] dwProcessId The identifier of the local process to be opened. If the specified process is the System Idle Process (0x00000000), the function fails and the last error code is ERROR_INVALID_PARAMETER. If the specified process is the System process or one of the Client Server Run-Time Subsystem (CSRSS) processes, this function fails and the last error code is ERROR_ACCESS_DENIED because their access restrictions prevent user-level code from opening them. If you are using GetCurrentProcessId as an argument to this function, consider using GetCurrentProcess instead of OpenProcess, for improved performance.

Return value If the function succeeds, the return value is an open handle to the specified process. If the function fails, the return value is NULL. To get extended error information, call GetLastError.

Optional: Run notepad.exe and then use OpenProcess to get a handle to the process (Hint: copy notepad’s PID from Process Hacker)

To print a handle in C using printf(), you typically convert the handle to a pointer type and use the %p format specifier.

For example, if you have a handle defined as HANDLE h = …;, you would write printf(“Handle: %p\n”, (void*)h); to display it.

#include <Windows.h>
#include <stdio.h>
 
HANDLE hProcess;
DWORD pid = 14844;
 
void main() {
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
printf("The handle is: %p", pid);
}

Review the different memory types

To reiterate, this module names three types of memory for processes:

  • Private memory dedicated to a single process.

  • Mapped memory shared libraries, shared memory segments, and shared files. Mapped memory is visible to other processes, but is protected from being modified by other processes. Source A memory-mapped file contains the contents of a file in virtual memory. This mapping between a file and memory space enables an application, including multiple processes, to modify the file by reading and writing directly to the memory. (…) There are two types of memory-mapped files: - Persisted memory-mapped files Persisted files (…) are associated with a source file on a disk. When the last process has finished working with the file, the data is saved to the source file on the disk. These memory-mapped files are suitable for working with extremely large source files. - Non-persisted memory-mapped files Non-persisted files (…) are not associated with a file on a disk. When the last process has finished working with the file, the data is lost and the file is reclaimed by garbage collection. These files are suitable for creating shared memory for inter-process communications (IPC). _To work with a memory-mapped file, you must create a view of the entire memory-mapped file or a part of it. (…) Use stream access views for sequential access to a file; this is recommended for non-persisted files and IPC. Random access views are preferred for working with persisted files. _ (…) This documentation page also features some example code (unfortunately C#, which I’m unfamiliar with). Some notes from this code: “Persisted Memory-Mapped Files”

  • create a memory-mapped file: ”_(…) `var mmf = MemoryMappedFile.CreateFromFile(@“c:\ExtremelyLargeImage.data”, FileMode.Open,“ImgA”) _”

  • create a random access view from a 256 megabytes offset to the 768th megabyte (the offset plus a 512 megabyte length): var accessor = mmf.CreateViewAccessor(offset, length)

  • Image memory contains the code and data of an executable file, DLL files loaded into address space.

Review the PEB and TEB structures

Module 12 - Undocumented Structures

reserved members within stuctures are presented as arrays of BYTES or PVOID data types and prevent users from understanding the structure to avoid modifications to these reserved members.

One way to determine PEB’s reserved members is through the !peb command in WinDbg.

Choosing a structure definition

  • Some structure definitions only work for a specific architecture (x86 or x64 f.e.)
  • It can be necessary to define multiple structures due to nested structures (PEB contains a member that acts as a pointer to another structure)
  • Select only one definition of a structure to avoid redefinition errors thrown by a compiler (f.e. Visual Studio’s)

Module 13 - Payload placement - .data & .rdata sections

The .data and .rdata sections can be merged / merged into the .text section.

.data section

benefit of string payload in #.data section: readable and writable, so you can have an encrypted payload that is decrypted at runtime

code snippet example of payload stored in .data section:

#include <Windows.h>
#include <stdio.h>
 
// msfvenomn calc shellcode generated with:
// msfvenomn -p windows/x64/exec CMD=calc.exe -f c
// .data saved payload
 
unsigned char Data_RawData[] = {
  // hex characters
};
 
int main() {
  printf("[i] Data_RawData var : 0x%p \n", Data_RawData);
  printf("[#] Press <Enter> To Quit ...");
  getchar();
  return 0;
}

Compiling and executing this code gives an antivirus warning

.rdata section

The ‘r’ in .rdata indicates read-only, which can be done using the const (constant) qualifier for variables.

An example of this using the previous code:

#include <Windows.h>
#include <stdio.h>
 
// msfvenom calc shellcode
// msfvenom -p windows/x64/exec CMD=calc.exe -f c
// .rdata saved payload
const unsigned char Rdata_RawData[] = {
  // hex characters
};
 
int main() {
  printf("[i] Rdata_RawData var: 0x%p\n", Rdata_RawData);
  printf("[#] Press <Enter> To Quit...");
  getchar();
  return 0;
}

.text section

The #.text section stores variables within executable memory permissions so they can be executed directly without editing the memory regions permissions. Mostly useful for small payloads, less than 10 bytes. Saving a variable in the .text section requires you to instruct the compiler to do so. An example:

#include <Windows.h.>
#include <stdio.h>
 
// mfsvenom calc shellcode
// msfvenom -p windows/x64/exec CMD=calc.exe -f c
// .text saved payload
#pragma section(".text")
__declspec(allocate(".text")) const unsigned char Text_RawData[] = {
  // hex characters
};
 
int main() {
  printf("[i] Text_RawData var: 0x%p\n", Text_RawData);
  printf("[#] Press <Enter> To Quit...");
  getchar();
  return 0;
}

.rsrc section

Larger payloads cannot be stored in the .data or .rdata sections due to size limits. The #.rsrc section is cleaner (no errors during compilation).

One way to store a payload in the .rsrc section with Visual Studio:

  1. right-click ‘Resource files’ ‘Add’ ‘New Item…’
  2. ‘Resource File’
  3. Right-click on the ‘Resource.rc’ default file select ‘Add Resource’
  4. ‘Import’
  5. Select calc.ico which is raw payload
  6. In prompt, enter ‘RCDATA’ under ‘Resource Type:’
  7. resource.h should be visible and named according to the previous .rc file

Payloads stored in .rsrc section cannot be accessed directly but through these WINAPIs:

  • FindResourceW - get location of specified data stored in resource section of a special ID passed in (this is defined in the header file)
  • LoadResource - retrieves a HGLOBAL handle of resource data, which can be used to obtain the base address of the specified resource in memory
  • LockResource - obtains pointer to specified data in resource section from its handle
  • SizeofResource - gets size specified data in resource section
#include <Windows.h>
#include <stdio.h>
#include "resource.h"
 
int main() {
 
  HRSRC hRsrc           = NULL;
  HGLOBAL hGlobal       = NULL;
  PVOID pPayloadAddress = NULL;
  SIZE_T sPayloadSize   = NULL;
 
  // Get location to data stored in .rsrc by its id *IDR_RCDATA1*
  hRsrc = FindResourceW(NULL, MAKEINTRESOURCEw(IDR_RCDATA1), RT_RCDATA);
  if (hRsrc == NULL) {
    // in case of function failure
    printf("[!] FindResourceW failed with error: %d \n", GetLastError());
    - [ ] return -1;
  }
 
  // GetHGLOBAL or the handle of the specified resource data since its required to call LockResource later on
  hGlobal = LoadResource(NULL, hRsrc);
  if (hGlobal == NULL) {
    // in case of function failure
    printf("[!] LoadResource failed with errror: %d \n", GetLastError());
    return -1;
  }
 
  // Get the address of our payload in .rsrc section
  pPayloadAddress = LockResource(hGlobal);
  if (pPayloadAddress == NULL) {
    // in case of function failure
    printf("[!] LockResource failed with Error: %d \n", GetLastError());
    return -1;
  }
 
  // Get the size of our payload in .rsrc section
  sPayloadSize = SizeofResource(NULL, hRsrc);
  if (sPayloadSize == NULL) {
    // in case of function failure
    printf("[!] SizeofResource failed with error: %d \n", GetLastError());
    return -1;
  }
 
  // Printing pointer and size to the screen
  printf("[i] pPayloadAddress var: 0x%p \n", pPayloadAddress);
  printf("[i] sPayloadSize var: %ld \n", sPayloadSize);
  printf("[#] Press <Enter> to quit...");
  getchar();
  return 0;
}

Important to note: the payload address that this code prints to the screen, is read-only memory. To edit a payload, you must allocate a buffer with the same size as the payload and copy it over. The new buffer is where changes (like decrypting a payload!) can be made. This can be done with memcpy like this:

// Allocating memory using a HeapAlloc call.
PVOID pTmpBuffer = HeapAlloc(GetProcessHeap(), 0, sPayloadSize);
if (pTmpBuffer != NULL){
  // copying the payload from the resource section to the new buffer.
  memcpy(pTmpBuffer, pPayloadAddress, sPayloadSize);
}
 
// Printing the base address of our buffer (pTmpBuffer)
printf("[i] pTmpBuffer var: 0x%p \n", pTmpBuffer);

Module 16 - Introduction to Payload Encryption

Encrypting (parts of) the malware is almost always necessary against modern security solutions. Encryption may not be effective against f.e. runtime-analysis and heuristic-analysis. The more data that’s encrypted within a file, the higher the entropy of the file. The most widely used encryption algorithms in malware development are:

  • XOR
  • AES
  • RC4

Payload Encryption - XOR

Exclusive or or XOR encryption is faster than AEC and RC4 and does not require additional libraries or the Windows API. It is also a bidirectional encryption algorithm.

Example of an XOR encryption function:

/*
 
  - pShellcode: Base address of the payload to encrypt.
  - sShellcodeSize: Size of the payload.
  - bKey: A single arbitrary byte representing the key for encrypting the payload.
*/
VOID XorByOneKey(IN PBYTE pShellcode, IN SIZE_T sShellcodeSize, IN BYTE bKey) {
  for (size_t i = 0; i < sShellcodeSize; i++){
    pShellcode[i] = pShellcode[i] ^ bKey;
  }
}