逆核 _05_ 64位&Windows内核6

0x00 初识x64

00. 64位CPU

x86: IA-32、IA-16、IA-8系列CPU
x64: AMD64 & Intel64(EM64T) (*和IA-64不同

01. 数据模型

ILP32 — MS Windows 32位
LLP64 — MS Windows 64位
LP64 —- UNIX 64位
与win32相比 win64由32位长度的指针变为64位指针

02. WOW64

在64位Windows中, 64位应用程序会加载kernel32.dll(64位)与ntdll.dll(64位); 32位应用程序会加载kernel32.dll(32位)与ntdll.dll(32位). WOW64会在中间将ntdll.dll(32位)的API调用重定向到ntdll.dll(64位)

0. 文件夹结构

System32 ——-64位专用
SysWOW64 —-32位专用

1. 注册表

HKLM\SOFTWARE—–64位专用
HKLM\SOFTWARE\Wow6432Node—- 32位专用

2. 差异

WOW64下的32位程序, 虽然把System32文件识别成”C:\Windows\system32”, 但内容指向SysWOW64文件夹; 而Progame Files目录是准确的”C:\Progame Files(x86)”

03. CALL/JMP指令

32位 FF15 XXXXXXXX为VA
64位 FF15 XXXXXXXX为RVA
转换方法: VA = CALL指令地址+RVA + 6(CALL指令长度)

04. 函数调用约定

首先, 函数参数<=4的情况下, 只使用RCX, RDX, R8, R9(依次)来传递参数
若>4个, 则超出4的部分使用栈来传递, 此时栈内仍然保留前4个参数的空间.
其次, 函数返回时传递参数过程中所用的栈由调用者清理, 视为变形的fastcall.

05. 栈 & 栈帧

栈的大小比函数实际需要的大小大得多, 调用子函数时, 不再使用PUSH来传递参数, 而是通过MOV指令操作寄存器与预定的栈来传递; 同时, 创建栈帧的不在使用RBP寄存器, 而是直接使用RSP寄存器

0x01 PE32+

00. NT头

差异: IMAGE_OPTIONAL_HEADER32 和 IMAGE_OPTIONAL_HEADER64

0. IMAGE_FILE_HEADER

差异: Machine字段值
PE32: 014C
PE32+: 8664

1.IMAGE_OPTIONAL_HEADER

差异:
0.Magic
PE32: 010B
PE32+; 020B

1.BaseOfData
PE32: 用于指示数据节的起始地址
PE32+: 删除了该字段

2.ImageBase
PE32: DWORD类型
PE32+: ULONGLONG类型 为了适应增大的进程虚拟内存(EXE/DLL被加载到低8TB的用户区域, SYS文件被加载到高8TB的内核区域)
注: AddressOdEntryPoint、SizeOfImage等字段大小与原PE32一样, 均为DWORD大小. 这意味着PE32+格式的文件占用的实际虚拟内存中, 各映像的大小最大为4GB(32位). 但由于ImageBase的大小为8个字节, 程序文件可以加载到进程虚拟内存中的任意地址位置

3.栈&堆
PE32: DWORD类型
PE32+: ULONGLONG类型 为了与增大的进程虚拟地址相适应.

01. IMAGE_THUNK_DATA

差异:
PE32: DWORD类型
PE32+: ULONGLONG类型
IMAGE_IMPORT_DESCRIPTOR结构体的OriginalFirstThunk(INT)和FirstThunk(IAT)字段值都是指向IMAGE_THUNK_DATA结构体数组的RVA

02. IMAGE_TLS_DIRECTORY

差异:
PE32+中, 此结构体中StartAddressOfRawData、EndAddressOfData、AddressOfIndex、AddressOfCallBacks字段持有的都是VA值, 所以它们被扩展到为64位OS的地址大小(8字节).

0x02 Debug on x64

00. Windbg

(写这篇博客的时候, 我的Windbg的符号文件还处于炸的状态, 等搞好了再说
本人win10环境下, 进行内核调试之前, 要先进入BIOS将Security Boot切换到Disable

###01. 64位调试
相同: EP代码-> Startup代码-> main()函数
差异: x64使用RCX、RDX、R8、R9于栈一起传递参数
Detail(windbg x64):

1
2
3
4
5
0. 根据基于控制台的应用程序, 在GetCommandLineW()API设置断点
0:00>bp Kernel32!GetCommandLineW
1. 运行调试器, 断后查看栈中储存地址
2. 追踪该地址, 接下来是多条Call指令, 调用多个函数切分获取的"Command line"字符串, 最终形成main()函数的argc, argv参数
3. 参数设置完成后, 紧接着CALL指令来调用main()函数

02. ASLR

ASLR(Address Space Layput Randomization)

差异:

  1. ASLR文件多出.reloc节区
  2. 文件头的Characteristics: 缺少IMAGE_FILE_RELOCS_STRIPPED标志
  3. 可选头的Characteristics: 设有IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE标志

删除ASLR功能
修改PE文件, 删除IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE标志

0x03 DLL Inject on x64

00. Windows 内核 6 会话管理机制

####0. 会话

简单来说, 会话指的是登陆后的用户环境. 大部分OS允许多个用户同时登陆, 并为每个登陆的用户提供独立的用户环境.
用户登陆系统后, 系统默认为相应会话创建csrss.exe、winlogon.exe、explorer.exe进程.
差异:
无论是Windows 7 还是Windows xp, 系统进程与服务进程都在ID为0的会话(系统会话)中运行, 但差异在第一个登录系统的用户的会话ID不同. Windows XP中, 第一个登陆系统的用户的会话ID为0,而Windows 7中第一个登陆系统的用户的会话ID为1, 非系统会话.

1. 会话0隔离机制

从Windows内核版本6开始, 第一个登录系统的用户会话ID被设为1, 与系统会话(ID:0)区分.

01. Windows 内核 6 DLL注入

DLL注入时, 调用CreateRemoteThread()API会失败

0. 差异:

Windows XP

1
2
Kernel32!CreateRemoteThread()
-> ntdll!ZwCreateThread()

Windows 7

1
2
3
Kernel32!CreateRemoteThread()
->kernelbase!CreateRemoteThreadEx()
-> ntdll!ZwCreateThreadEx()

1. ntdll!ZwCreateTheadEx()

根据ZwCreateTheadEx()的参数, 可以发现第7个参数(CreateSuspended)为FALSE(0)时可以成功DLL注入; 而在CreateRemoteThread()API内部调用ZwCreateThreadEx()时, 该参数被设为TRUE, 此时DLL注入失败

2. 解决方法

方法1: 修改CreateSuspended参数值
Debugger:
运行到ntdll.ZwCreateThreadEx()位置, 修改储存在栈中的CreateSuspended参数值.

方法2: 操作条件分支
Debugger:
步过ZwCreateThreadEx()函数, 发现pThreadHandle被赋值, 创建出线程句柄意味着线程正常创建, 但它无法正常工作, 原因可能是后面调用ntdll!ZwResumeThread()API发生了失败, 或者干脆无法调用(由于线程是以挂起模式创建的, 必须”Resume”才能正常执行).
继续debug, 发现直接跳过了ntdll!ZwResumeThread()API
修改标志位, 使跳转指令不成立, DLL成功注入

方法3: 调用ZwCreateTheadEx()
修改注入.cpp文件, 替换为NtCreateTheadEx()API.
注: 用户模式下, NtXXX()和ZwXXX()是一样的, 但内核模式不同