1 of 113

Hunting for overlooked cookies in Windows 11 KTM

and baking exploits for them

Jael Koh & Cedric Halbronn

OffensiveCon 2025

2 of 113

Why Should You Care?

3 of 113

My Promise

4 of 113

Why Should You Care?

5 of 113

My Promise

6 of 113

Kernel Transaction Manager (KTM)?

7 of 113

By Developers

Source: NT

8 of 113

Kernel Transaction Manager (KTM)?

9 of 113

10 of 113

11 of 113

By Attackers

Vista/ KTM

2007

2010

2015

2017

2018

2023

Proton Bot malware

2019

UAF x2

2024

Q4

2024

Q2

12 of 113

Be Curious

13 of 113

Be Curious

14 of 113

Be Curious

15 of 113

16 of 113

17 of 113

18 of 113

19 of 113

Do Not Find Bugs, Bugs Find You

20 of 113

21 of 113

Just Play Around

22 of 113

Ten Ideas

23 of 113

Ten Failed Ideas

1. Could there be a Double Free if I raced NtPropagationFailedExt with TmpDispatchPropagateRequest?

2. Could there be a Use after Free if I raced NtPropagationFailedExt in 2 threads?

3. Could I overflow the Propagation Request Refcount? (partially)

4. What happens when I mix COM+ Transactions with KTM APIs?

5. What happens when I race NtPropagationCompleteExt with TmpDispatchPropagateRequest?

6. What happens when I race TmpPromotionRequestTail with itself?

7. Is there uninitialized data in TmpProbeAndCaptureBuffer?

8. Could I get a Use After Free when racing NtPropagationFailedExt in 2 threads?

9. Could I get a Use After Free when racing NtPropagationFailedExt with NtPropagationCompleteExt, NtPropagationCompleteExt and NtPropagationCompleteExt?

10.Could the Transaction->PromotePropagation become a stale pointer in certain situations?

24 of 113

25 of 113

26 of 113

27 of 113

Failure Is Inevitable

28 of 113

But That’s Okay

29 of 113

Exercise Regularly

30 of 113

31 of 113

Protocol Creation

Resource Manager

Protocol

NtRegisterProtocolAddressInformationExt()

Cookie = 0xb00b0005

32 of 113

Protocol Creation

Resource Manager

ProtocolListHead

Cookie = 0xb00b0005

Protocol

33 of 113

Protocol

Protocol

ResourceManager

ProtocolGUID

34 of 113

Promoting Protocol

Protocol

ResourceManager

ProtocolGUID = { 0xac06cc84 , 0x1465, 0x428b, � { 0xa3, 0x98, 0x0a, 0xae, 0xef, 0xb4, 0x59, 0x9b} };

35 of 113

PropagateRequest Creation

Transaction

Propagate Request

TmpAllocatePropagateRequest()

Cookie = 0xb00b0006

36 of 113

PropagateRequest

Propagate

Request

ResourceManager = NULL

37 of 113

Dispatch PropagateRequest

Propagate

Request

TmpDispatchPropagateRequest()

38 of 113

Dispatch PropagateRequest

void TmpDispatchPropagateRequest(_KPROPAGATEREQUEST *PropagateRequest)

{

// 1. Find Promoting Protocol

KeWaitForSingleObject(&TmpAllProtocolsListMutex, Executive, 0, 0, 0LL);

KPROTOCOL* PromotingProtocol = find_promoting_protocol();

KeReleaseMutex(&TmpAllProtocolsListMutex, 0);

...

39 of 113

Dispatch PropagateRequest

void TmpDispatchPropagateRequest(_KPROPAGATEREQUEST *PropagateRequest)

{

// 1. Find Promoting Protocol

KeWaitForSingleObject(&TmpAllProtocolsListMutex, Executive, 0, 0, 0LL);

KPROTOCOL* PromotingProtocol = find_promoting_protocol();

KeReleaseMutex(&TmpAllProtocolsListMutex, 0);

...

// 2. Set Propagate Request ResourceManager to Promoting Protocol's ResourceManager

PropagateRequest->ResourceManager = PromotingProtocol->ResourceManager;

ObfReferenceObject(PromotingProtocol->ResourceManager);

...

40 of 113

Dispatch PropagateRequest - Vulnerability

void TmpDispatchPropagateRequest(_KPROPAGATEREQUEST *PropagateRequest)

{

// 1. Find Promoting Protocol

KeWaitForSingleObject(&TmpAllProtocolsListMutex, Executive, 0, 0, 0LL);

KPROTOCOL* PromotingProtocol = find_promoting_protocol();

KeReleaseMutex(&TmpAllProtocolsListMutex, 0);� /*

*

* ... We can free the Promoting Protocol in this window!

*

*/

// 2. Set Propagate Request ResourceManager to Promoting Protocol's ResourceManager

PropagateRequest->ResourceManager = PromotingProtocol->ResourceManager;

ObfReferenceObject(PromotingProtocol->ResourceManager);

...

41 of 113

Protocol Deletion

void TmpCloseResourceManager(_KRESOURCEMANAGER *ResourceManager)

{

...

// Lock global mutex and free Protocol

KeWaitForSingleObject(&TmpAllProtocolsListMutex, Executive, 0, 0, 0LL);

TmpDeleteProtocol(get_entry_from_list(ResourceManager->ProtocolListHead));

KeReleaseMutex(&TmpAllProtocolsListMutex, 0);

...

CloseProcedure of ResourceManager object

Called when calling CloseHandle(hRM)

Reclaim freed Protocol with Named Pipe ⇒ poison ResourceManager field

42 of 113

Dispatch PropagateRequest - Vulnerability

void TmpDispatchPropagateRequest(_KPROPAGATEREQUEST *PropagateRequest)

{

// 1. Find Promoting Protocol

KeWaitForSingleObject(&TmpAllProtocolsListMutex, Executive, 0, 0, 0LL);

KPROTOCOL* PromotingProtocol = find_promoting_protocol();

KeReleaseMutex(&TmpAllProtocolsListMutex, 0);

// ... Free the Promoting Protocol in this window and reclaim with Named Pipe data

// 2. We control PromotingProtocol->ResourceManager

PropagateRequest->ResourceManager = PromotingProtocol->ResourceManager;

ObfReferenceObject(PromotingProtocol->ResourceManager);

...

// 3. Use Poisoned ResourceManager

KeWaitForSingleObject(PromotingProtocol->ResourceManager);

add_to_linked_list(&PromotingProtocol->ResourceManager->PendingPropReqListHead, PropagateRequest);

KeReleaseMutex(PromotingProtocol->ResourceManager);

...

TmpSetNotificationResourceManager(PropagateRequest->ResourceManager)

...

43 of 113

Constraints

  1. Exploit needs to be ran on a Clustered Disk.

44 of 113

Patch 1: Emulate Clustered Disk

void TmpIsClusteredTransactionManager(_KTM *pKTM, char *pIsDiskClustered)

{

...

// 1. Send IOCTL_DISK_IS_CLUSTERED to device driver

Irp = IoBuildDeviceIoControlRequest(IOCTL_DISK_IS_CLUSTERED,...,&bIsDiskClustered,...);

Status = IofCallDriver(AttachedDeviceReference, Irp);

...

// 2. Save result for caller

*pIsDiskClustered = bIsDiskClustered;

}

void TmpIsClusteredTransactionManager(_KTM *pKTM, char *pIsDiskClustered)

{

...

// 1. Send IOCTL_DISK_IS_CLUSTERED to device driver

Irp = IoBuildDeviceIoControlRequest(IOCTL_DISK_IS_CLUSTERED,...,&bIsDiskClustered,...);

Status = IofCallDriver(AttachedDeviceReference, Irp);

...

// 2. Save result for caller

*pIsDiskClustered = 1; // PATCH: We are a clustered disk!

}

45 of 113

Constraints

  1. Only the ‘KtmRm’ Service can register a Promoting Protocol

KtmRm for Distributed Transaction Coordinator SID:��S-1-5-80-2818357584-3387065753- 4000393942-342927828-138088443

46 of 113

Patch 2: Emulate KtmRm Security Descriptor

int NtRegisterProtocolAddressInformationExt(..., GUID* ProtocolId, ...)

{

...

// 1. If trying to register promoting protocol

if (RtlCompareMemory(ProtocolId, &TmpPromotingProtocolId, 0x10) == 0x10)

{

// 2. Check user has KtmRm Security Descriptor

SeCaptureSubjectContext(&SubjectContext);

bAllowed = SeAccessCheck(TmpKtmRmDescriptor,&SubjectContext,...,);

SeReleaseSubjectContext(&SubjectContext);

}

...

// 3. Exit if not the right permissions

if (!bAllowed)

return 0;

TmRegisterProtocolAddressInformation(ResourceManager, ProtocolId, ...);

...

int NtRegisterProtocolAddressInformationExt(..., GUID* ProtocolId, ...)

{

...

// 1. If trying to register promoting protocol

if (RtlCompareMemory(ProtocolId, &TmpPromotingProtocolId, 0x10) == 0x10)

{

// 2. Check user has KtmRm Security Descriptor

SeCaptureSubjectContext(&SubjectContext);

bAllowed = 1; // PATCH: We are KtmRm user

SeReleaseSubjectContext(&SubjectContext);

}

...

// 3. Exit if not the right permissions

if (!bAllowed)

return 0;

TmRegisterProtocolAddressInformation(ResourceManager, ProtocolId, ...);

...

47 of 113

48 of 113

Winning the Race?

void TmpDispatchPropagateRequest(_KPROPAGATIONREQUEST *PropagateRequest)

{

KeWaitForSingleObject(&TmpAllProtocolsListMutex, Executive, 0, 0, 0LL);

KPROTOCOL* Protocol = find_promoting_protocol();

KeReleaseMutex(&TmpAllProtocolsListMutex, 0);

PropagateRequest->ResourceManager = Protocol->ResourceManager;

ObfReferenceObject(Protocol->ResourceManager);

KeWaitForSingleObject(&Protocol->ResourceManager->Mutex);

add_to_linked_list(&Protocol->ResourceManager->PendingPropReqListHead, PropagateRequest);

KeReleaseMutex(&Protocol->ResourceManager->Mutex);

TmpSetNotificationResourceManager(PropagateRequest->ResourceManager)

Protocol controlled HERE

Protocol controlled HERE

Protocol controlled HERE

Protocol controlled HERE

Medium IL

49 of 113

Winning the Race?

void TmpDispatchPropagateRequest(_KPROPAGATIONREQUEST *PropagateRequest)

{

KeWaitForSingleObject(&TmpAllProtocolsListMutex, Executive, 0, 0, 0LL);

KPROTOCOL* Protocol = find_promoting_protocol();

KeReleaseMutex(&TmpAllProtocolsListMutex, 0);

PropagateRequest->ResourceManager = Protocol->ResourceManager;

ObfReferenceObject(Protocol->ResourceManager);

KeWaitForSingleObject(&Protocol->ResourceManager->Mutex);

add_to_linked_list(&Protocol->ResourceManager->PendingPropReqListHead, PropagateRequest);

KeReleaseMutex(&Protocol->ResourceManager->Mutex);

TmpSetNotificationResourceManager(PropagateRequest->ResourceManager)

Protocol controlled HERE

PropagateRequest and Protocol have same ResourceManager

Need valid ResourceManager→Tm

No Protocol abuse possible :(

BSOD

50 of 113

Winning the Race?

void TmpDispatchPropagateRequest(_KPROPAGATIONREQUEST *PropagateRequest)

{

KeWaitForSingleObject(&TmpAllProtocolsListMutex, Executive, 0, 0, 0LL);

KPROTOCOL* Protocol = find_promoting_protocol();

KeReleaseMutex(&TmpAllProtocolsListMutex, 0);

PropagateRequest->ResourceManager = Protocol->ResourceManager;

ObfReferenceObject(Protocol->ResourceManager);

KeWaitForSingleObject(&Protocol->ResourceManager->Mutex);

add_to_linked_list(&Protocol->ResourceManager->PendingPropReqListHead, PropagateRequest);

KeReleaseMutex(&Protocol->ResourceManager->Mutex);

TmpSetNotificationResourceManager(PropagateRequest->ResourceManager)

Need valid Mutant→CurrentThread.

Medium IL: NtQuerySystemInformation

No BSOD

Arbitrary increment or 0 overwrite.

Same as CVE-2018-8611

Protocol controlled HERE

Block indefinitely

Protocol controlled HERE

Pass fake Mutant into KeReleaseMutex

51 of 113

Winning the Race?

void TmpDispatchPropagateRequest(_KPROPAGATIONREQUEST *PropagateRequest)

{

KeWaitForSingleObject(&TmpAllProtocolsListMutex, Executive, 0, 0, 0LL);

KPROTOCOL* Protocol = find_promoting_protocol();

KeReleaseMutex(&TmpAllProtocolsListMutex, 0);

PropagateRequest->ResourceManager = Protocol->ResourceManager;

ObfReferenceObject(Protocol->ResourceManager);

KeWaitForSingleObject(&Protocol->ResourceManager->Mutex);

add_to_linked_list(&Protocol->ResourceManager->PendingPropReqListHead, PropagateRequest);

KeReleaseMutex(&Protocol->ResourceManager->Mutex);

TmpSetNotificationResourceManager(PropagateRequest->ResourceManager)

No BSOD

PropagateRequest & Protocol have different ResourceManager

Protocol controlled HERE

52 of 113

Winning the Race, in Practice.

void TmpDispatchPropagateRequest(_KPROPAGATIONREQUEST *PropagateRequest)

{

KeWaitForSingleObject(&TmpAllProtocolsListMutex, Executive, 0, 0, 0LL);

KPROTOCOL* Protocol = find_promoting_protocol();

KeReleaseMutex(&TmpAllProtocolsListMutex, 0);

PropagateRequest->ResourceManager = Protocol->ResourceManager;

ObfReferenceObject(Protocol->ResourceManager);

KeWaitForSingleObject(&Protocol->ResourceManager->Mutex);

add_to_linked_list(&Protocol->ResourceManager->PendingPropReqListHead, PropagateRequest);

KeReleaseMutex(&Protocol->ResourceManager->Mutex);

TmpSetNotificationResourceManager(PropagateRequest->ResourceManager)

BSOD avoided

ETHREAD previously leaked. �Write primitive.�Block indefinitely.

PropagateRequest and Protocol have same ResourceManager

Protocol controlled HERE

53 of 113

Vulnerability Exploitation (Medium IL)

Propagate

Request

Resource Manager

Exploit Thread

Freeing Thread

Protocol

1. Creation

2. Leak kernel @

Exploit Thread

3bis. Close

3. Trigger Promotion

user

kernel

Delete

ntoskrnl.exe

54 of 113

Vulnerability Exploitation (Medium IL)

Fake kernel objects

Exploit Thread

user

kernel

@ExploitThread

@ntoskrnl.exe

5bis. Arbitrary increment primitive

Spraying Thread

4. Spray NamedPipe

5. Arbitrary 0 overwrite primitive

NamedPipeFake Protocol

Propagate

Request

KeReleaseMutex(&Protocol->ResourceManager->Mutex)

3. Trigger Promotion

55 of 113

Vulnerability Exploitation (Medium IL)

7. Block indefinitely

Exploit Thread

user

kernel

ntoskrnl.exe

6. PreviousMode = 0

6bis. SeDebugPrivilege += 3

Exploit Thread

@ExploitThread

@ntoskrnl.exe

5bis. Arbitrary increment primitive

5. Arbitrary 0 overwrite primitive

Propagate

Request

3. Trigger Promotion

KeReleaseMutex(&Protocol->ResourceManager->Mutex)

Other Thread

@OtherThread

@ntoskrnl.exe

56 of 113

Set up 4 Windows Server VMs�Find a 0day in KtmRm Service

Use 2 Windbg Commands

57 of 113

Demo

58 of 113

59 of 113

Propagate Request Low IL exploit?

60 of 113

Propagate Request Low IL exploit?

void TmpDispatchPropagateRequest(_KPROPAGATIONREQUEST *PropagateRequest)

{

KeWaitForSingleObject(&TmpAllProtocolsListMutex, Executive, 0, 0, 0LL);

KPROTOCOL* Protocol = find_promoting_protocol();

KeReleaseMutex(&TmpAllProtocolsListMutex, 0);

PropagateRequest->ResourceManager = Protocol->ResourceManager;

ObfReferenceObject(Protocol->ResourceManager);

KeWaitForSingleObject(&Protocol->ResourceManager->Mutex);

add_to_linked_list(&Protocol->ResourceManager->PendingPropReqListHead, PropagateRequest);

KeReleaseMutex(&Protocol->ResourceManager->Mutex);

TmpSetNotificationResourceManager(PropagateRequest->ResourceManager)

And still need another 0day in KtmRm service …

Protocol controlled HERE

Protocol controlled HERE

Fake ResourceManager in userland

Leak ETHREAD.

And temporarily block

Primitive ⇒ arbitrary 0 overwrite.

Can’t just block here

Need valid ResourceManager→Tm

61 of 113

TmpDispatchPropagateRequest Fix

void TmpDispatchPropagateRequest(_KPROPAGATIONREQUEST *PropagateRequest)

{� KeWaitForSingleObject(&TmpAllProtocolsListMutex, Executive, 0, 0, 0LL);

KPROTOCOL* Protocol = find_promoting_protocol();...

if ( TmEnableMSRC88939Fix ) {

// only fetch Protocol->ResourceManager once

ResourceManager = Protocol->ResourceManager;

ObfReferenceObject(ResourceManager);

// then release Protocol mutex

KeReleaseMutex(&TmpAllProtocolsListMutex, 0);

}

...

PropagateRequest->ResourceManager = ResourceManager;

KeWaitForSingleObject(&ResourceManager->Mutex);

62 of 113

63 of 113

Just Got Lucky?

  • No one thought to look at these objects
  • Not a real vulnerability
  • Smarter people have probably audited the rest of tm.sys already

64 of 113

Don’t Let Your Biases Hinder You

No one thought to look at these objects

Good! What else is being overlooked?

65 of 113

Don’t Let Your Biases Hinder You

Not a real vulnerability

Real benefits:

  • USD$2000 from MSRC Bounty
  • Job at PixiePoint Security
  • OffensiveCon Speaker

66 of 113

Don’t Let Your Biases Hinder You

Smarter people have probably audited the rest of tm.sys already

No penalties for trying

Different researchers find different vulnerabilities�

67 of 113

Most Code is Rarely Audited in Real Depth

68 of 113

e

69 of 113

70 of 113

71 of 113

Jael’s TmpMigrateEnlistments

  1. Lock Enlistment Mutex
  2. Lock Source Transaction Mutex
  3. Lock Destination Transaction Mutex

72 of 113

Jael’s TmpMigrateEnlistments

  • Lock Enlistment Mutex
  • Lock Source Transaction Mutex
  • Lock Destination Transaction Mutex
  • “Migrate” Enlistment

73 of 113

Jael’s TmpMigrateEnlistments

  • Lock Enlistment Mutex
  • Lock Source Transaction Mutex
  • Lock Destination Transaction Mutex
  • “Migrate” Enlistment
  • Release Destination Transaction Mutex
  • Release Source Transaction Mutex
  • Release Enlistment Mutex

74 of 113

TmpMigrateEnlistments

  • Lock Enlistment Mutex
  • Lock Source Transaction Mutex
  • Lock Destination Transaction Mutex
  • “Migrate” Enlistment
  • Release Destination Transaction Mutex
  • Release Source Transaction Mutex
  • Release Enlistment Mutex

75 of 113

License To UAF

76 of 113

Migrate Enlistment

  • Vulnerability

void TmpMigrateEnlistments(_KTRANSACTION *SourceTx, _KTRANSACTION *DestinationTx)

{

// 1. For each Enlistment in Source Transaction’s Enlistment list

currEnlistment= SourceTx->EnlistmentHead.Flink;

while ( currEnlistment!= &SourceTx->EnlistmentHead )

{

77 of 113

Migrate Enlistment

  • Vulnerability

void TmpMigrateEnlistments(_KTRANSACTION *SourceTx, _KTRANSACTION *DestinationTx)

{

// 1. For each Enlistment in Source Transaction’s Enlistment list

currEnlistment= SourceTx->EnlistmentHead.Flink;

while ( currEnlistment!= &SourceTx->EnlistmentHead )

{

// 2. Update Enlistment to point to Destination Transaction

currEnlistment->Transaction = DestinationTx;

78 of 113

Migrate Enlistment

  • Vulnerability

void TmpMigrateEnlistments(_KTRANSACTION *SourceTx, _KTRANSACTION *DestinationTx)

{

// 1. For each Enlistment in Source Transaction’s Enlistment list

currEnlistment= SourceTx->EnlistmentHead.Flink;

while ( currEnlistment!= &SourceTx->EnlistmentHead )

{

// 2. Update Enlistment to point to Destination Transaction

currEnlistment->Transaction = DestinationTx;

// 3. Remove Enlistment from Source Transaction’s Enlistment List

list_safely_remove(&currEnlistment->NextSameTx);�

79 of 113

Migrate Enlistment

  • Vulnerability

void TmpMigrateEnlistments(_KTRANSACTION *SourceTx, _KTRANSACTION *DestinationTx)

{

// 1. For each Enlistment in Source Transaction’s Enlistment list

currEnlistment= SourceTx->EnlistmentHead.Flink;

while ( currEnlistment!= &SourceTx->EnlistmentHead )

{

// 2. Update Enlistment to point to Destination Transaction

currEnlistment->Transaction = DestinationTx;

// 3. Remove Enlistment from Source Transaction’s Enlistment List

list_safely_remove(&currEnlistment->NextSameTx);�

// 4. Add Enlistment to Destination Transaction’s Enlistment List

list_safely_add(&DestinationTx->EnlistmentHead, currEnlistment);

currEnlistment = currEnlistment->Flink;

80 of 113

Migrate Enlistment - Vulnerability

  • Vulnerability

void TmpMigrateEnlistments(_KTRANSACTION *SourceTx, _KTRANSACTION *DestinationTx)

{

// 1. For each Enlistment in Source Transaction’s Enlistment list

currEnlistment= SourceTx->EnlistmentHead.Flink;

while ( currEnlistment!= &SourceTx->EnlistmentHead )

{

// 2. Update Enlistment to point to Destination Transaction

currEnlistment->Transaction = DestinationTx;�

/* We can free currEnlistment in this window so a fake Enlistment gets

* added to Destination Transaction’s Enlistment list!

*/

// 3. Remove Enlistment from Source Transaction’s Enlistment List

list_safely_remove(&currEnlistment->NextSameTx);�

// 4. Add Enlistment to Destination Transaction’s Enlistment List

list_safely_add(&DestinationTx->EnlistmentHead, currEnlistment);

currEnlistment = currEnlistment->Flink;

81 of 113

How to Free an Enlistment?

CommitComplete()

-> NtCommitComplete()

-> TmCommitComplete()

-> TmpProcessNotificationResponse()

-> TmpFinalizeEnlistment()

Traditionally done by committing a Prepared Enlistment

And releasing the Enlistment’s user handle with CloseHandle()

82 of 113

Why is there a Vulnerability?

void TmpMigrateEnlistments(_KTRANSACTION *SourceTx, _KTRANSACTION *DestinationTx)

{

currEnlistment= SourceTx->EnlistmentHead.Flink;

while ( currEnlistment!= &SourceTx->EnlistmentHead ) // [1]

{� // Destination Transaction’s mutex is NOT held

currEnlistment->Transaction = DestinationTx; // [2]� ...

void TmpProcessNotificationResponse(_KENLISTMENT *Enlistment, ...)

{

// Acquire Transaction’s Mutex before finalizing the Enlistment

Transaction = Enlistment->Transaction;

KeWaitForSingleObject(&Transaction->Mutex, Executive, 0, 0, 0LL);

...

We can call CommitComplete() in another thread during TmpMigrateEnlistments!

83 of 113

But….

Only Committed Transactions will free Enlistments.

We have to call CommitTransaction/CommitTransactionAsync on Destination Transaction before CommitComplete() works.

But… we don’t have the handle to Destination Transaction.

Traditional way of freeing Enlistment will NOT work in this case.

In our case, CommitComplete() does not free Enlistment.

84 of 113

Hmm…

Is this even a vulnerability?

85 of 113

Hmm…

86 of 113

87 of 113

88 of 113

89 of 113

90 of 113

Constraints

None…but?

91 of 113

Debugger Assistance to Win the Race

void TmpMigrateEnlistments(_KTRANSACTION *SourceTx, _KTRANSACTION *DestinationTx)

{

currEnlistment= SourceTx->EnlistmentHead.Flink;

while ( currEnlistment!= &SourceTx->EnlistmentHead ) // [1]

{

currEnlistment->Transaction = DestinationTx; // [2]

/*� * PATCH: Insert an infinite jump here to force a race win

*/� while (1) {}

list_safely_remove(&currEnlistment->NextSameTx); // [3]

list_safely_add(&DestinationTx->EnlistmentHead, currEnlistment); // [4]

currEnlistment = currEnlistment->Flink;

92 of 113

Demo with Debugger Assistance

93 of 113

94 of 113

Exploitation for TmpMigrateEnlistments?

95 of 113

KTM == Sandbox Escape?

Sandbox

KTM Accessible? (Before Feb 2021)

KTM Accessible? (Present)

Chrome

Firefox

Edge

96 of 113

97 of 113

Windows Component Filter Mitigation

if (component_filter_.ComponentFlags) {

if (!startup_info_.UpdateProcThreadAttribute(

PROC_THREAD_ATTRIBUTE_COMPONENT_FILTER, &component_filter_,

sizeof(component_filter_)) &&

::GetLastError() != ERROR_NOT_SUPPORTED) {

return false;

}

98 of 113

Blocked APIs

99 of 113

Blocked Allowed APIs

100 of 113

Is KTM Safe Now?

Ideas for the reader…

  • KtmRm service
  • TxF / TxR
  • MSDTC service
  • OleTx?

101 of 113

TxR

102 of 113

TxF

No public vulnerabilities?

Source: BHEU17

103 of 113

MSDTC.exe Service

104 of 113

OleTx

MSDTC Connection Manager: �OleTx Transaction Protocol

500-page specification from Microsoft ([MS-DTCO])

105 of 113

106 of 113

PreviousMode is Dead

Windows 11 24H2

  • PreviousMode enforced for every syscall

Windows 11 23H2 (and below)

  • PreviousMode still abusable (April 2025)

107 of 113

108 of 113

You CAN find vulnerabilities.

109 of 113

Questions?

Ask us a question and get an EXCLUSIVE autographed MTG card after the talk!

110 of 113

References

111 of 113

References (Jael)

112 of 113

References (Jael)

113 of 113

Addendum 1:

For the TmpDispatchPropagateRequest vulnerability: After TmpIsClusteredTransactionManager(), the “KtmRm for Distributed Transaction Manager” service will be started. This service will attempt to register a promoting protocol. �If the service successfully registers a promoting protocol, our exploit process will no longer be able to register a promoting protocol.�Since the exploit involves use-after-freeing this promoting protocol, an attacker would likely need to win the race on the first try BEFORE the service registers a promoting protocol or gain code execution within the service to close this promoting protocol handle and trigger the exploit.�In my demonstration, I disabled this service so I could retry the exploit repeatedly without fail.