葛强 唐慧丰 王磊 余文涛 王博
摘要:针对目前汇编语言程序设计课程教学中存在的问题,提出将逆向分析引入汇编语言程序设计教学中,通过一个教学实例,阐述结合逆向分析教学法的汇编语言程序设计教学过程。
关键词:教学;逆向分析;结合;汇编语言程序设计
引言
汇编语言程序设计是高等院校计算机及相近专业的主要核心课程之一,具有综合性、实践性的特点。汇编语言与计算机硬件及操作系统都是紧密相关的,因此学好这门课程对于学生深入理解计算机的工作原理、掌握程序设计方法等知识,具有重要作用。
1汇编语言教学现状
汇编语言以指令系统为核心,分别使用助记符、符号或符号地址来表示操作码、操作数或操作地址,是面向机器的语言。实践教学发现,由于汇编语言语句低级、概念多且抽象、可读性差,所以往往使学生感到枯燥乏味。总体而言,汇编语言程序设计教学中主要存在如下问题。
(1)学生学习方法不当。与高级语言具有很好的可读性和逻辑性不同,汇编语言指令琐碎,涉及相关硬件的知识点太多,而且汇编语言程序设计非常重视基础,学生必须理解基本知识点,才能进行后续知识的学习。如果任课教师引导失误,学生没有理解基础知识,而是采取死记硬背汇编指令的方法,那么结果往往导致学生学习效果不佳。
(2)教师教学手段存在缺陷。部分教师在教学中没有突出重点,缺乏与学生之间的互动,没有调动学生的积极性和主动性;部分教师的教学课件缺乏生动性和趣味性,没有能够将知识点形象、直观地呈现出来;同时汇编语言程序设计课程是实践性很强的课程,部分教师往往对这一点认识不足。
2结合逆向分析的教学方法
针对目前汇编语言程序设计教学现状,笔者提出了结合逆向分析的汇编语言程序设计教学方法,将逆向分析引入汇编语言程序设计课堂教学中,利用逆向分析单步跟踪,深入观察硬件细节的特点,将汇编语言程序设计课程中的知识点进行分解细化,直观地展示出来,便于学生理解。
该教学方法的具体做法是:针对某个知识点,首先讲解其原理,然后列举一个有助于理解该知识点的汇编源程序,利用逆向分析软件分析该源程序的可执行文件,通过单步分解程序的执行过程,让学生观察寄存器、堆栈以及代码和数据段的变化,使得学生能够深入理解该知识点的底层机制和内部原理。通过逆向分析,帮助学生总结该知识要点,加深对该知识点的理解,达到融会贯通的效果。
教学实践显示,结合逆向分析的教学方法具有如下优点:逆向分析可以使汇编语言程序设计的课程更加生动、直观,可以提高学生的学习兴趣;通过逆向分析观察汇编语言程序的运行机制,能够让学生深刻理解知识点,达到事半功倍的效果;通过逆向分析,可加深学生对于汇编语言及计算机硬件系统的理解。
3结合逆向分析的教学实践
3.1逆向分析方法及工具
逆向分析即对目标程序进行反向推导,分析其体系结构以及运行过程的细节。结合逆向分析的汇编语言程序设计教学方法,即利用逆向分析工具对汇编源程序的可执行文件进行逆向分析,通过对指令、堆栈、寄存器等的变化情况进行分析讲解,帮助学生直观认识相关知识点加深理解。
我们采用的逆向工具为OllyDbg。OllyDbg是一款具有可视化界面的调试器,操作简单,界面布局清晰,便于初学者操作。OllyDbg功能强大,在汇编语言程序设计教学过程中,只须让学生掌握单步跟踪,通过单条指令的执行,观察代码区、数据段、寄存器以及堆栈的变化,了解该条汇编指令的功能,加深理解。
3.2教学方案实例
3.2.1寻址方式
存储器寻址方式主要有三种,即直接寻址、寄存器间接寻址、寄存器相对寻址,概念比较多,不易掌握。
教学过程中首先对这三种寻址方式的原理进行讲解。直接寻址,有效地址只有位移量部分,且直接包含在指令代码中。寄存器间接寻址有效地址存放在寄存器中,通过寄存器间接寻址存储器操作数。寄存器相对寻址的有效地址是寄存器内容与位移量之和。
为了让学生更好地理解上述三种寻址方式的本质以及区别,我们构造下列三段源程序,并进行逆向分析教学,让学生直观地比较这三种寻址方法的不同。
(1)直接寻址方式
;数据段
bvar byte 12h,34h ;定义字节型变量bvar
dvar dword 12345678h ;定义双字型变量dvar
;代码段
mov eax,dvar
(2)寄存器间接寻址
;代码段
mov ebx,offset dvar ;变量dvar的地址传送至ebx
mov eaX,[ebx] ;ebx指向的内存地址的内容传至eax
说明;数据段与直接寻址方式中定义的数据相同。
(3)寄存器相对寻址
:代码段
mov ebx,offset bvar :变量bvar的地址传送至ebx
mov eax,dword ptr[ebx+2] ;ebx加2得到的有效地址中的内容传送至eax
说明:数据段与直接寻址方式中定义的数据相同。
上述三段源程序定义了相同的变量,代码段中的指令表示对数据段中定义的变量dvar进行寻址,并将dvar的值传送给寄存器eax。上述三段代码采取的寻址方式分别为直接寻址方式、寄存器间接寻址方式以及寄存器相对寻址方式。
为了让学生直观地感受这三种寻址方式的异同,将这三段代码分别汇编链接生成可执行文件,并用OllyDbg加载进内存,对每个可执行文件汇编指令单步运行,让学生注意观察寄存器eax的值。图1、图2、图3分别为三种寻址方式寻址过程的逆向分析结果。
上述三幅图显示,寻址结束后寄存器eax的内容均是dvar变量的值12345678h。通过逆向分析可以直观地看出这三种寻址方式的差异。
图1显示直接寻址的过程:将变量dvar的内存地址存储的内容(即变量dvar的值)直接传送给eax寄存器。变量dvar的内存地址包含在指令中。
图2显示寄存器间接寻址的过程:首先将变量dvar的内存地址传送至寄存器ebx,接着将ebx所指向的内存地址中存储的内容传送给寄存器eax。
图3显示寄存器相对寻址的过程:将变量bvar的内存地址传送至寄存器ebx,将寄存器ebx所指向的内存地址加2,并将该内存地址中存储的内容传送至寄存器eax。变量bvar和dvar的存储空间是按照定义的先后顺序一个接一个分配的,通过逆向分析可以看出变量dvar的内存地址比变量bvar的地址高2个字节。所以变量bvar的内存地址加2就是变量dvar的内存地址。
3.2.2循环程序结构
循环结构的程序是汇编语言程序设计的一个重点,其中循环控制部分是编程的关键和难点。汇编语言中最主要的循环指令是LOOP,该指令执行的基本原理是使用ecx寄存器作为循环计数器。每执行一次循环体指令,ecx的值减1,并判断ecx是否为0,如果为0,表示循环结束,顺序执行循环体的下一条指令,否则返回该循环体的开始处继续执行。
为了让学生加深对LOOP循环指令的理解,我们构造一个循环程序,并进行逆向分析教学。该程序实现将源字符串srcmsg的内容传送给目的字符串dstmsg。部分代码如下:
;数据段
srcmsg byte‘Hello,Assembly!,0
dstmsg byte sizeof srcmsg dup(?)
;代码段
xorebx,ebx
mov ecx,sizeof srcmsg
;ecx=字符串字符个数
copy:
mov al,srcmsg[ebx] ;取srcmsg字符串一个字符送至al
mov dstmsg[ebx],al ;将al传送至dstmsg
inc ebx;加1,指向下一个字符
loop copy;字符个数ecx减去1,并判断是否为0,不为0,跳至copy处执行
我们将包含上述代码的程序汇编链接生成可执行文件,用OllyDbg加载该可执行文件,进行单步分析,并注意观察eCX寄存器的变化,如图4所示。我们可以观察到当执行00401014h处的循环指令时,eex寄存器值减1,同时返回到地址为00401007h循环体开始的指令(源程序copy标号处),继续执行循环体。当ecx寄存器值减少至0,循环结束,顺序执行下一条指令。
图5数据段逆向分析显示,变量srcmsg在存储器的地址为00405000h,变量dstmsg在存储器的地址为00405010h。程序单步执行过程中,可以清楚地观察到存储器地址00405000h中的数据依次复制传送到存储器地址00405010h的过程。
为了让学生所学的知识融会贯通,增强学习兴趣,可以将上段代码中控制循环的loop.copy指令改为如下图所示的两条指令:
dec ecx;ecx减1
jnz copy;判断,ecx的结果是否为0,如果不为0,则跳转copy处执行,否则顺序执行下条指令。
程序的其他部分与上段代码相同。
将更改后的源程序汇编链接生成新的可执行文件,并用OllyDbg进行逆向分析。逆向分析结果显示:单步执行过程中,寄存器ecx及循环体执行情况以及数据段变量的变化情况与原可执行文件是相同的。逆向分析的结果验证了更改后的两条指令与LOOP指令的功能是一致的。
在具体教学过程中,可以让学生动手实践逆向分析过程,增强对该知识点的直观认识。
3.2.3模块化程序设计
当程序功能复杂、所有语句写到一起时,程序结构显得零乱。由于汇编语言功能简单,源程序更显得冗长,这对于维护程序非常不利,所以编写功能复杂的程序时,常会编写功能相对独立的程序段,作为一个相对独立的模块供程序使用,这就是模块化程序设计。
模块化程序设计是汇编语言程序设计课程中的一个难点。子程序可以实现源程序的模块化,简化程序结构。为了向学生清晰讲述模块化程序设计的原理,笔者在课堂上构造了一个包含子程序的源程序。下面为该模块化程序的部分程序片段。
start:
mov eax,0
mov ecx,3
call sum;调用子程序sum
SHill proc;定义子程序sum
s:addeax,2
.
100ps;循环求和
ret;子程序返回
slim endp;子程序结束
end start
该程序是典型的模块化程序设计,功能比较简单,实现了求3个2相加的和。程序的基本结构是子程序sum实现了求和的循环过程,主程序通过call surll指令调用该子程序。
汇编链接上述代码,并将生成的可执行文件用OllyDbg进行逆向分析,让学生进一步观察主程序调用子程序的具体过程及堆栈的变化情况。
通过如图6所示的逆向分析过程,我们可以看出主程序通过call指令调用sum子程序的具体执行过程:程序将call指令的下条指令的地址(地址为0040101fh)压入堆栈中,然后跳转到子程序sum的首条指令add eax,2处执行。子程序执行完毕后,程序由子程序返回主程序。
为了加强学生对主程序调用子程序的call指令的理解,我们可以让学生思考是否可以用其他汇编指令来替代call指令所包含的两步操作:将call指令的下条指令压栈;跳转到子程序。答案是肯定的,我们可以将上段代码的call指令替换成如下两条指令:
push$+7;$为求当前指令地址的操作符。因为“push$+7”和“jmp sum”指令的机器码;长度为7个字节,因此push$+7指令将“impsum”的下条指令的地址压入堆栈。
jmp sum;跳转到子程序sum
其他指令不变。将新的源程序汇编链接生成可执行文件,并用OllyDbg逆向分析,其逆向分析结果如图7所示。
从图7可以看出,这两条指令实现了call指令同样的操作,分别将指定的指令地址压人堆栈,并且跳转到子程序开始处。
子程序返回主程序由ret指令实现。ret指令同样可以依据上述方法更改为由其他指令代替。由于篇幅有限,这里不再展开讨论,可以留作课后习题让学生自己更改。