Calling the ChangeWindowTreeProtection function as a bypass to SetWindowDisplayAffinity

Umair Akbar
14 min readFeb 14, 2023
Random image from unsplash
Introduction
- Overview of the problem
- Basic concepts of calling functions in a remote process

Calling the ChangeWindowTreeProtection function

- Overview of the ChangeWindowTreeProtection function
- Techniques for calling the function in a remote process
- Using the assembly code stub technique
- Using the CreateRemoteThread function

Creating our program
- Creating a remote process using CreateProcess
- Allocating memory in the target process using VirtualAllocEx
- Writing assembly code to the allocated memory using WriteProcessMemory
- Modifying the target function to jump to the allocated memory
- Calling a remote function using the CallRemoteFunction function
- Handling errors and exceptions

Summary of key concepts and techniques
- Final thoughts and recommendations

Before we move forward, obligatory: I want to clarify that any actions taken were solely for educational purposes and to understand what is currently achievable. My goal was to expand my knowledge and gain a better understanding of what is and is not possible in a theoretical sense. I conducted all experiments in a controlled test lab environment, on my own personal equipment. It is my hope that by sharing this knowledge with the community, others can learn from my experiences and better understand the theoretical possibilities in this field.

Let’s begin!

Scenario of calling the ChangeWindowTreeProtection function as a bypass to SetWindowDisplayAffinity, we use the following steps in C++:

  1. Load the win32kfull library using the LoadLibrary function.
  2. Get a pointer to the ChangeWindowTreeProtection function using the GetProcAddress function.
  3. Call the ChangeWindowTreeProtection function with the appropriate parameters to set the protection level of the window.

Here’s a code snippet that demonstrates the usage of the ChangeWindowTreeProtection function:

#include <Windows.h>

int main()
{
// Load the win32kfull library
HMODULE hWin32K = LoadLibrary(L"win32kfull.dll");

if (hWin32K == NULL)
{
// Handle the error
return 1;
}

// Get a pointer to the ChangeWindowTreeProtection function
typedef BOOL(WINAPI* PChangeWindowTreeProtection)(HWND hWnd, DWORD flags);
PChangeWindowTreeProtection pChangeWindowTreeProtection =
(PChangeWindowTreeProtection)GetProcAddress(hWin32K, "ChangeWindowTreeProtection");

if (pChangeWindowTreeProtection == NULL)
{
// Handle the error
FreeLibrary(hWin32K);
return 1;
}

// Call the ChangeWindowTreeProtection function
HWND hWnd = GetForegroundWindow(); // Get the handle to the active window
DWORD flags = 0x00000001; // Set the protection level to PROTECTED
BOOL result = pChangeWindowTreeProtection(hWnd, flags);

if (!result)
{
// Handle the error
FreeLibrary(hWin32K);
return 1;
}

// Free the library handle
FreeLibrary(hWin32K);

return 0;
}

Next, I want the code to codecave return WDA-NONE’ (bypass the blackscreen) in windows at the api level. The solution involves writing a small assembly code stub that will execute our function and return the desired value. This stub can then be injected into our target process as a codecave using DLL injection or process hollowing.

; Assembly code to return WDA_NONE (0) from a codecave

push 0 ; Push the value of WDA_NONE onto the stack
ret ; Return from the function

This code simply pushes the value of WDA_NONE (which is defined as 0) onto the stack and then returns from the function. When this code is executed, the target process will effectively “see” a call to the desired function that returns the desired value.

To inject this code as a codecave we do the following steps:

  1. Open the target process using the OpenProcess function.
  2. Allocate a block of memory in the target process using the VirtualAllocEx function. This block of memory will serve as the codecave.
  3. Write the assembly code stub to the allocated memory using the WriteProcessMemory function.
  4. Modify the target function to jump to the codecave instead of executing the original code. This can be done by overwriting the first few bytes of the target function with a jump instruction that points to the address of the codecave.
  5. Execute the target function. The jump instruction will redirect the execution flow to the codecave, which will return the desired value.

Here is an example C++ code snippet that demonstrates the injection of the assembly code stub as a codecave:

#include <Windows.h>

// Define the assembly code stub
BYTE code[] = { 0x6A, 0x00, 0xC3 }; // Push 0 onto the stack, then return

int main()
{
// Open the target process
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, <processId>);

if (hProcess == NULL)
{
// Handle the error
return 1;
}

// Allocate a block of memory in the target process
LPVOID pCodecave = VirtualAllocEx(hProcess, NULL, sizeof(code), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

if (pCodecave == NULL)
{
// Handle the error
CloseHandle(hProcess);
return 1;
}

// Write the assembly code stub to the allocated memory
SIZE_T bytesWritten = 0;
BOOL result = WriteProcessMemory(hProcess, pCodecave, code, sizeof(code), &bytesWritten);

if (!result || bytesWritten != sizeof(code))
{
// Handle the error
VirtualFreeEx(hProcess, pCodecave, 0, MEM_RELEASE);
CloseHandle(hProcess);
return 1;
}

// Modify the target function to jump to the codecave
LPVOID pTargetFunction = <address of the target function>;
BYTE jump[] = { 0xE9, 0x00, 0x00, 0x00, 0x00 }; // Jump instruction
DWORD relativeAddress = (DWORD)pCodecave - ((DWORD)pTargetFunction + sizeof(jump));
*(DWORD*)(jump + 1) = relativeAddress; // Calculate the relative address of the codecave
bytesWritten = 0;
result = WriteProcessMemory(hProcess, pTargetFunction, jump, sizeof(jump), &bytesWritten);

if (!result || bytesWritten != sizeof(jump))
{
// Handle the error
VirtualFreeEx(hProcess, pCodecave, 0, MEM_RELEASE);
CloseHandle(hProcess);
return 1;
}

// Execute the target function
DWORD returnValue = 0;
result = CallRemoteFunction(hProcess, pTargetFunction, &returnValue);

if (!result)
{
// Handle the error
VirtualFreeEx(hProcess, pCodecave, 0, MEM_RELEASE);
CloseHandle(hProcess);
return 1;
}

// Free the allocated memory and close the process handle
VirtualFreeEx(hProcess, pCodecave, 0, MEM_RELEASE);
CloseHandle(hProcess);

// Handle the return value as necessary
return 0;

Note that the CallRemoteFunction function used in this code snippet is a custom function that takes care of the mechanics of calling a function in a remote process and returning the result.

Here, I implement the CallRemoteFunction

#include <Windows.h>
#include <TlHelp32.h>

// Define the assembly code stub
BYTE code[] = { 0x6A, 0x00, 0xC3 }; // Push 0 onto the stack, then return

// Function to call a remote function in a target process
BOOL CallRemoteFunction(HANDLE hProcess, LPVOID pFunction, PDWORD pReturnValue)
{
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFunction, NULL, 0, NULL);

if (hThread == NULL)
{
return FALSE;
}

WaitForSingleObject(hThread, INFINITE);
GetExitCodeThread(hThread, pReturnValue);
CloseHandle(hThread);

return TRUE;
}

// Function to get the process ID of a running process
DWORD GetProcessIdByName(const WCHAR* processName)
{
DWORD processId = 0;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 processEntry = { sizeof(PROCESSENTRY32) };

if (Process32First(hSnapshot, &processEntry))
{
do
{
if (_wcsicmp(processEntry.szExeFile, processName) == 0)
{
processId = processEntry.th32ProcessID;
break;
}
} while (Process32Next(hSnapshot, &processEntry));
}

CloseHandle(hSnapshot);

return processId;
}

int main()
{
// Get the process ID of the target process
DWORD processId = GetProcessIdByName(L"target.exe");

if (processId == 0)
{
// Handle the error
return 1;
}

// Open the target process
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);

if (hProcess == NULL)
{
// Handle the error
return 1;
}

// Allocate a block of memory in the target process
LPVOID pCodecave = VirtualAllocEx(hProcess, NULL, sizeof(code), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

if (pCodecave == NULL)
{
// Handle the error
CloseHandle(hProcess);
return 1;
}

// Write the assembly code stub to the allocated memory
SIZE_T bytesWritten = 0;
BOOL result = WriteProcessMemory(hProcess, pCodecave, code, sizeof(code), &bytesWritten);

if (!result || bytesWritten != sizeof(code))
{
// Handle the error
VirtualFreeEx(hProcess, pCodecave, 0, MEM_RELEASE);
CloseHandle(hProcess);
return 1;
}

// Modify the target function to jump to the codecave
LPVOID pTargetFunction = <address of the target function>;
BYTE jump[] = { 0xE9, 0x00, 0x00, 0x00, 0x00 }; // Jump instruction
DWORD relativeAddress = (DWORD)pCodecave - ((DWORD)pTargetFunction + sizeof(jump));
*(DWORD*)(jump + 1) = relativeAddress; // Calculate the relative address of the codecave
bytesWritten = 0;
result = WriteProcessMemory(hProcess, pTargetFunction, jump, sizeof(jump), &bytesWritten);

if (!result || bytesWritten != sizeof(jump))
{
// Handle the error
VirtualFreeEx(hProcess, pCodecave, 0, MEM_RELEASE);
CloseHandle(hProcess);
return 1;
}

// Execute the target function
DWORD returnValue = 0;
result = CallRemoteFunction(hProcess, pTargetFunction, &returnValue);

if (!result)
{
// Handle the error
VirtualFreeEx(hProcess, pCodecave, 0, MEM_RELEASE);
CloseHandle(hProcess);
return 1;
}

// Free the allocated memory and close the process handle
VirtualFreeEx(hProcess, pCodecave, 0, MEM_RELEASE);
CloseHandle(hProcess);

// Handle the return value as necessary
return 0;

Note that the `GetProcessIdByName` function is used to get the process ID of the target process. This function takes the name of the target process as a parameter and returns the corresponding process ID. This function uses the Windows API functions `CreateToolhelp32Snapshot`, `Process32First`, and `Process32Next` to iterate through the running processes and find the target process.

Also, the `<address of the target function>` on line 47 should be replaced with the actual address of the target function in the target process. The specific address depends on the function you are targeting, so you will need to find it using a debugger or other means.

Here’s an example of how to use WinDbg to find the address of a function in a target process:

  1. Open WinDbg and select File > Attach to a Process.
  2. Select the target process from the list and click OK.
  3. In the command window, type sxe ld to break on module loads.
  4. Type g to continue running the target process until a new module is loaded.
  5. Type lm to list all loaded modules.
  6. Find the module that contains the target function and note its base address.
  7. Type x <module base>+<offset> to disassemble the module and find the address of the target function.
  8. Replace <address of the target function> in the code with the actual address of the target function.

But who wants to do this? We can do it with code.

Function to get the address of a function

#include <Windows.h>
#include <Psapi.h>

// Function to get the address of a function in a module
LPVOID GetFunctionAddress(HMODULE hModule, const char* functionName)
{
// Get the export directory of the module
PIMAGE_NT_HEADERS pNTHeader = ImageNtHeader(hModule);
PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((LPBYTE)hModule + pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

// Get the array of function addresses and names
PDWORD pFunctionAddresses = (PDWORD)((LPBYTE)hModule + pExportDirectory->AddressOfFunctions);
PDWORD pFunctionNames = (PDWORD)((LPBYTE)hModule + pExportDirectory->AddressOfNames);
PWORD pFunctionNameOrdinals = (PWORD)((LPBYTE)hModule + pExportDirectory->AddressOfNameOrdinals);

// Find the index of the function name in the name array
DWORD index = (DWORD)-1;

for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++)
{
const char* pFunctionName = (const char*)((LPBYTE)hModule + pFunctionNames[i]);

if (_stricmp(pFunctionName, functionName) == 0)
{
index = i;
break;
}
}

if (index == (DWORD)-1)
{
return NULL;
}

// Get the address of the function
DWORD functionAddress = pFunctionAddresses[pFunctionNameOrdinals[index]];
LPVOID pFunction = (LPVOID)((LPBYTE)hModule + functionAddress);

return pFunction;
}

// Function to get the module handle of a running process by its name
HMODULE GetModuleHandleByName(DWORD processId, const WCHAR* moduleName)
{
HMODULE hModule = NULL;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, processId);
MODULEENTRY32 moduleEntry = { sizeof(MODULEENTRY32) };

if (Module32First(hSnapshot, &moduleEntry))
{
do
{
if (_wcsicmp(moduleEntry.szModule, moduleName) == 0)
{
hModule = moduleEntry.hModule;
break;
}
} while (Module32Next(hSnapshot, &moduleEntry));
}

CloseHandle(hSnapshot);

return hModule;
}

int main()
{
// Get the process ID of the target process by window name
HWND hWnd = FindWindow(NULL, L"target window name");

if (hWnd == NULL)
{
// Handle the error
return 1;
}

DWORD processId = 0;
GetWindowThreadProcessId(hWnd, &processId);

if (processId == 0)
{
// Handle the error
return 1;
}

// Get the module handle of the target module by name
HMODULE hModule = GetModuleHandleByName(processId, L"target.dll");

if (hModule == NULL)
{
// Handle the error
return 1;
}

// Get the address of the target function by name
const char* functionName = "targetFunction";
LPVOID pTargetFunction = GetFunctionAddress(hModule, functionName);

if (pTargetFunction == NULL)
{
// Handle the error
return 1;
}

// Open the target process
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);

if (hProcess == NULL)
{
// Handle the error
return 1;
}

// Allocate a block of memory in the target process
LPVOID pCodecave = VirtualAllocEx(hProcess, NULL, sizeof(code), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

if (pCodecave == NULL)
{
// Handle the error
CloseHandle(hProcess);
return 1;
}

// Write the assembly code stub to the allocated memory
SIZE_T bytesWritten = 0;
BOOL result = WriteProcessMemory(hProcess, pCodecave, code, sizeof(code), &bytesWritten);

if (!result || bytesWritten != sizeof(code))
{
// Handle the error
VirtualFreeEx(hProcess, pCodecave, 0, MEM_RELEASE);
CloseHandle(hProcess);
return 1;
}

// Modify the target function to jump to the codecave
BYTE jump[] = { 0xE9, 0x00, 0x00, 0x00, 0x00 }; // Jump instruction
DWORD relativeAddress = (DWORD)pCodecave - ((DWORD)pTargetFunction + sizeof(jump));
*(DWORD*)(jump + 1) = relativeAddress; // Calculate the relative address of the codecave
bytesWritten = 0;
result = WriteProcessMemory(hProcess, pTargetFunction, jump, sizeof(jump), &bytesWritten);

if (!result || bytesWritten != sizeof(jump))
{
// Handle the error
VirtualFreeEx(hProcess, pCodecave, 0, MEM_RELEASE);
CloseHandle(hProcess);
return 1;
}

// Execute the target function
DWORD returnValue = 0;
result = CallRemoteFunction(hProcess, pTargetFunction, &returnValue);

if (!result)
{
// Handle the error
VirtualFreeEx(hProcess, pCodecave, 0, MEM_RELEASE);
CloseHandle(hProcess);
return 1;
}

// Free the allocated memory and close the process handle
VirtualFreeEx(hProcess, pCodecave, 0, MEM_RELEASE);
CloseHandle(hProcess);

// Handle the return value as necessary
return 0;

This code uses the `GetWindowThreadProcessId` function to get the process ID of the target process by window name, the `GetModuleHandleByName` function to get the module handle of the target module by name, and the `GetFunctionAddress` function to get the address of the target function by name.

Once the address of the target function is obtained, the remaining steps of allocating memory in the target process, writing the assembly code to the allocated memory, and modifying the target function to jump to the codecave can be performed as before.

Putting it all together

here’s a step-by-step guide on how to use the C++ code I provided to call the ChangeWindowTreeProtection function from win32kfull in a test environment:

  1. Open a new Visual Studio project and create two source files named main.cpp and RemoteFunctionCall.cpp.
  2. Copy and paste the following code into main.cpp:
#include <Windows.h>

// Define the assembly code stub
BYTE code[] = { 0x6A, 0x00, 0xC3 }; // Push 0 onto the stack, then return

// Function to call a remote function in a target process
bool CallRemoteFunction(HANDLE hProcess, LPVOID pFunction, DWORD* pReturnValue)
{
// Create a remote thread in the target process to call the function
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFunction, NULL, 0, NULL);

if (hThread == NULL)
{
return false;
}

// Wait for the thread to exit
DWORD result = WaitForSingleObject(hThread, INFINITE);

if (result != WAIT_OBJECT_0)
{
CloseHandle(hThread);
return false;
}

// Get the return value of the function
result = GetExitCodeThread(hThread, pReturnValue);

if (result == 0)
{
CloseHandle(hThread);
return false;
}

// Close the thread handle
CloseHandle(hThread);

return true;
}

int main()
{
// Get the process ID of the target process by window name
HWND hWnd = FindWindow(NULL, L"test window");

if (hWnd == NULL)
{
// Handle the error
return 1;
}

DWORD processId = 0;
GetWindowThreadProcessId(hWnd, &processId);

if (processId == 0)
{
// Handle the error
return 1;
}

// Get the module handle of win32kfull.dll
HMODULE hModule = GetModuleHandle(L"win32kfull.dll");

if (hModule == NULL)
{
// Handle the error
return 1;
}

// Get the address of the ChangeWindowTreeProtection function
LPVOID pFunction = GetProcAddress(hModule, "ChangeWindowTreeProtection");

if (pFunction == NULL)
{
// Handle the error
return 1;
}

// Open the target process
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);

if (hProcess == NULL)
{
// Handle the error
return 1;
}

// Allocate a block of memory in the target process
LPVOID pCodecave = VirtualAllocEx(hProcess, NULL, sizeof(code), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

if (pCodecave == NULL)
{
// Handle the error
CloseHandle(hProcess);
return 1;
}

// Write the assembly code stub to the allocated memory
SIZE_T bytesWritten = 0;
BOOL result = WriteProcessMemory(hProcess, pCodecave, code, sizeof(code), &bytesWritten);

if (!result || bytesWritten != sizeof(code))
{
// Handle the error
VirtualFreeEx(hProcess, pCodecave, 0, MEM_RELEASE);
CloseHandle(hProcess);
return 1;
}

// Modify the target function to jump to the codecave
BYTE jump[] = { 0xE9, 0x00, 0x00, 0x00, 0x00 }; // Jump instruction
DWORD relativeAddress = (DWORD)pCodecave - ((DWORD)pFunction + sizeof(jump));
*(DWORD*)(jump + 1) = relativeAddress; // Calculate the relative address of the codecave
bytesWritten = 0;
result = WriteProcessMemory(hProcess, pFunction, jump, sizeof(jump), &bytesWritten);

if (!result || bytesWritten != sizeof(jump))
{
// Handle the error
VirtualFreeEx(hProcess, pCodecave, 0, MEM_RELEASE);
CloseHandle(hProcess);
return 1;
}

// Execute the target function
DWORD returnValue = 0;
result = CallRemoteFunction(hProcess, pFunction, &returnValue);

if (!result)
{
// Handle the error
VirtualFreeEx(hProcess, pCodecave, 0, MEM_RELEASE);
CloseHandle(hProcess);
return 1;
}

// Free the allocated memory and close the process handle
VirtualFreeEx(hProcess, pCodecave, 0, MEM_RELEASE);
CloseHandle(hProcess);

// Handle the return value as necessary
return 0;

3. Copy and paste the following code into `RemoteFunctionCall.cpp`:

#include <Windows.h>

// Function to call a remote function in a target process
bool CallRemoteFunction(HANDLE hProcess, LPVOID pFunction, DWORD* pReturnValue)
{
// Create a remote thread in the target process to call the function
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFunction, NULL, 0, NULL);

if (hThread == NULL)
{
return false;
}

// Wait for the thread to exit
DWORD result = WaitForSingleObject(hThread, INFINITE);

if (result != WAIT_OBJECT_0)
{
CloseHandle(hThread);
return false;
}

// Get the return value of the function
result = GetExitCodeThread(hThread, pReturnValue);

if (result == 0)
{
CloseHandle(hThread);
return false;
}

// Close the thread handle
CloseHandle(hThread);

return true;

4. Build the project and run the executable. Make sure to replace `”test window”` with the name of the window you want to target, and adjust the assembly code stub if necessary.

  1. Once the executable is running, it will try to locate the window with the specified name and obtain the process ID of the target process. If it fails to find the window, it will return an error and exit.
  2. The executable will then try to obtain the module handle of win32kfull.dll and the address of the ChangeWindowTreeProtection function. If either of these operations fail, it will return an error and exit.
  3. Next, the executable will allocate a block of memory in the target process, write the assembly code stub to the allocated memory, and modify the ChangeWindowTreeProtection function to jump to the codecave. If any of these operations fail, it will return an error and exit.
  4. Finally, the executable will execute the ChangeWindowTreeProtection function in the target process by calling it remotely, using the CallRemoteFunction function. If the remote function call fails, it will return an error and exit.
  5. If all of the operations are successful, the executable will free the allocated memory and close the process handle before exiting.

Note that this code assumes that the ChangeWindowTreeProtection function can be called successfully using the assembly code stub. If this is not the case, you may need to modify the assembly code or use a different technique to call the function (see below).

If the assembly code stub is not working to call the ChangeWindowTreeProtection function, you can try a different technique, such as using the CreateRemoteThread function to create a remote thread in the target process that will call the function. Here's an example of how to modify the code to use this technique:

  1. Replace the following code in main.cpp:
// Define the assembly code stub
BYTE code[] = { 0x6A, 0x00, 0xC3 }; // Push 0 onto the stack, then return

with

// Define the function parameters
BOOL enable = TRUE;
DWORD protect;

// Get the address of the ChangeWindowTreeProtection function
HMODULE hModule = GetModuleHandle(L"win32kfull.dll");

if (hModule == NULL)
{
// Handle the error
return 1;
}

LPVOID pFunction = GetProcAddress(hModule, "ChangeWindowTreeProtection");

if (pFunction == NULL)
{
// Handle the error
return 1;
}

// Call the ChangeWindowTreeProtection function using CreateRemoteThread
DWORD threadId = 0;
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFunction, &enable, 0, &threadId);

if (hThread == NULL)
{
// Handle the error
return 1;
}

// Wait for the thread to exit
DWORD result = WaitForSingleObject(hThread, INFINITE);

if (result != WAIT_OBJECT_0)
{
// Handle the error
CloseHandle(hThread);
return 1;
}

// Get the return value of the function
result = GetExitCodeThread(hThread, &protect);

if (result == 0)
{
// Handle the error
CloseHandle(hThread);
return 1;
}

// Close the thread handle
CloseHandle(hThread);
  1. Remove the CallRemoteFunction function from main.cpp and RemoteFunctionCall.cpp.
  2. Build the project and run the executable. The modified code will call the ChangeWindowTreeProtection function using CreateRemoteThread instead of the assembly code stub.

In this article, we have explored how to call the ChangeWindowTreeProtection function in a target process using C++. This function is part of the win32kfull.dll library in the Windows operating system, and is used to protect the window tree of a given window.

We started by discussing the basic concepts behind calling functions in a remote process, and outlined some of the challenges and considerations involved in this process. We then moved on to the specifics of calling the ChangeWindowTreeProtection function, and discussed several different techniques for achieving this, including using an assembly code stub and using the CreateRemoteThread function.

Next, we provided a step-by-step guide for implementing a C++ program that calls the ChangeWindowTreeProtection function using the assembly code stub technique. This guide included detailed code examples for creating a remote process, allocating memory in the target process, writing assembly code to the allocated memory, and modifying the target function to jump to the allocated memory. We also provided a separate code example for calling a remote function using the CreateRemoteThread function.

--

--

Umair Akbar

Hi, I'm Umair Akbar. Cloud Engineer. Artificially Intelligent. Experienced in deploying and managing cloud infrastructure, proficient in AWS and Google Cloud