如何查看程序的入口:使用VC6自带的堆栈调用窗口
F7生成可执行文件—F9设置断点—再F5运行到断点处停下
点击View—Debug Windows–Call Stack,打开堆栈调用窗口
可以看到有一个叫KERNEL32的东西(先不管是什么,后面会学),它调用了mainCRTStartup()函数,而这个函数又调用了我们写的代码程序的入口main
所以一个程序的真正的入口函数其实是mainCRTStartup(),这个入口不要轻易的修改,因为这个函数做了很多初始化工作,如果修改了这个入口后面的程序会出现很多问题
那么如何修改程序入口呢?
Project—Settings—Link—选择Category为Output—在Entry-point symbol中填写想要修改的入口函数
那么**mainCRTStartup()**函数做了哪些初始化工作:
//调用了下面特定功能的函数
GetVersion() //获得当前操作系统的版本
_heap_init() //初始化堆的空间大小
GetCommandLineA() //获取命令行的参数
_crtGetEnvironmentStringsA() //获取环境变量
_setargv() //等等
_setenvp()
_cinit()
上述初始化的东西都是控制台要用到的东西
我们使用堆栈调用窗口双击mainCRTStartup()函数,进去看一下它调用的main函数是怎么定义的
可以发现main函数有三个参数,所以如果我们想找程序入口,先找到GetVersion() 、_heap_init() 、
GetCommandLineA() 、_crtGetEnvironmentStringsA() 、_setargv()、_setenvp() 、_cinit()中的任意一个函数,那么再在它们的后面找,找有三个参数的方法,如果找到了,就是main入口
现在用OD打开一个程序,OD会自动识别二进制,形成注释,所以可以在反汇编窗口中右侧看到注释,会显示那一行指令表示哪一个函数,只要看到了上述函数,再往后找,找有传入了三个参数的方法,虽然用push和add esp,x的方法判断参数个数不一定正确,但是多半都满足这个规则,所以如果找到了一个call指令前有三个入栈,且后面跟了一个add esp,0xC 来平衡堆栈那么多半调用的就是main函数,按回车跟踪地址就可以看到main函数中的内容
先进过一个jmp过度中转后,就可以看到这个函数就是我们写的main方法,当中就是我们在main中写的代码翻译过来的汇编指令
找到以后就可以开始分析当中调用的函数了:分析参数,跟踪进去分析功能等,如果看到几个call后面跟的相同的地址,那么说明一个函数被调用了好几次;还注意最后一个call上面的指令是add esp,48/cmp ebp,esp这个就是编译器自动调用的检查堆栈平衡的函数,就不用逆向分析了
先找到程序的main入口:我们知道在mainCRTStartup()程序入口函数做完初始化工作后,才会调用main函数,所以我们要找GetVersion(),_heap_init(),GetCommandLineA()等由mainCRTStartup()调用的函数,找到它们后,再往后找找,如果遇到一个call指令前压入了三个参数,且在这个call指令后平衡堆栈的add esp指令中的后面操作数为0xc,那么就表名这个call指令很可能就是调用了main函数
找到后回车call指令跟踪地址进去,先经过jmp跳转,再就可以看到main函数了,就可以开始对main函数分析还原它的代码了
由于main函数也是一个函数,所以开始也要保留栈底、提升堆栈、保留现场、填充堆栈
接着就可以看到main函数调用了第一个函数A,现在可以初步猜测这个函数一共有五个参数,由于参数是倒着从后往前存储的,所以第一个函数A的参数依次为1,3,4,6,7;且前两个使用寄存器存储,可以推断出使用的是**__fastcall调用约定**,接着让我们跟踪call指令进入第一个函数
在0x401128处设置断点,让程序执行到这里停下,接着F8执行到call指令,再按F7进入call,经过jmp中转,最终可以进入到第一个函数A中
前面几行还是做一些准备工作,只是多了一步保留ecx的工作,因为ecx中存储了参数的值,但是填充缓冲区时要使用到它,所以要先将它的值保存起来,ecx使用完后再将值重新恢复回去
再下面两行是使用__fastcall调用约定时,编译器会自动将用两个寄存器存储的参数值放入内存中,方便后面对参数的运算操作,所以这一行不能理解为函数A又定义了两个局部变量!!!
再往下分析这个函数内部又调用了一个函数Ason1,且用到了三个参数,参数依次为A函数前三个参数1,3,4(因为参数是倒着存);可以看到call指令下面没有平衡堆栈的操作,且存储参数的是内存,所以推测Ason1函数使用的是__stdcall调用约定;将参数依次入栈,接着进入call指令,经过jmp中转,进入到Ason1函数中
同样函数先做准备工作:保留栈底,提升堆栈,保留现场,填充缓冲区
接着下面三行就是Ason1函数的功能:1+3+4,将结果8存入eax中
接着就是函数的收尾工作:恢复现场,降低堆栈,并且返回。唯一注意的是这里使用retn 0xC来内平栈,所以结合起来判断,Ason1函数确实有3个参数
此时第一个函数已经调用完成,将它的返回值8存入缓冲区中,说明这个返回值在函数A中有一个新的局部变量c接着,即A函数定义了新的局部变量c并且赋值为8
接着进入Ason2函数中,经过jmp中转后,可以看到Ason2函数
同样的先做准备工作(这里不再赘述)
接着下面的两行就是Ason2函数的功能:1+3,将结果4存入eax中
接着就是函数的收尾工作,最后返回到main函数中,执行add esp,8指令,外平栈
并且将Ason2返回结果4存入A函数的缓冲区,则说明A函数定义了新的局部变量d来接Ason2的返回值4,即A中新的局部变量d赋值为4
可以看到下面又调用了一次Ason2函数,因为call指令后面的地址就是Ason2函数的起始地址和上一个call后面地址一样
第二次调用Ason2,结合push和add esp指令可以得知Ason2有两个参数,第一个参数为接收第一次Ason2的返回值4的变量c,第二个参数为接收Ason1的返回值8的变量d,接着进入到call指令中,经过jmp指令,可以看到Ason2函数
上面已经详细分析过Ason2函数了,这里我们只提它的功能:4+8,将结果12存入eax中,作为Ason2的返回值
接着就返回到A函数中,执行add esp指令来外平栈
可以看到后面的指令就是先是恢复现场
此函数就是编译器自动调用的检查堆栈是否平衡的函数,所以如果为了还原C代码不用跟踪
接着就是降低堆栈,并且函数返回,此处有一个retn 0xC表示内平栈,此时A函数就调用结束了,且第二次调用的Ason2返回值作为A函数的返回值
当A函数结束回到main函数时,将eax中的值存入了main函数的缓冲区,所以此时main中应该有一个局部变量m,来接收A函数返回值,即m赋值为12
接着看到下面又调用了一个函数B,函数B有两个参数,第一个参数t1为main中局部变量m,赋值为12;第二个参数为一个数字—0x0041F10C,注释后面写的是"%d"的ASCII码?(可能是一个地址编号,也可以是一个数而已,我们进入到B函数中看看此参数是如何被使用的,就知道了)
进入到B函数中
可以看到开始是保留栈底,然后提升堆栈,接着保留现场,再将B函数的第一个参数t1所存入地址编号赋给eax,则eax的值为0x0019FEDC,即[ebp+C]
可以看到此时将eax中的值存入[ebp-C]内存中,即将t1参数的地址编号存入内存中;
接着比较参数2(0x0041F10C)和0的大小,此时ZF标志位为0;
然后有一个jnz指令,如果z=0则跳转到0x0040B813这个地址
先停下!!!这个函数和我们学的不太一样,一般人怎么会把一个像地址编号的十六进制数放进去,不太像我们写的代码,所以不妨先进去看看里面是什么,可以看到注释一栏有很多信息,大概和报错信息很相像,如果我们把一些标志位改了营造一种错误的假象,让它进入报错信息的函数看看会有什么效果,我们发现最后会弹出一个框,所以和我们预想的一样,这也是一个处理错误的函数,具体是什么不清楚,但是目前来说不用我们分析。故直接跳过
我们跳过上面的类似于报错的函数(可能是编译器自己调用的),但是发现下面有一条**xor eax,eax
的指令**,这条指令会把eax寄存器中的值清零,所以main函数应该就是返回一个0值,那么我们分析出main函数应该有返回类型,为int型,最后有一句return 0;
。然后继续向下分析,先恢复现场;然后编译器又自动调用了一个检查堆栈是否平衡的函数,所以不需要我们分析;接着就是降低堆栈;最后函数返回,至此main函数的调用结束,分析结束
#include "stdafx.h"
//Ason1函数
int __stdcall Ason1(int j1,int j2,int j3){
//使用__stdcall调用约定
return j1 + j2 + j3;
}
//Ason2函数
int Ason2(int x,int y){
//使用c语言默认的__cdecl调用约定
return x + y;
}
//A函数
int __fastcall A(int i1,int i2,int i3,int i4,int i5){
//使用__fastcall调用约定
int c;
int d;
c = Ason1(i1,i2,i3); //8
d = Ason2(i1,i2); //4
return Ason2(c,d); //12
}
//代码执行入口
int main(int argc, char* argv[])
{
int m;
m = A(1,3,4,6,7); //12
return 0;
}
注意逆向的时候如果一个函数中嵌套了另一个函数,那么这个嵌套的函数必须写在这个函数的上面,不然编译器无法识别这个嵌套函数是否定义过
为什么此程序中会出现这样的一段指令,是干什么用的?
函数的局部变量是按照顺序从[ebp-4]开始往上存,即存储在缓冲区中;而函数的参数是在函数调用之前编译器就帮我们做了,而且是倒着存储入栈或者内存,所以取的时候取参数是从[ebp+8]开始往下取,堆栈中从上到下就是参数在函数中从左到右的顺序
cstring szNum;GetDlgItemText(IDC_EDIT1, szNum); double Num; Num = _ttol(szNum); 转化成长整型 Num = _tstof(szNum);转化成double 型转载于:https://www.cnblogs.com/xzh1993/p/4711362.html...
在过去的几十年中,SRAM领域已划分为两个不同的产品系列,快速和低功耗,每个产品都有自己的功能,应用程序和价格。使用SRAM的设备需要它的高速性或低功耗性,但不能同时兼顾两者。但是人们越来越需要具有低功耗的高性能设备,以便在依靠便携式电源运行时执行复杂的操作。这种需求由新一代医疗设备和手持设备,消费电子产品和通信系统及工业控制器推动,而这些都由物联网驱动。下面介绍一款可兼容IS63WV1288DBLL-8TLI的国产SRAM芯片EMI501HB08PM45I。EMI501HB08PM45I描述EMI5
Dijkstra 算法 Dijkstra算法用来解决单源最短路径问题,即给定图G和起点s,通过算法得到s到达其他每个顶点的最短距离。Dijkstra的基本思想是对图G(V,E)设置集合S,存放已经被访问的顶点,然后每次从集合V-S中选择与起点s的最短距离最小的一个顶点(记为u),访问并加入集合S。之后,令顶点u为中介点,优化起点s与所有从u能到达的顶点v之间的最短距离。这样的操作...
感觉资料好像不太多啊。ROS与SLAM入门教程-多线雷达(velodyne 16)BLAM 三维建图谷歌cartographer使用速腾聚创3d激光雷达数据进行三维建图【激光雷达】3D激光雷达传感器建图:速腾聚创、velodyne建图过程总结Robosense 激光雷达slam建图(1):在ROS上进行测试---------------------...
下面从折线图、曲面图和图形说明与定制三个方面说明MATLAB作图的一些方法与技巧,注意,文中出现的单引号在MATLAB中实际运行的时候,需要改为MATLAB认可的单引号。一、 作折线图1.plot函数 ①以x为横坐标y为纵坐标,作折线图 x = [1 2 3 4 5 6 7 8 9 10];y = [5 2 3 5 7 14 10 4 2 3];plot(x...
android设置view透明度的效果推荐textView.setBackgroundColor(Color.TRANSPARENT);第一种方法:在xml文件中设置背景颜色。Xml代码android:background="#ff6495ED">前两位表示透明度,后面依次为RGB,透明度从0到255,0为完全...
一、非递归def gcd_test_one(a,b): if a!=0 and b!=0: if a>b: a,b=b,a if b%a==0: return a gcd_list=[] for i in range(1,a): if b%i==0 and a%i==0: gcd_list.append(i)
操作系统什么是操作系统人与硬件的中介/桥梁操作系统的组成操作系统可以理解为一个鸡蛋蛋黄 内核 Linux内核 托瓦斯鸡蛋清 命令解释器(shelll)GNU -- bash鸡蛋壳 程序软件 千万程序员做出来的Linux发展史关于LinuxLinux基本图解什么是LinuxLinux是一种开放源代码的,类似于Unix的操作系统。Linux发展过程1969年Unix诞生 贝尔...
在项目中有一些用户想要实现 软件自动检测,调查了下,在AppStore中有一款能实现自动检测手机使用时长的软件 叫Moment,发现了这个软件可以自动启动,并检测用户的使用手机时长,就调查了下这个应用的ApiMoment调查结果调查目的Moment是否可持续后台,耗电情况,是否能自动启动,底层实现方式。应用介绍功能:实现应用长时间驻留后台。 实现应用在不...
public class TestActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test);}@Overrideprotected void onStart() { super.onSta
Refused to display because an ancestor violates the following Content Security Policy directive: "frame-ancestors 'self'
C++的指针是C++的基础也是它的核心内容,在学习指针时,我们需要对内存空间进行剖析和理解,了解内存,学习起指针我认为是有事半功倍的效果的。一、预备知识—程序的内存分配一个由c/C++编译的程序占用的内存分为以下几个部分 1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 2、堆区(heap) — 一