一个反调试程序的分析

前言:一个反调试程序的分析笔记

知识点

1、如果反调试存在的话并且DLL也同样存在,那么此时不仅要考虑程序自身函数执行的反调试,还需要注意下DLL里面的反调试

2、有些DLL会在到达入口点之前被加载,而这些DLL会检测主程序是否正在被调试,如果正在被调试的话就立即结束进程。有一点必须明确,就是系统动态库并不会对OD进行检测。

3、关于jmp self的利用 -> EB FE

逆向过程

发现一个程序附带的一个DLL,结果一看里面全是用于反调试的操作,自己就记录学习下

IDA拖进这个DLL分析重点如下,这边只能看到入口点是进行初始化环境的操作

然后后面的指令全部都加花了,所以只能动态调试来进行分析了

因为这里发现拖入调试器的时候程序就直接结束了,并且此时这里还有DLL的加载,上面静态分析中看到DLL中也有相关的反调试,所以这里就先考虑反调试在DLL中进行,所以把调试器设置的系统断点选上,然后在DLL的代码区段打一个断点,然后F9,先来观察DLL的入口加载是如何进行运行的

F9之后就断在DLL的加载入口处DLL_PROCESS_ATTACH的代码,如下图所示

通过单步分析,发现下面的指令是主要进行反调试函数的操作,所以这里主要分析这个函数0x10002580

1000651F E8 5CC0FFFF CALL AntiDebu.10002580 ; 反调试主函数

跟进去发现确实加了很多的花指令

调试过程


HOOK NTDLL

7C921187 |. FF55 08 CALL DWORD PTR SS:[EBP+8] -> 7C921187 EBFE

CreateProcess

100027AE FF15 84C00010 CALL DWORD PTR DS:[<&KERNEL32.CreateProc>; kernel32.CreateProcessA

7C921187 FF55 08

7C921187 EB FE

7C921187 |. FF55 08 CALL DWORD PTR SS:[EBP+8]

EB FE jmp self

7C921187 |. FF55 08 CALL DWORD PTR SS:[EBP+8]

分析过程

这边把两个进程的IAT表都进行访问断点设置然后来进行调试,因为过程中需要观察如何进行反调试操作和整体的流程

子进程首先创建MainInstance互斥体,如下图所示

此时子进程就已经有了两个互斥体

接着子进程又开始对两个函数进行相关的WriteProcessMemory操作,第一个函数是,它的地址是0x7C97211C,将其写入C3机器码

1000147D FF15 4CC00010 CALL DWORD PTR DS:[<&KERNEL32.WriteProce>; kernel32.WriteProcessMemory

0012F0C8 FFFFFFFF |hProcess = FFFFFFFF
0012F0CC 7C97211C |Address = 7C97211C
0012F0D0 0012F0E3 |Buffer = 0012F0E3
0012F0D4 00000001 |BytesToWrite = 1
0012F0D8 00000000 \pBytesWritten = NULL

写入的是C3的机器码,当这里F8单步过后可以重新来查看下0x7C97211C的地址,如下图所示

第二个函数是DbgBreakPoint,函数地址为7C92120E

0012F0C8 FFFFFFFF |hProcess = FFFFFFFF
0012F0CC 7C92120E |Address = 7C92120E
0012F0D0 0012F0E3 |Buffer = 0012F0E3
0012F0D4 00000001 |BytesToWrite = 1
0012F0D8 00000000 \pBytesWritten = NULL

DbgBreakPoint写入的也是同样是一个字节,也是C3的机器码

接着子进程继续调用OpenMutexA来获取"WAIT"的互斥体,这个互斥体是在主进程中进行创建的,此时子进程要获取这个"WAIT"的互斥体

0012F0FC 001F0001 |Access = 1F0001
0012F100 00000000 |Inheritable = FALSE
0012F104 1000C324 \MutexName = "WAIT"

当获取到了"WAIT"互斥体之后下面就开始WaitForSingleObject,如果这里不将参数默认5秒修改的话,那么默认过了5秒之后,子进程就会直接进行ExitProcess的结束,所以这里将参数修改为INFINTE -> FFFFFFFF

10002DAD FF15 78C00010 CALL DWORD PTR DS:[<&KERNEL32.WaitForSin>; kernel32.WaitForSingleObject

接着子进程就会进行堵塞,如下图所示,此时如果单步F8走的话都一直无法进行响应

这里继续切回主进程来,这里主进程会开始CreateFile来打开相关的文件,但是不知道具体为什么这样做,wp说是检测相关的int 3 -> 0xCC断点,然后我发现就算0xCC存在的话,那么也不会存在相关的反调试的操作

0012EF4C 0012EFA8 |FileName = "AntiDebugDll.dll"
0012EF50 80000000 |Access = GENERIC_READ
0012EF54 00000000 |ShareMode = 0
0012EF58 00000000 |pSecurity = NULL
0012EF5C 00000003 |Mode = OPEN_EXISTING
0012EF60 00000000 |Attributes = 0
0012EF64 00000000 \hTemplateFile = NULL

接着又继续读取Patrick.exe,这里的时候读取模式FILE_SHARE_READ

0012F0E0 00146488 |FileName = "C:\get\Patrick.exe"
0012F0E4 80000000 |Access = GENERIC_READ
0012F0E8 00000001 |ShareMode = FILE_SHARE_READ
0012F0EC 00000000 |pSecurity = NULL
0012F0F0 00000003 |Mode = OPEN_EXISTING
0012F0F4 00000080 |Attributes = NORMAL
0012F0F8 00000000 \hTemplateFile = NULL

接着会看到主进程开始对0x00401000地址开始进行操作,这里VirtualProtectEx数据修改相关的权限,那么后面就开始进行读写内存了

0012EE74 00000064 |hProcess = 00000064 (window)
0012EE78 00401000 |Address = Patrick.00401000
0012EE7C 00005000 |Size = 5000 (20480.)
0012EE80 00000040 |NewProtect = PAGE_EXECUTE_READWRITE
0012EE84 0012EE9C \pOldProtect = 0012EE9C

可以看到下面就开始ReadProcessMemory进行读写操作

100036E7 8B1D 9CC00010 MOV EBX,DWORD PTR DS:[<&KERNEL32.ReadPro>; kernel32.ReadProcessMemory

这个还需要注意下,这里的ReadProcessMemory是一个循环操作,对401000这个区段重复进行读写操作,写入的次数这里取决于EBP寄存器中存储的值

上面写入完成之后主进程这里就会调用ReleaseMutex释放锁,给互斥体"WAIT"发送一个信号,那么就知道下面就会来到子进程的WaitForSingleObject就开始接收到对互斥体的控制权,下面就来到子进程的处理过程

10002B7C FF15 80C00010 CALL DWORD PTR DS:[<&KERNEL32.ReleaseMut>; kernel32.ReleaseMutex

这里切回子进程可以看到此时已经来到了WaitForSingleObject的下一行,原因就是上面的主进程进行ReleaseMutex释放锁,接着子进程的WaitForSingleObject就会接收到相关的信息

接着这里主进程还会进程WaitForInputIdle等待输入进行堵塞的操作,接下来就是子进程的开始了

10002BB5 FF15 70C10010 CALL DWORD PTR DS:[<&USER32.WaitForInput>; USER32.WaitForInputIdle

如下图所示,接下来继续来跟子进程,这里可以看到子进程又执行了ReleaseMutex,但是这里没有人通过WaitForSingleObject来接收互斥体的信号,所以这里的话子进程还是继续往下进行

子进程这里也会跟主进程进行相同的操作,这里调用了CreateFileA

10003E9C FF15 98C00010 CALL DWORD PTR DS:[<&KERNEL32.CreateFile>; kernel32.CreateFileA

0012F0E0 001451C8 |FileName = "AntiDebugDll.dll"
0012F0E4 80000000 |Access = GENERIC_READ
0012F0E8 00000001 |ShareMode = FILE_SHARE_READ
0012F0EC 00000000 |pSecurity = NULL
0012F0F0 00000003 |Mode = OPEN_EXISTING
0012F0F4 00000080 |Attributes = NORMAL
0012F0F8 00000000 \hTemplateFile = NULL

接着第二次读取AntiDebugDll.dll,但是这里的参数ShareMode 此时为0,默认的话就读取失败,所以这里需要自己将其设置为1

0012EF54 00000000 |ShareMode = 0

上面读取了,这里就会继续读取程序Patrick.exe

0012F0E0 00145458 |FileName = "C:\get\Patrick.exe"
0012F0E4 80000000 |Access = GENERIC_READ
0012F0E8 00000001 |ShareMode = FILE_SHARE_READ
0012F0EC 00000000 |pSecurity = NULL
0012F0F0 00000003 |Mode = OPEN_EXISTING
0012F0F4 00000080 |Attributes = NORMAL
0012F0F8 00000000 \hTemplateFile = NULL

子进程这里就开始第二次进行读取,默认读取ShareMode参数为0,但是如果为0的话正常的程序会读取失败,所以这里还是将其参数ShareMode修改为1让其读取成功

0012EF4C 0012EFA8 |FileName = "C:\get\Patrick.exe"
0012EF50 80000000 |Access = GENERIC_READ
0012EF54 00000000 |ShareMode = 0
0012EF58 00000000 |pSecurity = NULL
0012EF5C 00000003 |Mode = OPEN_EXISTING
0012EF60 00000000 |Attributes = 0
0012EF64 00000000 \hTemplateFile = NULL

子进程这里也会进行VirtualProtectEx函数调用,让00401000代码段进行修改访问权限

100036D2 FF15 00C00010 CALL DWORD PTR DS:[<&KERNEL32.VirtualPro>; kernel32.VirtualProtectEx

0012EE74 FFFFFFFF |hProcess = FFFFFFFF
0012EE78 00401000 |Address = Patrick.00401000
0012EE7C 00005000 |Size = 5000 (20480.)
0012EE80 00000040 |NewProtect = PAGE_EXECUTE_READWRITE
0012EE84 0012EE9C \pOldProtect = 0012EE9C

子进程一样的进行WriteProcessMemory,读取次数为EBP寄存器中的值

10003720 FF15 4CC00010 CALL DWORD PTR DS:[<&KERNEL32.WriteProce>; kernel32.WriteProcessMemory

0012EE74 FFFFFFFF |hProcess = FFFFFFFF
0012EE78 00401070 |Address = 401070
0012EE7C 0012EEA4 |Buffer = 0012EEA4
0012EE80 00000010 |BytesToWrite = 10 (16.)
0012EE84 0012EEA0 \pBytesWritten = 0012EEA0

子进程中读完完了之后,这里就会通过CreateThread来创建线程,如下图所示,这个是第一个线程,下面还可以看到第二个线程,如下图所示

0012F0F0 00000000 |pSecurity = NULL
0012F0F4 00000000 |StackSize = 0
0012F0F8 10002140 |ThreadFunction = AntiDebu.10002140
0012F0FC 00000000 |pThreadParm = NULL
0012F100 00000000 |CreationFlags = 0
0012F104 0012F390 \pThreadId = 0012F390

这里看到线程地址为0x10002140

10002140 51 PUSH ECX
10002141 56 PUSH ESI
10002142 8B35 70C00010 MOV ESI,DWORD PTR DS:[<&KERNEL32.Sleep>] ; kernel32.Sleep
10002148 68 C8000000 PUSH 0C8
1000214D FFD6 CALL ESI
1000214F C74424 04 00000>MOV DWORD PTR SS:[ESP+4],0
10002157 50 PUSH EAX
10002158 64:A1 18000000 MOV EAX,DWORD PTR FS:[18]
1000215E 3E:8B40 30 MOV EAX,DWORD PTR DS:[EAX+30]
10002162 3E:0FB640 02 MOVZX EAX,BYTE PTR DS:[EAX+2]
10002167 894424 08 MOV DWORD PTR SS:[ESP+8],EAX
1000216B 58 POP EAX
1000216C 8B4424 04 MOV EAX,DWORD PTR SS:[ESP+4]
10002170 85C0 TEST EAX,EAX
10002172 ^ 74 D4 JE SHORT AntiDebu.10002148
10002174 6A 00 PUSH 0
10002176 FF15 0CC00010 CALL DWORD PTR DS:[<&KERNEL32.ExitProces>; kernel32.ExitProcess
1000217C CC INT3
1000217D 90 NOP
1000217E 90 NOP
1000217F 90 NOP
10002180 68 0E000780 PUSH 8007000E
10002185 E8 F6EFFFFF CALL AntiDebu.10001180

这里可以到该线程执行的就是一直sleep,然后执行的就是ExitProcess,那么这里的话就不能让其执行,所以这里的话就是让线程进行挂起操作

到了这里就可以把之前设置的IAT内存断点都删除了,接着在代码段进行下个访问断点,最后进行F9来到如下,有问题

这时候将00401d55的地址为OEP进行DUMP,发现此时还是无法进行运行程序

接着这里重新打开,观察所有的函数相关调用,然后这里会发现还有一个DLL中调用的函数,这里不用该DLL,那么这里就对该DLL的函数进行NOP,直接不使用DLL即可

接着在DLL的启动口进行RETN处理,让其不使用DLL

然后这里F9运行,可以发现程序已经能运行了,而且是不依赖于DLL的情况下

posted @   zpchcbd  阅读(371)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
历史上的今天:
2020-03-20 PHP POP 链
点击右上角即可分享
微信分享提示