W4LK3R
GitHubLinkedInEmail
  • Home
    • Who am I ?
  • Research
    • Double Take Zero Day (CVE-2023–40459)
  • Red Team Diaries
    • #1 Domain Admin in 2 Hours
    • #2 Low Hanging Credentials
  • Malware Development
    • Basics
    • Dynamic Link Library
    • Code Execution
      • Create Local Thread
      • DLL Execution ( Disk )
      • Function Pointer (No API)
      • Handle2Self
      • Thread2Fiber
      • Callback Functions
      • Local Thread Hijacking / Context Injection
      • Local Mapping Injection
      • Local Module Stomping / DLL Hollowing
      • Local Function Stomping
Powered by GitBook
On this page
  • Error Handling
  • GetLastError
  • GetLastErrorAsString
  • SetLastError
  • Debug Print
  • Using CRT "printf"
  • Using Win32 API
  • Hiding the Console
  • Hiding the Console Window After It’s Created
  • Free the Console (Detach the Console)
  • Prevent Console Creation in the First Place
  • Changing Subsystem After Compilation

Was this helpful?

  1. Malware Development

Basics

Intro to error handling and some basic techniques

PreviousMalware DevelopmentNextDynamic Link Library

Last updated 7 months ago

Was this helpful?

Since this blog is not about Windows system programming, i will not be covering C or Win API basics. this section is about some basic tricks and considerations to make your life easier while dealing with Win API crazy syntax, structures, data types and error codes.


Error Handling

GetLastError

This is the most used Win API for resolving error codes. it checks the return code of the function/sub-routine that was executed right before and returns an integer code which can be checked with VisualStudio "Error Lookup" tool from the "Tools" menu.

Here is an example:

GetLastError.c
#include <windows.h>
#include <stdio.h>

int main() {
	// Attempt to open a non-existent file
	HANDLE hFile = CreateFile(
		"non_existent_file.txt",   // File name
		GENERIC_READ,              // Desired access
		0,                         // Share mode
		NULL,                      // Security attributes
		OPEN_EXISTING,            // Creation disposition
		FILE_ATTRIBUTE_NORMAL,     // File attributes
		NULL                       // Template file
	);

	// Check if the file handle is invalid
	if (hFile == INVALID_HANDLE_VALUE) {
		// Retrieve the last error code
		DWORD dwError = GetLastError();

		// Print the error code and a message
		printf("Failed to open file. Error code: %lu\n", dwError);
	}
	else {
		// If successful, close the file handle
		CloseHandle(hFile);
	}

	return 0;
}

output:

Failed to open file. Error code: 2

After looking up the error code, we can find the error message:


GetLastErrorAsString

Going a step further in error look up techniques, there is a function called FormatMessage in Win API that turns the error code into a human-readable string (the string is exactly the same as error lookup tool).

GetLastErrorAsString.c
#include <windows.h>
#include <stdio.h>

void GetLastErrorAsString(DWORD dwError) {
    LPVOID lpMsgBuf;

    // Format the error message from the error code
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dwError,
        0, // Default language
        (LPWSTR)&lpMsgBuf,
        0,
        NULL
    );

    // Display the error message
    wprintf(L"Error code %lu: %s", dwError, (LPWSTR)lpMsgBuf);

    // Free the buffer allocated by FormatMessage
    LocalFree(lpMsgBuf);
}

int main() {
    // Attempt to open a non-existent file
    HANDLE hFile = CreateFile(
        "non_existent_file.txt",   // File name
        GENERIC_READ,              // Desired access
        0,                         // Share mode
        NULL,                      // Security attributes
        OPEN_EXISTING,             // Creation disposition
        FILE_ATTRIBUTE_NORMAL,     // File attributes
        NULL                       // Template file
    );

    // Check if the file handle is invalid
    if (hFile == INVALID_HANDLE_VALUE) {
        // Retrieve the last error code
        DWORD dwError = GetLastError();

        // Print the error message as a string
        GetLastErrorAsString(dwError);
    }
    else {
        // If successful, close the file handle
        CloseHandle(hFile);
    }

    return 0;
}

output:

Error code 2: The system cannot find the file specified.

SetLastError

In some cases, you might need to set the return code of a function to check it later in the code. for example, lets say you wrote a custom function or you want to change all error codes to a custom category (using Win API macros or intigers).

example:

SetLastError.c
#include <windows.h>
#include <stdio.h>

int main() {
    // Set a custom error code using SetLastError
    SetLastError(ERROR_ACCESS_DENIED);  // Error code 5: Access Denied

    // Retrieve the error code using GetLastError (to verify it was set)
    DWORD dwError = GetLastError();

    SetLastError(1234);  // Error code 5: Access Denied

    // Retrieve the error code using GetLastError (to verify it was set)
    DWORD dwError2 = GetLastError();

    // Print the error code that was set
    printf("Error code set by SetLastError: %lu\n", dwError);
    printf("Error code set by SetLastError: %lu\n", dwError2);

    return 0;
}

output:

Error code set by SetLastError: 5
Error code set by SetLastError: 1234

Debug Print

Using CRT "printf"

This really doesn't need an explanation, we all use print statements to save our asses while debugging code without a debugger. here is a simple macro that can be used for that, it also has a compiler directive to enable/disable print statements across the entire code by simply commenting/un-commenting a single line.

crt_debug_print.c
#include <stdio.h>
#include <windows.h>
#include <stdarg.h>   // Include standard arguments library

// debug print toggle, comment this to disable debug print statements !!!!
#define DEBUG_MODE

// Function prototype for printd that accepts a format string and variable arguments
void printd(const char* format, ...) {
#ifdef DEBUG_MODE              // if DEBUG_PRINT is defined, enable printing
    va_list args;              // Declare a variable of type va_list to hold the argument list
    va_start(args, format);    // Initialize the va_list with the last fixed argument (format)
    // Print the formatted output using vprintf, which takes the format string and va_list
    vprintf(format, args);
    // Clean up the va_list after use
    va_end(args);
#endif
}

int main(int argc, char* argv[]) {
    printd("Integer: %d, String: %s, Character: %c\n", 42, "test", 'A');
    return 0;
}

when #define DEBUG_MODE is not commented, the output will be like this:

Integer: 42, String: test, Character: A

If we comment that line, printd function will return without doing anything and all debug print statements will be disabled.


Using Win32 API

Previous code was good enough for debugging when our code is using standard C run-time (CRT) libraries. but in some cases (discussed in later posts), we want to completly strip out CRT and only use Win32 API or our own custom library code.

In such cases, we can use the following code which is an implementation of CRT "printf" with mode tuggle and support for integers, strings, pointers and characters.

win_debug_print.c
#include <windows.h>

// Debug print toggle, comment this to disable debug print statements
#define DEBUG_PRINT

// Function to write a string to the console
void write_to_console(const char* str, int length) {
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    DWORD written;
    WriteConsoleA(hConsole, str, length, &written, NULL);
}

// Custom function to convert integer to string
void my_itoa(int value, char* str, int base) {
    int i = 0;
    int isNegative = 0;

    // Handle 0 explicitly
    if (value == 0) {
        str[i++] = '0';
        str[i] = '\0';
        return;
    }

    // Handle negative integers
    if (value < 0 && base == 10) {
        isNegative = 1;
        value = -value;
    }

    // Process individual digits
    while (value != 0) {
        int remainder = value % base;
        str[i++] = (remainder > 9) ? (remainder - 10) + 'a' : remainder + '0';
        value = value / base;
    }

    // Append negative sign for negative numbers
    if (isNegative) {
        str[i++] = '-';
    }

    // Null-terminate the string
    str[i] = '\0';

    // Reverse the string
    for (int j = 0; j < i / 2; j++) {
        char temp = str[j];
        str[j] = str[i - j - 1];
        str[i - j - 1] = temp;
    }
}

// Custom function to convert pointer to hexadecimal string
void my_ptrtoa(void* ptr, char* str, int max_size) {
    unsigned long long value = (unsigned long long)ptr;
    int i = 0;

    if (max_size < 3) {
        return;
    }

    if (value == 0) {
        str[i++] = '0';
    }
    else {
        while (value != 0 && i < max_size - 1) {
            int remainder = value % 16;
            str[i++] = (remainder > 9) ? (remainder - 10) + 'a' : remainder + '0';
            value = value / 16;
        }
    }

    for (int j = 0; j < i / 2; j++) {
        char temp = str[j];
        str[j] = str[i - j - 1];
        str[i - j - 1] = temp;
    }

    str[i] = '\0';
}

// Print function that accepts a format string and various arguments
void printd(const char* format, ...) {
#ifdef DEBUG_PRINT
    char buffer[256];
    char temp[64]; // Temporary buffer for formatted output
    int i = 0, j = 0;

    va_list args;
    va_start(args, format); // Initialize va_list

    while (format[i] != '\0') {
        if (format[i] == '%') {
            i++;
            if (format[i] == 's') { // Handle string
                const char* str = va_arg(args, const char*);
                while (*str != '\0' && j < sizeof(buffer) - 1) {
                    buffer[j++] = *str++;
                }
            }
            else if (format[i] == 'd') { // Handle integer
                int num = va_arg(args, int);
                my_itoa(num, temp, 10);
                for (int k = 0; temp[k] != '\0'; k++) {
                    buffer[j++] = temp[k];
                }
            }
            else if (format[i] == 'c') { // Handle character
                char ch = (char)va_arg(args, int);
                buffer[j++] = ch;
            }
            else if (format[i] == 'p') { // Handle pointer
                void* ptr = va_arg(args, void*);
                my_ptrtoa(ptr, temp, sizeof(temp));
                for (int k = 0; temp[k] != '\0'; k++) {
                    buffer[j++] = temp[k];
                }
            }
            else {
                // Handle unknown specifier
                buffer[j++] = '%';
                buffer[j++] = format[i];
            }
        }
        else {
            buffer[j++] = format[i];
        }
        i++;
    }

    buffer[j] = '\0';
    write_to_console(buffer, j);
    va_end(args); // Clean up va_list
#endif
}

// Testing printd function
int main() {
    char a[] = "test";
    int num = 42;
    char ch = 'A';
    printd("String is: %s\nInteger is: %d\nCharacter is: %c\nPointer is: 0x%p\n", a, num, ch, &num);
    printd("this is a test");
    return 0;
}

Output:

String is: test
Integer is: 42
Character is: A
Pointer is: e0fd3afaf4
this is a test

Hiding the Console

After you`re done with the code, your malware should silently execute on victim machine, so you probably don't want the nasty cmd shell popup after execution. there are a couple of tricks to hide the cmd shell console.

Hiding the Console Window After It’s Created

If you want to hide the console window after it is created, you can use the ShowWindow function to hide the console.

ShowWindow.c
#include <windows.h>
#include <stdio.h>

int main() {
    // Retrieve the current console window handle
    HWND hWnd = GetConsoleWindow();

    // Hide the console window
    ShowWindow(hWnd, SW_HIDE);

    // Your code here (the console is hidden)
    MessageBox(NULL, L"The console is hidden", L"Hidden Console", MB_OK);

    return 0;
}

After execution, the console will not be visible, even though MessageBox works correctly:

Free the Console (Detach the Console)

We can also free the console from your process using the FreeConsole function, which detaches the console window from your application.

FreeConsole.c
#include <windows.h>
#include <stdio.h>

int main() {
    // Detach the console from the current process
    FreeConsole();

    // Your code here (the console is no longer attached)
    MessageBox(NULL, "The console is detached", "Detached Console", MB_OK);

    return 0;
}

In this case, the console window is closed and detached from the process after the call to FreeConsole.

Prevent Console Creation in the First Place

One way to avoid showing a console window is to create a Windows application (rather than a console application) by using the WinMain entry point instead of main.

WinMain.c
#include <windows.h>

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    // Your hidden application code here
    MessageBox(NULL, "The console is hidden", "Hidden Console", MB_OK);
    return 0;
}

In this case, no console window is created, as WinMain is used for GUI applications, which don’t automatically open a console window.

Changing Subsystem After Compilation

In case you are dealing with a sample or tool that is already compiled, you can modify the PE to change the subsystem from CLI to GUI. this acts exactly like the previous technique and hides the console.

To do this, first open the PE file in DetectItEasy Tool:

Check "Advanced" option and go to "File Info" :

In the next window, uncheck "Readonly" :

Then go to IMAGE_NT_HEADERS > IMAGE_OPTIONAL_HEADER from the left panel and change the subsystem DWORD value to WINDOWS_GUI.

Save and exit, now the console should be gone.

Ignore the prompt, the original file gets modified.

GetLastError function (errhandlingapi.h) - Win32 appsMicrosoftLearn
Logo
SetLastError function (errhandlingapi.h) - Win32 appsMicrosoftLearn
Logo
GitHub - horsicq/Detect-It-Easy: Program for determining types of files for Windows, Linux and MacOS.GitHub
Logo