探索黑客技术攻防,实战研究与安全创新

导航菜单

简单驱动开发及逆向之C语言代码逆向基础

在学习编程的过程中,需要阅读大量的源代码才能提高我们的编程能力,可是通常情况下,在使用软件的过程中会发现某个软件的功能实现的非常不错,希望该功能能够在自己的软件中实现,但又得不到它的源代码进行参考,遇到这种情况,通常能够做的就是通过反汇编工具进行逆向分析,或者借助调试工具进行调试分析。

何为逆向工程?简单的说就是将已经编译好的二进制程序通过反汇编分析来还原相应的源代码。本届就来介绍C语言代码的逆向基础知识,主要讲解C语言中流程控制码对应的反汇编代码。

函数的识别

做逆向分析的第一步是先要对函数进行识别,也就是说确定函数的开始位置、结束位置、函数调用方式及函数的参数个数,在逆向分析的过程中,不会把单个指令当做时最基本的逆向单位,因为一条汇编指令很难说明任何问题,就像在C语言中,很难通过一行代码来说明问题一样。

大多情况下是针对自己比较感兴趣的部分进行重点分析,分析部分功能或者部分关键函数。因此,确定函数的开始位置及结束位置就很重要了。不过通常情况下.函数的起始位置和结束位置都可以通过反汇编工具自动识别,从而省去了我们自己的识别。使用强大的反汇编工具IDA更是如此,不过在某些特殊情况下仍然需要我们自己去识别。简单介绍一下如何识别函数的开始与结束的位置。写一个简单的C语言代码,用VC6进行编译连接,再用IDa打开。C语言代码如下:

在程序中由主函数调用了自定义函数test0,test0函数的返回值为imt类型。在test0函数中调用了prit|fo函数和MessageBox0两个函数。逆向分析是在DEBUO编译方式下进行的(RELEASE方式留给大家自己去研究)用IDA打开我们编译连接好的程序文件。用IDA打开后,将直接找到main0函数(多么方便的工具).如果没有找到main0的话,通过简单的操作也能找到。下面先来通过运行时启动函数定位找到main0函数。

首先在IDA上单击选项卡中的“Exports”项,在这里可以看到mainC RTStartup,如图7-13所示。

双击manCRTStartup就可以运行启动函数了。记住main0函数不是程序运行的第一个函数,而是程序员编写程序时的第一个函数。main()函数是由运行时启动函数来调用的。启动函数部分反汇编代码如下

从反汇编代码中可以看到,main()函数在004012B4位置处,启动函数获得版本号、获得命令行参数、获得环境变量字符串.......等一系列的操作后才调用了MAIN()函数,由于使用的是调试版且有pdb文件,因此在反汇编代码中能直接显示出程序中的符号,通常情况是没有pdb文件的,这样_main是一个地址,而不是直接给出的符号了,不过仍然可以按照规律来找到主函数。

我们已经顺利找到了主函数,直接双击_main,到达如下反汇编代码处:

我们看到了4行0040100A,起始不是有4行,真正的哪一行是JMP MAIN,其余的都是IDA为了方便查看而生成的,这时发现这并不是我们编写的主函数,这是以DEBUG方式编译生成的一个跳表,双击MAIN处、就到了真正的主函数处了,代码如下:

观察反汇编代码就可以确定这是我们的主函数了,代码中有一个对TEST()的调用,也看到了printf(),下面介绍一个这段反汇编代码。

这是函数返回的时候固定格式,这个格式跟入口的格式基本是对应的,首先是POPEDI/POPESI/POPEBX,这里是将上面保存的几个关键寄存器的值进行恢复,然后是ADDESPXXX,这里是与SUBESPXXX对应的,将临时开辟的栈空间释放掉,MOVESPEBD/POPEBO是恢复栈帧,retn就返回到上层函数了,在该反汇编代码中还有一部没有讲,这是cmpebpesp/call_chkesp,这两句是对chkeso函数的一个调用,在DEBUG编译方式下对几乎所有的函数调用完成后调用一次chkesp,该函数的功能是用来检查栈是否平衡,以保证程序的正确,如果栈不平,会给出错误提示,我们做个简单的测试,在主函数后面加一条内链汇编asmpushebx,然后编译链接运行,在输出过后会看到一个错误的提示,如图7-14所示。

图片8.png

整个的主函数还有最后一部分,反汇编代码如下:

首先是pish6/pushoffsetahello/calljtest/addesp8/movlebp+van4,eax是对test函数的一个调用,pusheax/pushoffset/callprintf/addesp8是对printf()函数的调用,xoreaxeax是对eax进行清零,我们重点来看一下对test()函数的调用,双击jtext来到如下反汇编代码处:

这里仍然是个跳表,双击test来到如下反汇编代码处:

函数开头和结尾的部分我们都已经熟悉了,直接其他部分吧,反汇编代码如下:

另外一段反汇编代码如下:

从这两段代码中可以看出分别是对printf()函数和MessageBoxAO函数的调用,在对printf()函数调用后有一条add esp,OCh,而在MessageBoxA()后面则没有。这是为什么呢?这就是调用约定。在vc下常见到两种调用约定,分别是stdCall和cdecl两种。

stdcall是Windows下的标准调用约定,Windows提供的API函数及WDK中提供的函数都是使用stdcall,当然也有例外,就是API中变参函数是使用cdecl调用约定。C语言默认的是使用cdccl调用约定。

sridcall参数的入栈方式是从右往左,平衡栈是在被调用函数的内部。cdecl参数的入栈方式也是从右往左,平衡栈是在调用函数方。这就是两种函数调用的约定方式,没有好与不好,只是调用方式不同而已,唯一一点是stdcall不能支持变参函数。

if.....else.....分支结构

下面用一个相对简单的if......else....分支实例来说明其对应的反汇编,c代码如下:

代码跟简单,用IDA看其关键的反汇编代码:

以上3句分别是对3个变量进行赋值初始化。

观察上面的反代码发现,代码中使用的比较关联符号是大于号,而反汇编代码却是用了相反的比较跳转指令,使用的是小于等于则跳转,否则不跳转,请注意观察00401043和004010se这两个地址,jle会跳过紧接着的下面的部分代码,跳转的目的地址上面是一条无条件跳转指令jmp,也就是说jle和jmp之间的部分是代码中比较表达式成功后执行的代码,上面C源代码中if.........else.......对应反汇编结构如下:

这基本上就是IF......ELSE....分支结构的执行流程了,当反汇编时候遇到这样的结构就可以马上知道这是一个IF......ELSE....分支结构了,大的流程定义以后就可以具体分析每个分支,要执行的代码了。

switch分支结构

前面讲了IF......ELSE....的分支结构,接下来介绍SWITCH分支结构,该结构是一个比较有趣的结构,他的反汇编代码和我们想的反汇编代码不同,而且switch的反汇编形式特别的是丰富,这里只介绍他的其中一种反汇编形式,C语言代码如下:

我们来看以上的代码对应的反汇编代码:

Do.....while循环对应的反汇编结构如下:

下面来看一个while循环的代码:

再看其反汇编代码

整个反汇编代码与do....while类似,只是把判断放到前面了,while循环的结构如下:

对于FOR、do....while和while这3种循环来收,do....while的效率显得高一些了,编程时可以选择DO.......WHILE循环来使用,

对于C语言的逆向知识介绍这么多了,大家可以写一些C语言代码,然后用IDA来学习分析。