最近常遇到的尴尬是看着汇编代码却无法在脑中反应出正确的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++逆向学习三步走》PDF精编版 v0.1(20100918) 《C++逆向学习三步走》是A1Pass精心打造的一个系列教程,每个章节都经过笔者深思熟虑与读者的层层考验,是初学逆向工程优秀自学参考资料。 此系列教程目前并没有...
C++逆向学习三步走
5.逆向C++(中文版).pdf
这些年来,逆向工程分析人员一直是凭借着汇编和C的知识对大多数软件进行逆向工程的,但是,现在随着越来越多的应用程序和恶意软件转而使用C++语言进行开发,深入理解C++面向对象方式开发的软件的反汇编技术就显得...
逆向C++ 简明资料,易学易懂,对需要了解学习逆向C++很有帮助!
欢迎光临A1Pass为您打造的逆向工程学习的神秘世界,我们将由这个大门开始逐渐了解逆向工程这个看似神秘的领域。当你踏入这扇大门的时候,你将发现自己变得不在与以前相同了,我在这里将于各位读者一齐经历这个化茧为...
这些年来,逆向工程分析人员一直是凭借着汇编和C 的知识对大多数软件进行逆向工程的,但是,现在随着越来越多的应用程序和恶意软件转而使用C++语言进行开发,深入理解C++面向对象方式开发的软件的反汇编技术就显得...
这些年来,逆向工程分析人员一直是凭借着汇编和C的知识对大多数软件进行逆向工程的,但是,现在随着越来越多的应用软件和恶意程序转而使用C++进行开发,深入理解C++面向对象方式开发的软件的反汇编技术就显得越发的...
提供c++ 及其逆向的相关知识,供破解人使用
很好的介绍了c++的逆向,对理解C++的一些内部机制也有帮助,不错的资料!
Reverse C++ (逆向 C++) 完整PDF文字版,原版的水印被我去掉了
c++逆向翻译的编程资料,pdf格式,本编程书籍试图通过分析在反汇编时如何手工识别C++对象,进而讨论如何自动完成这一分析过程最终介绍我们自己开发的自动化工具,一步一步的帮助阅读者掌握逆向C++程序的一些方法。
C++逆向工程的基础知识以及一些工具介绍等,是逆向工程入门的不错材料
这是一个针对广大初学者的逆向C++编程,你懂得 为了能从二进制可执行文件中把类识别出来,我们必须先要理解这些类的实例——对象是怎样被创建的。因为这个创建过程在汇编级别上具体是怎样实现的会给我们在反汇编时...
软件破解技术_逆向C++电子书 ,好东东。。
c++逆向工程源代码