一、不同语言程序的特征
0. 如何确定目标程序的语言?
- 入口点的特征(二进制特征)
- 链接器版本
- 区段的名称
1. BC++ 程序
-
入口点的特征
-
BC++ 编写的程序,调用IAT函数会使用到 FF25 ?? ?? ?? ??
-
链接器版本是: 5.0
-
二进制特征,后面会有 GetModuleHandle 函数的调用,由于大多数查壳工具使用的都是特征码匹配,例如 PEID 使用了 userdb.txt 中的特征,只要伪造二进制特征就可以让大多数的查壳工具失效。
EB 10 66 62 3A 43 2B 2B 48 4F 4F 4B 90
-
区段的特征:不同功能对应的数据放置在了相应的区段中
2. Delphi 程序
-
入口特征
-
由于 Delphi 和 BC++ 程序都是宝蓝公司的,所以 IAT 调用特征相同
-
链接器版本:2.25
-
二进制特征: B8 和 E8 后面都是不同的,未知的可以使用 ?? 代替
55 8B EC 83 C4 F0 B8 ?? ?? ?? ?? E8 ?? ?? ?? ??
-
区段特征:代码段(CODE)和数据段(DATA)以及未初始化的数据(BSS)
3. 汇编程序
-
特征,小,入口点直接就是逻辑代码
-
链接器版本:5.12
4. VC6.0\易语言
-
入口点特征:
-
链接器版本
-
区段特征
-
二进制特征:
55 8B EC 6A FF 68 ?? ?? ?? ?? 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 64 89 25 00 00 00 00
5. VS 程序的连接器版本对应的VS版本
- VS 版本的不同会导致编写出的程序特征(链接器、入口点、区段)
- debug 下编译的程序区段的数量相对于 Release 多一些
- release 的入口存在
call ???????? jmp ????????
,debug可能是两个call
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 |
二、VS数据的分析
1. 基本常量类型
- 大多数的常量是作为 OPCODE 的一部分被直接存储在代码区
- 字符串:常量字符串保存在常量区,赋值使用的实际是所在的地址
- 浮点数:被保存在常量区,初始化时需要通过 xmm 寄存器
#define SIZE 100
enum class eData
{
enum_TYPE_1 = 1,
enum_TYPE_2 = 2,
enum_TYPE_3 = 3
};
struct sData
{
int n;
float fNum;
char chA;
};
int main()
{
const bool bRet = true; // 布尔常量
// mov byte ptr[ebp - 5], 1
const int nCount = SIZE; // const常量
// mov dword ptr[ebp - 14h], 64h
const char* szHello = "Hello 15PB"; // 字符串常量
// mov dword ptr[ebp - 20h], 217BCCh(字符串地址)
const eData data = eData::enum_TYPE_1; // 枚举常量
// mov dword ptr[ebp - 2Ch], 1
const float fNum = 1.5; // 浮点常量
// movss xmm0, dword ptr ds : [00217BD8h]
// movss dword ptr[ebp - 38h], xmm0
// mov dword ptr[ebp - 4Ch], 1
const sData stc = { 1,2.0,'1' }; // 结构体常量
// movss xmm0, dword ptr ds : [00217BDCh]
// movss dword ptr[ebp - 48h], xmm0
// mov byte ptr[ebp - 44h], 31h
return 0;
}
- 通过解析头文件(Ctrl+F9)的方式添加头文件,头文件不能解析高标准的语法
- 可以直接在常量上按下'M'将常量解释为 枚举类型
2. 字符串的初始化
-
// 字符串数组,数据量较小,使用的是 mov char szStr[100] = { "szStr[100] Hello 15PB" };
-
// 宽字符串数组 ,数据量较大,使用的时串操作 wchar_t szWchar[100] = L"szWchar Hello 15PB";
-
// 普通的字符数组,相比于第一个,数组的大小由字符串长度决定,不需要调用 memset char szHello[] = "szHello[] Hello 15PB";
3. 指针和引用
int main()
{
int number = 10;
int* pnumber = &number;
/*
lea eax,[number]
mov dword ptr [pnumber],eax
*/
int& rnumber = number;
/*
lea eax,[number]
mov dword ptr [rnumber],eax
*/
return 0;
}
三、C++ 对象分析
1. 构造函数
- 构造函数使用 ecx 传递对象的地址,在其中进行初始化
- 初始化当前对象的虚函数表指针
- 会在执行具体的逻辑代码前,调用父类的构造函数
- 会将返回值设置为当前的 this 指针
2. 析构函数
- 构造函数使用 ecx 传递对象的地址,在其中进行初始化
- 会在执行具体的逻辑代码前,调用父类的构造函数
- 返回值不具备任何的意义
3. 普通成员函数
- 使用 ecx 传递 this 指针,其余的参数使用栈传递,函数内平衡堆栈
- 数据成员的寻址依赖的是this指针+数据相对于对象首地址偏移
4. 虚函数
- 只有同时使用虚函数和指针或引用调用才能实现动态联编
5. 友元函数 \ 静态成员函数
- 友元函数和静态成员函数与普通函数之间没有区别
评论区