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;
}
```
相关推荐
染指11104 分钟前
50.第二阶段x86游戏实战2-lua获取本地寻路,跨地图寻路和获取当前地图id
c++·windows·lua·游戏安全·反游戏外挂·游戏逆向·luastudio
TsengOnce12 分钟前
Docker 安装 禅道-21.2版本-外部数据库模式
运维·docker·容器
永卿00124 分钟前
nginx学习总结(不包含安装过程)
运维·nginx·负载均衡
Stark、25 分钟前
【Linux】文件IO--fcntl/lseek/阻塞与非阻塞/文件偏移
linux·运维·服务器·c语言·后端
dntktop1 小时前
Converseen:全能免费批量图像处理专家
windows
人类群星闪耀时1 小时前
大模型技术优化负载均衡:AI驱动的智能化运维
运维·人工智能·负载均衡
新时代农民工--小明2 小时前
前端自动化部署更新,自动化打包部署
运维·前端·自动化
一个不秃头的 程序员2 小时前
服务器上加入SFTP------(小白篇 1)
运维·服务器
fnd_LN2 小时前
Linux文件目录 --- 复制命令CP、递归复制目录、软连接、硬链接
linux·运维·服务器
MorleyOlsen2 小时前
【Trick】解决服务器cuda报错——RuntimeError: cuDNN error: CUDNN_STATUS_NOT_INITIALIZED
运维·服务器·深度学习