前言
开始学习逆向
从《逆向工程核心原理》这本经典开始
本篇笔记是第一部分:代码逆向技术基础
一、关于逆向工程
1、代码逆向工程
代码逆向工程(Reverse Code ENgineering, RCE)(吐槽下,英文缩写真的太乱了,代码执行漏洞也是RCE)
- 静态分析:不执行代码文件
- 动态分析:调试分析代码流
然后是些鸡汤
建议循序渐进
不要急躁贪婪
共勉
二、逆向分析hello world程序
运行hello world
1、调试程序:熟悉基础指令
扔进OD
目标是寻找main函数
入口点(EntryPoint, EP)
win可执行文件的代码入口点,依赖于CPU
依次是地址、指令、汇编代码、注释
OD基本指令
用F7进入CALL命令
执行JMP命令
从40104F开始
用上面的基本指令进行调试查看
寻找main函数
最终在40100有所发现
2、进一步熟悉调试器:更多指令和大本营
指令
设置大本营的方法
- cril+G定位地址,F4让调试流到定位处
- F2设置断点(BP)
;
添加注释,通过查找命令找到:
输入标签
3、查找指定代码
(1)代码执行法
即上面使用的方法
适用于调试代码量不大且程序功能明确的情况
不断F8寻找即可
(2)字符串检索法
右键 -> Search for -> All referenced text strings
(3)API检索法
右键 -> Search for -> All intermodular calls
由于程序跳出了消息窗口
推断调用了user32.MessageBoxW()
API
有时候OD不能列出所有API
那就向DLL代码库寻找
右键 -> Search for -> Name in all modules
4、打补丁修改字符串
(1)直接修改字符串缓冲区
即在dump直接修改
ctrl+G找到位置
ctrl+E修改
但是这对字符串有长度限制
且只是暂时的
若要真正修改,需要做保存
(2)内存里新建字符串并传递给消息函数
在程序中找空白填写字符串
然后改main函数里的push地址
5、小结:常用指令
借hello world简单感受下调试
指令如下
三、小端序标记法
字节序:多字节数据的储存顺序
- 小端序(little endian):内存地址低位存储数据的低位,内存地址高位存储数据的高位,逆序。常用于Intel x86 CPU
- 大端序(big endian):内存地址低位存储数据的高位,内存地址高位存储数据的低位,直观。常用于大型UNIX服务器的RISC系列的CPU、网络协议
四、IA-32寄存器基本讲解
IA-32(Intel Architecture 32位)寄存器
- 通用寄存器:32位,8个
- 段寄存器:16位,6个
- 程序状态与控制寄存器:32位,1个
- 指令指针寄存器:32位,1个
1、通用寄存器
通用寄存器共8个,且在16位时就存在,故加E表示扩展
- EAX:累加器,针对操作数和结果数据,所有win32 API函数都会把返回值保存在此
- EBX:基址寄存器,DS段中的数据指针
- ECX:计数器,字符串和循环操作,每循环一次减1
- EDX:数据寄存器,I/O指针
- EBP:扩展基址指针寄存器,SS段中栈内数据指针,表示栈区域的基地址
- ESI:源变址寄存器,字符串操作源指针
- EDI:目的变址寄存器,字符串操作目标指针
- ESP:栈指针寄存器,SS段中栈指针,指示栈顶地址
2、段寄存器
段
- 内存保护技术
- 将内存划分为多个区段,并赋予起始地址、范围、访问权限
- 和分页技术一起将虚拟内存变更为实际物理内存
段寄存器共6个,指向的段描述符与虚拟内存结合形成线性地址,借助分页技术(如果有的话),转为实际物理地址
- CS:代码段寄存器,存放程序代码所在段的段基址
- SS:栈段寄存器,存放栈段的段基址
- DS:数据段寄存器,存放数据段的段基址
- ES:附加段寄存器
- FS:数据段寄存器,调试中常用,计算SEH\TEB\PEB
- GS:数据段寄存器
3、程序状态与控制寄存器
名称为EFLAGS,标志寄存器
每一位都有意义,看图
重要的三位是
- ZF:零标志,运算结果为0则置1
- OF:溢出标志,有符号整数溢出或最高有效位改变,置1
- CF:进位标志,无符号整数溢出,置1
4、指令指针寄存器
名称为EIP,指令指针寄存器
- 保存CPU要执行的指令地址
- 不能直接修改
- 可以通过JMP、Jcc、CALL、RET、中断、异常来间接修改
五、栈
栈
- FILO原则
- 高地址向低地址扩展
- 特征:栈顶指针(ESP)初始状态指向栈底
六、分析abex’ crackme
这个abex’ crackme运行如下
1、OD打开
非常简短
因为这是直接汇编写的
逻辑非常简单清晰
- 调用MessageBox API出现第一个窗口
- 调用GetDriveType API获取C驱动器类型,然后操作它,使之被识别为CD-ROM类型
- 对寄存器做些操作
- 条件判断弹出调用成功或调用失败的窗口
2、破解
401026地址处指令
修改为JMP 0040103D
即直接改为跳转
七、栈帧
1、栈帧
栈帧在程序中用于声明局部变量、调用函数
- 用EBP寄存器访问站内局部变量、参数、函数返回地址等
- 调用函数时,将作为基准点的ESP值保存到EBP
栈帧结构如下:
2、示例:stackframe
以stackframe这个程序为例
感受下栈帧的使用
观察栈的行为动作
(1)生成栈帧
源码:
在main函数的起始地址401020设置断点
此时寄存器EBP和ESP如下:
运行完401020和401021两步后就生成了栈帧
(2)设置局部变量
首先是分配空间:从ESP减去8个字节,为函数的局部变量a, b开辟8个字节的空间
00401023 sub esp,0x8
然后是指针两个
执行完后的栈内如下
(3)add函数
这一段是把b和a压入栈,注意顺序与c语言相反
然后call401000的函数,即add
执行完之后
栈内
(4)printf函数
- 地址0401044处的EAX寄存器中存储着函数add()的返回值,它是执行加法运算后的结果值3
- 地址0040104A处的CALL 00401067命令中调用的是00401067地址处的函数,它是一个c标准库函数printf(),所有C标准库函数都有Visual C++编写而成
- 由于上面的printf()函数有2个参数,大小为8个字节(32位寄存器+32位常量=64位=8字节),所以在0040104F地址处使用ADD命令,将ESP加上8个字节,把函数的参数从栈中删除
(5)删除栈帧
释放EBP
然后return
八、分析abex’ crackme #2
程序打开如下
使用Visual Basic写的
- 使用msvbvm60.dll专用引擎
- VB文件可以便以为本地代码和伪代码
- 各种信息以结构体形式保存
1、OD打开
EP的地址是401238
- 把RT_MainStruct结构体的地址401E14压入栈
- 然后CALL命令调用JMP,跳转到VB引擎的主函数ThunRTMain(),这是VB常用的间接调用法
2、破解
通过字符串搜索找到“congratulation”
发现403332 的条件判断
往上面看,push的edx和eax是我们需要的参数
再往上看,找到栈帧
设置断点并调试
然后一步步调试
- 找到记录Name的地方
- 找到加密过程
- 从Name逐一读取字符
- 字符转换ASCII码
- ASCII码加64
- 转换为字符
- 连接字符
- 最后找到Serial=B6C9DAC9
这里过程太长了
就不一一记录
可以参考逆向工程核心原理学习笔记(二十七):abex’crackme #2 破解算法
九、Process Explorer
工具推荐
使用可参考
Process Explorer使用图文教程
十、函数调用约定
函数调用约定(Calling Convention),是对函数调用时如何传递参数的约定
- 函数执行完成后,栈中的参数不用管,会被覆盖
- 函数执行完成后,ESP要恢复到调用之前,不然ESP指向栈底,无法使用栈,这就是函数调用约定要解决的问题
1、cdecl
主要在C语言中使用,调用者负责处理栈
例子
#include "stdio.h"
int add(int a, int b)
{
return (a+b);
}
int main(int argc, char* argv[])
{
return add(1,2);
}
扔进OD可以看到
- add函数的参数逆序压入栈
- 然后用 ESP, 8 命令整理栈
- main函数直接清理压入栈中的函数参数
2、stdcall
常用于Win32 API,由被调用者清理栈
比cdecl的代码尺寸小
例子
#include "stdio.h"
int _stdcall add(int a, int b)
{
return (a+b);
}
int main(int argc, char* argv[])
{
return add(1,2);
}
扔进OD可以发现
- 省略了 ESP, 8 命令
- 用 retn 8 命令(retn + pop 8),使ESP增加到指定大小
3、fastcall
与stdcall类似,但通常用寄存器(而非栈内存)传递参数,若有4个参数,则前2个参数分别用ECX和EDX传递
由于使用寄存器,对函数的调用显然要快很多,但可能需要额外的系统开销来管理ECX和EDX
结语
主要是以下内容
- OD的基础指令
- 注意小端字节序
- IA-32位寄存器的基本介绍
- 栈的简单介绍
- 栈帧的介绍
- 函数调用约定