反调试-去除各种反调试

前不久破解一个软件的时候遇到了各种反调试,折腾的自己各种难受,最终爆破了之后感觉心情大快就顺手写下了这篇文章

使用工具

十六进制分析工具:winhex

查壳工具:PEID

脱壳工具:ollydump插件或者LordPE

脱壳修复工具:ImportREC

逆向工具:OllyDbg

分析过程

PE修复

打开源程序所在文件夹,发现有一个crackme,双机运行程序发现有这个提示:

应该是文件的PE结构被修改了,winhex载入分析发现:

果然是PE结构的问题,在DOS头后面的PE头的16进制应该为50 45,将上述52修改为45,保存文件之后发现仍然运行不了,证明PE结构仍然存在问题。

分析PE头后面的IMAGE_FILE_HEADER(映像文件头,NT头),对比结构:

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;                //运行平台
    WORD    NumberOfSections;       //文件的区块数目
    DWORD   TimeDateStamp;          //文件创建的日期和时间
    DWORD   PointerToSymbolTable;   //指向符号表
    DWORD   NumberOfSymbols;        //符号表中符号个数
    WORD    SizeOfOptionalHeader;       //IMAGE_OPTIONAL_HEADER32结构大小
    WORD    Characteristics;            //文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

上述地址分别相对于50450000之后偏移04h(4C01),06h(0300),08h,0Ch,10h,14h,16h对比之后发现在运行平台上为1F0h。

而我们熟知运行平台如下图:

故将其修改为14Ch,保存文件。修复PE结构之后可以正常运行了

双机运行之后如下图

输入用户名和注册码之后提示不正确,而且在过了一段时间之后程序自动退出,应该加了时间控制(此时主窗口已经退出,只有错误提示框)

脱壳

用PEID分析载入分析之后发现:

程序加了WinUpack的壳好在只是一个普通的压缩弱壳,用PEID自带的插件Krypto ANAlzer扫了一遍程序

了解到该程序并没有使用什么知名的加密算法。

因为这里加了壳,不便于静态分析,故笔者在这里并未使用IDA。用OD载入程序,OEP被壳修改,要先脱壳。因为WinUpack为弱壳,所以根据OEP定律,单步运行至OEP改变时右键数据窗口中跟随,然后下硬件访问断点,运行之后程序停在OEP,因为加了壳使OD并没有完全正常解析指令,:

开始脱壳,用ollydump记录下程序OEP,lordpe转存,脱壳之后因为IAT被破坏所以无法正常运行软件。

使用ImportREC修复脱壳后程序,将 OEP改为上述14EC,获取输入表,发现全部有效,然后修复上面脱壳的转存文件:

能正确运行。OD再次载入,停在正确的OEP,正式开始破解:(这里因为重建了输入表,所以程序的大小会比之前的源程序要大一些,属于正常情况)

初步分析

看到程序的入口点应该想到程序使用了较为高级的花指令,伪装了一些API调用,然后通过call eax致使无法查到这些API的调用。

根据最开始的提示Error(标题栏)和“注册码错误”,使用字符串查找如下:

Ctrl+G(转到上述地址),到这些地址处发现:

去除单步异常

退回到OEP一步步分析:运行到这一步时程序会自动终止:

因为前面有一个捕获异常函数SetUnhandledExceptionFilter,在程序被调试时,ptr ds[eax]此处地址为0是不可读写的,而这里向一块不可写的内存中写入0x1,自然触发异常,终止程序。

Nop掉这个函数和异常触发的mov。

去除父进程校验

重新载入程序至:

这个是窗口主函数了,这个API的第4个参数为00401340,就是窗口主程序所在地址了,转到在00401340下int 3断点,运行至后得:

先不着急单步,浏览一遍代码之后发现程序在此段中多次调用了0040101E处的函数,enter进去之后发现第二层反调试:

00401027  |.  68 28010000   push 0x128                               ; /Length = 128 (296.)
0040102C  |.  8D85 D8FEFFFF lea eax,[local.74]                       ; |
00401032  |.  50            push eax                                 ; |Destination
00401033  |.  E8 FA050000   call <jmp.&kernel32.RtlZeroMemory>       ; \RtlZeroMemory
00401038  |.  C785 D8FEFFFF>mov [local.74],0x128
00401042  |.  6A 00         push 0x0                                 ; /ProcessID = 0
00401044  |.  6A 02         push 0x2                                 ; |Flags = TH32CS_SNAPPROCESS
00401046  |.  E8 AB050000   call <jmp.&kernel32.CreateToolhelp32Snap>; \CreateToolhelp32Snapshot
0040104B  |.  8985 D4FEFFFF mov [local.75],eax
00401051  |.  8D85 D8FEFFFF lea eax,[local.74]
00401057  |.  50            push eax                                 ; /lppe
00401058  |.  FFB5 D4FEFFFF push [local.75]                          ; |hSnapshot
0040105E  |.  E8 C3050000   call <jmp.&kernel32.Process32First>      ; \Process32First
00401063  |.  EB 1F         jmp Xdump1.00401084
00401065  |>  E8 98050000   /call <jmp.&kernel32.GetCurrentProcessId>; [GetCurrentProcessId
0040106A  |.  3B85 E0FEFFFF |cmp eax,[local.72]
00401070  |.  74 26         |je Xdump1.00401098
00401072  |.  8D85 D8FEFFFF |lea eax,[local.74]
00401078  |.  50            |push eax                                ; /lppe
00401079  |.  FFB5 D4FEFFFF |push [local.75]                         ; |hSnapshot
0040107F  |.  E8 A8050000   |call <jmp.&kernel32.Process32Next>      ; \Process32Next
00401084  |>  0BC0           or eax,eax
00401086  |.^ 75 DD         \jnz Xdump1.00401065

上面主要是通过调用系统快照函数(红色字体标注部分),然后遍历这个系统当前的进程ID,直到找到当前dump的进程ID后跳走,数据窗口中跟踪[local.72]地址,发现确实在遍历进程名和ID。证实了上面我的想法。

而在以下代码中发现了第二层反调试的真面目:

将当前进程的父进程与系统下Explorer.exe进行对比。

继续单步运行:第二次判断父进程是否为CMD.exe

这里因为一般运行在windows系统下进程调度时,大部分进程都是有父进程Explorer.exe或者cmd.exe创建的,而当程序处于调试状态时父进程肯定是调试进程,所以这一层反调试能针对很多调试软件起到很好的反调试作用。

在程序中多次调用了这一层反调试,所以单纯的nop需要靠IDC脚本实现多次,这里我们让

00401114  |. /74 68         je Xdump1.0040117E

改为jmp 00401117E,让它恒跳走,让程序误以为父进程校验正确。

保存文件之后想到刚开始注册时会有成功或者失败提示,那么是调用了MessageBox这个函数。根据这个信息,我们想到了查找函数。于是查找api调用如下

发现这里并没有messagebox,这里应该是到了宏,调用api之前将api名字做了隐藏,之后直接call eax,程序刚开始时代码说明了这一点:

去除时间差校验

在调用的API中发现了GetDlgItem,这是一个破绽。直接下int 3断点,运行之后发现自己还没来得及输入用户名和注册码程序自动退出,这让我想到刚刚主窗口中的两个可疑的函数SetTimer

0040138E  |.  6A 00         push 0x0                                 ; /Timerproc = NULL
00401390  |.  68 E8030000   push 0x3E8                               ; |Timeout = 1000. ms
00401395  |.  6A 06         push 0x6                                 ; |TimerID = 6
00401397  |.  FF75 08       push [arg.1]                             ; |hWnd
0040139A  |.  E8 45020000   call <jmp.&user32.SetTimer>              ; \SetTimer
和
004013C4  |.  6A 00         push 0x0                                 ; /Timerproc = NULL
004013C6  |.  68 10270000   push 0x2710                              ; |Timeout = 10000. ms
004013CB  |.  6A 05         push 0x5                                 ; |TimerID = 5
004013CD  |.  FF75 08       push [arg.1]                             ; |hWnd
004013D0  |.  E8 0F020000   call <jmp.&user32.SetTimer>              ; \SetTimer

两个都是SetTimer,这个就能解释之前程序会自动退出的原因了,SetTmier函数即为每隔固定的一个时间向所在窗口发送消息。上面这段应该发送的是WM_CLOSE而销毁了窗口。,分析代码知道一个是1000ms一个是10000ms,而我们在反调试分析代码过程中所需要的时间远远大于这些时间,所以自然会退出。这也是利用调试时间差起到反调试的思路。

将timeout参数值改成FFFF,时间应该我们足够逆向分析用了。保存文件之后载入文件,进一步分析

逆向分析

在getdlgitem上下int 3断点,成功断下:

0040143B  |.  6A 03         push 0x3                                 ; /ControlID = 3
0040143D  |.  FF75 08       push [arg.1]                             ; |hWnd
00401440  |.  E8 8D010000   call <jmp.&user32.GetDlgItem>            ; \GetDlgItem
00401445  |.  A3 60304000   mov dword ptr ds:[0x403060],eax
0040144A  |.  6A 04         push 0x4                                 ; /ControlID = 4
0040144C  |.  FF75 08       push [arg.1]                             ; |hWnd
0040144F  |.  E8 7E010000   call <jmp.&user32.GetDlgItem>            ; \GetDlgItem
00401454  |.  A3 64304000   mov dword ptr ds:[0x403064],eax
00401459  |.  68 74304000   push dump3.00403074                      ; /lParam = 403074
0040145E  |.  6A 32         push 0x32                                ; |wParam = 32
00401460  |.  6A 0D         push 0xD                                 ; |Message = WM_GETTEXT
00401462  |.  FF35 60304000 push dword ptr ds:[0x403060]             ; |hWnd = C0B2C
00401468  |.  E8 71010000   call <jmp.&user32.SendMessageA>          ; \SendMessageA
0040146D  |.  68 F4304000   push dump3.004030F4                      ; /lParam = 4030F4
00401472  |.  6A 32         push 0x32                                ; |wParam = 32
00401474  |.  6A 0D         push 0xD                                 ; |Message = WM_GETTEXT
00401476  |.  FF35 64304000 push dword ptr ds:[0x403064]             ; |hWnd = A07EC
0040147C  |.  E8 5D010000   call <jmp.&user32.SendMessageA>          ; \SendMessageA

程序使用SendMessageA,将字符串的内容送至00403074和004030F4两处,避免使用GetDlgItemTextA函数直接能获取明文。

单步跟踪

发现算法在call eax之后来到如下代码。跟进之后算法分析见代码中注释:

004011D9   > \A1 56304000   mov eax,dword ptr ds:[0x403056]          ;  核心算法,此地址处存放用户名的长度
004011DE   .  83F8 06       cmp eax,0x6                              ;  用户名长度>=6
004011E1   .  0F8C 97000000 jl dump3.0040127E
004011E7   .  50            push eax
004011E8   .  59            pop ecx
004011E9   .  8D35 00304000 lea esi,dword ptr ds:[0x403000]          ;  预定字符串S1
004011EF   .  8D3D 74304000 lea edi,dword ptr ds:[0x403074]          ;  用户名
004011F5   >  33C0          xor eax,eax
004011F7   .  33DB          xor ebx,ebx
004011F9   .  8B07          mov eax,dword ptr ds:[edi]               ;  将4位用户名给eax
004011FB   .  8B1E          mov ebx,dword ptr ds:[esi]               ;  将4位s1给ebx
004011FD   .  25 FF000000   and eax,0xFF                             ;  去掉高位保留第一位,即取一位用户名
00401202   .  81E3 FF000000 and ebx,0xFF                             ;  去掉高位保留第一位,取S一位
00401208   .  33C3          xor eax,ebx                              ;  二者异或
0040120A   .  0305 4E304000 add eax,dword ptr ds:[0x40304E]          ;  然后累加
00401210   .  A3 4E304000   mov dword ptr ds:[0x40304E],eax
00401215   .  46            inc esi
00401216   .  47            inc edi                                  ;  循环向后读取
00401217   .^ E2 DC         loopd Xdump3.004011F5
00401219   .  33C9          xor ecx,ecx
0040121B   .  8B0D 5A304000 mov ecx,dword ptr ds:[0x40305A]          ;  注册码长度
00401221   .  8D35 25304000 lea esi,dword ptr ds:[0x403025]          ;  预定字符串S2
00401227   .  8D3D F4304000 lea edi,dword ptr ds:[0x4030F4]          ;  注册码
0040122D   >  33C0          xor eax,eax
0040122F   .  33DB          xor ebx,ebx
00401231   .  8B07          mov eax,dword ptr ds:[edi]               ;  算法同上
00401233   .  8B1E          mov ebx,dword ptr ds:[esi]
00401235   .  25 FF000000   and eax,0xFF
0040123A   .  81E3 FF000000 and ebx,0xFF
00401240   .  33C3          xor eax,ebx
00401242   .  0305 52304000 add eax,dword ptr ds:[0x403052]
00401248   .  A3 52304000   mov dword ptr ds:[0x403052],eax
0040124D   .  46            inc esi
0040124E   .  47            inc edi
0040124F   .^ E2 DC         loopd Xdump3.0040122D                    ;  循环读取
00401251   .  A1 52304000   mov eax,dword ptr ds:[0x403052]
00401256   .  8B1D 4A304000 mov ebx,dword ptr ds:[0x40304A]
0040125C   .  85DB          test ebx,ebx
0040125E   .  75 3A         jnz Xdump3.0040129A                      ;  不等跳至失败
00401260   .  8505 4E304000 test dword ptr ds:[0x40304E],eax         ;  比较用户名与S1异或和是否等于注册码与S2的异或和
00401266   .  75 32         jnz Xdump3.0040129A                      ;  不等则跳向失败
00401268   .  6A 00         push 0x0
0040126A   .  68 98114000   push dump3.00401198                      ;  ASCII "Success"

在jnz时将标志位Z改为1之后看到程序注册成功了,

然后直接将jnz修改为nop

保存之后按照理论上来讲,应该爆破成功,此时无论输入任何用户名和注册码都应该会提示成功,但是当我们再次运行程序的时候发现

去除SMC

我们在到反汇编窗口发现代码刚刚爆破的jnz被还原了

看来是有代码SMC防爆自校验,防止爆破技术,回溯之前代码,发现在程序载入时又一个这样的call

跟进之后发现:

004012F3  /$  B8 66124000   mov eax,dump4.00401266
004012F8  |.  A3 90384000   mov dword ptr ds:[0x403890],eax
004012FD  |.  8B18          mov ebx,dword ptr ds:[eax]
004012FF  |.  66:81FB 753A  cmp bx,0x3A75
00401304  |.  74 41         je Xdump4.00401347
00401306  |.  68 94384000   push dump4.00403894                      ; /pOldProtect = dump4.00403894
0040130B  |.  6A 40         push 0x40                                ; |NewProtect = PAGE_EXECUTE_READWRITE
0040130D  |.  6A 10         push 0x10                                ; |Size = 10 (16.)
0040130F  |.  FF35 90384000 push dword ptr ds:[0x403890]             ; |Address = NULL
00401315  |.  E8 2C030000   call <jmp.&kernel32.VirtualProtect>      ; \VirtualProtect
0040131A  |.  A1 90384000   mov eax,dword ptr ds:[0x403890]
0040131F  |.  BB 753A0000   mov ebx,0x3A75
00401324  |.  66:8918       mov word ptr ds:[eax],bx
00401327  |.  B8 6E124000   mov eax,dump4.0040126E
0040132C  |.  A3 90384000   mov dword ptr ds:[0x403890],eax
00401331  |.  8B18          mov ebx,dword ptr ds:[eax]
00401333  |.  66:81FB 7532  cmp bx,0x3275
00401338  |.  74 0D         je Xdump4.00401347
0040133A  |.  A1 90384000   mov eax,dword ptr ds:[0x403890]
0040133F  |.  BB 75320000   mov ebx,0x3275
00401344  |.  66:8918       mov word ptr ds:[eax],bx
00401347  \>  C3            retn

原来在程序载入的时候会对上面两个关键的jnz进行检验,因为jnz机器码为7532如果发现被修改了就再次修改回去,这样就无法简单的nop成功了。

于是我们将这个函数也nop掉保存文件,再次注册时成功了

总算分析的差不多了,贴上一张图

时间: 2024-08-27 02:58:31

反调试-去除各种反调试的相关文章

SEH反调试的实现与调试

SEH用于反调试或者用于注册码的隐藏时.在没有异常时永远都是错误的注册码,只有当触发异常时,程序才走到注册成功的地方-- 代码如下: void CSehDlg::RegSuc() { HWND hWnd = ::GetDlgItem(NULL, IDC_STC_TIP); ::SetWindowText(hWnd, "Success!!"); } void CSehDlg::RegFail() { HWND hDlgWnd = AfxGetApp()->GetMainWnd()-

教你如何动态调试 iOS App(反编译App)

教你如何动态调试 iOS App(反编译App) 开篇 通过本文你能了解 iOS 逆向的基本知识,对 iOS App 的安全有一定了解.然后能举一反三,在自家 App 找到危险漏洞加以预防,保证用户数据安全. 在安全领域,攻与防永远存在.哪怕是 iPhone 有着强大的安全防护机制,也挡不住那些极客们一次又一次的好奇,开发了很多强大且便利的工具.本文就是在这些极客们提供的工具的基础上完成的! 准备工具 Mac 电脑和越狱 iPhone 手机 查看手机系统目录工具 iFunbox 或 iTools

爬虫(Spider),反爬虫(Anti-Spider),反反爬虫(Anti-Anti-Spider)

爬虫(Spider),反爬虫(Anti-Spider),反反爬虫(Anti-Anti-Spider),这之间的斗争恢宏壮阔... Day 1小莫想要某站上所有的电影,写了标准的爬虫(基于HttpClient库),不断地遍历某站的电影列表页面,根据 Html 分析电影名字存进自己的数据库.这个站点的运维小黎发现某个时间段请求量陡增,分析日志发现都是 IP(1.1.1.1)这个用户,并且 useragent 还是 JavaClient1.6 ,基于这两点判断非人类后直接在Nginx 服务器上封杀.

MySQL中如何插入反斜杠,反斜杠被吃掉,反斜杠转义(转)

MySQL中如何插入反斜杠,反斜杠被吃掉,反斜杠转义 问题描述:mysql中带有反斜杠的内容入库后,发现反斜杠无故失踪了(俗话说被吃掉了) 例:插入insert into tb('url') values('absc\eeee'); 结果数据库里的内容是:absceeee(反斜杠没了呢) 这么详细了相信大家都搞清楚问题了吧,下面看解决方案. 解决方案:用addslashes(),mysql_escape_string()等函数进行处理,也就是在插入数据库前,把内容处理一下  如:$cc = ad

移动端Web开发调试之Chrome远程调试(Remote Debugging)

本篇主要说一下Chrome RemoteDebugging 的方法,之前也遇到一些坑,自己总结了一些经验,分享如下. Chrome DevTools调试移动设备Brower Page Tabs/WebViews 安卓远程调试目前支持所有操作系统(Windows,Mac, Linux, and Chrome OS.)中调试,支持: ● 调试站点的页面 ● 调试安卓原生App中的WebView ● 实时将安卓设备的屏幕图像同步显示到开发机器. ● 通过端口转发(port forwarding)与虚拟

VS2003"无法启动调试 没有正确安装调试器"的解决办法

VS2003"无法启动调试 没有正确安装调试器"的解决方法 在用VS2003做项目的时候,经常调试程序,但是有时候回出现如下问题"无法启动调试,没有正确安装调试器,请运行安装程序或修复调试器".第一次碰到还以为是运气不好,就重新用vs2003安装程序重新修复了这个工具,可以使用了.但是运行了一段时间又出现了如上这种问题,郁闷了我很久.因为修复一下这个工具要花费很多时间的,于是从网上找了资料,把问题给解决了. 主要原因:大部分问题都是因为,mdm被损坏了导致的. 解决

iOS开发——装逼技术精选&amp;关于反编译和防止反编译

关于反编译和防止反编译 反编译 内购破解 iOS应用需防反编译风险之一:插件法(仅越狱).iTools工具替换文件法(常见为存档破解).八门神器修改 网络安全风险 iOS应用需防反编译风险之二:截获网络请求,破解通信协议并模拟客户端登录,伪造用户行为,对用户数据造成危害 应用程序函数PATCH破解 iOS应用需防反编译风险之三:利用FLEX 补丁软件通过派遣返回值来对应用进行patch破解 源代码安全风险 iOS应用需防反编译风险之四:通过使用ida等反汇编工具对ipa进行逆向汇编代码,导致核心

【转】Android开发之反编译与防止反编译

Android开发之反编译与防止反编译 防止反编译是每个程序员的必修课,因为当你辛辛苦的研发一个应用,被人家三下五除二给反编译了,是一件多么尴尬的事啊.那么如何防止反编译啊?这里就用Google Android自带的代码混编的方式来防止反编译.孙子兵法中讲得好:“知彼知己百战不殆”,所以在讲解防止反编译之前,先让我们了解一下如何反编译一个应用. 一.反编译Android应用 实验环境: Windows8.1企业版.dex2jar-0.0.9.9 反编译工具包: Android反编译工具包(升级版

Android apk反编译 和 防止反编译

反编译: 反编译内容来源: http://blog.csdn.net/vipzjyno1/article/details/21039349 在学习Android开发的过程你,你往往会去借鉴别人的应用是怎么开发的,那些漂亮的动画和精致的布局可能会让你爱不释手,作为一个开发者,你可能会很想知道这些效果界面是怎么去实现的,这时,你便可以对改应用的APK进行反编译查看.下面是我参考了一些文章后简单的教程详解. (注:反编译不是让各位开发者去对一个应用破解搞重装什么的,主要目的是为了促进开发者学习,借鉴好