Files
2021-05-01 00:05:11 -04:00

304 lines
10 KiB
C++

// todo: investigate why MessageBoxA doesnt work
#include "stdafx.h"
#include <stdint.h>
#include <stdio.h>
#include <wchar.h>
#include "fork.h"
// Uncodumented headers csrss stuff
#include "csrss.h"
// ReWolf's library to fuck with 64-bit memory from 32-bit WoW64 programs
#ifndef _WIN64
#include "wow64ext.h"
#endif
#define _CRT_SECURE_NO_WARNINGS
#ifndef _DEBUG
#define printf(...)
#define DebugBreak()
#endif
// When a new child process is spawned, the parent must call
// CsrClientCallServer with API number BasepCreateProcess to notify
// the csrss subsystem of the new process. However, this seems to
// be optional as the child process will work without doing this
// call.
BOOL NotifyCsrssParent(HANDLE hProcess, HANDLE hThread)
{
PROCESS_BASIC_INFORMATION info;
if (!NT_SUCCESS(NtQueryInformationProcess(hProcess,
ProcessBasicInformation, &info,
sizeof(info), 0))) {
printf("FORKLIB: NtQueryInformationProcess failed!\n");
return FALSE;
}
BOOL bIsWow64;
if (!IsWow64Process(GetCurrentProcess(), &bIsWow64))
{
printf("FORKLIB: IsWow64Process failed!\n");
return FALSE;
}
NTSTATUS result;
if (bIsWow64)
{
CSR_API_MSG64 csrmsg;
RtlZeroMemory(&csrmsg, sizeof(csrmsg));
csrmsg.CreateProcessRequest.PebAddressNative = (ULONGLONG)info.PebBaseAddress;
csrmsg.CreateProcessRequest.ProcessorArchitecture = PROCESSOR_ARCHITECTURE_INTEL;
csrmsg.CreateProcessRequest.ProcessHandle = (ULONGLONG)hProcess;
csrmsg.CreateProcessRequest.ThreadHandle = (ULONGLONG)hThread;
csrmsg.CreateProcessRequest.ClientId.UniqueProcess = GetProcessId(hProcess);
csrmsg.CreateProcessRequest.ClientId.UniqueThread = GetThreadId(hThread);
//result = CsrClientCallServer64(&csrmsg, NULL, CSR_MAKE_API_NUMBER(BASESRV_SERVERDLL_INDEX, BasepCreateProcess), sizeof(csrmsg.CreateProcessRequest));
}
else
{
CSR_API_MSG csrmsg;
RtlZeroMemory(&csrmsg, sizeof(csrmsg));
csrmsg.CreateProcessRequest.PebAddressNative = info.PebBaseAddress;
#ifdef _WIN64
csrmsg.CreateProcessRequest.ProcessorArchitecture = PROCESSOR_ARCHITECTURE_AMD64;
#else
csrmsg.CreateProcessRequest.ProcessorArchitecture = PROCESSOR_ARCHITECTURE_INTEL;
#endif
csrmsg.CreateProcessRequest.ProcessHandle = hProcess;
csrmsg.CreateProcessRequest.ThreadHandle = hThread;
csrmsg.CreateProcessRequest.ClientId.UniqueProcess = (HANDLE)GetProcessId(hProcess);
csrmsg.CreateProcessRequest.ClientId.UniqueThread = (HANDLE)GetThreadId(hThread);
//result = CsrClientCallServer(&csrmsg, NULL, CSR_MAKE_API_NUMBER(BASESRV_SERVERDLL_INDEX, BasepCreateProcess), sizeof(csrmsg.CreateProcessRequest));
}
/*
if (!NT_SUCCESS(result))
{
printf("CsrClientCallServer(BasepCreateThread) failed!\n");
return FALSE;
}
*/
printf("FORKLIB: Successfully notified Csr of child!\n");
return TRUE;
}
// When the a new process is spawned, it must call CsrClientConnectToServer
// and RtlRegisterThreadWithCsrss to connect to the various csrss subsystems
// (such as Windows subsystem, Console subsystem, etc). If this is not done,
// then nearly every function in the Win32 API will lead to segfault. It seems
// that internally the APIs depend on csrss in some way.
//
// j00ru documented csrss on his blog:
// https://j00ru.vexillium.org/2010/07/windows-csrss-write-up-inter-process-communication-part-1/
// https://j00ru.vexillium.org/2010/07/windows-csrss-write-up-inter-process-communication-part-2/
//
// However, our situation is even trickier than usual, since we are a *forked*
// process, meaning that all memory values are cloned from the parent process.
// This is important because CsrClientConnectToServer and RtlRegisterThreadWithCsrss
// seem to initialize some of the global variables in ntdll, and the two functions
// will not work if these variable are already initialized.
// Therefore, it's our responsibility to also *manually de-initialize* these
// global variables by zeroing them before reconnecting with csrss.
//
// Yet *another* complication is WoW64: WoW64 enables the execution of 32-bit
// executeables on 64-bit Windows. The program sees a 32-bit address space and
// the 32-bit version of all system dlls. However, the 32-bit ntdll is really just
// a shim to call the 64-bit version of all of the functions it exposes. In other
// words on WoW64, there are actually *two* copies of ntdll loaded: the 32-bit
// version exposed by WoW64, and the 64-bit version that is loaded into every
// process. Therefore, we need to de-initialize the global variables in *both the
// 64- and 32-bit version of ntdll*. This is accomplished using some tRicKErY
// by jumping to 64-bit code from 32-bit.
//
// This method only supports Windows 10. Somewhere between Windows 7 and Windows 10,
// Microsoft refactored Windows to rely less and less on csrss. Hence the API
// and structures are much simpler on Windows 10 than Windows 7, and as a result
// our job is much easier.
BOOL ConnectCsrChild()
{
BOOL bIsWow64;
if (!IsWow64Process(GetCurrentProcess(), &bIsWow64))
{
printf("FORKLIB: IsWow64Process failed!\n");
return FALSE;
}
// Zero Csr fields???
// Required or else Csr calls will crash
printf("FORKLIB: De-initialize ntdll csr data\n");
HMODULE ntdll = GetModuleHandleA("ntdll.dll");
#ifdef _WIN64
void* pCsrData = (void*)((uintptr_t)ntdll + csrDataRva_x64); // HARDCODED OFFSET, see csrss_offsets.h
printf("FORKLIB: Csr data = %p\n", pCsrData);
memset(pCsrData, 0, csrDataSize_x64);
#else
void* pCsrData = (void*)((uintptr_t)ntdll + csrDataRva_x86); // HARDCODED OFFSET, see csrss_offsets.h
printf("FORKLIB: Csr data = %p\n", pCsrData);
memset(pCsrData, 0, csrDataSize_x86);
if (bIsWow64)
{
DWORD64 ntdll64 = GetModuleHandle64(L"ntdll.dll");
printf("FORKLIB: ntdll 64 = %llx\n", ntdll64);
char mem[csrDataSize_wow64];
memset(mem, 0, sizeof(mem));
DWORD64 pCsrData64 = ntdll64 + csrDataRva_wow64; // HARDCODED OFFSET, see csrss_offsets.h
printf("FORKLIB: Csr data 64 = %llx\n", ntdll64);
setMem64(pCsrData64, mem, sizeof(mem));
}
#endif
DWORD session_id;
wchar_t ObjectDirectory[100];
ProcessIdToSessionId(GetProcessId(GetCurrentProcess()), &session_id);
swprintf(ObjectDirectory, 100, L"\\Sessions\\%d\\Windows", session_id);
printf("FORKLIB: Session_id: %d\n", session_id);
// Not required?
printf("FORKLIB: Link Console subsystem...\n");
void* pCtrlRoutine = (void*)GetProcAddress(GetModuleHandleA("kernelbase"), "CtrlRoutine");
BOOLEAN trash;
//if (!NT_SUCCESS(CsrClientConnectToServer(L"\\Sessions\\" CSRSS_SESSIONID L"\\Windows", 1, &pCtrlRoutine, 8, &trash)))
if (!NT_SUCCESS(CsrClientConnectToServer(ObjectDirectory, 1, &pCtrlRoutine, 8, &trash)))
{
printf("FORKLIB: CsrClientConnectToServer failed!\n");
return FALSE;
}
printf("FORKLIB: Link Windows subsystem...\n");
// passing &gfServerProcess is not necessary, actually? passing &trash is okay?
char buf[0x240]; // this seem to just be all zero everytime?
memset(buf, 0, sizeof(buf));
//if (!NT_SUCCESS(CsrClientConnectToServer(L"\\Sessions\\" CSRSS_SESSIONID L"\\Windows", 3, buf, 0x240, &trash)))
if (!NT_SUCCESS(CsrClientConnectToServer(ObjectDirectory, 3, buf, 0x240, &trash)))
{
printf("FORKLIB: CsrClientConnectToServer failed!\n");
return FALSE;
}
printf("FORKLIB: Connect to Csr...\n");
if (!NT_SUCCESS(RtlRegisterThreadWithCsrss()))
{
printf("FORKLIB: RtlRegisterThreadWithCsrss failed!\n");
return FALSE;
}
printf("FORKLIB: Connected to Csr!\n");
return TRUE;
}
// Fix stdio handles of the child. If this isn't done, the child
// will inherit the stdio of the parent, and operations to those
// file descriptors will just not work.
void ReopenStdioHandles()
{
freopen_s((FILE**)stdout, "CONOUT$", "w+", stdout);
freopen_s((FILE**)stdin, "CONIN$", "r", stdin);
freopen_s((FILE**)stderr, "CONOUT$", "w+", stdout);
SetStdHandle(STD_INPUT_HANDLE, stdin);
SetStdHandle(STD_OUTPUT_HANDLE, stdout);
SetStdHandle(STD_ERROR_HANDLE, stderr);
}
#ifndef _WIN64
LONG WINAPI DiscardException(EXCEPTION_POINTERS *ExceptionInfo)
{
printf("FORKLIB: Discarding exception %08x to %p, at instruction %08x\n",
ExceptionInfo->ExceptionRecord->ExceptionCode,
ExceptionInfo->ExceptionRecord->ExceptionAddress,
ExceptionInfo->ContextRecord->Eip);
return EXCEPTION_CONTINUE_EXECUTION;
}
#endif
extern "C" DWORD fork(LPPROCESS_INFORMATION lpProcessInformation) {
printf("FORKLIB: Before the fork, my pid is %d\n", GetProcessId(GetCurrentProcess()));
PS_CREATE_INFO procInfo;
RtlZeroMemory(&procInfo, sizeof(procInfo));
HANDLE hProcess = NULL;
HANDLE hThread = NULL;
procInfo.Size = sizeof(PS_CREATE_INFO);
#ifndef _WIN64
// WTF???? Discard *BIZARRE* segfault in ntdll from read fs:[0x18] that you can ignore???
LPTOP_LEVEL_EXCEPTION_FILTER oldFilter = SetUnhandledExceptionFilter(DiscardException);
#endif
// This is the part that actually does the forking. Everything else is just
// to clean up after the mess that's created afterwards
NTSTATUS result = NtCreateUserProcess(
&hProcess, &hThread,
MAXIMUM_ALLOWED, MAXIMUM_ALLOWED,
NULL,
NULL,
PROCESS_CREATE_FLAGS_INHERIT_FROM_PARENT | PROCESS_CREATE_FLAGS_INHERIT_HANDLES, THREAD_CREATE_FLAGS_CREATE_SUSPENDED,
NULL,
&procInfo,
NULL);
#ifndef _WIN64
// Clear the exception handler installed earlier.
SetUnhandledExceptionFilter(oldFilter);
#endif
if (!result)
{
// Parent process
printf("FORKLIB: I'm the parent\n");
printf("FORKLIB: hThread = %p, hProcess = %p\n", hThread, hProcess);
printf("FORKLIB: Thread ID = %x\n", GetThreadId(hThread));
printf("FORKLIB: Result = %d\n", result);
// Not needed??
if (!NotifyCsrssParent(hProcess, hThread))
{
printf("FORKLIB: NotifyCsrssParent failed\n");
TerminateProcess(hProcess, 1);
return -1;
}
if (lpProcessInformation)
{
lpProcessInformation->hProcess = hProcess;
lpProcessInformation->hThread = hThread;
lpProcessInformation->dwProcessId = GetProcessId(hProcess);
lpProcessInformation->dwThreadId = GetThreadId(hThread);
}
ResumeThread(hThread); // allow the child to connect to Csr.
return GetProcessId(hProcess);
}
else
{
// Child process
FreeConsole();
// Remove these calls to improve performance, at the cost of losing stdio.
#ifdef _DEBUG
AllocConsole();
SetStdHandle(STD_INPUT_HANDLE, stdin);
SetStdHandle(STD_OUTPUT_HANDLE, stdout);
SetStdHandle(STD_ERROR_HANDLE, stderr);
#endif
printf("I'm the child\n");
if (!ConnectCsrChild())
{
DebugBreak();
ExitProcess(1);
}
#ifdef _DEBUG
// Not safe to do fopen until after ConnectCsrChild
ReopenStdioHandles();
#endif
return 0;
}
}