Official site anti-cheat Ultra Core Protector

Home Download F.A.Q. Addons Monitor Forum Support Advertise English version site UCP Anti-Cheat  Russian version site UCP Anti-Cheat
Ultra Core Protector - is the client-server anti-cheat freeware, for server protection from unscrupulous players.

Abilities Supported games  
Half-Life
Condition Zero
Counter-Strike 1.6
Day of Defeat
Adrenaline Gamer
Team Fortress Classic
Counter-Strike Source
MU Online
Ragnarok Online
Half-Life 2 Deathmatch
Adrenaline Gamer 2
Team Fortress 2
1-Line User-Mode Multi Anti-Cheat Bypass

What is this bypass?

This bypass gives you a process handle ready to use (usable for external cheats or for injection).

This process handle is a copy of an existing process handle from another process (e.g. system processes, anti-virus, …)

This bypass shares some commonalities with a previous bypass proof of concept I released, the Handle HiJacking through inheritance.

In this last bypass PoC, we injected into a process that had a handle with sufficient permissions (system processes, anti-viruses, …), we made the handle inheritable with Process Hacker and spawned our cheat as child process, which started it with the handle inherited as child process.

It was quite uneasy to use as it required to inject into the system process, hardcode our cheat path, finding the handle ID manually and hardcode it, and many annoying other things.
There was as well some detection vectors that are solved in this bypass, for example, our cheat process or DLL injector spawned was a child of the system process, which is unusual and raises suspicion among other things.

This time: No injection required, 1 or 2 lines of code, depending on how you want it configured, and that’s all.

How does this bypass work?

In this bypass I locate the handles to your game available in the system (using NtQuerySystemInformation, DuplicateHandle requesting only PROCESS_QUERY_LIMITED_INFORMATION, and NtQueryObject)
With this information, I create a process handle on the process that has a handle on your game (that I will refer to as “the parent process”) with interesting permissions.
I then use this handle to request some memory in which I write a small shellcode to set the desired handle as inheritable and I execute it (VirtualAllocEx, WriteProcessMemory, CreateRemoteThread)
Once inheritable by child processes, I start a new instance of the running program as child of the process having the handle fully externally.

Instead of calling CreateProcess or CreateProcessAsUser internally, I take advantage of the STARTUPINFOEX structure given to CreateProcess in another process.
This way it is possible to spawn child processes to any process you have a handle on with the permission PROCESS_CREATE_PROCESS.
This bypass should be placed at the beginning of your cheat as it will terminate and restart itself with the right settings (as a child of the process having a handle) to get the handle
Note that this is only true for the default configuration, it is possible to use it without a restart, I explain how further.

The bypass takes 3 parameter, the process name of your target (e.g. “Game.exe”), a parent process PID or one of the automatic constants, and a boolean to choose if you want to make it an orphan process (to reduce the detection vectors).
The parent process PID can be a specific PID (e.g. if you have an anti virus that has handles, put your anti virus PID here), otherwise I implemented 3 very useful constants: PARENT_CSRSS, PARENT_LSASS, and PARENT_PCASVC
As you may know if you read my posts and releases, modern Windows OS have some system processes and services that have natively process handles on other processes including games.
On Windows 10 the service PcaSvc (Program Compatility Assistant Service) has process handles with PROCESS_ALL_ACCESS and LSASS.EXE has a process handle with Query information, VM operation, VM read, VM write, Duplicate handles.

On Windows 7 there is in addition to these 2 CSRSS.EXE that has PROCESS_ALL_ACCESS (unfortunately this process is protected by Protected Process Light, PPL, on Windows 8+)
Using the automatic constants I implemented will make the bypass find the PID of the desired process or service automatically for you at every run, hassle-free.
You can use this bypass to get handles on games of any architecture (x86 & x64 games).

However, since I use shellcode to make the handles inheritable in the parent process and I haven’t find a way (yet) to get the base address of kernel32.dll in an x64 process from an x86 process, your cheat has to be either in the same architecture as the process that has the handle, or your cheat can be x64 with an x86 parent.

If this is troublesome for any reason, you can still use this bypass split into 2 programs:
– A launcher compiled in the same architecture as the parent process having the handle
– The receiver (your cheat) compiled in any architecture

Let’s see how this works step by step…

Operations, step by step (in the default behaviour)

First instance

Include the bypass and integrate it in your cheat, if possible at the very beginning, with the following line:

HANDLE hGame = GetBastardHandle(L"Game.exe", PARENT_PCASVC, TRUE);

The first instance will check if it has a handle to the target process “Game.exe”.
It will not be the case, so it will look for a process handle to the target process “Game.exe” belonging to the parent process specified (or to the automatically found parent if you used the constants to locate the system processes/services automatically)
This is done with NtQuerySystemInformation, DuplicateHandle requesting only PROCESS_QUERY_LIMITED_INFORMATION, and NtQueryObject.
When found, it will open a handle on the parent, request some memory, determine the parent process architecture and write some shellcode to make the handle inheritable.
The shellcode is pushed and executed with VirtualAllocEx, WriteProcessMemory, and CreateRemoteThread, the shellcode executed is a call to SetHandleInformation to make it inheritable.
If this succeeds, the second instance is started as a child of the parent process now having the handle inheritable externally with a classic CreateProcess, passing the parenthood information using the STARTUPINFOEX.
The handle is then set back as non inheritable with shellcode again to cover our tracks ans avoid detection vector.

The first instance terminates itself with ExitProcess(EXIT_SUCCESS);

Second instance
The second instance will check if it has a handle to the target process “Game.exe”.
This time it will be the case since we inherited it.
In that case if you specified in third parameter that you want to make the process “orphan” – not seen as a child process of the process you inherited the handle from –
(in the default behaviour), the instance will check if it is child of the parent process having the handles.
It will be the case, therefore to become orphan, the bypass will create a third instance of itself, spawned as its own child process before terminating the second instance.
This allows to break the lineage/parenthood chain.
Click here to make a small fun experiment to observe this behaviour:

Start a command line prompt.
In the command line enter "title Parent" and hit enter, this will rename the title of the window to "Parent".
Go in the task manager, "Details" tab, and remember the PID of cmd.exe
Now type "start cmd" and hit enter, this will spawn a new command line prompt.
In this new terminal, enter "title Child" and hit enter to rename this command line prompt.
Still in the child enter "start mspaint", this will start Microsoft paint.
In the task manager, Details tab, right click the cmd.exe with the PID you remember and click on "End process tree".
This will kill the 2 command line prompts and Paint.
Now repeat the operation entirely, but before clicking on "End process tree", close the command line titled "Child", you will see that Paint is not killed when the process tree is terminated because the chain of parenthood is broken.
Note that this is not a foolproof solution, there are other ways to track process parenthood more efficiently but they have to be setup before hand, for example, with Job objects.

Note that if you set makeOrphan as FALSE, the bypass returns the HANDLE here and continues the rest of your code and its operations.

Note as well that if you decide to use this bypass in 2 parts (a launcher and a receiver) only this instance is executed by the receiver, which allows to use this bypass without any process termination involved.
In that case you will receive the handle with the following function:

HANDLE ReceiveBastardHandle(std::wstring targetProcess, DWORD parentPID, BOOL makeOrphan);

Which take the exact same parameter.

Third instance

The third and last instance will check if it has the handle to your target, it will be that case, it will check if it is the direct child of the parent process that has the handle, it will not be the case, it will then try to OpenProcess the parent (therefore, the second instance) to see if it is really orphan.
If the parent process is still running (unlikely), the process will wait until it terminates to always run as orphan for more security.
You can also configure the bypass to wait until a timeout and if this time is reached, it will attempt to kill the parent with TerminateProcess to become orphan forcefully.
You can also decide in the configuration if you allow the bypass to continue if it cannot become orphan or if it should stop.

How to use it?

Bypass Syntax (Prototype):

HANDLE GetBastardHandle(
_In_ std::wstring targetProcess,
_In_ DWORD parentPID,
_In_Opt_ BOOL makeOrphan
);

Parameters:

targetProcess [in]
Target process name including “.exe” at the end.
Example of correct parameter: L”DayZ.exe”

parentPID [in]
Unique Process IDentifyer (PID) of the process having a handle to targetProcess
3 constants are available for automatic usage:
PARENT_CSRSS will give you the handle inherited from csrss.exe with PROCESS_ALL_ACCESS permission by default (available only in Windows 7 and previous)
PARENT_LSASS will give you the handle inherited from lsass.exe with Query information, VM operation, VM read, VM write, Duplicate handles permission by default (all modern Windows OS)
PARENT_PCASVC will give your the handle inherited from the svchost.exe of the Program Compatibility Assistant service (PcaSvc) that has PROCESS_ALL_ACCESS permission by default (all modern Windows OS)

Remarks & Other behaviour

If you want to setup the bypass to run without process termination, you should make a launcher program, calling the function SetRemoteHandleInformation on the parent PID to make the handle inheritable, then MakeBastardChild with as second parameter your cheat. This will launch your cheat as child of the desired parent. In the launcher, reuse SetRemoteHandleInformation to reset the handle as non-inheritable and cover your tracks.
In your cheat, receive the handle with ReceiveBastardHandle and makeOrphan as FALSE to avoid it to respawn to orphanize.

makeOrphan [in]
Defines if the process will detect if it runs as orphan and become orphan if it is not the case.
You can customize this behaviour at compilation time by changing the #defines in the configuration part of Bastard.h

Bypass Source

Bastard.hpp

#pragma once
#include
#include
#include
#include
#include
#include
#include // For cross-architure support
#include "GetHandle.hpp"

#pragma comment (lib, "Dbghelp.lib") // For cross-architure support

// Bypass configuration
#define TIMEOUT_ORPHANIZE 5000 // Max time to wait for parent to die and make sure we are orphan. Best security = INFINITE | Failsafe = 0-10000
#define KILL_PARENT_IF_PROBLEM FALSE // If problem while orphaning, can try to kill parent. Best security = TRUE | Failsafe = FALSE
#define RUN_IF_CANT_ORPHAN TRUE // If impossible to orphan for any reason, you can decide to abort by security. Best security = FALSE | Failsafe = TRUE

// If using system processes handles, these defines will be used to find & use them automatically
#define PARENT_LSASS -1
#define PARENT_PCASVC -2
#define PARENT_CSRSS -3
#define PROCESS_NAME_LSASS L"lsass.exe"
#define PROCESS_NAME_CSRSS L"csrss.exe"
#define SERVICE_NAME_PCASVC L"PcaSvc"

#define CMDLINE_MAX_LENGTH 0x7FFF // See CreateProcess documentation
#define KERNEL32DLL L"kernel32.dll"
#define SET_HANDLE_INFO_API_FUNC "SetHandleInformation"

// Functions prototypes
HANDLE GetBastardHandle(std::wstring targetProcess, DWORD parentPID, BOOL makeOrphan = TRUE);
HANDLE ReceiveBastardHandle(std::wstring targetProcess, DWORD parentPID, BOOL makeOrphan = TRUE);
PROCESS_INFORMATION MakeBastardChild(DWORD dwParentPID, std::wstring childProcess = L"", std::wstring cmdLineArgs = L"");
std::vector GetPIDs(std::wstring targetProcessName = L"");
DWORD GetAutoParentPID(DWORD parentPID = 0);
DWORD GetOwnParentPid();
DWORD FindServicePid(LPCTSTR ServiceName);
BOOL processIsX64(HANDLE hProcess = NULL);
BOOL SetRemoteHandleInformation(DWORD parentProcess, HANDLE handleID, DWORD dwMask, DWORD dwFlags);
DWORD64 FindModuleBaseAddrInProcess(DWORD targetPID, std::wstring moduleToFind = L"");
DWORD64 GetRvaOfFunctionInDLL(std::wstring dllFile, std::string functionName);

HANDLE GetBastardHandle(std::wstring targetProcess, DWORD parentPID, BOOL makeOrphan) {
if (targetProcess == L"" || parentPID == NULL)
return NULL; // No target process or parent specified

// Finding parent process ID automatically if a constant has been used
DWORD foundParentPID = parentPID;
if (parentPID == PARENT_LSASS || parentPID == PARENT_PCASVC || parentPID == PARENT_CSRSS)
foundParentPID = GetAutoParentPID(parentPID);
if (foundParentPID == NULL)
return NULL; // Couldn't find parent PID

// First, making sure that we are not already the bastard child with the handle
HANDLE hBastard = ReceiveBastardHandle(targetProcess, foundParentPID, makeOrphan);
if (hBastard != NULL)
return hBastard; // We are the bastard with the handle already!

// Getting the handle ID to the target process in the parent and making it inheritable
HANDLE handleIdInParent = GetHandleIdTo(targetProcess, foundParentPID);
if (handleIdInParent == NULL)
return NULL; // No handle to target found in parent
if (!SetRemoteHandleInformation(foundParentPID, handleIdInParent, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT))
return NULL; // Failed to make handle inheritable

// Creating child process externally using STARTUPINFOEX which will inherit the desired handle
PROCESS_INFORMATION piBastard = MakeBastardChild(foundParentPID);
if (piBastard.dwProcessId == NULL)
return NULL; // Failed to make bastard child process

// Resetting handle as non inheritable to cover our tracks
if (!SetRemoteHandleInformation(foundParentPID, handleIdInParent, HANDLE_FLAG_INHERIT, 0))
return NULL; // Failed to make handle non-inheritable

ExitProcess(EXIT_SUCCESS); // Success, passing control to second instance
}

HANDLE ReceiveBastardHandle(std::wstring targetProcess, DWORD parentPID, BOOL makeOrphan) {
HANDLE handleIdToTarget = GetHandleIdTo(targetProcess);
if (handleIdToTarget == NULL)
return NULL; // We do not have a handle to the target process (we're not the bastard)

if (makeOrphan == FALSE)
goto GiveHandleNonInheritable; // User has not requested to make the process orphan, returning the desired handle directly

// Checking if we are orphan as demanded
DWORD bastardParentPid = GetOwnParentPid();
if (bastardParentPid == parentPID) {
// We are the direct child, orphaning by spawning new instance and exiting this one, breaking the parenthood chain
PROCESS_INFORMATION piBastard = MakeBastardChild(GetCurrentProcessId());
if (piBastard.dwProcessId == NULL) {
if (RUN_IF_CANT_ORPHAN)
goto GiveHandleNonInheritable;
else
return NULL;
}
ExitProcess(EXIT_SUCCESS); // Last instance spawned, terminating this one to break parenthood chain & make it orphan
}

HANDLE hParentSync = OpenProcess(SYNCHRONIZE, FALSE, bastardParentPid);
if (hParentSync == NULL) // Parent is dead, we are orphan
goto GiveHandleNonInheritable; // Success

// We are not orphan, waiting for parent to die
DWORD waitReturn = WaitForSingleObject(hParentSync, TIMEOUT_ORPHANIZE);
CloseHandle(hParentSync);

if (waitReturn != WAIT_OBJECT_0 && KILL_PARENT_IF_PROBLEM) {
HANDLE hParentTerm = OpenProcess(PROCESS_TERMINATE, FALSE, bastardParentPid);
if (hParentTerm == NULL)
goto GiveHandleNonInheritable;
TerminateProcess(hParentTerm, EXIT_FAILURE);
CloseHandle(hParentTerm);
}

GiveHandleNonInheritable:
SetHandleInformation(handleIdToTarget, HANDLE_FLAG_INHERIT, 0);
GiveHandleInheritable:
return handleIdToTarget;
}

// Spawns a process (by default another instance of the current process) as child of another process externally (using STARTUPINFOEX)
PROCESS_INFORMATION MakeBastardChild(DWORD dwParentPID, std::wstring childProcess, std::wstring cmdLineArgs) {
PROCESS_INFORMATION pi;
SecureZeroMemory(&pi, sizeof(pi));

// Initialising attributes for process creation
SIZE_T cbAttributeListSize = 0;
InitializeProcThreadAttributeList(NULL, 1, 0, &cbAttributeListSize);
PPROC_THREAD_ATTRIBUTE_LIST pAttributeList = NULL;
pAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, cbAttributeListSize);
if (NULL == pAttributeList)
return pi; // Failed
if (!InitializeProcThreadAttributeList(pAttributeList, 1, 0, &cbAttributeListSize))
return pi; // Failed

// Getting handle on parent with only PROCESS_CREATE_PROCESS permission (required by UpdateProcThreadAttribute)
HANDLE hParentProcess = NULL;
hParentProcess = OpenProcess(PROCESS_CREATE_PROCESS, FALSE, dwParentPID);
if (NULL == hParentProcess)
return pi; // Failed

// Updating the attribute list with the desired parent for the future process to start
if (!UpdateProcThreadAttribute(pAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &hParentProcess, sizeof(HANDLE), NULL, NULL))
return pi; // Failed

// If child process is unspecified, we will spawning another instance of the current process, getting own path & image name
if (childProcess == L"") {
WCHAR thisProgram[MAX_PATH] = L"";
DWORD myLength = GetModuleFileName(NULL, (LPWSTR)&thisProgram, MAX_PATH);
childProcess = thisProgram;
}

// Command line arguments specified, formating the wide string to give CreateProcess
std::wstring cmdLineFullArgs;
if (cmdLineArgs != L"") {
cmdLineFullArgs = L'"' + childProcess + L'"' + L' ' + cmdLineArgs;
cmdLineFullArgs.resize(CMDLINE_MAX_LENGTH); // Getting the max possible size to avoid access violation (accordingly to CreateProcess documentation)
}

// Creating the bastard child process
STARTUPINFOEX sie = { sizeof(sie) };
sie.lpAttributeList = pAttributeList;
CreateProcess(childProcess.c_str(), (LPWSTR)cmdLineFullArgs.c_str(), NULL, NULL, TRUE, EXTENDED_STARTUPINFO_PRESENT | CREATE_NEW_CONSOLE, NULL, NULL, &sie.StartupInfo, &pi);

// Whether it succeeded or not, returning the PROCESS_INFORMATION (can check if failed if everything is null inside, since it's zeroed)
return pi;
}

// Get PIDs from process name
std::vector GetPIDs(std::wstring targetProcessName) {
std::vector pids;
if (targetProcessName == L"")
return pids; // No process name given
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); // All processes
PROCESSENTRY32W entry; // Current process
entry.dwSize = sizeof entry;
if (!Process32FirstW(snap, &entry)) // Start with the first in snapshot
return pids;
do {
if (std::wstring(entry.szExeFile) == targetProcessName)
pids.emplace_back(entry.th32ProcessID); // Names match, add to list
} while (Process32NextW(snap, &entry)); // Keep going until end of snapshot
CloseHandle(snap);
return pids;
}

DWORD GetOwnParentPid() {
DWORD myParentPid = NULL, myPid = GetCurrentProcessId();
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); // All processes
PROCESSENTRY32W entry; // Current process
entry.dwSize = sizeof entry;
if (!Process32FirstW(snap, &entry)) // Start with the first in snapshot
return NULL;
do {
if (entry.th32ProcessID == myPid) // This is the current process
myParentPid = entry.th32ParentProcessID; // Names match, add to list
} while (Process32NextW(snap, &entry)); // Keep going until end of snapshot
CloseHandle(snap);
return myParentPid;
}

DWORD FindServicePid(LPCTSTR ServiceName) {
SC_HANDLE ServiceManager;
SC_HANDLE Service;
SERVICE_STATUS_PROCESS* SvcStatus;
DWORD BytesNeeded;
LPBYTE ByteBuffer[sizeof(SERVICE_STATUS_PROCESS)];
DWORD ProcessId;

ServiceManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

if (!ServiceManager)
return NULL;

Service = OpenService(ServiceManager, ServiceName, SERVICE_QUERY_STATUS);

if (!Service) {
CloseServiceHandle(ServiceManager);
return NULL;
}

SvcStatus = (SERVICE_STATUS_PROCESS*)ByteBuffer;

if (!QueryServiceStatusEx(Service, SC_STATUS_PROCESS_INFO, (LPBYTE)SvcStatus, sizeof(ByteBuffer), &BytesNeeded)) {
SvcStatus = (SERVICE_STATUS_PROCESS*)malloc(BytesNeeded);
if (!QueryServiceStatusEx(Service, SC_STATUS_PROCESS_INFO, (LPBYTE)SvcStatus, BytesNeeded, &BytesNeeded)) {
free(SvcStatus);
CloseServiceHandle(Service);
CloseServiceHandle(ServiceManager);
return NULL;
}
}

ProcessId = SvcStatus->dwProcessId;

if (SvcStatus != (SERVICE_STATUS_PROCESS*)ByteBuffer)
free(SvcStatus);

CloseServiceHandle(Service);
CloseServiceHandle(ServiceManager);

return ProcessId;
}

// Attach to parent process
DWORD GetAutoParentPID(DWORD parentPID) {
DWORD foundParentPID = NULL;

// If using the system processes, getting the PID of the specified parents automatically
if (parentPID == PARENT_LSASS) {
/* Sort the PIDs increasingly, because there is normally only 1 instance of lsass.exe
But if there are more (e.g. the user has another process named lsass.exe running)
Then we will use the smallest PID since it should be the system one */
std::vector pidsLsass = GetPIDs(PROCESS_NAME_LSASS);
std::sort(pidsLsass.begin(), pidsLsass.end());
if (!pidsLsass.empty())
foundParentPID = pidsLsass[0];
}
if (parentPID == PARENT_CSRSS) {
/* There is normally only 2 instance of csrss.exe, 1 in session 0 and 1 session 0
The latter gets the handles with all access. We always use the csrss.exe
With the second smallest PID, it should be the right one */
std::vector pidsCsrss = GetPIDs(PROCESS_NAME_CSRSS);
std::sort(pidsCsrss.begin(), pidsCsrss.end());
if (pidsCsrss.size() >= 2)
foundParentPID = pidsCsrss[1];
}
if (parentPID == PARENT_PCASVC) {
/* PcaSvc (Program Compatibility Assistant Service) runs in a svchost.exe
We get the right process ID by querying which is the PID for the service
Using the service name. */
foundParentPID = FindServicePid(SERVICE_NAME_PCASVC);
}

return foundParentPID; // OpenProcess(PROCESS_CREATE_PROCESS | PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, parentPID);
}

/* Make a handle beloging to another process inheritable using shellcode execution (VirtualAlloc, WriteProcessMemory, CreateRemoteThread)
Because of the way the shell code is generated, it will only work if the bastard bypass is of same architecture as parent (x86 <> x86 || x64 <> x64) */
BOOL SetRemoteHandleInformation(DWORD parentProcess, HANDLE handleID, DWORD dwMask, DWORD dwFlags) {
BOOL status = FALSE;
// Getting handle to write shellcode and execute it
HANDLE hParentProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, parentProcess);
if (hParentProcess == NULL)
goto closehandle_exit;

//FARPROC functionAddr = NULL;
DWORD64 functionAddr = NULL;
BOOL targetIsX64 = processIsX64(hParentProcess);
BOOL sourceIsX64 = processIsX64();
if (targetIsX64 == sourceIsX64) {
// Taking advantage of kernel32.dll being normally loaded at the same address, getting function address locally
functionAddr = (DWORD64)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), SET_HANDLE_INFO_API_FUNC);
} else {
// Starting cross architecture black magic ...
DWORD64 kernel32BaseInTarget = FindModuleBaseAddrInProcess(parentProcess, KERNEL32DLL); // Kernel32.dll base
if (kernel32BaseInTarget == NULL)
goto closehandle_exit; // Curently only supports x64 > x86

// Getting function RVA from the kernel32.dll file
WCHAR winDir[MAX_PATH] = L"";
UINT winDirPathLen = GetWindowsDirectoryW((LPWSTR)&winDir, MAX_PATH);
std::wstring kernel32DllPath = winDir;
if (processIsX64() == TRUE) // x64 > x86
kernel32DllPath += L"\\SysWOW64\\"; // Forcing search to x86 kernel32.dll
else // x86 > x64
kernel32DllPath += L"\\Sysnative\\"; // Cannot use System32, otherwise Windows sends automatically to SysWOW64
kernel32DllPath += L"kernel32.dll";
DWORD64 kernel32Base = GetRvaOfFunctionInDLL(kernel32DllPath, SET_HANDLE_INFO_API_FUNC);
if (kernel32Base == NULL)
goto closehandle_exit;

// Calculating function address in loaded kernel32.dll for other architecture
functionAddr = kernel32Base + kernel32BaseInTarget;
}

// Raw shellcodes without addresses or values
UCHAR shellcode_x86[] = {
0x68, 0, 0, 0, 0, // push dwFlags offset +1
0x68, 0, 0, 0, 0, // push dwMask offset +6
0x68, 0, 0, 0, 0, // push handleID offset +11
0xB8, 0, 0, 0, 0, // mov eax, &SetHandleInformation offset +16
0xFF, 0xD0, // call eax
0xC3 // ret
// TODO: Get the return in eax and mov it to the address we can read to get the BOOL returned
};
UCHAR shellcode_x64[] = {
0x48, 0xB9, 0, 0, 0, 0, 0, 0, 0, 0, // mov rcx, handleID offset +2
0x48, 0xBA, 0, 0, 0, 0, 0, 0, 0, 0, // mov rdx, dwMask offset +12
0x49, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0, // mov r8, dwFlags offset +22
0x48, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0, // mov rax, &SetHandleInformation offset +32
0x48, 0x81, 0xEC, 0x18, 0, 0, 0, // sub rsp, 0x18
0xFF, 0xD0, // call rax
0x48, 0x81, 0xC4, 0x18, 0, 0, 0, // add rsp, 0x18
0xC3 // ret
// TODO: Get the return in eax and mov it to the address we can read to get the BOOL returned
};

// Filling addresses and values, and parameters for writing and executing it
SIZE_T shellcodeSize = 0;
PUCHAR pShellcode = NULL;
if (targetIsX64 == FALSE) { // x86
*(ULONG*)((PUCHAR)shellcode_x86 + 1) = (ULONG)(ULONG_PTR)dwFlags;
*(ULONG*)((PUCHAR)shellcode_x86 + 6) = (ULONG)(ULONG_PTR)dwMask;
*(ULONG*)((PUCHAR)shellcode_x86 + 11) = (ULONG)(ULONG_PTR)handleID;
*(ULONG*)((PUCHAR)shellcode_x86 + 16) = (ULONG)(ULONG_PTR)functionAddr;
shellcodeSize = sizeof(shellcode_x86);
pShellcode = (UCHAR*)&shellcode_x86;
}
if (targetIsX64 == TRUE) { // x64
*(ULONG*)((PUCHAR)shellcode_x64 + 2) = (ULONG)(ULONG_PTR)handleID;
*(ULONG*)((PUCHAR)shellcode_x64 + 12) = (ULONG)(ULONG_PTR)dwMask;
*(UINT64*)((PUCHAR)shellcode_x64 + 22) = (UINT64)(PUINT64)dwFlags;
*(UINT64*)((PUCHAR)shellcode_x64 + 32) = (UINT64)(PUINT64)functionAddr;
shellcodeSize = sizeof(shellcode_x64);
pShellcode = (UCHAR*)&shellcode_x64;
}

/* // For debug
for (int i(0); i < shellcodeSize; ++i) printf("%X ", (BYTE)pShellcode[i]); */ // Pushing shellcode to parent process void* shellCodeAddr = VirtualAllocEx(hParentProcess, NULL, shellcodeSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (shellCodeAddr == nullptr) return FALSE; if (!WriteProcessMemory(hParentProcess, shellCodeAddr, pShellcode, shellcodeSize, NULL)) goto virtualfree_exit; // Execution DWORD dwThreadID; HANDLE hThread = CreateRemoteThread(hParentProcess, NULL, 0, (LPTHREAD_START_ROUTINE)shellCodeAddr, NULL, 0, &dwThreadID); if (hThread == NULL) goto virtualfree_exit; WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); // TODO: Get the return value status = TRUE; // Success virtualfree_exit: VirtualFreeEx(hParentProcess, shellCodeAddr, shellcodeSize, MEM_RELEASE); closehandle_exit: CloseHandle(hParentProcess); return status; } BOOL processIsX64(HANDLE hProcess) { SYSTEM_INFO sysInfo; GetNativeSystemInfo(&sysInfo); WORD arch = sysInfo.wProcessorArchitecture; if (arch != PROCESSOR_ARCHITECTURE_AMD64 && arch != PROCESSOR_ARCHITECTURE_IA64) return FALSE; // Processor is not x64, processes cannot be x64 if (hProcess == NULL) // If no process specified, checking own process hProcess = GetCurrentProcess(); BOOL isX86 = FALSE; IsWow64Process(hProcess, &isX86); if (isX86) return FALSE; else return TRUE; } // Gets base address of specified module in process from PID, if no module given, returns process base address (x86 & x64) DWORD64 FindModuleBaseAddrInProcess(DWORD targetPID, std::wstring moduleToFind) { HANDLE snapshotModules = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, targetPID); if (snapshotModules == INVALID_HANDLE_VALUE) return NULL; MODULEENTRY32 me32; me32.dwSize = sizeof(MODULEENTRY32); // Set the size of the structure before using it. if (!Module32First(snapshotModules, &me32)) { CloseHandle(snapshotModules); return 0x0; } // For Case insensitive search std::wstring moduleToFindUp = moduleToFind; std::transform(moduleToFindUp.begin(), moduleToFindUp.end(), moduleToFindUp.begin(), ::towupper); DWORD64 baseAddr = NULL; do { if (moduleToFind == L"") { // No module specified, returning process base address baseAddr = (DWORD64)me32.modBaseAddr; break; } // For Case insensitive search std::wstring moduleName = me32.szModule; std::wstring moduleNameUp = moduleName; std::transform(moduleNameUp.begin(), moduleNameUp.end(), moduleNameUp.begin(), ::towupper); if (moduleNameUp == moduleToFindUp) { baseAddr = (DWORD64)me32.modBaseAddr; break; } } while (Module32Next(snapshotModules, &me32)); CloseHandle(snapshotModules); return baseAddr; } // Gets the relative virtual address of a function by its name in a DLL, supports x64 & x86 DLLs DWORD64 GetRvaOfFunctionInDLL(std::wstring dllFile, std::string functionName) { if (dllFile.empty() || functionName.empty()) return NULL; // Mapping DLL file into memory for analysis HANDLE libFile = CreateFile(dllFile.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, NULL); if (libFile == INVALID_HANDLE_VALUE || libFile == NULL) return 0x0; HANDLE libMapping = CreateFileMapping(libFile, NULL, PAGE_READONLY | SEC_IMAGE, NULL, NULL, NULL); if (libMapping == INVALID_HANDLE_VALUE || libMapping == NULL) goto exit_closeFile; LPVOID lib = MapViewOfFile(libMapping, FILE_MAP_READ, NULL, NULL, NULL); if (lib == NULL) goto exit_closeMapping; // Determining if DLL is x86 or x64 BOOL dllIsX64 = TRUE; PIMAGE_NT_HEADERS headerNoArch = ImageNtHeader(lib); // Note: Only valid until first misalignment between 64 & 32 struct if (headerNoArch->FileHeader.Machine == IMAGE_FILE_MACHINE_I386)
dllIsX64 = FALSE;

// Getting headers and pointer to export directory
LPVOID header = NULL;
PIMAGE_EXPORT_DIRECTORY exports = NULL;
if (dllIsX64 == TRUE) {
header = (PIMAGE_NT_HEADERS64)ImageNtHeader(lib);
if (((PIMAGE_NT_HEADERS64)header)->Signature != IMAGE_NT_SIGNATURE || ((PIMAGE_NT_HEADERS64)header)->OptionalHeader.NumberOfRvaAndSizes == 0)
goto exit_unmapFile;
exports = (PIMAGE_EXPORT_DIRECTORY)((DWORD64)lib + (DWORD64)((PIMAGE_NT_HEADERS64)header)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
}
else {
header = (PIMAGE_NT_HEADERS32)ImageNtHeader(lib);
if (((PIMAGE_NT_HEADERS32)header)->Signature != IMAGE_NT_SIGNATURE || ((PIMAGE_NT_HEADERS32)header)->OptionalHeader.NumberOfRvaAndSizes == 0)
goto exit_unmapFile;
exports = (PIMAGE_EXPORT_DIRECTORY)((DWORD64)lib + (DWORD64)((PIMAGE_NT_HEADERS32)header)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
}
if (exports->AddressOfNames == 0 || (exports->NumberOfFunctions != exports->NumberOfNames))
goto exit_unmapFile; // TODO: Currently doesn't support DLLs with functions exportable only by ordinals or forwarded exports

// Iterating through exports to find function
DWORD64 addr = 0x0;
DWORD* exportNames = (DWORD*)((DWORD64)lib + exports->AddressOfNames);
DWORD* exportAddresses = (DWORD*)((DWORD64)lib + exports->AddressOfFunctions);
for (int i = 0; i < exports->NumberOfNames; i++) {
std::string name = (char*)((DWORD64)lib + (DWORD)exportNames[i]);
if (dllIsX64 == TRUE)
addr = (DWORD64)exportAddresses[i];
else
addr = (DWORD64)exportAddresses[i + exports->Base];
if (name == functionName)
break;
}

// Cleanup
exit_unmapFile:
UnmapViewOfFile(lib);
exit_closeMapping:
CloseHandle(libMapping);
exit_closeFile:
CloseHandle(libFile);

return addr;
}

GetHandle.hpp

#pragma once
#include
#include
#include
#include
#include
#define SYSTEMHANDLEINFORMATION 16
#pragma comment (lib, "ntdll.lib")

typedef struct _SYSTEM_HANDLE {
ULONG ProcessId;
UCHAR ObjectTypeNumber;
UCHAR Flags;
USHORT Handle;
PVOID Object;
ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE, *PSYSTEM_HANDLE;

typedef struct _SYSTEM_HANDLE_INFORMATION {
ULONG HandleCount; // Or NumberOfHandles if you prefer
SYSTEM_HANDLE Handles[1];
} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;

typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO {
DWORD UniqueProcessId;
WORD HandleType;
USHORT HandleValue;
PVOID Object;
ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE_TABLE_ENTRY_INFO, *PSYSTEM_HANDLE_TABLE_ENTRY_INFO;

typedef struct _OBJECT_TYPE_INFORMATION {
UNICODE_STRING TypeName;
ULONG TotalNumberOfObjects;
ULONG TotalNumberOfHandles;
ULONG TotalPagedPoolUsage;
ULONG TotalNonPagedPoolUsage;
ULONG TotalNamePoolUsage;
ULONG TotalHandleTableUsage;
ULONG HighWaterNumberOfObjects;
ULONG HighWaterNumberOfHandles;
ULONG HighWaterPagedPoolUsage;
ULONG HighWaterNonPagedPoolUsage;
ULONG HighWaterNamePoolUsage;
ULONG HighWaterHandleTableUsage;
ULONG InvalidAttributes;
GENERIC_MAPPING GenericMapping;
ULONG ValidAccessMask;
BOOLEAN SecurityRequired;
BOOLEAN MaintainHandleCount;
UCHAR TypeIndex;
CHAR ReservedByte;
ULONG PoolType;
ULONG DefaultPagedPoolCharge;
ULONG DefaultNonPagedPoolCharge;
} OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;

/* This function finds a handle to a process from its name.
It can also find handles to a process belonging to other processes.
Important: Does NOT return a valid HANDLE, it only returns the Handle ID */
HANDLE GetHandleIdTo(std::wstring targetProcessName, DWORD pidOwner = NULL) {
if (targetProcessName == L"")
return (HANDLE)0x0; // Trying to get a handle to an empty process name

if (pidOwner == NULL) // No owner PID for the handle specified, assuming we are looking for a handle belonging to this program
pidOwner = GetCurrentProcessId();

NTSTATUS status = STATUS_UNSUCCESSFUL;
PVOID buffer = NULL;
ULONG buffersize = 0;
while (true) {
status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SYSTEMHANDLEINFORMATION, buffer, buffersize, &buffersize);
if (!NT_SUCCESS(status)) {
if (status == STATUS_INFO_LENGTH_MISMATCH) {
if (buffer != NULL)
VirtualFree(buffer, 0, MEM_RELEASE);
buffer = VirtualAlloc(NULL, buffersize, MEM_COMMIT, PAGE_READWRITE);
}
continue;
}
else
break;
}

// Enumerate all handles on system
PSYSTEM_HANDLE_INFORMATION handleInfo = (PSYSTEM_HANDLE_INFORMATION)buffer;

PVOID buffer2 = NULL;
ULONG buffersize2 = 0;
for (ULONG i = 0; i < handleInfo->HandleCount; i++) {
PSYSTEM_HANDLE_TABLE_ENTRY_INFO Handle = (PSYSTEM_HANDLE_TABLE_ENTRY_INFO)&handleInfo->Handles[i];
if (!Handle)
continue; // Error, no handle
if (!Handle->HandleValue)
continue; // Error, empty handle value
if (Handle->UniqueProcessId != pidOwner)
continue; // The handle doesn't belong to the owner we target

HANDLE localHandle = (HANDLE)Handle->HandleValue;
if (pidOwner != GetCurrentProcessId()) { // Only if trying to get handle from another process (OpenProcess + DuplicateHandle)
HANDLE hProcessHandleOwner = OpenProcess(PROCESS_DUP_HANDLE, FALSE, pidOwner);
BOOL dupStatus = DuplicateHandle(hProcessHandleOwner, HANDLE(Handle->HandleValue), GetCurrentProcess(), &localHandle, PROCESS_QUERY_LIMITED_INFORMATION, FALSE, 0);
CloseHandle(hProcessHandleOwner);
if (!dupStatus)
continue; // Couldn't get a handle to get info, will not be able to define if it is a handle to our process, exiting
}

int trys = 0;
while (true) {
if (trys == 20)
break;
trys += 1;

/* In rare cases, when a handle has been closed between the snapshot and this NtQueryObject, the handle is not valid at that line.
This is problematic in system processes with a strict handle policy and can result in process termination, forcing a reboot (Windows 8+) or a BSOD (Windows 7)
Note that this is not problematic in classic processes. */
status = NtQueryObject(localHandle, ObjectTypeInformation, buffer2, buffersize2, &buffersize2); // Return objecttypeinfo into buffer
if (!NT_SUCCESS(status)) {
if (buffer2 != NULL)
VirtualFree(buffer2, 0, MEM_RELEASE); // If buffer filled with anything, but call didnt succeed, assume its bullshit, so clear it
buffer2 = VirtualAlloc(NULL, buffersize2, MEM_COMMIT, PAGE_READWRITE); // Allocate with new mem
}
else {
if (wcsncmp(((POBJECT_TYPE_INFORMATION)buffer2)->TypeName.Buffer, L"Process", ((POBJECT_TYPE_INFORMATION)buffer2)->TypeName.Length + 1) == 0) {
wchar_t process[MAX_PATH];
if (GetModuleFileNameExW(localHandle, NULL, process, MAX_PATH)) {
std::wstring processname = process;
int pos = processname.find_last_of(L"\\");
processname = processname.substr(pos + 1, processname.length());
if (processname == targetProcessName) {
HANDLE handleFound = (HANDLE)Handle->HandleValue;
VirtualFree(buffer, 0, MEM_RELEASE); // Cleanup to avoid leaks
VirtualFree(buffer2, 0, MEM_RELEASE);
if (pidOwner != GetCurrentProcessId())
CloseHandle(localHandle);
return handleFound; // TODO: Improve by returning a vector of handles, there might be several with different access rights
}
else
break;
}
}
else
break;
}
}
if (Handle->UniqueProcessId != GetCurrentProcessId())
CloseHandle(localHandle); // Cleanup
continue;
}

VirtualFree(buffer, 0, MEM_RELEASE); // Empties buffers to avoid memory leaks
VirtualFree(buffer2, 0, MEM_RELEASE); // Empties buffers to avoid memory leaks
return (HANDLE)0x0;
}

main.c (for Proof of Concept only)

#include
#include
#include "Bastard.hpp"

using namespace std;

int main(int argc, char* argv[]) {
HANDLE hGame = GetBastardHandle(L"DayZ.exe", PARENT_PCASVC, TRUE);
cout << "Handle to target: 0x" << hex << hGame << endl; system("pause"); return EXIT_SUCCESS; }

Other valuable functions in this bypass

Have a look at the different functions in GetHandle.hpp and Bastard.hpp, several of them might be very useful for your experiment.

The function MakeBastardChild allows you to spawn child processes of other processes externally. It can spawn any process as child of any other process (granted that you can OpenProcess it with PROCESS_CREATE_PROCESS). It can also pass command line arguments to the spawned process if you need it.

The function GetPIDs retrieves PIDs of process from an image name.

The function GetAutoParentPID retrieves the PID automatically of system processes and services.

FindServicePid retrieves the process ID of a service by its name (e.g. "PcaSvc"). A big thank you to @MarkHC for sharing this and for his constant support on this project

SetRemoteHandleInformation is the function that executes shellcode on the parent, I commented it properly as it was an excellent way to learn to execute shellcode. If you never worked with shellcode execution, I suggest that you have a look at it.

FindModuleBaseAddrInProcess ... The name is self explanatory. It cannot unfortunately retrieve x64 module base addresses from x86 processes, which limits this bypass. If you have a function that can do this, please share it.

GetRvaOfFunctionInDLL retrieves the relative virtual address (RVA) of a function by its name from a DLL file on disk (e.g. to get the RVA of a function in a dll that we cannot load because it is not for our architecture). It is compatible for both x86 and x64 DLLs. It can be improved by going through the ordinals in addition to the names, but works perfectly for kernel32.dll since all functions can be found by their names only.
I warmly thank @WasserEsser and @MarkHC for their help in the nightmare that coding this function was.
Honestly, terrible.