在论坛看到一老兄求助地址(http://bbs.pediy.com/showthread.php?t=92330),对其开始异常处理分析了一下。
该anti手段是利用OD的单步机制来实现的
所以anti的前提是:单步
在钱老师的鼓励之下逆向了OD的step
如有不妥 请勿见怪
测试请见附件中的demo
既然说是利用单步来的
请先走到开头处构造的异常语句处
然后 单步一下 最后Run起来就可以了
先上逆向分析过程的代码
首先用OD load 官方原版od 1.10 F9 run起来
被调试od载入附件中demo程序 事先走到
mov eax,eax ;eax=0
这句来
然后回到调试od中 bp SetThreadContext 和 WaitForDebugEvent
为了方便 在调试od中忽略了内存访问异常
然后在被调试od中f8一下
0042EB1A |> \53 |PUSH EBX ; /pContext
0042EB1B |. 8B45 EC |MOV EAX,DWORD PTR SS:[EBP-14] ; |
0042EB1E |. 8B50 0C |MOV EDX,DWORD PTR DS:[EAX+C] ; |
0042EB21 |. 52 |PUSH EDX ; |hThread
0042EB22 |. E8 83060800 |CALL <JMP.&KERNEL32.SetThreadContext> ; \SetThreadContext
这段代码处于Go函数里面 该Go函数被调用于单步异常模块中
先不管它 F9 让它跑下那句异常代码先
00439616 > \6A 00 PUSH 0 ; /Timeout = 0. ms
00439618 . 68 14574D00 PUSH OFFSET <OLLYDBG.DebugEvent.dwDebugEventCode> ; |pDebugEvent = OFFSET <OLLYDBG.DebugEvent.dwDebugEventCode>
0043961D . E8 E85B0700 CALL <JMP.&KERNEL32.WaitForDebugEvent> ; \WaitForDebugEvent
00439622 . 85C0 TEST EAX,EAX
00439624 . 75 44 JNZ SHORT OLLYDBG.0043966A
;004D5714为全局的DEBUG_EVENT结构
004D5714 > 00000001 00000558 0000078C C0000005 ;ExceptionCode
004D5724 00000000 00000000 00401011 ;ExceptionAddress
这时候就捕获了内存访问异常
跟着它的处理 来到
00439743 . 833D 14574D00>CMP DWORD PTR DS:[<DebugEvent.dwDebugEventCode>],1 ;判断是不是EXCEPTION_DEBUG_EVENT
0043974A . 75 0E JNZ SHORT OLLYDBG.0043975A
0043974C . 8B0D 2C574D00 MOV ECX,DWORD PTR DS:[4D572C] ; OLLYDBG.00401011 ;当前指令地址
00439752 . 3B0D 30814D00 CMP ECX,DWORD PTR DS:[4D8130] ; OLLYDBG.00401013 ;理想中的下一条指令地址
00439758 . /74 0A JE SHORT OLLYDBG.00439764
0043975A > |C705 7C7C4D00>MOV DWORD PTR DS:[4D7C7C],1
00439764 > \33C0 XOR EAX,EAX
00439766 . 33D2 XOR EDX,EDX
00439768 . A3 30814D00 MOV DWORD PTR DS:[4D8130],EAX
0043976D . 8D4D AC LEA ECX,DWORD PTR SS:[EBP-54]
00439770 . C605 203A4E00>MOV BYTE PTR DS:[4E3A20],0
00439777 . 8915 543B4E00 MOV DWORD PTR DS:[4E3B54],EDX
0043977D . 51 PUSH ECX
0043977E . E8 4D54FFFF CALL OLLYDBG.0042EBD0 ;执行一些前奏 比如此时是内存访问异常 这里会先判断下是否处于kernel32和是否选择了忽略在kernel32内存中的异常 和sprintf一些UI字符串"Access violation when %s [%08lX]%s"这些
然后就到了这里 因为要单步了
00439964 . /75 24 JNZ SHORT OLLYDBG.0043998A
00439966 . |833D CC574D00>CMP DWORD PTR DS:[4D57CC],8
0043996D . |74 1B JE SHORT OLLYDBG.0043998A
0043996F . |6A 00 PUSH 0 ; /Arg5 = 00000000
00439971 . |6A 00 PUSH 0 ; |Arg4 = 00000000
00439973 . |6A 00 PUSH 0 ; |Arg3 = 00000000
00439975 . |6A 00 PUSH 0 ; |Arg2 = 00000000
00439977 . |A1 1C574D00 MOV EAX,DWORD PTR DS:[<DebugEvent.dwThreadId>] ; |
0043997C . |50 PUSH EAX ; |Arg1 => 0000078C
0043997D . |E8 92B0FFFF CALL OLLYDBG._Go ; \_Go
Go的原型:
int Go(ulong threadid,ulong tilladdr,int stepmode,int givechance,int backupregs);
关于参数三:
stepmode - stepping mode, one of the following:
STEP_SAME(0) Same action as on previous call to Go
STEP_RUN(1) Run program
STEP_OVER(2) Step over (execute calls at once)
STEP_IN(3) Step in (enter subroutines)
STEP_SKIP(4) Skip sequence till specified address
OllyDbg Plugin API v1.10
由于上一步为单步
所以在函数实现内部
00434A80 |> \837D 10 00 CMP DWORD PTR SS:[EBP+10],0 ;判断是否为STEP_SAME
00434A84 |. 75 11 JNZ SHORT OLLYDBG.00434A97
00434A86 |. 8B56 7C MOV EDX,DWORD PTR DS:[ESI+7C] ;取出上步操作
00434A89 |. 8955 10 MOV DWORD PTR SS:[EBP+10],EDX ;STEP_OVER(2)
所以此时变成了单步了
00434C88 |. 837D 10 02 CMP DWORD PTR SS:[EBP+10],2
00434C8C |. 0F94C0 SETE AL
00434C8F |. 83E0 01 AND EAX,1
00434C92 |. 83FF 70 CMP EDI,70
00434C95 |. 8945 F8 MOV DWORD PTR SS:[EBP-8],EAX
00434C98 |. 0F85 41010000 JNZ OLLYDBG.00434DDF ;判断是否为单步
最后来到此处:
0043534F |. 6A 00 PUSH 0 ; /Arg4 = 00000000
00435351 |. A3 583B4E00 MOV DWORD PTR DS:[4E3B58],EAX ; |
00435356 |. 8B55 18 MOV EDX,DWORD PTR SS:[EBP+18] ; |
00435359 |. C705 543B4E00>MOV DWORD PTR DS:[4E3B54],1 ; |
00435363 |. 52 PUSH EDX ; |Arg3
00435364 |. 8B4D 14 MOV ECX,DWORD PTR SS:[EBP+14] ; |
00435367 |. 51 PUSH ECX ; |Arg2
00435368 |. 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8] ; |
0043536B |. 50 PUSH EAX ; 线程ID
0043536C |. E8 BFC0FFFF CALL OLLYDBG.00431430 ; \OLLYDBG.00431430 ;跟进
00431498 |. E8 3FD3FFFF CALL <OLLYDBG.SetContext> ; \OLLYDBG.0042E7DC ; 该函数遍历被调试进程的各个线程 然后设置其CONTEXT
0042E8CA |. F680 F4020000>|TEST BYTE PTR DS:[EAX+2F4],1
0042E8D1 |. 74 11 |JE SHORT OLLYDBG.0042E8E4
0042E8D3 |. 818B C0000000>|OR DWORD PTR DS:[EBX+C0],100
eax是一个t_thread结构
typedef struct t_thread { // Information about active threads
ulong threadid; // Thread identifier
ulong dummy; // Always 1
ulong type; // Service information, TY_xxx
HANDLE thread; // Thread handle
ulong datablock; // Per-thread data block
ulong entry; // Thread entry point
ulong stacktop; // Working variable of Listmemory()
ulong stackbottom; // Working variable of Listmemory()
CONTEXT context; // Actual context of the thread
t_reg reg; // Actual contents of registers
int regvalid; // Whether reg is valid
t_reg oldreg; // Previous contents of registers
int oldregvalid; // Whether oldreg is valid
int suspendcount; // Suspension count (may be negative)
long usertime; // Time in user mode, 1/10th ms, or -1
long systime; // Time in system mode, 1/10th ms, or -1
ulong reserved[16]; // Reserved for future compatibility
} t_thread;
typedef struct t_reg { // Excerpt from context
int modified; // Some regs modified, update context
int modifiedbyuser; // Among modified, some modified by user
int singlestep; // Type of single step, SS_xxx
ulong r[8]; // EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI
ulong ip; // Instruction pointer (EIP)
ulong flags; // Flags
int top; // Index of top-of-stack
long double f[8]; // Float registers, f[top] - top of stack
char tag[8]; // Float tags (0x3 - empty register)
ulong fst; // FPU status word
ulong fcw; // FPU control word
ulong s[6]; // Segment registers ES,CS,SS,DS,FS,GS
ulong base[6]; // Segment bases
ulong limit[6]; // Segment limits
char big[6]; // Default size (0-16, 1-32 bit)
ulong dr6; // Debug register DR6
ulong threadid; // ID of thread that owns registers
ulong lasterror; // Last thread error or 0xFFFFFFFF
int ssevalid; // Whether SSE registers valid
int ssemodified; // Whether SSE registers modified
char ssereg[8][16]; // SSE registers
ulong mxcsr; // SSE control and status register
int selected; // Reports selected register to plugin
ulong drlin[4]; // Debug registers DR0..DR3
ulong dr7; // Debug register DR7
} t_reg;
最后也就是开头处的
0042EB1A |> \53 |PUSH EBX ; /pContext
0042EB1B |. 8B45 EC |MOV EAX,DWORD PTR SS:[EBP-14] ; |
0042EB1E |. 8B50 0C |MOV EDX,DWORD PTR DS:[EAX+C] ; |
0042EB21 |. 52 |PUSH EDX ; |hThread
0042EB22 |. E8 83060800 |CALL <JMP.&KERNEL32.SetThreadContext> ; \SetThreadContext
偏移2F4处就是singlestep字段 将此线程CONTEXT.EFlags |= 100h 了
走到系统处理异常领空之后 虽然这时的TF被清位了
但此时的CONTEXT结构被作为异常处理回调函数的参数了
当回调函数返回值为ExceptionContinueExection 也即0的时候
系统就会把线程环境设置为CONTEXT里的结构并继续执行
导致异常处理函数返回值 又增加了一个中断异常 这个也是该anti所使用的机制
由于此anti是利用了调试器的单步机制
所以避免方法就是:
F9过去 也就只是避免单步执行异常语句
菜鸟玩逆向 难免嗦了点
感谢钱老师的启发
by科锐三期学员
- 标 题:Anti OllyDbg
- 作 者:瘋子
- 时 间:2009-07-12 15:46:37
- 链 接:http://bbs.pediy.com/showthread.php?t=93316