[{"content":"Introduction When developing with C or C++ on Windows systems, we generally use GetProcAddress to call a function inside a DLL. The process is fundamentally simple: the DLL is loaded into memory with LoadLibrary, and then the DLL\u0026rsquo;s handle value and the name of the searched function are passed to GetProcAddress. But how exactly does this process work in the background? How does the operating system find the string value we provided inside a massive memory block and return its address to us? In this post, instead of using standard Windows APIs, we are writing our own GetProcAddress function by parsing the memory structure of a PE file.\nWhen a DLL is loaded into memory, it is not placed randomly; it is structured according to the rules of the PE format. The operating system relies on these PE Header structures when writing the file to disk and loading it into memory. DLLs keep the list of functions they export in a table called the \u0026ldquo;Export Directory\u0026rdquo;. The original GetProcAddress starts from the Base Address of the given module in memory. First, it reads the DOS Header, moves from there to the NT Header, and reaches the Export Directory. It compares the names in this directory one by one with the string it received. When a match is found, it retrieves the \u0026ldquo;Ordinal\u0026rdquo; value of that name. Using this value, it extracts the real memory address of the function from the array where the actual addresses are located and returns it as a pointer. The function we are going to write will follow this exact route.\nBefore moving on to the Custom Function itself, we need to examine the IMAGE_EXPORT_DIRECTORY struct.\ntypedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; DWORD Name; DWORD Base; DWORD NumberOfFunctions; DWORD NumberOfNames; DWORD AddressOfFunctions; DWORD AddressOfNames; DWORD AddressOfNameOrdinals; } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY; The critical elements in this structure are:\nNumberOfNames: The total number of functions the DLL exports by name. This value will determine the limit of our loop.\nAddressOfNames: The array that holds the memory addresses of the function names.\nAddressOfFunctions: The array that holds the memory addresses where the actual executable codes of the functions are located.\nAddressOfNameOrdinals: Holds the Ordinal values that establish the connection between the AddressOfNames array and the AddressOfFunctions array.\nThe values starting with \u0026ldquo;AddressOf\u0026rdquo; here are not direct absolute memory addresses, but RVA (Relative Virtual Address) values. RVA represents the distance (offset) of a piece of data from the Base Address where the DLL is loaded. To find the correct address, we will constantly perform the Base Address + RVA operation.\nNow let\u0026rsquo;s look at the code step by step. Our first principle is not to depend on any external API. Instead of using strcmp or lstrcmpA, we are writing our own custom string comparison function.\nBOOL CustomStringCompare(const char* str1, const char* str2) { if (!str1 || !str2) return FALSE; while (*str1 \u0026amp;\u0026amp; (*str1 == *str2)) { str1++; str2++; } return (*str1 == *str2); } We move forward on two string pointers and compare the characters. The loop continues until there is a difference or the string ends, and finally, we return the equality status.\nWe define our main function and make the basic pointer assignment:\nFARPROC CustomGetProcAddress(HMODULE moduleBase, const char* apiName) { if (!moduleBase || !apiName) return NULL; uint8_t* baseAddr = (uint8_t*)moduleBase; The moduleBase parameter is the starting address where the DLL is loaded into memory. We cast this to the uint8_t* type. Our goal is to ensure that C language pointer arithmetic works correctly on a byte basis (advancing 1 byte) when adding RVA values.\nWe check the DOS Header, which is the first structure of the PE file.\nPIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)baseAddr; if (dosHeader-\u0026gt;e_magic != IMAGE_DOS_SIGNATURE) return NULL; We cast the base address to the IMAGE_DOS_HEADER struct. The e_magic value of the DOS Header must be \u0026ldquo;MZ\u0026rdquo; in a valid Windows executable file. We perform this verification with IMAGE_DOS_SIGNATURE.\nThen, we transition to the NT Header.\nPIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(baseAddr + dosHeader-\u0026gt;e_lfanew); if (ntHeaders-\u0026gt;Signature != IMAGE_NT_SIGNATURE) return NULL; The e_lfanew value inside the DOS Header indicates where the NT Header begins. By adding this value to the base address, we reach the NT Header and verify its validity with the IMAGE_NT_SIGNATURE check.\nTo find the location of the Export Directory, we access the Data Directory:\nIMAGE_DATA_DIRECTORY exportDataDir = ntHeaders-\u0026gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; if (exportDataDir.Size == 0 || exportDataDir.VirtualAddress == 0) return NULL; We take the IMAGE_DIRECTORY_ENTRY_EXPORT index, which represents the Export table, from the OptionalHeader inside the NT Headers. If the size or address is zero, it means the module does not export any functions.\nWe locate the Export Directory structure in memory:\nPIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)(baseAddr + exportDataDir.VirtualAddress); By adding the RVA we got from the Data Directory to the base address, we directly reach the IMAGE_EXPORT_DIRECTORY struct.\nNow we define the necessary arrays inside this struct:\nDWORD* nameArray = (DWORD*)(baseAddr + exportDirectory-\u0026gt;AddressOfNames); DWORD* functionArray = (DWORD*)(baseAddr + exportDirectory-\u0026gt;AddressOfFunctions); WORD* ordinalArray = (WORD*)(baseAddr + exportDirectory-\u0026gt;AddressOfNameOrdinals); By adding the base address onto the respective array addresses (RVAs), we determine the actual locations of the arrays in memory. Let\u0026rsquo;s note that ordinalArray is of WORD (2 bytes) type, not DWORD.\nSo what exactly is this \u0026ldquo;Ordinal\u0026rdquo;, and why do we need such a bridge? The logic of the system works like this: the array named nameArray is sorted alphabetically so that the operating system can gain speed when searching (to be able to use algorithms like binary search). However, when names are arranged alphabetically, the order of a name in this array and its order in functionArray, where the actual memory address of that function is located, naturally do not match. This is exactly where ordinalArray comes into play. This array works completely in parallel with nameArray. When we find the function name we are looking for in nameArray, for example, in the 5th position (i = 5), the actual address of that function is not in the 5th position in functionArray. To find out the real index, we look at the ordinalArray[5] value, which acts as a bridge. The value that comes out of there (the ordinal) shows the actual location of the function in the address array. In summary, the ordinal is an index map that provides alignment between the alphabetical name list and the memory address list.\nAfter understanding this logic, we set up the loop where we will search among the function names:\nDWORD totalNames = exportDirectory-\u0026gt;NumberOfNames; for (DWORD i = 0; i \u0026lt; totalNames; i++) { char* currentFuncName = (char*)(baseAddr + nameArray[i]); We start a loop that iterates for the value of NumberOfNames. In each step, we add the RVA value in nameArray to the base address to obtain the memory address of the current function name.\nWe perform the comparison operation with the target API name and the use of the ordinal:\nif (CustomStringCompare(apiName, currentFuncName)) { WORD ordinalIndex = ordinalArray[i]; FARPROC targetFunction = (FARPROC)(baseAddr + functionArray[ordinalIndex]); return targetFunction; } } return NULL; } We check the match status with our own string function. When a match is found, we operate the bridge logic we just mentioned: Using the index i in the loop, we get the real sequence number (ordinalIndex) of the function via ordinalArray. We use this sequence number as an index on functionArray and extract the RVA value of the actual function. Finally, by adding this RVA to the base address, we reach the executable memory address of the target function (FARPROC) and return it. If no match is found when the loop ends, we return NULL.\nAs a result of these operations, we have implemented the background PE Header parsing operations of GetProcAddress, a standard Windows API, with our own code. The original GetProcAddress function, in addition to this basic structure, also manages exceptional situations like \u0026ldquo;Forwarded Exports\u0026rdquo; (where a function inside one DLL is redirected to a different DLL). However, writing this basic implementation step by step is highly productive for understanding the architecture in order to grasp RVA calculations on memory, the Ordinal bridge, and the PE file structure. We have completed the operation without external dependencies, directly over memory addresses.\nIf you want to access the entire code explained in the topic, you can find it at malware-development-library\n","permalink":"https://0xgently.github.io/en/posts/custom_getprocaddress/","summary":"\u003ch2 id=\"introduction\"\u003eIntroduction\u003c/h2\u003e\n\u003cp\u003eWhen developing with C or C++ on Windows systems, we generally use \u003ccode\u003eGetProcAddress\u003c/code\u003e to call a function inside a DLL. The process is fundamentally simple: the DLL is loaded into memory with \u003ccode\u003eLoadLibrary\u003c/code\u003e, and then the DLL\u0026rsquo;s handle value and the name of the searched function are passed to \u003ccode\u003eGetProcAddress\u003c/code\u003e. But how exactly does this process work in the background? How does the operating system find the string value we provided inside a massive memory block and return its address to us? In this post, instead of using standard Windows APIs, we are writing our own \u003ccode\u003eGetProcAddress\u003c/code\u003e function by parsing the memory structure of a PE file.\u003c/p\u003e","title":"Custom GetProcAddress"},{"content":"Introduction Analyzing exactly how Windows APIs work under the hood gives us many new techniques and tactics when writing malware. In this post, we will explore how to implement one of the most frequently used APIs, GetModuleHandle, from scratch without calling a single Windows API. Of course, one of the main reasons for doing this is to hide this heavily used function from the IAT (Import Address Table).\nAs you know, GetModuleHandle returns the base memory address of a loaded module given its DLL name. What it actually does is quite straightforward: it scans a list of loaded modules maintained by the OS loader for each process. This list resides inside the Process Environment Block (PEB) structure. The focus of this post will be to show, step by step, how to manually access the PEB and parse this list.\nHowever, before we start parsing the PEB, we need to understand the structure itself. The module data inside the PEB is essentially a linked list. Therefore, before diving into specific Windows structures, we will briefly discuss why the OS loader uses a linked list instead of a simple array for this task. Once you grasp this core concept, reading data from the PEB stops feeling like a complex operation and simply becomes traversing a standard linked list.\nArrays and Linked Lists When storing data in memory, arrays are contiguous memory blocks by design. As long as you know the element size, you can directly access any index by applying simple pointer arithmetic to the base address. This makes them extremely fast and cache-friendly. However, the biggest disadvantage of arrays is that expanding them dynamically is costly. When the allocated capacity is full and you want to add a new element, you have to allocate a new, larger space in memory and copy all the existing data over to this new area.\nThroughout the lifecycle of a process, new DLLs can constantly be loaded or existing ones unloaded. Using an array for such a dynamic module list whose size constantly changes is highly inefficient because it would require constant memory copying operations.\nLinked lists solve exactly this problem. Instead of a single, large contiguous memory block, an independent allocation is made for each element (node), and each element holds the memory address of the next (or previous) one via a pointer. When a new module is added to the list, there is no need to move the existing data; it is enough to just allocate a new space and update the pointer links in between.\nThe technical cost of this is losing the random access feature; you have to traverse the list from the beginning to reach a specific element. However, in the OS loader\u0026rsquo;s module list scenario, there is no need to directly access a specific index anyway. The operation performed is simply traversing the list from start to finish until a match with the desired DLL name is found. Since index-based access is not needed and dynamic insertion/removal operations are frequent, Windows using a linked list instead of an array for this structure is the most logical engineering choice.\nDoubly Linked Lists In singly linked lists, each node contains a single pointer that only points to the next one. If you are only going to move in one direction through the list, this structure is sufficient. However, it has a significant disadvantage: you cannot directly return to the previous node from your current node. When you need to go back, you have to return to the beginning of the list and traverse it all over again up to the relevant node.\nThe doubly linked list structure, on the other hand, completely eliminates this limitation by adding two pointers to each node. In the Windows architecture, this structure is called LIST_ENTRY. The pointers inside it are named Flink (Forward Link) for the forward connection and Blink (Backward Link) for the backward connection.\nLet\u0026rsquo;s look step by step at how Windows defines this structure in code. First comes the definition of the structure and the pointer indicating the next element:\ntypedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink; // Holds the memory address of the next node Right after comes the backward-pointing pointer, completing the structure:\nstruct _LIST_ENTRY *Blink; // Holds the memory address of the previous node } LIST_ENTRY, *PLIST_ENTRY; The entire structure consists of just these two pointers. Thanks to this minimal design, you can traverse the list forward via Flink and backward via Blink. The biggest performance advantage it provides to the system is that you can delete a node with O(1) complexity (in constant time). Since the relevant node already knows the addresses of both its previous and next neighbors, it can be removed simply by updating the pointers without the need to traverse the list from the beginning.\nThe LIST_ENTRY struct is a fundamental building block used as a standard not only in the OS loader\u0026rsquo;s module list, but in almost all Windows kernel and ntdll structures. Once you grasp the logic of this structure, you will have understood a large portion of the data structures within the inner workings of Windows.\nCircular Lists and List Head When examining the LIST_ENTRY structure in the Windows architecture, the first technical question that usually comes to mind is how to determine the end of the list. In a standard linked list design, the iteration condition is simple: the forward-pointing pointer of the last node holds a NULL value, and the loop terminates when this NULL value is reached.\nHowever, in its core structures, Windows uses a circular design instead of null-terminated lists. In this structure, the Flink pointer of the last node points back to the very beginning, rather than to NULL. Similarly, the Blink pointer of the first node is linked to the very end of the list. Thus, the structure becomes a continuous loop that closes in on itself—much like an Ouroboros, the snake eating its own tail.\nTo prevent this circular structure from turning into an infinite loop, there needs to be a fixed starting point that can be used as a reference. In Windows terminology, this point is called the List Head. The list head is essentially a standard LIST_ENTRY variable, but with one fundamental difference: it does not represent actual data (such as a loaded module). It merely acts as a fixed reference point residing within a parent struct (in our scenario, the PEB_LDR_DATA structure, which we will cover shortly). Its Flink and Blink pointers point to the first and last \u0026ldquo;real\u0026rdquo; data nodes in the list.\nLet\u0026rsquo;s take a step-by-step look at how we set up the loop logic when we want to traverse a list on this architecture. First, we define our starting point, which is the first real element pointed to by the head:\nPLIST_ENTRY current = head-\u0026gt;Flink; Instead of looking for a NULL value as in standard lists, our loop checks whether the read address has returned to the initial fixed reference point (the head):\nwhile (current != head) { // Operations on the node are performed here... After the operations in the loop body are finished, we advance to the next node using the Flink pointer again:\ncurrent = current-\u0026gt;Flink; } The greatest engineering advantage of this design is that it eliminates the need for an extra if (current == NULL) check for empty lists. If the list is completely empty, the head-\u0026gt;Flink pointer will point directly to the head itself, and since the while condition is not met from the start, the loop will never be entered.\nPEB_LDR_DATA Now that we have established the logic of circular and doubly linked lists, we can move on to the actual structure Windows uses in the background to keep track of loaded modules: PEB_LDR_DATA.\nAt the beginning of the structure, basic size and status information are located:\ntypedef struct _PEB_LDR_DATA { ULONG Length; BOOLEAN Initialized; HANDLE SsHandle; Right after that come three LIST_ENTRY fields that hold the module lists:\nLIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; // ... (remaining fields) } PEB_LDR_DATA, *PPEB_LDR_DATA; Notice that the OS loader tracks modules using three different orders (load order, memory address order, and initialization order) instead of a single list. In reality, all three lists connect the exact same module records in memory; only the routes change via different Flink/Blink pairs inside each module. For our GetModuleHandle implementation, the field we will focus on is InMemoryOrderModuleList.\nThe list head (fixed reference point) concept we mentioned in the previous section is exactly this InMemoryOrderModuleList. It is not an actual module itself. Its Flink pointer points to the first loaded module, and its Blink pointer points to the last loaded module; by moving in the Flink direction from this fixed reference point, we can reach every DLL loaded into the process.\nThe actual data nodes in this list are a different struct called LDR_DATA_TABLE_ENTRY. This is the structure that actually holds the module\u0026rsquo;s base address in memory and its name. However, a very critical technical detail comes into play here: the pointer we get via Flink while traversing the list does not point directly to the starting address of the LDR_DATA_TABLE_ENTRY structure. The address returned to us is the address of the LIST_ENTRY field embedded a bit deeper inside this struct.\nTo reach the actual module data (name, base address), we need to calculate the starting address of the struct based on this inner pointer. When we get to the coding phase, we will solve this memory arithmetic using the CONTAINING_RECORD macro in C.\nReading the PEB via the TEB We have examined how to traverse the module lists inside the PEB structure, but we haven\u0026rsquo;t answered the most fundamental question yet: how do we get the memory address (pointer) of the PEB in the first place? Due to our rule of not using any Windows APIs, we cannot resort to system calls like GetModuleHandle or similar ones. Therefore, we need to go directly to the hardware-level registers.\nEvery thread running on Windows has its own Thread Environment Block (TEB) structure. This TEB contains a pointer that points to the overall PEB of the process it belongs to. In Windows systems on the x64 architecture, the operating system holds the TEB address of the currently running thread via the GS segment register. You do not need to ask the operating system for any address; as long as the thread is running, the TEB\u0026rsquo;s address is always located at a fixed offset of GS:[0x30]. The offset needed to go from the TEB to the PEB pointer is 0x60. In short, reading the GS:[0x60] address gives us a PEB* directly, without any API calls.\nHowever, standard C syntax cannot express segment-based memory reads on its own. For this, we need to tell the hardware directly using inline assembly suitable for GCC/Clang compilers.\nFirst, we ensure that we are strictly on the x64 architecture and define our return variable:\nstatic PPEB GetPebManually(void) { #if defined(__x86_64__) PPEB peb; Next, we start our inline assembly block. The __volatile__ keyword used here is critical; it tells the compiler not to skip this code during optimization, not to change its execution order, and to definitely execute it:\n__asm__ __volatile__( The CPU instruction, which is the heart of the matter, is exactly here. We are reading 64-bit data with movq (move quadword). The %%gs:0x60 expression takes the data from the address at the 0x60 offset relative to the GS segment register and writes it to the first output target denoted by %0. (Since registers are indicated with % in GCC assembly syntax, we need to use a double % as an escape character when writing gs inside inline assembly):\n\u0026#34;movq %%gs:0x60, %0\u0026#34; Now we need to explain to the compiler which variable in the C world this %0 target from the assembly world corresponds to. The = in the \u0026quot;=r\u0026quot; expression indicates that this is a write-only output. The r gives the compiler the freedom to choose any general-purpose register it deems suitable for this copying operation. Consequently, the read value is transferred to the peb variable on the C side:\n: \u0026#34;=r\u0026#34; (peb) ); return peb; If the code is not being compiled on the x64 architecture, we throw an error and close the block:\n#else #error \u0026#34;x64 only (GS:[0x60])\u0026#34; #endif } That is it. By writing a single CPU instruction, we fetched the PEB pointer directly without touching any Windows API. Now, we can move on to the main function where we will parse the module lists using this pointer we just obtained.\nFindModuleBaseManually Function In the first step, we define our function and retrieve the base address of the PEB by calling the inline assembly function we wrote in the previous section:\nPVOID FindModuleBaseManually(const wchar_t* targetModuleName) { PPEB peb = GetPebManually(); Immediately after, we perform a standard safety check. If the PEB itself or the LDR_DATA structure inside it (the area holding the module lists) is uninitialized for any reason, we abort the operation and return NULL to prevent the program from crashing:\nif (!peb || !peb-\u0026gt;Ldr) { return NULL; } Now we can access the module list itself. To traverse the circular list, we define our fixed reference point (head) and select the first \u0026ldquo;real\u0026rdquo; element where we will start the loop via Flink:\nPLIST_ENTRY head = \u0026amp;peb-\u0026gt;Ldr-\u0026gt;InMemoryOrderModuleList; PLIST_ENTRY current = head-\u0026gt;Flink; After defining the variable where we will store the found base address, we start our circular list loop. As we discussed earlier, our stopping condition is not NULL, but rather the read address being equal to the initial fixed reference point (head) again:\nPVOID foundBase = NULL; while (current != head) { Here we arrive at the most critical mathematical operation of the entire process. Our current pointer points not to the very beginning of the module structure, but to the LIST_ENTRY field embedded within it. Using the CONTAINING_RECORD macro, we go backwards from this inner offset and calculate the starting address of our actual data structure, LDR_DATA_TABLE_ENTRY_COMPLETE:\nPLDR_DATA_TABLE_ENTRY_COMPLETE entry = CONTAINING_RECORD( current, LDR_DATA_TABLE_ENTRY_COMPLETE, InMemoryOrderLinks ); Now that we have reached the actual structure, we can start reading the module\u0026rsquo;s name. The BaseDllName field is typically a UNICODE_STRING structure. First, we check whether the buffer is valid, and then we calculate the character count (because the Length field gives the size in bytes, not the number of characters):\nif (entry-\u0026gt;BaseDllName.Buffer \u0026amp;\u0026amp; entry-\u0026gt;BaseDllName.Length \u0026gt; 0) { int charCount = entry-\u0026gt;BaseDllName.Length / sizeof(WCHAR); To see what is happening under the hood, we print out each DLL we find and its base address to the screen while traversing the list:\nwprintf(L\u0026#34;[Loaded Module] %-20.*s (Base: 0x%p)\\n\u0026#34;, charCount, entry-\u0026gt;BaseDllName.Buffer, entry-\u0026gt;DllBase); And the match check: Does the name of the module we read match the name of the module we are looking for (e.g., \u0026ldquo;kernel32.dll\u0026rdquo;)? We perform a case-insensitive comparison (_wcsnicmp). If it matches, we save the module\u0026rsquo;s base address (DllBase) and break out of the loop:\nif (targetModuleName \u0026amp;\u0026amp; _wcsnicmp(entry-\u0026gt;BaseDllName.Buffer, targetModuleName, charCount) == 0) { foundBase = entry-\u0026gt;DllBase; break; } } If this is not the module we are looking for, we move on to the next element via Flink to continue traversing the list:\ncurrent = current-\u0026gt;Flink; } Finally, if the loop was broken, we return the base address we found; if the list has ended and no match was found, we return the initial NULL value and terminate the function:\nreturn foundBase; } Thus, without touching the GetModuleHandle API at all, we have obtained the memory address of a DLL entirely through our own methods by manually parsing the operating system\u0026rsquo;s internal data structures.\nThat wraps up our topic. It might have taken a bit long, but this was the most concise and clear way I could explain the fundamental concepts and structs. If you want to access the complete code discussed in this post, you can find it at malware-development-library\n","permalink":"https://0xgently.github.io/en/posts/custom_getmodulehandle/","summary":"\u003ch2 id=\"introduction\"\u003eIntroduction\u003c/h2\u003e\n\u003cp\u003eAnalyzing exactly how Windows APIs work under the hood gives us many new techniques and tactics when writing malware. In this post, we will explore how to implement one of the most frequently used APIs, \u003ccode\u003eGetModuleHandle\u003c/code\u003e, from scratch without calling a single Windows API. Of course, one of the main reasons for doing this is to hide this heavily used function from the IAT (Import Address Table).\u003c/p\u003e\n\u003cp\u003eAs you know, \u003ccode\u003eGetModuleHandle\u003c/code\u003e returns the base memory address of a loaded module given its DLL name. What it actually does is quite straightforward: it scans a list of loaded modules maintained by the OS loader for each process. This list resides inside the Process Environment Block (PEB) structure. The focus of this post will be to show, step by step, how to manually access the PEB and parse this list.\u003c/p\u003e","title":"Custom GetModuleHandle"},{"content":"Apc Injection Today, there are many techniques we use to execute our payload when writing certain malware. In this post, we will focus on the APC Injection technique, which is one of the most fundamental and effective methods where we use the natural operation of the operating system to our advantage. Without wasting time on standard parts that are repeated in every injection, like memory allocation, we will focus directly on the technique.\nWhat is APC? (Asynchronous Procedure Call) In its simplest form, an APC is a function queued to be executed asynchronously within the context of a specific thread. Every thread running in the Windows operating system has its own APC queue.\nTechnically, this situation is related to the structure of thread objects in the operating system kernel. Windows keeps each thread in memory as a struct named _KTHREAD. Within this struct, there is a hidden list pointer that indicates the tasks that the thread will process in order. So, APC is directly a part of the system mechanism managed by this structure.\nWe can think of this as the \u0026ldquo;to-do list\u0026rdquo; of that thread. Under normal circumstances, the system or other programs can externally add tasks to this list by telling the target thread, \u0026lsquo;If you have some free time, take care of this function too.\u0026rsquo; Our goal, on the other hand, is to secretly add our malicious code to this list..\nWhat is a Queue? The \u0026ldquo;to-do list\u0026rdquo; we mentioned above actually works entirely with the logic of a Queue, which is an algorithmic data structure. Queues, one of the most fundamental structures in computer science, operate according to the FIFO (First In, First Out) rule. Just like people waiting in a line at a bank or a bakery, the first task added to the thread\u0026rsquo;s list is processed first by the thread. Thanks to this algorithmic structure, the operating system manages the order of tasks without ever mixing them up.\nWhat is an Alertable State? We have a critical problem here: Adding a task to a thread\u0026rsquo;s APC queue does not mean that task will be executed immediately. The thread will not drop its main job to look at the function you added.\nFor our malicious code to run, the thread we are targeting must enter a wait state called an \u0026ldquo;Alertable State\u0026rdquo;. When the thread enters this state, it gives the following message to the operating system: \u0026ldquo;I am going into a wait state now. If there is a pending APC task in my queue, I can execute it.\u0026rdquo; That is exactly the gap we are going to exploit.\nLocal APC Injection Our goals are quite clear when performing Local APC Injection. We are not trying to get into another process; we will use a thread within the process we created ourselves. Our flow basically proceeds as follows:\nWe create a thread within our own process.\nWe add the address of our malicious code to this thread\u0026rsquo;s APC queue.\nWe deliberately put the thread into an \u0026ldquo;Alertable State\u0026rdquo;.\nThe moment the thread goes into a wait state, it notices our task in the queue and executes our shellcode.\nQueueUserAPC Function We use the QueueUserAPC function to add our malicious code to the thread\u0026rsquo;s to-do list. The structure of this WinAPI is as follows:\nDWORD QueueUserAPC( [in] PAPCFUNC pfnAPC, [in] HANDLE hThread, [in] ULONG_PTR dwData ); Let\u0026rsquo;s briefly examine these three parameters:\npfnAPC: This is the address of the function to be executed. Here, we will provide the address of our shellcode that we have set as executable in memory.\nhThread: This is the handle value of the thread we are targeting. From here, we specify to which thread\u0026rsquo;s queue we will add and execute this task (payload).\ndwData: If our function needs an extra parameter, we send it from here. Since we are running shellcode in APC injection operations, we generally leave this value as NULL.\nFor instance, our example usage is as follows:\n// We are adding the malicious code address to the target thread\u0026#39;s queue. QueueUserAPC((PAPCFUNC)ShellcodeAddress, hThread, NULL); We added our task to the queue, but how do we put the thread into an Alertable State? Windows offers various wait functions that we can use for this job. Their common feature is that they put the thread into an alertable state when the correct parameters are provided.\nMost wait functions need an object handle to know what they are waiting for. In a real scenario, this object is triggered by another thread and the wait ends. However, since our only goal is to put the thread into an infinite wait and slip our APC in between, we create a \u0026ldquo;fake\u0026rdquo; event object that will never be triggered and make it wait on that.\nWe can create our fake object in a single line like this:\nHANDLE hDummyEvent = CreateEvent(NULL, FALSE, FALSE, NULL); Now let\u0026rsquo;s examine how we use a few functions that put this thread into an Alertable State:\nSleepEx SleepEx is a function that suspends a thread for a specified period of time. It does not require an extra object, which makes it the easiest to use. Its second parameter is bAlertable, and when set to TRUE, the thread enters an alertable state.\n// Puts the thread to sleep indefinitely and sets it to an Alertable state. SleepEx(INFINITE, TRUE); The thread falls into an infinite (INFINITE) sleep, but because it is Alertable, it immediately executes our code when our APC arrives.\nWaitForSingleObjectEx This is a function that only waits for the state of a specific object (in our case, the fake event that will never be triggered). Its third parameter determines the Alertable state.\n// Wait indefinitely for the fake event object to be triggered in an alertable manner. WaitForSingleObjectEx(hDummyEvent, INFINITE, TRUE); Since the event will never receive a signal, the wait will never end through normal means, but our code will pop out when the APC queue comes into play.\nWaitForMultipleObjectsEx This is the array version of the previous function. Instead of a single object, it is used to wait for multiple objects at the same time. We can exploit this function again by putting our own fake object into an array.\n// Wait for the object array, switch to Alertable mode with the last parameter being TRUE. HANDLE eventArray[] = { hDummyEvent }; WaitForMultipleObjectsEx(1, eventArray, FALSE, INFINITE, TRUE); MsgWaitForMultipleObjectsEx In addition to normal object waiting operations, this function can wait for input events in the thread\u0026rsquo;s message queue. It is generally used in GUI threads, but we will use it again for our own manipulation. We use the MWMO_ALERTABLE flag to make it alertable.\n// Wait for the fake object and also switch to an alertable state. MsgWaitForMultipleObjectsEx(1, \u0026amp;hDummyEvent, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE); SignalObjectAndWait This API first triggers an object, and immediately after, starts waiting for another object. At the end of its parameters, there is again the bAlertable flag. When using this, we can again point to the fake objects we created ourselves.\n// Signal the first event, wait for the second (fake) event in an alertable manner. SignalObjectAndWait(hSignalEvent, hDummyEvent, INFINITE, TRUE); That is all there is to it. In summary, first we slip our malicious code into the thread\u0026rsquo;s pocket using QueueUserAPC, and then we force the thread to look at that code using one of the functions above. As you can see, Local APC Injection is actually an elegant technique that consists of using a thread\u0026rsquo;s scheduling mechanisms to our advantage.\nNOTE: If you want to access the entire PoC code for this technique, you can check out the malware-development-library repository on my GitHub.\n","permalink":"https://0xgently.github.io/en/posts/apc_%C4%B1nj/","summary":"\u003ch1 id=\"apc-injection\"\u003eApc Injection\u003c/h1\u003e\n\u003cp\u003eToday, there are many techniques we use to execute our payload when writing certain malware. In this post, we will focus on the APC Injection technique, which is one of the most fundamental and effective methods where we use the natural operation of the operating system to our advantage. Without wasting time on standard parts that are repeated in every injection, like memory allocation, we will focus directly on the technique.\u003c/p\u003e","title":"Local APC Injection"},{"content":"In this article, I will explain how to gather information about processes or threads using the CreateToolhelp32Snapshot function.\nIf we want to interact with a target process in the tools we develop or the malware we write, we first need to pinpoint exactly where that process resides in the system.\nWe will divide our operation into two main stages. First, starting with plain text (such as a process name like notepad.exe), we will scan all the processes on the system and extract our target\u0026rsquo;s unique identification number, namely its PID. In the second stage, using this PID we obtained, we will dive into the target process and capture a valid thread that we can manipulate or hijack.\nIn short, in this section, we will see practically through code how to enumerate processes and threads in the system using the most fundamental APIs, and how to obtain the necessary handles from the operating system while doing so.\nNOTE: If you want to access all the functions mentioned in this topic, you can find them at malware-development-library\nStep 1: Process Enumeration via function First, we will look at how to get the PID and Handle of our target among the running processes in the system. Let\u0026rsquo;s examine the code step by step and understand what goes on in the background.\nOur first move is to obtain the current list of all processes in the system:\nHANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); Here, we take a snapshot of the system using the CreateToolhelp32Snapshot API. If we cannot retrieve this list due to security or another reason, the function returns FALSE right from the start and aborts the operation.\nNext, we set up the struct that will hold the data as we iterate through this list:\nPROCESSENTRY32W stEntry = { 0 }; stEntry.dwSize = sizeof(PROCESSENTRY32W); Here, we created the PROCESSENTRY32W structure and initialized it to zero. The dwSize assignment on the next line is a critical detail. When working with Windows APIs, we need to notify the API of the relevant struct\u0026rsquo;s size in memory; otherwise, the functions will usually throw an error. Now it\u0026rsquo;s time to enter the loop and compare the names in the list with our target:\nif (lstrcmpiW(stEntry.szExeFile, wszTargetName) == 0) { *pdwPid = stEntry.th32ProcessID; In each iteration of the loop, we compare the current process\u0026rsquo;s name (szExeFile) with the name we provided (wszTargetName). If we find a match, we write the target\u0026rsquo;s ID (th32ProcessID) to the pointer to pass it back to where we called the function.\n*phTarget = OpenProcess(PROCESS_ALL_ACCESS, FALSE, stEntry.th32ProcessID); if (*phTarget == NULL) { break; } After finding the target, we request full access (PROCESS_ALL_ACCESS) over the process using the OpenProcess API. If the target process is opened successfully, we obtain the handle we need.\nWe have a critical check here: If the process cannot be opened (returns NULL) due to a lack of privileges or system protections, we terminate the loop with the break statement. Normally, a system might have multiple processes with the same name but different PIDs (like Chrome tabs). In advanced scenarios, if the first process fails, trying other processes with the same name using continue is a better approach. However, to simplify the topic and keep the flow straightforward in this educational article, we choose to stop the search (break) if we fail on the first matching process.\nNote: In real-world scenarios, instead of requesting full access like PROCESS_ALL_ACCESS, requesting only the specific permissions for the operation you will perform (for example, only read/write permissions) is always a much better approach both to avoid getting caught by security mechanisms and for stealth.\nFinally, we need to clean up the open resources when we are done:\nCloseHandle(hSnapshot); To prevent a memory leak, we close the snapshot handle we obtained at the very beginning, returning the resource to the operating system and completing the operation.\nStep 2: Thread Enumeration via function Now that we have successfully found the target process, it\u0026rsquo;s time to capture a thread running inside that process.\nFirst of all, we start with a small safety precaution:\nif (pdwTid) *pdwTid = 0; if (phThread) *phThread = NULL; We clear out the pointers that come to us as external parameters. If these variables contain residual garbage data from before, it is always a good practice to initialize them to zero right from the start so they don\u0026rsquo;t cause headaches later.\nThen, similar to what we did in the first function, we take a snapshot of the system:\nhSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); stEntry.dwSize = sizeof(THREADENTRY32); This time, we use the TH32CS_SNAPTHREAD flag instead of TH32CS_SNAPPROCESS. This is because our goal is not to list processes, but all the active threads in the system at that moment. We again don\u0026rsquo;t forget to specify the THREADENTRY32 struct size so that the API runs smoothly.\nWe now have all the threads in the system. Now we need to grab the one that belongs to our process:\nif (stEntry.th32OwnerProcessID == dwProcessId) { *pdwTid = stEntry.th32ThreadID; The logic here is simple: We iterate through all the threads with a loop and ask, \u0026ldquo;Is the process ID that owns this thread (th32OwnerProcessID) the same as the dwProcessId we are looking for?\u0026rdquo; Once a match is found, we write the thread\u0026rsquo;s own ID (th32ThreadID) to the pointer to export it.\nNow it\u0026rsquo;s time to open this thread and take control:\n*phThread = OpenThread( THREAD_ALL_ACCESS, FALSE, stEntry.th32ThreadID); Here again, we are using almost the exact same function as above. Instead of asking for full access like PROCESS_ALL_ACCESS, requesting only the permissions specific to the operation you will perform is again much better. We are leaving it like this for now.\nAnd finally, that critical error-checking point we resolved earlier:\nif (*phThread == NULL) { CloseHandle(hSnapshot); return FALSE; } If OpenThread fails (due to lack of privileges, etc.), we don\u0026rsquo;t just quietly walk away. To avoid leaving a dangling handle in the system (which causes memory leaks), we cleanly shut down shop by calling CloseHandle(hSnapshot) and report our failure (FALSE). (Note that if we can\u0026rsquo;t get permission on the first thread, we return FALSE immediately. If you want to try the other threads of the process, you can build a continue logic here instead of return FALSE, just like we mentioned in the previous function.)\nTo wrap up, we\u0026rsquo;ve examined step-by-step how to dive into the system\u0026rsquo;s background and safely acquire target processes and threads using these two fundamental functions. When working at the operating system level, requesting only the permissions we need and cleaning up open handles when we are done are the core tenets of safe coding.\n","permalink":"https://0xgently.github.io/en/posts/createtoolhelp32snapshot/","summary":"\u003cp\u003eIn this article, I will explain how to gather information about processes or threads using the \u003ccode\u003eCreateToolhelp32Snapshot\u003c/code\u003e function.\u003c/p\u003e\n\u003cp\u003eIf we want to interact with a target process in the tools we develop or the malware we write, we first need to pinpoint exactly where that process resides in the system.\u003c/p\u003e\n\u003cp\u003eWe will divide our operation into two main stages.\nFirst, starting with plain text (such as a process name like \u003ccode\u003enotepad.exe\u003c/code\u003e), we will scan all the processes on the system and extract our target\u0026rsquo;s unique identification number, namely its PID. In the second stage, using this PID we obtained, we will dive into the target process and capture a valid thread that we can manipulate or hijack.\u003c/p\u003e","title":" Process Enumeration-1"},{"content":"Remote_Thread_Creation In this article, I will talk about a technique in which we suspend a legitimate process, inject shellcode into its memory region, and then manipulate the RIP/EIP registers of its existing thread.\nIn the cybersecurity world, APIs such as CreateRemoteThread are generally the preferred choice for executing code inside a target process. However, these functions are monitored very tightly by security mechanisms (AV/EDR) and blocked instantly. Thread Hijacking, on the other hand, instead of creating a brand-new and suspicious thread from scratch, aims to \u0026ldquo;steal\u0026rdquo; the route of an already existing legitimate thread.\nIn the scenario we are going to develop, we will first launch a victim Windows process (for example, a tool under System32) with the CREATE_SUSPENDED flag. Then, we will allocate a region in the target process\u0026rsquo;s memory and inject our malicious shellcode into that region.\nIn the next stage, using the GetThreadContext and SetThreadContext APIs, we will take the address stored in the RIP or EIP register — the register that holds the address of the next instruction the processor will execute — and replace it with the address of the malicious code we have allocated and written in the target process.\nFinally, when we wake the thread up with ResumeThread, the operating system will carry on executing our code without suspecting a thing. I will cover the topic in part 1-2-3. Rather than going through the entire code, I will explain the functions I used in parts 1-2-3 in the order of the chain.\nPart 1 In the first part of our code, we define the main function that will launch the target process in a suspended state:\nBOOL StartSuspendedProcess(const char* exeName, DWORD* outPid, HANDLE* outProcess, HANDLE* outThread) This function asks us for the name of the application we are targeting (exeName, for example \u0026ldquo;notepad.exe\u0026rdquo;). Once it completes its job successfully, it will hand the PID value, the process handle, and the main thread handle of the newly created process back to us through the pointers we pass in.\nNow let\u0026rsquo;s step inside the function and see what happens, step by step:\nif (!GetSystemDirectoryA(path, MAX_PATH)) { printf(\u0026#34;Oops! GetSystemDirectoryA failed. ^_^: %lu\\n\u0026#34;, GetLastError()); return FALSE; } The API we are using here is called GetSystemDirectoryA. Its purpose is to locate Windows\u0026rsquo; System32 directory (usually C:\\Windows\\System32) and write it into the path buffer. Using this API instead of hardcoding (manually writing) the path directly into the code is a much sounder approach, because Windows is not always installed on the C drive. If something goes wrong, we grab the error number Windows returned to us with GetLastError(), print it to the screen, and terminate the function.\n// C:\\Windows\\System32\\\u0026lt;exeName\u0026gt; if (!PathAppendA(path, exeName)) { printf(\u0026#34;Oops! PathAppendA failed. ^_^:\\n\u0026#34;); return FALSE; } Next up is the PathAppendA function. This function\u0026rsquo;s job is quite simple: to safely combine the System32 directory we just obtained with the file name it receives as a parameter. Once the operation is done, we end up with a clean full path (for example, C:\\Windows\\System32\\notepad.exe).\nAnd this is the point where the process is actually launched:\nif (!CreateProcessA(NULL, path, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, \u0026amp;si, \u0026amp;pi)) { printf(\u0026#34;Oops! CreateProcessA failed. ^_^: %lu\\n\u0026#34;, GetLastError()); return FALSE; } Here, we launch the target program using the CreateProcessA API. It takes a lot of parameters, but the most critical one for us is the CREATE_SUSPENDED flag.\nOur reason for passing this parameter is as follows: The process is created and the necessary memory regions are allocated, but the main thread is suspended instantly, before it starts executing any code (before entering the entry point). This gives us a huge advantage; the process has come up in a perfectly legitimate way, but since it has not started running yet, we have a wide time window in which we can inject our own malicious code into it. When the function succeeds, the PID and handle information we need is written into the pi (PROCESS_INFORMATION) structure and becomes ready for use.\nThat wraps up the first stage of the technique. The chain was simple:\nGather the required information from the user Find the actual path of the System32 directory on the system Combine it with the program name provided by the user Finally, launch that program with the CREATE_SUSPENDED flag using the required information. Part 2 Now that we have successfully launched the target process in a suspended state, it is time to secretly write our own malicious code into this application\u0026rsquo;s memory.\nLet\u0026rsquo;s take a look at the beginning of the custom function we wrote for this job:\nBOOL WriteCodeToRemoteProcess(HANDLE hTarget, unsigned char* code, size_t codeSize, void** outRemoteAddr) The name of the main function we will use in this step is WriteCodeToRemoteProcess. The function asks us for the handle of the target process (hTarget), the location in memory of the malicious code we are going to inject (code), and the size of that code (codeSize). When the job is done, it will give us back the exact address in the target process\u0026rsquo;s memory where the code was written, through the outRemoteAddr pointer. Our goal is to place the code into the target memory smoothly and safely.\nFirst, we allocate a region inside the target process.\nremoteBase = VirtualAllocEx(hTarget, NULL, codeSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); The API we are using here is called VirtualAllocEx. Its job is to allocate a memory region of the size we request (codeSize) inside another process (hTarget).\nWe pass the second parameter as NULL, because we leave it up to the operating system itself to decide where in memory the region will be allocated. The MEM_COMMIT | MEM_RESERVE parameters indicate that we are both reserving that region in memory and physically committing it for use. PAGE_READWRITE, on the other hand, says that this region is only readable and writable. If you pay attention, we have not granted \u0026ldquo;execute\u0026rdquo; permission for now, because we have not written the code into memory yet. This is a good practice for avoiding the attention of EDRs.\nWe have allocated our region; now it is time to copy our code into that region:\nif (!WriteProcessMemory(hTarget, remoteBase, code, codeSize, \u0026amp;bytesWritten) || bytesWritten != codeSize) { printf(\u0026#34;Oops! WriteProcessMemory failed. ^_^: %lu\\n\u0026#34;, GetLastError()); return FALSE; } The WriteProcessMemory API writes the code in our own memory (code) into that empty address we just allocated in the target process (remoteBase). If the function succeeds, it records how many bytes of data it wrote to the other side in the bytesWritten variable.\nIn this if block, we are not only checking whether the function returned an error; we are also checking whether the number of bytes written to the other side matches the original size of our code exactly (bytesWritten != codeSize). If fewer bytes were written, or if the function blows up, we print the error to the screen and abort the operation.\nWe have successfully written the code into the target process. We no longer need the code on our own side:\nSecureZeroMemory(code, codeSize); This one-line function is incredibly critical from an operational security (OPSEC) standpoint. After transferring the code to the target process, we destroy the original copy left in our own process\u0026rsquo;s memory by filling it with zeros. That way, an antivirus or an analyst who scans our memory later on cannot find any trace of a suspicious payload on our side.\nNow we are changing the permissions of the region we allocated inside the target process:\nif (!VirtualProtectEx(hTarget, remoteBase, codeSize, PAGE_EXECUTE_READWRITE, \u0026amp;oldProtect)) { printf(\u0026#34;Oops! VirtualProtectEx failed. ^_^: %lu\\n\u0026#34;, GetLastError()); return FALSE; } If you recall, when we allocated the region in memory with VirtualAllocEx, we granted that region only read and write (PAGE_READWRITE) permission. Windows\u0026rsquo; security mechanism (DEP - Data Execution Prevention) prevents code sitting in memory regions that lack execute permission from being run.\nThat is why we tamper with the target process once again using the VirtualProtectEx API, updating the permissions of that memory region where we wrote our code to PAGE_EXECUTE_READWRITE (Read, Write, and Execute). If the permissions fail to change without a problem, we print an error and bail out. Once we get past this step as well, our code becomes fully ready to be executed inside the target process.\nSo here is what we have done up to this point:\nGather the required information from the user with our own main function. Allocate a region inside the target process with VirtualAllocEx. Write the malicious code with WriteProcessMemory. Wipe the malicious code we wrote earlier with SecureZeroMemory. Change the permissions of that region in the target process with VirtualProtectEx. Part 3 Now it is time to change the address shown in the RIP register of the existing thread in the target process to the memory address of our own malicious code. The RedirectThreadExecution function does exactly this job; in other words, it breaks the thread\u0026rsquo;s current flow and tells it: \u0026ldquo;drop what you are doing, go run the code at the address I am pointing you to.\u0026rdquo;\nLet\u0026rsquo;s step into the code:\nBOOL RedirectThreadExecution(HANDLE hTargetThread, void* remoteCodeAddr) { CONTEXT ctx = { .ContextFlags = CONTEXT_CONTROL }; From the outside, we pass the target thread\u0026rsquo;s handle (hTargetThread) and the memory address of the code we want it to execute (remoteCodeAddr) into the function.\nHere, we first define a structure (struct) named CONTEXT. In Windows, when a thread is suspended, the state of its CPU registers at that moment is held in this structure. By writing .ContextFlags = CONTEXT_CONTROL, we tell the operating system: \u0026ldquo;I am not interested in all of this thread\u0026rsquo;s registers; just give me the control registers (Instruction Pointer, etc.).\u0026rdquo; This both improves performance and saves us from dealing with unnecessary data.\nif (!GetThreadContext(hTargetThread, \u0026amp;ctx)) { printf(\u0026#34;Oops! GetThreadContext failed. ^_^: %lu\\n\u0026#34;, GetLastError()); return FALSE; } Our next API is GetThreadContext. This function takes the current state (the register values) of the suspended target thread and fills it into the empty ctx structure we created a moment ago. We could say that we are taking a snapshot of its current state before changing the thread\u0026rsquo;s route. If this read operation fails, we print an error and exit.\nAnd here is where the heart of the operation beats and where the real manipulation happens:\n#ifdef _WIN64 ctx.Rip = (DWORD64)remoteCodeAddr; #elif defined(_WIN32) ctx.Eip = (DWORD)(ULONG_PTR)remoteCodeAddr; #else #error \u0026#34;Unsupported architecture\u0026#34; #endif In a CPU, there is a very special register that holds the memory address of the next instruction to be executed. This register is called RIP (Instruction Pointer) on 64-bit systems and EIP on 32-bit systems.\nHere, we completely overwrite the Rip or Eip value inside the ctx structure that we filled using GetThreadContext. In its place, we write the memory address where our own malicious code is located (remoteCodeAddr). The #ifdef blocks inside guarantee that the correct register gets selected depending on the architecture the code is compiled for (32-bit or 64-bit).\nif (!SetThreadContext(hTargetThread, \u0026amp;ctx)) { printf(\u0026#34;Oops! SetThreadContext failed. ^_^: %lu\\n\u0026#34;, GetLastError()); return FALSE; } } The final blow: we read the state with GetThreadContext, and we swapped the target address with our own code via ctx.Rip. Now we need to load this manipulated, tampered-with structure back into the thread again. That is exactly what SetThreadContext does.\nOnce this API succeeds, the \u0026ldquo;next instruction to be executed\u0026rdquo; address in the thread\u0026rsquo;s brain is now the address of our payload. All that is left for us to do is wake this suspended thread up (with ResumeThread). The second the thread wakes up, instead of going to the original program\u0026rsquo;s entry point, it will start running directly from the address we pointed it to.\nAnd so the final chain comes to an end like this:\nSave the thread\u0026rsquo;s current state with GetThreadContext. Check whether it is 32-bit or 64-bit with the #ifdef blocks. Load the modified thread back with SetThreadContext. As you can see, this topic really offers us an extra method for the question \u0026ldquo;how can we execute our own shellcode/payload (whatever name you give it)?\u0026rdquo; .Instead of imagining this process solely in this way, it’s helpful to consider that we could take a different approach and run our own code in an entirely different manner. After all, throughout this text, we’ve seen not just one method, but what a piece of malware can actually do when it goes beyond its own boundaries.\nNote: If you\u0026rsquo;d like the full code, you can find it at malware-development-library That\u0026rsquo;s all for today—thank you for reading.\n","permalink":"https://0xgently.github.io/en/posts/thread_hijacking/","summary":"\u003ch2 id=\"remote_thread_creation\"\u003eRemote_Thread_Creation\u003c/h2\u003e\n\u003cp\u003eIn this article, I will talk about a technique in which we suspend a legitimate process, inject shellcode into its memory region, and then manipulate the RIP/EIP registers of its existing thread.\u003c/p\u003e\n\u003cp\u003eIn the cybersecurity world, APIs such as \u003ccode\u003eCreateRemoteThread\u003c/code\u003e are generally the preferred choice for executing code inside a target process. However, these functions are monitored very tightly by security mechanisms (AV/EDR) and blocked instantly. Thread Hijacking, on the other hand, instead of creating a brand-new and suspicious thread from scratch, aims to \u0026ldquo;steal\u0026rdquo; the route of an already existing legitimate thread.\u003c/p\u003e","title":"Thread Hijacking"},{"content":"Process Enumeration Finding the Target\u0026rsquo;s PID Value (EnumProcesses) When developing malware, we often need a legitimate running process (e.g., notepad.exe or explorer.exe) on the system to execute techniques like hiding or running our payload. We need to speak Windows\u0026rsquo; language and find the PID (Process ID) value of that process.\nThis process of listing the active processes on the system to find our target\u0026rsquo;s PID or gather similar information about processes is generally called Process Enumeration. In this article, we will look step-by-step at how to do this from scratch using the standard Windows EnumProcesses API.\nFirst, let\u0026rsquo;s take a look at our function that brings the whole operation under one roof:\nBOOL FindSystem32Process(const wchar_t* target_name, DWORD* out_pid) As you can see, it is a BOOL type function. It takes 2 parameters within itself. 1st requires the name of our target process (e.g., svchost.exe). 2nd exports the PID of the process we designated as the target.\nFurther down in the function, there is this part:\nDWORD pids[2048], bytes; if (!EnumProcesses(pids, sizeof(pids), \u0026amp;bytes)) return FALSE; Here, we create an array named pids. The EnumProcesses API retrieves all active PIDs on the system and populates this array. The bytes variable holds how much data was written into this array. We now have a massive pool containing the PIDs of all processes on the system.\nNow we need to iterate through the PIDs in this pool one by one to find our actual target process. To do this, we start a loop:\nfor (DWORD i = 0; i \u0026lt; bytes / sizeof(DWORD); i++) { if (!pids[i]) continue; HANDLE h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pids[i]); if (!h) continue; With each iteration of the loop, we take the next PID and attempt to open it using the OpenProcess API. There is a very important point you need to pay attention to here: the privileges we request when opening the target process. Since we only need to read the process\u0026rsquo;s name, we request the PROCESS_QUERY_LIMITED_INFORMATION privilege. If we unnecessarily requested PROCESS_ALL_ACCESS (full access), it would be a highly suspicious action for security solutions (EDR/AV). If the handle (h) is successfully acquired, we can now look inside that process. Handle in hand, it\u0026rsquo;s now time to find out which application is running behind this PID.\nwchar_t path[MAX_PATH]; DWORD size = MAX_PATH; wchar_t: Because we are using Windows APIs that end with W (Wide), we must use wchar_t (2-byte wide character) which supports Unicode, instead of the standard char (1 byte).\nMAX_PATH: This is a standard macro defined in the Windows operating system header files, and its value is 260. It specifies the maximum character limit a classic file path can take in Windows. So, we create an empty array (box) named path that can hold a maximum of 260 characters.\nsize: To be able to tell the API, \u0026ldquo;I have 260 characters of space available\u0026rdquo;, we assign this value to a variable of type DWORD (32-bit unsigned integer).\nif (QueryFullProcessImageNameW(h, 0, path, \u0026amp;size)) { This function takes the process handle (h) and writes the full file path into the path array we just created.\nThe miraculous event here is the \u0026amp;size part. We pass the size variable not directly, but by memory address (as a reference) by placing an \u0026amp; in front of it. Why? Because when the API finishes its job, it essentially says, \u0026ldquo;You gave me 260 characters of space, but I only used 35 characters of it,\u0026rdquo; and writes the actual length of that process path directly back into this variable (In/Out parameter logic).\nIf this operation completes without error, the function returns TRUE (1), and we enter the if block.\nwchar_t* name = wcsrchr(path, L\u0026#39;\\\\\u0026#39;); wcsrchr (Wide Character String Reverse Character): It is a string search function in the C standard library. Its purpose is to scan a string not from left to right, but from right to left (from end to beginning).\nL'\\\\': The character we are looking for is a backslash. Since \\ is an escape character in C, it is written twice as \\\\ to represent itself. The L at the beginning indicates that this is a wide character (Unicode).\nLet\u0026rsquo;s say the data inside path is this: C:\\Windows\\System32\\notepad.exe. wcsrchr starts from the far right, goes e, x, e, ., d\u0026hellip; and stops at the first \\ sign it sees. It does not return a string, but rather the memory address (pointer) of the exact point where that backslash is located. So, the name pointer is now pointing exactly to the \\notepad.exe part.\nname = name ? name + 1 : path; This line contains C\u0026rsquo;s famous ternary operator (condition ? if_true : if_false) and pointer arithmetic. name ?: This means \u0026ldquo;If name is not null (i.e., if we managed to find a backslash)\u0026rdquo;.\nname + 1: This is where the real action happens. The name pointer was pointing to the \\ sign at the beginning of the \\notepad.exe string. By saying + 1, we shift the memory address one character to the right. This way, we skip that initial slash, and the pointer starts pointing only to the notepad.exe part. This is how we isolate the pure file name.\n: path: If wcsrchr cannot find any backslash (this is technically highly unlikely but it\u0026rsquo;s a rule of safe coding), the name pointer returns NULL. In this case, the condition is evaluated as false, and the operation after the colon (:) is executed: Treat whatever we have (the original path) as the pure name.\nIn the final stage, we check whether the name we found matches the target we are looking for.\nif (_wcsicmp(name, target_name) == 0) { *out_pid = pids[i]; CloseHandle(h); return TRUE; } } CloseHandle(h); } With _wcsicmp, we compare the name we have with our target_name without worrying about case sensitivity. If it matches, it means we found the target! It exports the PID value (*out_pid = pids[i]), cleans up the open handle (CloseHandle), and successfully finishes our search. If it doesn\u0026rsquo;t match, the loop continues to examine the next PID.\nNote: You can find the complete code described in this text at malware-development-library\n","permalink":"https://0xgently.github.io/en/posts/enum_process/","summary":"\u003ch2 id=\"process-enumeration\"\u003eProcess Enumeration\u003c/h2\u003e\n\u003ch3 id=\"finding-the-targets-pid-value-enumprocesses\"\u003eFinding the Target\u0026rsquo;s PID Value (EnumProcesses)\u003c/h3\u003e\n\u003cp\u003eWhen developing malware, we often need a legitimate running process (e.g., notepad.exe or explorer.exe) on the system to execute techniques like hiding or running our payload. We need to speak Windows\u0026rsquo; language and find the PID (Process ID) value of that process.\u003c/p\u003e\n\u003cp\u003eThis process of listing the active processes on the system to find our target\u0026rsquo;s PID or gather similar information about processes is generally called Process Enumeration. In this article, we will look step-by-step at how to do this from scratch using the standard Windows EnumProcesses API.\u003c/p\u003e","title":"Process Enumeration-2"},{"content":"Process Enumeration Finding the Target\u0026rsquo;s PID Value (NtQuerySystemInformation) During malware development ,we generally need a legitimate process that already exists on the system (such as notepad.exe, explorer.exe, or svchost.exe) to hide our payload in memory or to execute various techniques in a stable manner.\nThis is exactly where we refer to the practice of listing the currently active processes on the system to extract our target\u0026rsquo;s PID value or to collect telemetry about processes, generally known as Process Enumeration. In our previous article, we looked step-by-step at how to perform this operation from scratch using Windows’ standard EnumProcesses API. However, in the cybersecurity world, there are always more complex and harder-to-learn ways\u0026hellip;\nIn this article, we are diving much deeper and shifting our focus directly to the undocumented NtQuerySystemInformation function hidden within ntdll.dll. The reason we do this is due to security mechanisms like AV and EDR. Modern security products monitor Windows\u0026rsquo; high-level DLL files and APIs very strictly by hooking into them. They don\u0026rsquo;t just stop at monitoring; they can manipulate calls they deem suspicious and sabotage our operation before it even begins. That is why, in order to avoid getting caught by these hooks and to fly under the detection radars, the more low-level we operate, the better.\nThe first step is getting the handle of the ntdll module:\nHMODULE hNtdll = GetModuleHandleW(L\u0026#34;ntdll.dll\u0026#34;); We don\u0026rsquo;t use LoadLibrary here because ntdll.dll is already loaded into memory in every process. No process on Windows can start without it. GetModuleHandle only returns a reference to a module that is already loaded, so no extra loading is needed.\nIn the second step, we resolve the address of the function:\nPFN_NtQuerySystemInformation NtQuerySystemInformation = (PFN_NtQuerySystemInformation)GetProcAddress(hNtdll, \u0026#34;NtQuerySystemInformation\u0026#34;); This function is not officially documented by Microsoft, so it can\u0026rsquo;t be linked statically. Its name exists in ntdll.lib, but its prototype is missing (or completely absent) in the header files. That is why we have to resolve it at runtime, using a function pointer type that we define ourselves:\ntypedef NTSTATUS (NTAPI *PFN_NtQuerySystemInformation)( ULONG, PVOID, ULONG, PULONG); In this line, we teach the compiler the template of a function that it doesn\u0026rsquo;t officially know yet. Let\u0026rsquo;s look at what each part does:\ntypedef NTSTATUS (NTAPI *PFN_NtQuerySystemInformation): Tells the compiler that we are defining a new function type called PFN_NtQuerySystemInformation, which returns an NTSTATUS (an error/success code). 1st parameter (ULONG): A number that tells which type of system information we want from the kernel. To get the process list, we will send SystemProcessInformation, which is 5. 2nd parameter (PVOID): The address of the empty memory area (buffer) that we allocated beforehand, where the kernel will write the system information. 3rd parameter (ULONG): The size of this memory area in bytes. 4th parameter (PULONG): The address of a variable. If the memory we gave is not enough, the Windows kernel writes the real size it needs (\u0026ldquo;I actually need this many bytes\u0026rdquo;) into this variable and returns it to us. If GetProcAddress fails (for example, if it can\u0026rsquo;t find ntdll.dll, can\u0026rsquo;t find the target function, or any other problem happens), we exit directly without using the function.\nif (!NtQuerySystemInformation) return FALSE; In practice, this check almost never triggers, because ntdll and this function exist in every version of Windows. But it is a step that should not be skipped from the defensive programming point of view.\nThe first problem we face before calling NtQuerySystemInformation is this: we don\u0026rsquo;t know in advance how big the buffer should be to hold the information of all processes in the system. The number of running processes changes all the time, so we can\u0026rsquo;t think \u0026ldquo;there are N processes right now, so let me allocate N times the struct size\u0026rdquo;. Moreover, new processes can even start while we are still creating the buffer.\nTo solve this problem, we build a loop that works with a try-grow-retry logic. First, we choose a reasonable starting size:\nULONG size = 1 \u0026lt;\u0026lt; 16; // 64 KB initial estimate PVOID buffer = NULL; NTSTATUS status; In each step of the loop, we first free the previous buffer and then allocate a new one:\ndo { if (buffer) HeapFree(GetProcessHeap(), 0, buffer); buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size); if (!buffer) return FALSE; We use HeapAlloc, not malloc, because we work directly on the process heap. We also use the HEAP_ZERO_MEMORY flag to zero the allocated memory, which prevents unused fields in the struct from holding random values.\nThen we make the main call:\nstatus = NtQuerySystemInformation(SystemProcessInformation, buffer, size, \u0026amp;size); if (status == STATUS_INFO_LENGTH_MISMATCH) size += (1 \u0026lt;\u0026lt; 14); } while (status == STATUS_INFO_LENGTH_MISMATCH); There are two critical points here. The first one is that the size parameter is used as both input and output: we send it to the function as \u0026ldquo;I have this much space\u0026rdquo; (3rd parameter), and the function writes \u0026ldquo;I actually needed this much space\u0026rdquo; into the same variable (4th parameter).\nThe second one is that when we get STATUS_INFO_LENGTH_MISMATCH, we don\u0026rsquo;t only use the size reported by the function, we also add an extra 16 KB on top of it. The reason is this: new processes may start in the system even within a few milliseconds while our loop is running, so even if we give the exact size that was needed, the next call may still fail because of insufficient space. This extra margin largely prevents a second mismatch round.\nThe loop ends when STATUS_INFO_LENGTH_MISMATCH is no longer returned. Finally, we check for a real error:\nif (!NT_SUCCESS(status)) { HeapFree(GetProcessHeap(), 0, buffer); return FALSE; } The NT_SUCCESS macro checks whether the NTSTATUS value is in the success range. Of course, if an error is returned here, we don\u0026rsquo;t forget to free the memory we allocated before.\nAt this point, we have a correctly sized memory area in buffer that holds the information of all system processes.\nThe data returned from NtQuerySystemInformation is not a classic array. Each SYSTEM_PROCESS_INFORMATION record carries the offset (in bytes) to the next record inside itself. So what we actually have is a linked-list-like structure, but laid out one after another in memory. We start by pointing to the first element:\nPSYSTEM_PROCESS_INFORMATION entry = (PSYSTEM_PROCESS_INFORMATION)buffer; And inside an infinite loop, we move forward using the NextEntryOffset field:\nif (entry-\u0026gt;NextEntryOffset == 0) break; entry = (PSYSTEM_PROCESS_INFORMATION)((PBYTE)entry + entry-\u0026gt;NextEntryOffset); When NextEntryOffset is zero, it means we reached the last element of the list, so we exit the loop. Here we do the pointer arithmetic by casting to PBYTE, because the offset is given in bytes. If we did pointer arithmetic on the entry type, we would jump by the size of the struct, which would be wrong.\nIn each record, the name of the process is kept in the ImageName field as a UNICODE_STRING. Before doing the comparison, we need a safety check, because this field can be empty for some special processes like Idle and System:\nif (entry-\u0026gt;ImageName.Buffer \u0026amp;\u0026amp; _wcsicmp(entry-\u0026gt;ImageName.Buffer, target_name) == 0) { We use _wcsicmp because the comparison must be case-insensitive. The Windows file system is not case-sensitive, so System.exe and system.exe will be treated as the same process.\nA name match alone is not enough. If someone puts a binary with exactly the same name as a system process in a place like C:\\Users\\Public\\notepad.exe, a check that looks only at the name would think it is a legitimate service. That is why we also verify the real disk path for every matching PID.\nFirst, we get the PID from the struct:\nDWORD pid = (DWORD)(ULONG_PTR)entry-\u0026gt;UniqueProcessId; UniqueProcessId is actually defined as a HANDLE type, but in reality it carries a PID value. That is why we first cast it to ULONG_PTR, then to DWORD.\nTo get the path information, we need to open the process, but here we come to one of the most critical rules: we never ask for PROCESS_ALL_ACCESS.\nHANDLE hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); PROCESS_QUERY_LIMITED_INFORMATION came with Windows Vista. It is the lowest-level access right, only for querying, and it gives no permission to interfere with the process. It is enough for functions like QueryFullProcessImageNameW. Asking for PROCESS_ALL_ACCESS is both unnecessary and raises suspicion about why a normal program needs such wide permissions, which should not happen.\nIf the handle was opened successfully, we query the real disk path:\nWCHAR path[MAX_PATH]; DWORD len = MAX_PATH; if (QueryFullProcessImageNameW(hProc, 0, path, \u0026amp;len) \u0026amp;\u0026amp; _wcsnicmp(path, sysDir, sysDirLen) == 0 \u0026amp;\u0026amp; path[sysDirLen] == L\u0026#39;\\\\\u0026#39;) { *out_pid = pid; found = TRUE; } Here we look for two conditions at the same time: First, with the _wcsnicmp function, we check whether the beginning of the file path matches sysDir (that is, C:\\Windows\\System32). Right after that, we check whether there is a \\ character at the position path[sysDirLen].\nIf we don\u0026rsquo;t do this second check, paths like C:\\Windows\\System32Config\\update.exe or C:\\Windows\\System32-Drivers\\update.exe, which are created to fool a simple string match, can pass this filter because of the prefix match. Finding a \\ right at the point where the string ends guarantees that the process is running inside that system directory itself, not in another folder with a similar name.\nWe close the handle as soon as we are done with it, and we don\u0026rsquo;t carry it into the rest of the loop:\nCloseHandle(hProc); If you want, you can find the full code at malware-development-library address. That was our topic, thank you for reading.\n","permalink":"https://0xgently.github.io/en/posts/ntquerysysteminformation/","summary":"\u003ch2 id=\"process-enumeration\"\u003eProcess Enumeration\u003c/h2\u003e\n\u003ch3 id=\"finding-the-targets-pid-value-ntquerysysteminformation\"\u003eFinding the Target\u0026rsquo;s PID Value (NtQuerySystemInformation)\u003c/h3\u003e\n\u003cp\u003eDuring malware development ,we generally need a legitimate process that already exists on the system (such as notepad.exe, explorer.exe, or svchost.exe) to hide our payload in memory or to execute various techniques in a stable manner.\u003c/p\u003e\n\u003cp\u003eThis is exactly where we refer to the practice of listing the currently active processes on the system to extract our target\u0026rsquo;s PID value or to collect telemetry about processes, generally known as Process Enumeration. In our previous article, we looked step-by-step at how to perform this operation from scratch using Windows’ standard \u003ccode\u003eEnumProcesses\u003c/code\u003e API. However, in the cybersecurity world, there are always more complex and harder-to-learn ways\u0026hellip;\u003c/p\u003e","title":"Process Enumeration-3"},{"content":"When writing programs in high-level languages, there are many operations handled automatically in the background that we must manage manually in low-level languages (especially Assembly). Among the most critical of these are the prologue and epilogue sequences.\nBefore diving into this topic, let\u0026rsquo;s touch upon a few fundamental concepts:\nRegister: The fastest data storage area within the CPU; data to be processed is held here. Stack: A memory area operating on a LIFO (Last In, First Out) basis, holding temporary data during function calls. Stack Frame: A block of memory allocated on the stack each time a function is called, containing arguments and local variables. ESP/RSP: The stack pointer; it points to the top of the stack. It is called ESP in x86 and RSP in x64 architecture. EBP/RBP: The base pointer; it holds the base address of the stack frame and serves as a reference point for accessing variables. Calling Convention: A set of rules determining how arguments are passed to functions and how return values are retrieved. Push: Pushes a value onto the stack, moving the stack pointer down. Pop: Pops the value from the top of the stack, moving the stack pointer up. All these concepts form the foundation for understanding prologue and epilogue operations. Essentially, these two sequences consist entirely of setting up and tearing down the stack frame.\nWhat is a Prologue? The prologue runs at the beginning of a function and sets up a \u0026ldquo;frame\u0026rdquo; on the stack. This frame is used to store the function\u0026rsquo;s local variables and any registers that need to be preserved. This operation consists of 3 stages, as illustrated in the image below.\nNow let\u0026rsquo;s examine these 3 steps one by one.\n1. push ebp First, we save our old reference point. When the current function finishes, the values we used inside the function will no longer need to remain on the stack. Therefore, before the frame is even opened, we record the point to which we must return at the end of the function.\n2. mov ebp, esp Here, we are actually preparing the upper boundary of the frame we are about to build. We move EBP to where ESP is. In other words, we are saying; \u0026lsquo;from here on out, this function\u0026rsquo;s territory begins.\u0026rsquo; Thus, the function\u0026rsquo;s own workspace within the stack is clearly defined.\n3. sub esp, 0x10 Finally, we allocate as much space as this function needs to use for storage on the stack, and in this way, the rest of the frame area is completed. We move ESP down a bit; that is, we make some room on the stack. This space becomes the dedicated area reserved for the function\u0026rsquo;s local variables.\nIn Summary These three steps are essentially the process of creating a stack frame. The first command secures the return path by saving the old EBP value. The second command establishes the base of the new stack frame, facilitating access. The final step allocates enough space to store local variables. Consequently, the function obtains its own isolated working environment without corrupting the data of other functions.\nWhat is an Epilogue? The epilogue is the code that runs at the end of a function and reverses the actions of the prologue. Its main purpose is to clean up the space the function allocated on the stack, restore the old layout, and safely return to the calling code. While the prologue sets up the stack frame, the epilogue tears it down. Together, they ensure that functions execute safely and systematically.\nThe epilogue generally occurs in three basic steps:\n1. mov esp, ebp This step frees the stack space used by the function. The stack pointer (ESP) is brought back to the base pointer (EBP) it marked at the beginning of the function. As a result, the area temporarily used by the function is reclaimed by the stack.\n2. pop ebp The old base pointer (EBP), which was pushed to the top of the stack during the prologue, is now restored. The environment prior to the function—the stack and the base pointer—reverts exactly to its former state. This gives the impression that the function was never called.\n3. ret In the final step, the CPU pops the instruction pointer (EIP) from the stack and returns to the calling code. The function\u0026rsquo;s job is now completely finished, and program execution continues from where it left off.\nWithout the epilogue, the stack becomes corrupted and program flow is disrupted. If the stack frame is not removed, ESP remains in the wrong place, and functions cannot return correctly.\nFunctions are the fundamental building blocks of programs, and they require both prologue and epilogue sequences for correct operation. The prologue prepares the environment the function needs, while the epilogue finishes the job, cleans up the environment, and returns to the old layout.\nUnderstanding the epilogue is crucial, especially when working with low-level programming and Assembly. Proper management of the stack is essential for the stability and security of the program. Therefore, seeing how a function starts and ends is a major step toward understanding the inner workings of the code.\nIn this text, we discussed two fundamental operations that people will frequently encounter in Reverse Engineering or when reading Assembly. That\u0026rsquo;s all for our topic. See you in the next text.\n","permalink":"https://0xgently.github.io/en/posts/assembly/","summary":"\u003cp\u003eWhen writing programs in high-level languages, there are many operations handled automatically in the background that we must manage manually in low-level languages (especially Assembly). Among the most critical of these are the \u003cstrong\u003eprologue\u003c/strong\u003e and \u003cstrong\u003eepilogue\u003c/strong\u003e sequences.\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"Diagram of Prologue Operation on the Stack\" loading=\"lazy\" src=\"/assets/img/assembly-image.webp\"\u003e\u003c/p\u003e\n\u003cp\u003eBefore diving into this topic, let\u0026rsquo;s touch upon a few fundamental concepts:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eRegister:\u003c/strong\u003e The fastest data storage area within the CPU; data to be processed is held here.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eStack:\u003c/strong\u003e A memory area operating on a LIFO (Last In, First Out) basis, holding temporary data during function calls.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eStack Frame:\u003c/strong\u003e A block of memory allocated on the stack each time a function is called, containing arguments and local variables.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eESP/RSP:\u003c/strong\u003e The stack pointer; it points to the top of the stack. It is called ESP in x86 and RSP in x64 architecture.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eEBP/RBP:\u003c/strong\u003e The base pointer; it holds the base address of the stack frame and serves as a reference point for accessing variables.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eCalling Convention:\u003c/strong\u003e A set of rules determining how arguments are passed to functions and how return values are retrieved.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003ePush:\u003c/strong\u003e Pushes a value onto the stack, moving the stack pointer down.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003ePop:\u003c/strong\u003e Pops the value from the top of the stack, moving the stack pointer up.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eAll these concepts form the foundation for understanding prologue and epilogue operations. Essentially, these two sequences consist entirely of setting up and tearing down the stack frame.\u003c/p\u003e","title":"What are Prologue and Epilogue in Assembly?"},{"content":"RC4 Encryption RC4 is a symmetric stream cipher that encrypts data byte by byte rather than in blocks. Under the hood, it consists of two primary phases: the Key-Scheduling Algorithm (KSA), where the key is initialized and permuted in memory, and the Pseudo-Random Generation Algorithm (PRGA), which generates keystream bytes to be XORed with the data. Thanks to its symmetric architecture, the exact same routine is utilized for both encryption and decryption.\nIn malware development, three primary approaches are typically used to integrate RC4:\nSystemFunction032: An undocumented RC4 API hosted inside Advapi32.dll. SystemFunction033: An alternative undocumented API that takes identical parameters and performs the exact same operation as SystemFunction032. In practice, there is no functional difference between the two. Custom RC4: Implementing the algorithm from scratch without relying on any native Windows APIs. The following structure illustrates how to perform this operation using Windows\u0026rsquo; built-in SystemFunction032 (or 033).\n1. Required Structures Because SystemFunction032 is an undocumented API, it does not directly take a raw byte pointer (PBYTE). Instead, it expects a specific internal buffer descriptor structure similar to USTRING (or UNICODE_STRING / ANSI_STRING).\ntypedef struct _RC4_CTX { DWORD BufferSize; DWORD MaxBufferSize; PVOID pBufferData; } RC4_CTX, *PRC4_CTX; This struct, defined here as _RC4_CTX, provides the memory layout expected by the API. Both the encryption key and the target payload must be wrapped into this structure before processing.\n2. Execution Flow In this approach, the API is not statically imported (to prevent it from appearing in the Import Address Table / IAT). Instead, its address is resolved dynamically at runtime:\ntypedef NTSTATUS(WINAPI* PFN_SYS_FUNC_032)(PRC4_CTX pData, PRC4_CTX pKey); A suitable function pointer (PFN_SYS_FUNC_032) is defined to invoke the API once its memory address is resolved.\nExecution Chain: Struct Initialization: The rc4Key and rc4Data variables are defined, wrapping the raw memory addresses and lengths into the structure declared above: RC4_CTX rc4Key = { keySize, keySize, keyBuffer }; RC4_CTX rc4Data = { payloadSize, payloadSize, payloadBuffer }; Loading the Library: The module hosting the function is loaded into the process address space using LoadLibraryA: HMODULE hLib = LoadLibraryA(\u0026#34;Advapi32.dll\u0026#34;); Resolving the Address: The exact address of SystemFunction032 is located via GetProcAddress: PFN_SYS_FUNC_032 SysFunc032 = (PFN_SYS_FUNC_032)GetProcAddress(hLib, \u0026#34;SystemFunction032\u0026#34;); Execution: The resolved address is cast to the function pointer type and executed: SysFunc032(\u0026amp;rc4Data, \u0026amp;rc4Key); Upon completion, the contents of payloadBuffer are encrypted (or decrypted, if already encrypted) directly in-place in memory.\nNow, let\u0026rsquo;s examine how to implement RC4 with a custom routine.\nCustom RC4 To avoid Blue Team detection mechanisms such as API hooking, the most reliable method is to implement the RC4 algorithm directly from scratch within the code, without relying on any external DLLs or Windows APIs.\nNote: The core C implementation of the RC4 algorithm examined below is adapted from the open-source CycloneCRYPTO library developed by Oryx Embedded: https://www.oryx-embedded.com/doc/rc4_8c_source.html\nMathematically, the RC4 algorithm consists of two primary routines. The entire operation is carried out on a 256-byte state array known as the S-box.\nFirst, we define a structure (struct) to hold the state values in memory during the encryption process:\ntypedef struct _RC4_STATE { unsigned char stateArray[256]; int iter1; int iter2; } RC4_STATE; stateArray[256]: The core 256-byte lookup table (S-box) upon which all encryption and decryption operations take place. iter1 and iter2: Simple counters used in the subsequent loops to track state progression. State Initialization and Permutation (KSA) Once the struct is defined, we proceed to initialization via the PrepareRc4 function. This function requires three fundamental parameters: our empty state object to be populated (rc4Obj), the encryption key (secretKey), and the key length (keySize).\nvoid PrepareRc4(RC4_STATE* rc4Obj, const unsigned char* secretKey, size_t keySize) { int i, j = 0; unsigned char temp; // Step 1: Initialize the table sequentially for (i = 0; i \u0026lt; 256; i++) { rc4Obj-\u0026gt;stateArray[i] = i; } // Step 2: Permute the table using the key for (i = 0; i \u0026lt; 256; i++) { // Mathematical permutation formula j = (j + rc4Obj-\u0026gt;stateArray[i] + secretKey[i % keySize]) % 256; // Swap operation temp = rc4Obj-\u0026gt;stateArray[i]; rc4Obj-\u0026gt;stateArray[i] = rc4Obj-\u0026gt;stateArray[j]; rc4Obj-\u0026gt;stateArray[j] = temp; } } Here is a breakdown of what these code blocks accomplish step by step:\nStep 1 (Arranging the Deck): We initialize the 256-byte stateArray sequentially with values from 0 to 255. You can think of this as an ordered, freshly opened deck of cards. Step 2 (Shuffling the Deck): This is the core permutation stage. The routine takes the sequentially ordered array and shuffles it pseudo-randomly using the provided secretKey. The formula (j = ...) generates a pseudo-random index based on the key bytes. The % 256 operation ensures the index wraps within the array bounds. The temp variable immediately below executes a standard swap operation, exchanging the positions of the two selected values within the array. Once PrepareRc4 finishes, we obtain a fully permuted, high-entropy 256-byte stateArray uniquely determined by the secretKey.\nThe Encryption Function (PRGA) Having prepared and permuted the state table, we move to the data encryption phase using the Rc4Cipher function.\nStep 1: Context Restoration void Rc4Cipher(RC4_STATE* rc4Obj, const unsigned char* input, unsigned char* output, size_t length) { unsigned char temp; unsigned int i = rc4Obj-\u0026gt;iter1; unsigned int j = rc4Obj-\u0026gt;iter2; unsigned char* s = rc4Obj-\u0026gt;stateArray; Here, we retrieve the initialized table (s) and the loop counters (i and j) from the RC4_STATE struct.\nStep 2: Encryption Loop Now we proceed to the main loop that performs the actual payload transformation:\n// Core Encryption/Decryption Loop while (length \u0026gt; 0) { // Step 1: Increment indices i = (i + 1) % 256; j = (j + s[i]) % 256; // Step 2: Swap (continue shuffling the state) temp = s[i]; s[i] = s[j]; s[j] = temp; // Step 3: Keystream generation and XOR operation *output = *input ^ s[(s[i] + s[j]) % 256]; // Step 4: Advance to the next byte input++; output++; length--; } // Save the final state back into the struct upon completion rc4Obj-\u0026gt;iter1 = i; rc4Obj-\u0026gt;iter2 = j; } The loop operates as follows:\nIndex Shift and Swap: On each iteration, the state table does not remain static. The i and j indices advance, and the corresponding elements in the table are continuously swapped. The state continues to evolve throughout the entire encryption process. XOR Transformation: The core operation is *output = *input ^ s[(s[i] + s[j]) % 256];. A pseudo-random keystream byte is derived from the state table and XORed with the current byte (*input). The resulting byte is written directly to output. State Preservation: Once the entire buffer is processed, the updated index counters (i and j) are saved back to the struct so that subsequent operations can resume seamlessly from the same state offset. This establishes the baseline implementation of a custom RC4 routine. For further modifications or to inspect the complete source, refer to the CycloneCRYPTO repository linked above.\nNOTE: To hinder signature detection of standard RC4 loop constructs, minor variations can be introduced. For example, expanding the state table size or combining the keystream output with additional transformations (such as bitwise rotations or arithmetic offsets) alters the generated bytecode and disrupts static pattern matching.\n","permalink":"https://0xgently.github.io/en/posts/rc4/","summary":"\u003ch2 id=\"rc4-encryption\"\u003eRC4 Encryption\u003c/h2\u003e\n\u003cp\u003eRC4 is a symmetric stream cipher that encrypts data byte by byte rather than in blocks. Under the hood, it consists of two primary phases: the Key-Scheduling Algorithm (KSA), where the key is initialized and permuted in memory, and the Pseudo-Random Generation Algorithm (PRGA), which generates keystream bytes to be XORed with the data. Thanks to its symmetric architecture, the exact same routine is utilized for both encryption and decryption.\u003c/p\u003e","title":"RC4 Encryption"},{"content":"XOR Encryption In the world of malware development, one of the most critical factors determining the lifespan of your payload is how effectively you can conceal it. If you embed shellcode or a PE file into memory in plain text, static analysis tools and antivirus (AV) signatures will flag it within seconds.\nThis is precisely where payload encryption comes into play. In this post, we will examine the XOR algorithm—one of the most fundamental yet heavily modified encryption techniques in the malware domain. We will start from the basics and progress toward complex, position-dependent rolling structures.\nNote: You can find the complete code discussed in this post at: malware-development-library\nXOR (Exclusive OR) The XOR operator (^) is indispensable in cryptography and malware development because it is symmetric. In other words, you use the exact same operation to both encrypt and decrypt data:\nData ^ Key = Encrypted Data Encrypted Data ^ Key = Data This characteristic provides substantial flexibility and code economy; there is no need to write a separate decryption routine. Let\u0026rsquo;s see how this logic translates into code.\nSingle-Byte Key XOR Let\u0026rsquo;s begin with the simplest form: a function that encrypts the entire payload using a single byte key.\nstatic VOID SingleKeyXor(IN PBYTE pPayload, IN SIZE_T sPayloadSize, IN const BYTE bXorKey) { for (size_t x = 0; x \u0026lt; sPayloadSize; x++) { pPayload[x] = pPayload[x] ^ bXorKey; } } This function accepts three parameters:\npPayload: Memory address of the data to be encrypted (or decrypted). sPayloadSize: Total size of the data buffer. bXorKey: The single-byte key used for the operation (e.g., 0xAA). The logic is straightforward: a for loop iterates sequentially through the payload from start to finish. Each byte is XORed with the specified bXorKey and written back in place.\nWhile this approach might initially bypass basic signature-based static scans, it is largely insufficient today. Because a single-byte key is used, repeating byte sequences in the payload (such as consecutive 0x00 null bytes) will consistently produce the same encrypted byte value throughout the output.\nAdvanced Rolling XOR Having seen the limitations of a single-byte key, bypassing modern security controls requires a mechanism where identical input bytes yield distinct outputs across different offsets.\nTo achieve this, rather than relying on a static value, we implement a custom rolling XOR algorithm that leverages a multi-byte key array and introduces position-dependent mutation.\nstatic VOID CustomRollingXor(IN PBYTE dataBuffer, IN SIZE_T dataLen, IN PBYTE keyBuffer, IN SIZE_T keyLen) { SIZE_T keyIndex = 0; for (SIZE_T i = 0; i \u0026lt; dataLen; i++) { // Step 1: Calculate a dynamic mask (salt) unique to the current offset (i) BYTE positionSalt = (BYTE)(i * 0x9B) ^ (BYTE)(i \u0026gt;\u0026gt; 3); // Step 2: XOR the data byte with both the key byte and the position salt dataBuffer[i] = dataBuffer[i] ^ keyBuffer[keyIndex] ^ positionSalt; // Step 3: Advance the key index; wrap around to the beginning if the end is reached keyIndex++; if (keyIndex == keyLen) { keyIndex = 0; } } } This function extends basic XOR into a significantly more resilient routine. Here is how it functions step-by-step:\n1. Multi-Byte Rolling Key Structure (keyIndex) Instead of a single byte, encryption relies on an array of bytes (e.g., 0xDE, 0xAD, 0xBE, 0xEF). The keyIndex counter steps through this array sequentially. When the end of the key buffer is reached, the condition if (keyIndex == keyLen) resets the index to zero. This cyclic reuse (\u0026ldquo;rolling\u0026rdquo;) allows continuous encryption regardless of the payload\u0026rsquo;s length.\n2. Position-Dependent Mutation (positionSalt) This step disrupts repetitive byte patterns and defeats simple frequency analysis. In addition to the key byte, the current buffer index (i) is incorporated into the operation via the positionSalt variable:\ni * 0x9B: The current index is multiplied by a constant multiplier (0x9B). i \u0026gt;\u0026gt; 3: The index is bitwise right-shifted by 3 bits and XORed with the multiplied value. The resulting dynamic salt is combined with the key byte to transform the data byte. The primary benefit: even if the input contains dozens of identical consecutive characters (such as repeated 0x41 / 'A' bytes), each encrypted output byte will differ because the index i is unique for every position.\nPredicting or reversing this position-mutated sequence via static frequency analysis is substantially more difficult than breaking standard single-byte XOR schemes.\nHow is This Technique Detected? Static Analysis The clues we can identify during static analysis include:\nSuspicious Mathematical Signatures: XOR operations are frequently used in legitimate software. However, if consecutive instructions such as XOR, MUL (multiplication), and SHR (right shift) are contained within a loop structure (CMP and JMP blocks) inside a single code block, this is highly likely to be flagged as a \u0026ldquo;Decryption Stub.\u0026rdquo;\nHigh Entropy: If sections like .data or .rsrc contain large chunks of meaningless, completely random data (high entropy), this raises strong suspicion that the section contains encrypted data or code.\nDynamic Analysis During dynamic analysis, analysts typically look at the following workflow:\nHardware Breakpoint: A hardware breakpoint is placed directly on the memory address of the high-entropy encrypted data identified during static analysis.\nSkipping the Loop: When execution begins and that memory region is accessed, the debugger triggers and pauses execution. Instead of stepping into the complex loop line-by-line (Step Into), the analyst places an execution breakpoint on the instruction immediately following the end of the loop and resumes execution (Run).\nMemory Dump: As soon as the loop finishes, the program hits the breakpoint and pauses again. At this point, the elaborate Rolling XOR routine has completed its job. Inspecting the memory window reveals the fully decrypted, plain-text original payload in place of the encrypted bytes. Right-clicking and selecting \u0026ldquo;Dump to File\u0026rdquo; allows the original payload to be extracted in seconds.\n","permalink":"https://0xgently.github.io/en/posts/xor/","summary":"\u003ch2 id=\"xor-encryption\"\u003eXOR Encryption\u003c/h2\u003e\n\u003cp\u003eIn the world of malware development, one of the most critical factors determining the lifespan of your payload is how effectively you can conceal it. If you embed shellcode or a PE file into memory in plain text, static analysis tools and antivirus (AV) signatures will flag it within seconds.\u003c/p\u003e\n\u003cp\u003eThis is precisely where payload encryption comes into play. In this post, we will examine the XOR algorithm—one of the most fundamental yet heavily modified encryption techniques in the malware domain. We will start from the basics and progress toward complex, position-dependent rolling structures.\u003c/p\u003e","title":"XOR Encryption"},{"content":"IPv4/v6 DeObfuscation In this post, we will examine how a network configuration function, which looks completely ordinary from the outside, is transformed into a shellcode decoder. Our goal is to understand the attacker\u0026rsquo;s mindset and design our defensive systems to catch these types of \u0026ldquo;normal-looking anomalies.\u0026rdquo;\nNote: You can find the entirety of the code explained in this text at malware-development-library Furthermore, once you establish the logic of this topic and examine the full version of the code, you can also construct DeObfuscation processes applied to other techniques. They are pretty much the same anyway; you only need to make a few modifications, that is all.\nAt the center of this technique lies the RtlIpv4StringToAddressA function located within ntdll.dll. When you look at Microsoft\u0026rsquo;s official documentation, you will see that this function is quite simple and has a single purpose: to take a text-based (string) IPv4 address like \u0026quot;192.168.1.1\u0026quot; and convert it into a 4-byte binary struct in_addr structure so that it can be used in network sockets.\nWe, however, will view this function not as a network configuration tool, but as a built-in \u0026ldquo;decoder\u0026rdquo; function embedded into the heart of the system that accepts plain text as a parameter and produces 4-byte pure machine code (hex) in return.\n// Call our resolved IPv4 to address function to decode the current IP string into binary bytes NTSTATUS status = pRtlIpv4ToAddr(ObfuscatedIpv4Array[i], FALSE, \u0026amp;pszTerminator, pCurrentWritePos); The most critical point here is the 4th parameter, the pCurrentWritePos variable. Under normal circumstances, the API expects a struct in_addr* pointer to write the converted data. However, we are passing a PBYTE (byte array pointer) that we allocated ourselves.\nIn the software world, this concept is called Type-Punning. In C and C++ languages, the operating system does not care much about the \u0026ldquo;type\u0026rdquo; of the pointer you provide at runtime; it only focuses on the memory address that the pointer points to. While the API thinks it is writing an innocent network configuration to that address, we are actually forcing it to write our 4-byte shellcode piece, disguised as an IP address, directly into our own buffer.\n// Check if the API function failed to execute correctly if (status != 0x00000000) { // Free the allocated memory buffer before returning to prevent memory leaks HeapFree(GetProcessHeap(), 0, pDecodedBuffer); return NULL; } If the API fails for any reason (for example, if the IP string in the array is in an incorrect format), the code does not panic and exit blindly. First, it returns the memory it allocated back to the system using HeapFree. Remember; memory leaks cause processes to run unstably, and the behavioral analysis radars of modern EDRs are highly sensitive to such instabilities.\n// Move the current writing pointer 4 bytes forward to prepare for the next chunk pCurrentWritePos += 4; In each iteration of the loop, the target pointer is shifted forward by exactly 4 bytes. This is because each IPv4 address is mathematically a 32-bit (4-byte) data block. Thanks to this simple pointer arithmetic applied, dozens of IP addresses, which stand like independent texts within the code, turn into a contiguous and holistic shellcode in memory.\nAfter the shellcode is successfully written to memory, an alignment problem comes into play here.\nYour actual payload — for example, a Cobalt Strike beacon, a reverse shell, or a custom implant — will not always be an exact multiple of 4 in length. Let\u0026rsquo;s say you have a 201-byte payload. If you want to divide this into 4-byte IP blocks, a 3-byte padding must be appended to the end so that the final block is not left incomplete. Tools that prepare payloads usually do this according to the PKCS#7 standard, which we see very frequently in cryptography.\nIf your loader code attempts to execute the payload without clearing these redundant padding bytes in memory, the processor will try to read these padding bytes as Assembly instructions. And because of this, the process immediately crashes, throwing an \u0026ldquo;Access Violation\u0026rdquo; or \u0026ldquo;Illegal Instruction\u0026rdquo; error.\nTo prevent this, it is mandatory for us to go to the end of the payload and clear that padding part:\n// Read the very last byte of the decoded buffer to determine the padding value BYTE padValue = pDecodedBuffer[totalBufferSize - 1]; In the first step, we read the very last byte of the decoded memory area. The PKCS#7 standard relies on a very simple logic: the appended padding value is equal to the number of bytes added to ensure alignment. In other words, if a 3-byte padding was added to complete the payload to multiples of 4 bytes, the last 3 bytes will be 0x03, 0x03, 0x03. By reading only the last byte, we learn what this padding amount is (3 in our example).\n// Validate that the padding value is within the mathematically possible range if (padValue == 0 || padValue \u0026gt; 4 || padValue \u0026gt; totalBufferSize) { // Free the heap memory if the padding value is corrupted or invalid HeapFree(GetProcessHeap(), 0, pDecodedBuffer); return NULL; } This control line is one of the main elements that ensures the safety and stability of the code. Since an IPv4 address can be at most 4 bytes, the applied padding value can mathematically only be 1, 2, 3, or 4. If the padValue we read is 0 or greater than 4, it means the structure of the data is corrupted somehow. In this case, the code refuses to proceed blindly, aborts the operation, and cleans up the memory.\n// Loop backwards through the padding region to verify all padding bytes match for (BYTE i = 1; i \u0026lt;= padValue; i++) { // Check if the current trailing byte matches the expected padding value if (pDecodedBuffer[totalBufferSize - i] != padValue) { // Free the allocated memory if a corrupted byte sequence is detected HeapFree(GetProcessHeap(), 0, pDecodedBuffer); return NULL; } } We must verify whether this data is truly a valid padding sequence. The loop above reads backward from the end as much as the detected padding size. If the padValue is 3, it confirms that all 3 bytes it reads retrospectively are indeed 0x03. If even one of them is different, there is corrupted data involved, and the operation is canceled.\n// Subtract the verified padding amount from the total buffer size to get the actual payload size *DecodedSize = totalBufferSize - padValue; In the final step, we subtract the padding amount from the total buffer size we allocated at the beginning. Now we have a smooth and pure payload of *DecodedSize in size, completely ready to be executed, and stripped of padding bytes.\nIn offensive security research, the core of the work stems from knowing how the operating system manages memory, grasping the hardware counterparts of pointers within the C language, and understanding how you can stretch the type-checking of APIs in your own favor.\nWe saw how an ordinary string conversion function written for network administrators from the outside turns into a flawless shellcode decoder with a bit of pointer arithmetic and a correct Type-Punning approach. The things that complicate defense architectures and malware analysis are not just complex encryption algorithms, but these elegant engineering approaches that use the system\u0026rsquo;s own regular operation against the system itself.\n","permalink":"https://0xgently.github.io/en/posts/%C4%B1pv4_deobf/","summary":"\u003ch2 id=\"ipv4v6-deobfuscation\"\u003eIPv4/v6 DeObfuscation\u003c/h2\u003e\n\u003cp\u003eIn this post, we will examine how a network configuration function, which looks completely ordinary from the outside, is transformed into a shellcode decoder. Our goal is to understand the attacker\u0026rsquo;s mindset and design our defensive systems to catch these types of \u0026ldquo;normal-looking anomalies.\u0026rdquo;\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eNote\u003c/strong\u003e: You can find the entirety of the code explained in this text at \u003ca href=\"https://github.com/0xGently/malware-development-library/tree/main/Payload-Encryption-and-Obfuscation/06-IPv4_IPv6_DeObf\"\u003emalware-development-library\u003c/a\u003e Furthermore, once you establish the logic of this topic and examine the full version of the code, you can also construct DeObfuscation processes applied to other techniques. They are pretty much the same anyway; you only need to make a few modifications, that is all.\u003c/p\u003e","title":"IPv4/IPv6 DeObfuscation"},{"content":"ChaCha20 Encryption In this post, I will talk about ChaCha20. Frankly, I thought about how to introduce the topic, but I couldn\u0026rsquo;t come up with a sensible introductory sentence. Therefore, I prefer to dive straight into the subject.\nFile size and simplicity are everything. There is only one reason we prefer ChaCha20: it is compact, fast, and eliminates the need to write an extra decryption routine.\nSo why do we choose ChaCha20? Because it is exceptionally fast, does not rely on dedicated CPU cryptographic hardware extensions (such as AES-NI), and is far less cumbersome to implement from scratch in C/C++. Furthermore, we use the exact same code for both encryption and decryption, which minimizes the overall stub size of the software.\nToday, we will examine how this mechanism works through standard ChaCha20 C code. Without getting bogged down in hundreds of lines of memory management or error handling, we focus strictly on the 3 most crucial aspects of the algorithm.\nNote: If you would like to inspect the remainder of the code, you can find it via the following link: malware-development-library\n1. Initializing the State Matrix ChaCha20 is a stream cipher. Its objective is to use the provided key to generate a very long, pseudo-random keystream. Everything begins with constructing a 4x4 table (a state matrix of 16 32-bit words / 64 bytes) in memory.\nLooking at the chachaInit function in the code, we observe that the first row of this matrix is populated with specific constants:\n// First 4 words of the state matrix for a 32-byte (256-bit) key w[0] = 0x61707865; w[1] = 0x3320646E; w[2] = 0x79622D32; w[3] = 0x6B206574; When you place these hex values in little-endian order and convert them to ASCII, the phrase \u0026quot;expand 32-byte k\u0026quot; emerges. The remainder of the matrix is populated by the user-defined 32-byte key, block counter, and nonce values.\nNOTE: If you ever employ a cryptographic routine other than ChaCha20 and insert this constant segment somewhere in your binary, you can confuse and mislead analysts reverse engineering your code. At least, while not guaranteed to fool seasoned analysts, it represents an interesting anti-analysis concept.\n2. ARX and Permutation (Mixing) The security of the algorithm stems from mixing the values within that state matrix using an intricate mathematical routine prior to keystream generation. Let us take a close look at the core engine of the implementation, the QUARTER_ROUND macro:\n#define QUARTER_ROUND(a, b, c, d) \\ { \\ a += b; \\ d ^= a; \\ d = ROL32(d, 16); \\ c += d; \\ b ^= c; \\ b = ROL32(b, 12); \\ a += b; \\ d ^= a; \\ d = ROL32(d, 8); \\ c += d; \\ b ^= c; \\ b = ROL32(b, 7); \\ } Notice that there are only three elementary operations here: Addition (+=), XOR (^=), and Bitwise Left Rotation (ROL32). In cryptography, this design is known as an ARX (Add-Rotate-Xor) architecture. It puts minimal strain on the CPU while providing high diffusion and non-linearity.\nHow is this macro applied to the state table? In the chachaProcessBlock function, we observe the following sequence:\n// First, mix the columns... QUARTER_ROUND(w[0], w[4], w[8], w[12]); QUARTER_ROUND(w[1], w[5], w[9], w[13]); QUARTER_ROUND(w[2], w[6], w[10], w[14]); QUARTER_ROUND(w[3], w[7], w[11], w[15]); // ...then mix the diagonals. QUARTER_ROUND(w[0], w[5], w[10], w[15]); QUARTER_ROUND(w[1], w[6], w[11], w[12]); QUARTER_ROUND(w[2], w[7], w[8], w[13]); QUARTER_ROUND(w[3], w[4], w[9], w[14]); This sequence iterates over the table across 20 rounds (hence the name ChaCha20). The algorithm alternates 10 column rounds and 10 diagonal rounds. You can think of this like scrambling a Rubik\u0026rsquo;s cube 20 times consecutively. The resulting keystream is so thoroughly diffused that recovering the original plaintext without the key is mathematically intractable.\n3. Encrypting the Payload We constructed the matrix, executed the permutation rounds, and derived the keystream (k). Now we arrive at the core task: encrypting the target payload.\nThe transformation occurs within this concise for loop (located inside the chachaCipher function):\n// XOR the payload (input) with the keystream (k) for(i = 0; i \u0026lt; n; i++) { output[i] = input[i] ^ k[i]; } The resulting keystream (k) is XORed byte by byte with our original payload buffer (input). The result is a fully transformed, high-entropy output buffer (output).\nAs stated earlier, XOR is an involutory operation. When executing or processing this payload on the target system, initializing the cipher with the same key and nonce generates the exact same keystream, allowing the encrypted data to be decrypted using the identical routine without writing a separate decryptor function.\nAuxiliary Operations If you review the linked source code, you will find additional logic beyond the sections highlighted above. To focus on the cryptographic architecture rather than boilerplate, those segments were omitted. A brief summary of those supporting components:\nMatrix Population w[4] = LOAD32LE(key); w[5] = LOAD32LE(key + 4); w[6] = LOAD32LE(key + 8); w[7] = LOAD32LE(key + 12); // ... This is a data parsing/packing routine. The key and nonce buffers are passed in as flat byte arrays. Because the internal state of ChaCha20 consists of 32-bit registers, conditional branches verify whether the key is 128-bit (16 bytes) or 256-bit (32 bytes), followed by inspecting nonce sizing (64-bit, 96-bit, or 128-bit). Once verified, the LOAD32LE (Load 32-bit Little Endian) helper parses the stream into 4-byte chunks and loads them into their corresponding matrix slots (w).\nThe chachaDeinit Function void chachaDeinit(ChachaContext *context) { // Clear ChaCha context osMemset(context, 0, sizeof(ChachaContext)); } This function serves a single purpose: scrubbing RAM once operations are complete. In cryptographic implementations, leaving keys or internal state tables lingering in volatile memory introduces security risks. osMemset overwrites the context struct with zeros to ensure secure cleanup.\nIn Summary The operational philosophy of ChaCha20 remains straightforward: construct the state matrix, diffuse the data using ARX rounds, and XOR the output with the payload buffer.\n","permalink":"https://0xgently.github.io/en/posts/chacha20/","summary":"\u003ch2 id=\"chacha20-encryption\"\u003eChaCha20 Encryption\u003c/h2\u003e\n\u003cp\u003eIn this post, I will talk about ChaCha20. Frankly, I thought about how to introduce the topic, but I couldn\u0026rsquo;t come up with a sensible introductory sentence. Therefore, I prefer to dive straight into the subject.\u003c/p\u003e\n\u003cp\u003eFile size and simplicity are everything. There is only one reason we prefer ChaCha20: it is compact, fast, and eliminates the need to write an extra decryption routine.\u003c/p\u003e\n\u003cp\u003eSo why do we choose ChaCha20? Because it is exceptionally fast, does not rely on dedicated CPU cryptographic hardware extensions (such as AES-NI), and is far less cumbersome to implement from scratch in C/C++. Furthermore, we use the exact same code for both encryption and decryption, which minimizes the overall stub size of the software.\u003c/p\u003e","title":"Chacha20 Encryption"},{"content":"IPv4/IPv6 Obfuscation In the world of cybersecurity, when we want to hide data, we generally have two fundamental paths ahead of us: we either encrypt the data directly (such as AES, RC4, or XOR) or we obfuscate it.\nWhen developing software, obfuscating the code sometimes plays a critical role not only in protecting intellectual property but also in bypassing security mechanisms. For example, suppose we have a shellcode and we want to hide it. Although encryption is the first solution that comes to mind, it might not always be the smartest route. This is because encrypted data causes the entropy (randomness rate) in that specific section of the file to skyrocket, and this situation immediately catches the radar of security analysts or AV/EDR solutions. At this point, obfuscating without drawing attention transforms into a much more logical alternative.\nBut what if we could take this shellcode and make it look like a perfectly innocent network configuration file? In this post, that is exactly what we are going to do. We will split our shellcode into pieces and convert each piece into a standard IPv4 address string (such as 192.168.1.1). This way, instead of suspicious hex values inside our file, hundreds of innocent-looking IP addresses will appear.\nNote: You can find the entirety of the code explained in this text at malware-development-libraryFurthermore, once you grasp the logic of this topic, the workflow of other techniques (such as MAC and UUID obfuscation) is exactly identical.\n1. The GeneratePkcs7Padding Function An IPv4 address consists of exactly 4 bytes (e.g., 192.168.1.1 -\u0026gt; C0 A8 01 01). However, a shellcode generated from tools like msfvenom or Cobalt Strike is not guaranteed to have a size that is an exact multiple of 4. For example, if you have a 275-byte shellcode and you divide it into groups of 4, you will be left with 3 bytes at the very end. When the program attempts to generate that final IP address, it will crash and throw a memory error because it cannot find the 4th byte.\nTo prevent this, we need to fill the end of our shellcode with safe bytes (padding) and align the total size to an exact multiple of 4. We will have a function dedicated solely to this operation, and this function will make the byte count of the shellcode perfectly divisible by 4. Let\u0026rsquo;s name the function that will perform this operation GeneratePkcs7Padding. This function will take the address of the raw shellcode and its size from us.\nBefore moving on to the code, let\u0026rsquo;s define our constants (macros) that will ease our workload and increase code readability:\n#define IPV4_ALIGNMENT 4 // To create an IPv4 address, we must take 4 bytes from the shellcode Now, let\u0026rsquo;s take a look at a portion of the function we named GeneratePkcs7Padding:\nuint8_t padValue = (uint8_t)(IPV4_ALIGNMENT - (originalSize % IPV4_ALIGNMENT)); Let\u0026rsquo;s assume the size of our shellcode (originalSize) is 275 bytes. The operation 275 % 4 yields a remainder of 3. When we subsequently perform the operation 4 - 3 = 1, we understand that the amount of padding we need to add is 1 byte, and we assign this value to our variable named padValue.\nsize_t newBufferSize = originalSize + padValue; // Allocating memory equal to newBufferSize uint8_t* paddedBuffer = (uint8_t*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, newBufferSize); if (!paddedBuffer) return NULL; Right after, we add this 1 byte to the original size (newBufferSize = 276) and allocate our space in memory. Here, a tiny detail saves the day: since we call the HeapAlloc function with the HEAP_ZERO_MEMORY flag, Windows does not just allocate space for us, it automatically zeroes out (0x00) all the bytes within that region.\nWhen we copy our original shellcode here, it will become 276 bytes with the added padding. Our payload is now ready and perfectly divisible by 4!\n2. The ConvertBytesToIP Function Now, it is time for our ConvertBytesToIP function, which will take these 4-byte segments and produce strings from them in the format of 255.255.255.255.\nMany poorly written codes beg the operating system for new memory every single time. To avoid losing performance, we will move the memory allocation task outside of this function. This function will only perform translation into an \u0026ldquo;externally provided text box (buffer).\u0026rdquo;\nTo convert the hex values we have into decimal numbers separated by dots, we utilize snprintf or sprintf_s, which are secure functions of the standard C library:\nsprintf_s(outIpString, bufferSize, \u0026#34;%d.%d.%d.%d\u0026#34;, pBytes[0], pBytes[1], pBytes[2], pBytes[3]); Let\u0026rsquo;s say the next 4 bytes in line from our shellcode are 0xFC, 0x48, 0x83, 0xE4. The sprintf_s function converts these hex values into the decimal base in order, inserts dots between them, and writes them into outIpString. Consequently, those 4 bytes inside our shellcode turn into the IP address 252.72.131.228!\n3. The PayloadIPv4Array Function We have completed the preparation phases; we have data that is perfectly divisible into multiples of 4 and a function that converts this data into an IP address. Now, the only thing left is to write the main control mechanism that will manage this process from start to finish, reading the data in blocks and printing it to the screen.\n// 1. We call our padding function and retrieve our aligned data uint8_t* paddedData = GeneratePkcs7Padding(rawData, rawSize, \u0026amp;paddedSize); // 2. We calculate how many 4-byte pieces (chunks) we will obtain size_t chunkCount = paddedSize / 4; In the first step, we call our GeneratePkcs7Padding function and receive the aligned data directly into our pointer named paddedData.\nIn the line immediately below, simple math takes place: if an aligned data of 276 bytes is formed as a result of the padding process, thanks to the operation 276 / 4 = 69, we calculate that we will obtain a total of exactly 69 IPv4 addresses (chunkCount).\n// We open a fixed space on the Stack to avoid allocating memory continuously inside the loop char ipStackBuffer[16]; // Our loop that converts each 4-byte piece into the IP format for (size_t i = 0; i \u0026lt; chunkCount; i++) { ConvertBytesToIP(\u0026amp;paddedData[i * 4], ipStackBuffer, sizeof(ipStackBuffer)); // ... (Operations to print the obtained string to the screen) } The for loop we opened will run as many times as the total amount we obtained, and it will process the data step by step in each iteration. The most important detail here is the line char ipStackBuffer[16]. Instead of slowing down the program by continuously requesting memory from the operating system inside the loop, we allocate a fixed space of 16 characters on the stack of the processor, and we change the text inside it in every iteration. This is a massive step for performance and cleanliness.\nThe operation of traversing over the actual data lies within the logic of \u0026amp;paddedData[i * 4]. Inside that raw data stack in memory, the following actions take place step by step:\nIn the first step (i = 0): The calculation 0 * 4 = 0 is made, and the address of the 0th byte in memory is sent to the function. In the second step (i = 1): It becomes 1 * 4 = 4, and the function jumps directly to the 4th byte in memory. In the third step (i = 2): The address of the 8th byte is read directly. In this manner, without sacrificing a single shred of performance, we scan our entire shellcode by jumping precisely in 4-byte increments in memory.\nIPv6 If we want to perform obfuscation in IPv6 as well, we need to use the exact same logic that we applied for IPv4 above; almost nothing changes. The logic of the code or the functions used are completely identical. The only difference is that you must remember that it needs to be a multiple of 16 this time, and you must adapt it to 2001:0db8:85a3:0000:0000:8a2e:0370:7334 while merging.\nI will explain the Deobfuscation part of the matter in another post.\n","permalink":"https://0xgently.github.io/en/posts/%C4%B1pv4_obf/","summary":"\u003ch2 id=\"ipv4ipv6-obfuscation\"\u003eIPv4/IPv6 Obfuscation\u003c/h2\u003e\n\u003cp\u003eIn the world of cybersecurity, when we want to hide data, we generally have two fundamental paths ahead of us: we either encrypt the data directly (such as AES, RC4, or XOR) or we obfuscate it.\u003c/p\u003e\n\u003cp\u003eWhen developing software, obfuscating the code sometimes plays a critical role not only in protecting intellectual property but also in bypassing security mechanisms. For example, suppose we have a shellcode and we want to hide it. Although encryption is the first solution that comes to mind, it might not always be the smartest route. This is because encrypted data causes the \u003cstrong\u003eentropy (randomness rate)\u003c/strong\u003e in that specific section of the file to skyrocket, and this situation immediately catches the radar of security analysts or AV/EDR solutions. At this point, obfuscating without drawing attention transforms into a much more logical alternative.\u003c/p\u003e","title":"IPv4/IPv6 Obfuscation"},{"content":"\nIn the world of network security and cybersecurity, understanding how a packet is formed, how it can be manipulated, and how to craft custom packets is a crucial skill. The Python programming language and the Scapy library provide an excellent platform for learning these skills. In this post, I will detail step-by-step the fundamentals of creating and analyzing network packets using Scapy.\n1. What is Scapy and Why is it Important? Scapy is a powerful network packet manipulation library written in Python. Essentially, it allows us to:\nCapture network traffic (sniffing)\nInspect and analyze packets\nCreate custom packets (crafting)\nModify packets (manipulation)\nSimulate network tests and security scenarios\nScapy\u0026rsquo;s power comes from its ability to be used in both passive and active network tasks. Passively, we can analyze existing traffic on the network; actively, we can send custom packets to target systems and perform response analysis.\n2. Basic Concepts Before performing packet manipulation and crafting, it is very important to understand the structure of network packets. Network packets are directly related to the layers of the OSI model:\nEthernet (Data Link Layer): Ensures the transmission of packets on the physical network. Contains source and destination MAC addresses.\nIP (Network Layer): Ensures the routing of packets across networks. IP addresses reside here.\nTCP/UDP (Transport Layer): Data transport protocols. TCP is connection-oriented, whereas UDP is connectionless.\nApplication Layer: High-level protocols such as HTTP, DNS, and FTP reside here.\nWith Scapy, we can perform packet manipulation at every layer. For example, we can change the port number of a TCP packet, spoof an ICMP packet, or forge DNS queries.\n3. Core Logic of Working with Scapy When working with Scapy, it is necessary to understand three fundamental concepts:\nPacket Crafting: When creating a packet with Scapy, we can think of each layer as an independent object. For example, you create an IP layer, a TCP layer, and a payload (data) layer, and stack/bind them together using the slash (/) operator.\nSending Packets: To send the packet you crafted, we use Scapy\u0026rsquo;s functions such as send() or sr(). send() is typically used for the TCP/IP (Layer 3) level, while sendp() is used for the Ethernet (Layer 2) level.\nPacket Sniffing: We use the sniff() function to capture packets on the network. This is essential for real-time analysis and attack simulations. We can filter traffic to capture only specific protocols or ports.\nNow, these sections will be explained in detail.\n4. Packet Crafting In Scapy, packet crafting essentially means designing the network data unit (packet) as you desire. The logic here operates across OSI layers:\nLayer Logic Each layer represents a layer in the OSI model: Ethernet (Layer 2), IP (Layer 3), TCP/UDP (Layer 4), Payload (Layer 7).\nEach layer is created as an independent object. For example, IP() is an IP layer object, and TCP() is a TCP layer object.\nLayers are stacked on top of each other using the / operator. That is:\nEthernet / IP / TCP / Payload\nConstructing this structure determines how the packet will be transported across the network and how the destination will interpret it.\nBind Logic The lower layer acts like the carrier layer for the upper layer. In other words, TCP is carried over IP, and IP over Ethernet.\nThe bind logic is implemented in Scapy using the / operator. Example: ip_layer / tcp_layer / Raw(\u0026quot;data\u0026quot;).\nThis creates a natural packet hierarchy according to the OSI model.\nCrafting Logic First, we select which protocol to use (IP, TCP, UDP, ICMP).\nYou customize the fields of the layer:\nIP: src, dst\nTCP: sport, dport, flags\nPayload: Raw(load=\u0026quot;data\u0026quot;)\nYou combine the layers using /.\nYour packet is now crafted and ready to be sent.\nExample Logic Flow:\nYou want to send a TCP SYN packet to a target IP.\nCreate IP layer → Create TCP layer → Add payload (optional) → Combine with / → Packet is ready.\nPython\npacket = IP(dst=\u0026#34;192.168.1.30\u0026#34;) / TCP(dport=80, flags=\u0026#34;S\u0026#34;) / Raw(load=\u0026#34;Hello World!\u0026#34;) 5. Sending Packets Sending the crafted packet is just as critical a step as creating it. Scapy allows us to do this at two main levels:\n5.1 Network Layer (IP Layer) Transmission send() or sr() is used for packets at the TCP/IP layer.\nsend(): Sends the packet and does not wait for an answer.\nsr(): Sends the packet and waits for a response; typically used in request-response scenarios.\nLogic: We are sending the IP layer to the destination. TCP or UDP sits on top of this layer, and the payload is carried above that. Here, the transmission takes place at the IP layer, not \u0026ldquo;from Layer 2.\u0026rdquo; We are operating at the Layer 3 level according to the OSI model.\n5.2 Data Link Layer (Ethernet Layer) Transmission sendp() or srp() is used for packets at the Ethernet level.\nAt this level, MAC addresses are directly relevant.\nWe are operating at Layer 2 according to the OSI model.\nLogic: We send data over the Ethernet frame without relying on the operating system to construct the Layer 2 header. This is generally used in low-level testing or intra-LAN scenarios.\n6. Packet Sniffing Sniffing is capturing and analyzing network packets in real time. Scapy accomplishes this via the sniff() function.\nSniffing Logic Filtering:\nInstead of capturing all traffic, we can filter for specific protocols (TCP, ICMP, UDP) or ports.\nExample: filter=\u0026quot;tcp and port 80\u0026quot;\nLayer-by-Layer Analysis:\nCaptured packets can be analyzed in detail using packet.show().\nLayers can be viewed individually: IP, TCP, payload.\nReal-Time Capture or File Export:\nsniff(count=10) captures 10 packets.\nIt is possible to save captured packets to a pcap file for later analysis.\nOnce you properly grasp these three steps — crafting, sending, and sniffing — you can both analyze the data flow across OSI layers and manipulate it in a controlled manner for security testing.\n","permalink":"https://0xgently.github.io/en/posts/packet-crafting/","summary":"\u003cp\u003e\u003cimg alt=\"Scapy Logo\" loading=\"lazy\" src=\"/assets/img/scapy-image.webp\"\u003e\u003c/p\u003e\n\u003cp\u003eIn the world of network security and cybersecurity, understanding how a packet is formed, how it can be manipulated, and how to craft custom packets is a crucial skill. The Python programming language and the Scapy library provide an excellent platform for learning these skills. In this post, I will detail step-by-step the fundamentals of creating and analyzing network packets using Scapy.\u003c/p\u003e\n\u003ch2 id=\"1-what-is-scapy-and-why-is-it-important\"\u003e1. What is Scapy and Why is it Important?\u003c/h2\u003e\n\u003cp\u003eScapy is a powerful network packet manipulation library written in Python. Essentially, it allows us to:\u003c/p\u003e","title":"Packet Crafting with Scapy"}]