// We must add this header file to support writing to S2E's logs. s2e.h resides // in the libcommon project, so the libcommon project must be added as a // dependency to the malware-inject project #define USER_APP #include<s2e/s2e.h>
staticvoidGetFullPath(LPCWSTR path, PWCHAR fullPath) { if (!path) { Message("Path has not been provided\n"); exit(1); }
if (!PathFileExistsW(path)) { Message("Invalid path %S has been provided\n", path); exit(1); }
if (!GetFullPathNameW(path, MAX_PATH_LEN, fullPath, NULL)) { Message("Unable to get full path of %S\n", path); exit(1); } }
intmain() { INT argc; LPWSTR *argv = CommandLineToArgvW(GetCommandLineW(), &argc);
if (argc < 5) { printf("Usage: %S [options..]\n" " --dll <dll> Path to DLL to inject into the application\n" " --app <target> Path to application to start\n" " --timeout <time> Timeout value in milliseconds " "(infinite if not provided)\n", argv[0]); exit(1); }
// Used by the Message function to decide where to write output to s2eVersion = S2EGetVersion();
// Check that the given paths are valid GetFullPath(dllPath, fullDllPath); GetFullPath(appPath, fullAppPath);
// Start the target application (in a suspended state) and inject the given // DLL ULONG pid; NTSTATUS result = RhCreateAndInject(appPath, L"", CREATE_SUSPENDED, EASYHOOK_INJECT_DEFAULT, #if defined(_M_IX86) dllPath, NULL, #elif defined(_M_X64) NULL, dllPath, #else #error"Platform not supported" #endif NULL, 0, &pid);
if (FAILED(result)) { Message("RhCreateAndInject failed: %S\n", RtlGetLastErrorString()); exit(1); }
Message("Successfully injected %S into %S (PID=0x%x)\n", fullDllPath, fullAppPath, pid);
DWORD exitCode = 1;
// Get a handle to the newly-created process and wait for it to terminate. // Once the process has terminated, get its return code and return that as // our return code HANDLE hProcess = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, FALSE, pid); if (hProcess) { WaitForSingleObject(hProcess, timeout); GetExitCodeProcess(hProcess, &exitCode); CloseHandle(hProcess); } else { Message("Unable to open process 0x%x: 0x%X\n", pid, GetLastError()); }
return exitCode; }
当然,恶意软件完全有可能正在监视 API hook 的操作。虽然这是一个重要问题,但是我们不打算在这篇博客中处理。
// EasyHook will be looking for this export to support DLL injection. If not // found then DLL injection will fail extern"C"void __declspec(dllexport) __stdcall NativeInjectionEntryPoint(REMOTE_ENTRY_INFO *);
// Call the original GetLocalTime to get a concrete value GetLocalTime(lpSystemTime);
// Make the value concolic S2EMakeSymbolic(lpSystemTime, sizeof(*lpSystemTime), "SystemTime"); }
// The names of the functions to hook (and the library they belong to) static LPCSTR functionsToHook[][2] = { { "kernel32", "GetLocalTime"} , { NULL, NULL }, };
// The function hooks that we will install static PVOID hookFunctions[] = { GetLocalTimeHook, };
// The actual hooks static HOOK_TRACE_INFO hooks[] = { { NULL }, };
// This function was defined previously void __stdcall NativeInjectionEntryPoint(REMOTE_ENTRY_INFO *inRemoteInfo){ // ...
// Replace the previous TODO with the following code to install the // GetLocalTime hook for (unsigned i = 0; functionsToHook[i][0] != NULL; ++i) { LPCSTR moduleName = functionsToHook[i][0]; LPCSTR functionName = functionsToHook[i][1];
// Install the hook NTSTATUS result = LhInstallHook( GetProcAddress(GetModuleHandleA(moduleName), functionName), hookFunctions[i], NULL, &hooks[i]);
if (FAILED(result)) { Message("Failed to hook %s.%s: %S\n", moduleName, functionName, RtlGetLastErrorString()); } else { Message("Successfully hooked %s.%s\n", moduleName, functionName); }
// Ensure that all threads _except_ the injector thread will be hooked ULONG ACLEntries[1] = { 0 }; LhSetExclusiveACL(ACLEntries, 1, &hooks[i]); }
# ... # The target does not get executed directly - we execute it via malware-inject function execute_target { local TARGET TARGET="$1"
./malware-inject.exe --dll "./malware-hook.dll" --app ${TARGET} } # ... # We also need to download the files required for hooking # Download the target file to analyze ${S2EGET} "GetLocalTime-test.exe" ${S2EGET} "EasyHook32.dll" ${S2EGET} "malware-hook.dll" ${S2EGET} "malware-inject.exe" # ...
// Force a fork via a symbolic variable. Since both branches are feasible, // both paths are taken UINT8 returnResource = S2ESymbolicChar("hInternet", 1); if (returnResource) { // Explore the program when InternetOpenUrlA "succeeds" by returning a // dummy resource handle. Because we know that the resource handle is // never used, we don't have to do anything fancy to create it. // However, we will need to keep track of it so we can free it when the // handle is closed. HINTERNET resourceHandle = (HINTERNET) malloc(sizeof(HINTERNET));
// Record the dummy handle so we can clean up afterwards dummyHandles.insert(resourceHandle);
return resourceHandle; } else { // Explore the program when InternetOpenUrlA "fails" returnNULL; } }
std::set<HINTERNET>::iterator it = dummyHandles.find(hInternet);
if (it == dummyHandles.end()) { // The handle is not one of our dummy handles, so call the original // InternetCloseHandle function returnInternetCloseHandle(hInternet); } else { // The handle is a dummy handle. Free it free(*it); dummyHandles.erase(it);
// Get this DLL's path HMODULE hDll = NULL; DWORD hModFlags = GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT; if (!GetModuleHandleEx(hModFlags, (LPCTSTR)&Message, &hDll)) { Message("Failed to retrive DLL handle: 0x%X\n", GetLastError()); goto default_create_process; }
WCHAR dllPath[MAX_PATH_LEN]; if (!GetModuleFileNameW(hDll, dllPath, MAX_PATH_LEN)) { Message("Failed to retrive DLL path: 0x%X\n", GetLastError()); goto default_create_process; }
// Create the new process, but force it to be created in a suspended state if (!CreateProcessA(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags | CREATE_SUSPENDED, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation)) { Message("Failed to create suspended process: 0x%X\n", GetLastError()); goto default_create_process; }
// Inject ourselves into the new, suspended process. // NativeInjectionEntryPoint will call RhWakeupProcess, which will kick // ourselves out of the suspended state NTSTATUS result = RhInjectLibrary(lpProcessInformation->dwProcessId, lpProcessInformation->dwThreadId, EASYHOOK_INJECT_DEFAULT, #if defined(_M_IX86) dllPath, NULL, #elif defined(_M_X64) NULL, dllPath, #else #error"Platform not supported" #endif NULL, 0);
if (FAILED(result)) { Message("RhInjectLibrary failed: %S\n", RtlGetLastErrorString()); goto default_create_process; }
if (childPids.size() > 0) { // Convert the set of PIDS to a list of handles with the appropriate // permissions std::vector<HANDLE> childHandles; for (DWORD pid : childPids) { Message("Getting handle to process 0x%x\n", pid); HANDLE childHandle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, FALSE, pid); if (childHandle) { childHandles.push_back(childHandle); } else { Message("Unable to open child process 0x%x: 0x%X\n", pid, GetLastError()); return FALSE; } }
// Wait for the processes to terminate Message("Waiting %d ms for %d children processes to terminate...\n", timeout, childHandles.size()); DWORD waitRes = WaitForMultipleObjects(childHandles.size(), childHandles.data(), TRUE, timeout); switch (waitRes) { case WAIT_FAILED: Message("Failed to wait for child processes: 0x%X\n", GetLastError()); retCode = FALSE; break; case WAIT_TIMEOUT: Message("Timeout - not all child processes may have terminated\n"); break; }
// Close all handles for (HANDLE handle : childHandles) { CloseHandle(handle); } }
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved){ switch (fdwReason) { // Don't exit until all child processes have terminated (or a timeout is // reached) case DLL_PROCESS_DETACH: returnWaitForChildProcesses(CHILD_PROCESS_TIMEOUT); }