在本系列的上一篇文章中,我介绍了操作的概念,并演示了每个操作如何具有支撑它的函数调用图。在那篇博文中,我特意展示了相对于我的知识而言不完整的函数调用图,因为我只想展示明显基于我们通过 mimikatz 观察到的内容的程度(在本系列的第一篇文章中)。将函数调用图限制为我们在本系列中积极发现的内容的另一个好处是,我们可以证明,即使我们知道部分图片不完整,在正式记录时仍然有用。下面是 Process Enumerate 操作的函数调用图,它将作为本文的基础:
此图相对稀疏。在对 mimikatz 的分析过程中,我们看到它调用来枚举进程列表,并最终找到 LSASS 进程的进程标识符 (pid)。然后,我们分析了函数调用堆栈,以确定和替代的本机 API 函数名称。一般来说,我们已经观察到原生 API 函数并不是大多数应用程序开发人员应该与之交互的层,因此一个合理的问题是,“是否有任何更高级别的函数最终可能调用或类似的函数?一位研究人员就是这样做的。来自 MDSec 的 @modexpblog 写了一篇出色的博客文章,探讨了用于识别 LSASS 进程的进程标识符的 14 个替代选项。这正是我们所关心的!第一种选择是调用 ,我们已经介绍过了,但第二种方法提供了一种值得研究的不同方法。NtQuerySystemInformation
syscall
NtQuerySystemInformation
NtQuerySystemInformation
博客文章中介绍的第二种方法侧重于 Windows 终端服务 (WTS)。它描述了一个 Windows API 函数,该函数可用于列出进程,然后可以查找 LSASS pid。WTSEnumerateProcesses
这篇博文的一个很酷的功能是它们包含每个方法的示例源代码,因此我们还可以看到如何使用此函数来获取 LSASS pid。
在开始分析之前,我们必须确定哪个库实现了 。为此,我们可以浏览该函数的 Microsoft 文档。WTSEnumerateProcessesA
WTSEnumerateProcessesA
在“要求”部分中,我们发现实现 DLL 的名称是 。wtsapi32.dll
现在我们知道库实现了该函数,我们可以在反汇编器中打开它。在研究 API 函数时,我的第一步是检查导出表。特别是在相关函数以 或 结尾的情况下,我喜欢更通用地搜索导出表,因为该函数可能有替代版本。为此,我搜索了“进程”一词,发现有四个函数(、、和)。wtsapi32.dll
WTSEnumerateProcessesA
A
W
WTSEnumerateProcesses*
WTSEnumerateProcessesA
WTSEnumerateProcessesExA
WTSEnumerateProcessesExW
WTSEnumerateProcessesW
由于本文是我的“论检测”系列的一部分,因此我们将创建一个图表来直观地表示我们通过此分析学到的所有内容。我们知道目前有四个独立的函数,但我们对其他功能知之甚少。下图反映了此信息:
在设计这篇文章时,我为分析这四个函数的顺序而苦苦挣扎。通常有两种选择,同时分析所有四个函数或按顺序评估每个函数。我决定,如果我跟踪单个函数的执行,然后重新分析其余函数,则更容易遵循。当我们遇到新的想法时,我会解释它们,然后,如果我们以后遇到它们,我会把你推荐回涵盖这些信息的部分,同时提供分析的输出。
我们遇到的第一个函数是 。我们可以双击函数名称并查看其实现。这个函数非常简单,因为它调用了我们感兴趣的其他函数之一,即 。WTSEnumerateProcessesA
WTSEnumerateProcessesW
我们更新的函数调用图现在显示调用。WTSEnumerateProcessesA
WTSEnumerateProcessesW
继续我们的分析,我们可以深入研究 .分析此函数可显示两个可能的调用。第一个是调用的导入函数,第二个是调用的导入函数。看一眼函数调用流,就会发现只有当调用以某种方式失败时才会调用。此流程通过调用 来显示,具体检查错误代码。WTSEnumerateProcessesW
WinStationGetAllProcesses
WinStationEnumerateProcesses
WTSEnumerateProcessesW
WinStatonEnumerateProcesses
WinStationGetAllProcesses
GetLastError
0x6D1
在上图中,我们看到对 和 的调用以 为前缀,这表示这些是导入的函数或以不同的方式表示。这些函数是从外部库导入的,因为它们在 中不存在。要确定哪个二进制文件实现了这些函数,我们可以在 Import 表中搜索它们。下图显示了这两个函数的 Import 表条目,并显示它们都是在 中实现的。WinStationGetAllProcesses
WinStationEnumerateProcesses
__imp_
winsta32.dll
winsta.dll
我们可以更新我们的函数调用图以指示可以调用 either 或 。WTSEnumerateProcessesW
WinStationEnumerateProcesses
WinStationGetAllProcesses
现在,我们可以加载到反汇编程序中,并且可以再次查看 Export 表以查找对函数的引用。同样,我们可以使用通用搜索词来确保如果存在这些函数的替代品,我们会看到它们。在这种情况下,似乎没有任何有效的替代方案。winsta.dll
现在,我们可以跳转到 的代码实现,并立即看到对名为 的内部函数的调用。WinStationGetAllProcesses
GetSystemProcessInformation@CProcessUtils
我们可以遵循该调用,我们看到它最终调用了本机 API 函数。GetSystemProcessInformation@CProcessUtils
NtQuerySystemInformation
我们在分析中达到了一个拐点,因为我们发现该图与我们现有的图收敛。我更新了图形以显示这些新函数,并连接起始图形和新创建的图形。WTSEnumerateProcess*
NtQuerySystemInformation
WinStationGetAllProcesses
Legacy_WinStationGetAllProcesses
我们对该函数的分析揭示了对该函数的调用,该函数处理 RPC 过程调用。因此,我们必须分析传入的参数,以准确了解正在调用哪个 RPC 过程。Legacy_WinStationGetAllProcesse
NdrClientCall3
NdrClientCall3
第一步是标识 RPC 接口,该接口作为第一个参数(标记为 )中的字段传入,如下所示:pProxyInfo
可以使用 PowerShell 将上图中标记为 、 和 的值分析为字符串,形成全局唯一标识符 (GUID),该标识符表示正在调用的 RPC 接口。Data1
Data2
Data3
Data4
NdrClientCall3
我们可以使用 Google 搜索 GUID 字符串 。此搜索将导致发现 的 Microsoft 文档,也称为 。5ca4a760-ebb1–11cf-8611–00a0245420ed
Terminal Services Terminal Server Runtime Interface Protocol
[MS-TSTS]
第 1.9 节 标准分配发现 GUID 与旧版 RPC 接口相关联。这与调用函数的名称 .Legacy_WinStationGetAllProcesses
下一位重要信息是 RPC 过程编号(Opnum 或 Procnum),它由传递给 的第二个参数指示,标记为 。我们可以看到,传递的值是 。NdrClientCall3
pProcNum
70
参考 RPC 接口文档,我们看到 Opnum 70 引用了一个调用的过程,它似乎与我们对调用函数的了解非常吻合。RpcWinStationGetAllProcesses_NT6
现在,我们可以更新函数调用图以包含替代调用,然后对名为 的 RPC 过程进行 RPC 调用。Legacy_WinStationGetAllProcesses
RpcWinStationGetAllProcesses_NT6
正如您可能已经猜到的那样,RPC 过程调用不是执行行的末尾。事实上,到目前为止,在为改变或列举系统而采取的行动中,还没有发生任何事情。若要继续遵循执行路径,我们必须调查与 RPC 过程关联的代码,但由于它不是我们之前看到的导入函数,因此我们必须使用不同的方法。RPC 是一个客户端/服务器接口,在本例中,客户端调用服务器,服务器执行与过程关联的代码。这意味着系统上可能有一个实现 RPC 接口并充当服务器的二进制文件,因此我们需要找到它。RpcWinStationGetAllProcesses_NT6
Legacy_WinStationGetAllProcesses
要找到服务器,我们可以使用 James Forshaw 的 NtObjectManager 中的函数,该函数名为 ,该函数解析传递给它的二进制文件,以确定该二进制文件是否在其代码中实现了 RPC 服务器。暴力策略是列出所有 DLL 文件,并通过 PowerShell 管道将它们全部传递到 .我们可以通过查找从参数中标识的接口 GUID 来筛选结果。这个过程是有效的,因此,我们看到它实现了协议的遗留接口。Get-RpcServer
system32
Get-RpcServer
NdrClientCall3
termsrv.dll
[MS-TSTS]
现在我们知道它实现了 RPC 服务器,我们可以将其加载到我们的反汇编程序中并找到相关的代码。是一个 RPC 过程,而不是导出的函数,因此我们必须在常规函数菜单中找到它,而不是在 Exports 表中查找它。termsrv.dll
RpcWinStationGetAllProcesses_NT6
导航到函数的实现后,第一个相关调用是调用名为 的内部函数。GetSessionProcessInformation@CProcessUtils
该函数最终调用本机 API 函数。已存在于我们的函数调用图中,因此我们已经到达了此代码路径的末尾。GetSessionProcessInformation@CProcessUtils
NtQuerySystemInformation
NtQuerySystemInformation
下面是更新的函数调用图,该图现在枚举了函数路径和生成的 RPC 过程调用。Legacy_WinStationGetAllProcesses
RpcWinStationGetAllProcesses_NT6
我们可以返回到函数并继续遵循代码。我们立即看到,如果调用失败并出现错误代码,将调用第二个 RPC 过程。我们可以看到,第二个调用的第一个参数指向传递给第一个调用的相同结构 ()。这意味着正在调用相同的 RPC 接口 ()。但是,我们可以看到包含 RPC 过程编号的第二个参数是不同的。第二个调用是指 ProcNum 。Legacy_WinStationGetAllProcesses
winsta.dll
RpcWinStationGetAllProcesses_NT6
0x6D1
stru_18002E308
NdrClientCall3
[MS-TSTS] Legacy
43
通过回顾 RPC 协议文档,我们发现 Opnum 被称为 .43
RpcWinStationGetAllProcesses
我们已经知道实现了RPC Server,所以我们可以在函数表中搜索,找到名为 .当我们找到它时,我们可以导航到它的代码实现。到达那里后,我们发现 调用 ,我们已经将其包含在我们的图表中。termsrv.dll
termsrv.dll
RpcWinStationGetAllProcesses
RpcWinStationGetAllProcesses
RpcWinStationGetAllProcesses_NT6
我们更新了函数调用图,以包含 RPC 过程。RpcWinStationGetAllProcesses
本文的前几节分析了该调用。但是,如果调用以某种方式失败 (),则将进行第二次调用。让我们看一下这个函数的实现。WinStationGetAllProcesses
WinStationGetAllProcesses
cmp eax, 6D1h
WinStationEnumerateProcesses
跳回 ,我们可以打开函数。最终调用一个名为 的函数。让我们来看看它的实现。winsta.dll
WinStationEnumerateProcesses
WinStationEnumerateProcesses
Legacy_WinStationEnumerateProcesses
该函数进行 RPC 调用。在本例中,我们可以看到第一个参数 () 指向我们之前看到的相同结构,因此我们知道这部分 .唯一的区别是该参数设置为 。Legacy_WinStationEnumerateProcesses
pProxyInfo
[MS-TSTS] LegacyApi
nProcNum
36
的文档说 Opnum 与该过程相关联,这似乎与调用函数的名称非常一致。[MS-TSTS] LegacyApi
36
RpcWinStationEnumerateProcesses
现在,我们可以更新图形以包含函数发出的 RPC 调用,如下所示:WinStationEnumerateProcesses
请记住,它充当协议的 RPC 服务器,因此应该调用一个内部函数进行分析。乍一看代码,没有任何实质性的调用指令。我们看到对 的调用和对 的第二次调用,但根据这些函数的名称,它们似乎只是帮助程序。进一步的分析在调用后直接确定了加载有效地址 () 指令,并将字符串加载到寄存器中。我们可以看到,根据命令,这个字符串说“!!RpcWinStationEnumerateProcesses depr “...其中“depr”代表“deprecated”。这意味着,虽然此功能路径似乎存在,但至少在我们用于此分析的操作系统版本上,预计不会到达或执行任何进程枚举。termsrv.dll
LegacyApi
RpcWinStationEnumerateProcesses
RpcCallTrace
DbgPrintMessage
lea
RpcCallTrace
RDX
虽然这个函数路径是不可行的,但在这个版本的操作系统上,我仍然认为必须将其记录为函数调用图的一部分。但是,由于这些函数似乎不是函数调用图中执行 Process Enumerate 操作的可能入口点,因此我已将此路径上的节点更改为黑色而不是红色。此更改表明这些节点不是现代操作系统上的有效入口点。
尽管我们在代码中看到了与此操作相关的三个不同的 RPC 过程,但我们不应假设这是仅有的三个 RPC 过程。我使用的一种策略是找出特定 RPC 接口的过程使用的命名约定。例如,在 Legacy Interface 的情况下,我们看到所有过程都遵循命名约定。我们可以使用此约定来搜索我们可能遗漏的相关函数。在这样做的过程中,我发现了一个名为 .让我们来看看。RpcWinStation*
OldRpcWinStationEnumerateProcesses
我们可以通过在文档中查找它来验证这实际上是一个 RPC 过程;果然,我们看到它就在那里。
然后我们可以检查 的代码实现,我们看到它只是调用 ,我们已经分析过了。OldRpcWinStationEnumerateProcesses
RpcWinStationEnumerateProcesses
现在,我们可以将此 RPC 过程添加到函数调用图中,但由于它会导致调用被弃用,因此我们可以为节点提供黑色边框,因为它不是现代系统上图形的有效入口点。
至此,我们可以回过头来分析一下。导航到函数的代码实现后,我们会看到对列表中另一个要浏览的 API 函数的调用。WTSEnumerateProcessesExA
WTSEnumerateProcessesExA
WTSEnumerateProcessesExW
我们更新了函数调用图,以指示调用 .接下来,我们将研究 的内部工作原理。WTSEnumerateProcessesExA
WTSEnumerateProcessesExW
WTSEnumerateProcessesExW
要分析的最后一个 Windows API 函数是 。一旦我们导航到函数的代码实现,我们就会看到它调用了 ,这是我们之前调查过的一个未记录的函数。WTSEnumerateProcessesExW
WinStationGetAllProcesses
现在,我们可以完成相对于这四个新函数的函数调用图。WTSEnumerateProcesses
在结束之前,我们应该谈谈 API 集。您可能已经注意到,在一开始,当我们在“要求”部分中分析 Microsoft API 文档时,引用了一个名为 .使用此信息,我们可以派生出四个额外的函数,这些函数可以作为函数调用图的入口点。这意味着开发人员可以参考函数的 API Set 版本,例如 .WTSEnumerateProcessesA
ext-ms-win-session-wtsapi32-l1–1–0
WTSEnumerateProcesses*
ext-ms-win-session-wtsapi32-l1–1–0!WTSEnumerateProcessesA
我们可以更新函数图以包含函数的四个 API 集版本,如下所示:WTSEnumerateProcesses*
虽然本文重点分析这些函数,但攻击者可以使用其他一些函数来枚举进程,特别是进程标识符。为了完整起见,下图添加了这些附加功能。话虽如此,必须强调的是,我们应该始终假设我们的函数调用图仍然不完整。它们用于记录和代表我们目前对领土的理解,但假设我们的知识实际上是完整的是愚蠢的。WTSEnumerateProcesses*
我们可能都熟悉已知已知、已知未知、未知已知和未知未知的概念。唐纳德·拉姆斯菲尔德(Donald Rumsfeld)将已知的已知定义为我们知道的事物。这很有趣,因为“我们”是一个相对的概念。我开始这篇博文的函数调用图是相对于我的知识(在这种情况下,我们被定义为一个人)关于某人可以枚举进程的不同方式。很酷的是,“我们”的概念可以扩展到包括你的朋友圈、你的公司,甚至整个行业。这篇博文展示了我们如何利用他人的知识,至少在概念上扩大WE的范围,使我们能够拥有一张更好地代表环境现实的地图。
其它课程
二进制漏洞课程(更新中)
windows网络安全防火墙与虚拟网卡(更新完成)
windows文件过滤(更新完成)
USB过滤(更新完成)
游戏安全(更新中)
ios逆向
windbg
还有很多免费教程(限学员)
更多详细内容添加作者微信