Sunday, April 16, 2023

A Guide to Multi-Level Pointer Analysis

 

A Comprehensive Guide to Multi-Level Pointer Analysis

 


A regular pointer points to only one address, but when it's accompanied by a list of offsets, you can walk that pointer chain to find the final or end address. This end address is the address of the variable you want, and it can be used for malware analysis.

By walking this pointer chain, you're actually replicating the actual logic that malware uses to access that variable. When you find a pointer in your malware analysis tool, what you're finding is a path from one address to another using pointers and relative offsets. It's like following a treasure map to reach the buried treasure.

The logic behind this is based on two important features of modern object-oriented programming:

  1. Applications are made memory-efficient by dynamically allocating objects only when needed. Pointers are used to point at these objects, so you can access them via the pointers. Similarly, malware dynamically creates objects and points to them with pointers.

  2. Classes can contain member variables that are pointers, specifically pointers to other objects in memory. In malware, these classes could be hiding malicious payloads, and these pointers can be used to execute malicious code.

The baseAddress or first pointer in the pointer chain is generally one that exists in static memory, meaning it's always located at the same address or can be accessed using a relative offset from the base address of a module. This base address acts as the starting point for your malware analysis and helps you trace the path to the desired variable.

Here's a simple example of real multi-level pointer logic in malware analysis:

// Defining a class
class MaliciousClass
{
public:
    int malwareType;
    int payloadSize;
    char* payload;
};

// Creating a pointer capable of pointing at a MaliciousClass object
MaliciousClass* maliciousObject;

// Creating a new dynamic MaliciousClass object and making our pointer point at it
maliciousObject = new MaliciousClass();

In this example, the malwareType and payloadSize variables are stored as integers at offsets 0x0 and 0x4, respectively. The payload variable is a pointer to a character array at offset 0x8, which contains the actual malicious payload.

Just like with regular programming, we use pointers to access and manipulate these variables. We can use the pointer arrow -> (called the structure dereference operator) to access the payload variable, like this:

maliciousObject->payload
maliciousObject->payload

To perform multi-level pointer analysis in malware, you can use malware analysis tools such as IDA Pro and OllyDbg. By using the "Find What Accesses This Address" or the pointer scanner on a variable, you can find a multi-level pointer where the baseAddress is the address of the maliciousObject and offset 0x8 leads you to the payload pointer. When dereferenced, this pointer yields the address of the actual character array containing the malicious payload.

In C++, we can manually perform multi-level pointer analysis by reading the process memory of each pointer in the chain, like this:

ReadProcessMemory(handle, (LPVOID)pointerAddress, &newPointerAddress, sizeof(newPointerAddress), NULL);
ReadProcessMemory(handle, (LPVOID)(newPointerAddress + offset[0]), &newPointerAddress, sizeof(newPointerAddress), NULL);
ReadProcessMemory(handle, (LPVOID)(newPointerAddress + offset[1]), &newPointerAddress, sizeof(newPointerAddress), NULL);
ReadProcess(handle, (LPVOID)(newPointerAddress + offset[2]), &finalAddress, sizeof(finalAddress), NULL);

This is a very basic example of how a multi-level pointer chain works in practice. In reality, it can be much more complex, with many more levels and offsets to navigate.But why would you need to do this in the first place? One common use case is in malware analysis. Malware often uses multi-level pointer chains to hide its own data and code from security software. By understanding how multi-level pointer chains work, security researchers can better analyze malware and find hidden information.

In conclusion, multi-level pointer chains are a powerful tool used in computer programming and analysis. By understanding how they work, you can gain a deeper understanding of how computers locate and act on data stored in memory. Whether you're a computer engineer, a game developer, or a security researcher, understanding multi-level pointer chains is a valuable skill to have.

 

Finding the Module Base Address in C++ for Malware Analysis

 

dododododo inspect the process dodododo hoohoo dododododo inspect the process dododododo.. hoohoo ...🎜♫♫

When analyzing malware, it's often necessary to locate the base address of a module in memory. This can be a challenging task, especially if the module has been loaded at a randomized address due to address space layout randomization (ASLR). However, with some basic knowledge of C++ and the Windows API, it's possible to write a simple function to find the base address of a module at runtime.

What is a module?

In the context of malware analysis, a module refers to a DLL (Dynamic Link Library) or EXE (Executable) file that has been loaded into memory by a process. Each module has a base address, which is the starting address of the module in memory. This base address can be found by examining the process's memory map.

How to find the base address of a module using C++

To find the base address of a module in C++, we can use the ToolHelp32 library and its associated functions. Here's an example code snippet that shows how to find the base address of a module given its name:

#include <windows.h>
#include <tlhelp32.h>

DWORD GetModuleBaseAddress(DWORD dwProcessId, const char* lpModuleName) {
    MODULEENTRY32 lpModuleEntry = { 0 };
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessId);

    if (hSnapshot == INVALID_HANDLE_VALUE)
        return NULL;

    lpModuleEntry.dwSize = sizeof(lpModuleEntry);

    if (!Module32First(hSnapshot, &lpModuleEntry)) {
        CloseHandle(hSnapshot);
        return NULL;
    }

    do {
        if (strcmp(lpModuleEntry.szModule, lpModuleName) == 0) {
            CloseHandle(hSnapshot);
            return (DWORD)lpModuleEntry.modBaseAddr;
        }
    } while (Module32Next(hSnapshot, &lpModuleEntry));

    CloseHandle(hSnapshot);
    return NULL;
}

int main() {
    DWORD processId = GetCurrentProcessId();
    const char* moduleName = "kernel32.dll";
    DWORD baseAddress = GetModuleBaseAddress(processId, moduleName);

    if (baseAddress == NULL) {
        printf("Could not find module '%s'\n", moduleName);
    } else {
        printf("Module '%s' found at base address 0x%08X\n", moduleName, baseAddress);
    }

    return 0;
}
 

This code defines a function called GetModuleBaseAddress that takes the process ID and module name as parameters and returns the module base address. In the main function, we get the current process ID and module name, and then call the GetModuleBaseAddress function to find the base address of the module.

How to navigate through the module list

The CreateToolhelp32Snapshot function creates a snapshot of the specified process and its associated modules. The Module32First function retrieves information about the first module in the module list of the specified process. The Module32Next function retrieves information about the next module in the module list.

By using these functions in combination, we can loop through the list of modules and find the one with the specified name.

How to obtain a list of modules for a specified process

To obtain a list of modules for a specified process in malware analysis, we can use the CreateToolhelp32Snapshot function with the TH32CS_SNAPMODULE flag. This creates a snapshot of the specified process and its associated modules. We can then use the Module32First and Module32Next functions to loop through the list of modules and get their information.

Here is an example C++ code that demonstrates how to get the module list for a process:

#include <windows.h>
#include <tlhelp32.h>
#include <iostream>

void printModuleList(DWORD processId)
{
    HANDLE hModuleSnap = INVALID_HANDLE_VALUE;
    MODULEENTRY32 me32;
    hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, processId);
    if (hModuleSnap == INVALID_HANDLE_VALUE)
    {
        std::cerr << "CreateToolhelp32Snapshot failed with error code: " << GetLastError() << std::endl;
        return;
    }
    me32.dwSize = sizeof(MODULEENTRY32);
    if (!Module32First(hModuleSnap, &me32))
    {
        std::cerr << "Module32First failed with error code: " << GetLastError() << std::endl;
        CloseHandle(hModuleSnap);
        return;
    }
    std::cout << "Module list for process " << processId << ":" << std::endl;
    do
    {
        std::cout << me32.szModule << " (base address: 0x" << std::hex << (DWORD_PTR)me32.modBaseAddr << ")" << std::endl;
    } while (Module32Next(hModuleSnap, &me32));
    CloseHandle(hModuleSnap);
}

int main()
{
    DWORD processId = GetCurrentProcessId();
    printModuleList(processId);
    return 0;
}

Final Thoughts

The printModuleList function takes the process ID as a parameter and prints out the name and base address of each module in the process. The GetCurrentProcessId function is used to get the ID of the current process. By using this code, you can get a list of modules loaded into a process, which can be useful for identifying malicious code and analyzing its behavior.


Saturday, April 15, 2023

Strings, Strang, Strung, Unicode, Shmoonicode, ASCII - The Wonderful Life of Strings

 Stringssssssssss Thy Precious'

 


Intro

Strings are fascinating entities in the world of programming. For C# and Java programmers, they may seem easy to work with, but for those of us working in C and C++, they can be quite complex beasts. Imagine them like chameleons on a plain colored leaf, waiting to deceive the observer with their next background.

To understand the habitat of strings, we must go back to their distant ancestors - the typewriters. These mechanical beasts ruled the days before their electronic kin were brought into being. They were at the beginning purely mechanical in their form. Users had to move the type bars that impressed the types upon paper, using their own energy. But with the advent of electric typewriters and teletypewriters, things changed. These machines were driven by an arcane power called electricity, which converted the user's input into electrical signals, and moved these impulses into the arms that impressed the type.

However, when you decouple one part from another, an intermediate form becomes a necessity. To make the mechanical parts of the typewriter interact with the electrical parts, sets of character encodings were devised. These eventually begat the unified ASCII standard of character encoding. ASCII defined a 7-bit code for denoting all characters that can be typed, which allowed for the addressing of 2^7 characters or 128 of them. At the time, this was sufficient as English can be written with 26 upper and 26 lower case letters, and some punctuation.

But with the rise of computers and video displays, ASCII became not only the internal storage format but also the character set that was displayed on screen. The emptiness of video displays meant that it was useful to have lines, borders, and other characters drawn on screen to make the information look nicer. This caused the ASCII character set to grow organically, adding 1 more bit to itself and 128 more characters (all "special" characters for drawing stuff with).

Now, extended ASCII needs 8 bits to store each character, which is 1 byte on most architectures. If each character is 8 bits or 1 byte long, then for the computer programmer, a "string" of such characters is n bytes long, with each byte in the string being a character encoded in ASCII.

The evolution of strings is like the evolution of our species. They started out simple, but as the need for more complex forms arose, they evolved and adapted to their environment. They went from being purely mechanical to being driven by electricity and eventually evolved into the ASCII standard of character encoding we know today.

Introducing the C String

If you're a programmer, you may have heard of the C string - the grandfather of all string implementations. But what exactly is it? In its simplest form, a C string is an array of characters encoded in ASCII, and it's terminated by a Null character. Think of it like a train, with each character in the array being a carriage, and the null terminator acting as the caboose.

Here's an example: 

[cpp]char discworld [16] = “Discworld”;[/cpp]

 In this line of code, we define an array of type char with a length of 16. However, it can hold only 15 characters at most, because the last character is (meant to be) reserved for the null terminator (‘\0’). This is a crucial aspect of C strings, as the null terminator indicates where the string ends. It's like a period at the end of a sentence - without it, the string would keep going on and on.

In a C string, the length of the char array that holds the characters must be at least one character larger than the string contained therein. However, it could be much larger if required - it's like a parking lot with only one car parked in it. The parking lot could hold many more cars, but it's currently only occupied by one.

Let's take a closer look at some examples!

Printing a string to the console:

#include int main() { 
 char myString[10] = "Hello"; 
 
 printf("%s\n", myString); 
 
 return 0; 
}
 

Concatenating two strings:

 
#include 
int main() { 
 char myString[10] = "Hello";
 int length = strlen(myString); 
 
 for (int i = 0; i < length / 2; i++) { 
     char temp = myString[i]; 
     myString[i] = myString[length - i - 1]; 
     myString[length - i - 1] = temp; 
printf("%s\n", myString);
return 0; }

 Reversing a string:

#include int main() { 
char myString[10] = "Hello"; 
int length = strlen(myString); 
 
for (int i = 0; i < length / 2; i++) { 
     char temp = myString[i];
     myString[i] = myString[length - i - 1]; 
     myString[length - i - 1] = temp;
printf("%s\n", myString);
return 0; 
}

Comparing two strings:

}#include <stdio.h>
#include <string.h>

int main() {
   char myString[10] = "Hello";
   int length = strlen(myString);
   for (int i = 0; i < length / 2; i++) {
      char temp = myString[i];
      myString[i] = myString[length - i - 1];
      myString[length - i - 1] = temp;
   }
   printf("%s\n", myString);
   return 0;
}
 

There you have it a very quick run down on strings. There will be a future post that is longer and boring but I'll save that one for later!

Unicode and Windows: A Toxic Love Story

 


 

Intro

Unicode string support in Windows is a topic that has intrigued many developers over the years. When Microsoft was tasked with developing a new operating system, they initially planned to create a new version of OS/2 in collaboration with IBM. However, the success of Windows 3 caused a last-minute change of plan. The team behind the new OS decided to shift their API from the extended OS/2 API to an extended version of the Windows API. This change resulted in a strange API, particularly when it came to string handling in the new OS known as Windows NT 3.1.

Windows NT uses Unicode encoding internally, which means that the operating system itself utilizes Unicode encoding in its own private functions. On the other hand, the Windows API used ANSI strings, where each character was an 8-bit char, in Windows 3.0. To ensure that programs written for Windows 3.0 could also run on Windows NT, the team behind the new OS tacked on the old API as is onto NT. However, they had to write code to convert the ANSI strings used in Windows 3.0 into Unicode strings for API calls into NT and then convert them back into ANSI when returning results to the program.

For new programs that would support Unicode, duplicate functions were written that took Unicode strings and returned Unicode strings. As a result, there are two versions of the same function in the Windows API. One accepts ANSI strings, and the other accepts Unicode strings. However, you cannot use both encodings as your native string encoding in the same executable. You must decide at compile time whether to store your characters as 8-bit ANSI or 16-bit Unicode.

 Some Mystic Wizardry

There are 2 atomic character types in Unicode Supported Windows.

char - Character type. Each character is 8 bits.
wchar_t - Wide character type. Each character is 16 bits.
wchar_t UnicodeString[]"UNICODE string óé . This code declares and initializes two character arrays, ansiString and UnicodeString, that hold strings with different character encodings.

The first string, ansiString, is a regular character array that holds a string of characters encoded in the ANSI format. The characters in this string are represented using one byte per character, with each character being 8 bits in size. This encoding is commonly used for text in the English language and other Western European languages that use the Latin alphabet.

The second string, UnicodeString, is a wide character array that holds a string of characters encoded in the Unicode format. The characters in this string are represented using two bytes per character, with each character being 16 bits in size. This encoding is capable of representing characters from all languages in the world, including non-Latin alphabets like Arabic, Chinese, and Hindi.

In addition to the different encodings used in these strings, there is also a difference in the way the strings are declared. The first string is declared using a regular character array with square brackets, while the second string is declared using the wchar_t data type and the L prefix before the string literal. This is because wide character strings in C and C++ are typically declared using the wchar_t data type instead of the regular char data type used for regular character strings. The L prefix indicates that the string literal should be treated as a wide character string.

Finally, the second string includes a special character, ó, which has an accent mark. This character cannot be represented using the ANSI encoding, as it is not part of the regular ASCII character set. However, it can be represented using the Unicode encoding, which is why it is included in the second string.

 s previously noted, the Windows API contains duplicate versions of each function, with one implementation supporting ANSI strings and the other supporting Unicode strings. Let's take a closer look at an example of this in action with the DrawText function.

There are two versions of the DrawText function available in the Windows API: DrawTextA and DrawTextW. Both of these functions perform the same task, but one takes in ANSI strings (A) while the other takes in wide strings (W). The difference in encoding is indicated by the final character in the function name.

To illustrate the usage of these functions, let's take a look at some sample code. In this code, we use the DrawTextA function to draw text encoded in the ANSI format, and the DrawTextW function to draw text encoded in the Unicode format.

DrawTextA(hdc, ansiString, -1, &rect1, DT_SINGLELINE | DT_NOCLIP);
DrawTextW(hdc, UnicodeString, -1, &rect2, DT_SINGLELINE | DT_NOCLIP);

 

n this example, we pass the device context handle (hdc) and the text to be drawn to each of the functions, along with other parameters that control the text layout and formatting. Note that the ansiString variable contains text encoded in the ANSI format, while the UnicodeString variable contains text encoded in the Unicode format.

By using the appropriate function for the encoding of our text, we ensure that the text is correctly displayed on the screen. This is just one example of the many ways in which the Windows API handles different character encodings to support a wide range of languages and text formats.

Attempting to use an ANSI string with a Unicode function, or vice versa, would result in a compilation error due to the type mismatch between char and wchar_t data types. In order to avoid the tedium of checking which function to call in each scenario, the developers behind Windows NT came up with an ingenious solution.

In most cases, it is common practice for a single application to use a consistent string representation, either ANSI or Unicode. The Windows API headers were designed to allow programmers to mostly ignore the existence of the two different string types. Instead, a new "generic" char type called TCHAR is defined in the headers.

The definition of TCHAR is implemented using preprocessor directives, as shown below:

#ifdef UNICODE
#define TCHAR wchar_t
#else
#define TCHAR char
#endif
 

This means that if the UNICODE preprocessor directive is defined, TCHAR will be defined as wchar_t. If not, TCHAR will be defined as char. By using the TCHAR type instead of char or wchar_t, programmers can write code that is agnostic to the underlying string representation. This helps to simplify code and make it more maintainable, as it does not need to be updated if the underlying string representation is changed.

Overall, the use of TCHAR and the clever design of the Windows API headers make it easier for programmers to work with strings in a consistent and seamless manner, regardless of the underlying representation.

NOTE:

The UNICODE preprocessor symbol is defined by default in new Visual Studio projects, which means that TCHAR is replaced by wchar_t by default. However, if you are working with an older codebase or need to support ANSI for some reason, you can define the symbol yourself. To do this, you can add a preprocessor definition in your project settings or use the #define directive in your code. For example:

More C++ Sample Dample

#define UNICODE 0 // define UNICODE as 0 to use ANSI #include <windows.h> TCHAR anyString[] = TEXT("This can either Unicode or ansi. Olé!");

In this example, UNICODE is defined as 0 before including the windows.h header file, which means that TCHAR will be replaced by char instead of wchar_t. The TEXT macro will also be replaced by nothing, resulting in an ANSI string literal.

When it comes to programming on Windows, understanding the differences between Unicode and ANSI character sets is crucial for proper text rendering. If you compile a program using the Unicode Character set, all text should be displayed correctly. However, if you compile using the Multi Byte Character set (ANSI), characters that require Unicode encoding will not render properly and you will see the infamous "?" characters instead.

It is important to note that while it is possible to convert a well-written Unicode program into an ANSI one, it is essential to ensure that any text used in the program fits into the character set it is being compiled into. Otherwise, text will not be displayed as intended.

Fin

In addition to the Unicode and ANSI character sets, Windows headers also define a variety of other string types such as LPSTR, LPWSTR, LPTSTR, LPCTSTR, and more. These types are made up of combinations of pointers to char, wchar_t, or TCHAR. While the names of these types may seem intimidating at first, a closer look at their definitions should make their meanings relatively simple.

To illustrate the importance of character set compatibility, consider the example of a program that receives user input. If the user enters text that contains characters outside of the character set that the program is compiled for, the program will not be able to display or process that text correctly. Therefore, it is important to carefully consider the character set used in your program to ensure proper text rendering and processing.

 

 Til next time! -intro

 

 

 

 

Thursday, April 13, 2023

Kernel32.dll: Understanding Windows Kernel Functions - Rootkits and More!


 


Overview

Kernel32.dll is a system library file that contains various functions used by Windows programs. It serves as a bridge between software applications and the Windows operating system, providing the necessary programming interface for system-level services such as memory management, process creation, file handling, and device control. In simpler terms, you can think of Kernel32.dll as the "glue" that holds the Windows operating system together.

Here's a simple analogy to help understand how Kernel32.dll works: Imagine a library with many books that are stored in a particular order on a set of shelves. You can think of the books as programs, the shelves as the computer's memory, and the librarian as the operating system. The librarian, like Kernel32.dll, acts as an intermediary between the books (programs) and the shelves (memory) and helps manage the flow of information between them.

 

Usage

To use Kernel32.dll in your C++ program, you can call its functions directly from your code. These functions provide a convenient way to access system-level services without having to write low-level assembly code. For example, you can use the CreateProcess() function to launch a new process, or the VirtualAlloc() function to allocate memory for your program.

In the context of security research, Kernel32.dll can be used for a variety of purposes such as code injection, privilege escalation, and rootkit development.

One example of kernel32.dll has been used in security research is for detecting rootkits. A rootkit is a type of malware that is designed to hide its presence on a system by modifying the operating system's kernel. To detect rootkits, researchers can use kernel32.dll to access system-level functions that are not accessible to user-mode applications. These functions can be used to analyze the kernel memory and detect any anomalies that may indicate the presence of a rootkit.

Here's an example of how kernel32.dll can be used in C++ to detect rootkits:

kernel32.dll rootkit detect

 

#include <Windows.h>
#include <iostream>

// Define the ZwQuerySystemInformation function
typedef NTSTATUS(WINAPI* pfnZwQuerySystemInformation)(
    ULONG SystemInformationClass,
    PVOID SystemInformation,
    ULONG SystemInformationLength,
    PULONG ReturnLength
);

int main()
{
    // Load kernel32.dll and get the ZwQuerySystemInformation function
    HMODULE hKernel32 = LoadLibrary(TEXT("kernel32.dll"));
    pfnZwQuerySystemInformation ZwQuerySystemInformation = (pfnZwQuerySystemInformation)GetProcAddress(hKernel32, "ZwQuerySystemInformation");

    // Define the buffer size for the system information
    ULONG bufferSize = 0;

    // Call ZwQuerySystemInformation with SystemKernelDebuggerInformation to get the buffer size
    ZwQuerySystemInformation(91, NULL, 0, &bufferSize);

    // Allocate memory for the system information buffer
    PVOID buffer = VirtualAlloc(NULL, bufferSize, MEM_COMMIT, PAGE_READWRITE);

    // Call ZwQuerySystemInformation with SystemKernelDebuggerInformation to get the system information
    ZwQuerySystemInformation(91, buffer, bufferSize, NULL);

    // Check if the kernel debugger is present
    SYSTEM_KERNEL_DEBUGGER_INFORMATION* kernelDebuggerInfo = (SYSTEM_KERNEL_DEBUGGER_INFORMATION*)buffer;
    if (kernelDebuggerInfo->KernelDebuggerEnabled)
    {
        std::cout << "Kernel debugger detected!" << std::endl;
    }
    else
    {
        std::cout << "Kernel debugger not detected." << std::endl;
    }

    // Free the system information buffer
    VirtualFree(buffer, 0, MEM_RELEASE);

    // Unload kernel32.dll
    FreeLibrary(hKernel32);

    return 0;
}



In this example, the ZwQuerySystemInformation function is used to retrieve information about the system kernel, specifically the presence of a kernel debugger. The code loads the kernel32.dll library, gets the address of the ZwQuerySystemInformation function, and calls it with the SystemKernelDebuggerInformation parameter. If the kernel debugger is enabled, the code prints a message to the console.

Another example of using kernel32.dll in security research is for creating shellcode. Shellcode is a small piece of code that is used to exploit vulnerabilities and execute arbitrary commands on a system. Kernel32.dll contains many functions that can be used for this purpose, such as VirtualAlloc, VirtualProtect, and CreateThread.

Here's an example of how kernel32.dll can be used in C++ to execute shellcode:

 kernel32 shellcode

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

// This shellcode launches calc.exe
unsigned char shellcode[] = {
    0x31, 0xc0, 0x31, 0xdb, 0x31, 0xd2, 0x50, 0x68, 0x63, 0x61, 0x6c, 0x63,
    0x54, 0x59, 0x52, 0x51, 0x64, 0x8b, 0x72, 0x30, 0x8b, 0x76, 0x0c, 0x8b,
    0x76, 0x1c, 0x8b, 0x46, 0x08, 0x8b, 0x7e, 0x20, 0x8b, 0x36, 0x38, 0x4f,
    0x18, 0x75, 0xf3, 0x89, 0x7c, 0x24, 0x24, 0x61, 0xff, 0xe0
};

int _tmain(int argc, _TCHAR* argv[])
{
    DWORD dwOldProtect;
    void * pMem = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (pMem == NULL) {
        printf("VirtualAlloc failed\n");
        return 1;
    }

    memcpy(pMem, shellcode, sizeof(shellcode));

    if (!VirtualProtect(pMem, sizeof(shellcode), PAGE_EXECUTE_READ, &dwOldProtect)) {
        printf("VirtualProtect failed\n");
        return 1;
    }

    HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pMem, NULL, 0, NULL);
    if (hThread == NULL) {
        printf("CreateThread failed\n");
        return 1;
    }

    WaitForSingleObject(hThread, INFINITE);

    VirtualFree(pMem, 0, MEM_RELEASE);
    return 0;
}

 

This code allocates memory, copies the shellcode into it, sets the memory page to be executable, creates a thread to execute the shellcode, and then waits for the thread to finish. The shellcode itself simply launches the Windows Calculator application.

 Conclusion

Overall, kernel32.dll is a powerful tool for security researchers and vulnerability hunters. It provides a wide range of functionality for interacting with the Windows operating system, and has been used in many successful exploits and security tools. Whether you're exploring Windows internals or looking for ways to harden your own applications, understanding how kernel32.dll works is an essential skill for any security-minded programmer.

 So, have you ever used kernel32.dll in your own security research or exploits? What other Windows DLLs have you found particularly useful? Let us know in the comments! And, as always, happy hacking!

NT.DLL and Its Role in Security Research: A Comprehensive Guide

 

NT.DLL and Its Role in Security Research: A Comprehensive Guide

 

 

Are you a security researcher looking to find vulnerabilities or weaknesses in the Windows operating system? Do you want to be a gigachad? If so, then you've come to the right place. During research you will probably come across NT.DLL, which is a critical component of the Windows kernel that contains many functions used by other system components and applications. In this comprehensive guide, we'll explore how NT.DLL has been used in security research, including some examples of C++ code that you can use in your own projects.

But first, let's start with a programming joke:

Why do programmers prefer dark mode? Because light attracts bugs!

Now, let's get back to NT.DLL. One of the primary uses of NT.DLL in security research is to provide access to low-level system functions that are not otherwise exposed through the standard Windows API. For instance, researchers can use NT.DLL to hook system calls and monitor activity to detect and analyze malicious behavior.

 A quick analogy before we dive a bit lower! (another joke)

NT.DLL is like a secret door into the inner workings of Windows: Just as a secret door might allow you to access hidden rooms or areas of a building, NT.DLL provides access to undocumented functions and data structures that are not otherwise exposed through the standard Windows API. These functions and structures can be used to perform advanced system operations, such as modifying system behavior, analyzing process activity, or exploiting vulnerabilities. However, because they are not officially supported by Microsoft, don't be surprised if Bill Gates party vans you for acting leet.

But before we dive into the technical details, here's another programming joke:

another one Meaning & Origin | Slang by Dictionary.com

Why do programmers hate nature? Because it has too many bugs!

The general flow of NT.DLL once called by other components/applications:

  1. Initialization: When the NT.DLL process is loaded into memory, it performs a number of initialization tasks, such as setting up data structures, loading system resources, and registering system functions.

  2. Handling system calls: When other system components or applications need to perform a system-level operation, they call a function provided by the Windows API, which is implemented in NT.DLL. NT.DLL then communicates with the Windows kernel to carry out the requested operation, and returns the results to the calling component or application.

  3. Handling exceptions: If a system-level exception occurs, such as an access violation or stack overflow, NT.DLL is responsible for handling the exception and either terminating the affected process or recovering from the error.

  4. Providing low-level system functions: NT.DLL provides a wide range of low-level system functions and interfaces that other system components and applications can use to access system resources, perform advanced system operations, or modify system behavior. These functions can be called directly by other components or applications, or used indirectly through higher-level system components and libraries.

  5. Interacting with other system components: NT.DLL interfaces with a wide range of other system components and libraries, such as the Windows kernel, drivers, and other system services. These interactions allow NT.DLL to perform its critical system-level functions and provide a bridge between user-mode and kernel-mode components of Windows.

 See..Easy as 1234..

 

Alright, now let's look at an example of how to hook a system call using NT.DLL in C++:

NT.DLL sys call hooking

#include <Windows.h>
#include <iostream>

typedef NTSTATUS(NTAPI* PNT_QUERY_SYSTEM_INFORMATION)(SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG);

PNT_QUERY_SYSTEM_INFORMATION OriginalNtQuerySystemInformation;

NTSTATUS HookedNtQuerySystemInformation(SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength)
{
    std::cout << "NtQuerySystemInformation hooked!" << std::endl;
    return OriginalNtQuerySystemInformation(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength);
}

int main()
{
    HMODULE ntdll = LoadLibrary("ntdll.dll");
    OriginalNtQuerySystemInformation = (PNT_QUERY_SYSTEM_INFORMATION)GetProcAddress(ntdll, "NtQuerySystemInformation");

    if (OriginalNtQuerySystemInformation == NULL)
    {
        std::cerr << "Failed to get address of NtQuerySystemInformation!" << std::endl;
        return 1;
    }

    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    DetourAttach(&(PVOID&)OriginalNtQuerySystemInformation, HookedNtQuerySystemInformation);
    DetourTransactionCommit();

    // Call some function that will trigger a call to NtQuerySystemInformation...

    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    DetourDetach(&(PVOID&)OriginalNtQuerySystemInformation, HookedNtQuerySystemInformation);
    DetourTransactionCommit();

    FreeLibrary(ntdll);
    return 0;
}


Another common use of NT.DLL in security research is to enumerate running processes to identify potential security risks. Here's an example of how to enumerate processes using NT.DLL in C++:

Enumerate NT.DLL 

 #include <windows.h>
#include <iostream>
#include <tlhelp32.h>

// Define the NtQuerySystemInformation function
typedef NTSTATUS(WINAPI* pfnNtQuerySystemInformation)(
    ULONG SystemInformationClass,
    PVOID SystemInformation,
    ULONG SystemInformationLength,
    PULONG ReturnLength
);

int main()
{
    // Load ntdll.dll and get the NtQuerySystemInformation function
    HMODULE hNtdll = LoadLibrary(TEXT("ntdll.dll"));
    pfnNtQuerySystemInformation NtQuerySystemInformation = (pfnNtQuerySystemInformation)GetProcAddress(hNtdll, "NtQuerySystemInformation");

    // Define the buffer size for the system information
    ULONG bufferSize = 0;

    // Call NtQuerySystemInformation with SystemProcessInformation to get the buffer size
    NtQuerySystemInformation(5, NULL, 0, &bufferSize);

    // Allocate memory for the system information buffer
    PVOID buffer = VirtualAlloc(NULL, bufferSize, MEM_COMMIT, PAGE_READWRITE);

    // Call NtQuerySystemInformation with SystemProcessInformation to get the system information
    NtQuerySystemInformation(5, buffer, bufferSize, NULL);

    // Loop through the process information and print out the process names and IDs
    PSYSTEM_PROCESS_INFORMATION processInfo = (PSYSTEM_PROCESS_INFORMATION)buffer;
    while (processInfo->NextEntryOffset != 0)
    {
        // Print the process name and ID
        std::wcout << processInfo->ImageName.Buffer << " (PID: " << processInfo->UniqueProcessId << ")" << std::endl;

        // Move to the next process information block
        processInfo = (PSYSTEM_PROCESS_INFORMATION)(((LPBYTE)processInfo) + processInfo->NextEntryOffset);
    }

    // Free the system information buffer
    VirtualFree(buffer, 0, MEM_RELEASE);

    // Unload ntdll.dll
    FreeLibrary(hNtdll);

    return 0;
}

And finally, one more programming joke before we wrap up:

NT.DLL can be used as a wrapper for other system components and libraries in the Windows operating system. In fact, many Windows system components and libraries, such as the Windows API and the .NET Framework, rely on it. On top of this you can also use it as a wrapper for custom libraries and functions developed by software developers or security researchers or yourself or grandma..IDC!!!

Bottom line - by using NT.DLL as a wrapper, developers and researchers can leverage its low-level system functions and interfaces to access system resources and perform advanced system operations

 I know I said one more joke but maybe that wrapper joke wasn't good enough so here is another:

Why do programmers prefer dark chocolate? Because it's bitter like their code!

So, there you have it - a comprehensive guide to NT.DLL and its role in security research, complete with some programming humor along the way. Before we go, here's a question for you: what other Windows system files do you think are critical for security research, and why? What should we post on next?

Wednesday, April 12, 2023

Reverse Engineering Notes - Windows API

 

 

 

Introduction

The Windows API (Application Programming Interface) is a collection of functions and resources that are available to software developers to create Windows applications. These functions allow developers to interact with the underlying operating system and hardware, enabling them to create powerful and feature-rich applications. In this blog post, we will explore the Windows API, its components, and how it can be utilized for security research.

A (some what) quick analogy for the windows API:

The Windows API can be thought of as a waiter in a restaurant.

When you go to a restaurant, you sit down at a table and look at the menu. You decide what you want to eat, and then you tell the waiter. The waiter takes your order and goes to the kitchen to communicate it to the chef.

In the same way, when you use a Windows application, you interact with its graphical user interface (GUI) to decide what you want the application to do. The GUI is like the menu in a restaurant. Once you have made your selection, the Windows API acts like the waiter, communicating your request to the computer's operating system.

The operating system is like the chef in the kitchen. It receives the request from the Windows API and executes the appropriate actions. For example, if you want to open a file in an application, the Windows API will communicate this request to the operating system, which will then locate and open the file.

Just like a waiter in a restaurant, the Windows API can handle multiple requests simultaneously. It also ensures that requests are handled in the correct order, just as a waiter ensures that orders are delivered to the table in the order in which they were received.

Overall, the Windows API is a crucial component of the Windows operating system, acting as an intermediary between applications and the operating system itself. By understanding this analogy, we can better appreciate the role that the Windows API plays in enabling us to interact with our computers and run the applications that we rely on every day.

Now to more tech speak sine we now have an obvious clear picture right?😦🥳

What is a DLL?

A DLL (Dynamic Link Library) is a file containing functions and resources that can be used by other programs. DLLs are used to provide a common set of functionality that can be accessed by multiple applications, reducing duplication of effort and improving code reuse. When a program uses a DLL, it loads the DLL into memory and calls the functions it needs.

What is kernel32.dll?

kernel32.dll is a core Windows DLL that contains functions that are used by most Windows applications. It provides functions for memory management, process and thread management, file input/output (I/O), and more. Some of the most commonly used functions in kernel32.dll include CreateFile, CloseHandle, ReadFile, WriteFile, and VirtualAlloc.

What is user32.dll?

user32.dll is another core Windows DLL that contains functions related to the user interface (UI). It provides functions for creating and managing windows, handling user input, and more. Some of the most commonly used functions in user32.dll include CreateWindowEx, SendMessage, GetWindowRect, and SetWindowText.

What is windows.h?

windows.h is a header file that contains definitions for the Windows API. It includes definitions for functions, data types, and constants used by the API. When a program wants to use the Windows API, it includes the windows.h header file in its source code.

Where do all of these things come from?

The Windows API is part of the Windows operating system. It is developed and maintained by Microsoft and is available on all versions of Windows.

What is an API?

An API (Application Programming Interface) is a set of functions and resources that are provided by a software system for use by other software programs. APIs define how programs can interact with a system or application, providing a standard way for programs to communicate with each other.

What is the relation between the .h files and the .dll files?

Header files (.h files) provide declarations for functions, data types, and constants used by the Windows API. These declarations tell the compiler how to call the functions in the DLLs. DLLs are compiled binary files that contain the actual code for the functions.

How can you utilize the Windows API?

The Windows API can be used in a variety of ways. Some common use cases include:

  • Creating Windows applications: Developers can use the Windows API to create applications that run on the Windows operating system.
  • Automating tasks: The Windows API can be used to automate repetitive tasks, such as file operations or UI interactions.
  • System administration: The Windows API can be used for system administration tasks, such as managing processes, threads, and services.
  • Security research: The Windows API can be used by security researchers and reverse engineers to analyze and manipulate Windows applications and the operating system itself.

What is an SDK?

An SDK (Software Development Kit) is a collection of tools and resources that are provided by a software vendor to help developers create applications for a specific platform or system. The Windows SDK includes tools and libraries for developing Windows applications, including the Windows API.

What are variable types?

In C++, variables can be of different types, such as integer, float, or string. The type of a variable determines what kind of data it can hold and what operations can be performed on it.

What are naming conventions?

Naming conventions are a set of rules for naming variables, functions, and other elements in a program. Consistent naming conventions can !

help make code more readable and easier to understand. The Windows API has its own naming conventions, which are important to follow when developing applications that use the API.

One important naming convention in the Windows API is the use of prefixes for kernel components. For example, functions in kernel32.dll typically begin with the prefix "k". This helps distinguish kernel-level functions from those at the user level. Similarly, functions in user32.dll typically begin with the prefix "u".

Special Considerations for Security Researchers and Reverse Engineers

The Windows API can be a valuable tool for security researchers and reverse engineers. By understanding how applications interact with the operating system through the API, researchers can identify potential vulnerabilities and weaknesses. Here are some key considerations for using the Windows API for security research:

  1. Understanding DLL loading: When an application loads a DLL, it can potentially execute any code contained in the DLL. This means that DLLs can be used to inject malicious code into a system. Security researchers need to be aware of how DLL loading works and how to identify potentially malicious DLLs.

  2. Analyzing function calls: The Windows API provides a wealth of functions that can be called by applications. Security researchers can analyze function calls made by an application to identify potential vulnerabilities or exploits. By understanding how functions work and how they interact with the system, researchers can gain insight into how an application operates.

  3. Identifying system calls: System calls are the interface between user-level code and the kernel. By understanding how system calls work, researchers can gain a deeper understanding of the operating system and how it can be manipulated.

  4. Understanding data types: The Windows API uses a variety of data types to represent different types of information. Security researchers need to understand these data types and how they are used to identify potential vulnerabilities and weaknesses.

C++ Source Code Examples

Here are some C++ source code examples that demonstrate how to use the Windows API:

Example 1: Opening a file using CreateFile

#include <windows.h> int main() { HANDLE fileHandle = CreateFile( L"C:\\example.txt", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if (fileHandle == INVALID_HANDLE_VALUE) { printf("Failed to open file\n"); return 1; } // File opened successfully, do something with it... CloseHandle(fileHandle); return 0; }

Example 2: Creating a new process using CreateProcess

#include <windows.h> int main() { STARTUPINFO si = { 0 }; PROCESS_INFORMATION pi = { 0 }; if (!CreateProcess( L"C:\\Windows\\notepad.exe", NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi )) { printf("Failed to create process\n"); return 1; } // Process created successfully, do something with it... CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return 0; }

Conclusion

The Windows API is a powerful tool for creating Windows applications and performing system-level operations. It provides a rich set of functions and resources that can be used

to create complex and feature-rich applications. However, working with the Windows API can be challenging and requires a strong understanding of its architecture and functionality.

When used effectively, the Windows API can enable developers to create powerful applications that are tightly integrated with the Windows operating system. It can also provide access to a range of system-level features, such as security and networking, that are not available through other application development frameworks.

In conclusion, the Windows API is an essential tool for Windows application development. While it can be challenging to work with, the rewards of creating feature-rich and tightly integrated Windows applications can be significant. As such, developers should take the time to familiarize themselves with the Windows API and explore its full range of capabilities.

A Guide to Multi-Level Pointer Analysis

  A Comprehensive Guide to Multi-Level Pointer Analysis   A regular pointer points to only one address, but when it's accompanied by a l...