|
每一種語言都有一些字符串操作函數(shù),反匯編后的代碼也不例外。網(wǎng)上講的都比較零碎,我將它們收集起來,并配上自己的理解,形成本文。
一、字節(jié)操作指令:lodsb和stosb 1. lodsd需要寄存器esi配合使用。每執(zhí)行一次lodsb,就將[esi]中的一個字節(jié)復(fù)制到al寄存器中。 即:lodsd == [esi] --> al
2. stosb需要寄存器edi配合使用。每執(zhí)行一次stosb,就將al中的內(nèi)容復(fù)制到[edi]中。 即:stosb == al --> [edi]
3. ecx和cld指令。以上兩條指令,一次只能復(fù)制一個字節(jié)。要是有多個字節(jié)需要復(fù)制該怎么辦呢?那就需要用到一個計數(shù)器,所以常用ecx保存需要復(fù)制的字節(jié)個數(shù)。 為了能夠自動復(fù)制,esi和edi需要能夠自動的遞增或者遞減。這就用到了DF標(biāo)志位(Directory Flag)。DF=0,esi和edi自動增加;DF=1,esi和edi自動減少。通常用cld指令來設(shè)置DF標(biāo)志位,使其置為0。
4. 舉代碼示例說明。下面使用代碼說明,該代碼的功能是實現(xiàn)strcpy。 定義需要拷貝的源字符變量: szSource db 'abcdefghijklmnopqrstuvwxyz', 0
4.1 首先看一下不使用lodsb和stosb的方法_StrCopy_1。 _StrCopy_1 proc local @szBuffer[32]:byte ;字符串單個復(fù)制指令 invoke lstrlen, offset szSource mov ecx, eax ;要復(fù)制的字節(jié)個數(shù) mov esi, offset szSource ;源數(shù)據(jù)地址 lea edi, @szBuffer ;目的數(shù)據(jù)地址 @@: mov bl, [esi] ;一次復(fù)制一個字節(jié) mov [edi], bl ;又將該字節(jié)復(fù)制到目的地
inc edi ;目的地址和源數(shù)據(jù)地址都向上加一 inc esi dec ecx ;復(fù)制個數(shù)減一 ;cmp byte ptr [esi], 0 ;以null結(jié)尾的字符串 cmp dec, 0 jnz @B ;如果沒有復(fù)制到結(jié)尾,則繼續(xù)復(fù)制 mov [edi], 0 ;將目的數(shù)據(jù)的最后一個字節(jié)置為null 。。。。。 ;其它代碼 _StrCopy_1 endp 該段代碼,沒有使用任何特殊的指令。esi和edi也沒有任何其它的意義,只是為了便于理解。用eax,edx代替esi,edi也是一樣的。
4.2 使用lodsb和stosb的函數(shù)_StrCopy_2 _StrCopy_2 proc local @szBuffer[32]:byte
xor eax, eax mov esi, offset szSource ;將要拷貝的源數(shù)據(jù)地址放入esi中 test esi, esi ;是否是有效地址? jz proc_end ;不是的話就停止運行
lea edi, @szBuffer ;將要寫入的目的地址放入edi中 test edi, edi jz proc_end
;方向標(biāo)志位。當(dāng)方向標(biāo)志位DF=0時,則esi自動增加;DF=1時,esi自動減小 cld ;設(shè)置DF=0
@@: ;代碼的關(guān)鍵所在 ;一次復(fù)制一個字節(jié),將源地址的一個字節(jié)復(fù)制到al中。 lodsb ;byte ptr [esi]-->al。 ;本次傳輸一個字節(jié),將al中的字節(jié)復(fù)制到目的地址中。 stosb ;al-->byte ptr [edi]。 test al, al ;字符串是否已經(jīng)到了結(jié)尾 jnz @B ;沒有到結(jié)尾,繼續(xù)傳輸下一個字符 ;可以看到:從@@到此處的代碼,就實現(xiàn)了字符串的自動復(fù)制。 ;用到了DF標(biāo)志位,esi和edi自動遞增。 ;用test al, al/jnz @B來判斷是否到了結(jié)尾。也可以將要復(fù)制的長度放到ecx中, ;用代碼dec ecx/test ecx, ecx/jnz @B來實現(xiàn)同樣的功能。 ;本函數(shù)比上個函數(shù)多使用了esi和edi寄存器。而且在循環(huán)里面,沒有用指令遞增地址(inc edi/inc esi),而是自動遞增。
;加上這段代碼,可以求得字符串的長度strlen,并放在ecx里面 lea ecx, @szBuffer sub edi, ecx ;地址末尾減去地址頭,等于該段地址的長度(字節(jié)數(shù)) mov ecx, edi ;除了lodsb外,還有l(wèi)odsw, lodsd,分別是一次傳輸一個字,雙字到ax,eax里面。 。。。。。。。。。 ;其它代碼 _StrCopy_2 endp
二、字,雙字操作指令 不是每次都要復(fù)制一個字節(jié),有時候需要復(fù)制一個字或者雙字怎么辦呢?在_StrCopy_2的注釋里面,也提到了,用lodsw/lodsd,相對應(yīng)的指令是stosw/stosd。 概括說來:
2.1 從內(nèi)存復(fù)制數(shù)據(jù)到寄存器 【lodsb指令】:從esi指向的源地址中逐一讀取一個字符,送入AL中(然后,可以先判斷這個字符是什么字符,如0dh,0ah之類等,再執(zhí)行相應(yīng)的操作)。 類似有【lodsw指令】:如果是lodsw,表明要處理的是字,而不是字符。則采用的相應(yīng)指令是:lodsw;那么復(fù)制到的寄存器是AX,而不是AL了。 【lodsd指令】:如果是lodsd,表明要處理的是雙字。則采用的相應(yīng)指令是:lodsd;那么復(fù)制到的寄存器是EAX,而不是AL或AX了。
2.2 從寄存器復(fù)制數(shù)據(jù)到內(nèi)存 【stosb指令】:將AL中的字符寫入edi指向的目的地址。 類似有【stosw指令】:如果是stosw,表明要處理的是字,而不是字符。則采用的相應(yīng)指令是:stosw;那么被復(fù)制的數(shù)據(jù)源寄存器是AX,而不是AL了。 【stosd指令】:如果是stosd,表明要處理的是雙字。則采用的相應(yīng)指令是:stosd;那么被復(fù)制的數(shù)據(jù)源寄存器是EAX,而不是AL或AX了。
三、結(jié)合rep指令 上面的介紹,都是一次復(fù)制一個字節(jié)、字或雙字。比如一個雙字數(shù)組長度位500個,那么就需要重復(fù)500次使用雙字復(fù)制指令。有沒有方法可以自動復(fù)制完所有數(shù)據(jù)?有,將要復(fù)制的長度500放到ecx中,然后在復(fù)制指令前加上rep指令即可(rep指令表示repeat)。 比如要在一個字數(shù)組的內(nèi)存區(qū)域填充0xCC的代碼如下: local @wBuf[256]:word
mov ax, 0cccch ;源數(shù)據(jù) lea edi, @wBuf ;目的地址 cld ;設(shè)置方向遞增順序 mov ecx, 256 ;將要復(fù)制的次數(shù)放在ecx中 rep stosw ;復(fù)制 需要注意的是,如果是復(fù)制字符串的情況,要注意在后面加上0x00.
四 如果要寫一個memcpy函數(shù),使用rep是不行的,因為rep只會將eax里面的東西搬運的到edi,而不會將源字符里面的東西搬運到eax。還是要使用_StrCopy_2里面的方法。 我所寫的一個memcpy函數(shù)如下:
1 ;內(nèi)存拷貝函數(shù) 2 ;_lpDest目的內(nèi)存地址。本函數(shù)將把源內(nèi)存的內(nèi)容搬運到該內(nèi)存地址上,搬運長度是_dwSize 3 ;_lpSource源內(nèi)存地址 4 ;_dwSize,拷貝的長度。即使_lpSource里面包含了0x00的字符,只要小于_dwSize,仍然繼續(xù)搬運。 5 ;例如: 6 ;_lpSource上的內(nèi)容是:abcd(0x00)efg 7 ;_dwSize是8 8 ;函數(shù)運行的結(jié)果是:abcd(0x00)efg,雖然里面有字符串的結(jié)束符,但仍然搬運后面的剩下3個字符 9 _memcpy proc _lpDest, _lpSource, _dwSize 10 mov edi, _lpDest 11 mov esi, _lpSource 12 mov ecx, _dwSize 13 xor eax, eax 14 cld 15 @@: 16 lodsb ; byte ptr [esi]-->al 17 stosb ; al -----> byte ptr [edi] 18 dec ecx 19 test ecx, ecx 20 jnz @B 21 22 ret 23 _memcpy endp
可以使用以下代碼作為測試: 1 local @szBuffer[256]:byte 2 3 invoke RtlZeroMemory, addr @szBuffer, 256 4 push 'abcd' 5 mov ebx, esp ;設(shè)置一個臨時變量 6 7 invoke _memcpy, addr @szBuffer, ebx, 4 8 9 pop ebx ;平衡棧 10 invoke MessageBox, NULL, addr @szBuff, NULL, MB_OK
repe cmpsb
repe是一個串操作前綴,它重復(fù)串操作指令,每重復(fù)一次ECX的值就減一
MOVZX
匯編語言數(shù)據(jù)傳送指令MOV的變體。無符號擴展,并傳送。 movzx一般用于將較小值拷貝到較大值中。 這個指令是非常有用的,大家以后學(xué)程序設(shè)計的話,如果需要處理windows中的消息,例如WM_COMMAND消息,這個消息結(jié)構(gòu)的wParam的高 16位是通知碼,而低16位則是命令id。有時候需要判斷命令id的話,則需要將這個wparam的低16位擴展成32位的,并且其余位用0填充。這就用 到了movzx。
movzx是將源操作數(shù)的內(nèi)容拷貝到目的操作數(shù),并將該值0擴展至16位或者32位。但是它只適用于無符號整數(shù)。 他大致下面的三種格式。
舉個例子。
又如: MOV BL,80H MOVZX AX,BL
總結(jié): movzx其實就是將我們的源操作數(shù)取出來,然后置于目的操作數(shù),目的操作數(shù)其余位用0填充。
repne scasb 詳解
SCAS是在檢索目標(biāo)字符串;REPNE/REPNZ是重復(fù)前綴(CX<>0 且ZF=0重復(fù)執(zhí)行字符串指令),類似的還有REPE/REPZ、REP
利用 REPNE SCAS 來檢測代碼中是否被下int3斷點(CC):
利用 REPNE SCAS 來計算字符串長度:
|
|
|
來自: herowuking > 《Cracker》