葛強 唐慧豐 王磊 余文濤 王博



摘要:針對目前匯編語言程序設計課程教學中存在的問題,提出將逆向分析引入?yún)R編語言程序設計教學中,通過一個教學實例,闡述結(jié)合逆向分析教學法的匯編語言程序設計教學過程。
關鍵詞:教學;逆向分析;結(jié)合;匯編語言程序設計
引言
匯編語言程序設計是高等院校計算機及相近專業(yè)的主要核心課程之一,具有綜合性、實踐性的特點。匯編語言與計算機硬件及操作系統(tǒng)都是緊密相關的,因此學好這門課程對于學生深入理解計算機的工作原理、掌握程序設計方法等知識,具有重要作用。
1匯編語言教學現(xiàn)狀
匯編語言以指令系統(tǒng)為核心,分別使用助記符、符號或符號地址來表示操作碼、操作數(shù)或操作地址,是面向機器的語言。實踐教學發(fā)現(xiàn),由于匯編語言語句低級、概念多且抽象、可讀性差,所以往往使學生感到枯燥乏味。總體而言,匯編語言程序設計教學中主要存在如下問題。
(1)學生學習方法不當。與高級語言具有很好的可讀性和邏輯性不同,匯編語言指令瑣碎,涉及相關硬件的知識點太多,而且匯編語言程序設計非常重視基礎,學生必須理解基本知識點,才能進行后續(xù)知識的學習。如果任課教師引導失誤,學生沒有理解基礎知識,而是采取死記硬背匯編指令的方法,那么結(jié)果往往導致學生學習效果不佳。
(2)教師教學手段存在缺陷。部分教師在教學中沒有突出重點,缺乏與學生之間的互動,沒有調(diào)動學生的積極性和主動性;部分教師的教學課件缺乏生動性和趣味性,沒有能夠?qū)⒅R點形象、直觀地呈現(xiàn)出來;同時匯編語言程序設計課程是實踐性很強的課程,部分教師往往對這一點認識不足。
2結(jié)合逆向分析的教學方法
針對目前匯編語言程序設計教學現(xiàn)狀,筆者提出了結(jié)合逆向分析的匯編語言程序設計教學方法,將逆向分析引入?yún)R編語言程序設計課堂教學中,利用逆向分析單步跟蹤,深入觀察硬件細節(jié)的特點,將匯編語言程序設計課程中的知識點進行分解細化,直觀地展示出來,便于學生理解。
該教學方法的具體做法是:針對某個知識點,首先講解其原理,然后列舉一個有助于理解該知識點的匯編源程序,利用逆向分析軟件分析該源程序的可執(zhí)行文件,通過單步分解程序的執(zhí)行過程,讓學生觀察寄存器、堆棧以及代碼和數(shù)據(jù)段的變化,使得學生能夠深入理解該知識點的底層機制和內(nèi)部原理。通過逆向分析,幫助學生總結(jié)該知識要點,加深對該知識點的理解,達到融會貫通的效果。
教學實踐顯示,結(jié)合逆向分析的教學方法具有如下優(yōu)點:逆向分析可以使匯編語言程序設計的課程更加生動、直觀,可以提高學生的學習興趣;通過逆向分析觀察匯編語言程序的運行機制,能夠讓學生深刻理解知識點,達到事半功倍的效果;通過逆向分析,可加深學生對于匯編語言及計算機硬件系統(tǒng)的理解。
3結(jié)合逆向分析的教學實踐
3.1逆向分析方法及工具
逆向分析即對目標程序進行反向推導,分析其體系結(jié)構(gòu)以及運行過程的細節(jié)。結(jié)合逆向分析的匯編語言程序設計教學方法,即利用逆向分析工具對匯編源程序的可執(zhí)行文件進行逆向分析,通過對指令、堆棧、寄存器等的變化情況進行分析講解,幫助學生直觀認識相關知識點加深理解。
我們采用的逆向工具為OllyDbg。OllyDbg是一款具有可視化界面的調(diào)試器,操作簡單,界面布局清晰,便于初學者操作。OllyDbg功能強大,在匯編語言程序設計教學過程中,只須讓學生掌握單步跟蹤,通過單條指令的執(zhí)行,觀察代碼區(qū)、數(shù)據(jù)段、寄存器以及堆棧的變化,了解該條匯編指令的功能,加深理解。
3.2教學方案實例
3.2.1尋址方式
存儲器尋址方式主要有三種,即直接尋址、寄存器間接尋址、寄存器相對尋址,概念比較多,不易掌握。
教學過程中首先對這三種尋址方式的原理進行講解。直接尋址,有效地址只有位移量部分,且直接包含在指令代碼中。寄存器間接尋址有效地址存放在寄存器中,通過寄存器間接尋址存儲器操作數(shù)。寄存器相對尋址的有效地址是寄存器內(nèi)容與位移量之和。
為了讓學生更好地理解上述三種尋址方式的本質(zhì)以及區(qū)別,我們構(gòu)造下列三段源程序,并進行逆向分析教學,讓學生直觀地比較這三種尋址方法的不同。
(1)直接尋址方式
;數(shù)據(jù)段
bvar byte 12h,34h ;定義字節(jié)型變量bvar
dvar dword 12345678h ;定義雙字型變量dvar
;代碼段
mov eax,dvar
(2)寄存器間接尋址
;代碼段
mov ebx,offset dvar ;變量dvar的地址傳送至ebx
mov eaX,[ebx] ;ebx指向的內(nèi)存地址的內(nèi)容傳至eax
說明;數(shù)據(jù)段與直接尋址方式中定義的數(shù)據(jù)相同。
(3)寄存器相對尋址
:代碼段
mov ebx,offset bvar :變量bvar的地址傳送至ebx
mov eax,dword ptr[ebx+2] ;ebx加2得到的有效地址中的內(nèi)容傳送至eax
說明:數(shù)據(jù)段與直接尋址方式中定義的數(shù)據(jù)相同。
上述三段源程序定義了相同的變量,代碼段中的指令表示對數(shù)據(jù)段中定義的變量dvar進行尋址,并將dvar的值傳送給寄存器eax。上述三段代碼采取的尋址方式分別為直接尋址方式、寄存器間接尋址方式以及寄存器相對尋址方式。
為了讓學生直觀地感受這三種尋址方式的異同,將這三段代碼分別匯編鏈接生成可執(zhí)行文件,并用OllyDbg加載進內(nèi)存,對每個可執(zhí)行文件匯編指令單步運行,讓學生注意觀察寄存器eax的值。圖1、圖2、圖3分別為三種尋址方式尋址過程的逆向分析結(jié)果。
上述三幅圖顯示,尋址結(jié)束后寄存器eax的內(nèi)容均是dvar變量的值12345678h。通過逆向分析可以直觀地看出這三種尋址方式的差異。
圖1顯示直接尋址的過程:將變量dvar的內(nèi)存地址存儲的內(nèi)容(即變量dvar的值)直接傳送給eax寄存器。變量dvar的內(nèi)存地址包含在指令中。
圖2顯示寄存器間接尋址的過程:首先將變量dvar的內(nèi)存地址傳送至寄存器ebx,接著將ebx所指向的內(nèi)存地址中存儲的內(nèi)容傳送給寄存器eax。
圖3顯示寄存器相對尋址的過程:將變量bvar的內(nèi)存地址傳送至寄存器ebx,將寄存器ebx所指向的內(nèi)存地址加2,并將該內(nèi)存地址中存儲的內(nèi)容傳送至寄存器eax。變量bvar和dvar的存儲空間是按照定義的先后順序一個接一個分配的,通過逆向分析可以看出變量dvar的內(nèi)存地址比變量bvar的地址高2個字節(jié)。所以變量bvar的內(nèi)存地址加2就是變量dvar的內(nèi)存地址。
3.2.2循環(huán)程序結(jié)構(gòu)
循環(huán)結(jié)構(gòu)的程序是匯編語言程序設計的一個重點,其中循環(huán)控制部分是編程的關鍵和難點。匯編語言中最主要的循環(huán)指令是LOOP,該指令執(zhí)行的基本原理是使用ecx寄存器作為循環(huán)計數(shù)器。每執(zhí)行一次循環(huán)體指令,ecx的值減1,并判斷ecx是否為0,如果為0,表示循環(huán)結(jié)束,順序執(zhí)行循環(huán)體的下一條指令,否則返回該循環(huán)體的開始處繼續(xù)執(zhí)行。
為了讓學生加深對LOOP循環(huán)指令的理解,我們構(gòu)造一個循環(huán)程序,并進行逆向分析教學。該程序?qū)崿F(xiàn)將源字符串srcmsg的內(nèi)容傳送給目的字符串dstmsg。部分代碼如下:
;數(shù)據(jù)段
srcmsg byte‘Hello,Assembly!,0
dstmsg byte sizeof srcmsg dup(?)
;代碼段
xorebx,ebx
mov ecx,sizeof srcmsg
;ecx=字符串字符個數(shù)
copy:
mov al,srcmsg[ebx] ;取srcmsg字符串一個字符送至al
mov dstmsg[ebx],al ;將al傳送至dstmsg
inc ebx;加1,指向下一個字符
loop copy;字符個數(shù)ecx減去1,并判斷是否為0,不為0,跳至copy處執(zhí)行
我們將包含上述代碼的程序匯編鏈接生成可執(zhí)行文件,用OllyDbg加載該可執(zhí)行文件,進行單步分析,并注意觀察eCX寄存器的變化,如圖4所示。我們可以觀察到當執(zhí)行00401014h處的循環(huán)指令時,eex寄存器值減1,同時返回到地址為00401007h循環(huán)體開始的指令(源程序copy標號處),繼續(xù)執(zhí)行循環(huán)體。當ecx寄存器值減少至0,循環(huán)結(jié)束,順序執(zhí)行下一條指令。
圖5數(shù)據(jù)段逆向分析顯示,變量srcmsg在存儲器的地址為00405000h,變量dstmsg在存儲器的地址為00405010h。程序單步執(zhí)行過程中,可以清楚地觀察到存儲器地址00405000h中的數(shù)據(jù)依次復制傳送到存儲器地址00405010h的過程。
為了讓學生所學的知識融會貫通,增強學習興趣,可以將上段代碼中控制循環(huán)的loop.copy指令改為如下圖所示的兩條指令:
dec ecx;ecx減1
jnz copy;判斷,ecx的結(jié)果是否為0,如果不為0,則跳轉(zhuǎn)copy處執(zhí)行,否則順序執(zhí)行下條指令。
程序的其他部分與上段代碼相同。
將更改后的源程序匯編鏈接生成新的可執(zhí)行文件,并用OllyDbg進行逆向分析。逆向分析結(jié)果顯示:單步執(zhí)行過程中,寄存器ecx及循環(huán)體執(zhí)行情況以及數(shù)據(jù)段變量的變化情況與原可執(zhí)行文件是相同的。逆向分析的結(jié)果驗證了更改后的兩條指令與LOOP指令的功能是一致的。
在具體教學過程中,可以讓學生動手實踐逆向分析過程,增強對該知識點的直觀認識。
3.2.3模塊化程序設計
當程序功能復雜、所有語句寫到一起時,程序結(jié)構(gòu)顯得零亂。由于匯編語言功能簡單,源程序更顯得冗長,這對于維護程序非常不利,所以編寫功能復雜的程序時,常會編寫功能相對獨立的程序段,作為一個相對獨立的模塊供程序使用,這就是模塊化程序設計。
模塊化程序設計是匯編語言程序設計課程中的一個難點。子程序可以實現(xiàn)源程序的模塊化,簡化程序結(jié)構(gòu)。為了向?qū)W生清晰講述模塊化程序設計的原理,筆者在課堂上構(gòu)造了一個包含子程序的源程序。下面為該模塊化程序的部分程序片段。
start:
mov eax,0
mov ecx,3
call sum;調(diào)用子程序sum
SHill proc;定義子程序sum
s:addeax,2
.
100ps;循環(huán)求和
ret;子程序返回
slim endp;子程序結(jié)束
end start
該程序是典型的模塊化程序設計,功能比較簡單,實現(xiàn)了求3個2相加的和。程序的基本結(jié)構(gòu)是子程序sum實現(xiàn)了求和的循環(huán)過程,主程序通過call surll指令調(diào)用該子程序。
匯編鏈接上述代碼,并將生成的可執(zhí)行文件用OllyDbg進行逆向分析,讓學生進一步觀察主程序調(diào)用子程序的具體過程及堆棧的變化情況。
通過如圖6所示的逆向分析過程,我們可以看出主程序通過call指令調(diào)用sum子程序的具體執(zhí)行過程:程序?qū)all指令的下條指令的地址(地址為0040101fh)壓入堆棧中,然后跳轉(zhuǎn)到子程序sum的首條指令add eax,2處執(zhí)行。子程序執(zhí)行完畢后,程序由子程序返回主程序。
為了加強學生對主程序調(diào)用子程序的call指令的理解,我們可以讓學生思考是否可以用其他匯編指令來替代call指令所包含的兩步操作:將call指令的下條指令壓棧;跳轉(zhuǎn)到子程序。答案是肯定的,我們可以將上段代碼的call指令替換成如下兩條指令:
push$+7;$為求當前指令地址的操作符。因為“push$+7”和“jmp sum”指令的機器碼;長度為7個字節(jié),因此push$+7指令將“impsum”的下條指令的地址壓入堆棧。
jmp sum;跳轉(zhuǎn)到子程序sum
其他指令不變。將新的源程序匯編鏈接生成可執(zhí)行文件,并用OllyDbg逆向分析,其逆向分析結(jié)果如圖7所示。
從圖7可以看出,這兩條指令實現(xiàn)了call指令同樣的操作,分別將指定的指令地址壓人堆棧,并且跳轉(zhuǎn)到子程序開始處。
子程序返回主程序由ret指令實現(xiàn)。ret指令同樣可以依據(jù)上述方法更改為由其他指令代替。由于篇幅有限,這里不再展開討論,可以留作課后習題讓學生自己更改。