第21天 保護操作系統
2020.4.24
1. 攻克難題——字符串顯示API(harib18a)
-
在harib17h中hello.hrb運行出現異常。應該是內存段的鍋。
-
顯示單個字符時,用[CS:ECX]的方式特意指定了CS(代碼段寄存器),因此可以成功讀取msg的內容。但是在顯示字符串時,由於無法指定段地址,程序誤以爲是DS從而從完全錯誤的內存地址中讀取了內容,碰巧讀出的內容是0,於是什麼也沒顯示。
-
因此,需要修改API,使API能夠將應用程序傳遞的地址解釋成爲代碼段內的地址。
-
執行應用程序的過程:
-
hrb_api並不知道代碼段的起始位置(指針p)位於哪個內存地址,但是cmd_app知道。沒有辦法從cmd_app向hrb_api直接傳遞數據,因此,將內存地址0xfe8存放指針p。 此前,0xfec存放着cons。
-
修改cmd_app:
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline) { …… if (finfo != 0) { /* 找到文件的情況 */ p = (char *) memman_alloc_4k(memman, finfo->size); *((int *) 0xfe8) = (int) p; /*將指針p寫入內存地址*/ file_loadfile(finfo->clustno, finfo->size, p, fat, (char *) (ADR_DISKIMG + 0x003e00)); set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER); farcall(0, 1003 * 8); memman_free_4k(memman, (int) p, finfo->size); cons_newline(cons); return 1; } /* 沒有找到文件的情況 */ return 0; }
-
修改hrb_api:
void hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax) { int cs_base = *((int *) 0xfe8); /*從特定內存地址讀出指針p指向的地址*/ struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec); if (edx == 1) { cons_putchar(cons, eax & 0xff, 1); } else if (edx == 2) { cons_putstr0(cons, (char *) ebx + cs_base); } else if (edx == 3) { cons_putstr1(cons, (char *) ebx + cs_base, ecx); } return; }
- 在執行應用程序時,CS存放的值其實就是指針p。
- 沒有從CS中直接取出p。而是通過上述方式將應用程序傳遞的地址解釋成爲代碼段內的地址。
- ebx內存放的是指向字符串的指針(存放一個內存地址),char可以將其強制類型轉換。
-
make
後用VMware運行:
- 果然是內存段的鍋!
2. 用C語言編寫應用程序(harib18b)
-
使用C語言編寫應用程序a.c:
void api_putchar(int c); void HariMain(void) { api_putchar('A'); return; }
- 注意,這裏的HariMain和bootpack.c中的HariMain不是一回事。用C語言編寫程序時,開始執行的入口就是HariMain。bootpack.c和a.c是完全獨立編譯的。
- 需要編寫api_putchar函數(這個函數是應用程序的函數,不是OS的)。
- api_putchar函數需要使用彙編語言編寫,以C語言來調用。
- api_putchar函數的功能是:向EDX和AL賦值,並調用INT 40。
-
編寫api_putchar(a_nask.nas):
[FORMAT "WCOFF"] ; 生成對象文件的模式 [INSTRSET "i486p"] ; 表示使用486兼容指令集 [BITS 32] ; 生成32位模式機器語言 [FILE "a_nask.nas"] ; 源文件名信息 GLOBAL _api_putchar [SECTION .text] _api_putchar: ; void api_putchar(int c); MOV EDX,1 MOV AL,[ESP+4] ; c INT 0x40 RET
- api_putchar需要和a.c的編譯結果進行連接,因此使用對象文件模式。
-
修改Makefile:
a.bim : a.obj a_nask.obj Makefile $(OBJ2BIM) @$(RULEFILE) out:a.bim map:a.map a.obj a_nask.obj a.hrb : a.bim Makefile $(BIM2HRB) a.bim a.hrb 0
- 這裏借鑑了生成bootpack.hrb的方式。至於爲什麼使用這種方式,以後會講到。
-
make
後生成一個72字節的a.hrb(命名只顯示1個字符,卻比顯示5個字符的hello.hrb還要大,這也見怪不怪,畢竟是C語言寫的嘛)。- 用VMware運行試試:
- BOOM,出錯了。
- 用VMware運行試試:
-
修改BUG方法:
- 用二進制編輯器打開a.hrb:
- 將開頭的6個字節換成
E8 16 00 00 00 CB
。
- 用二進制編輯器打開a.hrb:
-
原理:
E8 16 00 00 00 CB
這6個字節相當於下面代碼經過nask彙編之後的結果:[BITS 32] CALL 0x1b RETF
- 0x1b相當於.hrb文件中的HariMain的地址。
- 聯想到在asmhead.nas中,最後調用bootpack.hrb時有一句:
JMP DWORD 2*8:0x0000001b
- 這個0x1b是一樣的。
- 執行完畢以後,需要一個far-RET,來返回命令行。
-
然後
make
用VMware運行:
- 成功了!
-
趁熱打鐵,編寫應用程序hello3.c:
void api_putchar(int c); void HariMain(void) { api_putchar('h'); api_putchar('e'); api_putchar('l'); api_putchar('l'); api_putchar('o'); return; }
- 相應地也需要修改Makefile,做法同a.c。這裏不再贅述。
make
後hello3.hrb剛好100字節。
-
每次都要修改應用程序可執行文件.hrb的前6字節的數據很是麻煩。因此,修改cmd_app函數,更改.hrb寫入內存中的前6字節的數據。
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline) { …… if (finfo != 0) { /* 找到文件 */ p = (char *) memman_alloc_4k(memman, finfo->size); *((int *) 0xfe8) = (int) p; file_loadfile(finfo->clustno, finfo->size, p, fat, (char *) (ADR_DISKIMG + 0x003e00)); set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER); if (finfo->size >= 8 && strncmp(p + 4, "Hari", 4) == 0) { p[0] = 0xe8; p[1] = 0x16; p[2] = 0x00; p[3] = 0x00; p[4] = 0x00; p[5] = 0xcb; } farcall(0, 1003 * 8); memman_free_4k(memman, (int) p, finfo->size); cons_newline(cons); return 1; } /* 未找到文件 */ return 0; }
- 凡是通過
bim2hrb
生成的hrb文件,其4~7字節一定是Hari
。
- 凡是通過
-
make
後用VMware運行:
3. 保護操作系統(1)(harib18c)
-
OS的應用程序,可能是OS的開發者編寫的,也有可能是其他人編寫的。這些應用程序有善意的,也有惡意的。
-
應用程序不論善意還是惡意,不可避免有BUG,而某些BUG會對OS造成破壞。比如,擅自刪除重要文件,使其他任務運行產生異常,造成OS司機而不得不重啓等。
-
因此,需要花點功夫讓OS變得更加安全。x86架構的CPU爲我們提供了保護OS的功能。只要運用這些功能完事操作系統的安全性,就可以最大限度地保護OS。目標:沒有漏洞。
-
製作一個惡意應用程序crack1.c:
void HariMain(void) { *((char *) 0x00102600) = 0; return; }
- 向內存地址0x00102600寫入0。0x00102600是:
struct FILEINFO *finfo = (struct FILEINFO *) (ADR_DISKIMG + 0x002600);
- finfo指向的地址是0x00102600。
- 因此將無法獲取磁盤文件列表。
- 向內存地址0x00102600寫入0。0x00102600是:
-
make
後移用VMware運行:
- 輸入crack1後,貌似沒有反應。
- 接着輸入dir,結果出BUG了。
- 再輸入一次crack1,顯示“Bad command.”。
- 這在意料之中。
-
重啓一下系統就恢復了。現在,只有重啓能解決這個問題。因此,對於這種程序,我們必須在它造成破壞之前強行終止它才行。
4. 保護操作系統(2)(harib18d)
-
crack1所做的事情,無非就是訪問(修改)了本該由OS來管理的內存空間。
-
解決方法:
- 爲應用程序提供專用的內存空間,並且禁止其訪問其他內存空間。
-
具體做法:
- 爲應用程序創建應用程序專用的數據段,並在應用程序運行期間,將DS和SS指向該段地址。
- SS: 棧段寄存器(stack segment)
- DS: 數據段寄存器(data segment)
- 段寄存器資料:
- 代碼段寄存器CS(Code Segment):存放當前正在運行的程序代碼所在段的段基址,表示當前使用的指令代碼可以從該段寄存器指定的存儲器段中取得,相應的偏移量則由IP提供。
- 數據段寄存器DS(Data Segment):指出當前程序使用的數據所存放段的最低地址,即存放數據段的段基址。
- 堆棧段寄存器SS(Stack Segment):指出當前堆棧的底部地址,即存放堆棧段的段基址。
- 附加段寄存器ES(Extra Segment):指出當前程序使用附加數據段的段基址,該段是串操作指令中目的串所在的段。
- 爲應用程序創建應用程序專用的數據段,並在應用程序運行期間,將DS和SS指向該段地址。
-
規定:
- OS用代碼段:2*8
- OS用數據段:1*8
- 應用程序用代碼段:1003*8
- 應用程序用數據段:1003*4
- (3*8~1002*8爲TSS所使用的段)
- 這裏可以聯想到asmhead.nas(第8天)中除了CS外的段寄存器都賦值1*8,CS賦值2*8。
-
修改console.c中的cmd_app:
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline) { …… char name[18], *p, *q; …… if (finfo != 0) { /* 找到文件的情況 */ p = (char *) memman_alloc_4k(memman, finfo->size); /*代碼段*/ q = (char *) memman_alloc_4k(memman, 64 * 1024); /*數據段*/ *((int *) 0xfe8) = (int) p; file_loadfile(finfo->clustno, finfo->size, p, fat, (char *) (ADR_DISKIMG + 0x003e00)); set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER); /*註冊應用程序代碼段段*/ set_segmdesc(gdt + 1004, 64 * 1024 - 1, (int) q, AR_DATA32_RW); /*註冊應用程序數據段*/ …… start_app(0, 1003 * 8, 64 * 1024, 1004 * 8); /*調用函數start_app*/ memman_free_4k(memman, (int) p, finfo->size); memman_free_4k(memman, (int) q, 64 * 1024); cons_newline(cons); return 1; } /* 未找到文件 */ return 0; }
- 暫時先爲應用程序數據段專用空間分配64KB的內存空間吧。
- 用start_app函數代替原先的farcall。這個start_app是用來啓動應用程序的函數。原先的farcall只是執行了far-CALL,現在的start_app需要設置ESP、DS和SS。
-
添加start_app(naskfunc.nas):
_start_app: ; void start_app(int eip, int cs, int esp, int ds); PUSHAD ; 將32位寄存器的值全部保存起來 MOV EAX,[ESP+36] ; 應用程序用EIP MOV ECX,[ESP+40] ; 應用程序用CS MOV EDX,[ESP+44] ; 應用程序用ESP MOV EBX,[ESP+48] ; 應用程序用DS/SS MOV [0xfe4],ESP ; OS用ESP CLI ; 在切換過程中禁止中斷請求 MOV ES,BX MOV SS,BX MOV DS,BX MOV FS,BX MOV GS,BX MOV ESP,EDX ; 注意,此時ESP已經改變位置,下面的PUSH是跟着ESP的。 STI ; 切換完成後恢復中斷請求 PUSH ECX ; 用於far-CALL的PUSH(CS) PUSH EAX ; 用於far-CALL的PUSH(EIP) CALL FAR [ESP] ; 執行far-CALL調用應用程序 ; 應用程序結束後返回此處 MOV EAX,1*8 ; OS用的DS/SS CLI ; 從應用程序切換回OS MOV ES,AX MOV SS,AX MOV DS,AX MOV FS,AX MOV GS,AX MOV ESP,[0xfe4] STI ; 恢復中斷 POPAD ; 恢復之前的寄存器值 RET
- 在向SS和DS賦值的同時,也向ES、FS和GS賦值,以防萬一。
- 將OS用的ESP保存在0xfe4這個地址(0xfec:cons;0xfe8:指針p),以便程序返回時OS正確使用。
- 注意PUSH是根據當前ESP來的,ESP永遠指向當前棧的棧頂。
-
應用程序調用API時會產生中斷,進而調用asm_hrb_api,進而調用hrb_api。調用asm_hrb_api時,需要將段地址設回OS用的段。因此修改asm_hrb_api:
_asm_hrb_api: ; INT 40.現在就是處於禁止中斷 PUSH DS PUSH ES PUSHAD ; 用於保存寄存器的值的PUSH MOV EAX,1*8 MOV DS,AX ; 先只將DS設爲OS用 MOV ECX,[0xfe4] ; 取出OS用的ESP ADD ECX,-40 MOV [ECX+32],ESP ; 保存應用程序的ESP MOV [ECX+36],SS ; 保存應用程序的SS ; 將PUSH後的值複製到系統棧 MOV EDX,[ESP ] MOV EBX,[ESP+ 4] MOV [ECX ],EDX ; 複製傳遞給hrb_api MOV [ECX+ 4],EBX ; 複製傳遞給hrb_api MOV EDX,[ESP+ 8] MOV EBX,[ESP+12] MOV [ECX+ 8],EDX ; 複製傳遞給hrb_api MOV [ECX+12],EBX ; 複製傳遞給hrb_api MOV EDX,[ESP+16] MOV EBX,[ESP+20] MOV [ECX+16],EDX ; 複製傳遞給hrb_api MOV [ECX+20],EBX ; 複製傳遞給hrb_api MOV EDX,[ESP+24] MOV EBX,[ESP+28] MOV [ECX+24],EDX ; 複製傳遞給hrb_api MOV [ECX+28],EBX ; 複製傳遞給hrb_api MOV ES,AX ; 將剩餘的段寄存器也設爲OS用 MOV SS,AX MOV ESP,ECX STI ; 恢復中斷請求 CALL _hrb_api ; 有8個參數 MOV ECX,[ESP+32] ; 取出應用程序的ESP MOV EAX,[ESP+36] ; 取出應用程序的SS CLI ; 切換回應用程序 MOV SS,AX MOV ESP,ECX POPAD POP ES POP DS IRETD ; 這個命令會自動執行STI
- PUSHAD是將值複製到應用程序的棧中,因此OS的系統棧的hrb_api無法讀取這些值,需要把他們複製過來。
- 這次,將FS和GS的設置省略了。
-
應用程序運行中也會產生中斷請求(比如鼠標,鍵盤等中斷請求),中斷產生後悔調用asm_inthandler20等函數,這些函數也是屬於OS。因此,的需要對DS和SS進行切換。
-
修改asm_inthandler20函數:
_asm_inthandler20: PUSH ES PUSH DS PUSHAD MOV AX,SS CMP AX,1*8 JNE .from_app ; 當OS活動產生中斷時的情況和之前差不多 MOV EAX,ESP PUSH SS ; 保存中斷時的SS PUSH EAX ; 保存中斷時的ESP MOV AX,SS MOV DS,AX MOV ES,AX CALL _inthandler20 ADD ESP,8 ; 按照順序畫一下棧,就可以知道爲什麼+8了 POPAD POP DS POP ES IRETD .from_app: ; 當應用程序活動時發生中斷 MOV EAX,1*8 MOV DS,AX ; 先只將DS設定爲OS用 MOV ECX,[0xfe4] ; 取出OS的ESP ADD ECX,-8 MOV [ECX+4],SS ; 保存中斷時的SS MOV [ECX ],ESP ; 保存中斷時的ESP MOV SS,AX MOV ES,AX MOV ESP,ECX CALL _inthandler20 ; inthandler20需要一個參數esp POP ECX POP EAX MOV SS,AX ; 將SS設迴應用程序用 MOV ESP,ECX ; 將ESP設迴應用程序用 POPAD POP DS POP ES IRETD
- 以
.
開頭的標籤名,這是一種被稱爲本地標籤的特殊標籤,基本上和普通標籤一致,區別在於即使標籤名和其他函數中的標籤名重複,系統也能將它們區分開來。
- 以
-
修改asm_inthandler21和asm_inthandler2c,跟上面的asm_inthandler20大同小異。
-
以上彙編語言程序懂個大概即可,因爲CPU實際上本身有自動進行這種複雜切換的功能,最終還是要使用CPU本身的功能來實現。
-
現在的harib18d是應用程序棧和系統棧是分開的。
-
make
後用VMware運行:- 運行應用程序crack1.hrb:
- 很不幸,這次的BUG直接讓OS崩潰了。
- 這大概是因爲我們區分了系統棧和應用程序棧,當應用程序crack1強行訪問由OS管理的內存段時,CPU發現不對勁,給終止了。
- 因此,接下來應該實現,當發現不對勁時,強制結束crack1。這就是harib18e的工作了。
- 運行應用程序crack1.hrb:
5. 對異常的支持(harib18e)
-
實現強制結束應用程序的功能。
-
要想強制結束程序,只要在中斷號0x0d中註冊一個函數即可。在x86架構規範中,當應用程序試圖破壞OS,或者違背OS的設置時,就會自動產生0x0d中斷。該中斷也被稱爲異常。
-
編寫asm_inthandler0d函數:
_asm_inthandler0d: STI PUSH ES PUSH DS PUSHAD MOV AX,SS CMP AX,1*8 JNE .from_app ; 當OS活動時產生中斷的情況和之前差不多 MOV EAX,ESP PUSH SS ; 保存產生中斷時的SS PUSH EAX ; 保存產生中斷時的ESP MOV AX,SS MOV DS,AX MOV ES,AX CALL _inthandler0d ADD ESP,8 POPAD POP DS POP ES ADD ESP,4 ; INT 0x0d 中需要這句 IRETD .from_app: ; 當應用程序活動時產生中斷 CLI MOV EAX,1*8 MOV DS,AX ; 先只將DS設爲OS用 MOV ECX,[0xfe4] ; 取出OS用的ESP ADD ECX,-8 MOV [ECX+4],SS ; 保存產生中斷時的SS MOV [ECX ],ESP ; 保存產生中斷時的ESP MOV SS,AX MOV ES,AX MOV ESP,ECX STI CALL _inthandler0d CLI CMP EAX,0 JNE .kill POP ECX POP EAX MOV SS,AX ; 將SS恢復爲應用程序用 MOV ESP,ECX ; 將ESP恢復爲應用程序用 POPAD POP DS POP ES ADD ESP,4 ; INT 0x0d 中需要這句 IRETD .kill: ; 將應用程序強制結束 MOV EAX,1*8 ; OS用的DS/SS MOV ES,AX MOV SS,AX MOV DS,AX MOV FS,AX MOV GS,AX MOV ESP,[0xfe4] ; 強制返回start_app時的ESP STI ; 切換完成後恢復中斷請求 POPAD ; 恢復事先保存的寄存器值 RET
- 這個函數與asm_inthandler20的主要區別在於增加了STI/CLI這樣的控制中斷請求禁止、恢復的指令和根據inthandler0d的結果來執行強制結束應用程序的操作。
- 在強制結束時,儘管中斷處理完成了,但卻沒有使用IRETD指令,而且還把棧強制恢復到start_app時的狀態,使程序運行返回到cmd_app。
- 這種奇怪的做法是沒有問題的。
-
編寫中斷處理程序inthandler0d(console.c):
int inthandler0d(int *esp) { struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec); cons_putstr0(cons, "\nINT 0D :\n General Protected Exception.\n"); return 1; /* 強制結束程序 */ }
- 只要應用程序運行過程中產生INT 0x0d中斷,inthandler0d函數一定會返回1,那麼應用程序一定會被強制結束。
- 但是OS產生INT 0x0d中斷時,只是打印提示信息。
- 顯示信息是“General Protection Exception”,意思是一般保護異常。處理一般保護異常,還有一些其他特殊的異常,這些特殊的異常並不是由應用程序的bug和破壞行爲所引發的,而是其他種類的異常情況,特殊的異常會由INT 0x0d以外的中斷來處理。
-
將asm_inthandler0d註冊到IDT中:
void init_gdtidt(void) { …… /* IDT的設置 */ set_gatedesc(idt + 0x0d, (int) asm_inthandler0d, 2 * 8, AR_INTGATE32); /*將asm_inthandler0d中斷處理程序註冊到0x0d號中斷*/ set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32); set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32); set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32); set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32); set_gatedesc(idt + 0x40, (int) asm_hrb_api, 2 * 8, AR_INTGATE32); return; }
-
make
後用VMware運行:- 執行可執行文件crack1.hrb:
- 繼續輸入指令dir:
- 顯然,應用程序crack1企圖破壞操作系統,由於harib18e是在harib18d的基礎上添加了INT 0x0d中斷以強制結束應用程序,所以,當CPU發現應用程序企圖破壞OS時(訪問不屬於它管理的內存空間),立即產生了0x0d中斷,然後強制回到cmd_app中。
- 執行可執行文件crack1.hrb:
6. 保護操作系統(3)(harib18f)
-
OS會指定應用程序用的DS,因此應用程序的破壞行爲會發生異常。如果忽略OS指定的DS,而是用彙編語言直接將OS用的段地址存入DS的話,便會破壞OS。
-
編寫crack2.nas:
[INSTRSET "i486p"] [BITS 32] MOV EAX,1*8 ; OS用的段號 MOV DS,AX ; 將其存入DS MOV BYTE [0x102600],0 RETF
- crack2.nas將OS用的數據段寄存器(DS)賦值給自己的DS,因此,crack2.nas就可以訪問地址0x102600了。
-
make
後用VMware運行:- 運行crakc2.hrb後並輸入dir:
- 沒有產生異常,且dir命令失效,OS崩潰。
- 運行crakc2.hrb後並輸入dir:
7. 保護操作系統(4)(harib18g)
-
harib18f之所以會被crack2.hrb成功攻擊,是因爲應用程序擅自向自己用的DS存入了OS用的段地址DS。
-
解決方法:讓應用程序無法使用OS用的段地址。
- x86架構的CPU正好有這樣的功能。
-
在段定義的地方,如果將訪問權限加上
0x60
的話,就可以將段設置爲應用程序用。當CS(代碼段寄存器)中的段地址爲應用程序的段地址時,CPU就會認爲“當前正在運行應用程序”,這時如果過訪問OS用的段地址就會產生異常。 -
修改cmd_app:
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline) { …… char name[18], *p, *q; struct TASK *task = task_now(); /*獲取當前任務的指針*/ …… if (finfo != 0) { /* 找到文件的情況 */ …… set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER + 0x60); /*訪問權限加上0x60*/ set_segmdesc(gdt + 1004, 64 * 1024 - 1, (int) q, AR_DATA32_RW + 0x60); /*訪問權限加上0x60*/ …… start_app(0, 1003 * 8, 64 * 1024, 1004 * 8, &(task->tss.esp0)); /*啓動*/ …… } /* 沒有找到文件的情況 */ return 0; }
- 應用程序段的訪問權限加上了0x60,代表使用x86架構的方法。
- 如果使用上述方法,必須在TSS中註冊OS用的段地址和ESP。 因此,start_app函數增加了一個參數,用於傳遞註冊地址。
-
用上述方法,在啓動應用程序時,需要讓“OS嚮應用程序用的段執行far-CALL”。但根據x86的規則,是不允許操作系統CALL應用程序的(如果強行CALL會產生異常)。同樣,在x86中,“OS嚮應用程序用的段進行far-JMP”也是被禁止的。(不用上述方法,OS是可以嚮應用程序執行far-CALL的,harib18e就是這麼辦的。)
-
使用RETF:就像是被應用程序CALL過之後那樣,事先將地址PUSH到棧中,然後執行RETF,這樣就能成功啓動應用程序了。
-
修改naskfunc.nas中的start_app:
_start_app: ; void start_app(int eip, int cs, int esp, int ds, int *tss_esp0); PUSHAD ; 將32位寄存器的值全部保存下來 MOV EAX,[ESP+36] ; 應用程序用EIP MOV ECX,[ESP+40] ; 應用程序用CS MOV EDX,[ESP+44] ; 應用程序用ESP MOV EBX,[ESP+48] ; 應用程序用DS/SS MOV EBP,[ESP+52] ; tss.esp0的地址 MOV [EBP ],ESP ; 保存OS用的ESP MOV [EBP+4],SS ; 保存OS用的SS MOV ES,BX MOV DS,BX MOV FS,BX MOV GS,BX ; 調整棧,使用RETF跳轉到應用程序 OR ECX,3 ; 將應用程序用的段號和3進行OR運算 OR EBX,3 ; 將應用程序用的段號和3進行OR運算 PUSH EBX ; 應用程序的SS PUSH EDX ; 應用程序的ESP PUSH ECX ; 應用程序的CS PUSH EAX ; 應用程序的EIP RETF ; 應用程序結束後不會到這裏
- 將應用程序的段號和3進行運算的部分,是爲用RETF調用應用程序使用的小技巧。存疑:小技巧沒明白
- 由於不是通過far-CALL來調用應用程序的,因此應用程序無法通過RETF的方式結束並返回。需要使用其他方法結束並返回。
-
接收API調用的asm_hrb_api也需要修改:
_asm_hrb_api: STI PUSH DS PUSH ES PUSHAD ; 用於保存的PUSH PUSHAD ; 用於向hrb_api傳遞參數的PUSH MOV AX,SS MOV DS,AX ; 將OS用的段地址存入DS和ES MOV ES,AX CALL _hrb_api CMP EAX,0 ; 當EAX不爲0時程序結束 JNE end_app ADD ESP,32 POPAD POP ES POP DS IRETD end_app: ; EAX爲tss.esp0的地址 MOV ESP,[EAX] POPAD RET ; 返回cmd_app
- 多虧了CPU幫我們自動執行那些麻煩的棧切換工作,這裏的asm_hrb_api比harib18e的asm_hrb_api斷了許多。
- 當hrb_api返回(EAX中存放返回值)0時應用程序繼續運行,當返回值非0時,返回值當做tss.esp0的地址來處理,強制結束應用程序。
- 這樣設計,是想做一個強制結束應用程序的API。因爲沒有通過far-CALL啓動應用程序,自然也無法使用RETF來結束應用程序,因此做一個用於結束應用程序的API。
-
將結束應用程序的API分配到EDX=4 ,修改hrb_ api:
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax) { int cs_base = *((int *) 0xfe8); struct TASK *task = task_now(); /*獲取指向當前任務的指針*/ struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec); if (edx == 1) { cons_putchar(cons, eax & 0xff, 1); } else if (edx == 2) { cons_putstr0(cons, (char *) ebx + cs_base); } else if (edx == 3) { cons_putstr1(cons, (char *) ebx + cs_base, ecx); } else if (edx == 4) { return &(task->tss.esp0); /*返回當前任務的tss.esp0*/ } return 0; }
- 返回0代表繼續運行應用程序,返回非0則強制結束應用程序。
-
修改inthandler0d:
int *inthandler0d(int *esp) { struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec); struct TASK *task = task_now(); cons_putstr0(cons, "\nINT 0D :\n General Protected Exception.\n"); return &(task->tss.esp0); /* 強制應用程序結束 */ }
- 只要產生INT 0x0d中斷,應用程序一定會被強制結束。(產生了保護性異常)
-
由於把麻煩的棧切換全部交給了CPU去處理了,因此中斷處理部分(鍵盤鼠標等)需要修改:
_asm_inthandler20: PUSH ES PUSH DS PUSHAD MOV EAX,ESP PUSH EAX MOV AX,SS MOV DS,AX MOV ES,AX CALL _inthandler20 POP EAX POPAD POP DS POP ES IRETD
- 其實就是將asm_inthandler20改回原來的版本。
- asm_inthandler21和asm_inthandler2c也改回原來的版本。
-
負責處理異常中斷的asm_inthandler0d也需要修改:
_asm_inthandler0d: STI PUSH ES PUSH DS PUSHAD MOV EAX,ESP PUSH EAX MOV AX,SS MOV DS,AX MOV ES,AX CALL _inthandler0d CMP EAX,0 ; 比較EAX和0 JNE end_app ; 不同,跳轉到end_app POP EAX POPAD POP DS POP ES ADD ESP,4 ; INT 0x0d 需要這句 IRETD
- 添加了強制結束的代碼。
-
修改IDT設置:
void init_gdtidt(void) { …… /* IDT設置 */ set_gatedesc(idt + 0x0d, (int) asm_inthandler0d, 2 * 8, AR_INTGATE32); set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32); set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32); set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32); set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32); set_gatedesc(idt + 0x40, (int) asm_hrb_api, 2 * 8, AR_INTGATE32 + 0x60); /*修改屬性*/ return; }
- 在我們已經清晰地區分OS系統段和應用程序段的情況下,當應用程序試圖調用未經OS授權的中斷時,CPU會認爲“這傢伙亂用奇怪的中斷號,想把OS搞壞,這是壞蛋”,CPU便會產生異常。因此,需要在IDT設置中,將INT 40設置爲“可供應用程序作爲API來調用的中斷”。
- 將INT 40的訪問屬性編碼加上0x60就完成了上述設置。至於其他中斷,並不是用於應用程序對OS的調用,而是鼠標鍵盤等控制以及對異常的處理,因此禁止應用程序調用這種中斷,倘若應用程序強行調用這些中斷,CPU會自動產生異常中斷0x0d然後結束應用程序。
-
應用程序運行流程圖解
-
因爲不能使用RETF結束應用程序了,所以需要修改所有的應用程序:
-
修改hello.nas:
[INSTRSET "i486p"] [BITS 32] MOV ECX,msg MOV EDX,1 putloop: MOV AL,[CS:ECX] CMP AL,0 JE fin INT 0x40 ADD ECX,1 JMP putloop fin: MOV EDX,4 INT 0x40 msg: DB "hello",0
- fin調用了4號API。結束程序。
-
修改hello2.nas:
[INSTRSET "i486p"] [BITS 32] MOV EDX,2 MOV EBX,msg INT 0x40 MOV EDX,4 ; 調用4號API INT 0x40 msg: DB "hello",0
-
修改a.c:
void api_putchar(int c); void api_end(void); void HariMain(void) { api_putchar('A'); api_end(); }
- api_end函數是調用4號API的。
-
修改a.nas:
[FORMAT "WCOFF"] [INSTRSET "i486p"] [BITS 32] [FILE "a_nask.nas"] GLOBAL _api_putchar GLOBAL _api_end [SECTION .text] _api_putchar: ; void api_putchar(int c); MOV EDX,1 MOV AL,[ESP+4] ; c INT 0x40 RET _api_end: ; void api_end(void); MOV EDX,4 INT 0x40
- C語言編寫的應用程序需要編譯後和a.nas進行連接。(比如a.c和下面的hello3.c)
-
修改hello3.c:
void api_putchar(int c); void api_end(void); void HariMain(void) { api_putchar('h'); api_putchar('e'); api_putchar('l'); api_putchar('l'); api_putchar('o'); api_end(); }
-
修改crack1.c:
void api_end(void); void HariMain(void) { *((char *) 0x00102600) = 0; api_end(); }
-
修改crack2.nas:
[INSTRSET "i486p"] [BITS 32] MOV EAX,1*8 ; OS用的段號 MOV DS,AX ; 將其存入DS MOV BYTE [0x102600],0 MOV EDX,4 ; 調用4號API INT 0x40
-
-
make
後用VMware運行:- 運行可執行文件hello、hello2、a和hello3:
- 運行crack2:
- 運行crack1:
- crack1和crack2都產生了0x0d中斷。
- 運行可執行文件hello、hello2、a和hello3:
8. 接下來做幾個有趣的實驗(harib18g_extend)
-
在harib18g的基礎上修改應用程序代碼,
make
後用VMware運行。 -
修改hello.nas:
[INSTRSET "i486p"] [BITS 32] MOV ECX,msg MOV EDX,1 putloop: MOV AL,[CS:ECX] CMP AL,0 JE fin INT 0x40 ADD ECX,1 JMP putloop fin: msg: DB "hello",0
- 上述代碼,假設了一種情況:編寫應用程序hello.nas的程序員忘記調用4號API結束應用程序。
- 這樣的話,CPU執行到fin會發現應用程序出現了BUG,會自動產生0x0d中斷(情況4)。
- 驗證一下:
- 果然和我們預想的一樣。
-
修改hello2.nas:
[INSTRSET "i486p"] [BITS 32] MOV EDX,2 MOV EBX,msg INT 0x40 INT 0x21 msg: DB "hello",0
- 調用0x21中斷(應用程序禁止調用的中斷):
- (情況3,和我們預想的一樣)
- 調用0x21中斷(應用程序禁止調用的中斷):
-
修改crack1.c:
void api_end(void); void HariMain(void) { *((char *) 0x00102600) = 0; //api_end(); }
- 註釋掉了代碼api_end()。其實有沒有api_end都一樣,因爲在執行
*((char *) 0x00102600) = 0;
時crack2已經觸發了0x0d中斷,crack1.hrb被強制結束,然後返回cmd_app了。
- 和我們想的一樣。
- 註釋掉了代碼api_end()。其實有沒有api_end都一樣,因爲在執行
9. 寫在現在
- 現在是2020.4.26 16:51。這篇文檔寫了3天。
- 4.24休息了一天,可能是第21天的彙編太多,加上之前的勞累,偷懶了一天。
- 4.25~4.26兩天時間啃完了第21天這根硬骨頭。
- 第21天的確難,尤其是彙編代碼太多了,晦澀難懂。
- 最後感慨一句,轉眼4月底了,真是韶華易逝啊。