轻松看懂的加解密系列(1)番外篇I:用Procmon监视一次AES加解密全过程

什么是 Procmon:

        Windows Procmon(Process Monitor)是一种用于监视和记录 Windows 操作系统上系统活动的高级工具。它由 Microsoft 提供,并可免费下载和使用。

你既可以下载其独立安装包:

https://download.sysinternals.com/files/ProcessMonitor.zip

也可以下载【微软系统内部工具套件】,其中也包含了 Procmon.exe(推荐方式)

https://download.sysinternals.com/files/SysinternalsSuite.zip

Procmon 能做什么:

        Procmon 通常用于分析和排查与文件系统、注册表、进程、线程、网络等相关的问题,包括性能问题、安全问题和应用程序故障等。它通常是系统管理员、开发人员和安全专业人员的重要工具。

        由于 Procmon 就像一个“360度无死角摄像头集群”一样时刻监视着 Windows 操作系统的一举一动,所以系统管理员和安全专业人员经常通过它来监视和分析系统行为。比如对于组织内新购买或升级的软件,在不得到其源代码的情况下如何甄别其是否有恶意行为。反过来,黑客和骇客在进行恶意活动时,常常想方设法来掩盖他们的轨迹,包括禁用或绕过监视工具,以便尽可能地保持隐秘性。

        以下是 Windows Procmon 的主要特点和功能:

  1. 详细事件记录:Procmon 可以记录各种系统事件,包括文件和注册表操作、进程和线程创建、网络活动、DLL加载等。这些事件以详细的方式进行记录,包括时间戳、操作类型、进程信息等。

  2. 过滤和搜索:工具允许用户设置各种过滤器,以仅记录感兴趣的事件。这有助于减小记录文件的大小,使分析更加容易。还可以使用搜索功能查找特定的事件或关键字。

  3. 导出和导入日志:Procmon 支持将记录的日志导出到文件,以便在不同的系统上进行分析。比如对于一些偶发性的问题,客户可以简单方便地将问题发生时的 Procmon 日志导出给相关专业人员,而避免了双方花费大量时间在线 Debug。

  4. 进程和线程信息:您可以查看每个进程和线程的详细信息,包括可执行文件路径、命令行参数等。这有助于确定哪个进程或线程触发了特定的事件。

  5. 网络监视:Procmon可以记录应用程序的网络活动,包括TCP和UDP通信。这对于排查网络问题和安全审计非常有用。

  6. 自动收集信息:工具还可以捕获调用栈信息,以帮助确定事件的来源。比如很多软件的奇怪问题是由于某种杀毒软件“中途作梗”造成的。这类问题即使通过软件自己的日志也很难确定具体原因,而通过 Procmon 的调用栈信息,可以很容易定位问题所在。

Procmon 对开发人员最大的价值:

  1. 故障排除和调试: 开发人员在开发和测试过程中经常会遇到各种问题,如应用程序崩溃、资源泄漏(如内存泄漏、文件句柄泄漏等)、性能问题、文件访问错误等。使用 Procmon 可以监视应用程序的行为,帮助开发人员识别和解决这些问题。

  2. 分析应用程序行为: Procmon 可以记录应用程序与操作系统的交互,包括文件和注册表访问、网络通信等。这对于开发人员来说是一个有用的工具,可以帮助他们理解应用程序的行为,确保其按预期运行。

Procmon 监视加解密全过程详解:

        由于例程没有涉及到网络操作,所以我们用 Procmon 主要监视例程的文件和注册表操作、进程和线程创建、DLL加载等。笔者所用的 Procmon 版本如下:

图-1 Procmon About
图-2 仅打开了监视注册表、文件和进程

        废话不多说,我们在《轻松看懂的加解密系列(1)》源程序里的每个关键处都设置了断点,在每个断点相关行,我们都会同时分析一下程序当时的堆栈信息和 Procmon 在连续的两个断点之间都监控到了什么。接下来就请放松心情,让我们来见识一下在系统层面 Windows 究竟做了些什么吧。

断点1: 执行到 CryptAcquireContext 之前

图-3 断点1 堆栈截图
程序堆栈层面,可以观察到以下几点:
  • 栈变量初始化后,DWORD, HCRYPTPROV, HCRYPTKEY 被归类到 unsigned long,即无符号32位整数类型,并被初始化为 NULL 或者0。
  • BYTE* 本质上是 unsigned char *,其中将要被加密的明文 “Hello, World!” 是窄字符串,长度为13,起始内存地址为【0x00dd7bc8】。其余的 BYTE* 变量被初始化为 NULL。
  • 值得顺便一提的是,一般编译器在编译整个程序的时候,会把字符串常量都安排到了一起。所以在紧随其后的内存里(图-3),甚者还能看到“CryptAquireContext failed: %d” 等字样。
图-4 断点1 Procmon截图1
图-5 断点1 Procmon截图2
Procmon 记录的内容:

        如上【图-4】和【图-5】,此断点区间记录到的事件主要为加载实例主程序 exe 文件和 Windows 操作系统内的诸多核心系统库、扩展库和安全库,以及相关注册表键值,比如:

  • 【ntdll.dllkernel32.dllkernelbase.dll】 都是 Windows 操作系统的核心库,它们提供了操作系统级别的功能和服务,由开发人员间接使用,因为更高级别的 API 和库会在其基础上构建。
  • 【wow64.dll,wow64win.dll,wow64log.dll,wow64cpu.dll】,这几个DLL,看名字应该大致就可以猜出来是为了确保32位应用程序能够在64位系统上顺利运行。
  • 【advapi32.dll】 包含了一系列的函数和服务,用于处理操作系统的安全性、认证、授权、加密、注册表、事件日志、服务控制和其他系统管理任务。
  • 【msvcrt.dll】 是 Microsoft C Runtime Library 的一部分,它是 Microsoft Visual C/C++ 运行时库的动态链接库文件(DLL),其中 rt 就是 Runtime 的缩写。这个库提供了一系列的C语言运行时函数和宏,用于支持C/C++编程语言的基本功能。包括最常用的输入输出(I/O)、内存管理、字符串处理、数学运算、时间和日期函数等。
  • 【sechost.dll】 是 Windows 安全主机服务(Security Host Service),负责管理和支持操作系统的安全功能,包括身份验证、授权、加密、凭据管理和安全策略等。
  • 【rpcrt4.dll】 这个库提供了与远程过程调用(RPC,Remote Procedure Call)相关的函数和服务。
  • 【vcruntime140d.dll 和 ucrtbased.dll】 都是 【Visual Studio 2015 的 Microsoft Visual C++ 可再发行组件】的一部分。这些 DLL 文件包含了与 Visual C++ 2015 相关的运行时库函数和支持。其名称最后部分字母 d,或者 based,都表明了其是被用于 debug 版本可执行文件加载的。

        此外还记录到例程读取了一些相关注册表配置用于初始化,比如:

  • 【HKLM\System\CurrentControlSet\Control\Session Manager】 是注册表中的一个关键路径,包含了一系列与系统启动、进程管理、服务控制和系统设置等相关的配置信息。下图-6 就是存储系统环境变量的路径。看到后是不是有种豁然开悟的感觉?
图-6 注册表存储系统环境变量的路径
  • 【HKLM\SOFTWARE\Policies\Microsoft\Windows】 路径下存储了 Windows 操作系统的一些策略设置。这些策略设置可以用于配置和管理计算机上的各种行为和功能,以增强系统的安全性、隐私性和管理性。
图-7 HKLM下的 Windows 策略设置及其子项

        综合以上内容,可以看出在此区间,应用程序主要涉及到系统初始化、配置、远程过程调用、C/C++ 编程支持、系统安全性和权限管理等方面的操作。

断点2: 执行到 CryptGenKey 之前

图-8 断点2 堆栈截图
在程序堆栈层面,

        可以观察到 CSP 句柄已经被成功赋值。

图-9 断点2 Procmon截图1
Procmon 记录的内容:

        Cryptographic Service Provider (CSP) 是用于提供加密和解密功能的组件,cryptsp.dll 是其一部分。可以看到程序在执行 CryptAcquireContext 的时候,首先尝试在其执行目录中搜索cryptsp.dll。如果此时恰好有名称为 cryptsp.dll 的文件在此,它将首先被加载。本例实际执行当中,由于 cryptsp.dll 是 Windows 操作系统的一个核心系统文件,一般不会位于应用程序执行目录,所以 Result 为 NAME NOT FOUND,接着程序按照默认的动态链接库加载顺序,顺位到系统目录继续搜索。由于例程是一个运行在64位系统上的32位程序,所以顺利地在 【C:\Windows\SysWOW64\cryptsp.dll】 下完成了加载。

        在这里特别要说明,很多恶意软件正是利用了默认的动态链接库加载顺序,将篡改过的同名DLL设法置于应用程序的执行目录,让应用程序误加载,从而代替真正的系统文件来执行其恶意代码。

        接下来可以观察到程序开始尝试访问【HKLM\SOFTWARE\WOW6432Node\Microsoft\Cryptography\Defaults\Provider Types\Type 024】

图-10 CSP类型相关注册表键值

        之所以目标明确地直接访问 Type 024,是因为我们在源程序里调用 CryptAcquireContext 时选择的 CSP提供类型参数是 PROV_RSA_AES,其被定义在 wincrypt.h 里的序列号正是 24。

// certenrolld_begin -- PROV_RSA_*
#define PROV_RSA_FULL           1
#define PROV_RSA_SIG            2
#define PROV_DSS                3
#define PROV_FORTEZZA           4
#define PROV_MS_EXCHANGE        5
#define PROV_SSL                6
#define PROV_RSA_SCHANNEL       12
#define PROV_DSS_DH             13
#define PROV_EC_ECDSA_SIG       14
#define PROV_EC_ECNRA_SIG       15
#define PROV_EC_ECDSA_FULL      16
#define PROV_EC_ECNRA_FULL      17
#define PROV_DH_SCHANNEL        18
#define PROV_SPYRUS_LYNKS       20
#define PROV_RNG                21
#define PROV_INTEL_SEC          22
#if (NTDDI_VERSION >= NTDDI_WINXP)
#define PROV_REPLACE_OWF        23
#define PROV_RSA_AES            24
#endif //(NTDDI_VERSION >= NTDDI_WINXP)
图-11 CSP名称相关注册表键值

        由 Type 024 接着映射到了 【Name】 “Microsoft Enhanced RSA and AES Cryptographic Provider”,最后从 【HKLM\SOFTWARE\WOW6432Node\Microsoft\Cryptography\Defaults\Provider\Microsoft Enhanced RSA and AES Cryptographic Provider\Image Path】 取到了能提供对应 CSP 的动态链接库文件 rsaenh.dll。又由于当前运行的系统是64位的,所以重定向的最终目录是 【C:\Windows\SysWOW64\rsaenh.dll,而不是%SystemRoot%\system32\rsaenh.dll】。

        这里还有一处细节会是大家日后阅读 Procmon 日志经常遇到的,就是 【Result】 “BUFFER OVERFLOW”,从【图-9】可以看到程序在读取 【Name】 和 【Image Path】 的时候都出现了几次 “BUFFER OVERFLOW”,这一般是因为程序在不知道读取目标字符串长度的情况下,先会尝试设置较短长度缓冲区,如果失败就继续尝试申请更长的缓冲区直至成功读取。并不是程序真正造成了“缓冲区溢出”这样严重的错误。所以我们虽然看不到微软操作系统核心文件的源代码,但根据其行为结果可以大致猜想到其逻辑步骤,并且这也是VC程序常见的一种写法。

   【rsaenh.dll】(也称为 Microsoft Enhanced Cryptographic Provider)它提供了加密和解密功能,是 Windows Cryptographic API 的一部分。这个库包括了对称加密、非对称加密、散列算法、数字签名等密码学功能的支持。其中就包括了对 AES 算法的支持。

      【bcrypt.dll】 提供了密码学相关的功能和安全服务。包括加密、散列、随机数生成、密钥管理等。

   【sspicli.dll】(Security Support Provider Interface Client Library)是 Windows 操作系统的一个核心系统文件,它是 Windows 安全支持提供程序接口(Security Support Provider Interface,SSPI)的一部分。SSPI 是 Windows 提供的一种安全支持架构,用于实现身份验证、加密和安全通信等安全相关的功能。

图-12 断点2 Procmon截图2

        在最终关闭注册表键 【HKLM\SOFTWARE\WOW6432Node\Microsoft\Cryptography\Defaults\Provider\Microsoft Enhanced RSA and AES Cryptographic Provider】,执行到下一个断点之前。程序除了继续加载一些必要的 DLL(包括 crypt32.dll, dpapi.dll, cryptbase.dll)之外,最值得注意的操作就是对文件【C:\Users\jiesu\AppData\Roaming\Microsoft\Crypto\RSA\S-1-5-21-1315148102-856555410-3627708698-1001\663ed5f4fe50a280b70b3badd6c59de5_7914d52b-311e-450f-ad30-0ea710e89468】的访问了,详细解释如下:

   【crypt32.dll】 是 Windows 操作系统的一个核心系统文件,它提供了与数字证书和加密相关的功能和服务。这个库是 Windows Cryptographic API 的一部分,用于处理数字证书管理、加密、解密、签名和验证等密码学操作。

   【dpapi.dll】 是 Windows 操作系统的一个核心系统文件,它是 Data Protection API(DPAPI)的一部分。DPAPI 是一组密码学功能和接口,用于帮助应用程序和系统组件保护和加密敏感数据。

   【cryptbase.dll】 是 Windows 操作系统的一个核心系统文件,它是 Cryptographic Base API 的一部分。这个库提供了密码学相关的基本功能和接口,用于支持高级密码学服务和应用程序。

        以【图-12】中的路径【C:\Users\jiesu\AppData\Roaming\Microsoft\Crypto\RSA\S-1-5-21-1315148102-856555410-3627708698-1001】为例,每个用户都有一个类似的目录,用于存放加解密相关的文件,比如默认和自定义的密钥容器文件。其中路径的最后一段正是当前用户在系统中唯一的 SID。而目录下的【663ed5f4fe50a280b70b3badd6c59de5_7914d52b-311e-450f-ad30-0ea710e89468】这一文件,理论上应该是程序在调用获取CSP接口时所用到的默认密钥容器文件。

        笔者曾经做过测试,如果强行用管理员权限在执行本测试程序前删除掉此文件,将会因为无法创建目标 CSP 导致程序执行失败。而产品级软件,在面对即使删除了相关文件时,也会自动再次生成。这些软件究竟是如何做到的,后续系列文章会给予更多相关讲解,敬请期待。

断点3:执行到 CryptEncrypt 之前

图-13 执行加密函数之前的堆栈截图
在程序堆栈层面,可以观察到以下几点:
  • 上一步生成的密钥句柄【hKey】将被传入加密函数 CryptEncrypt
  • 【*ppEncryptedData】在加密前指向地址【0x007D9718】,此地址所指向的缓冲区在加密结束后将存入被加密的密文;
  • 【*pEncryptedDataLen】通过计算,首轮设置的长度为30;
Procmon 记录的内容:
图-14 产生密钥过程中 Procmon 所记录的注册表读取
图-15 产生密钥过程中的注册表读取路径

        可以看到从产生密钥至加密函数被执行之前,仅有少量的读取 Key Provider 的注册表操作事件被记录到。这从另一个侧面反映了程序在完成一个加密工作所必要的“资源”都已经加载完毕了,包括文件加载、进程和线程创建、配置读取等初始化工作。接下来最主要的就是程序运算执行了,而 Procmon 并不善于监控程序的运算。其中 Result 【REPARSE】是一种系统读取注册表的重定向结果,并不是什么错误。

断点4:执行完加密函数 CryptEncrypt

图-16 执行完加密函数的堆栈截图
在程序堆栈层面:
  • 可以观察到之前存放明文的缓冲区已经被密文替换;
  • 密文的长度为16,所以之前预留的长度30是足够的,所以没有出现 ERROR_MORE_DATA;

断点5:执行到解密函数 CryptDecrypt 之前

图-17 执行解密函数前的堆栈截图
在程序堆栈层面:
  • 新的准备传入给解密函数的缓冲区里已经拷贝了密文;
  • 指定的密文长度为16;

断点6:执行完解密函数 CryptDecrypt

图-18 执行完解密函数的堆栈截图
在程序堆栈层面:
  • 存放密文的缓冲区被解密后的明文所替换;
  • 解密函数返回了明文的实际长度为14(含末尾字符0)
图-19 从产生密钥至程序退出的 Procmon 记录

        笔者还不放心,多次执行程序后反复确认从产生密钥至程序退出所有的能被记录的事件都在【图-19】中了,几乎没有再加载额外的文件和配置,反而主要都是些资源回收工作了。

        关于 Procmon 的其它知识点,包括笔者以往所亲历的一些通过 Procmon 解决产线的实际案例,将集中放在《轻松看懂的加解密系列(1)番外篇II》中,以飨读者。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值