Dns被莫名篡改的逆向分析定位(笔记)

引言:最近发现用户的多台机器上出现了Dns被莫名修改的问题,从系统事件上看并未能正常确定到是那个具体软件所为,现在的需求就是确定和定位哪个软件具体所为。

解决思路:

  1. 首先到IPv4设置页面对Dns进行设置:

  2. 通过ProcExp确定了该窗口的宿主进程是Explorer.exe,通过ProcMon对Explorer进行监控,并未发现Explorer将静态Dns的地址写入注册表(后来发现其实Explorer是通过DllHost.exe来实现对注册表修改的,所以没监控到)。

  3. 通过对Explorer进行逆向分析发现Explorer实现比较复杂,后来通过网络发现修改Dns可以通过Netsh.exe这个程序来实现:

  4. 于是转到对Netsh.exe的逆向分析上来,经过仔细分析,发现Netsh.exe对dns的修改是通过netiohlp.dll的NhIpHandleSetDnsServer来实现的:

  5. 通过进一步定位发现是NhIpAddDeleteSetServer:

  6. 并发现会通过写入注册表来保存相关信息:

    并通过定位发现注册表地址是:

  7. 并且有重启Dnscahe服务等相关操作:

  8. 1)通过设置系统全局钩子来挂钩系统下所有进程然后挂钩SetRegvalue等api监控,该进程通过SetWindowHookEx来设置全局钩子(其实该挂钩方式不能挂钩没有消息循环的经常),通过inject-helper.exe进程来挂钩发现不能挂钩系统下的所有进程,而且新创建的进程也无法挂钩。
    2)通过设置KnowDlls注册表发现也无法正常挂钩所有进程。
    3)通过底层驱动挂钩,这个方法能监控到应用层的所有进程对注册表的操作,但为了回溯到目标进程,可能也需要加入对父子进程的回溯,这个相对麻烦一些。

  9. 笔者采用相对比较简单容易操作的方法。采用ProcMon来对注册表的监控:
    1)设置第一项筛选:operation is RegSetValue 操作

    2)设置第二项筛选:path is HKLM \SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\{33701f65-c437-47a4-9162-071bd72b3425}\NameServer (复制这个字符串修改UUID即可)

    最后的效果如下图:

    设置好后可以看到监控效果:

  10. 但是我们需要追踪最初始的设置Dns的进程,比如进程A调用了Netsh或DllHost等其他的三方进程来设置Dns,这个时候仅仅监控到Netsh或者DllHost等进程是没用的,需要对进程进行父进程的回溯,才知道源操作进程。

  11. 这个时候需要写一个ProcMon的插件,然后在ProcMon在监控到操作进程后能第一时间对父进程进行回溯。

    编写一个Dll插件,并通过窗口子类化方式来对ProcMon的ListView控件进行消息监控:

    cpp 复制代码
    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;
     }
    cpp 复制代码
    // 这里是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;
     }
    cpp 复制代码
    // 这里是父进程回溯操作
    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;
     }
  12. 代码写好了,这个时候通过CFF软件修改ProcMon的导入表,使其依赖我们的插件,这个时候当ProcMon启动的时候就会自动加载我们的插件了,相当于变相注入到了ProcMon进程。
    看看效果:

  13. 这样的监控程序就算写好了,可以交付运维去部署监控了,一旦监控到就会输出到日志文件,并把父子进程进行了回溯。

复制代码
###### 附录(全局钩子注入代码):

```cpp
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);
}
```

插件代码:

```cpp
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 CALLBACK
        dummy_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的代码:

```cpp

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 _WIN64
	proc = GetProcAddress(lib, "dummy_debug_proc");
#else
	proc = GetProcAddress(lib, "_dummy_debug_proc@12");
#endif

	if (!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;
}
```
相关推荐
XIAOHEZIcode1 天前
Linux系统鼠标偏移常见原因以及修复方案
linux·运维·游戏
用户0328472220702 天前
如何搭建本地yum源(上)
运维
大树885 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠5 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质5 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
qq_369224335 天前
Windows全系通用!ntdll.dll文件丢失、报错、闪退问题的完整排查与修复教程
windows·dll·dll修复·dll丢失·dll错误
Inhand陈工5 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智5 天前
ARP代理--工作原理
运维·网络·arp·arp代理
shushangyun_5 天前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化
施努卡机器视觉5 天前
SNK施努卡侧滑门锁上滑轮总成自动化装配线,从零件到组件,全流程精密制造方案
运维·自动化·制造