安全中国首页 > 文章中心 > 反跟踪技术
 
安全中国网友投稿专用上传FTP空间:
Ftp服务器:download.anqn.com
Ftp端口:21
用户名:anqn
密 码:anqn.com
 

一个奇怪的带壳crackme的简单分析(Anti、Validate)

更新时间:2007-9-27 22:16:21
责任编辑:池天
热 点:
【文章标题】: 一个奇怪的带壳crackme的简单分析
【文章作者】: hawking
【作者邮箱】: rich_hawking@hotmail.com
【软件名称】: Bustme4
【软件大小】: 21.5k
【下载地址】: bm4.zip
【加壳方式】: 不详
【保护方式】: 未知
【使用工具】: PEiD OLLYICE
【操作平台】: win2k
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
  首先声明,本人菜菜,分析水平有限,本文只是自己破解过程的一个记录,供和我一样的菜鸟参考,也备自己日后遇到类似问题时查找。侠之大者可以直接漂过。如果肯指点一二,感激不尽。
  
  近来坛子里加壳与脱壳版里一个奇怪的带壳crackme讨论的人很多,不少大牛都信手给出了key,成了创意比拼。我等小菜,载入OD里还没开始跑就GameOver了。实在是心有不甘。
  
  发扬一下cracker的风格,自己动手搞定它。感谢skylly,我一开始茫无头绪时,是你的提示给了我前进的方向。

  (一)Anti                              (Validate部分参见11楼) 

  首先用PDiD查一下壳,显示Nothing found * 点击EP Section后面的按钮可以看出有6个段,段名都是$Content$nbsp;看来好像是加壳了。
  
  OD载入

  00409508 >- E9 00500000     jmp     0040E50D
  0040950D    0000            add     byte ptr [eax], al
  0040950F    0000            add     byte ptr [eax], al

  F8,来到0040E50D,OD提示不知如何单步,晕,才第一条指令啊。

  1 tlscallback+CC保护入口  
  
  我们再次在PEiD中打开,点一下Subsystem后面的按钮,看看PE Details
  可以看到
  
  Directory Information
               RVA           SIZE
  TLSTable:  00009000       00000024
  
  点一下后面的按钮打开TLS Viewer可以看到
  
  CallbackTableVA:   00409060
  
  在数据窗口里可以看到00409060处的数值是00409040。
  
  具体TLS是什么我也不清楚,如果有大侠路过,请指点一下。只是大概了解这里的Callback Function会在系统加载程序的时候执行(程序第一条代码执行之前),我们可以看
  到这里的Callback Function地址是00409040,我们在这里下断。然后将OD调试选项中事件选项卡设置第一次暂停于系统断点(默认情况下会暂停于WinMain)。
  Ctrl+F2重新开始。
  
  77F813B2    C3              retn
  77F813B3    33C9            xor     ecxecx
  77F813B5    E9 A5BE0000     jmp     77F8D25F
  
  F9
  
  00409040    803D 08954000 C>cmp     byte ptr [<模块入口点>], 0CC     ;看看模块入口点是否下断 是则很可能被调试
  00409047    75 0A           jnz     short 00409053                   ;如果没下断则直接去模块入口点
  00409049    C705 09954000 0>mov     dword ptr [409509], 5000         ;如果下断了则修改模块入口点代码,使jmp跳向一个没有内容的地址,引发异常
  00409053    C2 0C00         retn    0C
  
  原来OD会自动在模块入口点下Int3断点,如果我们Alt+B打开断点窗口就会看到
  
  地址     模块    激活    反汇编
  00409508 Bustme4 仅一次  jmp 00409180
  
  我们修改jnz 为 jmp ,始终让它跳就可以避免这里的检测了。
  
  -----------------------------------------------------------------------------------------
  CC 反调试
  
  注意后面通过的00750000段代码也存在Int3反调试,有些地址下F2断点将会导致程序异常。
  
  0075001E    8B0424          mov     eaxdword ptr [esp]
  00750021    8038 CC         cmp     byte ptr [eax], 0CC
  00750024  - 0F84 A5C5FFFF   je      0074C5CF
  0075002A    C3              retn
  
  另外00401000代码段也存在Int3反调试。
  
  00401081    A1 F2324000     mov     eaxdword ptr [<&USER32.GetDlgItemTextA>]
  00401086    8038 CC         cmp     byte ptr [eax], 0CC
  00401089  - 0F84 2AE1FFFF   je      003FF1B9
  0040108F    A1 F6324000     mov     eaxdword ptr [<&USER32.MessageBoxA>]
  00401094    8038 CC         cmp     byte ptr [eax], 0CC
  00401097  - 0F84 2AE1FFFF   je      003FF1C7
  
  如果想在上述地址下断点的话,就要修改一下je处的代码,直接nop掉就可以了。
  
  -----------------------------------------------------------------------------------------
  
  由于程序老是出现意外,我们不得不频繁重新开始,这种重复性的工作太没有意思了。怎么办,交给脚本来处理好了,这里我们用OllyScript写脚本通过检测。
  脚本其实很简单的,脚本命令总共也就那么几条,看一下OllyScript附带的说明文档就搞定了,也可以参考hnhuqiong大侠的ODbgScript 入门系列。
  
  Script:
  asm 00409047,"jmp 00409053" 
  esto   
  
  脚本很简单,由于水平不高,地址都是硬编码的,有些地址可能会因为机器的不同而存在差异,请自行修改。
  
  2 进程入口参数校验
  
  来到模块入口点
  00409508 >^\E9 73FCFFFF     jmp     00409180
  
  00409180    803D E1944000 0>cmp     byte ptr [4094E1], 1            ;比较4094E1处标记,是1的话则Call ExitProcess Game Over!
  00409187    0F84 73030000   je      00409500
  0040918D    C605 E1944000 0>mov     byte ptr [4094E1], 1            ;设4094E1处标记为1,如果程序被Dump的话,则执行Dump后的程序会不正常
  00409194    FF15 1E314000   call    dword ptr [<&KERNEL32.GetCommand>; KERNEL32.GetCommandLineA 取命令行参数
  0040919A    83F8 00         cmp     eax, 0
  0040919D    74 16           je      short 004091B5
  0040919F    50              push    eax
  004091A0    68 52944000     push    00409452                         ; ASCII "Teeth_In_The_Grass"
  004091A5    FF15 C6304000   call    dword ptr [<&KERNEL32.lstrcmp>]  ; KERNEL32.lstrcmpA  比较参数与上面的字符串
  004091AB    83F8 00         cmp     eax, 0
  004091AE    75 05           jnz     short 004091B5                   ;如果命令行参数不等于上面的字符串则跳
  004091B0  - E9 F7FAFFFF     jmp     00408CAC
  004091B5    68 58020000     push    258
  004091BA    68 F6914000     push    004091F6
  004091BF    6A 00           push    0
  004091C1    FF15 1A314000   call    dword ptr [<&KERNEL32.GetModuleF>; KERNEL32.GetModuleFileNameA
  004091C7    68 80944000     push    00409480
  004091CC    68 A0944000     push    004094A0
  004091D1    6A 00           push    0
  004091D3    6A 00           push    0
  004091D5    6A 00           push    0
  004091D7    6A 00           push    0
  004091D9    6A 00           push    0
  004091DB    6A 00           push    0
  004091DD    68 52944000     push    00409452                         ; ASCII "Teeth_In_The_Grass"
  004091E2    68 F6914000     push    004091F6
  004091E7    FF15 16314000   call    dword ptr [<&KERNEL32.CreateProc>; KERNEL32.CreateProcessA   将CommandLine设为上面的参数然后创建进程
  004091ED    6A 00           push    0
  004091EF    FF15 C2304000   call    dword ptr [<&KERNEL32.ExitProces>; KERNEL32.ExitProcess      Game Over!
  004091F5    C3              retn
  
  这里我们一路F8,可以看到由于程序没有带命令行参数启动,导致上面的字符串比较没能通过,程序将"Teeth_In_The_Grass"作为CommandLine的值,重新创建了一个新的进程,然后就退出了。
  要通过这里的检测,我们必须将"Teeth_In_The_Grass"作为参数创建进程并调试。
  
  Script:
  asm 0040918D,"mov byte ptr[4094E1],0"
  go 40919a
  mov eax,00409452
  go 4091B0
  
  SetUnhandledExceptionFilter
  
  然后就是一路jmp来jmp去,来到
  004033C7    FF20            jmp     dword ptr [eax]                   ;这里jmp到SetUnHandledExceptionFilter
  
  看看堆栈
  
  0006FFBC   FFFFFFFF  /CALL 到 SetUnhandledExceptionFilter
  0006FFC0   00405325  \pTopLevelFilter = Bustme4.00405325
  0006FFC4   77E71AF6  返回到 KERNEL32.77E71AF6
  
  观察堆栈可以知道,如果程序出现了UnhandledException,正常运行时将会由00405325处的代码进行处理。由于我们正在调试程序,这时如果F9,OD会提示程序不知如何继续。
  程序从004091B0处开始就一直跳来跳去,也没干什么正事。要想过这里的检测,我们改一下004091B0处的代码
  
  Script:
  asm 004091B0,"jmp 00405325"
  
  4 CreateThead 线程保护 
  ........
  
  004053AF    FF15 DE304000   call    dword ptr [<&KERNEL32.VirtualAll>; KERNEL32.VirtualAlloc
  ........
  
  00405441    F3:A4           rep     movs byte ptr es:[edi], byte ptr [esi]
  ........
  
  0040589B    FF15 F2304000   call    dword ptr [<&KERNEL32.GetCurrentProcessId>]    ; KERNEL32.GetCurrentProcessId
  ........
  
  0040590D    FF15 D2304000   call    dword ptr [<&KERNEL32.OpenProcess>]            ; KERNEL32.OpenProcess
  ........
  
  00405953    6A 02           push    2                                              ;传Options参数
  ........
  
  00405A03    FF15 EE304000   call    dword ptr [<&KERNEL32.DuplicateHandle>]        ; KERNEL32.DuplicateHandle
  
  堆栈:
  0006FF74   00000040  |hSourceProcess = 00000040 (window)
  0006FF78   FFFFFFFE  |hSource = FFFFFFFE
  0006FF7C   00000040  |hTargetProcess = 00000040 (window)
  0006FF80   004052F5  |phTarget = Bustme4.004052F5
  0006FF84   00000000  |Access = 0
  0006FF88   00000001  |Inheritable = TRUE
  0006FF8C   00000002  \Options = DUPLICATE_SAME_ACCESS
  
  这儿程序复制了一个句柄,源句柄为FFFFFFFE(-2),这里按F8将会出现引用的内存不能为"written"的错误。
  SourceProcess应该是不存在FFFFFFFE的句柄的,这里不知道有没有什么好的方法处理一下。我们采用最笨的方法,直接跳过它,注意堆栈平衡就可以了。
  
  Script:
  asm 00405953,"jmp 00405A09"
  
  ........
  
  00405A66    FF15 D6304000   call    dword ptr [<&KERNEL32.CloseHandle>]            ; KERNEL32.CloseHandle
  ........
  
  00405AF1    FF15 02314000   call    dword ptr [<&KERNEL32.CreateThread>]           ; KERNEL32.CreateThread
  
  堆栈:
  0006FF80   00000000  |pSecurity = NULL
  0006FF84   00000000  |StackSize = 0
  0006FF88   00406727  |ThreadFunction = Bustme4.00406727
  0006FF8C   00000000  |pThreadParm = NULL
  0006FF90   00000000  |CreationFlags = 0
  0006FF94   0040501C  \pThreadId = Bustme4.0040501C
  
  这里程序创建了一个新线程,线程函数入口地址00406727,这个线程除了SuspendThread VirtualQuery ResumeThread外没做什么特别的事情。其实并没有像有些大侠说的那样是什么线程保护。
  
  ........
  
  00405B9D    FF15 0A314000   call    dword ptr [<&KERNEL32.GetExitCodeThread>]      ; KERNEL32.GetExitCodeThread
  ........
  
  00405BB8    813D F5524000 0>cmp     dword ptr [4052F5], 103                        ;比较线程退出代码
  ........
  
  00405BD9  ^\0F84 5EFFFFFF   je      00405B3D
  00405BDF    66:87CB         xchg    bxcx
  
  直接在00405BDF处下断F9就可以通过以上代码了。
  
  GetTickCount 时间校验
  
  ........
  
  00405C46    FF15 12314000   call    dword ptr [<&KERNEL32.GetTickCount>]           ; KERNEL32.GetTickCount
  ........
  
  00405C63    89C6            mov     esieax
  ........
  
  00405D6E    FF15 12314000   call    dword ptr [<&KERNEL32.GetTickCount>]           ; KERNEL32.GetTickCount
  ........
  
  00405D8B    29F0            sub     eaxesi
  ........
  
  00405DB6    3D D0070000     cmp     eax, 7D0
  ........
  
  00405DCD  ^\0F8F 8BF7FFFF   jg      0040555E                                      ;如果单步调试的话需要将这一句nop掉,如果直接F4过来没有在两个GetTickCount中间的代码停顿过的话可不作处理
  
  RaiseException 
  ........
  
  00405E55    C705 F9524000 3>mov     dword ptr [4052F9], 30                          ;后面RaiseException会循环0x30次
  ........
  
  00405FB4    FF15 EA304000   call    dword ptr [<&KERNEL32.RaiseException>]         ; KERNEL32.RaiseException
  
  堆栈:
  0006FF40   40010007  |ExceptionCode = 40010007
  0006FF44   00000000  |ExceptionFlags = EXCEPTION_CONTINUABLE
  0006FF48   00000002  |nArguments = 2
  0006FF4C   00401000  \pArguments = Bustme4.00401000
  0006FF50   0006FFE0  指向下一个 SEH 记录的指针
  0006FF54   004060BC  SE处理程序
  
  很明显程序在这里人为地制造了一处错误。如果要想程序正常运行,必须要跳到SE处理程序入口处004060BC。
  我们在004060BC处下断,不断地Shift+F9,可最终程序也没有停在我们想断下来的004060BC处,而是弹出一个窗口告诉我们某某地址内存不能为"read",然后就Game Over了。
  这里不知道有没有什么好的办法处理通过,我用的是将ExceptionCode改为C0123333,然后通过。
  
  Script:
  asm 00405E55,"mov dword ptr[4052F9],1"
  asm 00405F9B,"push 0C0123333"
  
  ........
  
  00406227    FF15 12314000   call    dword ptr [<&KERNEL32.GetTickCount>]           ; KERNEL32.GetTickCount
  ........
  
  00406286    C705 F9524000 3>mov     dword ptr [4052F9], 30
  ........
  
  0040640B    FF15 EA304000   call    dword ptr [<&KERNEL32.RaiseException>]         ; KERNEL32.RaiseException
  
  堆栈:
  0006FF40   40010007  |ExceptionCode = 40010007
  0006FF44   00000000  |ExceptionFlags = EXCEPTION_CONTINUABLE
  0006FF48   00000002  |nArguments = 2
  0006FF4C   00401000  \pArguments = Bustme4.00401000
  0006FF50   0006FFE0  指向下一个 SEH 记录的指针
  0006FF54   00406508  SE处理程序
  
  按上面相同的方法处理通过
  
  Script:
  asm 00406286,"mov dword ptr [4052F9],1"
  asm 004063E7,"push 0C0123333"
  
  
  0040667D    FF15 12314000   call    dword ptr [<&KERNEL32.GetTickCount>]           ; KERNEL32.GetTickCount
  
  004066B2    3D 88130000     cmp     eax, 1388
  
  004066D5   /0F8F 84050000   jg      00406C5F                                        ;同上处理
  
  GetWindowThreadProcessId 检测调试器
  .......
  
  00750000    58              pop     eax
  00750001    3D 5FAF2F9F     cmp     eax, 9F2FAF5F
  00750006  - 75 FE           jnz     short 00750006
  00750008    E8 11000000     call    0075001E
  ........
  
  007513D2    FF10            call    dword ptr [eax]                                ; USER32.GetForegroundWindow
  ........
  
  0075145D    FF10            call    dword ptr [eax]                                ; USER32.GetWindowThreadProcessId
  
  堆栈:
  0006FFB4   0075145F  /CALL 到 GetWindowThreadProcessId 来自 0075145D
  0006FFB8   003C0690  |hWnd = 003C0690 ('OllyICE - Bustme4.exe',class='pediy06',wndproc=03503168)
  0006FFBC   004052ED  \pProcessID = Bustme4.004052ED
  
  这里取得创建当前窗口的进程ID,并保存在004052ED处。由于我们正在用OD调试程序,所以这里取得是OD的进程ID。
  
  0075151D    FF10            call    dword ptr [eax]                                ; KERNEL32.OpenProcess
  
  0075162F    FF10            call    dword ptr [eax]                                ; KERNEL32.ReadProcessMemory
  
  007516B5    FF10            call    dword ptr [eax]                                ; KERNEL32.CloseHandle
  
  00751728    FF15 C6304000   call    dword ptr [<&KERNEL32.lstrcmp>]                ; KERNEL32.lstrcmpA
  
  00751760    83F8 00         cmp     eax, 0
  
  00751778   /75 2F           jnz     short 007517A9                                  ;这里必须跳,否则紧随其后的代码会将004052EC的值减1 
  
  这里程序设置了一个暗桩,如果检测到被调试,则最终004052EC值为1,否则004052EC值为2。检测后并不立即异常,而是在后面有对004052EC寄存的值检测的代码,如果检测到是1则会使程序运行不正常而退出。
  要跳过这里程序的检测,可以按照通用的方法在调用GetWindowThreadProcessID后立即将004052ED处的值改为Explorer.exe的进程ID,
  也可以修改上面的jnz代码为jmp。
  
  Script:
  asm 00751778,"jmp 007517A9"
  
  8 OutputDebugStringA 检测调试器
  
  00751851    FF15 0E314000   call    dword ptr [<&KERNEL32.OutputDebugStringA>]     ; KERNEL32.OutputDebugStringA
  
  这里还有一处检测,由于我用的是OllyICE,己经针对OutputDebugStringA作了修改了,此处顺利通过。
  
  9 Anti_Dump 
  
  00751287    FF15 E2304000   call    dword ptr [<&KERNEL32.VirtualPro>; KERNEL32.VirtualProtect
  
  堆栈:
  0006FF6C   00405000  |Address = Bustme4.00405000
  0006FF70   00003A53  |Size = 3A53 (14931.)
  0006FF74   00000001  |NewProtect = PAGE_NOACCESS
  0006FF78   0006FF7C  \pOldProtect = 0006FF7C
  
  这里将00405000段代码保护方式设置成了PAGE_NOACCESS,这样在您DUMP程序的时候就会出错。如果您想Dump程序,就要想办法跳过这段代码或者将NewProtect的值设置成PAGE_READWRITE之类的。
  
  
  之后程序就没有什么Anti了,程序又VirtualAlloc若干段内存,并对00401000段进行解码,解码完之后会来到00401014,然后进入0076000调用DialogBoxParamA载入程序界面,并设定DlgProc地址在00401040。
  
  给个跑到00401014处的完整Script:
 ************************************************** 
  asm 00409047,"jmp 00409053" 
  esto                 
  asm 0040918D,"mov byte ptr[4094E1],0"
  go 40919a
  mov eax,00409452
  asm 004091B0,"jmp 00405325"
  asm 00405953,"jmp 00405A09"
  go 00405A09
  asm 00405E55,"mov dword ptr[4052F9],1"
  asm 00405F9B,"push 0C0123333"
  esto
  asm 00406286,"mov dword ptr [4052F9],1"
  asm 004063E7,"push 0C0123333"
  esto
  asm 00751778,"jmp 007517A9"
  go 00401014
  ret
 **************************************************  
  注意您有可能需要修改脚本中的相应地址,并且在运行脚本之前请清除所有的断点,调试选项中选择忽略所有异常。

 
相关文章
一日一文章
 
一日一软件
一日一动画