引言:最近发现用户的多台机器上出现了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 window UINT_PTR uMsg, // message identifier WPARAM wParam, // first message parameter LPARAM 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进程。
看看效果: -
这样的监控程序就算写好了,可以交付运维去部署监控了,一旦监控到就会输出到日志文件,并把父子进程进行了回溯。
-