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