[原创]某驱动里的一处EAT Hook函数分析
对某驱动里的一处EAT Hook函数进行了分析,由于技术比较菜,费了大半天时间,不过也学到了不少,在此分享总结下。
EAT Hook的流程没什么好说的,找到EAT里需要Hook的函数地址,关掉页保护,原子写入,再恢复页保护。稍微有点特色的地方在于函数名匹配时的二分查找,这地方也是耗费我时间最多的地方。
不多说,直接上码:
.text:000104AC ; ULONG __stdcall EATHook(PVOID ImageBase, PANSI_STRING pstrExportName, ULONG HookFunc, ULONG* pFuncRVA) .text:000104AC pNameOrdinals = dword ptr -14h .text:000104AC pszExportName = dword ptr -10h .text:000104AC pNames = dword ptr -0Ch .text:000104AC Size = dword ptr -8 .text:000104AC i = dword ptr -4 .text:000104AC ImageBase = dword ptr 8 .text:000104AC pstrExportName = dword ptr 0Ch .text:000104AC HookFunc = dword ptr 10h .text:000104AC pFuncRVA = dword ptr 14h .text:000104AC .text:000104AC mov edi, edi .text:000104AE push ebp .text:000104AF mov ebp, esp .text:000104B1 sub esp, 14h .text:000104B4 push ebx .text:000104B5 push esi .text:000104B6 push edi .text:000104B7 lea eax, [ebp+Size] .text:000104BA push eax ; Size .text:000104BB push 0 ; DirectoryEntry, 导出表 .text:000104BD push 1 ; MappedAsImage, 返回VA .text:000104BF push [ebp+ImageBase] ; ImageBase .text:000104C2 call ds:RtlImageDirectoryEntryToData .text:000104C8 test eax, eax .text:000104CA jz loc_10563 ; return 0 .text:000104D0 mov edx, [eax+20h] ; _IMAGE_EXPORT_DIRECTORY.AddressOfNames .text:000104D3 mov ecx, [eax+24h] ; _IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals .text:000104D6 add edx, [ebp+ImageBase] ; ImageBase + AddressOfNames .text:000104D9 add ecx, [ebp+ImageBase] ; ImageBase + AddressOfNameOrdinals .text:000104DC mov edi, [eax+18h] ; _IMAGE_EXPORT_DIRECTORY.NumberOfNames .text:000104DF and [ebp+i], 0 ; i = 0 .text:000104E3 dec edi ; NumberOfNames - 1 .text:000104E4 mov [ebp+pNames], edx .text:000104E7 mov [ebp+pNameOrdinals], ecx .text:000104EA js short loc_10563 ; NumberOfNames为0时return 0 .text:000104EC mov ecx, [ebp+pstrExportName] .text:000104EF mov ecx, [ecx+4] ; ANSI_STRING.Buffer .text:000104F2 mov [ebp+pszExportName], ecx ; pszExportName = pstrExportName->Buffer .text:000104F5 jmp short loc_104FA .text:000104F7 ; --------------------------------------------------------------------------- .text:000104F7 .text:000104F7 loc_104F7: .text:000104F7 mov edx, [ebp+pNames] .text:000104FA .text:000104FA loc_104FA: .text:000104FA mov ecx, [ebp+i] .text:000104FD add ecx, edi .text:000104FF sar ecx, 1 ; 二分查找 .text:00010501 mov esi, [edx+ecx*4] ; _IMAGE_EXPORT_DIRECTORY.AddressOfNames数组 .text:00010504 mov edx, [ebp+pszExportName] .text:00010507 add esi, [ebp+ImageBase] ; ImageBase + Export Name's RVA .text:0001050A mov [ebp+pstrExportName], edx .text:0001050D .text:0001050D loc_1050D: .text:0001050D mov edx, [ebp+pstrExportName] ; 字符串匹配, 每两个字节匹配 .text:00010510 mov bl, [edx] ; 第一个字节 .text:00010512 mov dl, bl .text:00010514 cmp bl, [esi] .text:00010516 jnz short loc_10537 ; 不匹配则跳转, 注意这里的通过cf判断字符大小: cf=1表示导出表里的名字比参数的字符大 .text:00010518 test dl, dl .text:0001051A jz short loc_10533 ; 匹配结束结束则跳 .text:0001051C mov edx, [ebp+pstrExportName] .text:0001051F mov bl, [edx+1] ; 第二字节 .text:00010522 mov dl, bl .text:00010524 cmp bl, [esi+1] .text:00010527 jnz short loc_10537 ; 不匹配则跳 .text:00010529 add [ebp+pstrExportName], 2 .text:0001052D inc esi .text:0001052E inc esi .text:0001052F test dl, dl .text:00010531 jnz short loc_1050D ; 匹配未结束则跳 .text:00010533 .text:00010533 loc_10533: .text:00010533 xor edx, edx ; 匹配上了到这里 .text:00010535 jmp short loc_1053C .text:00010537 ; --------------------------------------------------------------------------- .text:00010537 .text:00010537 loc_10537: .text:00010537 sbb edx, edx ; 不匹配到这里 .text:00010539 sbb edx, 0FFFFFFFFh ; cf=0时edx=1, cf=1时edx=-1 .text:0001053C .text:0001053C loc_1053C: .text:0001053C test edx, edx .text:0001053E jge short loc_10545 ; edx>=0时跳 .text:00010540 lea edi, [ecx-1] ; 到这里意味这edx<0, 即上面edx=-1的情况, 即导出表中的名字比参数的字符大 .text:00010543 jmp short loc_1054D ; 导出表的名字按字母顺序排列, 因此要调整比较的Name, 通过这里的ecx - 1对Name数组下标-1 .text:00010545 ; --------------------------------------------------------------------------- .text:00010545 .text:00010545 loc_10545: .text:00010545 jle short loc_10552 ; 匹配到 .text:00010547 lea edx, [ecx+1] ; 到这里意味着导出表Name数组下标要调整为+1 .text:0001054A mov [ebp+i], edx ; 参数字符串肯定是要比ecx + 1的下标Name大了, 故要更新最小下标值 .text:0001054D .text:0001054D loc_1054D: .text:0001054D cmp edi, [ebp+i] ; i其实是二分查找里的最小下标值 .text:00010550 jge short loc_104F7 ; edi为二分查找里的最大下标值, 因此edi >= i时继续二分查找 .text:00010552 .text:00010552 loc_10552: .text:00010552 cmp edi, [ebp+i] .text:00010555 jl short loc_10563 ; 二分查找没找到return 0 .text:00010557 mov edx, [ebp+pNameOrdinals] .text:0001055A movzx ecx, word ptr [edx+ecx*2] .text:0001055E cmp ecx, [eax+14h] ; ecx为匹配到的函数Ordinal, 与_IMAGE_EXPORT_DIRECTORY.NumberOfFunctions作比较 .text:00010561 jb short loc_10567 ; 不小于则return 0 .text:00010563 .text:00010563 loc_10563: .text:00010563 xor eax, eax .text:00010565 jmp short loc_1059E .text:00010567 ; --------------------------------------------------------------------------- .text:00010567 .text:00010567 loc_10567: .text:00010567 mov eax, [eax+1Ch] ; _IMAGE_EXPORT_DIRECTORY.AddressOfFunctions .text:0001056A add eax, [ebp+ImageBase] .text:0001056D mov edx, [ebp+HookFunc] .text:00010570 lea edi, [eax+ecx*4] .text:00010573 mov esi, [edi] ; 匹配到的函数的RVA .text:00010575 add esi, [ebp+ImageBase] .text:00010578 test edx, edx .text:0001057A jz short loc_10597 .text:0001057C call PageProtectOff .text:00010581 sub edx, [ebp+ImageBase] ; Value, 计算Hook函数相对ImageBase的RVA .text:00010584 mov ecx, edi ; Target, EAT Hook .text:00010586 call ds:InterlockedExchange .text:0001058C sti ; PageProtectOn .text:0001058D push eax .text:0001058E mov eax, OldCR0 .text:00010593 mov cr0, eax .text:00010596 pop eax .text:00010597 .text:00010597 loc_10597: .text:00010597 mov eax, [ebp+pFuncRVA] .text:0001059A mov [eax], edi ; 匹配到的函数的RVA .text:0001059C mov eax, esi ; return匹配到的函数地址 .text:0001059E .text:0001059E loc_1059E: .text:0001059E pop edi .text:0001059F pop esi .text:000105A0 pop ebx .text:000105A1 leave .text:000105A2 retn 10h .text:000105A2 EATHook endp
由于分析得少,技能不行,sbb这里折腾了好久,估计不少新手也会弄不明白,这里单独提一下。sbb是两个操作数相减再减去cf位,将结果存放在操作数一里面。
.text:00010514 cmp bl, [esi] .text:00010516 jnz short loc_10537 ; 不匹配则跳转, 注意这里的通过cf判断字符大小: cf=1表示导出表里的名字比参数的字符大 ...... .text:00010537 loc_10537: .text:00010537 sbb edx, edx ; 不匹配到这里 .text:00010539 sbb edx, 0FFFFFFFFh ; cf=0时edx=1, cf=1时edx=-1
.text:00010514 cmp这里会影响到cf,因此到sbb edx, edx时,edx会有0和-1两种情况,再sbb一下-1就会有1和-1两种情况。
考虑到有人可能看到汇编会头疼,这里写了类似的C代码:
ULONG EATHook(ULONG ulImageBase, ANSI_STRING* lpastrExportName, ULONG ulHookFunc, ULONG* pulOrgFuncRVA)
{
ULONG ulRet = 0;
do
{
if(NULL == lpastrExportName || NULL == pulOrgFuncRVA)
{
break;
}
KdPrint(("EATHook %s.\n", lpastrExportName->Buffer));
ULONG ulSize = 0;
PIMAGE_EXPORT_DIRECTORY pDirectory = RtlImageDirectoryEntryToData(ulImageBase, TRUE, IMAGE_DIRECTORY_ENTRY_EXPORT, &ulSize);
if(NULL == pDirectory)
{
break;
}
int nHighIndex = pDirectory->NumberOfNames - 1;
if(nHighIndex < 0)
{
break; // NumberOfNames为0
}
int nLowIndex = 0;
int nMidIndex = 0;
char* pszExportName = lpastrExportName->Buffer;
BOOL bMatch = FALSE;
while(nHighIndex >= nLowIndex)
{
nMidIndex = (nLowIndex + nHighIndex) / 2;
char* pBuffer = (char*)(((ULONG*)(pDirectory->AddressOfNames + ulImageBase))[nMidIndex] + ulImageBase);
KdPrint(("nLowIndex: %d, nMidIndex: %d, nHightIndex: %d, ExportName: %s.\n", nLowIndex, nMidIndex, nHighIndex, pBuffer));
int i = 0;
for(; pszExportName[i] != 0; ++i)
{
if(pszExportName[i] - pBuffer[i] > 0)
{
nLowIndex = nMidIndex;
break;
}
else if(pszExportName[i] - pBuffer[i] < 0)
{
nHighIndex = nMidIndex;
break;
}
else
{
// 单个字符匹配
}
}
if(0 == pszExportName[i])
{
// 完全匹配
bMatch = TRUE;
break;
}
}
if(!bMatch)
{
break;
}
WORD wOrdinal = ((WORD*)(pDirectory->AddressOfNameOrdinals + ulImageBase))[nMidIndex];
KdPrint(("Ordinal: %d, NumberOfFunctions: %d.\n", wOrdinal, pDirectory->NumberOfFunctions));
if(wOrdinal >= pDirectory->NumberOfFunctions)
{
break;
}
ULONG ulAddressOfFunctions = pDirectory->AddressOfFunctions + ulImageBase;
*pulOrgFuncRVA = ((ULONG*)ulAddressOfFunctions)[wOrdinal];
ulRet = *pulOrgFuncRVA + ulImageBase;
if(ulHookFunc != 0)
{
PageProtectOff();
InterlockedExchange(&((LONG*)ulAddressOfFunctions)[wOrdinal], ulHookFunc - ulImageBase);
PageProtectOn();
}
} while (0);
return ulRet;
}
调用该函数找了下KeServiceDescriptorTable和ZwCreateKey:
