5.1 匯編語(yǔ)言:匯編語(yǔ)言概述
匯編語(yǔ)言是一種面向機(jī)器的低級(jí)語(yǔ)言,用于編寫(xiě)計(jì)算機(jī)程序。匯編語(yǔ)言與計(jì)算機(jī)機(jī)器語(yǔ)言非常接近,匯編語(yǔ)言程序可以使用符號(hào)、助記符等來(lái)代替機(jī)器語(yǔ)言的二進(jìn)制碼,但最終會(huì)被匯編器編譯成計(jì)算機(jī)可執(zhí)行的機(jī)器碼。
相較于高級(jí)語(yǔ)言(如C、Python等),匯編語(yǔ)言學(xué)習(xí)和使用難度相對(duì)較大,需要對(duì)計(jì)算機(jī)內(nèi)部結(jié)構(gòu)、指令集等有深入的了解,以及具有良好的編程習(xí)慣和調(diào)試能力。但對(duì)于需要對(duì)計(jì)算機(jī)底層進(jìn)行操作的任務(wù),匯編語(yǔ)言是極其高效的,因?yàn)槠淇梢詫?shí)現(xiàn)對(duì)計(jì)算機(jī)底層資源的精細(xì)控制,極大地提高了計(jì)算機(jī)運(yùn)行效率。
盡管在當(dāng)今計(jì)算機(jī)界已經(jīng)不再使用匯編語(yǔ)言來(lái)開(kāi)發(fā)程序,但作為一名安全從業(yè)者掌握匯編語(yǔ)言將會(huì)是高手與專(zhuān)家之間最大的差距,匯編語(yǔ)言作為底層語(yǔ)言,具有直接訪問(wèn)計(jì)算機(jī)硬件和系統(tǒng)資源的能力,因此在系統(tǒng)級(jí)漏洞挖掘、內(nèi)核安全、計(jì)算機(jī)反病毒等領(lǐng)域中具有非常重要的作用。
以下是關(guān)于匯編語(yǔ)言的應(yīng)用場(chǎng)景:
??系統(tǒng)級(jí)漏洞挖掘:許多系統(tǒng)級(jí)漏洞,如堆棧溢出、整數(shù)溢出等,都是由于程序員沒(méi)有理解底層操作系統(tǒng)和硬件的工作原理而導(dǎo)致的。因此,理解匯編語(yǔ)言可以幫助安全研究人員更好地了解底層的操作系統(tǒng)和硬件原理,從而更好地挖掘漏洞。
??內(nèi)核安全:匯編語(yǔ)言是編寫(xiě)內(nèi)核模塊或驅(qū)動(dòng)程序所必需的語(yǔ)言,例如,Linux內(nèi)核中的大部分代碼都是使用匯編語(yǔ)言實(shí)現(xiàn)的。因此,對(duì)于理解內(nèi)核原理和進(jìn)行內(nèi)核安全研究來(lái)說(shuō),掌握匯編語(yǔ)言非常重要。
??計(jì)算機(jī)反病毒:許多計(jì)算機(jī)病毒和惡意軟件都使用匯編語(yǔ)言編寫(xiě),因此掌握匯編語(yǔ)言可以幫助研究人員更好地理解這些惡意軟件的工作原理和行為,并提高反病毒軟件的捕獲率和準(zhǔn)確性。
總之,熟練掌握匯編語(yǔ)言對(duì)于進(jìn)行系統(tǒng)級(jí)漏洞挖掘、內(nèi)核安全研究、計(jì)算機(jī)反病毒等領(lǐng)域都非常有幫助。雖然匯編語(yǔ)言相對(duì)來(lái)說(shuō)比較底層和難以理解,但是深入掌握匯編語(yǔ)言將會(huì)極大地提高軟件安全研究人員的技能和水平,讓讀者從一個(gè)高手蛻變成一名安全專(zhuān)家。
本章中所提到的匯編語(yǔ)言為Windows
匯編,在Windows
平臺(tái)下讀者可使用MASM
工具對(duì)匯編語(yǔ)言進(jìn)行編譯測(cè)試,也可以使用通用的集成開(kāi)發(fā)環(huán)境實(shí)現(xiàn)編譯,筆者推薦使用RadASM工具,RadASM 是一個(gè)面向匯編編程的開(kāi)發(fā)環(huán)境,提供了一系列工具和功能,用于編寫(xiě)、調(diào)試和優(yōu)化匯編語(yǔ)言程序。該工具具有良好的可定制性和擴(kuò)展性,且能提供豐富的工具和功能,方便程序員進(jìn)行匯編語(yǔ)言的開(kāi)發(fā)和調(diào)試工作。
1.1 RadASM
當(dāng)讀者準(zhǔn)備好開(kāi)發(fā)環(huán)境后可打開(kāi)RadASM
工具,選擇文件新建工程按鈕,并選擇ConsoleApp
選項(xiàng)填入自定義工程名稱(chēng)并一直點(diǎn)擊下一步即可,當(dāng)讀者進(jìn)入到主頁(yè)面后會(huì)看到如下圖所示的窗體,其中最右側(cè)則是我們的項(xiàng)目目錄,該目錄下的Resources
則是我們需要測(cè)試代碼的地方,讀者可自行點(diǎn)開(kāi)*.asm
文件并在此處寫(xiě)代碼,當(dāng)讀者需要編譯代碼可使用快捷鍵Ctrl+Shift+V
快速構(gòu)建,也可點(diǎn)擊右上角的編譯構(gòu)建按鈕自行構(gòu)建;
使用Win32匯編語(yǔ)言做開(kāi)發(fā)其開(kāi)發(fā)感覺(jué)與高級(jí)語(yǔ)言基本一致,并沒(méi)有像大家想象中的那么困難,唯一的區(qū)別只是在高級(jí)語(yǔ)言中可以很容易實(shí)現(xiàn)的語(yǔ)句,而到了匯編語(yǔ)言這里將會(huì)變得較為繁瑣,讀者只要認(rèn)真理解匯編語(yǔ)言中的每一條指令所代表的含義,則同樣可以靈活的運(yùn)用匯編語(yǔ)言開(kāi)發(fā)大型項(xiàng)目,首先筆者來(lái)解釋一下關(guān)于上述圖片中代碼的具體含義;
根據(jù)上述代碼中第一行的定義.386p
代表了指令集的選擇,此處代表我們選用Intel 80386
處理器的指令集,其中的p
則代表將代碼對(duì)齊到32位指令上,接著看第二行.model flat, stdcall
此處代表了調(diào)用約定采用stdcall
模式,并設(shè)置代碼和數(shù)據(jù)段都使用平坦模型(flat model)來(lái)處理內(nèi)存,第三行option casemap:none
代表后續(xù)程序不區(qū)分大小寫(xiě),當(dāng)有了上述這三行定義后匯編語(yǔ)言的預(yù)定義部分也就結(jié)束了。
接著就是比較熟悉的定義語(yǔ)法了,這里的include/includelib
分別代表頭文件以及庫(kù)文件的引用,如果讀者需要調(diào)用Windows系統(tǒng)內(nèi)的函數(shù)定義,則此處的頭文件則是必須要包含windows.inc
以及kernel32.inc
頭的,此外還需要導(dǎo)入kernel32.lib
庫(kù)來(lái)完成頭文件功能的導(dǎo)入;
??.data:定義已初始化變量。該指令定義了一個(gè)16位的可賦值變量Main,并將其初始化為1024。
??.data?:定義未初始化變量。該指令定義了一個(gè)32位的未初始化變量lyshark。
??.const:定義常量。該指令定義了一個(gè)以0h(十六進(jìn)制)為結(jié)尾的字符串常量var1,內(nèi)容為“l(fā)yshark”。
??.code:代碼段開(kāi)始。該指令表示代碼段的開(kāi)始。
接下來(lái)就是main PROC
以及main ENDP
定義了,此處的定義部分讀者可理解為int main()
函數(shù),此處的功能同樣是定義主程序入口和結(jié)束,而當(dāng)我們需要編寫(xiě)應(yīng)用程序時(shí)只需要在上方不同的段內(nèi)填充參數(shù)即可,其開(kāi)發(fā)流程可以與高級(jí)語(yǔ)言一致。
1.2 匯編中的變量
MASM 定義了多種內(nèi)部數(shù)據(jù)類(lèi)型,每種數(shù)據(jù)類(lèi)型都描述了該類(lèi)型的變量和表達(dá)式的取值集合,匯編語(yǔ)言中數(shù)據(jù)類(lèi)型的基本特征是以數(shù)據(jù)位數(shù)為度量單位,8,16,32,48,64,80
位,而除此之外其他的特征如(符號(hào),指針,浮點(diǎn)數(shù))
主要是為了方便我們記憶變量中存儲(chǔ)的數(shù)據(jù)類(lèi)型,如下表中所定義的部分,則是IEEE委員會(huì)發(fā)布的標(biāo)準(zhǔn)內(nèi)部數(shù)據(jù)類(lèi)型;
數(shù)據(jù)類(lèi)型作用(無(wú)符號(hào))數(shù)據(jù)類(lèi)型作用(有符號(hào))BYTE8位無(wú)符號(hào)整數(shù)SBYTE8位有符號(hào)整數(shù)WORD16位無(wú)符號(hào)整數(shù)SWORD16位有符號(hào)整數(shù)DWORD32位無(wú)符號(hào)整數(shù)SWORD32位有符號(hào)整數(shù)FWORD48位整數(shù)(遠(yuǎn)指針)QWORD64位整數(shù)定義REAL432位(4字節(jié))短實(shí)數(shù)REAL864位(8字節(jié))長(zhǎng)實(shí)數(shù)
數(shù)據(jù)類(lèi)型定義語(yǔ)句為變量在內(nèi)存中保留存儲(chǔ)空間,并且可以選擇為變量指定一個(gè)名字,在匯編語(yǔ)言中所有的數(shù)據(jù)無(wú)非就是BYTE
的集合,數(shù)據(jù)的定義語(yǔ)句格式如下;
[變量名]?數(shù)據(jù)定義偽指令?初始值[....]
在數(shù)據(jù)定義語(yǔ)句中使用BYTE(定義字節(jié))
和SBYTE(定義有符號(hào)字節(jié))
偽指令,可以為每一個(gè)或多個(gè)有符號(hào)或無(wú)符號(hào)字節(jié)分配存儲(chǔ)空間,每個(gè)初始值必須是8位整數(shù)表達(dá)式或字符常量,例如下面的定義:
.data
??var1?BYTE?'A'??????;?定義字符常量
??var2?BYTE??????????;?定義未初始化變量
??var3?BYTE?0????????;?最小的無(wú)符號(hào)字節(jié)常量
??var4?BYTE?255??????;?最大的無(wú)符號(hào)字節(jié)常量
??var5?SBYTE?-128????;?最小的有符號(hào)字節(jié)常量
??var6?SBYTE?+127????;?最大的有符號(hào)字節(jié)常量
如果一條數(shù)據(jù)定義語(yǔ)句中有多個(gè)初始值,那么標(biāo)號(hào)僅僅代表第一個(gè)初始值的偏移,如下我們首先定義一個(gè)BYTE數(shù)組,然后通過(guò)反匯編查看地址的偏移變化就能看到效果啦:
.data
??list?BYTE?10,20,30,40,50
00E71000?|?B8?0030E700????????|?mov?eax,main.E73000?????????????????|?E73000=10
00E71005?|?B8?0130E700????????|?mov?eax,main.E73001?????????????????|?E73001=20
00E7100A?|?B8?0230E700????????|?mov?eax,main.E73002?????????????????|?E73002=30
00E7100F?|?B8?0330E700????????|?mov?eax,main.E73003?????????????????|?E73003=40
00E71014?|?B8?0430E700????????|?mov?eax,main.E73004?????????????????|?E73004=50
并非所有的數(shù)據(jù)定義都需要標(biāo)號(hào),如果想繼續(xù)定義以list開(kāi)始的字節(jié)數(shù)組,可以在隨后的行上接著上面的定義:
.data
??list?BYTE?10,20,30,40,50
??list?BYTE?60,70,80,90,100
當(dāng)然除了定義整數(shù)字符以外,還可以定義字符串,要想定義字符串應(yīng)將一組字符用單引號(hào)或雙引號(hào)括起來(lái),最常見(jiàn)的字符串是以空格結(jié)尾0h,在C/C++中定義字符串無(wú)需添加結(jié)尾0h,這是因?yàn)榫幾g器會(huì)在編譯的時(shí)候自動(dòng)的在字符串后面填充了0h,在匯編語(yǔ)言中我們需要手動(dòng)添加字符串結(jié)尾的標(biāo)志,以告訴匯編器字符串的結(jié)束。
.data
??string1?BYTE?"hello?lyshark",0h
??string2?BYTE?"good?night",0h
00F23000??68?65?6C?6C?6F?20?6C?79?73?68?61?72?6B?00?67?6F?hello?lyshark.go?
00F23010??6F?64?20?6E?69?67?68?74?00?00?00?00?00?00?00?00?od?night........?
字符串也可以占用多行,而無(wú)須為每行都提供一個(gè)編號(hào),如下代碼也是合法的:
.data
??string1?BYTE?"welcom?to?the?Demo?program"
??????BYTE?"created?by?lyshark",0dh,0ah,
??????BYTE?"url:lyshark"
??????BYTE?"send?me?a?copy",0dh,0ah,0
十六進(jìn)制0dh,0ah
也稱(chēng)為CR/LF(回車(chē)換行符)
,或者是行結(jié)束的字符,在向標(biāo)準(zhǔn)輸出設(shè)備上寫(xiě)的時(shí)候,回車(chē)換行符可以將光標(biāo)移動(dòng)到下一行的開(kāi)頭位置,從而繼續(xù)填充新的字符串。
有時(shí)我們需要初始化一些空值的內(nèi)存空間,在為內(nèi)存地址分配空間的時(shí)候,DUP偽指令就顯得尤為重要,初始化和未初始化數(shù)據(jù)均可使用DUP指令定義,其定義語(yǔ)法如下:
.data
??string1?BYTE?20?DUP(0)???????;?分配20字節(jié),全部填充0
????BYTE?20?DUP(?)?????????????;?分配20字節(jié),且未初始化
????BYTE?50?DUP("stack")???????;?分配50字節(jié),"stackstack..."
.data
??smallArray?DOWRD?10?DUP(0)?;?分配40字節(jié)
??bigArray?DOWOR?5000?DUP(?)?;?分配20000字節(jié)
除了上面的例子以外,我們也可以直接定義常量,常量是不可以動(dòng)態(tài)修改的數(shù)據(jù)類(lèi)型,一般情況下一旦定義,那么在程序運(yùn)行期間不可以被修改,常量的定義很簡(jiǎn)單,只需要將.data
換成.const
即可。
.const
??var1?BYTE??"hello?world",0h???;?初始化為BYTE的字符串
??var2?DWORD?10?????????????????;?初始化為10的DWORD類(lèi)型
??var3?DWORD?100?dup(1,2)???????;?200個(gè)DWORD的緩沖區(qū)
??var4?BYTE??1024?dup(?)????????;?1024字節(jié)的緩沖區(qū)
??var5?BYTE?"welcome",0dh,0ah,0?;?0dh,0ah為換行符
有時(shí)我們需要計(jì)算一個(gè)指定數(shù)組的所占空間的大小,但手動(dòng)計(jì)算顯得特別麻煩,此時(shí)我們可以使用MASM提供的$符號(hào)來(lái)進(jìn)行數(shù)組大小的計(jì)算過(guò)程,如下定義匯編器會(huì)將其進(jìn)行預(yù)處理后回寫(xiě)到變量中存儲(chǔ)。
.data
??list?BYTE?10,20,30,40,50
??listsize?=?($?-?list)???????;?計(jì)算字節(jié)數(shù)據(jù)大小
.data
??list?WORD?1000h,2000h,3000h,4000h
??listsize?=?($?-?list)?/2????;?計(jì)算字?jǐn)?shù)據(jù)大小
.data
??list?DWORD?100000h,200000h,300000h,400000h
??listsize?=?($?-?list)?/4????;?計(jì)算雙字?jǐn)?shù)據(jù)大小
.data
??MyString?BYTE?"hello?lyshark",0h
??MyString_len?=?($?-?MyString)
1.3 標(biāo)準(zhǔn)輸入與輸出
在匯編語(yǔ)言中,有時(shí)我們需要獲取到數(shù)據(jù)的輸入輸出,由于匯編中并不存在屏幕打印功能,此處如果讀者需要使用此功能,則必須調(diào)用系統(tǒng)所提供的庫(kù)函數(shù)來(lái)實(shí)現(xiàn),一般要想實(shí)現(xiàn)輸入輸出有多種圖形,具體來(lái)說(shuō),StdIn
和StdOut
分別代表標(biāo)準(zhǔn)輸入流和標(biāo)準(zhǔn)輸出流;WriteFile
函數(shù)用于向文件或其他輸出設(shè)備寫(xiě)入數(shù)據(jù);crt_scanf
和crt_printf
是格式化輸入/輸出函數(shù),這些庫(kù)函數(shù)的調(diào)用都可以使用invoke
這個(gè)偽指令來(lái)實(shí)現(xiàn),invoke是MASM中提供的調(diào)用關(guān)鍵字,使用它可實(shí)現(xiàn)調(diào)用各類(lèi)API函數(shù)的目的。
StdIn/StdOut
如果讀者需要使用該函數(shù)輸出,則需要包含masm32.inc
頭文件,該頭文件為匯編語(yǔ)言程序員提供了一組常用的宏和函數(shù),在這個(gè)頭文件中,定義了StdIn、StdOut
和StdErr
三個(gè)宏,它們分別代表標(biāo)準(zhǔn)輸入流、標(biāo)準(zhǔn)輸出流和標(biāo)準(zhǔn)錯(cuò)誤流。
使用masm32.inc
中的這些宏,可以方便地將輸入輸出重定向到控制臺(tái)或文件中,而無(wú)需直接調(diào)用Windows API函數(shù)。例如,可以使用StdIn
宏從控制臺(tái)讀取用戶輸入,使用StdOut
宏向控制臺(tái)輸出字符流。這些宏的使用方式與在C語(yǔ)言中使用 stdin 和 stdout 類(lèi)似。
下面是一些示例代碼,使用masm32.inc
頭文件來(lái)實(shí)現(xiàn)標(biāo)準(zhǔn)的輸入輸出:
??.386
??.model?flat,?stdcall
??
??include?masm32.inc
??include?kernel32.inc
??includelib?masm32.lib
??includelib?kernel32.lib
.data
??len?equ?20
??OutText?dw??
??ShowText?db?"請(qǐng)輸入一個(gè)數(shù):?",0
.code
??main?PROC
????invoke?StdOut,?addr?ShowText????;?輸出提示信息
????invoke?StdIn,?addr?OutText,len??;?等待用戶的輸入
????invoke?StdOut,?addr?OutText?????;?輸出剛才輸入的內(nèi)容
????ret
??main?ENDP
END?main
crt_printf/crt_scanf
除了使用MASM定義的宏之外,讀者也可以使用C語(yǔ)言庫(kù)函數(shù)中的一些輸出函數(shù),為了使用crt_printf
,需要在程序中包含msvcrt.inc
頭文件,并將msvcrt.lib
庫(kù)作為鏈接器參數(shù)之一。然后,可以使用crt_printf
宏來(lái)輸出格式化的文本信息到控制臺(tái)或文件中。
下面是一個(gè)簡(jiǎn)單的使用crt_printf
的示例程序:
??.386
??.model?flat,?stdcall
??
??include?msvcrt.inc
??includelib?msvcrt.lib
??
.data
??PrintText?db?"EAX=%d;EBX=%d;EDX=%d?|?InPut?->:?",0
??ScanFomat?db?"%s",0
??PrintTemp?db??
.code
??main?PROC
????mov?eax,10
????mov?ebx,20
????mov?ecx,30
????invoke?crt_printf,addr?PrintText,eax,ebx,ecx????????;?打印提示內(nèi)容
????invoke?crt_scanf,?addr?ScanFomat,?addr?PrintTemp????;?輸入內(nèi)容并接收參數(shù)
????invoke?crt_printf,?addr?PrintTemp???????????????????;?輸出輸入的內(nèi)容
????ret
??main?ENDP
END?main
本文作者: 王瑞 本文鏈接: https://www.lyshark.com/post/9d939a6f.html 版權(quán)聲明: 本博客所有文章除特別聲明外,均采用 BY-NC-SA 許可協(xié)議。轉(zhuǎn)載請(qǐng)注明出處!