逆向基础04

识别程序的特征

如何识别程序的特征

使用PE查看工具查看 链接器版本

使用PE查看工具查看 区段信息

查看目标程序的 二进制特征

BC++ 程序(Borland C++)

程序的二进制特征:

image-20191220081907452

1
2
3
EB 10         JMP SHORT BC6.0040130E
66 62 3A 43 2>ASCII "fb:C++HOOK"
90 NOP

BC++ 编写的程序,IAT函数的调用通常都是 E8 跳转表 + FF25 IAT 地址

image-20191220083318643

链接器版本:

image-20191220082759624

区段特征:

image-20191220082933038

Delphi 程序

连接器版本:2.25

image-20191220083522510

区段特征:

image-20191220083622399

二进制特征:连续的 5 个 call 后面紧跟着一堆 0

image-20191220083713955

1
2
3
4
5
6
7
55            PUSH EBP
8BEC MOV EBP,ESP
83C4 F0 ADD ESP,-0x10
B8 ???????? MOV EAX,Delphi7.00454834 ; UNICODE ";"
E8 ???????? CALL Delphi7.00405C60
A1 ???????? MOV EAX,DWORD PTR DS:[0x456058]
8B00 MOV EAX,DWORD PTR DS:[EAX]

image-20191220083811375

VB6 程序

链接器版本

image-20191220084800738

区段特征

image-20191220084845698

二进制特征(VB程序一般直接使用工具进行反编译)

image-20191220085103692

易语言、VC6

二进制特征,两个的特征基本信息

image-20191220085953203

1
2
3
4
5
6
55              PUSH EBP
8BEC MOV EBP,ESP
6A FF PUSH -0x1
68 ???????? PUSH VC6_0_De.005BDC80
68 ???????? PUSH VC6_0_De.00422B78
64:A1 00000000 MOV EAX,DWORD PTR FS:[0]

连接器特征

image-20191220090102677

区段特征

image-20191220090037179

汇编程序

汇编程序的链接器版本和区段特征都是不固定的,也没有明确的二进制特征。可以通过查看文件的大小来猜测是不是一个汇编程序。程序的 OEP 部分通常直接就是逻辑代码,也可以作为标识。

VS 程序的特征

不同版本的VS编写出来的程序,特征是不同的

通常 debug 版本的程序入口点可能存在 call + call,而 release 是 call + jmp

VS 版本 链接器版本
VC 6.0 6.0
VC2003 7.0 / 7.1
VS2005 8.0
VS2008 9.0
VS2010 10.0
VS2012 11.0
VS2013 12.0
VS2015 14.0
VS2017 14.1
VS2019 14.2

基本数据类型的识别

常量的识别

1
2
3
4
5
6
const bool bRet = true;
const int nCount = SIZE;
const char* szHello = "Hello 15PB";
const eData data = eData::enum_TYPE_1;
const float fNum = 1.5;
const sData stc = { 1,2.0,'1' };

常量信息可能被保存在常量数据区和代码区

image-20191220094911401

字符串常量的初始化

1
2
3
4
5
6
// 字符串数组
char szStr[100] = { "szStr[100] Hello 15PB" };
// 宽字符串数组
wchar_t szWchar[100] = L"szWchar Hello 15PB";
// 普通的字符数组
char szHello[] = "szHello[] Hello 15PB";

对于一个字符串,初始化通常是: 四字节拷贝 + 不足四字节拷贝 + 填充剩余的空间为 0

如果初始化数据较多,第一步和第二步会简化成一个串操作进行赋值

对于一个没有指定大小的字符串初始化操作,就没有填充剩余空间的步骤

image-20191220101214702

指针和引用

指针和引用在汇编层面的实现完全一致

1
2
3
4
5
6
7
int* pnumber = &number;
// 00F21739 lea eax, [number]
// 00F2173C mov dword ptr[pnumber], eax

int& rnumber = number;
// 00F2173F lea eax, [number]
// 00F21742 mov dword ptr[rnumber], eax

分析对象

构造函数的分析

构造函数会使用 ecx 传递对象的首地址(this),构造函数的返回值是 this

一个存在继承关系且有虚函数的构造函数 = 构造父类\成员 + 初始化虚表指针 + 用户代码 + 返回值

image-20191220105137737

image-20191220110306338

image-20191220111502533

析构函数的分析

析构函数中没有设置返回值。

一个存在继承关系且有虚函数的析构函数 = 重置虚表指针 + 用户代码 + 析构父类

image-20191220105452142

image-20191220110611026

image-20191220112023141

成员函数和数据成员

成员函数的调用会使用 ecx 传递 this 指针

数据成员的使用是通过 this 加上一个偏移得到

image-20191220113319905

image-20191220113037429

虚函数

并不是所有的虚函数调用都会有动态联编

image-20191220114045510

对系统代码的识别

1
2
3
4
002A171E  mov         eax,dword ptr [__security_cookie (02AA004h)]  
002A1723 xor eax,ebp
002A1725 mov dword ptr [ebp-4],eax
; 安全 cookie 是一个运行时确定的值,使用当前的 ebp 对它进行加密,并放置到 ebp - 4 的位置,如果被缓冲区溢出攻击了,那么 ebp - 4 绝对会被覆盖,覆盖后的值再次使用 ebp 异或解密就无法恢复之前的数值,导致程序结束

检查一段代码是否是用户代码

1
2
002A1728  mov         ecx,offset _2A6D85CB_
002A172D call @__CheckForDebuggerJustMyCode@4 (02A1208h)

检查数组是否越界

1
2
002A17EE  lea         edx,ds:[2A181Ch]  
002A17F4 call @_RTC_CheckStackVars@8 (02A1235h)

检查当前的程序是否被缓冲区溢出攻击

1
2
3
002A17FE  mov         ecx,dword ptr [ebp-4]  
002A1801 xor ecx,ebp
002A1803 call @__security_check_cookie@4 (02A11DBh)

检查堆栈是否平衡

1
2
3
002A1808  add         esp,220h  
002A180E cmp ebp,esp
002A1810 call __RTC_CheckEsp (02A1212h)