欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]一种破反调试的方法-修改窗口标题

[原创]一种破反调试的方法-修改窗口标题

反调试技术中,有一种方法,是查找当前开启的进程的主窗口的标题,如果发现反调试器的标志如OllyDBG等字样, 反调试的混淆或者异常等机制就会出现。使用这种反调试方法的程序我曾经遇到过,而且跟进去发现就是该使用方法进行反调试的。
昨天,看一个帖子,有人说可以通过改变窗口的标题来破解这种反调试机制,然后他说自己在下面贴了代码的图片,我看了半天也没有看到这种图片,更没有看到代码。不过我觉得这个想法很好,所以,就想去实现试试。
这破程序,调了我四个小时左右才搞定,至于是什么原因,我最后面再说吧。
首先说说思路:
Windows有个函数,叫做SetWindowText,可以改变窗口标题,不过MSDN上明确说了,该函数不能用来改变其他程序的窗口标题。原话如下:
However, SetWindowText cannot change the text of a control in another application.
Setwindowtext无法改变其他程序的窗口标题,那该怎么办?如何让我们可控的代码成为目标进程的一部分?远程线程注入!OK,这样思路就很清楚了,通过远程线程注入,把我们可控的DLL注入到目标进程中,我们就可以使用SetWindowText来改变目标进程的窗口标题了。
        具体实现:
1.        远程线程注入DLL到目标进程
程序运行时,需要指定目标进程的程序名称,也就是在任务栏管理器的进程栏看到的程序名称,核心代码如下:
/*******************************获取进程的访问权限******************************/
//首先是启用访问进程的权限。与此相关的一些API函数有OpenProcessToken、LookupPrivilegevalue、AdjustTokenPrivileges。
        if (!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,&hToken))
        {
                        printf("Call OpenProcessToken Failed,Error Code %08x\n",GetLastError());
                return 1;
        }
        if (!LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&Luid))
        {
                        printf("Call LookupPrivilegeValue Failed,Error Code %08x\n",GetLastError());
                return 1;
        }
        tp.PrivilegeCount = 1;
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
        tp.Privileges[0].Luid = Luid;
        if (!AdjustTokenPrivileges(hToken,0,&tp,sizeof(TOKEN_PRIVILEGES),NULL,NULL))
        {
                        printf("Call AdjustTokenPrivileges Failed,Error Code %08x\n",GetLastError());
                return 1;
        }
        /*******************************获取进程的访问权限******************************/
        pe.dwSize = sizeof(pe);
                /*******************************枚举所有进程,以找到指定的进程*****************/
        hSnap=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);        //获取所有的进程
        bNext=Process32First(hSnap, &pe);
        while(bNext)
        {
                if(!stricmp(pe.szExeFile,prosess))              //找到了指定的进程
                {
                                                //获取指定进程的句柄
                                                printf("Good,I found the specified process.\n");
                        hkernel32=OpenProcess(PROCESS_CREATE_THREAD |
                                                                                        PROCESS_QUERY_INFORMATION |
                                                                                        PROCESS_VM_OPERATION|
                                                                                        PROCESS_VM_WRITE |
                                                                                                                        PROCESS_VM_READ,1,pe.th32ProcessID);
                                                if(hkernel32==NULL || hkernel32 == INVALID_HANDLE_VALUE)
                                                {
                                                        printf("Call OpenProcess Failed,Error Code %08x\n",GetLastError());
                                                }
                        break;
                }
                bNext=Process32Next(hSnap, &pe);
        }
        CloseHandle(hSnap);
                /*******************************枚举所有进程,以找到指定的进程*****************/
                /*******************************远程注入到指定进程*****************************/
                p=NULL;
        p=VirtualAllocEx(hkernel32,NULL,strlen(pkill),MEM_COMMIT,PAGE_READWRITE);        //在目标进程中分配DLL文件名的空间
                if(p==NULL)
                {
                        printf("Call VirtualAllocEx Failed,Error Code %08x\n",GetLastError());
                }
        if( WriteProcessMemory(hkernel32,p,(void *)pkill,strlen(pkill),NULL) ==0)        //在分配的空间中,写入DLL文件名
                {
                        printf("Call WriteProcessMemory Failed,Error Code %08x\n",GetLastError());
                }
        pfn=GetProcAddress(GetModuleHandle("kernel32.dll"),"LoadLibraryA");                        //得到LoadLibraryA的地址
                if(pfn==NULL)
                {
                        printf("Call GetProcAddress Failed,Error Code %08x\n",GetLastError());
                }
                HANDLE hThreadCreate= CreateRemoteThread(hkernel32,NULL,0,(LPTHREAD_START_ROUTINE )pfn,p,NULL,0);        //根据进程句柄,创建远程线程。
        if(  hThreadCreate == NULL || hThreadCreate==INVALID_HANDLE_VALUE  ) //远程线程开启后,就执行pfn(p),也就是加载dll
                {
                        printf("Call CreateRemoteThread Failed,Error Code %08x\n",GetLastError());
                        return -2;
                }
                printf("OK,inject success!\n");
                WaitForSingleObject(hThreadCreate, INFINITE);
                /*******************************远程注入到指定进程*****************************/
2.        DLL中改变目标程序的窗口标题(下面以OllyDBG为例):
运行OD,窗口标题为OllyDbg:

我们的DLL内容的关键内容如下:
BOOL APIENTRY DllMain( HANDLE hModule,
                       DWORD  reason,
                       LPVOID lpReserved
                                         )
{
        if (reason == DLL_PROCESS_ATTACH)        //DLL被加载时
        {
                MessageBoxA(NULL,"Loaded","DLL LOAD",MB_OK);
                m = GetMainWindow();
                SetWindowTextA(m,test);
        }
        else if (reason == DLL_PROCESS_DETACH)        //DLL被卸载时
        {
                UnHookAPI();
        }
        return TRUE;
}
其中,GetMainWindow()函数,是自定义的,功能是获取主窗口的句柄(SetWindowText需要使用该句柄作为参数,以修改其窗口标题)(为什么要自定义?因为AfxGetMainWindows()函数在DLL中不能使用):
HWND GetMainWindow()
{
        DWORD dwCurrentProcessId = GetCurrentProcessId();
        if(!EnumWindows(EnumWindowsProc, (LPARAM)&dwCurrentProcessId))
        {     
                return (HWND)dwCurrentProcessId;
        }
        return NULL;
}
其中的EnumWindows是枚举窗口的,也是自定义的:
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
        DWORD dwCurProcessId = *((DWORD*)lParam);
        DWORD dwProcessId = 0;
        GetWindowThreadProcessId(hwnd, &dwProcessId);
        if(dwProcessId == dwCurProcessId && GetParent(hwnd) == NULL)
        {
                *((HWND *)lParam) = hwnd;
                return FALSE;
        }
        return TRUE;
}
我们把该DLL拷贝到system32目录下,或者和OllyDbg同目录,然后运行前面的注入程序(首先要先让OD运行起来):
install.exe OllyDbg.exe
                注入成功后,会弹出一个框来,表明注入成功:

        点击确定,然后再去查看OD的标题,看是否已经被改了:

        可以看到,标题被成功修改。
        对于部分程序,这样就足够了,但是对于OD,notepad等会在标题中显示正在处理什么内容的程序而言,还不够,因为用OD加载程序时,OD会再次改变标题栏的内容,这样,OllyDbg字样又再次出现,如下图所示:

显然,这样的话,OD加载使用该反调试技术的程序时,就会露馅,从而达不到破除反调试的目的。
那么,该怎么办?怎么才能让OD的标题栏不要改回去呢?
HOOK技术!!OK,下面进行HOOK,以让OD的标题栏为我所控!
3.        HOOK SetWindowText
使用HOOK,拦截SetWindowText事件,从而达到操控OD标题栏的目的。但是,OD的其他控件也需要使用SetWindowText,我们应该放过这些,以便一些内容正常显示,因为我们的目的,只是改变OD标题栏的内容,而不是其他的窗口的内容或者标题。
那么我们如何判断调用SetWindowText是要该标题栏,还是要改其他窗口的内容或者标题?这个我们可以通过窗口句柄来判断,如果SetWindowText的句柄是主窗口,则说明是要该标题,否则不是。OK,思路有了,下面就实现。关键代码如下:
BOOL WINAPI SetWindowText2(
                                                  HWND hWnd,
                                                  LPCTSTR lpString
                                                        )
{
        BOOL ans = TRUE;
        UnHookAPI();
        if (hWnd != m) //只要是m,就change
        {
                                ans = SetWindowTextA(
                                                                        hWnd,
                                                                        lpString
                                                                        );
        }
        else
                ans = SetWindowTextA(m,test);
        HookAPI();
        return ans;
}
SetWindowText2,就是新的函数,每当OD要调用SetWindowText的时候,就会调用SetWindowText2函数,函数里面发生的事情有:首先解除HOOK,因为接下来需要调用真正的SetWindowText以显示内容,然后进行判断,如果句柄不是主窗口句柄,我们就让它正常调用显示,如果是主窗口句柄,我们就把其内容改为test执行的内容,这个内容我们可以任意写入。调用成功后,再次HOOK,以便下次再次拦截。
使用HOOK技术后,当OD载入要调试的程序时,OK的标题也不会恢复为OllyDbg,而是我们控制的那样,如下图所示:

这样,我们就实现了对程序窗口标题的有效修改,并且始终会发挥作用。
上面说了,这个破程序搞了我4个小时左右的时间,原因有以下三点:
1.User32.dll默认是不自动加载的。因此,使用getprocAddress里面的函数之前 ,要先加载该dll        (我本以为user32.dll和kernel32.dll一样会自动加载,没想到居然不是,user32.dll需要在程序中手动加载,否则定位不到SetWindowsText函数的地址)
2.SetWindowTextA并不再调用SetWindowTextW(本以为所有的xxxA()都会最终还是要调用xxxxW()函数,所以起初HOOK时,HOOK的时SetWindowTextW,结果始终不对)
3.OllyDBG里面用的是SetWindowTextA,而不是SetWindowTextW(本以为作为一个发布出来的程序,都会使用xxxW函数,而OD显示标题居然是SetWindowTextA函数)

上传的附件 1.png
2.png
3.png
4.png
5.png

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功