二进制文件加载研究
LoadDll ->LdrLoadDll
GetProcAddr -> LdrGetProcAddr
位于操作系统服务接口层, 根本不用uchar这些字符串, 而是使用unicode_string字符串,我想是和以前windows刚开始使用ansi字符有关
所以 multibytetowidechar的实现原理和rtlinitializeString也是不相同的
Lodlibrary -> ldrLoadDll
loadlibrary是属于kernel32的内容, 但是真正的实现体是在 ntdll.dll里面, ldr系列api
NTSTATUS出现在ntdll里面, 看来服务层已经包含了驱动层的某些api了
UnicodeString工作在系统服务层级, 一般开发者用不到他
NT系列api都是win32子系统的api
上次我写的判断用户态指针的语句获得圆满成功, 目前判断是否是用户空间的指针的标准做法如下
#define ProbeForWriteGenericType(Ptr, Type) \
do { \
if ((ULONG_PTR)(Ptr) + sizeof(Type) - 1 < (ULONG_PTR)(Ptr) || \
(ULONG_PTR)(Ptr) + sizeof(Type) - 1 >= (ULONG_PTR)MmUserProbeAddress) { \
ExRaiseAccessViolation(); \
} \
*(volatile Type *)(Ptr) = *(volatile Type *)(Ptr); \
} while (0)
ULONG64 MmUserProbeAddress = 0x7FFFFFF0000ULL;
用户态指针是不能超过 0x7FFFFFF0000, 这里我也有一个疑问, 那就是用户空间的最后一页是干什么用的呢?
ob系列指针是工作在内核层, 注意了
ob系列管理的是内核对象
user系列管理的是用户对象
以下是 内核对象的管理层的对象结构, 对象结构其实还有一个存储层的对象结构, 我想为了便于管理, 微软付出的可真多
typedef struct _OBJECT_DIRECTORY
{
struct _OBJECT_DIRECTORY_ENTRY *HashBuckets[NUMBER_HASH_BUCKETS];
#if (NTDDI_VERSION < NTDDI_WINXP)
ERESOURCE Lock;
#else
EX_PUSH_LOCK Lock;
#endif
#if (NTDDI_VERSION < NTDDI_WINXP)
BOOLEAN CurrentEntryValid;
#else
struct _DEVICE_MAP *DeviceMap;
#endif
ULONG SessionId;
#if (NTDDI_VERSION == NTDDI_WINXP)
USHORT Reserved;
USHORT SymbolicLinkUsageCount; //上层的引用计数
#endif
} OBJECT_DIRECTORY, *POBJECT_DIRECTORY;
typedef struct _OBJECT_HEADER
{
LONG PointerCount;
union
{
LONG HandleCount;
volatile PVOID NextToFree;
};
POBJECT_TYPE Type;
UCHAR NameInfoOffset;
UCHAR HandleInfoOffset;
UCHAR QuotaInfoOffset;
UCHAR Flags;
union
{
POBJECT_CREATE_INFORMATION ObjectCreateInfo;
PVOID QuotaBlockCharged;
};
PSECURITY_DESCRIPTOR SecurityDescriptor;
QUAD Body;
} OBJECT_HEADER, *POBJECT_HEADER;
内核中管理对象的基本结构
现在我们来对比下用户空间中管理用户对象的基本结构
typedef struct _USER_HANDLE_ENTRY
{
void *ptr; /* pointer to object */
union
{
PVOID pi;
PTHREADINFO pti; // pointer to Win32ThreadInfo
PPROCESSINFO ppi; // pointer to W32ProcessInfo
};
unsigned char type; /* object type (0 if free) */
unsigned char flags;
unsigned short generation; /* generation counter */
} USER_HANDLE_ENTRY, * PUSER_HANDLE_ENTRY;
大致上我们可以预先得出这样的判断: 用户空间的对象数据较内核数据少了 安全描述符、引用计数啥的
同样用户空间有进程和线程的概念比较强烈,
ObpLookupObjectName 这个函数的实现很简单:
RootHandle -》 句柄转成RootDirectory -> 转成 object 搞定
内核对象存储层的对象结构
typedef struct _HANDLE_TABLE_ENTRY
{
union
{
PVOID Object;
ULONG_PTR ObAttributes;
PHANDLE_TABLE_ENTRY_INFO InfoTable;
ULONG_PTR Value;
};
union
{
ULONG GrantedAccess;
struct
{
USHORT GrantedAccessIndex;
USHORT CreatorBackTraceIndex;
};
LONG NextFreeTableEntry;
};
} HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY;
RtlAllocHeap 是通过ZwAllocateVirtualMemory 来进行分配内存的, 那么内核模式中的ExAllocPageNoZero系列的api和这个又是什么关系呢, 接下来看
LdrpLoadModule ----> 准备进入内核级, 所以我们调用了 NtMapViewOfSection -> 准备进入内核级中的内存管理级, 即由我们的内存管理代言人, 即 内核中mm组 组长来帮我们处理这个事情 MmMapViewOfSection -》 内存分配执行者的小兵呢,这时section小弟就出现了,好,交给你吧 MmFindGapTopDown -》 section找好了房间后, 就根据exe中section来对号安排内存安置啦-》 交由region小兵来负责