一般来说,逆向分析并不是对那些庞大的程序进行完整的逆向,只是分析特定位置的一个或几个函数。
首先从静态分析 CRUEHEAD’s CRACKME 这个程序开始。
程序加载
打开菜单上的 VIEW-OPEN SUBVIEW-SEGMENTS,可以看到已经自动加载的程序区块。
HEADER段是没有加载的,IDA仅仅自动加载了一些可执行的区段。想要加载HEADER段需要手动加载。
在区段的名称(NAME)一列后面,是区段起始(START)和终点(END)的地址。RWX 这一列显示初始状态下这个区段是否有读 READ(R)、写 WRITE(W)以及执行 EXECUTION(X)权限。
后面的 D 和 L 两列分别对应于 DEBUGGER(调试器)和 LOADER(加载器)。
第一列(D)为空,只有程序在调试模式时才会填充已经加载的区段。L 这一列显示了加载器创建的区段。其他列的内容不是那么重要。
在这个例子里面,加载 HEADER 区段也不是那么有意义。依然介绍一下如何加载 HEADER 段。
重新加载 CRACKME.exe 程序。
选择手动加载(MANUAL LOAD),点击 OK。
按照程序提示,输入加载的基址:
在Windows操作系统中,程序的加载基址通常取决于程序的位数和其他因素。
- 32位程序:默认的加载基址通常是0x00400000。这个地址是Windows为32位应用程序预留的空间。
- 64位程序:64位应用程序的默认加载基址是0x0000000140000000。这个地址是Windows为64位应用程序预留的空间。
不过,实际加载基址可能会因为地址空间随机化(ASLR)等安全机制而有所不同。系统可能会在运行时选择不同的地址来加载程序,以提高安全性
在接下来弹出的每一个对话框上点击 OK 加载所有区段:
完成之后,可以看到:
通常这是操作不必要的,有时候考虑进来会更好。
切到反汇编视图,跳转到 0x400000 位置,也就是程序起始位置:
HEADER:00400000 ;
HEADER:00400000 ; +-------------------------------------------------------------------------+
HEADER:00400000 ; | This file was generated by The Interactive Disassembler (IDA) |
HEADER:00400000 ; | Copyright (c) 2022 Hex-Rays, <support@hex-rays.com> |
HEADER:00400000 ; +-------------------------------------------------------------------------+
HEADER:00400000 ;
HEADER:00400000 ; Input SHA256 : 0C7CDFDB6D4C8876E9C5BAE906FCF1CBF174F019EF45D518954885856501A0BE
HEADER:00400000 ; Input MD5 : 66F573036F8B99863D75743EFF84F15D
HEADER:00400000 ; Input CRC32 : 503D64C9
HEADER:00400000
HEADER:00400000 ; IMAGE_DOS_HEADER
HEADER:00400000
HEADER:00400000 .686p
HEADER:00400000 .mmx
HEADER:00400000 .model flat
HEADER:00400000
HEADER:00400000 ; ===========================================================================
HEADER:00400000
HEADER:00400000 ; Segment type: Pure data
HEADER:00400000 HEADER segment page public 'DATA' use32
HEADER:00400000 assume cs:HEADER
HEADER:00400000 ;org 400000h
HEADER:00400000 4D 5A __ImageBase dw 5A4Dh ; DATA XREF: HEADER:0040003C↓o
HEADER:00400000 ; HEADER:00400128↓o
HEADER:00400000 ; HEADER:0040012C↓o
HEADER:00400000 ; HEADER:00400130↓o
HEADER:00400000 ; HEADER:00400134↓o
HEADER:00400000 ; HEADER:00400178↓o
HEADER:00400000 ; HEADER:00400180↓o
HEADER:00400000 ; HEADER:00400188↓o
HEADER:00400000 ; HEADER:004001A0↓o
HEADER:00400000 ; HEADER:00400204↓o
HEADER:00400000 ; HEADER:0040022C↓o
HEADER:00400000 ; HEADER:00400254↓o
HEADER:00400000 ; HEADER:0040027C↓o
HEADER:00400000 ; HEADER:004002A4↓o
HEADER:00400000 ; HEADER:004002CC↓o ...
HEADER:00400000 ; PE magic number
查找关键字符串
字符串对于定位关键部分十分重要。如果能找到字符串将对分析十分有利。打开 VIEW-OPEN SUBVIEW-STRING,查看字符串。
由于加载了更多的区段,从而检测到更多的字符串。
即使不使用 IDA 直接运行 crackme.exe 程序,可以看到在 HELP 菜单中的 REGISTER 选项,要求用户 输入一个用户名和密码。这些明面上的字符串就是我们逆向的方向。
我们运行程序,顺便输入,会出现如下结果:
在 IDA 的字符串清单中查找这条字符串。
双击这条字符串。
在 0x402169 地址上保存了这条字符串。在该地址上按“D”键可以查看具体的字节。
在这个地址上再按“A”可以恢复成显示字符串。按“X”键可以更好地显示右边的两条引用。
查找关键函数
有两个不同的函数引用了这条字符串。一个是 sub_401362
,另一个是 sub_40137E
,都在我们当前地址的上方。当前地址是 0x402169。
我们在上面的图中,可以看到 Direction 是 Up,而且在上上个图中,标记红圈的位置,地址后面也出现了向上的箭头。
为什么说这里是两个不同的函数呢?
因为 IDA 显示引用都是 functions + XXXX 这种方式,如果他们属于同一个函数,那么只有 XXXX 这个值会改变,而前面的部分不变,但是这里 sub_
后的地址是不一样的,所以是两个函数。
我们先看第一个函数sub_401362
:
CODE:00401362
CODE:00401362 ; =============== S U B R O U T I N E =======================================
CODE:00401362
CODE:00401362
CODE:00401362 ; int __usercall sub_401362@<eax>(int@<ebp>)
CODE:00401362 sub_401362 proc near ; CODE XREF: WndProc+11D↑p
CODE:00401362 6A 00 push 0 ; uType
CODE:00401364 E8 AD 00 00 00 call MessageBeep
CODE:00401364
CODE:00401369 6A 30 push 30h ; '0' ; uType
CODE:0040136B 68 60 21 40 00 push offset aNoLuck ; "No luck!"
CODE:00401370 68 69 21 40 00 push offset aNoLuckThereMat ; "No luck there, mate!"
CODE:00401375 FF 75 08 push dword ptr [ebp+8] ; hWnd
CODE:00401378 E8 BD 00 00 00 call MessageBoxA
CODE:00401378
CODE:0040137D C3 retn
CODE:0040137D
CODE:0040137D sub_401362 endp
CODE:0040137D
这里调用了 messagebox API 函数来显示 NO LUCK THERE MATE
这样的消息。这个 API 函数接收 NO LUCK
字符串作为窗口标题,NO LUCK THERE MATE
字符串作为显示的文本。
再看另一个函数 sub_40137E
,发现大体逻辑是一样的。意味着在两个地方都会触发注册不成功的消息。有可能是处理不同的信息。如果要显示注册成功的消息,这两处都要绕开。
再回到第一个函数sub_401362
,接下来按 X 键查看程序对 sub_401362
函数的引用:
只有一处。
在转到这个引用之前,根据函数逻辑来重命名一下 0x401362 函数,比如说 show_error_dialog
。
在函数地址上按 N 键,然后填写这个新的名字,反汇编视图变化如下:
然后我们再看引用这个函数的地方:
这里有一个比较标准的正确/错误分支控制流。
为了更方便查看,可以为成功或失败的代码块加上不同的颜色。
点击这里的图标可以改变显示颜色。读者可能觉得无关紧要,但是在一些特别复杂的函数里,这种方法会有很大的帮助。
我们跟踪一下绿色流程,在call 指令的 sub_40134D 位置处按回车键,进入被调用的函数 0x40134d:
这里有可能是注册成功的消息。但是建议从不成功的信息开始研究,因为这有可能是个假消息。
如果发现程序在决策是否要跳转到这里或者另一条注册失败的路径时,那么这里很有可能是正确的消息。
将这个函数重命名为 show_success_dialog,并标记为绿色:
标记指令位置
选择 0x401243 处的 JZ 指令,打开菜单 JUMP-MARK POSITION,将其命名为 ”最后跳转“,之后就能轻松回到这个位置:
打开菜单 JUMP-JUMP TO MARKED POSITION,将会显示已经标记的位置。
修改指令
按照之前的分析,如果把 0x401243 处的 JZ 改成 JNZ,那么输入一个非法密码的时候,程序也会执行注册成功这条路径。下面具体操作来看一下结果。
利用 keypatch 修改完指令之后,使用 IDA 的 EDIT - Patch program - APPLY PATCH TO 功能对实际的可执行文件进行修改。修改完成后,再次运行程序,发现先是弹出了失败弹窗,然后弹出了成功弹窗:
说明,我们还没有修改完全。
上面我们分析过,有两处引用,我们只修改了一处,再看另一处失败消息引用函数:
不难看出,这里对输入的用户名的字符和 0x41 也就是 ASCII 中的 A 字符进行了比较,如果它小于 0x41(JB),程序将会显示注册失败消息。
我们输入的是数字 123,显然,数字是小于 0x41 的(0 = 30, 1=31 等)。所以当程序检测到用户名含有数字的时候,就会显示这条注册失败消息。那么下面来进行修改,这里不能直接把 JB 改成 JNB,如果这样做就会在输入字母用户名的时候报错。
我们切到反汇编模式(不知道为啥,我流程视图里面不显示地址,有点奇葩):
jb 指令的下面其实就是绿色控制流,那么我们只需要把 jb 指令给 patch 成 nop 就好了。
patch 完之后:
程序再也无法执行到错误控制流了。
但是看起来,这里似乎有个循环,我们先运行下程序看看。
输入数字与字符串,都没有问题。
这只是一个开始。本章通过静态分析的方式成功对文件进行了修改。在后面的章节还会对这个程序进行完整的逆向,然后制作注册机。
原文链接:https://juejin.cn/post/7433966457035227147