在这个由五部分组成的博客文章系列中,我们将讨论Windows内核中的本地特权提升漏洞 CVE-2018-8611的利用。最初,这个漏洞是由卡巴斯基在野外发现 0day 漏洞利用而被披露的。目前尚未发现有关此漏洞的大量公开细节发布,也未公开发布此漏洞的样本。此外,卡巴斯基发现的漏洞利用的哈希值也从未发布过。
CVE-2018-8611影响Windows内核的称为内核事务管理器(KTM)的组件,公共的安全社区尚未对此进行过多研究。CVE-2018-8611是一个内核竞争条件漏洞,也是Windows( bochspwn 除外)上讨论得相对较少的一类bug。有趣的是,对KTM的访问不受当前系统调用沙箱过滤器的限制。这不同于通常被沙箱阻止的 win32k组件,例如 Chrome 强制执行的 win32k syscall 过滤器 。这意味着这个漏洞在客户端利用场景中充当了宝贵的沙箱逃逸角色。
我们将深入研究 KTM 的内部,展示我们的补丁分析,弄清漏洞的根本原因是什么(借助卡巴斯基相对较少的技术细节),最后讨论了我们开发相当可靠的漏洞利用方法。我们能够开发一种可在x86和x64体系结构上的Windows Vista至Windows 10 1809的所有版本上使用的漏洞利用程序。
这项研究由NCC团队漏洞利用开发小组(EDG)的Aaron Adams 和 Cedric Halbronn 完成。这项研究首先在 POC2019 上发表,可以在这里 找到该演讲的幻灯片。
与我们的许多其他公开的 writeup 一样,我们将在进行这项研究时尝试详细解释我们的想法和方法,以帮助刚接触该领域的人们。我们试图突出一些遇到的障碍,错误和死胡同。与往常一样,我们很高兴收到建议,更正和反馈。
我们最初发现这个漏洞,是阅读了卡巴斯基于2018年12月发布的的 博客 。文章指出,2018年10月,卡巴斯基(Kaspersky)一项名为自动漏洞利用防御(AEP)的技术检测到该漏洞的利用,然后将其报告给微软。这导致该漏洞于2018年12月被微软修复。
卡巴斯基博客在细节上篇幅讲述较少,但是提供了一些非常有用的提示。老实说,刚开始的时候其中的某些信息感觉没有多大用处,或者只是暂时还没有找到他们之间的关联,但是后来我们加深了对 KTM 和漏洞本身的理解后,他们提供的有关该漏洞利用的信息开始变得越来越有用。
卡巴斯基针对漏洞和利用行为的全部公开技术说明均逐字粘贴在以下两段中,当我们描述分析时,我们会不时地提及到它们:
这两段中包含很多信息,对于读者来说,其中的大多数现在可能没有多大意义,但是希望您继续阅读。值得注意的是,即使知道如何利用此漏洞,也不是所有的内容都有意义,但可以仅仅表示略有不同的漏洞利用方法。
在2019年初利用此漏洞,并随后在2019年10月准备博客和POC演示文稿之后,我们被告知,卡巴斯基于2019年5月在BlueHat Shanghai上提供了有关0day漏洞利用的更多 细节 。我们分析了0day漏洞利用的一些技术,并将其与博客系列第5部分中的方法进行了比较。
如果您想复现其中的一些工作,我们使用以下工具进行了大部分研究:
许多在线工具/资源也非常有帮助:
感谢所有开发和维护以上工具和资源的人!
关于从Windows 7上开始,我们进行了一些反复讨论,最后之所以选择它,是因为根据我们在使用 win32k 时的经验,有时会有更多的符号,因此我们认为 KTM 也可能是这种情况-尽管据我们所知最终实际上并没有什么不同。
选择 Windows 7 的另一个原因是 IDA 对于包含多个相关文件的项目效果不佳。从Windows 8开始,KTM 从ntoskrnl.exe
分离到 tm.sys
中。由于只包含 KTM 函数,因此首先关注tm.sys
可能会吸引人。但是,由于KTM使用了ntoskrnl.exe
中的许多函数,因此逆向分析实际上会更加繁琐,因此我们将在两个IDA数据库之间进行切换。
首先选择 Windows 7 的一个缺点是,我们首先构建了可在Vista和Windows 7上运行的写原语。但是,事实证明它由于Windows 8及更高版本上的 缓解措施 而失败,从而迫使我们重新考虑我们的方法。这些决定总是需要权衡的。通常最好不要过度分析并继续下去。
鉴于我们的逆向工作主要是在Windows 7上完成的,本文中几乎所有代码段均来自经过反编译的Windows 7 ntoskrnl.exe
二进制文件。如果在其他Windows版本中存在一些值得注意的行为差异,我们会尝试提及,但由于该漏洞相当复杂,我们可能会忽略一些细节。因此,如果您选择复现,请注意当您从 Windows Vista 移植到 Win10 时,您会发现在代码中的结构布局,偏移量方面遇到细微差异。如果存在这些差异,则在移植漏洞利用程序时,有时会需要作特殊处理。
还需要注意的是,在Windows 8及更高版本上,通过补丁对比分析 tm.sys
不会导致其他 ntoskrnl.exe
更改或漏洞出现在您的结果中,因此,在Windows 8及更高版本上进行补丁对比分析 KTM 漏洞要更加容易一些。
我们专门逆向了 Windows 7 x64 版本的 ntoskrnl.exe
(版本号为 6.1.7601.24291
)。
在查看了补丁(稍后我们将描述)之后,并没有真正理解我们所看到的内容,我们决定首先需要尽可能多地了解KTM 是什么。我们选择从逆向大多数主要内核API和系统调用实现开始,同时建立我们自己的一组用户层代码示例以探索 API。我们知道最终我们将能够重用许多小样例代码进行利用编写,这使我们能够缓慢地建立起坚实的理解基础。
与许多其他MSDN页面不同, MSDN KTM 门户网站 上很少有公开的可用的KTM代码示例,因此我们没有很多可重用的代码段。
另一个资源是 MSDN 内核模式KTM门户 ,它从内核方面讨论KTM内部。在理解新概念时,它非常有用,但也很麻烦。刚开始不必理解所有内容,但确实值得一读。另外请注意,我们只对内核API感兴趣,以便更好地理解概念,但由于我们本地的特权提升漏洞利用只能调用用户层KTM API,因此我们将无法直接调用它们。
因此,我们编写的大部分内容都是通过反复试验或通过逆向KTM组件使用API的方式得出的。即使MSDN上的KTM文档非常详尽,但在没有工作代码段来演示概念之间的相关性的情况下,对所有术语进行整理还是有些令人困惑。
我们还找到了有关KTM总体工作原理的很好概述,而官方文档中没有,这是Microsoft在2005年至2007年之间发布的3个视频系列,作为“深入”系列中有关Vista新发行版Windows技术的一部分的视频:
最近发现一些名为 "Proton Bot" 的恶意软件使用 KTM 函数试图绕过 API监控/钩子之类的东西。不过,它似乎并没有真正利用KTM的事务性来实现其他任何目的。
KTM 是Windows Vista中添加的一种技术,用于引入“事务操作”概念。Windows本身至少在Windows注册表和NTFS文件系统中使用此组件。这个概念特别存在于数据库领域,例如SQL等。想法是,某些给定的操作(称为“事务”)可能需要跨多个资源完成大量相关工作。这样的事务在较高的层次上代表着单个工作,需要有效地实现原子化,如果任何一个部分失败,则整个事务都会失败。
这种类型的“事务”对于复杂的多进程操作尤其重要。自动取款机(ATM)试图协调自己的现金和请求者的账户信息,这是一个常见的例子,可能受益于原子交易的概念。安装软件是另一个例子,在中断时,它需要回滚所有更改,因为比如说用户取消安装。
这意味着,如果操作的任何部分最终失败,则可以回滚已完成的与该事务相关的所有其他工作,使其看起来好像从未发生过一样。只有当与事务相关的所有工作都成功时,该事务才实际完成。
KTM的概念是从某些故障中恢复并回滚某些事务。我们稍后将详细介绍它们,但现在只需注意,如果与事务相关的某个工作失败,系统的设计将通知参与该事务的所有其他工作人员,以确保他们知道某些事情已经失败,或者他们都需要重新同步到某个商定的状态。
MSDN KTM 门户 是微软如何提供api以允许用户层客户端软件处理事务的主要参考。
我们对KTM中涉及的四个主要组件感兴趣,我们将更详细地描述它们。在整个文档中,我们在下一节中交替使用它们的长格式和短格式名称:
上面已经暗示了这一点,但是事务是KTM中其他所有事情的基础。微软文档 提供 了相当全面的高层解释。它们还在 这里 提供了较低层次的视图。
从我们的角度来看,事务是 _KTRANSACTION
内核结构,与事务管理器有关联,还有一个或多个登记。它有效地代表了一些期望原子化完成的多部分工作。事务用于跟踪即将启动、正在执行或已完成的工作。
事务主要可以执行三个操作:创建,提交和回滚。提交事务意味着将部分操作转换为永久更改。回滚事务只是意味着在事务实际完成之前还原到目前为止已发生的所有部分操作。已回滚的事务无法提交,反之亦然。
以下是 _KTRANSACTION
结构的代码段:
实际上,我们并没有太多使用到 _KTRANSACTION
结构,但是很有趣的是它跟踪了登记数量(EnlistmentCount
),登记链表(EnlistmentHead
)和指向关联事务管理器(Tm
)的指针。
我们只需调用 CreateTransaction()
用户态函数来创建事务:
值得注意的是,大多数 KTM 内核结构都有一个cookie
字段,可以在上面的_KTRANSACTION
结构中看到。这些对于每种结构类型都是唯一的,因此在调试器中查找以确认我们正在查看正确的_KTM *
类型和偏移量时非常有用。下面我们列出了有用的 KTM 类型的cookie值:
大多数KTM *
对象还检测到池标记 ,允许在内核池上跟踪它们。下面我们仅显示与我们将描述的漏洞最相关的结构的标记。
事务管理器基本上是一个管理一般事务的实体。它是KTM层次结构中顺序最高的部分。通常,事务管理器将包含一个或多个与其关联的资源管理器。为了实际执行任何类型的事务,必须首先创建一个事务管理器。
出于利用目的,一个重要的事务管理器概念是易失性和持久性之间的区别:
从 MSDN 总结的事务管理器:
使用持久性事务管理器时,您所做的一切最终都会记录在磁盘上的日志文件中。如果您正在做类似重复尝试使用成千上万的登记实现条件竞争的事情,那么它很可能会很快填满日志并可能导致错误。通过反复试验,我们了解到使用易失性事务管理器可以解决许多问题,而这正是我们用来利用的方法。
描述事务管理器的内核结构是 _KTM
:
与 _KTRANSACTION
一样,在开发利用过程中,我们无需在实践中使用过多。许多内容与日志记录(LogFileName
,LogFileObject
)有关,但它也跟踪所有关联的资源管理器(TmRm
)。
Flags
字段使用未记录的标记,在逆向时我们对其进行了如下注释:
通常,创建事务管理器是您要做的第一件事,必须调用 CreateTransactionManager()
:
资源管理器是管理资源的实体,它管理将在完成与事务相关的某些工作中涉及的资源。例如,为了完成事务,可能必须同时涉及文件系统和注册表并进行一些工作。在这种情况下,文件系统和注册表都将拥有自己的关联资源管理器,并且每个资源管理器将负责管理该事务工作的某些部分。每个资源管理器都将通过创建与该事务相关联的登记加入事务中以完成必要的工作。在等待事务完成时,您将等待相关的两个资源管理器完成工作,然后再继续。
从利用的角度来看,我们只是创建自己的资源管理器,并加入到自己的事务中,因此它并不能真正反映大多数真实的用例。
使用 CreateResourceManager()
用户层函数创建资源管理器:
资源管理器的内核结构是 _KRESOURCEMANAGER
:
这是了解漏洞和利用的非常重要的内核结构。最重要的字段是:
Flags
字段使用未记录的标记,在逆向时我们对其进行了如下注释:
我们将在描述漏洞以及如何利用此漏洞时,进一步讨论如何使用其中的某些字段。
理解登记的最好方法是将其视为某个资源管理器为完成事务而执行某项工作的承诺。该资源管理器已登记到事务中,这意味着所有其他登记都需要协调状态,以便完成事务。一个登记可以看作是与事务相关联的一项工作。只要该登记没有转换为只读登记,就需要指示它已完成其工作的给定阶段,这最终允许事务转换到下一个状态。是否可以进行回滚或恢复还将取决于与事务相关的某些或所有登记的状态。我们稍后将更详细地描述这些状态。
_KENLISTMENT
的内核结构如下:
最重要的字段是:
Flags
字段使用未记录的标记,在逆向时我们对其进行了如下注释:
要创建登记,请调用 CreateEnlistment()
函数。您必须为先前创建的资源管理器和事务指定一个句柄。
在这里,我们将继续讨论一些暂时使我们放慢速度的事情。 微软 文档 将TRANSACTION_NOTIFY_MASK = 0x3fffffff
指定为指示所有有效位的掩码,我们希望仅使用它来接收所有通知。但是,我们在调用CreateEnlistment()
时总是会出错,事实证明,当TRANSACTION_NOTIFY_MASK
指定的标志组合作为您要接收的所有通知集传递时,本身就是无效的组合。我们也不想只指定文档化的位,或者将所有位进行“或”运算,因为可能会有未文档化的通知。我们逆向了逻辑,注意到一个名为TmpIsNotificationmaskValid()
的函数所做的测试:
在我们的例子中,我们实际上并没有使用“高级”登记,因此我们只对第二个 if
条件感兴趣。TRANSACTION_NOTIFY_MASK
为 0x3FFFFFFF
,但我们需要确保0x60000F0
中的位没有设置。其余测试将确保如果将一个位置1,则不会设置另一位。因此,我们只需执行“设置所有可能的路由”,这意味着我们只需要避免使用0x60000F0
位:
这就是为什么我们在前面的示例中传递 0x39ffff0f
的原因。
上面我们简要介绍了一些与“高级”登记有关的逻辑,并表示我们不会故意使用它们。但是,值得一提的是,尽管我们没有明确地将它们用于开发利用,但我们仍会滥用一些与逻辑相关的功能来帮助我们进行调试。
高级登记用于“分布式”事务场景,其中一些登记分散在某个分布式渠道中,而一个登记负责其他登记。我们认为,在 MSDN KTM 门户上 对它们的描述有些欠缺,因为没有一个地方可以简洁地对其进行解释。它在传递各种函数描述时更容易被提及。
最重要的是,当您试图了解KTM API并选择要使用的函数时,了解某些功能仅适用于高级登记,而另一些功能用于非高级登记是很有用的, 这可能会有些混乱。特别是,仅高级函数的命名通常看起来像您想使用的名称。区别的方法是任何以Enlistment
结尾的 API 名称通常只能在高级登记中使用。但是,如果API名称以Complete
结尾,则适用于非高级登记。API文档中对此进行了详细的技术描述,但是如果您不熟悉KTM,并不一定总是很清楚。例如,对于诸如 CommitEnlistment()
之类的内容,它的含义如下:
将它与 CommitComplete()
相对比,后者表示以下内容:
下面是两个简短的列表,显示了转换登记状态的常用函数。
从内核的角度来看,通过在 _KENLISTMENT.Flags
字段中设置的未记录的KENLISTMENT_SUPERIOR
标志来指示是否为高级登记(如上所述)。如果使用非高级登记来触发漏洞,则通常不会输入与高级登记有关的条件语句。记住这一点很有趣,因为我们将利用这一事实来帮助我们的内核调试,在随后的一场成功的条件竞争中成功。
在任何时候,内核结构都会跟踪所有登记以及相关的工作。根据API的请求,或发生其他错误时,可以还原部分提交的事务(称为回滚)。相反,请求将登记或与资源管理器关联的所有登记恢复到某个以前的状态称为恢复 。
两者之间的主要区别在于,回滚意味着正在回滚的事务实际上已被搁置,而恢复是尝试使活动事务中涉及的所有登记表都达到某种已知的同步状态,以便事务可以继续进行。这种区别足以围绕我们将要讨论的内容进行讨论。
为了恢复与资源管理器关联的所有登记,我们调用 RecoverResourceManager()
。同样,RecoverTransactionManager()
触发与事务管理器关联的所有内容的恢复。或者,可以使用RecoverEnlistment()
恢复单个登记。
我们使用 RollbackTransaction()
回滚事务,或者,我们可以通过 RollbackEnlistment()
使用与该事务关联的登记来回滚事务。
对于理解CVE-2018-8611,我们在测试期间注意到的最重要的一点是,为了进行资源管理器恢复,此资源管理器必须已经具有至少一个已提交的事务要回滚。
每个资源管理器都有一组发生在关键节点上的关联事务通知,例如从一个状态切换到另一个状态的登记。其中一个非常相关的示例是,在恢复资源管理器时,将为恢复中涉及的每个登记发送通知。如果要从用户区读取这些通知,请调用GetNotificationResourceManager()
。这些事件被放入资源管理器(_KRESOURCEMANAGER.NotificationQueue
)跟踪的FIFO队列中。
从关联的TRANSACTION_NOTIFICATION
结构获取通知并打印有关通知的某些信息的示例如下所示。该结构后接TRANSACTION_NOTIFICATION_RECOVERY_ARGUMENT
,因此我们使用以下结构:
以下代码显示了如何读取通知:
当您请求通知信息以及其他信息时,您会收到与拥有相关通知的确切登记相关联的GUID。
我们将进一步讨论登记和事务稍后可能转换到的实际状态。
在这篇博客文章中,我们介绍了KTM在内核中的工作原理及其相关的KTM内核对象结构的基本背景。我们还详细介绍了如何在用户层使用与KTM相关的重要函数与KTM进行交互。
我们博客的第2 部分 将深入了解CVE-2018-8611漏洞和补丁。
原文链接:https://research.nccgroup.com/2020/04/27/cve-2018-8611-exploiting-windows-ktm-part-1-5-introduction/
翻译: 本文由看雪翻译小组 fyb波 翻译
校对: 本文由看雪翻译小组 一壶葱茜 校对
为了滥用这个漏洞,漏洞利用程序首先创建一个命名管道,然后将其打开以用于读和写。然后,它创建一对新的事务管理器对象, 资源管理器对象,事务对象并创建大量登记对象,我们称之为“事务
在所有的初步工作准备完成后,来到漏洞利用的第二部分——触发漏洞。它创建多个线程并将它们绑定到一个CPU核心。第一个创建的线程在循环中调用 NtQueryInformationResourceManager,而第二个线程试图执行一次NtRecoverResourceManager。但是漏洞本身是在第三个线程中触发。该线程使用 NtQueryInformationThread 的执行技巧获取第二个线程最近一次执行的系统调用信息 NtRecoverResourceManager的成功执行说明已经发生条件竞争情况,并且进一步在先前创建的命名管道上执行WriteFile操作,导致内存崩溃。
为了滥用这个漏洞,漏洞利用程序首先创建一个命名管道,然后将其打开以用于读和写。然后,它创建一对新的事务管理器对象, 资源管理器对象,事务对象并创建大量登记对象,我们称之为“事务
在所有的初步工作准备完成后,来到漏洞利用的第二部分——触发漏洞。它创建多个线程并将它们绑定到一个CPU核心。第一个创建的线程在循环中调用 NtQueryInformationResourceManager,而第二个线程试图执行一次NtRecoverResourceManager。但是漏洞本身是在第三个线程中触发。该线程使用 NtQueryInformationThread 的执行技巧获取第二个线程最近一次执行的系统调用信息 NtRecoverResourceManager的成功执行说明已经发生条件竞争情况,并且进一步在先前创建的命名管道上执行WriteFile操作,导致内存崩溃。
/
/
0x2d8
bytes (sizeof)
struct _KTRANSACTION
{
struct _KEVENT OutcomeEvent;
/
/
0x0
ULONG cookie;
/
/
0x18
struct _KMUTANT Mutex;
/
/
0x20
[...]
struct _GUID UOW;
/
/
0xb0
enum _KTRANSACTION_STATE State;
/
/
0xc0
ULONG Flags;
/
/
0xc4
struct _LIST_ENTRY EnlistmentHead;
/
/
0xc8
ULONG EnlistmentCount;
/
/
0xd8
[...]
union _LARGE_INTEGER Timeout;
/
/
0x128
struct _UNICODE_STRING Description;
/
/
0x130
[...]
struct _KTM
*
Tm;
/
/
0x200
[...]
};
/
/
0x2d8
bytes (sizeof)
struct _KTRANSACTION
{
struct _KEVENT OutcomeEvent;
/
/
0x0
ULONG cookie;
/
/
0x18
struct _KMUTANT Mutex;
/
/
0x20
[...]
struct _GUID UOW;
/
/
0xb0
enum _KTRANSACTION_STATE State;
/
/
0xc0
ULONG Flags;
/
/
0xc4
struct _LIST_ENTRY EnlistmentHead;
/
/
0xc8
ULONG EnlistmentCount;
/
/
0xd8
[...]
union _LARGE_INTEGER Timeout;
/
/
0x128
struct _UNICODE_STRING Description;
/
/
0x130
[...]
struct _KTM
*
Tm;
/
/
0x200
[...]
};
HANDLE hTx
=
CreateTransaction(
NULL,
/
/
lpTransactionAttributes
0
,
/
/
UOW
0
,
/
/
CreateOptions
0
,
/
/
IsolationLevel
0
,
/
/
IsolationFlags
0
,
/
/
infinite timeout
L
"ExampleTx"
/
/
Description
);
HANDLE hTx
=
CreateTransaction(
NULL,
/
/
lpTransactionAttributes
0
,
/
/
UOW
0
,
/
/
CreateOptions
0
,
/
/
IsolationLevel
0
,
/
/
IsolationFlags
0
,
/
/
infinite timeout
L
"ExampleTx"
/
/
Description
);
Cookie
Object type
0xb00b0001
_KTRANSACTION
0xb00b0002
_KRESOURCEMANAGER
0xb00b0003
_KENLISTMENT
0xb00b0004
_KTM
0xb00b0005
Protocol Address Info?
0xb00b0006
Propagate Request?
Pool tag
Object type
TmTx
_KTRANSACTION
TmRm
_KRESOURCEMANAGER
TmEn
_KENLISTMENT
TmTm
_KTM
/
/
0x3c0
bytes (sizeof)
struct _KTM
{
ULONG cookie;
/
/
0x0
struct _KMUTANT Mutex;
/
/
0x8
enum KTM_STATE State;
/
/
0x40
[...]
ULONG Flags;
/
/
0x80
ULONG VolatileFlags;
/
/
0x84
struct _UNICODE_STRING LogFileName;
/
/
0x88
struct _FILE_OBJECT
*
LogFileObject;
/
/
0x98
[...]
struct _KRESOURCEMANAGER
*
TmRm;
/
/
0x2a8
[...]
};
/
/
0x3c0
bytes (sizeof)
struct _KTM
{
ULONG cookie;
/
/
0x0
struct _KMUTANT Mutex;
/
/
0x8
enum KTM_STATE State;
/
/
0x40
[...]
ULONG Flags;
/
/
0x80
ULONG VolatileFlags;
/
/
0x84
struct _UNICODE_STRING LogFileName;
/
/
0x88
struct _FILE_OBJECT
*
LogFileObject;
/
/
0x98
[...]
struct _KRESOURCEMANAGER
*
TmRm;
/
/
0x2a8
[...]
};
enum KTM_FLAGS {
KTM_FLAG_VOLATILE
=
0x01
,
KTM_FLAG_COMMIT_SYSTEM_VOLUME
=
0x02
,
KTM_FLAG_COMMIT_SYSTEM_HIVES
=
0x04
,
KTM_FLAG_COMMIT_LOWEST
=
0x08
,
KTM_FLAG_THAW
=
0x10
,
KTM_FLAG_NO_IDENTITY
=
0x20
,
KTM_FLAG_CORRUPT_FOR_PROGRESS
=
0x40
,
KTM_FLAG_CORRUPT_FOR_RECOVERY
=
0x80
,
KTM_FLAG_CLUSTERED
=
0x100
,
KTM_FLAG_UNK4000
=
0x4000
};
enum KTM_FLAGS {
KTM_FLAG_VOLATILE
=
0x01
,
KTM_FLAG_COMMIT_SYSTEM_VOLUME
=
0x02
,
KTM_FLAG_COMMIT_SYSTEM_HIVES
=
0x04
,
KTM_FLAG_COMMIT_LOWEST
=
0x08
,
KTM_FLAG_THAW
=
0x10
,
KTM_FLAG_NO_IDENTITY
=
0x20
,
KTM_FLAG_CORRUPT_FOR_PROGRESS
=
0x40
,
KTM_FLAG_CORRUPT_FOR_RECOVERY
=
0x80
,
KTM_FLAG_CLUSTERED
=
0x100
,
KTM_FLAG_UNK4000
=
0x4000
};
HANDLE hTM
=
CreateTransactionManager(
NULL,
/
/
lpTransactionAttributes
NULL,
/
/
LogFileName
TRANSACTION_MANAGER_VOLATILE,
/
/
CreateOptions
0
/
/
CommitStrength
);
HANDLE hTM
=
CreateTransactionManager(
NULL,
/
/
lpTransactionAttributes
NULL,
/
/
LogFileName
TRANSACTION_MANAGER_VOLATILE,
/
/
CreateOptions
0
/
/
CommitStrength
);
HANDLE hRM
=
CreateResourceManager(
NULL,
/
/
lpTransactionAttributes
pRMGuid,
/
/
ResourceManagerId
-
GUID can't be NULL
RESOURCE_MANAGER_VOLATILE,
/
/
CreateOptions
-
No log
file
hTM,
/
/
TmHandle
-
Previously created transaction manager handle
NULL,
/
/
Description
);
HANDLE hRM
=
CreateResourceManager(
NULL,
/
/
lpTransactionAttributes
pRMGuid,
/
/
ResourceManagerId
-
GUID can't be NULL
RESOURCE_MANAGER_VOLATILE,
/
/
CreateOptions
-
No log
file
hTM,
/
/
TmHandle
-
Previously created transaction manager handle
NULL,
/
/
Description
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2021-5-6 23:51
被fyb波编辑
,原因: