逆向工程核心原理(1)逆向基础
所有资源可以在官网下载
代码逆向工程
逆向分析法
静态分析
不执行代码,观察外部特征,获取其文件类型、大小、PE头、Import/Export API、字符串、是否运行时解压缩、注册信息、调试信息、数字证书、反汇编代码等
动态分析
在程序文件执行的过程中对代码进行分析。
一般首先静态分析,再进行动态分析。
逆向Hello World
代码如下:
#include<windows.h>
#include<tchar.h>
int _tmain(int argc,TCHAR *argv[])
{
MessageBox(NULL, L"Hello World!", L"Muling", MB_OK);
return 0;
}
调试器与汇编
分析二进制文件时,需要使用调试器(Debugger)的反汇编模块,将二进制代码转换为汇编代码。
调试HelloWorld.exe
目标
目标:调试HelloWorld.exe可执行文件,在汇编代码中找到main函数中的MessageBox函数的调用。
打开文件
Ollydbg打开,窗口如下:
调试器停下的位置为程序的入口地址。
在入口点调用了0040270c函数,并跳转到了0040104f。
跟踪0040270c
F7(步进)进入0040270c
右侧区域有Od提供的注释,红色是调用的API函数名称,这里看到的是VS自动添加的一些启动函数调用,F8步过即可。
直到RETN返回仍然没有看到main和MessageBox
跟踪0040104f
F7步入,如下:
这个部分为VS启动函数部分,跟踪它们可以找到main的位置。
相同IDE生成的启动函数都大致相同,在熟悉后可以比较快速地找到启动函数并定位代码
不断跟踪,F7进入后Ctrl+F9退出尝试,找到调用了MessageBox的Main函数位置
设置camp
-
使用Goto(Ctrl+G)跳转指针到40104f,再使用F4运行到指标位置
-
F2设置BP,打开Alt+B可以看到BP列表,双击一个BP即可跳转到任一个断点(只是高亮位置移到该点),也可直接调试,会自动停止在断点处。
-
使用‘;’可以进行注释,右键->查找->注释可以查到注释的位置。
-
使用‘:’可以添加标签,
如下为在40104f处添加标签
右键->查找->所有用户定义的标签可以查看标签
查找指定的代码
一般来说main函数不会出现在EP位置上,EP上一般是开发工具添加的一些启动函数。
代码执行法
在程序功能明确、代码量不大的情况下可以使用,逐条执行指令来找到需要查找的指令位置。
比如调用MessageBox的代码,一直按F8步过,一定会有一个指令执行后出现消息框。弹出消息对话框的函数为main函数。
字符串检索
右键->查找->所有被引用的字符串可以查看所有程序引用的字符串。
双击”Hello World!”可以定位到MessageBox函数。
在内存Dump窗口使用Ctrl+G可以进一步查看004092A0处。
API检索法(1):调用代码中设置断点
右键->查找->所有模块间调用
程序输出时,需要调用Win32 API,观察一个程序后,就可以大致了解其调用的APi函数有哪些。
比如程序跳出消息框,可以断定调用了MessageBox API。
这个方法可以找到程序调用的所有API列表。
API检索法(2):API代码中设置断点
右键->查找->所有
对于压缩器/保护器,列出API调用列表比较麻烦
在这种情况下,可以在DLL代码库加载到进程内存后直接向DLL代码库添加断点。
API是操作系统提供的一系列函数,存放在C:\Windows\systems32
文件夹内
所以程序执行操作时需要向OS提出请求使用API,然后API对应的DLL文件才会被加载进来。
(Alt+M)打开内存映射菜单,可以看到user32库被加载到了内存。
使用查找所有模块中的名称可以列出被加载的DLL中的所有API,单击按名称排序,再键盘输入MessageBoxW可以找到MessageBox
双击即可定位到其代码部分。
在函数起始位置使用F2设置断点,然后继续执行,会自动停在这一行。
此时,ESP=0019FF70,此值指向堆栈位置中存放返回地址值为00401014,也即调用此函数后的下一句。
打补丁修改程序字符串
打补丁常常用于修复程序中的BUG/添加新功能,打补丁的对象可以是文件、内存、程序的代码、数据等
Ctrl+F2重新调试,F9执行到main函数位置。
直接修改字符串缓冲区
该方法操作简单,但对新内容的长度有限制
“Hello World!”保存在4092A0处,只要修改这段即可。
跳转后Ctrl+E打开编辑窗口,修改字符串即可。
在Dump窗口,右键->复制到可执行文件即可将修改保存到exe文件中,在弹出的hex窗口中右键保存文件。
其他区域添加新字符串并传给消息函数
再次运行到main函数处。
在401007处有一条Push命令,将字符串传给消息函数。
再次找到HelloWorld处4092A0,往后一直拖,会看到一个未填充区域。
选择合适的位置如409F50处写入新字符串。
再转到代码401007处,空格打开汇编窗口,输入PUSH 409F50
小端序标记法
IA-32寄存器
IA-32支持以下寄存器
通用寄存器
ESP指示栈顶位置,EBP指示栈的及地址,函数调用时用于保存ESP的值,返回时再还给ESP。ESI和EDI主要用于内存复制。
段寄存器
状态/控制寄存器
EFLAGS 标志寄存器
指令指针寄存器 EIP
栈
作用
-
局部变量存储
-
传参
-
保存返回地址
特征
栈结构如下:
ESP为栈顶指针,初始在栈底(高地址),PUSH使ESP自减移到栈顶方向。
参数入栈
传参时,参数逆序入栈(相对C语言),即从右向左
操作示例
- OD打开stack.exe。初始时栈顶位置在0019FF74处
- 执行push 0x100,ESP=ESP-4,
栈帧
介绍
利用EBP访问栈内的局部变量、参数、函数返回地址等内容。
调用函数时,先将栈顶指针ESP赋给EBP,无论ESP如何变化,EBP不变,能方便访问到局部变量、参数、返回地址等值。
调试stackframe.exe
//StackFrame.exe
#include<stdio.h>
long add(long a,long b)
{
long x = a, y = b;
return x + y;
}
int main(int argc,char* argv[])
{
long a = 1, b = 2;
printf("%d\n",add(a,b));
return 0;
}
-
Ctrl+G到401000
-
执行
mov ebp,esp
后,栈空间大致如下
-
可以右键->地址->相对于EBP,方便观察
-
call 时自动push返回地址并跳转,call后自动pop地址并跳转
abex crackme#2-VB文件
运行
这个程序要求找出程序的序列号,输入Name和Serial,按Check,如下:
VB文件
这个程序由VB编写而成,调试前需要先学习相关特征。
VB专用引擎
使用VB专用引擎MSVBVM60.dll。
比如VB代码中调用MsgBox()实现消息框,其真正调用的是MSVBVM60.dll中的rtcMsgBox(),该函数再调用user32.dll中的MessageBoxW()。
本地代码和伪代码
根据编译选项,VB文件可以编译为本地代码(N code)与伪代码(P code),本地代码使用易于调试的IA-32指令,而伪代码是解释性语言,使用VB引擎实现虚拟机并自解析指令。
要想准确解析VB伪代码,需要分析VB引擎并实现模拟器。
事件处理程序
VB主要用于编写GUI程序,采用Windows的事件驱动方式工作,在main和WinMain中不存在用户代码,用户代码在各个事件处理程序中。
本程序的用于代码就在Check代码触发的事件处理程序内。
未文档化的结构体
VB中各种信息以结构体形式存在文件内部,这些结构体并没有被微软公开。
调试
先push一个RT_MainStruct结构体的地址,然后call 调用401232处的JMP,跳转到VB引擎的主函数ThunRTMain(),push的值作为其参数。
间接调用
40123D处Call 401232调用ThunRTMain()函数,使用了间接调用,通过401232处的JMO跳转,而不是直接call MSVBVM60.dll中的ThunRTMain()函数。
401232处的JMP如下:
4010A0为IAT(导入地址表)区域,包含MSVBVM60.ThunRTMain()函数的实际地址。
RT_MainStruct
该结构体在401E14处。
虽然微软没有公布,但在国外有逆向高手完成了对其的分析。
该结构体用于获取程序运行需要的所有信息。
ThunRTMain
进入主函数,如下:
内存地址完全不同,因为这里是MSVBVM60.dll的地址区域。
分析crackme
-
检索字符串
-
双击 wrong serial可以转到代码,这种代码一般分为两个部分,即序列号正确和错误两种,判断后通过跳转输出正确/错误的消息框。
- 向上拖动代码,可以找到条件转移代码
- 此处调用了__vbaVarTstEq()函数,比较(TEST)返回值AX后,如果AX为0,则跳转到输出正确消息,否则不跳转输出错误消息。
-
该函数为字符串比较函数,前面两个push语句应当是传参,即被比较的字符串。
-
F4运行到403329处,输入字符串,点击check。edx=0019F1AC,eax=0019F1BC
- dump区转到19f1AC。类似于C++中的stringg类型,VB中的字符串使用可变长度的字符串类型。(字符串对象)
- 两串内容只有中间4个字节不同,推断这串是地址。使用右键->长型->ASCII数据地址,可以对地址对应的字符串进行显示。
可以看出EDX(EBP-0x44)为正确serial值,而EAX(EBP-0x34)为用户输入serial值。
-
如下,输入serial即可
Serial生成算法
预测代码
如果win32 API程序,应当有如下步骤:
-
读取Name字符串(GetWindowText、GetDlgItemText 等API)
-
启动循环,对字符运算
VB代码也应当是类似的原理。所以可以从代码开始处(指check回调函数的开始)调试,找到读取name字符串的部分,紧接着就会出现加密循环。
找到name读取代码
由于需要调用API函数,所以可以以call指令为主要注意对象。可以注意到如下call语句。
先将ebp-0x88的位置赋给edx,然后将此地址作为参数传入,调试跳过该call后会发现ebp-0x88位置出现name,判断此call为name读取函数。
找到加密循环
书上并没有详细说明加密循环的部分是如何找出来的,所以对于书上的这部分,我并没有太理解,以下是自己的分析过程。
既然是循环,肯定会有一个向上的jmp过程,主要找到符合这种特征的指令即可。翻找jmp指令,可以看到4032A0处有一个jmp向上跳转。
看看跳转到的部分,可以看到jmp到的位置为test 接jmp,这段主要判断eax是否为0,如果为0就跳到前面那个jmp的后一条指令,这样的指令完美符合循环的特征。可以判断4032A0为循环末尾,403197为循环判断部分。
后面不会啦~
函数调用约定
主要函数调用约定如下:
- cdecl
- stdcall
- fastcall
- this
- naked
cdecl
C语言中常用的调用方式,调用者处理栈。使用push从右至左传参。
cdecl方式好处在于可以传递长度可变的参数(如printf)。
stdcall
常用于win32 API,被调用者处理栈。参数传递方式相同。在C语言中需要在函数前添加“_stdcall”关键字以使用stdcall方式编译。
RETN 8表示返回后pop8个字节,将ESP增加到合适的大小。
fastcall
被调用者平衡栈。ECX、EDX传递第1、2个参数,其他参数与stdcall相同。常用于内核程序,这种调用方式比较快。
this
被调用者平衡栈。ECX传递this,其余参数相同。C++成员函数常用。
naked
被调用者平衡栈。传参方式相同。使用此调用方式,编译器自动为函数生成如下的进入和退出代码。
PUSH EBP
MOV EBP,ESP
......
POP EBP
RET 8
CRACKME
程序分析
-
打开exe,弹出消息框
要求去除所有的nag(大概就是消息框吧)并取得注册码。
-
点击确定,界面如下:
-
随意输入,如下
去除消息框
观察
- 先尝试去除消息框,查找所有的代码间调用。这个一般调用rtcMsgBox(从外面的dll文件再到熟悉的启动函数可以判断是vb程序),可以找到三个调用。右键选择在每个rtcMsgBox上设置断点,直接F9运行,调试器自动停止,这个点(402CFE)就是msgbox的位置。需要注意窗口主界面有一个“Nag?”按钮,可以尝试再次F9运行,发现它执行跳转到402CFE,所以只需要对一个进行修改。
方法一
- 可以运行msgbox函数试试,观察寄存器:
这样可以知道msgbox传入了0x14字节的参数。
-
空格修改汇编,如下。nop是为了填补空缺,因为原来的call占用5字节,而add指令占用3字节,为了不影响后面的指令,必须填补这两字节的空缺。
-
这样修改后,msgbox会被越过,且栈会平衡,但仍然有一个问题,就是返回值并没有处理。msgbox会使用eax返回1,表示用户按下确定,这里并没有处理eax,导致返回值为0,所以程序会在运行时认为按下了否,程序自动退出。
大家可能会想到这样的代码:
add esp,0x14 mov eax,1
这个代码是可行的,但是由于代码长度只有5,所以添加一个mov指令会超出这个限制,导致后面的代码被侵占。
方法二
-
直接向上翻找到函数入口,修改push ebp
-
这个0x4可以由两个地方确定,一个是函数末尾使用了retn 0x4,一个是返回地址处的call前后esp变化了0x4。
-
这样修改后直接跳过该函数的执行过程,消息框自然就被去除了。
找到注册码
- 查找所有引用字符串,可以找到“Yep”
这个应当是注册码输入正确时执行的指令。
-
向前找到je
这个jmp指令应当为对注册码正确与否的判断。
这里要求di!=si
,可以向上继续寻找,
- 在前面有个strcmp函数,其push 的两个参数分别为“I'mlena151”和用户输入的注册码(ebp-0x58)。基本可以猜测注册码即为“I'mlena151”。