最近常遇到的尴尬是看着汇编代码却无法在脑中反应出正确的C、C++代码 。近日偶得一篇好文《reverse C++》,细读下来收获不少。遂打算在《reverse C++》的基础上扩展,从汇编角度来认识一些C、C++中的语法现象~先从一些简单的开始...
一、数组
数组是非常基本的数据结构,C++中的数组表现为一段连续可用的内存。注意这段内存地址是连续的,这与很多动态语言中的数组不一样,举例来说PHP中的数组实际上是一种hash表式的实现,因而肯定不会是一段连续的内存。
对于充当全局变量的数组,会自动为数组中的每个变量附上默认值;而对于声明在函数体内部的局部变量,则需要手动进行初始化。来看一段简单的代码:
#include <iostream.h>
void main()
{
int arr[10];
arr[3] = 22;
cout<<arr;
}
这段C++代码在main函数中声明了一个长度为10的arr数组,不过并没有初始化,而是仅仅给数组中的一个单元赋予了初始值。在VC6 release下进行编译,优化选项为最快速度,得到的汇编代码如下:
00401000 sub esp, 0x28 //在栈上开辟40个字节的空间,每个int占4字节
00401003 lea eax, dword ptr [esp] //arr 指向栈顶,因此栈顶相当于arr[0]
00401007 mov ecx, 0040B9B8 //cout指针存入ecx,为方法调用做准备
0040100C push eax
0040100D mov dword ptr [esp+10], 16 //相当于arr[3]=22
00401015 call 00401020 //eax中存放的arr,执行cout<<ar
0040101A add esp, 28 //回收数组占用的40个字节的空间
0040101D retn
这里的汇编看着有点乱,比较理想的编译出来的代码应该是:
sub esp, 28 // 执行 int arr[10]
mov dword ptr [esp+C], 16 // 注意这里是esp+C ,esp 是 arr[0],所以 arr[3] = esp + C
lea eax, dword ptr [esp]
push eax
mov ecx, 0040B9B8
call 00401020
add esp, 28
retn
可能有人注意到了这里栈并不平衡,中间的push并没有对应的pop操作,这是因为调用cout对象的<<操作符相当于调用一个类得成员函数,需满足thiscall约定,当参数个数确定的时候,由被调用的call本身来清理堆栈(push的参数个数不确定时,才由调用方来清理)。
题外话,cout对象也是一个全局对象,必须要确保在main函数调用之前完成初始化,这样在main里才能直接使用。从Entry Point到main函数调用(5):_cinit
中介绍了cinit 函数,其实cout 对象的生成也是在cinit中完成的。准确的说,是在
/*
* do C++ initializations
*/
_initterm( __xc_a, __xc_z )
中完成的。在CRT源码中的IOSTRINI.CPP 文件中有如下语句:
ostream_withassign cout(_new_crt filebuf(1));
可见cout 是 ostream_withassign 类型的一个实例。
1)数组初始化
还是从一段简单的示例代码说起:
void main()
{
int arr[10] = {22,99};
}
arr依然是局部变量, 这里只会将arr[0] 和 arr[1] 初始化为22、99,数组中其余的8个变量都会初始化为0。来具体看看main函数的汇编代码:
push ebp
mov ebp, esp
sub esp, 28 // 开辟40个字节空间当数组
push edi // 暂存edi的值, 因为下面要用到edi寄存器
mov dword ptr [ebp-28], 16 // arr[0] = 22
mov dword ptr [ebp-24], 63 // arr[1] = 99
mov ecx, 8
xor eax, eax
lea edi, dword ptr [ebp-20] // 将arr[2] 的地址放入edi
rep stos dword ptr es:[edi] // 该指令将arr[2] - arr[9] 全部清0
pop edi // 回复edi寄存器
mov esp, ebp
pop ebp
retn
rep stos dword ptr es:[edi] 会根据ECX的值重复执行,它所作的动作就是从EAX中取4个字节,然后传到EDI所指的内存地址,这个动作会不停重复直到ECX减到0为止。由于是将arr[2] - arr[9]全部置0,所以先将EAX清0。
2)全局数组
这里仅仅是验证一下全局变量并非存放在栈上。
int arr[10] = {1,2,3};
void main()
{
arr[5]=10;
}
在PE文件的.data
区发现了:
数组arr 被放到了数据区 00406030 - 00406057 的区域。很显然,这里无法利用ESP + XXX 或者 EBP - XXX 来直接对数组中的内容进行访问,因为arr 并不是存在于栈区。因此在main函数中,对该数组的访问直接写成了硬编码的地址。一旦PE 被映射进内存,就可以直接操作写死的内存地址。这里的main 函数编译成如下:
mov dword ptr [406044], 1
retn
一般对数组的访问都是由基地址(数组首地址)+偏移量组成。
首地址形如: ESP+XXX 或者 EBP-XXX
偏移量形如: ElementSize*INDEX(ElementSize是数组中元素的大小,index放在eax或者ecx等寄存器中)
但是这里直接写死的地址406044,406044就表示arr[5],甚至连“基址 + 偏移量”都没用到。
分享到:
相关推荐
逆向C++ 逆向工程 逆向C++ 逆向工程
C++逆向学习比较好的入门资料 版权归作者所有
### 逆向C++程序的关键知识点 #### 一、引言和必要性 逆向工程在信息安全领域扮演着至关重要的角色。随着C++语言在软件开发中的广泛应用,特别是恶意软件和现代应用程序领域的增长,深入理解和掌握逆向C++程序的...
1. **恶意软件增多**:近年来,使用C++编写的恶意软件数量显著增加。例如,Agobot等知名病毒就是用C++编写的。由于C++支持复杂的面向对象特性,因此这些恶意软件往往更难以分析和检测。 2. **现代应用广泛采用**:...
C++逆向学习三步走
### 逆向C++关键技术详解 #### I. 引言和必要性 逆向工程是一项技术密集型工作,尤其在面对使用C++编写的软件时更是如此。由于C++支持面向对象编程特性,如封装、继承和多态,使得逆向这类程序变得更加复杂。随着...
这些年来,逆向工程分析人员一直是凭借着汇编和C 的知识对大多数软件进行逆向工程的,但是,现在随着越来越多的应用程序和恶意软件转而使用C++语言进行开发,深入理解C++面向对象方式开发的软件的反汇编技术就显得...
这些年来,逆向工程分析人员一直是凭借着汇编和C的知识对大多数软件进行逆向工程的,但是,现在随着越来越多的应用软件和恶意程序转而使用C++进行开发,深入理解C++面向对象方式开发的软件的反汇编技术就显得越发的...
- **资料稀缺**:目前市场上关于C++逆向工程的资料较少,逆向工程师需要更多实践经验和系统学习。 #### II. 手工方法 - **目标**:识别C++程序中的类、构造函数、析构函数、成员变量及类间关系。 ##### A. 识别...
提供c++ 及其逆向的相关知识,供破解人使用
1. **创建新系统**: 使用逆向工程工具(如GDPro)对C++代码进行逆向处理时,会创建一个新的系统模型。 2. **生成视图**: - **类模型视图**: 描述了各个类的结构及其相互之间的关系。 - **实现模型视图**: 描述了...
1. **恶意软件增多**:根据实践经验,C++编写的恶意软件越来越多。例如,Agobot病毒就是用C++编写的,这增加了逆向分析的难度,尤其是在处理虚函数调用时。 2. **现代应用程序的发展**:随着操作系统和应用程序规模...
### 逆向C++关键技术详解 #### I. 引言和必要性 随着现代软件开发技术的发展,C++因其高效性和强大的功能成为了许多高级应用程序和恶意软件开发的首选语言。因此,对于逆向工程师而言,掌握针对C++编译代码的分析...
c++逆向翻译的编程资料,pdf格式,本编程书籍试图通过分析在反汇编时如何手工识别C++对象,进而讨论如何自动完成这一分析过程最终介绍我们自己开发的自动化工具,一步一步的帮助阅读者掌握逆向C++程序的一些方法。
同时,附带的"reverse c++.pdf"很可能是文章的全文,通过阅读这份文档,读者可以深入学习到C++的底层细节,以及如何利用这些知识进行逆向分析。 总的来说,逆向C++是一个涵盖广泛的主题,包括C++对象模型、内存管理...
### 逆向C++编程与反汇编技术详解 #### I. 引言与必要性 随着技术的发展,越来越多的应用程序和恶意软件采用C++语言编写。因此,掌握C++程序的逆向工程技术变得尤为重要。逆向工程师们长期以来依赖汇编语言和C语言...