查看原文
其他

逆向工程 | C 语言之循环分支

计算机与网络安全 计算机与网络安全 2022-06-01

一次性进群,长期免费索取教程,没有付费教程。

教程列表见微信公众号底部菜单

进微信群回复公众号:微信群;QQ群:460500587



微信公众号:计算机与网络安全

ID:Computer-network

循环是在代码中应用频率最高的一种编程语法,例如用于复制字符串、遍历图片像素点等。


C语言的循环主要分为for、while与do-while 3种,但是当我们以逆向的角度来看待这些循环结构的时候,会发现其本质上只有一种,而且即便再次细分,也仅仅能分离出两种情况。


一、do-while循环


先看下面代码清单1。

代码清单1  do-while循环

以上代码应该比较容易理解,0x41是字母A的ASCII码,变量nNum的初始值是26,因此0x41+(26-nNum)配合着每次的nNum——,实质上就是一个从字母A到Z的打印过程。


现在让我们从Debug版反汇编代码的角度看看小Baby是怎么唱ABC歌的。

以上代码还是较容易理解的,让我们再看看Release版的反汇编代码。

通过以上代码我们不难看出,编译器直接将我们的代码优化为以下模式了:

聪明的编译器,直接看透了我们代码的本质,把我们稍显复杂的代码变得非常简单。但是这只是个巧合,编译器的真实意图是想将我们的减法运算尽可能地转换为加法运算,原因很简单,CPU本质上只会做加法运算,因此加法对于CPU来说是执行速度最快的计算。

我们现在可以看到的最大的特点就是一个有条件判断的向上跳转。因此可以这样理解:如果我们看到了一个判断分支的跳转是向上的,那么这必然就是一个循环。以下是总结的特点:

二、while循环


为了便于理解,我们将精力放在软件逆向上,因此下面这个示例代码与上面的几乎一样,就是换了种语法而已,具体如代码清单2所示。

代码清单2  while循环

代码清单3所示是上述代码的Debug版反汇编代码。

代码清单3  while循环的Debug版反汇编代码

除了在循环头部多了两条用于判断是否跳出循环的指令外,其他的与do-while循环如出一辙。那Release版的代码又会是怎样的呢?反汇编代码如下:

请不要怀疑代码错了,事实就是这样,do-while与while生成的Release版代码在这里看就是100%相同的。编译器很明显已经探测出了我们的循环判断用的是一个常量,因此不存在首次执行条件不匹配的情况。


当然,如果我们将判断条件改为一个变量,那么就是另外一番景象了。修改后的源码如代码清单4所示。

代码清单4  修改后的while循环

下面是其Release版的反汇编代码:

我们可以看出,用变量做判断条件很明显与常量不一样,而关于优化,很显然编译器只是单纯地将0x41+(26-argc)优化成了0x5B-argc。


到此,已经可以对while循环的反汇编特征做一个有效的总结了。

三、for循环


for循环与while循环本质上是一样的,唯一的不同在于for循环在循环体内多了一个步长部分。接下来我们一起看看for循环的反汇编代码。先看源码,如代码清单5所示。

代码清单5  for循环

接下来我们再看看Debug版的反汇编代码,如代码清单6所示。

代码清单6  for循环的Debug版反汇编代码

显然从循环语句这个集合来讲是do-while先诞生的,而后是while,最后才是for,这从一个侧面说明for应该是最“高级”的了。


从执行效率上看,代码最短且判断最少的就是do-while循环了。


但是事实又是如何呢,让我们一起看看Release版反汇编

由于本例中又使用了常量,并且基本逻辑也没有改变,因此这段代码与do-while、while一模一样,有疑问的朋友可以返回上面仔细观察一下,连地址都是一样的。


很明显,while与for都是以do-while为基础框架的,只不过是在里面加了一些小判断。为了让您更清晰地看到它们之间的异同,下面是一个变量版for循环的反汇编例子,如代码清单7所示。

代码清单7  判断值为变量的for循环

下面是它的Release版反汇编代码:

以变量为判断条件的for循环与while循环所生成的代码是完全相同的,包括地址都是一样的。下面是对Debug版for循环的一个反汇编特点的总结:

到此,我们应该可以做一个总结了,Debug版下3种循环各不相同,Release版下可总结如下:


当循环采用常量为判断条件时,相同逻辑的3种循环生成的代码完全相同。


当循环采用变量为判断条件时,相同逻辑的while与for生成的代码完全相同,而do-while则自成一格。


四、循环体的语句外提优化


只有在循环体内被重复操作,并且不影响其最终结果的语句,才会被编译器做外提优化处理。我们看看代码清单8。

代码清单8  循环体的语句外提演示代码

通过这段代码我们可以发现一处可以优化的地方,就是argc=(int)argv这条语句,很明显这条语句即便是被执行再多次其结果也不会发生任何变化。代码清单9可以很好地证实我们的想法。

代码清单9  循环体的语句外提的Release版反汇编代码

由代码清单9可知,循环体内很明显没有"argc=(int)argv;"的代码,这段代码被提到了外面,这就是编译进行的代码外提优化。

微信公众号:计算机与网络安全

ID:Computer-network

【推荐书籍】

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存