引言:最近发现用户的多台机器上出现了Dns被莫名修改的问题,从系统事件上看并未能正常确定到是那个具体软件所为,现在的需求就是确定和定位哪个软件具体所为。
解决思路:
- 首先到IPv4设置页面对Dns进行设置:
- 通过ProcExp确定了该窗口的宿主进程是Explorer.exe,通过ProcMon对Explorer进行监控,并未发现Explorer将静态Dns的地址写入注册表(后来发现其实Explorer是通过DllHost.exe来实现对注册表修改的,所以没监控到)。
- 通过对Explorer进行逆向分析发现Explorer实现比较复杂,后来通过网络发现修改Dns可以通过Netsh.exe这个程序来实现:
- 于是转到对Netsh.exe的逆向分析上来,经过仔细分析,发现Netsh.exe对dns的修改是通过netiohlp.dll的NhIpHandleSetDnsServer来实现的:
- 通过进一步定位发现是NhIpAddDeleteSetServer:
- 并发现会通过写入注册表来保存相关信息:
并通过定位发现注册表地址是:
- 并且有重启Dnscahe服务等相关操作:
- 1)通过设置系统全局钩子来挂钩系统下所有进程然后挂钩SetRegvalue等api监控,该进程通过SetWindowHookEx来设置全局钩子(其实该挂钩方式不能挂钩没有消息循环的经常),通过inject-helper.exe进程来挂钩发现不能挂钩系统下的所有进程,而且新创建的进程也无法挂钩。
2)通过设置KnowDlls注册表发现也无法正常挂钩所有进程。
3)通过底层驱动挂钩,这个方法能监控到应用层的所有进程对注册表的操作,但为了回溯到目标进程,可能也需要加入对父子进程的回溯,这个相对麻烦一些。 - 笔者采用相对比较简单容易操作的方法。采用ProcMon来对注册表的监控:
1)设置第一项筛选:operation is RegSetValue 操作
2)设置第二项筛选:path is HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\{33701f65-c437-47a4-9162-071bd72b3425}\NameServer (复制这个字符串修改UUID即可)
最后的效果如下图:
设置好后可以看到监控效果:
- 但是我们需要追踪最初始的设置Dns的进程,比如进程A调用了Netsh或DllHost等其他的三方进程来设置Dns,这个时候仅仅监控到Netsh或者DllHost等进程是没用的,需要对进程进行父进程的回溯,才知道源操作进程。
- 这个时候需要写一个ProcMon的插件,然后在ProcMon在监控到操作进程后能第一时间对父进程进行回溯。
编写一个Dll插件,并通过窗口子类化方式来对ProcMon的ListView控件进行消息监控:
static DWORD WINAPI DoWork(LPVOID param){swprintf_s(g_szProfilePath, L"%s\\record_%d.log", GetCurrentExePath().c_str(), GetCurrentProcessId());do {HWND hwndTaskManager = FindWindowW(L"PROCMON_WINDOW_CLASS", L"Process Monitor - Sysinternals: www.sysinternals.com");if (!hwndTaskManager){MessageBox(NULL, L"查找进程窗口失败", L"错误", NULL);break;}DWORD TaskManagerPID = 0;GetWindowThreadProcessId(hwndTaskManager, &TaskManagerPID);if (TaskManagerPID != GetCurrentProcessId()){MessageBox(NULL, L"TaskManagerPID != GetCurrentProcessId()", L"错误", NULL);break;}//EnumChildWindows(hwndTaskManager, _EnumChildProc, NULL);g_hListView = FindWindowExW(hwndTaskManager, NULL, L"SysListView32", L"");if (!g_hListView){MessageBox(NULL, L"获取listview句柄为空", L"错误", NULL);break;}fnOriginNetworkList = (WNDPROC)SetWindowLongPtrW(g_hListView,GWLP_WNDPROC,(LONG_PTR)NetworkListWndProc);} while (0);return 0;}
// 这里是ListView控件的消息处理函数 LRESULT CALLBACK NetworkListWndProc(HWND hwnd, // handle to windowUINT_PTR uMsg, // message identifierWPARAM wParam, // first message parameterLPARAM lParam // second message parameter){if (uMsg == LVM_SETITEMTEXT || uMsg == LVM_SETITEMTEXTA || uMsg == 4211){ wchar_t szSection[50] = { 0 };wsprintf(szSection, L"%d_%d", g_iPrevItem, pItem->iItem);wchar_t szSubItem[50] = { 0 };wsprintf(szSubItem, L"%d", pItem->iSubItem);WritePrivateProfileStringW(szSection, szSubItem, pItem->pszText, g_szProfilePath);if (pItem->iSubItem == 3) // process ID{time_t tm = time(NULL);struct tm now;localtime_s(&now, &tm);wchar_t str[100] = { 0 };wcsftime(str, sizeof(str) / 2, L"%A %c", &now);WritePrivateProfileStringW(szSection, L"time", str, g_szProfilePath);DWORD pid = _wtoi(pItem->pszText);stuProcNode node = { _wtoi(pItem->pszText), szSection };PushProcNodeQueue(node); // 这里不卡顿消息线程,把父进程回溯抛到另一个独立线程处理}}LRESULT rc = CallWindowProc(fnOriginNetworkList,hwnd,uMsg,wParam,lParam);return rc;}
// 这里是父进程回溯操作 static DWORD WINAPI DoQueryProcess(LPVOID param){while (1){WaitForSingleObject(g_hQueryProcEvt, INFINITE); // 新的请求进入队列会触发事件std::vector<stuProcNode> ProcNodeList;GetProcNodeQueue(ProcNodeList);for (int i = 0; i < ProcNodeList.size(); i++){std::wstring strpPidNameList;DWORD pid = ProcNodeList[i].dwPid;while (1){ // 回溯一下父进程HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);if (hProcess == (HANDLE)-1 || hProcess == 0){break;}WCHAR szPath[MAX_PATH] = { 0 };GetProcessImageFileName(hProcess, szPath, MAX_PATH);strpPidNameList += L" [";wchar_t buffer[20] = { 0 };_itow_s(pid, buffer, 20, 10);strpPidNameList += buffer;strpPidNameList += L" ";strpPidNameList += wcsrchr(szPath, L'\\') ? wcsrchr(szPath, L'\\') + 1 : L"NULL";strpPidNameList += L"] ";PROCESS_BASIC_INFORMATION pbi = { 0 };if (0 == NtQueryInformationProcess(hProcess, 0, &pbi, sizeof(pbi), NULL)){pid = pbi.InheritedFromUniqueProcessId;}else{break;}CloseHandle(hProcess);}WritePrivateProfileStringW(ProcNodeList[i].strSection.c_str(), L"PidNameINFO", strpPidNameList.c_str(), g_szProfilePath);}}return 0;}
- 代码写好了,这个时候通过CFF软件修改ProcMon的导入表,使其依赖我们的插件,这个时候当ProcMon启动的时候就会自动加载我们的插件了,相当于变相注入到了ProcMon进程。
看看效果: - 这样的监控程序就算写好了,可以交付运维去部署监控了,一旦监控到就会输出到日志文件,并把父子进程进行了回溯。
-
附录(全局钩子注入代码):
BOOL create_inject_process(LPCWSTR inject_helper, LPCWSTR hook_dll, DWORD id, bool bRemoteThread) {BOOL success = FALSE;WCHAR sz_command_line[4096] = { 0 };PROCESS_INFORMATION pi = { 0 };STARTUPINFO si = { 0 };si.cb = sizeof(si);swprintf_s(sz_command_line, L"\"%s\" \"%s\" %s %lu",inject_helper,hook_dll,bRemoteThread ? L"0" : L"1",id);success = CreateProcessW(inject_helper, sz_command_line, NULL, NULL,false, CREATE_NO_WINDOW, NULL, NULL, &si, &pi);if (success) {CloseHandle(pi.hThread);WaitForSingleObject(pi.hProcess, 300);CloseHandle(pi.hProcess);}else {GetLastError();}return success; }BOOL remote_thread_inject(LPCWSTR inject_helper, LPCWSTR hook_dll, DWORD process_pid) {return create_inject_process(inject_helper, hook_dll, process_pid, true); }BOOL UI_Message_inject(LPCWSTR inject_helper, LPCWSTR hook_dll, DWORD process_tid) {return create_inject_process(inject_helper, hook_dll, process_tid, false); }调用: #define SHELLPLUGINNAME64 L"PatchPlg64.dll" #define SHELLPLUGINNAME32 L"PatchPlg32.dll" #define INJECTHELPER64 L"inject-helper64.exe" #define INJECTHELPER32 L"inject-helper32.exe" void CHookMonitorDlg::OnBnClickedOk() {HWND hwnd = (HWND)0x6060241A; //FindWindowExW(NULL, NULL, L"TaskManagerWindow", L"");DWORD dwPid = 0;DWORD dwTid = GetWindowThreadProcessId(hwnd, &dwPid);BOOL b64BitProcess = IsProcessBit(dwPid);std::wstring strCurpath = GetCurrentExePath();std::wstring strInjectHelper = strCurpath + L"\\" + (b64BitProcess ? INJECTHELPER64 : INJECTHELPER32);std::wstring strInjectPlugin = strCurpath + L"\\" + (b64BitProcess ? SHELLPLUGINNAME64 : SHELLPLUGINNAME32);//remote_thread_inject(strInjectHelper.c_str(), strInjectPlugin.c_str(), dwPid);UI_Message_inject(strInjectHelper.c_str(), strInjectPlugin.c_str(), /*dwTid*/0); }
插件代码:
BOOL APIENTRY DllMain( HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved) {switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:/* this prevents the library from being automatically unloaded* by the next FreeLibrary call */GetModuleFileNameW(hModule, name, MAX_PATH);LoadLibraryW(name);dll_inst =(HINSTANCE)hModule;init_dummy_window_thread();Hook();break;case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:case DLL_PROCESS_DETACH:break;}return TRUE; }#define DEF_FLAGS (WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS)HWND dummy_window; static DWORD WINAPI dummy_window_thread(LPVOID* unused) {static const wchar_t dummy_window_class[] = L"temp_d3d_window_4033485";WNDCLASSW wc;MSG msg;OutputDebugStringA("lzlong dummy_window_thread");memset(&wc, 0, sizeof(wc));wc.style = CS_OWNDC;wc.hInstance = dll_inst;wc.lpfnWndProc = (WNDPROC)DefWindowProc;wc.lpszClassName = dummy_window_class;if (!RegisterClass(&wc)) {//hlog("Failed to create temp D3D window class: %lu",// GetLastError());return 0;}dummy_window = CreateWindowExW(0, dummy_window_class, L"Temp Window",DEF_FLAGS, 0, 0, 1, 1, NULL, NULL,dll_inst, NULL);if (!dummy_window) {//hlog("Failed to create temp D3D window: %lu", GetLastError());return 0;}while (GetMessage(&msg, NULL, 0, 0)) {TranslateMessage(&msg);DispatchMessage(&msg);}(void)unused;return 0; }static inline void init_dummy_window_thread(void) {HANDLE thread =CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)dummy_window_thread, NULL, 0, NULL);if (!thread) {//hlog("Failed to create temp D3D window thread: %lu",// GetLastError());return;}CloseHandle(thread); }extern "C" {__declspec(dllexport) LRESULT CALLBACKdummy_debug_proc(int code, WPARAM wparam, LPARAM lparam){static bool hooking = true;MSG* msg = (MSG*)lparam;if (hooking && msg->message == (WM_USER + 432)) {HMODULE user32 = GetModuleHandleW(L"USER32");BOOL(WINAPI * unhook_windows_hook_ex)(HHOOK) = NULL;unhook_windows_hook_ex = (BOOL(WINAPI*)(HHOOK))GetProcAddress(GetModuleHandleW(L"user32"), "UnhookWindowsHookEx");if (unhook_windows_hook_ex)unhook_windows_hook_ex((HHOOK)msg->lParam);hooking = false;}return CallNextHookEx(0, code, wparam, lparam);} }
部分inject-helper的代码:
typedef HHOOK (WINAPI *set_windows_hook_ex_t)(int, HOOKPROC, HINSTANCE, DWORD);#define RETRY_INTERVAL_MS 500 #define TOTAL_RETRY_TIME_MS 4000 #define RETRY_COUNT (TOTAL_RETRY_TIME_MS / RETRY_INTERVAL_MS)int inject_library_safe_obf(DWORD thread_id, const wchar_t *dll,const char *set_windows_hook_ex_obf) {HMODULE user32 = GetModuleHandleW(L"USER32");set_windows_hook_ex_t set_windows_hook_ex;HMODULE lib = LoadLibraryW(dll);LPVOID proc;HHOOK hook;size_t i;if (!lib || !user32) {return INJECT_ERROR_UNLIKELY_FAIL;}#ifdef _WIN64proc = GetProcAddress(lib, "dummy_debug_proc"); #elseproc = GetProcAddress(lib, "_dummy_debug_proc@12"); #endifif (!proc) {return INJECT_ERROR_UNLIKELY_FAIL;}set_windows_hook_ex = (void*)GetProcAddress(user32, set_windows_hook_ex_obf);hook = set_windows_hook_ex(WH_GETMESSAGE, proc, lib, thread_id);if (!hook) {return GetLastError();}/* SetWindowsHookEx does not inject the library in to the target* process unless the event associated with it has occurred, so* repeatedly send the hook message to start the hook at small* intervals to signal to SetWindowsHookEx to process the message and* therefore inject the library in to the target process. Repeating* this is mostly just a precaution. */for (i = 0; i < RETRY_COUNT; i++) {Sleep(RETRY_INTERVAL_MS);PostThreadMessage(thread_id, WM_USER + 432, 0, (LPARAM)hook);}return 0; }