#ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif #include "injector.h" #include #include #include #include typedef BOOL(WINAPI *IsWow64Process2_t)(HANDLE hProcess, USHORT *pProcessMachine, USHORT *pNativeMachine); static DWORD page_size = 0; static size_t func_LoadLibraryW; static size_t func_FreeLibrary; static size_t func_GetLastError; static char errmsg[512]; static injector_t *g_injector; #ifdef _M_AMD64 static const char x64_code_template[] = // ---------- call LoadLibraryW ---------- /* 0000: */ "\x48\x83\xEC\x28" // sub rsp,28h /* 0004: */ "\xFF\x15\x3E\x00\x00\x00" // call LoadLibraryW // 0x0000003e = X64_ADDR_LoadLibraryW - (0x0004 + 6) /* 000A: */ "\x48\x85\xC0" // test rax,rax /* 000D: */ "\x74\x0B" // je L1 /* 000F: */ "\x48\x89\x05\xEA\x0F\x00\x00" // mov [load_address], rax // 0x00000fea = 0x1000 - (0x000F + 7) /* 0016: */ "\x33\xC0" // xor eax,eax /* 0018: */ "\xEB\x06" // jmp L2 /* 001A: L1: */ "\xFF\x15\x38\x00\x00\x00" // call GetLastError // 0x00000038 = X64_ADDR_GetLastError - (0x001A + 6) /* 0020: L2: */ "\x48\x83\xC4\x28" // add rsp,28h /* 0024: */ "\xC3" // ret // ---------- call FreeLibrary ---------- #define X64_UNINJECTION_CODE_OFFSET 0x25 /* 0025: */ "\x48\x83\xEC\x28" // sub rsp,28h /* 0029: */ "\xFF\x15\x21\x00\x00\x00" // call FreeLibrary // 0x00000021 = X64_ADDR_FreeLibrary - (0x0029 + 6) /* 002F: */ "\x85\xC0" // test eax,eax /* 0031: */ "\x74\x04" // je L1 /* 0033: */ "\x33\xC0" // xor eax,eax /* 0035: */ "\xEB\x06" // jmp L2 /* 0037: L1: */ "\xFF\x15\x1B\x00\x00\x00" // call GetLastError // 0x0000001B = X64_ADDR_GetLastError - (0x0037 + 6) /* 003D: L2: */ "\x48\x83\xC4\x28" // add rsp,28h /* 0041: */ "\xC3" // ret // padding /* 0042: */ "\x90\x90\x90\x90\x90\x90" // ---------- literal pool ---------- #define X64_ADDR_LoadLibraryW 0x0048 /* 0048: */ "\x90\x90\x90\x90\x90\x90\x90\x90" #define X64_ADDR_FreeLibrary 0x0050 /* 0050: */ "\x90\x90\x90\x90\x90\x90\x90\x90" #define X64_ADDR_GetLastError 0x0058 /* 0058: */ "\x90\x90\x90\x90\x90\x90\x90\x90"; #define X64_CODE_SIZE 0x0060 #endif #if defined(_M_AMD64) || defined(_M_IX86) static const char x86_code_template[] = // ---------- call LoadLibraryW ---------- /* 0000: */ "\xFF\x74\x24\x04" // push dword ptr [esp+4] #define X86_CALL_LoadLibraryW 0x0004 /* 0004: */ "\xE8\x00\x00\x00\x00" // call LoadLibraryW@4 /* 0009: */ "\x85\xC0" // test eax,eax /* 000B: */ "\x74\x09" // je L1 #define X86_MOV_EAX 0x000D /* 000D: */ "\xA3\x00\x00\x00\x00" // mov dword ptr [load_address], eax /* 0012: */ "\x33\xC0" // xor eax,eax /* 0014: */ "\xEB\x05" // jmp L2 #define X86_CALL_GetLastError1 0x0016 /* 0016: L1: */ "\xE8\x00\x00\x00\x00" // call GetLastError@0 /* 001B: L2: */ "\xC2\x04\x00" // ret 4 // ---------- call FreeLibrary ---------- #define X86_UNINJECTION_CODE_OFFSET 0x001E /* 001E: */ "\xFF\x74\x24\x04" // push dword ptr [esp+4] #define X86_CALL_FreeLibrary 0x0022 /* 0022: */ "\xE8\x00\x00\x00\x00" // call FreeLibrary@4 /* 0027: */ "\x85\xC0" // test eax,eax /* 0029: */ "\x74\x04" // je L1 /* 002B: */ "\x33\xC0" // xor eax,eax /* 002D: */ "\xEB\x05" // jmp L2 #define X86_CALL_GetLastError2 0x002F /* 002F: L1: */ "\xE8\x00\x00\x00\x00" // call GetLastError@0 /* 0034: L2: */ "\xC2\x04\x00" // ret 4 ; #define X86_CODE_SIZE 0x0037 #endif #ifdef _M_AMD64 #define CURRENT_ARCH "x64" #define CODE_SIZE X64_CODE_SIZE #endif #ifdef _M_IX86 #define CURRENT_ARCH "x86" #define CODE_SIZE X86_CODE_SIZE #endif static void set_errmsg(const char *format, ...); static const char *w32strerr(DWORD err); static USHORT process_arch(HANDLE hProcess); static const char *arch_name(USHORT arch); struct injector { HANDLE hProcess; char *remote_mem; char *injection_code; char *uninjection_code; }; static BOOL init(void) { SYSTEM_INFO si; HANDLE hToken; LUID luid; TOKEN_PRIVILEGES tp; HMODULE kernel32 = GetModuleHandleA("kernel32"); if (kernel32 == 0) { return FALSE; } GetSystemInfo(&si); page_size = si.dwPageSize; func_LoadLibraryW = (size_t)GetProcAddress(kernel32, "LoadLibraryW"); func_FreeLibrary = (size_t)GetProcAddress(kernel32, "FreeLibrary"); func_GetLastError = (size_t)GetProcAddress(kernel32, "GetLastError"); if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) { return FALSE; } if (!LookupPrivilegeValue(0, SE_DEBUG_NAME, &luid)) { CloseHandle(hToken); return FALSE; } tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL)) { CloseHandle(hToken); return FALSE; } CloseHandle(hToken); return TRUE; } #if defined(_M_AMD64) static int cmp_func(const void *context, const void *key, const void *datum) { ptrdiff_t rva_to_va = (ptrdiff_t)context; const char *k = (const char *)key; const char *d = (const char *)(rva_to_va + *(const DWORD *)datum); return strcmp(k, d); } static int funcaddr(DWORD pid, size_t *load_library, size_t *free_library, size_t *get_last_error) { HANDLE hSnapshot; MODULEENTRY32W me; BOOL ok; HANDLE hFile = INVALID_HANDLE_VALUE; HANDLE hFileMapping = NULL; void *base = NULL; IMAGE_NT_HEADERS *nt_hdrs; ULONG exp_size; const IMAGE_EXPORT_DIRECTORY *exp; const DWORD *names, *name, *funcs; const WORD *ordinals; ptrdiff_t rva_to_va; int rv = INJERR_OTHER; /* Get the full path of kernel32.dll. */ retry: hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid); if (hSnapshot == INVALID_HANDLE_VALUE) { DWORD err = GetLastError(); switch (err) { case ERROR_BAD_LENGTH: goto retry; case ERROR_ACCESS_DENIED: rv = INJERR_PERMISSION; break; case ERROR_INVALID_PARAMETER: rv = INJERR_NO_PROCESS; break; default: rv = INJERR_OTHER; } set_errmsg("CreateToolhelp32Snapshot error: %s", w32strerr(err)); return rv; } me.dwSize = sizeof(me); for (ok = Module32FirstW(hSnapshot, &me); ok; ok = Module32NextW(hSnapshot, &me)) { if (wcsicmp(me.szModule, L"kernel32.dll") == 0) { break; } } CloseHandle(hSnapshot); if (!ok) { set_errmsg("kernel32.dll could not be found."); return INJERR_OTHER; } /* Get the export directory in the kernel32.dll. */ hFile = CreateFileW(me.szExePath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { set_errmsg("failed to open file %s: %s", me.szExePath, w32strerr(GetLastError())); goto exit; } hFileMapping = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, NULL); if (hFileMapping == NULL) { set_errmsg("failed to create file mapping of %s: %s", me.szExePath, w32strerr(GetLastError())); goto exit; } base = MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0); if (base == NULL) { set_errmsg("failed to map file %s to memory: %s", me.szExePath, w32strerr(GetLastError())); goto exit; } nt_hdrs = ImageNtHeader(base); if (nt_hdrs == NULL) { set_errmsg("ImageNtHeader error: %s", w32strerr(GetLastError())); goto exit; } exp = (const IMAGE_EXPORT_DIRECTORY *)ImageDirectoryEntryToDataEx(base, FALSE, IMAGE_DIRECTORY_ENTRY_EXPORT, &exp_size, NULL); if (exp == NULL) { set_errmsg("ImageDirectoryEntryToDataEx error: %s", w32strerr(GetLastError())); goto exit; } if (exp->NumberOfNames == 0) { set_errmsg("No export entires are not found."); goto exit; } names = (const DWORD *)ImageRvaToVa(nt_hdrs, base, exp->AddressOfNames, NULL); if (names == NULL) { set_errmsg("ImageRvaToVa error: %s", w32strerr(GetLastError())); goto exit; } ordinals = (const WORD *)ImageRvaToVa(nt_hdrs, base, exp->AddressOfNameOrdinals, NULL); if (ordinals == NULL) { set_errmsg("ImageRvaToVa error: %s", w32strerr(GetLastError())); goto exit; } funcs = (const DWORD *)ImageRvaToVa(nt_hdrs, base, exp->AddressOfFunctions, NULL); if (funcs == NULL) { set_errmsg("ImageRvaToVa error: %s", w32strerr(GetLastError())); goto exit; } rva_to_va = (size_t)ImageRvaToVa(nt_hdrs, base, names[0], NULL) - (size_t)names[0]; /* Find the address of LoadLibraryW */ name = bsearch_s((void *)"LoadLibraryW", names, exp->NumberOfNames, sizeof(DWORD), cmp_func, (void *)rva_to_va); if (name == NULL) { set_errmsg("Could not find the address of LoadLibraryW"); goto exit; } *load_library = (size_t)me.modBaseAddr + funcs[ordinals[name - names]]; /* Find the address of FreeLibrary */ name = bsearch_s((void *)"FreeLibrary", names, exp->NumberOfNames, sizeof(DWORD), cmp_func, (void *)rva_to_va); if (name == NULL) { set_errmsg("Could not find the address of FreeLibrary"); goto exit; } *free_library = (size_t)me.modBaseAddr + funcs[ordinals[name - names]]; /* Find the address of GetLastError */ name = bsearch_s((void *)"GetLastError", names, exp->NumberOfNames, sizeof(DWORD), cmp_func, (void *)rva_to_va); if (name == NULL) { set_errmsg("Could not find the address of GetLastError"); goto exit; } *get_last_error = (size_t)me.modBaseAddr + funcs[ordinals[name - names]]; rv = 0; exit: if (base != NULL) { UnmapViewOfFile(base); } if (hFileMapping != NULL) { CloseHandle(hFileMapping); } if (hFile != INVALID_HANDLE_VALUE) { CloseHandle(hFile); } return rv; } #endif int cki_attach(injector_t **injector_out, DWORD pid) { injector_t *injector; DWORD dwDesiredAccess = PROCESS_QUERY_LIMITED_INFORMATION | /* for IsWow64Process() */ PROCESS_CREATE_THREAD | /* for CreateRemoteThread() */ PROCESS_VM_OPERATION | /* for VirtualAllocEx() */ PROCESS_VM_READ | /* for ReadProcessMemory() */ PROCESS_VM_WRITE; /* for WriteProcessMemory() */ USHORT arch; DWORD old_protect; SIZE_T written; int rv; char code[CODE_SIZE]; size_t code_size; size_t load_library, free_library, get_last_error; if (page_size == 0) { init(); } load_library = func_LoadLibraryW; free_library = func_FreeLibrary; get_last_error = func_GetLastError; injector = (injector_t *)calloc(1, sizeof(injector_t)); if (injector == NULL) { set_errmsg("malloc error: %s", strerror(errno)); return INJERR_NO_MEMORY; } injector->hProcess = OpenProcess(dwDesiredAccess, FALSE, pid); if (injector->hProcess == NULL) { DWORD err = GetLastError(); set_errmsg("OpenProcess error: %s", w32strerr(err)); switch (err) { case ERROR_ACCESS_DENIED: rv = INJERR_PERMISSION; break; case ERROR_INVALID_PARAMETER: rv = INJERR_NO_PROCESS; break; default: rv = INJERR_OTHER; } goto error_exit; } arch = process_arch(injector->hProcess); switch (arch) { #ifdef _M_AMD64 case IMAGE_FILE_MACHINE_AMD64: break; case IMAGE_FILE_MACHINE_I386: rv = funcaddr(pid, &load_library, &free_library, &get_last_error); if (rv != 0) { goto error_exit; } break; #endif #ifdef _M_IX86 case IMAGE_FILE_MACHINE_I386: break; #endif default: set_errmsg("%s target process isn't supported by %s process.", arch_name(arch), CURRENT_ARCH); rv = INJERR_UNSUPPORTED_TARGET; goto error_exit; } injector->remote_mem = (char *)VirtualAllocEx(injector->hProcess, NULL, 2 * page_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (injector->remote_mem == NULL) { set_errmsg("VirtualAllocEx error: %s", w32strerr(GetLastError())); rv = INJERR_OTHER; goto error_exit; } injector->injection_code = injector->remote_mem; switch (arch) { #ifdef _M_AMD64 case IMAGE_FILE_MACHINE_AMD64: /* x64 */ memcpy(code, x64_code_template, X64_CODE_SIZE); code_size = X64_CODE_SIZE; *(size_t *)(code + X64_ADDR_LoadLibraryW) = load_library; *(size_t *)(code + X64_ADDR_FreeLibrary) = free_library; *(size_t *)(code + X64_ADDR_GetLastError) = get_last_error; injector->uninjection_code = injector->remote_mem + X64_UNINJECTION_CODE_OFFSET; break; #endif #if defined(_M_AMD64) || defined(_M_IX86) case IMAGE_FILE_MACHINE_I386: /* x86 */ memcpy(code, x86_code_template, X86_CODE_SIZE); code_size = X86_CODE_SIZE; #define FIX_CALL_RELATIVE(addr, offset) \ *(uint32_t *)(code + offset + 1) = addr - ((uint32_t)(size_t)injector->remote_mem + offset + 5) FIX_CALL_RELATIVE(load_library, X86_CALL_LoadLibraryW); FIX_CALL_RELATIVE(free_library, X86_CALL_FreeLibrary); FIX_CALL_RELATIVE(get_last_error, X86_CALL_GetLastError1); FIX_CALL_RELATIVE(get_last_error, X86_CALL_GetLastError2); *(uint32_t *)(code + X86_MOV_EAX + 1) = (uint32_t)(size_t)injector->remote_mem + page_size; injector->uninjection_code = injector->remote_mem + X86_UNINJECTION_CODE_OFFSET; break; #endif default: set_errmsg("Never reach here: arch=0x%x", arch); rv = INJERR_OTHER; goto error_exit; } if (!WriteProcessMemory(injector->hProcess, injector->remote_mem, code, code_size, &written)) { set_errmsg("WriteProcessMemory error: %s", w32strerr(GetLastError())); rv = INJERR_OTHER; goto error_exit; } if (!VirtualProtectEx(injector->hProcess, injector->remote_mem, page_size, PAGE_EXECUTE_READ, &old_protect)) { set_errmsg("VirtualProtectEx error: %s", w32strerr(GetLastError())); rv = INJERR_OTHER; goto error_exit; } *injector_out = injector; return 0; error_exit: cki_detach(injector); return rv; } int cki_inject(injector_t *injector, const char *path, void **handle) { DWORD pathlen = (DWORD)strlen(path); wchar_t *wpath; DWORD wpathlen; if (pathlen == 0) { set_errmsg("The specified path is empty."); return INJERR_FILE_NOT_FOUND; } if (pathlen > MAX_PATH) { set_errmsg("too long file path: %s", path); return INJERR_FILE_NOT_FOUND; } wpath = (wchar_t *)_alloca((pathlen + 1) * sizeof(wchar_t)); wpathlen = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, path, pathlen, wpath, pathlen + 1); wpath[wpathlen] = L'\0'; return cki_inject_w(injector, wpath, handle); } int cki_inject_w(injector_t *injector, const wchar_t *path, void **handle) { struct { void *load_address; wchar_t fullpath[MAX_PATH]; } data = { NULL, }; DWORD pathlen; SIZE_T written; HANDLE hThread; DWORD err; pathlen = GetFullPathNameW(path, MAX_PATH, data.fullpath, NULL); if (pathlen > MAX_PATH) { set_errmsg("too long file path: %S", path); return INJERR_FILE_NOT_FOUND; } if (pathlen == 0) { set_errmsg("failed to get the full path: %S", path); return INJERR_FILE_NOT_FOUND; } if (!WriteProcessMemory(injector->hProcess, injector->remote_mem + page_size, &data, sizeof(data), &written)) { set_errmsg("WriteProcessMemory error: %s", w32strerr(GetLastError())); return INJERR_OTHER; } hThread = CreateRemoteThread(injector->hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)injector->injection_code, injector->remote_mem + page_size + sizeof(void *), 0, NULL); if (hThread == NULL) { set_errmsg("CreateRemoteThread error: %s", w32strerr(GetLastError())); return INJERR_OTHER; } WaitForSingleObject(hThread, INFINITE); GetExitCodeThread(hThread, &err); CloseHandle(hThread); if (err != 0) { set_errmsg("LoadLibrary in the target process failed: %s", w32strerr(err)); return INJERR_ERROR_IN_TARGET; } if (!ReadProcessMemory(injector->hProcess, injector->remote_mem + page_size, &data, sizeof(void *), &written)) { set_errmsg("ReadProcessMemory error: %s", w32strerr(GetLastError())); return INJERR_OTHER; } if (handle != NULL) { *handle = data.load_address; } return 0; } int cki_uninject(injector_t *injector, void *handle) { HANDLE hThread; DWORD err; hThread = CreateRemoteThread(injector->hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)injector->uninjection_code, handle, 0, NULL); if (hThread == NULL) { set_errmsg("CreateRemoteThread error: %s", w32strerr(GetLastError())); return INJERR_OTHER; } WaitForSingleObject(hThread, INFINITE); GetExitCodeThread(hThread, &err); CloseHandle(hThread); if (err != 0) { set_errmsg("FreeLibrary in the target process failed: %s", w32strerr(err)); return INJERR_ERROR_IN_TARGET; } return 0; } int cki_detach(injector_t *injector) { if (injector->remote_mem != NULL) { VirtualFreeEx(injector->hProcess, injector->remote_mem, 0, MEM_RELEASE); } if (injector->hProcess != NULL) { CloseHandle(injector->hProcess); } free(injector); return 0; } const char *cki_error(void) { return errmsg; } static void set_errmsg(const char *format, ...) { va_list ap; int rv; va_start(ap, format); rv = vsnprintf(errmsg, sizeof(errmsg), format, ap); va_end(ap); if (rv == -1 || rv >= sizeof(errmsg)) { errmsg[sizeof(errmsg) - 1] = '\0'; } } static const char *w32strerr(DWORD err) { static char errmsg[512]; DWORD len; len = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), errmsg, sizeof(errmsg), NULL); if (len > 0) { while (len > 0) { char c = errmsg[len - 1]; if (c == ' ' || c == '\n' || c == '\r') { len--; } else { break; } } errmsg[len] = '\0'; } else if ((int)err >= 0) { sprintf(errmsg, "win32 error code %d", err); } else { sprintf(errmsg, "win32 error code 0x%x", err); } return errmsg; } static USHORT process_arch(HANDLE hProcess) { static IsWow64Process2_t IsWow64Process2_func = (IsWow64Process2_t)-1; if (IsWow64Process2_func == (IsWow64Process2_t)-1) { IsWow64Process2_func = (IsWow64Process2_t)GetProcAddress(GetModuleHandleA("kernel32"), "IsWow64Process2"); } if (IsWow64Process2_func != NULL) { /* Windows 10 */ USHORT process_machine; USHORT native_machine; if (IsWow64Process2_func(hProcess, &process_machine, &native_machine)) { if (process_machine != IMAGE_FILE_MACHINE_UNKNOWN) { return process_machine; } else { return native_machine; } } } else { /* Windows 8.1 or earlier */ /* arch will be either x86 or x64. */ #ifdef _M_AMD64 BOOL is_wow64_proc; if (IsWow64Process(hProcess, &is_wow64_proc)) { if (is_wow64_proc) { return IMAGE_FILE_MACHINE_I386; } else { return IMAGE_FILE_MACHINE_AMD64; } } #endif #ifdef _M_IX86 BOOL is_wow64_proc; if (IsWow64Process(GetCurrentProcess(), &is_wow64_proc)) { if (!is_wow64_proc) { /* Run on 32-bit Windows */ return IMAGE_FILE_MACHINE_I386; } /* Run on Windows x64 */ if (IsWow64Process(hProcess, &is_wow64_proc)) { if (is_wow64_proc) { return IMAGE_FILE_MACHINE_I386; } else { return IMAGE_FILE_MACHINE_AMD64; } } } #endif } return IMAGE_FILE_MACHINE_UNKNOWN; } static const char *arch_name(USHORT arch) { switch (arch) { case IMAGE_FILE_MACHINE_AMD64: return "x64"; case IMAGE_FILE_MACHINE_I386: return "x86"; default: return "unknown"; } } BOOL InjectDll(DWORD pid, const WCHAR *dllpath) { if (cki_attach(&g_injector, pid) != 0) { printf("%s\n", cki_error()); return FALSE; } if (cki_inject_w(g_injector, dllpath, NULL) == 0) { return TRUE; } else { fprintf(stderr, " %s\n", cki_error()); return FALSE; } } BOOL EnjectDll(DWORD pid, const WCHAR *dllname) { if (cki_detach(g_injector) == 0) { return TRUE; } return FALSE; }