C语言与汇编语言对照分析

游戏通常会包含各种各样的功能,如战斗系统、UI渲染、经济系统、生产系统等,每个系统又包含各式各样子功能,如伤害判定、施法、使用道具、角色移动、玩家之间交易等等。这些游戏功能在代码实现中往往少不了条件判断(如伤害判定)、循环(遍历物品列表,播放游戏动画)等。

在逆向过程中如果可以从汇编语言识别出对应的语法结构,在分析过程中将汇编代码转换为C语言语法结构,可以帮助对程序执行流程的理解。

下面分别介绍最常见的逻辑语法结构:

a) if...else

b) switch...case

c) for、while

注:文中使用的反汇编工具为IDA

一、 if...else

汇编代码:

if...else结构比较固定,通常包含cmp指令、jcc指令以及满足条件后执行的指令块。

if...else结构可以串联,串联后的if...else有明显的代码块边界,逆向工具通常可以将代码块标识出来(图中虚线)。

二、 switch...case

  1. 一个简单switch...case

  

汇编代码:

上图显示了switch...case基本的结构:a) 跳转表达式;b) 分支代码;c) 跳转表

a) 跳转表达式

其中loc_401235代码块对应switch...case中default分支。

当nGameEvent > 4时,跳转到loc_401235代码块,即default分支。

当nGameEvent <= 4时,根据跳转表达式进行跳转:

jmp     ds:off_40123C[nGameEvent*4]

其中off_40123C为跳转表地址,跳转表中每一项代表一个32位地址(4个字节),当nGameEvent为0按第一项地址跳转,当nGameEvent为1按第二项地址跳转,依次类推。

b) 分支代码

各个分支的处理逻辑都在这里,示例代码中仅仅简单的调用对应函数。
(PS:这里用jmp而不用call是编译器优化的结果)

c) 跳转表

跳转表实际是一个地址数组,存放了每个跳转分支的地址(32位绝对地址),当nGameEvent为0时,跳转表达式读取数组中第一项数据(0x0040121C),即

.text:0040121C E9 8F FF FF+     jmp     [email protected]@YAXXZ

调用DoLogin函数。

(PS: 实际运行时,由于随机化基址,从调试器看到的跳转表内容可能与静态分析时不同,这是重定位引起的,关于重定位的原理可以参考相关文档,这里不再详述)

  1. 不连续的switch...case

上面的示例中case的值是连续的,因此跳转表比较规则。在实际使用中可能会遇到不规则的case值,如下图:

汇编代码:

上面的代码有两个特点:

  1. 最小case值非0
    上图中最小case值为3,为了不浪费跳转表空间,编译器会将索引值减去3保证最小的case值对应跳转表中的第一项。

  2.case值不连续
  编译器会在跳转表间隔中插入default跳转,保证逻辑正确。(以空间换取时间)

  3.双重跳转表

汇编代码:

相对于前一个示例,此处case值间隔更大。如果按照之前的方法,跳转表的大小需要(110-30 + 1)* 4 = 324字节,占用内存空间大。

编译器为了节省空间,使用了双重跳转:跳转表、间接跳转表。其中跳转表与之前介绍的跳转表一致,而间接跳转表保存的不是分支地址,而是索引值,指向跳转表中的索引。

跳转表:

间接跳转表:

在进入switch...case时,先算根据间接跳转表获得索引号,再根据索引号查找跳转表,获取实际分支地址。

使用双重跳转表后,实际占用空间:5*4 +(110 – 30 + 1)= 101字节,大大减少空间占用。

  1. swtich...case退化

当case值间隔过大,使用跳转表、双重跳转表消耗的空间太大,编译器会将switch...case退化为if...else,如下图:

汇编代码:

这里没有跳转表结构,只剩下cmp/jcc指令,可见编译器已经将swtich...case转换为等价的if...else。但在转换过程中,编译还是做了力所能及的优化:通过二叉查找法加快跳转分支的查找。

  1. 嵌套switch...case

汇编代码:

可以看出嵌套的switch...case结构在汇编代码上是相对独立的,外层和内层switch结构有各自的跳转表。

外层跳转表:

内存跳转表(双重跳转表):

根据跳转表中的地址项,也可以清楚的区分外层和内层的跳转分支。

三、 循环语句

a) for循环

汇编代码:

其中nop dword ptr[eax+00h] 为指令对齐,没有实际意义。循环的汇编实现为:

b) while循环

汇编代码:

其中nop dword ptr[eax+eax+00h] 为指令对齐,没有实际意义。循环的汇编实现为:

从上面可以看出,for和while结构的汇编实现几乎一摸一样,仅仅是使用的寄存器有些区别。实际逆向过程中将循环映射为for或者while结构都是可以的。同时还可以看出,循环有个明显的特征:往回跳转(向地址小的方向跳转),大部分情况下遇到往回跳转的指令就是循环,极少数如编译器代码结构优化生成的往回跳转不是循环除外。

四、 总结

语法结构对应的汇编代码与编译器有很大关系,同一份源代码不同编译器生成的汇编代码结构不一样;即使是同一个编译器,不同的编译选项生成的汇编代码结构也不尽相同。需要在逆向过程中慢慢熟悉编译器的特性。

*转载请注明来自游戏安全实验室(GSLAB.QQ.COM)

原文地址:https://www.cnblogs.com/nothx/p/8537367.html

时间: 2024-08-09 16:22:59

C语言与汇编语言对照分析的相关文章

浅谈单片机中C语言与汇编语言的转换

做了一单片机设计,要用C语言与汇编语言同时实现,现将这次设计的感受和收获,还有遇到的问题写下,欢迎感兴趣的朋友交流想法,提出建议. 单片机设计:基于51单片机的99码表设计 软件环境:Proteus8.0 + Keil4 要求:1,开关按一下,数码管开始计时.2,按两下,数码管显示静止.3,按三下,数码管数值清零. C语言程序如下: 1 #include<reg51.h> 2 #define uint unsigned int 3 #define uchar unsigned char 4 u

C语言与汇编语言相互调用原理以及实例

下面两个分别是一个foo.asm(汇编语言文件),bar.c(c语言文件) 首先来了解C语言为什么能调用汇编语言,以及汇编语言为什么能调用C语言.其实不管是C语言还是汇编语言想要执行都是最终编译链接成为二进制文件. 注意是编译链接这个两个步骤,编译产生的并不是可执行的二进制文件,链接之后才是可执行的二进制文件. 这里一定要明确编译和链接是两个步骤,生成的文件格式也是不一样的. 编译生成的文件是一定格式的,里面包括函数符号表.参数表...等信息,这些信息主要是提供给链接阶段使用,函数调用是怎么调用

汇编语言的寻址方式与C语言中的指针是一个东西。

汇编语言的寻址方式与C语言中的指针是类似的! 汇编语言的寻址方式与C语言中的指针:寻找数据的方法. 指针就是存了 变量的地址,寻址方式就是得到保存变量的地址. 当你学了汇编语言,你就知道为什么C语言有函数指针,为什么函数要以return 结尾.为什么main()函数为程序的入口. 给我的感觉就是C语言是汇编语言的进化版本.因为C语言处处都有着汇编语言的影子. 一个小小的建议:先学习汇编语言.自学书籍是 清华大学 王爽的 汇编语言.之后,学习C语言,自学书籍,我还真不好推荐,我用的是C语言入门经典

程序设计语言基本概念语 与经典真题

一.基本概念 在计算机中,程序设计语言可划分为低级语言和高级语言两大类,与高级语言相比,用低级语言开发的程序,其运行效率高,但开发效率低.与程序设计相关的基本概念如下. (1)低级语言:又称面向机器语言,它是特定的计算机系统所固有的语言. (2)汇编语言:是机器语言的一种提升,它使用了一些助记符来表示机器指令中的操作码和操作数.但它仍然是一种和计算机机器语言十分接近的语言,使用起来仍然不太方便. (3)高级语言:与人们的自然语言比较接近,使用起来很方便,也极大的提高了程序设计效率. (4)编译程

windows下gcc开发c语言环境配置

工具准备:(配置windows) 1.安装qt5 2.在window系统的path路径中增加化境变量: C:\Qt\Qt5.6.2\5.6\mingw49_32\bin C:\Qt\Qt5.6.2\Tools\mingw492_32\bin 3.打开命令窗口,出入gcc –v,可以查看版本信息 4.打开qt生成一个空的项目,编译后程序可以运行,表示qt配置成功. 工具准备:(配置linux) linux自带gcc编译器. Helloworld程序的结构: #include "stdio.h&qu

[Linux内核分析第一周课程] 由C语言程序的汇编表示观察CPU寄存器与内存的互动

孟宁<Linux内核分析>第一周实验 作者:Zou Le 原创作品转载请注明出处. 课程信息: <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ---------------------------实验正文--------------------------- 本实验在实验楼64位LIinux虚拟机下进行. C代码如下: int increment5(int x) { return x + 5; }

用C语言编写简单的病毒

[摘要]在分析病毒机理的基础上,用C语言写了一个小病毒作为实例,用TURBOC2.0实现. [Abstract] This paper introduce the charateristic of the computer virus,then show a simple example written by TURBOC2.0. 一.什么是病毒        恶意软件可能是第一个对我们产生影响的计算机安全问题.所以病毒在信息安全中是非常重要的.    我们要对付病毒,就要了解病毒.    写一

对汇编语言第4周的总结反馈

汇编语言程序设计课到第4周.这周的课后总结中,让同学们写下"我的疑惑".这倒好,真引出了一大堆好问题,其实,这也就是大家学习的最真实情况. 为同学们的解答公布如下.下一周,老贺不做这种一对多的事情了,我们开启互评模式,靠同学们的多对多,完成更有意义的深入交流. 学号 学生小结 老师点评 (14)1147 我得到的知识: loop指令和[bx]指令的运用及其相关的内容,还有源程序文件.目标文件和可执行文件的相互转换.我得到的技能: 用editplus去编写源程序,使用masm进行编译.我

C语言函数参数压栈顺序为何是从右到左?(从左向右的话,碰到printf的会陷入死循环)

上学期学习了汇编语言,并在操作系统实验中使用了汇编+C语言混合编程,中间也了解了一些C语言与汇编语言的对应关系. 由于汇编语言是底层的编程语言,各种函数参数都要直接控制栈进行存取,在混合编程中,要用汇编来调用C函数,当然就要知道参数的压栈情况了. 当知道C函数的参数压栈顺序是从右到左时,我觉得很奇怪,因为大多数情况下,人们的习惯是从左到右的,难不成设计者学咱们中国古代写字从右到左的习惯不成? 当时只是记下了这个规则而已,并没有去探究这其中的缘由,后来在实验中自己用汇编实现了printf和scan