前言
很久没有更新过了,今天抽空记录一下如何用WinDbg去查看Windows内存,这对于我们排查内存马是非常有帮助的。同时如果免杀想要精进,掌握查看、调试Windows内存或内核是必不可少的。
环境搭建
我们搭建一个WinDbg远程调试环境,也就是物理机运行 WinDbg 去调试虚拟机的内存,这样能确保 WinDbg 能在一个较为流程的环境运行,不会产生卡顿,同时又不会对物理机产生破坏,这被称为双机调试。
首先关闭虚拟机,然后编辑虚拟机设置 -> 添加 -> 串行端口 。

串行端口配置如下,管道名称填:\\.\pipe\com_1,这里有个地方要注意一下,如果你移除打印机,那么添加新的串行端口为2,此时管道名称填\\.\pipe\com_1是不行的。

现在启动虚拟机,管理员运行下面的命令,告诉虚拟机:"启用调试模式,并通过 COM1 口把数据发出去。"
bcdedit /debug on
bcdedit /dbgsettings serial debugport:1 baudrate:115200
接着 WinDbg 点击 File -> Attach to Kernel,配置如下
选择 "COM" 选项卡:
-
Baud Rate: 115200
-
Port: \\.\pipe\com_1
-
Pipe: 勾选 "Pipe" 复选框(这一步非常重要,说明这不是物理串口,是虚拟管道)。
-
Reconnect: 建议勾选。
最后点击 OK 即可。
此时我们重启虚拟机,看到显示 Debuggee is running... 就代表成功了。

内存查看
为了方便,我运行了一个 project1.exe 加载一个shellcode上线,接下来我们就在内存中查找这个 shellcode,以此来了解Windows内存。
首先我们点击Break,让虚拟机处于冻结状态,否则我们是查看不了的。

输入下面命令,列出当前Windows中正在运行的所有进程,这里要注意 !process 0 0 数据**来自内核态的 EPROCESS 链表 (ActiveProcessLinks),而不是用户态的进程列表。**所以,有些 rootkit 即使在用户态隐藏了进程,我们还是可以在内核态找到。
!process 0 0

这里简单解释一下各个信息
- PROCESS ffffce0385906080: 这是最重要的地址! (我们称之为 EPROCESS),后续所有针对这个进程的操作都基于这个地址。
-
Cid (Client ID): 进程 ID (PID)
-
ParentCid: 父进程 ID。如果你发现 notepad.exe 的父亲是 word.exe,那就是 Process Hollowing 攻击。
-
Peb: 用户态环境块地址 (存放 DLL 加载信息等)。
-
HandleCount:进程句柄数量
-
Image:进程名称
-
DirBase :这是目录基址,它指向进程的页表目录。在 32 位和 64 位 Windows 操作系统中,页表用于虚拟内存到物理内存的映射。这个地址用于页面管理和内存保护。
-
ObjectTable: : 这是进程的对象表的地址。对象表包含了进程中打开的句柄(例如文件、线程、共享内存等),用于管理和访问对象。这个地址指向了包含句柄信息的数据结构。

我们直接查看 project1.exe 进程的相关信息。
6: kd> !process 0 0 project1.exe

这里知道了 project1.exe 的 EPROCESS 地址,我们可以通过查看 EPROCESS 地址,去查看更详细的进程信息,其中 1 为简单输出,7 是最详细的输出。
! process ffff8082a9288080 1

现在 windbg 默认查看的是Windows全局的内存,如果我们要查看指定进程地址的VAD,那么首先要切换到该进程的 VAD 才行。
.process /i ffff8082a9288080
g

此时我们已近切换进去了,可以输入命令 lm 去查看加载了哪些DLL。

我们输入下面的命令,查看当前进程的 VAD 这也是排查shellcode最直接的手段。
!vad

这里解释一下各个参数
VAD:虚拟地址
Star:起始地址
End:结束地址
Commit:1为页数,一页大小为4kb,Private 代表这是私有内存(程序自己申请的堆),而不是加载硬盘上的 DLL 文件
READWRITE :表示这块地址的权限,READWRITE 表示可读可写
那我们如何找到那个地址是被写入shellcode的呢,很简单,我们只需查找哪些地址的权限是可写可执行的,因为shellcode首先要写如地址,然后还要执行,所以要申请一块可写可执行的地址,
并且正常程序是极少申请可写可执行的地址的。
我们直接用瞪眼法,发现一块VAD是可写可执行的,并且页数只有1,也就是4kb大小,这非常符合shellcode的情形。

注意 Start 列的数值 26c9acb0,这通常是 虚拟页号 (VPN) 。要得到真实的内存地址,通常需要在后面加三个 0 (乘以 0x1000),所以地址大概率是:26c9acb0000。
这里我解释一下为什么是查看起始地址的数据,而不是VAD地址的数据,因为VAD 地址相当于索引卡片,记录着一本书,放在哪一层,哪一个架子。如果你直接 db 查看VAD地址的内容,只能看到一堆指针啥的。起始地址才是我们要查看的那本书,里面存放shellcode。
db 26c9acb0000

此时我们对比一下CS生成的shellcode,可以发现一模一样。

现在我们再来看一下哪个线程执行了shellcode,输入下面的命令查看 project1.exe 进程最详细信息。
!process ffff8082a83ea080 7
我们看下面这两个线程,很明显第一次线程是执行shellcode的,因为第一个线程的开头是
0x000001f2`636cce70
这显然与我们的shellcode地址是处于同一内存区域。
并且起始地址 (Start Address): Project1!mainCRTStartup,这说明这不是一个被外挂"注入"进来的新线程,这是程序自己的主线程。

第二张截图则是一个正常的线程,开头是 ntdll!RtlUserThreadStart+0x21
我们还可以查看一下进程的 PEB,以此来判断进程是否被伪造,输入下面命令查看指定进程的PEB。
!peb 680f39b000

还可以通过查看EPROCESS信息去对比PEB是否被伪造,因为EPROCESS是内核级的,PEB伪造同时是在用户态。

在 Windows 内核中,EPROCESS 还有一个更隐秘的字段记录了进程创建时的原始全路径,这个通常黑客懒得改或者很难改。
dt nt!_EPROCESS ffff8082a83ea080 SeAuditProcessCreationInfo.ImageFileName

然后输入下面命令,查看 _OBJECT_NAME_INFORMATION 结构体内容。
dt nt!_OBJECT_NAME_INFORMATION 0xffff8082a1575340

总结
简单记录了一下如何查看进程内容是否写入了shellcode,以及如何判断exe是否伪造了PEB