1. 程式人生 > >16匯編第十講完結Call變為函數以及指令的最後講解

16匯編第十講完結Call變為函數以及指令的最後講解

循環 image 直接 註意 style 等待 開始 選址 協處理器

16匯編完結Call變為函數以及指令的最後講解

學了10天的16位匯編,這一講就結束了,這裏總結一下昨天的LOOP指令的缺陷,因為lOOP指令的缺陷,所以我們都改為下面的匯編代碼使用了,自己去寫,其中條件是你自己寫的

請看匯編代碼:

do while 的匯編代碼
 WHILE:
       mov ax,ax
       cmp ax, 10
       jl WHILE
while 的匯編代碼

 WHILE:
    cmp ax, 10
     jge WHILE_END
    mov ax,ax
     jmp WHILE
WHILE_END:

一丶Call指令(子程序)變為函數調用(重要,這個以後逆向會天天看到所以一定掌握)

首先我們明白一點,昨天我們寫的只是一個單獨的子程序(什麽保存棧地址,開辟局部變量空間,....都沒有寫,只是單獨的平棧),今天我們就寫一個Call變為函數調用的例子,一步一步的看為什麽這麽做,對以後的逆向很有幫助

1.首先我們明白Call調用的幾種方式

段內: 一個段中去調用,或者跳轉

段間: 代碼不在同一個段中,從一個段跳躍到另一個段

Call指令分為4中類型 (類似於JMP)

Call label(標號) ; 段內調用丶直接尋址

Call r16/m16 (16位寄存器,或者2字節內存) 段內調用丶間接選址

Call far ptr label;                段間調用丶直接尋址

Call far ptr mem; 段間調用丶間接尋址

昨天我們寫的簡單的子程序例子:

mov ax,1
push ax
mov bx,2
push bx
Call   標號
add sp,4
....
標號: mov bp,sp
   mov ax,[bp +2]
   mov bx,[bp +4]
   add ax,bx
ret

我們壓入了兩個參數,一個Ax,一個Bx,在Call的時候,會把下一行的地址壓入到棧中,也就是 add sp,4的所在的地址

我們畫一下棧

技術分享

可以看出,這個就是當前的棧結構了, 我們執行代碼的時候,首先 bp 和 sp是平等的了

現在 bp+2 能尋得bx, bp +4 能尋得 ax

然後ret 註意,ret只是把sp +2了,也就是彈棧彈出了返回地址,並且給IP了,現在IP就會跳轉到下一條指令執行的位置

也就是(add sp,4) 現在要註意了,bx 和 ax 都還在棧中,我們沒辦法讓棧恢復所以在外面用 add sp,4 讓sp的位置(現在的位置+2了,已經在bx的位置了)變為棧底了

所以這個就是C語言的 C調用約定

如果我們想StdCall (std 調用約定,標準的調用約定) 就要用retn 這個指令了,不能是ret了,

寫成 retn 4 代表先把返回地址返回出去,然後再讓sp +4 個字節,相當於在函數內部就平棧了,這樣外部就不用寫

add sp,4了,不用自己平棧了

2.由Call 變為函數一步一步來

現在基於上面的原理我們知道如何平衡一個棧了,但是你有沒有發現,為什麽我們一開始要把sp 給Bp

也就是讓 BP和SP一樣的位置

這個是有原因的

首先C語言調用函數的時候會進行幾步操作

1.保存棧底

2.申請局部變量空間

3.保存環境

4.恢復環境

5.釋放局部變量空間

6.恢復棧底

7.彈棧返回

好看上面的我們可能有點不明白,下面我把整體的棧圖放出來

技術分享

整體的棧是這樣的,這裏為什麽要一開始把bp和sp相等,是有原因的,我們不妨這樣想,如果我們申請局部變量空間的

時候,是不是參數的偏移也要改動,這樣每次都要自己計算偏移,相當麻煩,所以只能這樣,

我們以後找參數就 bp + xxx (因為bp一開始就在棧底的值這一欄) 這樣就能尋找到參數,你開辟多大的局部變量看空間都和我沒關系,所以以後我只需要 bp-xxx 就是找到的局部變量

現在看下匯編代碼的模版吧

MY_ADD:  ;stdcall    
    push bp
    mov bp, sp    ;1. 保存棧底
    
    sub sp, 10    ;2. 申請局部變量空間這裏隨你便,申請的時候先擡棧,讓sp高一點,但是不會影響bp
    push bx       ;3.保存環境 保存環境的意思就是外面的寄存器的值要保存一下,這樣恢復寄存器的值
                 
    mov word ptr [bp-4], 1        ;每次我只需要 bp -xxx 就知道尋找局部變量
    mov word ptr  [bp-2], 2
    mov ax, [bp+6]  ;參數1         ;每次我bp+ xxx 就知道我是找參數,所以不會沖突,打死參數的地址不會變
    add ax, [bp+8]  ;參數2
    
                 ;xxxxxxx
    pop bx          ;4. 恢復環境      彈棧的時候寄存器信息先回復
    mov sp, bp     ;5. 釋放局部變量空間  而且恢復變量控件的時候也很容易,直接把bp當前的位置給sp即可,釋放空間了
    pop bp        ;恢復以前的棧底的值
    retf         ;6. 返回   ,retf下面詳細講

在這裏主要是掌握bp所在的位置即可,就能明白為什麽這樣寫了,不信的話自己寫個程序,看下反匯編,大體的就是這個套路,這裏講解的是為什麽這樣做,不是和市面的匯編視頻一樣,你看到 bp -xxx 就知道他在訪問局部變量就行

其實這個是錯誤的,我們要知其然,並知其所以然

看下棧圖,掌握bp所在的位置

技術分享

只要掌握bp所在的位置即可,上面的代碼即可明白

3.Call指令的retf段間轉移

這個我們首先要明白,在Call的時候會把Call下邊一條指令的地址保存到棧中,出棧的時候要給IP,讓其更改跳轉,

跳轉到Call下一條指令執行的位置的地方

但是現在我們是段間Call,也就是不在一個段中,這個時候棧不光會保存返回地址,還會保存當前CS段寄存器的地址

這樣返回的時候 CS:IP返回,但是現在有一個問題,就是我們自己根本就平不了棧,我們把IP拿出來了,給IP,CS段寄存器根本沒辦法改,這樣我們必須同時修改CS:IP的值才能回到以前的地方,但是現在沒辦法了,因為你改IP回跳,改CS會跳,必須同時改,弄不了,所以弄一個retf的指令去幫我們去做

註意retf 你也需要平棧,比如我們壓入了兩個參數,就要 retf 4, retf會默認把棧頂4個字節的數據取出來分別給 ip和CS段寄存器,但是剩下4個字節都是我們的參數,比如自己去釋放,讓SP的加4到棧底才可以

二丶中斷指令

1.什麽是中斷指令

中斷,是有一種改變程序執行順序的方法

中斷具有很多的中斷類型

中斷的指令有3條

  1.INT i8(i8代表一個八位的立即數)

  2.IRET IRET 和Call差不對,Call的ret返回的時候會把棧頂的元素彈出兩個字節,這兩個字節是返回地址,所以可以回到正確的地方執行指令,但是IRET明顯比ret保存的東西多,其中ret我們可以手工的pop和jmp去執行,IRET也可以自己去做,但是你要完整的模擬才可以,一般還是調用IRET即可

  3.INTO

2.中斷指令的自我理解

  其實中斷指令就是調用硬件提供的API(也稱為系統調用)我們前邊用過很多次了

比如顯示一個字符串

  

mov ah,09h
int 21h

其中參數是09,int 21h代表執行,還有很多

介紹下指令

  INT I8: 中斷的調用指令: 產生I8號中斷,就是調用int代表我要調用了,其中指令是什麽使我們給的,是一個八位立即數比如 09

  IRET: 中斷的返回指令,理解為返回,可以進行下一條指令的執行

  INTO: 不常用,不講解.

3.21h中斷,到底是個啥玩意

我們每次都調用21h什麽的,但是不知道他是個啥玩意

他是DOS提供給用戶的,用於調用系統功能的中斷(簡單理解就是DOS提供的API,讓用戶調用),他有近百個功能讓公戶選擇使用.包括設備管理,目錄管理,和文件管理

ROM-BIOS(主板的BIOS)也是這種形式,這就是為什麽程序一開機顯示器就會顯示字符,別忘了這個時候系統還沒有啟動,還沒有操作系統一說

看下圖:

  技術分享

現在操作系統也還是這樣調用,操作系統的API很多,底層就依賴於這256個中斷,只不過操作系統可能處理的方式更多,比如根據AH的值,調用一個函數指針,也就是一個函數的地址,這個函數地址裏面又有很多封裝,慢慢的操作系統API就越來越多.這些都是我們不關心的

具體的中斷,可以看下第一課所有的課堂資料中的指令字典,尋找一下中斷號自己去調用

比如判斷按鍵的匯編例子

getkey:    mov ah,01h    ;功能號:ah←01h
    int 21h              ;功能調用
    cmp al,’Y’           ;處理出口參數al
    je yeskey            ;是“Y”
    cmp al,’N’
    je nokey             ;是“N”
    jne getkey
    ...
yeskey:    ...
nokey:    ...

三丶最後的指令詳解

LOCK指令 封鎖總線,不讓總線接受指令

這個常用與多線程的時候操作,多線程的操作中的同步對象的 自加鎖就是這樣實現的

比如:

  

lock add word ptr[bp],3

當我們吧3給內存的時候,其余的程序也可能再往這裏寫數據,所以同步一下,這樣就完成這一條指令,才可以進行下一條指令的操作

自減鎖就是 把add 變為sub,交換鎖就是 把指令變為 xchg lock只能同時處理一條指令,這是為防止我們把系統總線都鎖死,這樣操作系統就會崩潰,信息到不了,不過這個不是我們關心的

解鎖是CPU自己解鎖的,沒有解鎖指令

HLT 暫停指令

這個就比較簡單的,我們電腦都有休眠功能,就是用的這個指令,讓CPU功耗降低,不執行,以前是無限循環NOP指令

但是NOP也是一個指令也會有功耗,所以現在改為HLT指令了,執行了這個指令HLT不進行任何操作,當我們發送了一條指令過去之後,就會脫離暫停狀態

就好比電腦掛機了,屏幕黑了,就是進入HLT了,我們點擊鼠標或者鍵盤,發送了一條指令,接著就喚醒了

交權指令

ESC 6位立即數,reg/mem

我們都知道,以前算浮點數的時候都是CPU一個去做的,現在有了浮點處理器,也就是協處理器,專門算浮點的一個CPU

我們的CPU計算浮點數的時候,要把權力交給浮點處理器,這時候就稱為交權,在這個時候CPU要等待浮點處理器返回的結果,期間一直等待

FADD FDIV 是浮點計算指令,就是我們計算指令前面加F就會計算浮點數了

浮點數有7個寄存器

ST -> st7 按照標號來的

浮點處理器的st不能和通用寄存器一樣去使用,它是吧ST寄存器壓入棧中,讓前兩個棧中的數據相加返回的

關於浮點處理,後面再說,這個不是16為匯編中使用的

WAIT 等待指令

這個就簡單了,CPU交權後,就使用這個指令去等待浮點處理器返回結果

總結:

  16位匯編在我的博客上都精簡了,但是你想搞明白就要多花點時間,細細品味匯編的內容,掌握匯編

過幾天開始32位匯編的講解,如果覺得好,請評論一下,關註一下,加個粉絲.支持一下,謝謝

對於Call變為函數的哪裏,一定要掌握,不懂的可以去看下C語言的棧內存結構,或者看下它的匯編代碼,一定搞明白

這個以後逆向的時候天天看.

學習資料: 鏈接:http://pan.baidu.com/s/1nuPNUFf 密碼:x44c

16匯編第十講完結Call變為函數以及指令的最後講解