逆向工程核心原理(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打开,窗口如下:

image-20230308160553332 image-20230308160657713

调试器停下的位置为程序的入口地址。

image-20230308160849646

在入口点调用了0040270c函数,并跳转到了0040104f。

跟踪0040270c

F7(步进)进入0040270c

image-20230308161432584

右侧区域有Od提供的注释,红色是调用的API函数名称,这里看到的是VS自动添加的一些启动函数调用,F8步过即可。

直到RETN返回仍然没有看到main和MessageBox

跟踪0040104f

F7步入,如下:

image-20230308162040903

这个部分为VS启动函数部分,跟踪它们可以找到main的位置。

相同IDE生成的启动函数都大致相同,在熟悉后可以比较快速地找到启动函数并定位代码

不断跟踪,F7进入后Ctrl+F9退出尝试,找到调用了MessageBox的Main函数位置

image-20230309153140803

设置camp

  • 使用Goto(Ctrl+G)跳转指针到40104f,再使用F4运行到指标位置

  • F2设置BP,打开Alt+B可以看到BP列表,双击一个BP即可跳转到任一个断点(只是高亮位置移到该点),也可直接调试,会自动停止在断点处。

  • 使用‘;’可以进行注释,右键->查找->注释可以查到注释的位置。

    image-20230309154144225 image-20230309154120667
  • 使用‘:’可以添加标签,

    如下为在40104f处添加标签

    image-20230309154607833 image-20230309154641666

    右键->查找->所有用户定义的标签可以查看标签

查找指定的代码

一般来说main函数不会出现在EP位置上,EP上一般是开发工具添加的一些启动函数。

代码执行法

在程序功能明确、代码量不大的情况下可以使用,逐条执行指令来找到需要查找的指令位置。

比如调用MessageBox的代码,一直按F8步过,一定会有一个指令执行后出现消息框。弹出消息对话框的函数为main函数。

字符串检索

右键->查找->所有被引用的字符串可以查看所有程序引用的字符串。

image-20230309161414518

双击”Hello World!”可以定位到MessageBox函数。

在内存Dump窗口使用Ctrl+G可以进一步查看004092A0处。

API检索法(1):调用代码中设置断点

右键->查找->所有模块间调用

程序输出时,需要调用Win32 API,观察一个程序后,就可以大致了解其调用的APi函数有哪些。

比如程序跳出消息框,可以断定调用了MessageBox API。

这个方法可以找到程序调用的所有API列表。

image-20230309162338165

API检索法(2):API代码中设置断点

右键->查找->所有

对于压缩器/保护器,列出API调用列表比较麻烦

image-20230309171606232

在这种情况下,可以在DLL代码库加载到进程内存后直接向DLL代码库添加断点。

API是操作系统提供的一系列函数,存放在C:\Windows\systems32文件夹内

所以程序执行操作时需要向OS提出请求使用API,然后API对应的DLL文件才会被加载进来。

(Alt+M)打开内存映射菜单,可以看到user32库被加载到了内存。

image-20230309172206119

使用查找所有模块中的名称可以列出被加载的DLL中的所有API,单击按名称排序,再键盘输入MessageBoxW可以找到MessageBox

image-20230309173105641

双击即可定位到其代码部分。

image-20230309173237760

在函数起始位置使用F2设置断点,然后继续执行,会自动停在这一行。

image-20230309173508549

此时,ESP=0019FF70,此值指向堆栈位置中存放返回地址值为00401014,也即调用此函数后的下一句。

image-20230309173800936

打补丁修改程序字符串

打补丁常常用于修复程序中的BUG/添加新功能,打补丁的对象可以是文件、内存、程序的代码、数据等

Ctrl+F2重新调试,F9执行到main函数位置。

直接修改字符串缓冲区

该方法操作简单,但对新内容的长度有限制

“Hello World!”保存在4092A0处,只要修改这段即可。

跳转后Ctrl+E打开编辑窗口,修改字符串即可。

image-20230310100825151

在Dump窗口,右键->复制到可执行文件即可将修改保存到exe文件中,在弹出的hex窗口中右键保存文件。

其他区域添加新字符串并传给消息函数

再次运行到main函数处。

在401007处有一条Push命令,将字符串传给消息函数。

再次找到HelloWorld处4092A0,往后一直拖,会看到一个未填充区域。

选择合适的位置如409F50处写入新字符串。

再转到代码401007处,空格打开汇编窗口,输入PUSH 409F50

image-20230310103428825 image-20230310103631084

小端序标记法

image-20230310104521988

IA-32寄存器

IA-32支持以下寄存器

通用寄存器



ESP指示栈顶位置,EBP指示栈的及地址,函数调用时用于保存ESP的值,返回时再还给ESP。ESI和EDI主要用于内存复制。

段寄存器

状态/控制寄存器

EFLAGS 标志寄存器
image-20230310112826490
image-20230310112845836

指令指针寄存器 EIP


作用

  • 局部变量存储

  • 传参

  • 保存返回地址

特征

栈结构如下:

image-20230310132241127

ESP为栈顶指针,初始在栈底(高地址),PUSH使ESP自减移到栈顶方向。

参数入栈

传参时,参数逆序入栈(相对C语言),即从右向左

操作示例

  • OD打开stack.exe。初始时栈顶位置在0019FF74处
image-20230310215122098
  • 执行push 0x100,ESP=ESP-4,

    image-20230310215247583

栈帧

介绍

利用EBP访问栈内的局部变量、参数、函数返回地址等内容。

调用函数时,先将栈顶指针ESP赋给EBP,无论ESP如何变化,EBP不变,能方便访问到局部变量、参数、返回地址等值。

image-20230311231218136

调试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
    image-20230311233245555

  • 执行mov ebp,esp后,栈空间大致如下

image-20230311235135674
  • 可以右键->地址->相对于EBP,方便观察
    image-20230311235356400

  • call 时自动push返回地址并跳转,call后自动pop地址并跳转

abex crackme#2-VB文件

运行

image-20230312165952839

这个程序要求找出程序的序列号,输入Name和Serial,按Check,如下:

image-20230312170116323

VB文件

这个程序由VB编写而成,调试前需要先学习相关特征。

VB专用引擎

使用VB专用引擎MSVBVM60.dll。

比如VB代码中调用MsgBox()实现消息框,其真正调用的是MSVBVM60.dll中的rtcMsgBox(),该函数再调用user32.dll中的MessageBoxW()。

本地代码和伪代码

根据编译选项,VB文件可以编译为本地代码(N code)与伪代码(P code),本地代码使用易于调试的IA-32指令,而伪代码是解释性语言,使用VB引擎实现虚拟机并自解析指令。

要想准确解析VB伪代码,需要分析VB引擎并实现模拟器。

image-20230312170709633

事件处理程序

VB主要用于编写GUI程序,采用Windows的事件驱动方式工作,在main和WinMain中不存在用户代码,用户代码在各个事件处理程序中。

本程序的用于代码就在Check代码触发的事件处理程序内。

未文档化的结构体

VB中各种信息以结构体形式存在文件内部,这些结构体并没有被微软公开。

调试

image-20230312195534006

先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处。

image-20230312200307086

虽然微软没有公布,但在国外有逆向高手完成了对其的分析。

该结构体用于获取程序运行需要的所有信息。

ThunRTMain

进入主函数,如下:

image-20230312200455692

内存地址完全不同,因为这里是MSVBVM60.dll的地址区域。

分析crackme

  • 检索字符串
    image-20230312200849332

  • 双击 wrong serial可以转到代码,这种代码一般分为两个部分,即序列号正确和错误两种,判断后通过跳转输出正确/错误的消息框。

image-20230312200927413
  • 向上拖动代码,可以找到条件转移代码
image-20230313125030999
  • 此处调用了__vbaVarTstEq()函数,比较(TEST)返回值AX后,如果AX为0,则跳转到输出正确消息,否则不跳转输出错误消息。
image-20230312201257150
  • 该函数为字符串比较函数,前面两个push语句应当是传参,即被比较的字符串。

  • F4运行到403329处,输入字符串,点击check。edx=0019F1AC,eax=0019F1BC

    image-20230312201516074

  • dump区转到19f1AC。类似于C++中的stringg类型,VB中的字符串使用可变长度的字符串类型。(字符串对象)
image-20230312202532432
  • 两串内容只有中间4个字节不同,推断这串是地址。使用右键->长型->ASCII数据地址,可以对地址对应的字符串进行显示。

可以看出EDX(EBP-0x44)为正确serial值,而EAX(EBP-0x34)为用户输入serial值。

  • 如下,输入serial即可

    image-20230312204626709

Serial生成算法

预测代码

如果win32 API程序,应当有如下步骤:

  • 读取Name字符串(GetWindowText、GetDlgItemText 等API)

  • 启动循环,对字符运算

VB代码也应当是类似的原理。所以可以从代码开始处(指check回调函数的开始)调试,找到读取name字符串的部分,紧接着就会出现加密循环。

找到name读取代码

由于需要调用API函数,所以可以以call指令为主要注意对象。可以注意到如下call语句。

image-20230312225356326

先将ebp-0x88的位置赋给edx,然后将此地址作为参数传入,调试跳过该call后会发现ebp-0x88位置出现name,判断此call为name读取函数。

找到加密循环

书上并没有详细说明加密循环的部分是如何找出来的,所以对于书上的这部分,我并没有太理解,以下是自己的分析过程。

既然是循环,肯定会有一个向上的jmp过程,主要找到符合这种特征的指令即可。翻找jmp指令,可以看到4032A0处有一个jmp向上跳转。

image-20230315124526833

看看跳转到的部分,可以看到jmp到的位置为test 接jmp,这段主要判断eax是否为0,如果为0就跳到前面那个jmp的后一条指令,这样的指令完美符合循环的特征。可以判断4032A0为循环末尾,403197为循环判断部分。

image-20230315124632639

后面不会啦~

函数调用约定

主要函数调用约定如下:

  • cdecl
  • stdcall
  • fastcall
  • this
  • naked

cdecl

C语言中常用的调用方式,调用者处理栈。使用push从右至左传参。

cdecl方式好处在于可以传递长度可变的参数(如printf)。

stdcall

常用于win32 API,被调用者处理栈。参数传递方式相同。在C语言中需要在函数前添加“_stdcall”关键字以使用stdcall方式编译。

image-20230315105742309

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,弹出消息框

    image-20230315133331048

要求去除所有的nag(大概就是消息框吧)并取得注册码。

  • 点击确定,界面如下:

    image-20230315133419767
  • 随意输入,如下

    image-20230315133504477

去除消息框

观察

  • 先尝试去除消息框,查找所有的代码间调用。这个一般调用rtcMsgBox(从外面的dll文件再到熟悉的启动函数可以判断是vb程序),可以找到三个调用。右键选择在每个rtcMsgBox上设置断点,直接F9运行,调试器自动停止,这个点(402CFE)就是msgbox的位置。需要注意窗口主界面有一个“Nag?”按钮,可以尝试再次F9运行,发现它执行跳转到402CFE,所以只需要对一个进行修改。
image-20230315135320442

方法一

  • 可以运行msgbox函数试试,观察寄存器:

这样可以知道msgbox传入了0x14字节的参数。

  • 空格修改汇编,如下。nop是为了填补空缺,因为原来的call占用5字节,而add指令占用3字节,为了不影响后面的指令,必须填补这两字节的空缺。

  • 这样修改后,msgbox会被越过,且栈会平衡,但仍然有一个问题,就是返回值并没有处理。msgbox会使用eax返回1,表示用户按下确定,这里并没有处理eax,导致返回值为0,所以程序会在运行时认为按下了否,程序自动退出。

    大家可能会想到这样的代码:

    add esp,0x14
    mov eax,1
    

    这个代码是可行的,但是由于代码长度只有5,所以添加一个mov指令会超出这个限制,导致后面的代码被侵占。

方法二

  • 直接向上翻找到函数入口,修改push ebp

    image-20230315143002263
  • 这个0x4可以由两个地方确定,一个是函数末尾使用了retn 0x4,一个是返回地址处的call前后esp变化了0x4。

  • 这样修改后直接跳过该函数的执行过程,消息框自然就被去除了。

找到注册码

  • 查找所有引用字符串,可以找到“Yep”
    image-20230315152830773

这个应当是注册码输入正确时执行的指令。

  • 向前找到je

    image-20230315152933346

这个jmp指令应当为对注册码正确与否的判断。

这里要求di!=si,可以向上继续寻找,

  • 在前面有个strcmp函数,其push 的两个参数分别为“I'mlena151”和用户输入的注册码(ebp-0x58)。基本可以猜测注册码即为“I'mlena151”。
posted @ 2023-03-16 16:12  Muling_m  阅读(393)  评论(0编辑  收藏  举报