源地址:http://www.cnblogs.com/mfm11111/archive/2009/03/27/1423099.html
一. [bx]和內存單元
[bx]是什麼呢?
和[0]有些類似,[0]表示內存單元,它的偏移地址是0。
我們要完整地描述一個內存單元,需要兩種信息:
(1)內存單元的地址;
(2)內存單元的長度(類型)。
我們用[0]表示一個內存單元時,0 表示單元的偏移地址,段地址默認在ds中,單元的長度(類型)可以由具體指令中的其他操作對象(比如說寄存器)指出
所以,按照這樣的原理,我們對如下指令可以這樣理解:
Mov ax,[0]:表示將一個內存單元的內容送入ax寄存器。其中內存單元的長度爲2字節,偏移地址爲0,段地址在ds中。
Mov al,[0]:表示將一個內存單元的內容送入al寄存器。其中內存單元的長度爲1字節,偏移地址爲0,段地址在ds中。
Mov ax,[bx]:表示將一個內存單元的內容送入ax寄存器。其中內存單元的長度爲2字節,偏移地址在bx中,段地址在ds中。
例子:比如AX=1234,BX=1000, DS:1000內容爲1111
mov [bx],ax 把AX的值賦予BX所指向的內存單元
那麼執行後AX=1234,BX=1000,DS:1000的內容爲1234
這種做法就叫做:寄存器間接尋址
二. 描述性符號():
爲了描述上的簡潔,我們將使用一個描述性的符號 “() ”來表示一個寄存器或一個內存單元中的內容。
例如:
(ax):表示寄存器ax中的內容。
(20000h)表示內存單元20000h中的內容,(?)括號中的表示物理地址。
((ds)*16+(bx)),如果ds中的內容爲1230,bx中的內容爲4567,則(1230*16+4567)可以理解爲:
Ds中的內容作爲段地址,bx中的內容作爲偏移地址,這樣形成的物理地址的內容。
爲了加強理解,我們看看下面的如何描述:
我們看一下(X)的應用,比如:
(1)ax中的內容爲0010H,我們可以這樣來描述:(ax)=0010H;
(2)2000:1000 處的內容爲0010H,我們可以這樣來描述:(21000H)=0010H;
(3)對於mov ax,[2]的功能,我們可以這樣來描述:(ax)=((ds)*16+2);
(4)對於mov [2],ax 的功能,我們可以這樣來描述:((ds)*16+2)=(ax);
(5)對於 add ax,2 的功能,我們可以這樣來描述:(ax)=(ax)+2;
(6)對於add ax,bx的功能,我們可以這樣來描述:(ax)=(ax)+(bx);
(7)對於push ax的功能,我們可以這樣來描述:
(sp) = (sp)-2
((ss)*16+(sp))=(ax)
三. mov ax,2 那其機器代碼是 B8 02 00,這個設計計算機指令,彙編語言教材第一章一般就講這個,B8表示把後面的一個WORD存放到AX裏面,比如MOV AL,1的機器指令是B0 01,B0就表示把後面的字節保存到AL裏面,而MOV AH,1的機器指令是B4 01。
四.總結:現在我們通過一個實例,將上述的指令綜合一下:
題目:從offffh:0006內存單元中取一個值,並相加三次
分析:(1)運算後的結果是否會超出dx所能存儲的範圍?
Ffff:0006 單元中的數是一個字節型的數據,範圍在0~255之間,則用它和3相乘結果不會大於65535,可以在dx 中存放下
(2)我們用循環累加來實現乘法,用哪個寄存器進行累加?
我們將ffff:0006單元中的數賦值給ax,用dx進行累加。先設(dx)=0,然後做3次(dx)=(dx)+(ax)。
(3) ffff:0006單元是一個字節單元,ax是一個 16 位寄存器,數據長度不一樣,如何賦值?
我們說的是“賦值”,就是說,讓 ax 中的數據的值(數據的大小)和ffff:0006 單元中的數據的值(數據的大小)相等。
8 位數據01H和16位數據0001H的數據長度不一樣,但它們的值是相等的。
那麼我們如何賦值?
設ffff:0006單元中的數據是XXH,若要ax中的值和ffff:0006單元中的相等,ax中的數據應爲00XXH。
所以,若實現ffff:0006單元向ax 賦值,我們應該令(ah)=0,(al)=(ffff6H)。
實現計算ffff:0006單元中的數乘以3,結果存儲在dx中的程序代碼。
代碼:
assume cs:codesg
codesg segment
mov ax,0ffffh
mov ds,ax
mov bx,6
mov al,[bx]
mov ah,0
mov dx,0
mov cx,3
s:add dx,ax
loop s
mov ax,4c00h
int 21h
codesg ends
end
現在我們用debug來跟蹤這個程序:
我們來分析一下:
1. 圖中ds=165h,所以程序cs=166f,ss的值也爲166f,爲什麼?如果還不清楚的可以看看我們前面的學習彙編系列(大略提一下:不明白cs的值的,請仔細回頭去看.exe文件裝載的那張圖,ss的值不明白的請回頭看棧的那張圖),實際上程序運行是,先要找到一塊起始地址的內存,此處起始地址爲:ds:0,但是在.exe加載時,因爲dos需要與.exe文件通訊,所以會有一塊psp區用來存放通訊的內容。所以此處cs的值實際上是cs=ds+10h=166F,我們說cpu取得內存單元,就是通過cs:ip,所以cs:0實際上就是指程序的第一條指令。
2. 同樣的道理,此時指針也是指向程序的第一條指令,所以指針的段地址也爲166f,不明白的回頭去看棧的原理,這裏就不多說了。
3. 破解軟件時,我們要先學會用g命令,爲了使大家看得更清楚一點,我們將循環次數改爲10:
我們可以使用g 0012,則會自動執行到偏移地址爲0012處。
當我們遇到loop時,因爲後面的循環次數太多,我們可以用p命令。也可以用g 0016
4. 同一種寫法,不同的處理方式:
對於這種寫mov al,[0],debug表示:把ds:0內存單元處的內容放入al.
源程序中表示:就是把數據0放入al中。
解決方法:1.用過渡寄存器,比如:bx,如下:
Mov bx,0
Mov ax,[bx]
2.Mov ax,ds:[0]
五.活用:loop和[bx]的聯合應用
例子:計算ffff:0~ffff:b單元中的數據的和,結果存儲在dx中。
我們還是先分析一下
1.運算後的結果是否會超出 dx 所能存儲的範圍?
ffff:0~ffff:b內存單元中的數據是字節型數據,範圍在0~255之間,12個這樣的數據相加,結果不會大於 65535 ,可以在dx中存放下。
2. 我們是否將 ffff:0~ffff:b中的數據直接累加到dx中?
當然不行,因爲ffff:0~ffff:b中的數據是8位的,不能直接加到16位寄存器dx中。
3.我們能否將ffff:0~ffff:b中的數據累加到dl中,並設置(dh=0,從而實現累加到dx中的目標?
這也不行,因爲dl是8位寄存器,能容納的數據的範圍在小 255 之間,ffff : 0~ffff:b中的數據也都是 8 位,如果僅向dl中累加12個 8 位數據,很有可能造成進位丟失。
4. 我們到底怎樣將用ffff:0~ffff:b中的8位數據,累加到16位寄存器dx中?
我們將內存單元中的 8 位數據賦值到一個16位寄存器ax中,再將ax中的數據加到dx上,從而使兩個運算對象的類型匹配並且結果不會超界。
分析後,我們現在來寫出代碼:
Mov ax,0ffffh
Mov ds,ax
Mov bx,0
Mov dx,0
Mov cx,0
S:Mov al,[bx]
Mov ah,0
Add dx,ax
Inc bx
Loop s
請大家自行加上僞指令運行。
通過前面的介紹,我們知道了[寄存器]的含義以及各個寄存器之間如何靈活計算的方式,這是學習破解的基礎,那麼破解時入口地址的確認以及如何查看反彙編的數據段,代碼段,棧段呢?看下面的幾個基礎點。
六.在一個段中存放數據、代碼、棧,我們先來體會一下不使用多個段時的情況;
1. 考慮這樣一個問題,編程計算以下8個數據的和,結果存在ax 寄存器中:
0123H,0456H,0789H,0abcH,0defH,0fedH,0cbaH,0987H
看下面的程序:
assume cs:codesg
codesg segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
mov bx,0
mov ax,0
mov cx,8
s: add ax,cs:[bx]
add bx,2
loop s
mov ax,4c00h
int 21h
codesg ends
end
解釋一下,程序第一行中的 “dw”的含義是定義字型數據。dw即define word。
在這裏,我們使用dw定義了8個字型數據(數據之間以逗號分隔),它們所佔的內存空間的大小爲16個字節
這8個數據的偏移地址是多少呢?
因爲用dw定義的數據處於代碼段的最開始,所以偏移地址爲0,這8 個數據就在代碼段的偏移0、2、4、6、8、A、C、E處。
程序運行時,它們的地址就是CS:0、CS:2、CS:4、CS:6、CS:8、CS:A、CS:C、CS:E。
2.如何讓這個程序在編譯後可以存系統中直接運行呢?我們可以在源程序中指明界序的入口所在.
用 Debug加載後,我們可以將 IP 設置爲10h,從而使CS:IP指向程序中的第一條指令。然後再用T命令、P命令、或者是G 命令執行。
可是這樣一來,我們就必須用Debug 來執行程序。
程序 6.1 編譯成可執行文件後,在系統中直接運行可能會出現問題,因爲程序的入口處不是我們所希望執行的指令
我們在程序的第一條指令的前面加上了一個標號start,而這個標號在僞指令end的後面出現。end 除了通知編譯器程序結束外,還可以通知編譯器程序的入口在什麼地方。
2.通過上面的分析,我們明白了在代碼段中使用數據的情況,現在在來看看在代碼段中使用棧的情況。
完成下面的程序,利用棧,將程序中定義的數據逆序存放。
assume cs:codesg
codesg segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
?
code ends
end
分析:
(1).程序運行時,定義的數據存放在cs:0~cs:15單元中,共8個字單元。依次將這8個字單元中的數據入棧,然後再依次出棧到這 8 個字單元中,從而實現數據的逆序存放。
(2).問題是,我們首先要有一段可當作棧的內存空間。如前所述,這段空間應該由系統來分配。我們可以在程序中通過定義數據來取得一段空間,然後將這段空間當作棧空間來用。
程序如下所示:
assume cs:codesg
codesg segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
dw 0,0,0,0,0,0,0,0
start:mov ax,cs
mov ss,ax
mov sp,32
mov bx,0
mov cx,8
s: push cs:[bx]
add bx,2
loop s
mov bx,0
mov cx,8
s0:pop cs:[bx]
add bx,2
loop s0
mov ax,4c00h
int 21h
codesg ends
end start
解釋:(1).其中mov ax,cs
mov ss,ax
mov sp,32
我們要講 cs:16 ~ cs:31 的內存空間當作棧來用,初始狀態下棧爲空,所以 ss:sp要指向棧底,則設置ss:sp指向cs:32
七。看下面的代碼,實現了兩個字符串變大寫和變小寫的方法。
assume cs:codesg,ds:data
data segment
db 'BaSiC'
db 'iNfOrMaTiOn'
data ends
codesg segment
start:mov ax,data
mov ds,ax
mov bx,0
mov cx,5
s:mov al,[bx]
and al,11011111B
mov [bx],al
inc bx
loop s
mov bx,5
mov cx,11
s0:mov al,[bx]
or al,00100000B
mov [bx],al
inc bx
loop s0
mov ax,4c00h
int 21h
codesg ends
end start
1. 數據段是放在程序開頭的,程序指令執行段是在數據段之後,所以數據段的首地址爲:ds+10h.
2. 注意data,and,or ,[bx]的用法。
3. [bx]與[bx+idata]的用法。後者只是取出bx中的數值(即偏移地址)後+一個數=新的偏移地址而已。例如:
2000:1000 00 be 00 06 00 05
Mov ax,2000h
Mov ds,ax
Mov bx,1000h
Mov cx,[bx] 那麼此時cx=00
Mov cx,[bx+1],那麼此時cx=be
4.si與di,與bx相似,只是不能分成兩個8位寄存器。所以在傳輸時,一般按字傳。(比如將一個字符串複製到另外的空間地址)。
5.mov ax,[bx+si],[bx+si]此種方式與數組類似。一般si計數,然後通過si找新偏移地址的數據。例如:
Mov ax,2000h
Mov ds,ax
Mov bx,1000h
Mov si,0
Mov ax,[bx+si]
Inc si
6.[bx+si+idata]與[bx+si]類似。
最後總結一下:
n 如果我們比較一下前而用到的幾種定位內存地址的方法(可稱爲尋址方式),就可以發現有以下幾種方式:
n (1)[iata] 用一個常量來表示地址,可用於直接定位一個內存單元;
n (2)[bx]用一個變量來表示內存地址,可用於間接定位一個內存單元;
n (3)[bx+idata] 用一個變量和常量表示地址,可在一個起始地址的基礎上用變量間接定位一個內存單元;
n (4)[bx+si]用兩個變量表示地址;
n (5)[bx+si+idata] 用兩個變量和一個常量表示地址。