32位匯編第一講x86和8086的區別,以及OllyDbg調試器的使用
一丶32位(x86也稱為80386)與8086(16位)匯編的區別
1.寄存器的改變
AX 變為 EAX 可以這樣想,16位通用寄存器前邊都加個E開頭
例如:
EAX EBX ECX EDX ESI EDI ESP EBP ;八個寄存器 EIP EFLAGES ;特殊寄存器
CS ES SS DS GS FS ;其中GS FS是新增加的寄存器,這些段寄存器,並不是4個字節(32位的)還是以前16位的
注意在32位下沒有分段的概念的,因為尋址能力是 0- FFFFFFFF ,在當時的inter認為當初的4G已經很厲害了,那是后最好的內存才1G,放到現在看
我們感覺4G不夠用了,但也是近幾年才開始用的8G
有分區的概念,比如我們16位匯編中,給代碼分段的時候,順便分了一下區,分區是為了更好的管理代碼的編寫
2.地址有20根總線變為32根總線(也就是4G)
3.寄存器的數量沒有做改變
2.32位寄存器和16位寄存器的兼容
EAX 的低16位變為AX了,所以兼容的16位,其余的寄存器同理
32位中的段寄存器不是我們能操作的了,給操作系統使用,所以有了權限一說
在16位中,我們可以直接操作段寄存器分段,或者尋址,而這樣很不安全,萬一你分段的時候,正好在操作系統的代碼區,那么你可以修改代碼,那么操作系統就崩潰了
所以為了系統的穩定,操作系統不讓使用段寄存器了,而這些段寄存器操作系統都記錄了一些表的信息
二丶編寫32位中的匯編代碼
1.介紹
在編寫32位匯編的時候,介紹一下編譯器和連接器,以前我們使用的匯編編譯器是可以編譯32位匯編的,但是連接器是不能連接32位匯編程序
所以link連接器需要改為32位的,如果有安裝過vc++6.0 那么是可以找到它的連接器的,我們使用它的連接器即可.
2.分區概念
上面說了,操作系統不讓我們使用段寄存器,那么我們可以去分區,分為 常量區 全局數據區 代碼區 (沒有棧區,棧區由編譯器維護,編譯器分配)
首先介紹一下偽指令的用法(偽指令在16位匯編最后一講都講了,那么這節課就要調用偽指令去編寫匯編代碼了,還會增加偽指令去講解)
1.偽指令
①.model偽指令的使用
memorymodel: 表示你要設置的內存模式 這里我們設置平坦模式(表示內存是連續的,因為不能分段了)平坦模式 FLAT
[,langtype]調用約定: 如果這里寫了調用約定,那么以后我們使用 函數的偽指令(PROC)的時候,就不用指明調用約定
了而且win32可以調用操作系統API,而調用API的時候,這些API的調用約定,也是你這里給指定的
用法例子:
.386 ;這里表示我們要寫386的程序(也就是32位)匯編程序,指明一下,這個不是偽指令 .model FLAT,stdcall ;內存設置為平坦模式,默認調用約定stdcall
②偽指令PROTO(函數聲明)
函數聲明的偽指令,這個主要是針對我們自己寫的函數,如果調用的時候,函數正好在下面(他會從上面找,找不到報錯)所以聲明一下告訴它存在即可
例子:
;使用偽指令聲明 My_ADD PROTO n1:dword,n2:dword ;調用 invoke My_Add ,1,2 ;函數實現在下面,如果不寫聲明告訴存在,就會調用出錯 My_ADD PROC n1:dword,n2:dword ;定義了一個函數,參數是n1,n2,指明的大小是DWORD(4個字節的),這里沒有寫調用約定,上面寫了默認的調用約定了
My_ADD endp ;函數定義的結束標志
③偽指令 option(選項的偽指令)
這個偽指令主要是增加額外選項,比如上面我們調用函數,匯編不區分大小寫,你這樣寫是可以調用的,但是為了
不必要的麻煩,我們加上一個選項,也就是大小寫敏感,也就是區分大小寫,這樣我們調用系統API的時候就不用怕出錯了
使用例子:
option casemap:none ;使用大小寫敏感的選項
④定義常量去的偽指令(.const)
上面說了,內存有了保護模式,分為了 可讀可寫可執行,如果是常量去,那么只能讀,不能寫,不能執行
語法:
這個比較簡單了
使用例子:
.const ;定義常量區 g_szTitle db "Hello" ;在常量區中定義常量字符串
⑤數據區的定義(.data)
數據區,專門定義數據使用的,是可讀可寫的
語法:
它分為兩種,一種是初始化的數據區,一種是未初始化的數據區
初始化數據區的寫法:
.data ;定義數據區 ....;你自己的數據
未初始化的數據區寫法
.data ?;加?號表示未初始化 g_szData dw ? ;數據的申請必須是? 也就是未初始化的
兩者的區別
初始化的數據,不過你定義數據的時候,是否給? 都會寫的EXE(PE文件中)
未初始化的數據, 定義數據的時候只能給? 不在PE文件中保存
⑥代碼區的偽指令(.code)
定義執行的代碼區
語法:
例子:
.code START: ;代碼開始執行的標號 end START; end表示文件結束START表示要從START開始執行代碼
⑦多文件編譯ASM(#include 后綴名.inc)
我們有時候會想,代碼不可能一個文件寫完,比如多個文件聯合編譯,所以就有了.inc文件
一般我們定義數據區,或者定義的宏都放在.inc的文件中
然后ASM文件使用include xxxx.inc 包含你自己的.inc文件即可
3.一段完整的win32匯編代碼框架
上面的偽指令已經講完了,這里寫一段完整的匯編代碼
.386 ;定義為386的匯編程序 .model FLAT,stdcall ;內存為平坦模式,默認調用約定stdcall option casemap:none ;增加選項,區分大小寫 .const ;定義常量區(這些應該放到.inc文件中這里不妨了,放的話就是拷貝過去,然后這個文件引用即可) g_szTitle db "Title",0 ;win32字符串結尾都是0結尾了 g_szMsg db "Hello 51asm.com",0 .data ;定義數據區 .code ;定義代碼區 START: ..... ;你的核心代碼 end START
三丶編譯連接Win32匯編程序
在32位中,編譯匯編程序和連接匯編程序就有點不同了
1.編譯:
在CMD中輸入
ml /c /coff 文件名.asm
上面說過,我們在32位下,有了PE文件格式(exe文件),而PE文件格式是 COFF格式,也稱作為PE
編譯幫助:
表示我們要編譯為一個PE的obj格式
編譯我們的代碼
然后出現這個,表示編譯成功,看下obj文件
如果我們不加,就會編譯成了16位的了,而連接的時候就會找16位的連接器,就會出錯,顯示找不到入口點的
錯誤
2.連接
連接的時候,不能在使用16位的連接器了,這里可以使用VC自帶的link,沒有沒有關系,我會在每天的資料中上傳所用的工具
連接選項(對我們有用的)
這個對我們有用,因為在32系統下,有了窗口的概念的,表示你要連接成什么程序,控制台的還是窗口的
假設我們要連接為一個控制台的程序
link /subsystem:console 文件名.obj ;連接成一個控制台的程序
代碼沒有出錯,則正常顯示
四丶寫一個窗口版本探彈消息的程序,並用OllyDbg去分析
1.編寫窗口程序
我們基於上面的32位程序的框架,寫一個簡單版本的信息框,彈出一個消息,把我們常量區的數據彈出來
並用OlleyDbg去分析
首先查一下MessageBox的用法
我們知道了,第一個參數是窗口句柄,沒有我們可以給NULL 而NULL 在匯編中沒有,我們就用宏定義 (EQU)
第二個參數是一個0結尾字符串的首地址,那么在匯編中可以通過 offset偽指令,把常量區的地址給它
第三個參數一樣
第四個參數是顯示彈框的按鈕風格,我們一般使用MB_OK,而MB_OK 是0,匯編中也沒有,所以我們定義一下
匯編代碼例子:
.386 .model FLAT,stdcall ;設置內存為平坦模式,默認調用約定STDCALL option casemap:none ;區分大小寫 NULL EQU 0 ;定義NULL MB_OK EQU 0 ;定義為0 MessageBoxA PROTO hWnd:DWORD, :DWORD,:DWORD,:DWORD ;函數聲明,聲明為有4個參數,默認調用約定是Stdcall .const g_szTitle db "Title",0 g_szMsg db "Hello www.w1x8.com ",0 .data .code START: invoke MessageBoxA,NULL, offset g_szMsg,offset g_szTitle,MB_OK;調用API end START
這里使用的是MessageBoxA,因為操作系統分為寬字節和Ascii碼版本
這里編譯的時候命令還是上面的那個命令,(ml /c /coff 文件名.obj)
連接的時候不一樣了
連接的時候我們需要鏈接為Windows窗口程序,而且最重要的一點就是MessageBoxA的實現代碼在User32.lib中,所以也要一並的加入進來
link /subsystem:windows 文件名.obj user32.lib ;注意,user32.lib放到匯編程序所在的目錄下
看下編譯出來的程序
2.使用OllyDbg分析
把我們的exe放到OllyDbg中分析
這里先說下常用的快捷鍵

可以看到我們的匯編代碼都在這里,我們F8單步執行,找到第一個Call,也就是MessageBoxA,F8走到Call的地方
F7進入
然后看下,他也是一樣壓棧ebp,出棧ebp,然后看下棧區,
看下EBP的位置(這里的EBP是執指向棧頂的,因為我們 mov ebp,esp了)
然后棧的格式和我們前邊講的是一樣的
棧的當前結構:
保存棧底的值(ebp)
返回地址
參數一
參數二
參數三
參數四
3.使用OD把我們的標題修改了成輸出的消息,把以前的標題,修改為輸出的消息(有點繞,就是兩個互換輸出)
思路:
我們把壓棧的順序修改一下
雙擊,把當前的壓棧的順序修改一下
push 1.00402008 修改為: push 1.0040200E push 1.0040200E 修改為: push 1.00402008
然后選擇這兩行,右鍵 -> 復制到可執行文件 然后選擇 選擇所有復制(相當於修改后的EXE)
最后彈出了個新的,我們點擊保存文件即可
修改后的EXE
五丶關於PE文件的那點事
上面說了我們的信息會保存在exe文件中(也就是PE)我們用WinHex(16進制編輯器)查看一下
1.32位執行的開始
我們的EXE在這里上面的位置,都是為了兼容16位的,而真正的32位程序是從PE這里開始執行的,
上面的某些字段保存了PE所在的偏移,比如PE所在的位置是C8,那么上面的字段就會有C8保存,因為軟件已啟動
會根據這個偏移尋找PE文件的位置,這里C8位置在3C的位置
那么就代表我們不改變這個值,其余的隨便修改,那么不影響32位程序的使用,我們修改一下
修改后還是能運行的
這個匯編程序會崩潰,原因是我們沒有寫退出,比如16位匯編中的退出是
mov ah,4c00h int 21h
這里就不寫了
2.32匯編中簡單的Dll劫持和API HOOK(思想)
注入方法很多,這里有個簡單的,比如我們上面調用了一個MessageBoxA
他是在Lib中尋找dll的路徑,以及MessageBoxA在那個Dll中
我們使用的這個Dll是動態的Dll,里面記錄了Dll所在的路徑,以及導出函數
而我們匯編中剛在這樣用則是把user32.lib中當前調用的MessageBoxA所在的Dll路徑,以及Dll導出函數的信息
連接到EXE文件中
所以說EXE文件中也會保存Dll的信息
我們使用WinHex查找一下EXE中是否有MessageBoxA
CTRL + F 查找,輸入字符串
找到了所在的位置,我們把USER32.DLL改下名
可以看到,他找不到AAER32.dll,如果厲害的自己可以寫一個AAER32.DLL,(當然細節很多,這里只是簡單的思想)
我們就可以把DLL劫持了
比如我們把前邊的函數名字修改了,那么如果你厲害,可以寫個相同函數,就形成了APIHOOK
課堂資料下載地址:
當前第一課課程資料: 鏈接:http://pan.baidu.com/s/1geLWBzP 密碼:2ko2
當前32位匯編所有課程資料: 鏈接:http://pan.baidu.com/s/1geC3iNL 密碼:0hpc