看雪学院课程《汇编语言详解与二进制漏洞初阶》笔记
前言和聲明
安全工程師這條路任重道遠。如今國際形勢復雜,網絡戰一旦爆發,安全勢力弱的一方很快會處于競爭的下風,加上國家的安全人才缺口過大,我輩則應當肩挑重擔,為祖國安全盡一份力。
本博客是博主在學習看雪學院《匯編語言詳解與二進制漏洞初階》課程的筆記,筆記中大部分是對課程作業的解答,少部分是博主補充的知識。
如若轉載,請聲明出處。謝謝。
與廣大有心朝安全方向前進的網友們共勉之。
另注:本博客會持續更新到課程學習完畢。
匯編語言部分
學習匯編語言的意義
- 開發遇到bug時調試更加便利。在用高級語言進行調試時往往會遇到一些非常難以調試出來的bug,此時程序員若能將高級語言代碼反匯編成匯編代碼來進行調試則可以提高調試效率。
- 逆向分析時的代碼閱讀。逆向分析時,所分析的軟件對于分析者來說其實是一個“黑盒”,此時若不懂匯編語言則將寸步難行。
- 對某些特殊技術的使用。用高級語言編寫的程序往往占較大的內存,當程序員編寫shellcode、殼等代碼時,要盡量壓縮其大小,此時匯編語言則大有用處,因為使用匯編語言編寫程序時可以精確到字節。
shellcode: 能在一個完整程序中的任意位置運行的代碼。
EFLAGS寄存器
EFLAGS寄存器包含了獨立的二進制位,用于控制CPU操作,或是反應一些CPU操作的結果。有些指令可以測試和控制這些單獨的處理器標志位。
- 中文圖
- 英文圖
這篇文章對EFLAGS寄存器的講解非常詳細,請點擊這里🔗
各寄存器的名稱及其主要用途
| 代號 | EAX | EBX | ECX | EDX | ESI | EDI | EBP | ESP |
| 主要用途 | 算術運算、存儲中間結果、函數返回值 | 基地址指針 | 循環計數、移位操作計數、重復操作計數 | 乘除運算、存儲中間結果 | 存儲指針、字符串指令的源操作數指針 | 存儲指針、字符串指令的目的操作數指針 | 基址指針寄存器(extended base pointer),其內存放著一個指針,該指針永遠指向系統棧最上面一個棧幀的底部 | 棧指針寄存器(extended stack pointer),其內存放著一個指針,該指針永遠指向系統棧最上面一個棧幀的棧頂。 |
內存尋址范圍
給出幾個計算例子。
1、某計算機字長32位,存儲容量8MB。按字編址,其尋址范圍為(0~2M-1) 計算步驟:8MB字節=810241024*8位。所以8MB/32位=2M.
2、某計算機字長32位,其存儲容量為4MB,若按半字編址,它的尋址范圍是(0-2M-1)計算步驟:若按半字就是16位了 4MB=410241024*8位,所以4MB/16 = 2M;
3、字長為32位.存儲器容量為64KB.按字編址的尋址范圍是多少計算步驟:64K字節=64*1024*8位. 所以64KB/32位=(64*1024*8)/32=16*1024=16K 故尋址范圍為: 0-16K-1
4、某機字長32位,存儲容量1MB,若按字編址,它的尋址范圍是什么?
解釋:容量1M=210241024 位 一個字長是32 位
所以,尋址范圍是二者相除=256K
內存的五種表現形式
聲明:圖片來源于🔗
若想加深對內存表現形式的理解,請點擊這里🔗
數據存儲模式(大小端模式)
下面以unsigned int value = 0x12345678為例,分別看看在兩種字節序下其存儲情況,我們可以用unsigned char buf[4]來表示value
- Big-Endian: 低地址存放高位,如下:
高地址
---------------
buf[3] (0x78) – 低位
buf[2] (0x56)
buf[1] (0x34)
buf[0] (0x12) – 高位
---------------
低地址 - Little-Endian: 低地址存放低位,如下:
高地址
---------------
buf[3] (0x12) – 高位
buf[2] (0x34)
buf[1] (0x56)
buf[0] (0x78) – 低位
--------------
低地址
| 低地址 | 0x4000 | 0x78 | 0x12 |
| ↓ | 0x4001 | 0x56 | 0x34 |
| ↓ | 0x4002 | 0x34 | 0x56 |
| 高地址 | 0x4003 | 0x12 | 0x78 |
使用VS編寫匯編程序
- VS環境配置
本課程中教學用VS2015,具體操作請看🔗。
若使用VS2019則與VS2015操作有所不同,VS2019可以直接在搜索框中搜索“生成自定義”、“屬性”等關鍵詞,操作也很方便。 - 一個匯編程序的大致結構
匯編語言中的數學運算
在匯編語言中,有 加 減 乘 除 自增 自減 六種運算。
- 加法
- 減法
- 乘法
- 除法
- 自增
- 自減
堆棧操作
理論
- 什么是棧?
學過數據結構一般都知道,后進先出數據結構者為棧。
千萬要牢記,棧的基本特點是:后進先出。
就跟你把羽毛球裝在球筒里面一樣,最后放進去的肯定是第一個拿出來的。
- 棧操作指令
最基本的無非就是 PUSH 和 POP 。
- 棧的作用
棧的空間有限,所以只能存儲少量數據;
所謂保存寄存器環境,也就是說我們對寄存器進行了一些操作,但我想在操作完成之后恢復程序的全部功能,此時寄存器的內容應該也要跟原來一樣,故稱之為保存寄存器環境。
實踐
使用OllyDbg實現棧操作。
-
將任意PE文件拖入OllyDbg,如圖修改前四行代碼:
-
按F8執行,將eax的內容push進棧
-
再按F8,將ebx的內容push進棧
-
再按F8,將棧頂元素彈出到ecx
-
再按F8,棧頂元素彈出至edx
再強調一次,棧的基本特點是:后進先出。
數據移動指令
- MOV 指令
- LEA 指令
- XCHG 指令
作業:用VS和OllyDbg復現課程中代碼,熟練掌握上述三個指令的使用方法。
比較指令
- CMP 指令
- TEST 指令
JCC條件轉移指令
-
JCC 表
-
JMP 指令
本例子可供總結 jmp 指令的使用方法。
這段匯編代碼給寄存器eax賦初值為0,
然后進入jmpflag段并執行add eax,1語句,
對eax加1之后再跳轉(jmp)回jmpflag段起始語句add eax,1,
又對eax加1。
這樣子就形成了一個無限循環。 -
JZ/JE 指令
本例子可供總結 jz/je 指令的使用方法。
代碼解釋:
給eax賦初值為5,
之后使用 cmp 指令(上文有提到其作用)與6比較,
不等,
則不執行 jz 指令。 -
JNE 指令
本例子可供總結 jne 指令的使用方法。
代碼解釋:
給eax賦初值為5,
之后使用 cmp 指令(上文有提到其作用)與6比較,
不等,
則執行 jz 指令。
串操作指令
匯編中的串應理解為字節串、字串等,應該屬于數組的范疇,可以進行掃描查找、比較、傳送(填充)等操作。
- MOVS 指令
- STOS 指令
- REP 指令
重復操作字符串指令。
CALL和RETN指令
CALL指令和RETN指令是配合起來寫函數的指令。
理論
- CALL 指令
可以調用任意地址,并且將call指令的下一條指令的地址壓入棧中。 - RETN 指令
將棧頂元素(該元素是執行call語句時壓入的call語句下一條語句的地址)彈出,返回該地址,所以它的功能是使程序執行完call指令繼續往下執行。
實踐
- 修改程序第1條語句為 call 指令語句,修改77EF3CC3處語句為mov eax,1(修改為其他語句也可以,無所謂的),修改77EF3CC8處語句為 retn
- 執行call 指令
是跳轉到紅色框框那里,不是跳轉到箭頭的位置。。。 - 執行retn指令
右邊第一個框框中的77EF3CA0已經彈出了,所以在棧中看不到它。
匯編中的函數
過程調用的方式要根據編譯器而定,下面圖片中的只是一個例子。
寄存器數量有限,為防止出現寄存器不夠用的情況,所以出現了棧傳參。
- 寄存器傳參
可根據此程序代碼動手實踐,觀察各相關寄存器的內容的變化情況。 - 堆棧傳參
[esp+4]是第二參數,[esp+8]是第一參數。
為什么呢?因為棧的特點是先進后出,后進先出!所以越靠近棧底的元素(地址越高)的元素越先進棧!
那么為什么要+4和+8呢?因為一個整數占4個字節,所以棧頂元素的首地址應該為esp+4。 - 作業
Win32匯編入門
理論
實踐
注意!!!
更改: 函數MessageBoxA參數部分,第1個push 0是將uType的參數值壓入棧
作業:
C語言部分
C語言概述
寫程序的過程:
定義程序目標
設計程序
編寫代碼
編譯
運行程序
調試程序
維護和修改程序
函數指針
即指向函數的指針。
- 代碼
直觀展示什么是指向函數的指針。
在上圖中,下面兩行代碼是相等的。
MyAdd是函數add的指針。
- 反匯編
從匯編層面認識什么是指向函數的指針。
補充:
dword ptr [myadd]是什么意思?
dword 雙字 就是四個字節
ptr pointer縮寫 即指針
[]里的數據是一個地址值,這個地址指向一個雙字型數據
比如mov eax, dword ptr [12345678] 把內存地址12345678中的雙字型(32位)數據賦給eax
指針函數
即返回值是指針的函數。
函數分析
"Hello world!"程序分析
建立新棧時將新棧刷為CCCCCC…h,很多程序的匯編代碼中都能看到CCCCCC…h。
建立新棧是程序健壯性的體現。
作業
記住函數分析的內容。
C語言命名規則
逆向入門
尋找main函數
- 程序版本
- Release
編譯器會對程序進行優化,程序占存較小,但調試相對困難。發布程序時一般為此版本。 - Debug
少量優化,程序占存較大,方便調試。
- Release
實踐
OllyDebug
Debug版本程序
#include <stdio.h>int main() {printf("das");return 0; }- 以管理員身份運行OllyDebug
Release版本
看視頻如何操作
視頻 3:14~ 🔗
- OllyDebug
- 第一種方法
- 第一種方法
- 第二種方法
IDA
使用IDA打開,IDA會自動分析出main函數,按F5進入main函數。
雙擊上圖高亮部分,即可查看main函數匯編代碼。
修改內存中的數據
- 直接修改待修改內存中的數據
- 修改任意地址處的數據,再跳轉到該地址,從而間接修改待修改內存中的數據
思考:
某些地址中的數據不能隨便修改,因為可能會導致程序運行時所需的必要數據缺失而損壞程序。
修改跳轉
修改跳轉進而改變程序執行流程。
直接上實踐例子
問題:如何通過修改跳轉達到就算鍵入0也打印"True!"
程序為Release x86版本。
注意!!圖中修改匯編代碼應該修改為jmp short 008110AE
進行上述操作之后,無論是否鍵入0,都回跳轉到008110AE處,這樣就只會打印"True!"了。
有個小問題,我們要做的第一步其實是找到main函數,但在這節課里并沒用到上面筆記中的方法,以后補充。
驅動入門
第一個驅動程序
- 創建驅動項目
- 程序編寫
- 程序
- 程序的配置
- 尋找驅動程序的存放位置
- 程序運行
第一個驅動程序
代碼如下:
#include <ntddk.h>//DriverUnload是驅動的卸載函數,負責清理資源,再驅動卸載的時候調用 VOID myDriverUnload(PDRIVER_OBJECT pDriverObject) {//指明這個參數是我故意不用的,不是我忘了,告知編譯器不要警告我UNREFERENCED_PARAMETER(pDriverObject);//打印信息,證明我們已經成功地卸載了這個驅動DbgPrint("Unload success!"); }//DriverEntry相當于三環程序,也就是應用層程序的main函數 //pDriverObject驅動對象指針 //pRegPath注冊表路徑指針 NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegPath) {//指明這個參數是我故意不用的,不是我忘了,告知編譯器不要警告我UNREFERENCED_PARAMETER(pRegPath);//指定驅動卸載函數pDriverObject->DriverUnload = myDriverUnload;//打印信息,證明我們的驅動啟動成功了DbgPrint("Hello World!");//返回一個成功的狀態函數return STATUS_SUCCESS; } 與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的看雪学院课程《汇编语言详解与二进制漏洞初阶》笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 面向智能电网的电力大数据存储与分析应用
- 下一篇: DPDK:不仅是加速