恶意代码分析-第十六章-反调试技术

目录

笔记

实验

Lab16-1

Lab16-2

Lab16-3


笔记

反调试技术:识别是否被调试,或者让调试器失效。

探测windows调试器

       Windows API:IsDebuggerPresent-->查询进程块环境块PEB中的IsDebugged标志

                               CheckRemoteDebuggerPresent-->检测本地机器中的一个进程是否运行在调试器中,也可以通过传递自身进程句柄来探测自己是否被调试

                            NtQueryInformationProcess-->Ntdll.dll中的一个原生的API,NtQueryInformationProcess(句柄,进程信息类型),第二个参数设置为0x7,会告诉这个句柄的标识是否正在被调试。

                               OutputDebugString-->在调试器中显示一个字符串,事先设置一个错误码。

      手动检测调试器:手动检测数据结构

                                  1.检测BeingDebugged           

typedef struct _PEB {
  BYTE                          Reserved1[2];
  BYTE                          BeingDebugged; //被调试状态
  BYTE                          Reserved2[1];
  PVOID                         Reserved3[2];
  PPEB_LDR_DATA                 Ldr;
  PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
  BYTE                          Reserved4[104];
  PVOID                         Reserved5[52];
  PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
  BYTE                          Reserved6[128];
  PVOID                         Reserved7[1];
  ULONG                         SessionId;
} PEB, *PPEB;

BYTE BeingDebugged //,这里就是记录程序的调试状态的,(1代表被调试,0代表没有被调试) 我们可以使用 BOOL WINAPI IsDebuggerPresent(void);这个函数来检测是否有调试器存在,返回非0值代表被调试,如果返回0代表没有被调试。                   

76A9A720 64 A1 30 00 00 00    mov         eax,dword ptr fs:[00000030h]  //获取PEB结构基地址
76A9A726 0F B6 40 02          movzx       eax,byte ptr [eax+2]                       //根据PEB结构,我们知道是取
76A9A72A C3                   ret 

            PEB结构在TEB结构的0x30偏移的地方,也就是fs:[0x30]处可以取到PEB的基地址。

解决方法:手动改0标志,设置BeingDebugged属性为0

                                              2.检测ProcessHeap

Reserved4数组中一个未公开的位置叫做ProcessHeap,位于PEB结构的0x18处,第一个堆头部都有一个属性来指明这个堆是否在调试中创建。这些属性叫ForceFlags(堆头部偏移量为0x10/0x44处)和Flags(堆头部偏移量为0x0c/0x40处)。

解决办法:手动修改ProcessHeap值

                                  3.检测NTGlobalFlag

调试器中启动进程与正常模式下启动进程有些不同,所以创建内存堆的方式也不同,使用PEB结构偏移量0x68的位置决定如何创建堆结构。如果值为0x70就是正在运行。

解决办法:手动修改ProcessHeap属性

            系统痕迹检测:注册表,查找文件目录,进程列表,FindWindow

默认情况下是Dr.Watson,当被调试时,可能变为OllyDbg

识别调试器的行为

            INT扫描:在代码中查找0xCC

                                repne scasb扫描代码CC,解决的办法就是应用硬件断点

           代码校验和检查:CRC循环冗余校验或者MD5

      时钟检测:被调试的时候,代码的运行速度会降低。 rdstc(0x0F31)-->返回至系统启动以来的时间。QueryPerformanceCounter函数和GetTickCount函数有同样的功能

                                                  一种方法记录操作前后的时间戳,比较这两个时间戳:查看两次调用 rdstc的差值是否大于0xFF

                                                  一种方法记录一个异常前后的时间戳。                                             

干扰调试器功能

        使用TLS回调:在程序入口点之前执行的代码是TLS回调。TLS是Windows的一个存储类。TLS运行每个线程维护一个用TLS声明的专有变量。实现TLS回调时,会在PE头部保包含一个.tls段。

                                               解决:Ctrl+E查看所有二进制的入口点,每一个TLS回调函数都有一个前缀字符串TlsCallback。

                                                          OD Options --> Debugging Options --> Events,设置System break-point为第一个暂停的位置

         使用异常:使用异常来破坏或者探测调试器,多数调试不把异常传递给应用程序,这时来探测调试器。

                                               解决:把所有异常传递给应用程序。

         插入中断:在合法指令序列插入中断,破坏程序正常运行

                           1.插入INT3

设置一个新的SEH,然后调用INT 3

                           2.插入INT 2D --->内核调试器设置断点的方法

                           3.插入ICE断点--->会产生一个单步异常,所以在遇到这个指令时,不要使用单步。

调试器漏洞

         PE头漏洞

        1.PE头中的NumberOfRvaAndSize属性是后面DataDirectory数组中的元素个数,当大于0x10时,Windows加载器会忽略,而OD将会崩溃(Bad or Unknown 32-bit Executable File)错误。

        2.当SizeOfRawData不合法调试就会报错(File contains too much data)。因为Windows加载器加载时会比较VirtualSize和SizeOfRawData的大小,取小的加载到内存。而调试器直接会选SizeOfRawData的值

         OutputDebugString漏洞:格式化字符串漏洞,%s会崩溃。

 

实验

Lab16-1

检测BeingDebugged为1正在调试状态。fs:30的位置线程环境块

 检测ProcessHeap是否为0

检测NTGlobalFlag

这个插件可以很好的去抵抗这些反调试技术,或者手动dump fs:[30]+2

Lab16-2

查看导出函数。Ctrl+K 程序有两个入口点,第一个函数的功能是查看是否有OLLYDBG调试器,用于反调试。并且第一个函数位于TLS节中,先于start函数执行。禁用这个反调试技术是把exit的函数调用变成NOP,或者选上一个例子的PHantOm插件。

创建一个线程CreateThread,然后调用比较字符串函数strncmp。可以在OD里面载入发现要比较的字符串是bzrr,实际上这个字符串是不正确的。其中压入的一个参数是encode_password,应该是一个正确的字符串。现在跟入CreateThread创建的StartAddress函数。

StartAddress

关于encode_password的移位操作,说明这是一个解密函数

检测BeingDebugged是否为调试状态,要对抗这个反调试,就需要bl恒为0

byte_40A968参与了运算 

byte_40A968在第二条语句被修改了

先将3039定义为一个错误码,然后利用一个OutputDebugStringA输出一个b,然后得到这个错误码与刚刚定义的错误码进行比较。这里的区别是在调试中的错误码就是3039,所以会有一个自增的操作,而在DOS窗口中,这个错误码不是3039,所以不会有自增操作。这里也实现反调试的功能。解决方法把自增NOP掉

然后OD调试,跟到密码处为byrr

Lab16-3

根据导出函数,可以看到这个两个函数都是获取时候 

联续两次调用,相减来与4B0进行比较

联续两次调用,获取时间差,这两个函数用于回至系统启动以来的时间。

main函数开始看,在栈空间上创建了两个字符串:1qbz2wsx3edc和ocl.exe,然后调用了字符串比对函数。但在比对之前调用sub_4011E0

sub_4011E0

调用了两次QueryPerformanceCounter。第二个QueryPerformanceCounter函数下方的语句。函数的返回值保存在了edx中,然后用edx减去第一次调用QueryPerformanceCounter函数所得到的返回值。接下来用二者的差值与0x4B0,也就是十进制的1200毫秒进行比较。如果二者的差值大于1200毫秒,那么var_118的值就会被设置为2,否则它会保持它的初始值1。

第一处QueryPerformanceCounter后面的两条语句的作用就是获取当前EIP的值,并将这个值存入eax寄存器

两个QueryPerformanceCounter的中间有一个除0异常(div ecx),在此之前还定义了一个异常处理例程。(恶意程序获取了当前的EIP值,并将其保存到eax寄存器以后,程序会间接地将eax中保存的值与0x2C相加,也就是0x00401228+0x2C=0x00401254,并且将这个值入栈)

结论:运行在调试器中的程序会花费较多的时间来进行异常的处理。通过这个时间差,于是就能够判定当前程序是否运行在调试器中了

该循环会利用var_118与传入这个函数的参数字符串进行运算。因此,QueryPerformanceCounter对于时间的计算结果会直接对这个循环中的运算产生影响。解决办法:nop将0x00401292处的语句填充掉

接着往下,这里调用了两次取时间的函数,然后检验差值是不是等于1毫秒,如果超出了1毫秒,执行eax的异或指令,之后将edx中的内容保存到地址为0的内存空间中,那么这显然是一个非法操作,因此程序就会崩溃。如果比1小就直接退出。中间有一个sub_401000

解决办法:为了修正这个位置,需要让jbe的跳转指令始终执行才行。或者将0x004015B2到0x004015B6位置的语句全都nop掉。

sub_401000

发现,这里再次出现了除零异常

跟入sub_401300函数

 两次调用rdtsc,rdstc(0x0F31)-->返回至系统启动以来的时间。而在这两处指令中间,又是一个除零异常。

结论:QueryPerformanceCounter检查成功,恶意程序会修改它正常运行所需的字符串;如果GetTickCount检查成功,恶意程序会产生一个导致程序崩溃的异常;如果rdtsc检查成功,恶意程序会从硬盘上删除自身。

          恶意程序修改SEH机制,在两个计时函数调用之间,加入了恶意程序自己的异常处理例程,这样它就可以捕获一个它处理的异常。异常在调试器内处理比在调试器外处理慢得多

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值