立即注册 登录
鱼C论坛 返回首页

小甲鱼的个人空间 http://bbs.fishc.com/?9 [收藏] [复制] [分享] [RSS]

日志

反调试技术揭秘(转)

热度 104已有 4600 次阅读2013-3-4 20:11 |个人分类:破解知识| 反调试技术, 反调试揭秘, 逆向技术, 反调试API

在调试一些病毒程序的时候,可能会碰到一些反调试技术,也就是说,被调试的程序可以检测到自己是否被调试器附加了,如果探知自己正在被调试,肯定是有人试图反汇编啦之类的方法破解自己。为了了解如何破解反调试技术,首先我们来看看反调试技术。

一、Windows API方法

Win32提供了两个API, IsDebuggerPresent和CheckRemoteDebuggerPresent可以用来检测当前进程是否正在被调试,以IsDebuggerPresent函数为例,例子如下:
 
BOOL ret = IsDebuggerPresent();
printf("ret = %d\n", ret);
 
破解方法很简单,就是在系统里将这两个函数hook掉,让这两个函数一直返回false就可以了,网上有很多做hook API工作的工具,也有很多工具源代码是开放的,所以这里就不细谈了。
 

二、查询进程PEB的BeingDebugged标志位

当进程被调试器所附加的时候,操作系统会自动设置这个标志位,因此在程序里定期查询这个标志位就可以了,例子如下:
 
bool PebIsDebuggedApproach()
{
       char result = 0;
       __asm
       {
// 进程的PEB地址放在fs这个寄存器位置上
              mov eax, fs:[30h]
// 查询BeingDebugged标志位
              mov al, BYTE PTR [eax + 2] 
              mov result, al
       }
 
       return result != 0;
}
 

三、查询进程PEB的NtGlobal标志位 

跟第二个方法一样,当进程被调试的时候,操作系统除了修改BeingDebugged这个标志位以外,还会修改其他几个地方,其中NtDll中一些控制堆(Heap)操作的函数的标志位就会被修改,因此也可以查询这个标志位,例子如下:
 
bool PebNtGlobalFlagsApproach()
{
       int result = 0;
 
       __asm
       {
  // 进程的PEB
              mov eax, fs:[30h]
 // 控制堆操作函数的工作方式的标志位
              mov eax, [eax + 68h]
 // 操作系统会加上这些标志位FLG_HEAP_ENABLE_TAIL_CHECK, 
 // FLG_HEAP_ENABLE_FREE_CHECK and FLG_HEAP_VALIDATE_PARAMETERS,
 // 它们的并集就是x70
 //
 // 下面的代码相当于C/C++的
 //     eax = eax & 0x70
              and eax, 0x70
              mov result, eax
       }
 
       return result != 0;
}
 

四、查询进程堆的一些标志位

这个方法是第三个方法的变种,只要进程被调试,进程在堆上分配的内存,在分配的堆的头信息里,ForceFlags这个标志位会被修改,因此可以通过判断这个标志位的方式来反调试。因为进程可以有很多的堆,因此只要检查任意一个堆的头信息就可以了,所以这个方法貌似很强大,例子如下:
 
bool HeapFlagsApproach()
{
       int result = 0;
 
       __asm
       {
     // 进程的PEB
              mov eax, fs:[30h]
     // 进程的堆,我们随便访问了一个堆,下面是默认的堆
              mov eax, [eax + 18h]
 // 检查ForceFlag标志位,在没有被调试的情况下应该是
              mov eax, [eax + 10h]
              mov result, eax
       }
 
       return result != 0;
}


五、使用NtQueryInformationProcess函数

NtQueryInformationProcess函数是一个未公开的API,它的第二个参数可以用来查询进程的调试端口。如果进程被调试,那么返回的端口值会是-1,否则就是其他的值。由于这个函数是一个未公开的函数,因此需要使用LoadLibrary和GetProceAddress的方法获取调用地址,示例代码如下:

// 声明一个函数指针。
typedef NTSTATUS (WINAPI *NtQueryInformationProcessPtr)(
       HANDLE processHandle,
       PROCESSINFOCLASS processInformationClass,
       PVOID processInformation,
       ULONG processInformationLength,
       PULONG returnLength);
 
bool NtQueryInformationProcessApproach()
{
       int debugPort = 0;
       HMODULE hModule = LoadLibrary(TEXT("Ntdll.dll "));
       NtQueryInformationProcessPtr NtQueryInformationProcess = (NtQueryInformationProcessPtr)GetProcAddress(hModule, "NtQueryInformationProcess");
       if ( NtQueryInformationProcess(GetCurrentProcess(), (PROCESSINFOCLASS)7, &debugPort, sizeof(debugPort), NULL) )
              printf("[ERROR NtQueryInformationProcessApproach] NtQueryInformationProcess failed\n");
       else
              return debugPort == -1;
 
       return false;
}
 

六、NtSetInformationThread方法

这个也是使用Windows的一个未公开函数的方法,你可以在当前线程里调用NtSetInformationThread,调用这个函数时,如果在第二个参数里指定0x11这个值(意思是ThreadHideFromDebugger),等于告诉操作系统,将所有附加的调试器统统取消掉。示例代码:

// 声明一个函数指针。
typedef NTSTATUS (*NtSetInformationThreadPtr)(HANDLE threadHandle,
       THREADINFOCLASS threadInformationClass,
       PVOID threadInformation,
       ULONG threadInformationLength);
 
void NtSetInformationThreadApproach()
{
      HMODULE hModule = LoadLibrary(TEXT("ntdll.dll"));
      NtSetInformationThreadPtr NtSetInformationThread = (NtSetInformationThreadPtr)GetProcAddress(hModule, "NtSetInformationThread");
    
      NtSetInformationThread(GetCurrentThread(), (THREADINFOCLASS)0x11, 0, 0);
}
 

七、触发异常的方法

这个技术的原理是,首先,进程使用SetUnhandledExceptionFilter函数注册一个未处理异常处理函数A,如果进程没有被调试的话,那么触发一个未处理异常,会导致操作系统将控制权交给先前注册的函数A;而如果进程被调试的话,那么这个未处理异常会被调试器捕捉,这样我们的函数A就没有机会运行了。

这里有一个技巧,就是触发未处理异常的时候,如果跳转回原来代码继续执行,而不是让操作系统关闭进程。方案是在函数A里修改eip的值,因为在函数A的参数_EXCEPTION_POINTERS里,会保存当时触发异常的指令地址,所以在函数A里根据这个指令地址修改寄存器eip的值就可以了,示例代码如下:

// 进程要注册的未处理异常处理程序A
LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *pei)
{
       SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)
              pei->ContextRecord->Eax);
       // 修改寄存器eip的值
       pei->ContextRecord->Eip += 2;
       // 告诉操作系统,继续执行进程剩余的指令(指令保存在eip里),而不是关闭进程
       return EXCEPTION_CONTINUE_EXECUTION;
}
 
bool UnhandledExceptionFilterApproach()
{
       SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
       __asm
       {
              // 将eax清零
              xor eax, eax
              // 触发一个除零异常
              div eax
       }
 
       return false;
}

八、调用DeleteFiber函数

如果给DeleteFiber函数传递一个无效的参数的话,DeleteFiber函数除了会抛出一个异常以外,还是将进程的LastError值设置为具体出错原因的代号。然而,如果进程正在被调试的话,这个LastError值会被修改,因此如果调试器绕过了第七步里讲的反调试技术的话,我们还可以通过验证LastError值是不是被修改过来检测调试器的存在,示例代码:

bool DeleteFiberApproach()
{
       char fib[1024] = {0};
       // 会抛出一个异常并被调试器捕获
       DeleteFiber(fib);
 
       // 0x57的意思是ERROR_INVALID_PARAMETER
       return (GetLastError() != 0x57);
}
4

路过
1

鸡蛋
12

鲜花
78

握手

雷人

刚表态过的朋友 (95 人)

发表评论 评论 (22 个评论)

回复 拉登o睡觉 2013-3-4 20:50
好东西,顶一下@!
回复 游人啊k 2013-3-5 09:09
甲鱼老师哟,什么时候更解密系列啊?..
回复 ▄︻┳一_飞 2013-3-6 01:43
什么时候更解密系列啊?.
回复 小甲鱼 2013-3-6 02:45
游人啊k: 甲鱼老师哟,什么时候更解密系列啊?..
最新的已经出炉啦~
回复 小甲鱼 2013-3-6 02:45
▄︻┳一_飞: 什么时候更解密系列啊?.
最新的已经出炉啦~
回复 因為·有你 2013-3-6 21:26
好高深哦 文字读不懂 代码也看不明白呀
回复 sbwcwusi 2013-3-7 22:53
的确是很高深,坐等Win32异常结构和Pe环境块。问一句为什么环境块保存到FS:0寄存器
回复 bafengao 2013-3-9 17:48
小甲鱼老师真乃全才
回复 xihongshi01 2013-3-16 22:25
哟系
回复 伽利略幼稚 2013-4-4 20:07
小顶一下~
回复 daiyitong 2013-4-13 18:26
小甲鱼老师,我是一个工作6年的java程序员,没怎么接触过windows API编程和汇编,所以这方面还是个菜鸟。最近想做个仙5前传修改器,用CE取基址的时候发现游戏自动退出。感觉就是用了您说的反调试技术。针对这个问题,有什么方法可以解决吗?
回复 keison 2013-6-7 22:58
第七种方法好像没说清楚如何反调试的,如果进程没有被调试则不会执行异常处理函数A,那里面的代码又有什么意义呢?
回复 softice112 2013-6-17 20:28
今天调试一天的文件,终于有些信心了  很多东西都被摸出来了 都是从小甲鱼老师那借鉴的经验,我调试的文件和你的课件完全不一样  更大更难
回复 小甲鱼 2013-6-18 18:11
softice112: 今天调试一天的文件,终于有些信心了  很多东西都被摸出来了 都是从小甲鱼老师那借鉴的经验,我调试的文件和你的课件完全不一样  更大更难
不错,加油!
回复 ravenhu13 2013-6-23 22:35
我也是ST的,呵呵,第一次听见你操着那一口普通到没办法再普通的普通话时,我就知道你应该是ST的了,哈,加油吧。
回复 my_angel 2013-11-1 11:39
最近逆向某些软件都遇到反调试情况,看完这篇文章后收益良多。。。。
回复 R-S 2014-2-16 11:38
我真心希望能看懂 好好努力啊还要
回复 无名侠 2014-2-22 13:54
八、调用DeleteFiber函数 这个没测试成功,在没有被调试器调试的情况下 程序还是会崩溃,我是沿用第七个的测试代码做的实验。
回复 大黑鱼 2014-5-18 12:49
鱼哥怎么什么都懂啊!!!
回复 破灬王 2014-6-13 17:56
顶起
12下一页

facelist

您需要登录后才可以评论 登录 | 立即注册

    移动客户端下载(未启用)
    微信公众号

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备11014136号

Copyright 2018 鱼C论坛 版权所有 All Rights Reserved.

Powered by Discuz! X3.1 Copyright
© 2001-2018 Comsenz Inc.    All Rights Reserved.

小黑屋|手机版|Archiver|鱼C工作室 ( 粤公网安备 44051102000370号 | 粤ICP备11014136号

GMT+8, 2017-11-18 05:09

返回顶部