目录
笔记
反调试技术:识别是否被调试,或者让调试器失效。
探测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