存档

‘内核编程’ 分类的存档
767 views

内核从 PID 获得进程信息

2010年6月8日
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;
}

内核编程

746 views

在 Windows NT 内核如何判断文件访问请求 IRP 来自网络

2010年6月3日
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;
} 

内核编程

1,031 views

SYSENTER 系统服务调用过程

2010年5月26日

以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同时指向它。结构如下:

阅读全文…

内核编程

549 views

获取二进制代码的操作码大小

2010年5月25日

头文件

#ifndef __OpCodeSize_H__
#define __OpCodeSize_H__

EXTERN_C ULONG __stdcall GetOpCodeSize(unsigned char * startaddress);

#endif	// __OpCodeSize_H__

阅读全文…

内核编程, 技术心得

590 views

Zw* 与 Nt* 的区别

2010年5月25日

某些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
阅读全文…

内核编程

844 views

Windows 中 FS 段寄存器

2010年5月25日

代码运行在 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) :
阅读全文…

内核编程

714 views

获取文件对象的名称

2010年5月25日

一.取文件对象名称

我们可以使用函数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');
…

阅读全文…

内核编程

673 views

通过挂钩 NtCreateSection 监控可执行模块

2010年5月25日

在 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. 天书夜读

内核编程 ,

1,066 views

RDMSR/WRMSR 指令

2010年5月25日

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指令。

内核编程

897 views

SYSENTER/SYSEXIT 指令

2010年5月25日

在 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指令执行步骤如下:

  1. 将IA32_SYSENTER_CS保存到CS中
  2. 将IA32_SYSENTER_EIP保存到EIP中
  3. 将IA32_SYSENTER_CS + 8 保存到SS中
  4. 将IA32_SYSENTER_ESP保存到ESP寄存器中
  5. 切换到ring 0级别
  6. 如果EFLAGS中的VM标志被设定,那么清0该标志
  7. 开始执行ring0代码

下面再说说 sysexit

这条指令执行前需要如下准备工作:

  • 设置 EDX 为 ring3 下要执行的指令的首地址
  • 设置 ECX 为 ring3 下的栈指针

sysexit指令的执行步骤如下:

  1. 将IA32_SYSENTER_CS + 16保存到CS中。(ring3下代码段)
  2. 将EDX赋值给EIP
  3. 将IA32_SYSENTER_CS + 24保存到SS中
  4. 将ECX赋值给ESP
  5. 切换到ring3下继续执行ring3代码

参考:
《软件调试》 张银奎 8.3.3节
ia32 intel architecture software developer manuals 指令卷2 sysenter/sysexit部分

内核编程 ,