首页
社区
课程
招聘
[原创]一种破反调试的方法-修改窗口标题
发表于: 2013-5-27 19:39 24757

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

2013-5-27 19:39
24757

反调试技术中,有一种方法,是查找当前开启的进程的主窗口的标题,如果发现反调试器的标志如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函数)


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

上传的附件:
收藏
免费 5
支持
分享
最新回复 (27)
雪    币: 47147
活跃值: (20405)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
2
鼓励一下,1个小时左右,论坛系统会自动将你转为正式会员。
2013-5-27 20:44
0
雪    币: 46
活跃值: (37)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
3
非常感谢~~
2013-5-27 21:49
0
雪    币: 198
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
这个着实不错。记得从资源区下载过OD的源码,等下试试看看能不能从这上面改一下,直接去掉上面的OD字样
2013-5-28 14:29
0
雪    币: 1042
活跃值: (495)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
俺感觉OD插件比较方便~
2013-5-28 14:52
0
雪    币: 8209
活跃值: (4518)
能力值: ( LV15,RANK:2473 )
在线值:
发帖
回帖
粉丝
6
只能说楼主没读懂msdn

你试试直接修改其他进程主窗口标题,不需要注入的
2013-5-28 17:55
0
雪    币: 3520
活跃值: (1852)
能力值: ( LV6,RANK:93 )
在线值:
发帖
回帖
粉丝
7
现在加精的门槛很低啊~~
2013-5-28 18:39
0
雪    币: 45
活跃值: (55)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
8
要么MSDN错了,要么窗口不能叫做控件。。
2013-5-28 19:08
0
雪    币: 270
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
原创都是精~
2013-5-28 20:04
0
雪    币: 40
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
支持了  正在学注入技术。
2013-5-28 21:24
0
雪    币: 1392
活跃值: (5137)
能力值: ( LV13,RANK:240 )
在线值:
发帖
回帖
粉丝
11
啊,这样就能拿精华啦?。。55555早知道我也来赚精华了。。5555
2013-5-28 21:33
0
雪    币: 967
活跃值: (1138)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
12
大家应该知道 有个东西 叫模拟按键吧
首先获取xxx的句柄 接着xxxx
估计就可以了
2013-5-28 21:46
0
雪    币: 47147
活跃值: (20405)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
13
原先发在『临时会员版』 版,为了鼓励新人,门槛比较低。
2013-5-28 21:47
0
雪    币: 276
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
msdn不会看,改变标题居然需要注入,误导。
2013-5-29 05:43
0
雪    币: 89
活跃值: (53)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
都用OD各种插件,   很少能见到这么朴实的孩子了
2013-5-29 09:12
0
雪    币: 2984
活跃值: (2765)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
这里注入的主要作用,是防止OD的标题又变回去
2013-5-29 09:27
0
雪    币: 141
活跃值: (318)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
可以看看呀,,,,
2013-5-29 12:15
0
雪    币: 107
活跃值: (404)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
随便用个Spyxx之类的,先看看窗口句柄,,在你程序直接操作一下那个hWnd嘛
2013-5-29 13:04
0
雪    币: 206
活跃值: (14)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
现在的检测OD软件太厉害了
2013-5-29 22:48
0
雪    币: 29
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
呵呵 LZ为了解决一只蚂蚁动用了核武器! MSDN文档都读错了 ~
不过过程还是不错的!
2013-5-29 23:33
0
雪    币: 154
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
能贴一下HOOKAPI函数的代码吗?新人菜鸟想学习一下,多谢LZ了
2013-5-30 09:10
0
雪    币: 34
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
新人Mark一下,有空实验一下
2013-5-30 14:39
0
雪    币: 4
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
pfn=GetProcAddress(GetModuleHandle("kernel32.dll"),"LoadLibraryA");      //得到LoadLibraryA的地址

本进程获取到的LoadLibraryA的地址和目标进程的地址相同吗? LZ能解释一下么
2013-5-31 16:26
0
雪    币: 4
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
为什么能直接把这个地址用在目标进程作为目标进程LoadLibraryA的地址呢?
2013-5-31 16:27
0
雪    币: 1689
活跃值: (379)
能力值: ( LV15,RANK:440 )
在线值:
发帖
回帖
粉丝
25
UE直接修改Ollydbg.exe 文件不就行了?
2013-5-31 16:40
0
游客
登录 | 注册 方可回帖
返回
//