typedef struct PROCESSINFO
{
HANDLE hProcess;
PCLIENT_ID cidThreads;
unsigned long ulNumberOfThreads;
} PROCESSINFO, * PPROCESSINFO;
NTSTATUS GetProcInfoByPid(unsigned long pid, PPROCESSINFO procInfoOut)
{
#define SYS_INFO_PROCESSES_SIZE 256
#define SystemProcessesAndThreadsInformation 5
NTSTATUS status = STATUS_UNSUCCESSFUL;
SYSTEM_PROCESSES * sysProcInfo;
HANDLE hHandleBuffer;
OBJECT_ATTRIBUTES objAttrib;
void* allocationBase;
unsigned long bufSize;
ULONG i;
bufSize = SYS_INFO_PROCESSES_SIZE;
do
{
allocationBase = (SYSTEM_PROCESSES *) kmalloc(bufSize);
if(!allocationBase)
return status;
status = ZwQuerySystemInformation(SystemProcessesAndThreadsInformation, allocationBase, bufSize, &i);
if(status == STATUS_INFO_LENGTH_MISMATCH)
{
kfree(allocationBase);
bufSize += SYS_INFO_PROCESSES_SIZE;
} else if (!NT_SUCCESS(status)) {
kfree(allocationBase);
return status;
}
} while (status == STATUS_INFO_LENGTH_MISMATCH);
status = STATUS_UNSUCCESSFUL;
sysProcInfo = (SYSTEM_PROCESSES*) allocationBase;
while(TRUE)
{
if(pid == sysProcInfo->ProcessId)
{
if((unsigned long)sysProcInfo->ThreadCount != 0)
{
InitializeObjectAttributes(&objAttrib, 0, OBJ_KERNEL_HANDLE, 0, 0);
status = ZwOpenProcess(&hHandleBuffer, PROCESS_ALL_ACCESS,
&objAttrib, &sysProcInfo->Threads[0].ClientId);
if (!NT_SUCCESS(status)) {
break;
}
procInfoOut->hProcess = hHandleBuffer;
procInfoOut->ulNumberOfThreads = sysProcInfo->ThreadCount;
i = sysProcInfo->ThreadCount * sizeof(CLIENT_ID);
if(i)
{
procInfoOut->cidThreads = (PCLIENT_ID)kmalloc(i);
for(i = 0; i < sysProcInfo->ThreadCount; i++) {
RtlCopyMemory((PCHAR)procInfoOut->cidThreads + (i * sizeof(CLIENT_ID)),
&(sysProcInfo->Threads[i].ClientId), sizeof(CLIENT_ID));
}
}
else
procInfoOut->cidThreads = NULL;
status = STATUS_SUCCESS;
}
break;
}
if(sysProcInfo->NextEntryDelta) {
(unsigned long)sysProcInfo += (unsigned long)sysProcInfo->NextEntryDelta;
} else {
break;
}
}
kfree(allocationBase);
return status;
}
内核编程
BOOLEAN _IsFromNetAccess(PIRP Irp);
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, _IsFromNetAccess)
#endif
BOOLEAN _IsFromNetAccess(PIRP Irp)
{
NTSTATUS status;
PACCESS_TOKEN pToken = NULL;
PTOKEN_SOURCE pTokenSrc = NULL ;
PSECURITY_SUBJECT_CONTEXT secSubCtx;
BOOLEAN bResult = FALSE;
PIO_STACK_LOCATION IrpSp = NULL;
PAGED_CODE();
__try
{
IrpSp = IoGetCurrentIrpStackLocation(Irp);
secSubCtx = & (IrpSp->Parameters.Create.SecurityContext->
AccessState->SubjectSecurityContext);
if (secSubCtx->ClientToken != NULL ||
secSubCtx->PrimaryToken != NULL)
{
pToken = SeQuerySubjectContextToken(secSubCtx);
}
if (NULL == pToken)
{
__leave;
}
//
// Get TokenSource Name If SourceName is "NtLmSsp",
// it was logged-in via Lanmanager,
// "User32" represents locally logged-in users.
//
status = SeQueryInformationToken(pToken, TokenSource, &pTokenSrc);
if (NT_SUCCESS(status))
{
pTokenSrc->SourceName[TOKEN_SOURCE_LENGTH-1] = 0x00;
//kdprintf(NC_DRV_PREFIX "Token Name :%s Len:%d\r\n",
// pTokenSrc->SourceName,strlen(pTokenSrc->SourceName));
if (_stricmp(pTokenSrc->SourceName, "NtLmSsp") == 0 )
{
// kdprintf(NC_DRV_PREFIX "NetWork Access Token Find\r\n");
bResult = TRUE;
}
}
}
__finally
{
if ( pTokenSrc ) {
ExFreePool(pTokenSrc);
}
}
return bResult;
}
内核编程
以NtReadFile调用为例。
一.NtDll.Dll中,NtReadFile过程如下:
ntdll!NtReadFile:
7c92d9b0 b8b7000000 mov eax,0B7h
7c92d9b5 ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)
7c92d9ba ff12 call dword ptr [edx]
7c92d9bc c22400 ret 24h
二.0x7ffe0300地址存放0x7c92e4f0,反汇编它:
ntdll!KiFastSystemCall:
7c92e4f0 8bd4 mov edx,esp
7c92e4f2 0f34 sysenter
因为SYSENTER执行时,CPU并不会向CALL一样保存返回地址和状态信息,所以需要这样一个“桩”(STUB)段,通过它来保存返回信息。同样的,下面的(五)就是利用这里保存的返回信息返回值NtReadFile中。
这里的KiFastSystemCall和(五)中的SystemCallReturn 都是保存在 _KUSER_SHARED_DATA结构中。用户态下的0X7FFE0000和系统态下的0XFFDF0000同时指向它。结构如下:
阅读全文…
内核编程
SYSENTER
头文件
#ifndef __OpCodeSize_H__
#define __OpCodeSize_H__
EXTERN_C ULONG __stdcall GetOpCodeSize(unsigned char * startaddress);
#endif // __OpCodeSize_H__
阅读全文…
内核编程, 技术心得
某些Zw*和Nt*函数既在ntdll.dll中导出又在ntoskrnl.exe中导出,他们有什么区别呢?
我们分三部分比较:
step 1: ntdll.dll中的Zw*和Nt*有什么区别?
step 2: ntoskrnl.exe中的Zw*和Nt*有什么区别?
step 3: ntdll.dll中的Zw*与ntoskrnl.exe中的Zw*有什么区别?
ntdll.dll中的Nt*与ntoskrnl.exe中的Nt*有什么区别?
在下面的讨论中我们以ZwCreateFile和NtCreateFile为例
讨论前:我先贴点Kd给我们的答案
Part1:
kd> u Ntdll! ZwCreateFile L4
ntdll!ZwCreateFile:
77f87cac b820000000 mov eax,0×20
77f87cb1 8d542404 lea edx,[esp+0x4]
77f87cb5 cd2e int 2e
77f87cb7 c22c00 ret 0x2c
kd> u Ntdll! NtCreateFile L4
ntdll!ZwCreateFile:
77f87cac b820000000 mov eax,0×20
77f87cb1 8d542404 lea edx,[esp+0x4]
77f87cb5 cd2e int 2e
77f87cb7 c22c00 ret 0x2c
阅读全文…
内核编程
代码运行在 RING0 (系统地址空间)和 RING3 (用户地址空间)时, FS 段寄存器分别指向 GDT( 全局描述符表 ) 中不同段:在 RING3 下, FS 段值是 0x3B (这是 WindowsXP 下值;在 Windows2000 下值为 0×38 。差别就是在 XP 下 RPL=3 );运行在 RING0 下时, FS 段寄存器值是 0×30 。下面以 XP 为例说说。
一. RING3 下的 FS
当代码运行在 Ring3 下时, FS 值为指向的段是 GDT 中的 0×38 段( RPL 为 3 )。该段的长度为 4K ,基地址为 当前线程 的线程环境块( TEB ),所以该段也被称为“ TEB 段”。
WINXPSP1 及以前的 Windows2000 等系统中,进程环境块( PEB )的地址固定为 0X7FFDF000 ,该进程的第一个线程的 TEB 地址为 0X7FFDE000 ,第二个 TEB 的地址为 0X7FFDD000….. 但是自从 WindowsXP SP2 开始 PEB 和 TEB 的地址都是随机映射的 ( 详见博文: MiCreatePebOrTeb函数注释 ) 。
下图是 WindowsXP SP3 下的 TEB 结构 ( 大小为 0XFB8) :
阅读全文…
内核编程
一.取文件对象名称
我们可以使用函数ObQueryNameString 来查询获取文件对象(FILE_OBJECT )的名称。由于文件对象有专门的名称查询函数IopQueryName ,所以ObQueryNameString 在内部会直接调用这个函数来查询文件对象名。
我们还有另外一种方法比较“直接”地获得文件对象名称。我们知道:文件对象名包括驱动器名和文件路径名。
FILE_OBJECT 结构中有一个成员FileName ,它只包括文件路径名(注意:不包括驱动器名)。
FILE_OBJECT 结构中另外有一个成员DeviceObject ,它是指向DEVICE_OBJECT 的设备对象。该设备就是包含该文件的驱动器设备对象。我们可以调用ObQueryNameString 查询获取此设备对象的名称。但是设备名称的格式是这样的:\\Device\\HarddiskvolumeX (X 为数字),并不是常见的C/D/E… 驱动器名格式。这是因为我们说的驱动器名其实是上面这些设备对象的符号链接(Symbolic link) 名,我们可以通过IoVolumeDeviceToDosName (在XP/2003 等下使用,在NT/2000 下使用RtlVolumeDeviceToDosName )来将设备名称转成驱动器名。
但是若直接使用ObQueryNameString 查询文件对象的话,返回的名称就是设备名称和文路径名,如:
\Device\HarddiskVolume1\WINDOWS\system32\smss.exe
此时我们是没有设备对象,所以就没有办法调用IoVolumeDeviceToDosName 来转化了,此时该怎么办?有一个比较笨的方法,就是调用ZwOpenSymbolcLink 对象对所有A~Z 字母进行打开链接对象,并调用ZwQuerySymbilicLink 来获得对应的设备对象名,并将这个设备名与上面的ObQueyNameString 返回的设备名进行比较,以此来确定驱动器名。见下面的代码片段:
…
RtlInitUnicodeString(&SymbolicLink, L"\\??\\C:");
LinkTarget.MaximumLength = 200;
LinkTarget.Buffer = ExAllocatePoolWithTag(NonPagedPool, 200, 'test');
for (c = 'A'; c <='Z'; c++)
{
SymbolicLink.Buffer[4] = c;
InitializeObjectAttributes(&oa, &SymbolicLink, OBJ_KERNEL_HANDLE, 0, NULL);
Status = ZwOpenSymbolicLinkObject(&LinkHandle, GENERIC_READ, &oa);
if (Status != STATUS_SUCCESS)
continue;
Status = ZwQuerySymbolicLinkObject(LinkHandle, &LinkTarget, NULL);
//LinkTarget 返回的就是设备名称
ZwClose(LinkHandle);
if (Status == STATUS_SUCCESS)
{
RetLength = LinkTarget.Length;
if (RtlCompareMemory(LinkTarget.Buffer, ObjectNameInfo->Name.Buffer, RetLength) == RetLength)
break; //ObjectNameInfo 为ObQueryName 返回的文件对象名
}
}
ExFreePoolWithTag(LinkTarget.Buffer, 'test');
…
阅读全文…
内核编程
在 Win32 中,我们使用 CreateFileMapping 来创建映射文件对象,函数原型如下:
HANDLE CreateFileMapping(
HANDLE hFile, // handle to file to map
LPSECURITY_ATTRIBUTES lpFileMappingAttributes, // optional security attributes
DWORD flProtect, // protection for mapping object
DWORD dwMaximumSizeHigh, // high-order 32 bits of object size
DWORD dwMaximumSizeLow, // low-order 32 bits of object size
LPCTSTR lpName // name of file-mapping object
);
这个函数会调用 Native Api(Ntdll.dll) 中的 ZwCreateSection ( NtCreateSection )函数,而后者又通过系统调用 Ntoskrnl.exe 中的 NtCreateSection 函数,其函数原型如下:
NTSTATUS
NtCreateSection(
OUT PHANDLE SectionHandle ,
IN ACCESS_MASK DesiredAccess ,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN PLARGE_INTEGER MaximumSize OPTIONAL,
IN ULONG SectionPageProtection ,
IN ULONG AllocationAttributes ,
IN HANDLE FileHandle OPTIONAL
);
不仅是用户创建的映射文件, Windows 加载所有的可执行模块,包括 EXE 文件、 DLL 文件等都使用了此函数,只是在参数 SectionPageProtection 和 AllocationAttributes 与普通的映射文件有所区别:
|
可执行模块 |
一般映射文件 |
| SectionPageProtection |
包括 PAGE_EXECUTE |
任何参数,可能亦包括 PAGE_EXECUTE |
| AllocationAttributes |
SEC_IMAGE |
不包括 SEC_IMAGE |
根据这个特点,我们可以通过挂钩 NtCreateSection 函数来截获所有将要加载的可执行模块的文件名称(关于如何获取文件名称,可以参看博文《获取文件对象的名称》)反馈给用户。用户根据文件路径、文件的 MD5 或其他信息来判断是否允许该模块的加载。
(AllocationAttributes == 0X1000000) && (SectionPageProtection & PAGE_EXECUTE)
参考文章:
1. Hooking the native API and controlling process creation on a system-wide basis
2. 天书夜读
内核编程
NtCreateSection, 可执行模块
RDMSR/WRMSR 指令
MSR 本义是 Model Specific Register, 目前 MSR 寄存器一般都是 64 位大小, 但是有些 MSR 的某些位保留不用。一般每个 MSR 寄存器都有一个整数 ID 用做标识,有时也把 MSR 寄存器的 ID 称为该寄存器的地址。
RDMSR 指令
RDMSR 指令用于读取 MSR 寄存器,首先应该将要读的 MSR 的 ID 放入 ECX 寄存器,然后执行 RDMSR 指令,如果操作成功,返回值会被放入 EDX:EAX 中(在支持intel64架构的处理器中 RCX 的高32位忽略)。MSR的高32位内容存放在 EDX 寄存器中,MSR的低32位内容存放在 EAX 寄存器中(在支持intel64架构的处理器中RDX和RAX的高32位忽略)。如果MSR 中没有64位(有些位没有实现),则EDX:EAX中没有实现的位置则未定义。
该指令必须在 0 层权限或者实地址模式下执行;否则会触发#GP(0)异常。在ECX中指定一个保留的或者未实现的MSR地址也会引发异常。
MSR控制着可测试性、执行跟踪、性能检测和机器错误检查等功能。附录B列出所有能读写的MSR以及它们的地址。注意不同的处理器族有自己不同的MSR.
我们可以在使用本指令前用CPUID指令来检查是否支持MSR。(CPUID.01H:EDX[5]=1).
WRMSR 指令
WRMSR 指令用来写 MSR 寄存器,也是先把要写的 MSR 的 ID 放入 ECX 寄存器,并把要写入的数据放入 EDX:EAX 寄存器中,然后执行 WRMSR指令。
内核编程
在 x86 下,以前的系统调用都是通过 int 指令来发出,然后跳转到内核中的服务例程。但 int 指令执行时要做一些不必要的安全检查,而且因为系统调用是系统内被调用最频繁的地方,因此采用 int 指令会影响系统的性能。
这时 Intel 和 AMD 就专门设计了系统调用指令来代替以前的 int 指令。
Intel 中采用的指令是 SYSENTER/SYSEXIT , AMD 中采用的指令是 SYSCALL/SYSRETURN.
下面我简单的描述一下 Intel 下的 SYSENTER/SYSEXIT 指令的执行前提和执行过程。
准备工作:
1. 在调用 SYSENTER 指令进行系统调用时,需要在 GDT 中建立四个段,分别用来描述 SYSENTER 指令进入内核模式时使用的代码段 (CS) 和栈段 (SS), 以及 SYSEXIT 指令从内核模式返回用户模式时使用的代码段 (CS) 和栈段 (SS)。这四个描述符在 GDT 表中的排列应该严格按照以上顺序,这样只要指定一个段描述符的位置便能计算出其他的。
2. 设置下表中专门用于系统调用的 MSR 寄存器(使用 WRMSR 指令来设置这些寄存器):
| MSR |
Address |
| IA32_SYSENTER_CS |
174H |
| IA32_SYSENTER_ESP |
175H |
| IA32_SYSENTER_EIP |
176H |
- IA32_SYSENTER_CS 寄存器保存了内核代码段的描述符的 index。
- IA32_SYSENTER_EIP 寄存器保存了内核里的快速系统调用分发例程的线性地址。
- IA32_SYSENTER_ESP 寄存器保存了内核里能获得本地 TSS 描述符的基地址. (只有这样, 进入内核态后才能正确的设定 esp 为正确的值)
sysenter指令执行步骤如下:
- 将IA32_SYSENTER_CS保存到CS中
- 将IA32_SYSENTER_EIP保存到EIP中
- 将IA32_SYSENTER_CS + 8 保存到SS中
- 将IA32_SYSENTER_ESP保存到ESP寄存器中
- 切换到ring 0级别
- 如果EFLAGS中的VM标志被设定,那么清0该标志
- 开始执行ring0代码
下面再说说 sysexit
这条指令执行前需要如下准备工作:
- 设置 EDX 为 ring3 下要执行的指令的首地址
- 设置 ECX 为 ring3 下的栈指针
sysexit指令的执行步骤如下:
- 将IA32_SYSENTER_CS + 16保存到CS中。(ring3下代码段)
- 将EDX赋值给EIP
- 将IA32_SYSENTER_CS + 24保存到SS中
- 将ECX赋值给ESP
- 切换到ring3下继续执行ring3代码
参考:
《软件调试》 张银奎 8.3.3节
ia32 intel architecture software developer manuals 指令卷2 sysenter/sysexit部分
内核编程
SYSENTER, SYSEXIT
近期评论