Files
sslab-gatech-winnie/experiments/hook-example/hook-example.cpp
T
2021-05-01 00:05:11 -04:00

410 lines
12 KiB
C++

#include "pch.h"
#include <Psapi.h>
#include <stdio.h>
#include <conio.h>
#include "../forkserver-proto.h"
#define FATAL(f, ...) {printf(f ": %d\n", ##__VA_ARGS__##, GetLastError()); getc(stdin); ExitProcess(GetLastError()); }
#define dank_perror(msg) { \
LPCSTR _errorText = NULL; \
FormatMessageA( \
FORMAT_MESSAGE_FROM_SYSTEM \
| FORMAT_MESSAGE_ALLOCATE_BUFFER \
| FORMAT_MESSAGE_IGNORE_INSERTS, \
NULL, \
GetLastError(), \
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), \
(LPSTR)&_errorText, \
0, \
NULL); \
if (_errorText) \
{ \
FATAL(msg " failed: %s", _errorText); \
LocalFree((HLOCAL) _errorText); \
_errorText = NULL; \
} \
else \
{ \
FATAL(msg " failed"); \
} \
}
HMODULE FindModule(HANDLE hProcess, const char* szModuleName)
{
HMODULE hMods[1024];
DWORD cbNeeded;
if (!EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded))
{
dank_perror("EnumProcessModules");
return NULL;
}
for (unsigned int i = 0; i < cbNeeded / sizeof(HMODULE); i++)
{
char szModName[MAX_PATH];
if (GetModuleFileNameExA(hProcess, hMods[i], szModName, sizeof(szModName) / sizeof(char)))
{
if (!_stricmp(szModuleName, szModName))
{
return hMods[i];
}
}
}
return NULL;
}
PIMAGE_NT_HEADERS map_pe_file(LPCSTR szPath, LPVOID* lpBase, HANDLE* hMapping, HANDLE* hFile)
{
BY_HANDLE_FILE_INFORMATION bhfi;
*hFile = CreateFileA(szPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (*hFile == INVALID_HANDLE_VALUE) {
FATAL("Invalid handle when map PE file");
return NULL;
}
*hMapping = CreateFileMappingA(*hFile, NULL, PAGE_READONLY | SEC_IMAGE_NO_EXECUTE, 0, 0, NULL);
if (!*hMapping) {
FATAL("Cannot make file mapping");
return NULL;
}
*lpBase = (char *)MapViewOfFile(*hMapping, FILE_MAP_READ, 0, 0, 0);
if (!*lpBase) {
FATAL("Cannot make MapViewOfFile");
return NULL;
}
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)*lpBase;
if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
FATAL("IMAGE_DOS_SIGNATURE not matched");
return NULL;
}
PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((uintptr_t)*lpBase + dosHeader->e_lfanew);
if (ntHeader->Signature != IMAGE_NT_SIGNATURE) {
FATAL("IMAGE_NT_SIGNATURE not matched");
return NULL;
}
return ntHeader;
}
DWORD get_entry_point(LPCSTR szPath)
{
DWORD dwEntryPoint = NULL;
HANDLE hMapping = INVALID_HANDLE_VALUE, hFile = INVALID_HANDLE_VALUE;
BYTE* lpBase = NULL;
PIMAGE_NT_HEADERS ntHeader = map_pe_file(szPath, (LPVOID*)&lpBase, &hMapping, &hFile);
if (ntHeader)
{
dwEntryPoint = ntHeader->OptionalHeader.AddressOfEntryPoint;
}
else {
FATAL("Cannot parse the PEfile!");
}
if (lpBase) UnmapViewOfFile((LPCVOID)lpBase);
if (hMapping != INVALID_HANDLE_VALUE) CloseHandle(hMapping);
if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile);
return dwEntryPoint;
}
// GetProcAddress that works on another process
DWORD get_proc_offset(char *data, char *name) {
DWORD pe_offset;
pe_offset = *((DWORD *)(data + 0x3C));
char *pe = data + pe_offset;
DWORD signature = *((DWORD *)pe);
if (signature != 0x00004550) {
return 0;
}
pe = pe + 0x18;
WORD magic = *((WORD *)pe);
DWORD exporttableoffset;
if (magic == 0x10b) {
exporttableoffset = *(DWORD *)(pe + 96);
}
else if (magic == 0x20b) {
exporttableoffset = *(DWORD *)(pe + 112);
}
else {
return 0;
}
if (!exporttableoffset) return 0;
char *exporttable = data + exporttableoffset;
DWORD numentries = *(DWORD *)(exporttable + 24);
DWORD addresstableoffset = *(DWORD *)(exporttable + 28);
DWORD nameptrtableoffset = *(DWORD *)(exporttable + 32);
DWORD ordinaltableoffset = *(DWORD *)(exporttable + 36);
DWORD *nameptrtable = (DWORD *)(data + nameptrtableoffset);
WORD *ordinaltable = (WORD *)(data + ordinaltableoffset);
DWORD *addresstable = (DWORD *)(data + addresstableoffset);
DWORD i;
for (i = 0; i < numentries; i++) {
char *nameptr = data + nameptrtable[i];
if (strcmp(name, nameptr) == 0) break;
}
if (i == numentries) return 0;
WORD oridnal = ordinaltable[i];
DWORD offset = addresstable[oridnal];
return offset;
}
HMODULE InjectDll(HANDLE hProcess, LPCSTR szDllFilename)
{
LPVOID pMem = VirtualAllocEx(hProcess, NULL, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!pMem)
{
dank_perror("VirtualAllocEx");
return NULL;
}
printf("pMem = 0x%p\n", pMem);
BOOL bSuccess = WriteProcessMemory(hProcess, pMem, szDllFilename, strlen(szDllFilename) + 1, NULL);
if (!bSuccess)
{
dank_perror("WriteProcessMemory");
return NULL;
}
printf("Wrote %s\n", szDllFilename);
LPTHREAD_START_ROUTINE pLoadLibraryA = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("kernel32"), "LoadLibraryA");
printf("LoadLibraryA = 0x%p\n", pLoadLibraryA);
DWORD dwThreadId;
HANDLE hThread = CreateRemoteThread(hProcess, NULL, NULL, pLoadLibraryA, pMem, 0, &dwThreadId);
if (!hThread)
{
dank_perror("CreateRemoteThread");
return NULL;
}
printf("Thread created, ID = %d\n", dwThreadId);
if (WaitForSingleObject(hThread, INFINITE) == WAIT_FAILED)
{
dank_perror("WaitForSingleObject");
return NULL;
}
Sleep(100);
printf("Success\n");
return FindModule(hProcess, szDllFilename);
}
#define HARNESS_DLL "forkserver.dll"
#define DRYRUN_DLL "dryrun.dll"
#define SYNC_EVENT_NAME "Global\\harness-sync"
int main()
{
// The process to start and inject into
char* targetProgram = "toy_example\\math3.exe";
char* argvs = "toy_example\\math3.exe toy_example\\in\\input";
BOOL dryrun = FALSE;
printf("Spawning the forkserver.\n");
// Spawn the process suspended. We can't inject immediately, however. Need to let the program initialize itself before we can load a library.
PROCESS_INFORMATION pi;
STARTUPINFOA si;
RtlZeroMemory(&pi, sizeof(pi));
RtlZeroMemory(&si, sizeof(si));
BOOL success = CreateProcessA(NULL, argvs, NULL, NULL, FALSE, CREATE_SUSPENDED | CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP, NULL, NULL, &si, &pi);
if (!success)
{
dank_perror("CreateProcessA");
return 1;
}
// Derive entrypoint address from PEB and PE header
CONTEXT context;
context.ContextFlags = CONTEXT_INTEGER;
GetThreadContext(pi.hThread, &context);
uintptr_t pebAddr, dwBaseAddr = 0;
#ifdef _WIN64
pebAddr = context.Rdx;
ReadProcessMemory(pi.hProcess, (PVOID)(pebAddr + 0x10), &dwBaseAddr, sizeof(dwBaseAddr), NULL);
#else
pebAddr = context.Ebx;
ReadProcessMemory(pi.hProcess, (PVOID)(pebAddr + 8), &dwBaseAddr, sizeof(dwBaseAddr), NULL);
#endif
printf("peb=%p, base address=%p\n", pebAddr, dwBaseAddr);
uintptr_t oep = get_entry_point(targetProgram);
printf("oep=%x\n", oep);
uintptr_t pEntryPoint = oep + dwBaseAddr;
if (!pEntryPoint)
{
dank_perror("GetEntryPoint");
return 1;
}
printf("entrypoint = %p\n", pEntryPoint);
// assemble infinite loop at entrypoint
DWORD dwOldProtect;
VirtualProtectEx(pi.hProcess, (PVOID)pEntryPoint, 2, PAGE_EXECUTE_READWRITE, &dwOldProtect);
BYTE oepBytes[2];
ReadProcessMemory(pi.hProcess, (PVOID)pEntryPoint, oepBytes, 2, NULL);
WriteProcessMemory(pi.hProcess, (PVOID)pEntryPoint, "\xEB\xFE", 2, NULL);
ResumeThread(pi.hThread);
// Poll the instruction pointer until it reached the entrypoint, or time out.
#ifdef _WIN64
for (int i = 0; context.Rip != pEntryPoint; Sleep(100))
#else
for (int i = 0; context.Eip != pEntryPoint; Sleep(100))
#endif
{
if (++i > 50)
{
TerminateProcess(pi.hProcess, -1);
perror("entrypoint trap trimed out\n");
}
context.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(pi.hThread, &context);
}
printf("entrypoint trap hit, injecting the dll now!\n");
SuspendThread(pi.hThread);
// Event for synchronizing with the harness.
HANDLE hEvent = CreateEventA(NULL, FALSE, FALSE, SYNC_EVENT_NAME);
// Actually inject the dll now.
char* injectedDll = dryrun ? DRYRUN_DLL : HARNESS_DLL;
char szDllFilename[MAX_PATH];
GetCurrentDirectoryA(sizeof(szDllFilename) - 1, szDllFilename);
strncat(szDllFilename, "\\", max(0, MAX_PATH - strlen(szDllFilename) - 1));
strncat(szDllFilename, injectedDll, max(0, MAX_PATH - strlen(szDllFilename) - 1));
printf("injecting %s\n", szDllFilename);
HMODULE hModule = InjectDll(pi.hProcess, szDllFilename);
if (!hModule)
{
perror("InjectDll");
return 1;
}
printf("harness dll injected, base address = %p\n", hModule);
// Write coverage info
HANDLE hMapping = INVALID_HANDLE_VALUE, hFile = INVALID_HANDLE_VALUE;
BYTE* lpBase = NULL;
PIMAGE_NT_HEADERS ntHeader = map_pe_file(injectedDll, (LPVOID*)&lpBase, &hMapping, &hFile);
if (!ntHeader)
FATAL("Failed to parse export table of %s", injectedDll);
DWORD off_num_basicblocks = get_proc_offset((char*)lpBase, "num_basicblocks");
DWORD off_arr_basicblocks = get_proc_offset((char*)lpBase, "arr_basicblocks");
if (!off_num_basicblocks || !off_arr_basicblocks)
FATAL("Fail to find num_basicblocks and arr_basicblocks in injected dll\n");
printf("num_basicblocks offset = %08x, arr_basicblocks offset = %08x\n", off_num_basicblocks, off_arr_basicblocks);
LPVOID pNum_basicblocks = (LPVOID)((uintptr_t)hModule + off_num_basicblocks);
LPVOID pArr_basicblocks = (LPVOID)((uintptr_t)hModule + off_arr_basicblocks);
printf("num_basicblocks = %p, arr_basicblocks = %p\n", pNum_basicblocks, pArr_basicblocks);
// dummy data
LPVOID some_array[] = { (LPVOID)(dwBaseAddr+0x1105) };
size_t numberofbasicblocks = ARRAYSIZE(some_array);
LPVOID* basicblock_addresses = some_array;
if (!WriteProcessMemory(pi.hProcess, pNum_basicblocks, &numberofbasicblocks, sizeof(size_t), NULL))
{
dank_perror("Failed to write number of basic blocks into child");
}
size_t arr_size = sizeof(LPVOID) * numberofbasicblocks;
LPVOID pMem = VirtualAllocEx(pi.hProcess, NULL, arr_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!pMem)
{
dank_perror("Failed to allocate basic blocks list into child");
}
if (!WriteProcessMemory(pi.hProcess, pMem, some_array, arr_size, NULL))
{
dank_perror("Failed to write basic blocks list into child");
}
if (!WriteProcessMemory(pi.hProcess, pArr_basicblocks, &pMem, sizeof(LPVOID*), NULL))
{
dank_perror("Failed to write number of basic blocks into child");
}
if (lpBase) UnmapViewOfFile((LPCVOID)lpBase);
if (hMapping != INVALID_HANDLE_VALUE) CloseHandle(hMapping);
if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile);
// Signal to harness that coverage info is written
SetEvent(hEvent);
// Wait for harness to setup hooks before we resume the main thread.
if (WaitForSingleObject(hEvent, INFINITE) == WAIT_FAILED)
{
perror("WaitForSingleObject");
return 1;
}
printf("Ok, the harness is ready. Resuming the main thread now.\n");
// a possible problem is if the injected harness overwrites pEntryPoint before we restore oepBytes.
// to deal with that just check that nothing edited that code before we restore it.
WriteProcessMemory(pi.hProcess, (PVOID)pEntryPoint, oepBytes, 2, NULL);
DWORD trash;
VirtualProtectEx(pi.hProcess, (PVOID)pEntryPoint, 2, dwOldProtect, &trash);
ResumeThread(pi.hThread);
printf("Connecting to forkserver...\n");
HANDLE hPipeForkserver;
do
{
hPipeForkserver = CreateFileA(AFL_FORKSERVER_PIPE, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hPipeForkserver == INVALID_HANDLE_VALUE)
{
if (GetLastError() == ERROR_FILE_NOT_FOUND)
{
Sleep(10);
continue;
}
dank_perror("CreateFileA");
}
} while (hPipeForkserver == INVALID_HANDLE_VALUE);
DWORD dwMode = PIPE_READMODE_MESSAGE;
if (!SetNamedPipeHandleState(hPipeForkserver, &dwMode, NULL, NULL))
{
dank_perror("SetNamedPipeHandleState");
}
for (int i = 0 ; i < 3; i++)
{
printf("Iteration %d\n", i);
AFL_FORKSERVER_REQUEST forkserverRequest;
forkserverRequest.Operation = AFL_CREATE_NEW_CHILD;
DWORD nWritten;
if (!WriteFile(hPipeForkserver, &forkserverRequest, sizeof(forkserverRequest), &nWritten, NULL) || nWritten != sizeof(forkserverRequest))
{
FATAL("Broken forkserver pipe, WriteFile");
}
AFL_FORKSERVER_RESULT forkserverResult;
do
{
if (!ReadFile(hPipeForkserver, &forkserverResult, sizeof(forkserverResult), &nWritten, NULL) || nWritten != sizeof(forkserverResult))
{
FATAL("Broken forkserver pipe, ReadFile (main)");
}
if (forkserverResult.StatusCode == AFL_CHILD_COVERAGE)
{
printf("Got coverage: %s+%p\n", forkserverResult.CoverageInfo.ModuleName, forkserverResult.CoverageInfo.Rva);
}
} while (forkserverResult.StatusCode == AFL_CHILD_COVERAGE);
printf("Result from forkserver: %d\n", forkserverResult.StatusCode);
}
printf("Done.\n");
return 0;
}