The opinions expressed in this blog are my own and do not represent those of my current employer

Sometimes in kernel developement is needed to process some user mode data. But some of data – structs are internal and not so well documented, and due to this are available functions which work with these structures, but these are often exported just for user mode only. What are options in that case ?

user mode component – service / application

find kernel mode alternative function – often not exported

reverse structure – parse it by yourself

nt!KeUserModeCallback

In this blog post I will describe last mentioned method, you do not need additional resources or reversing undocomented structures. Some articles related to nt!KeUserModeCallback ring0 – ring3 – ring0 gate :

So data are copied onto stack, nice! And when you take closer look at user32!_fnDWORD function, you can see that 5 parameters which are passed to well looked call are stored in inputBuffer. And in addition address of this call is stored in inputBuffer as well!

Seems all count for us, but there are one more thing left. How to provide cpl3 code address for calling ?

ROP – all you need you already have!

MDL & PTE – get less privilages to your code

ROP technique :

I personaly like this technique because it is fun to play with it, but for developers it is most probably not so cool sollution, because it need to carry on with various OS version for compatibility, and same time optionally have and ROP gadgets tool (OptiRop) / compiler (ROPC) to spare your time at finding appropriate ROP sled.

I was lucky enough and I rellatively easly find suffictient ROP sled for rulling over control flow, after user32!_fnDWORD magic call was invoked. Indeed it was due to fact of low complexity of needed code

PoC for ROP method :

ROP method

C++

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

50

51

52

53

54

55

56

57

58

59

60

61

62

63

//not invasive, but OS version dependent!

template<classTYPE,classPARAM>

classCRop:

publicCCallback2User<TYPE>

{

public:

CRop(

__in void*ntdllZwCallbackReturn,

__in void*targetFunction,

__in ULONG_PTR*userStack

):CCallback2User(ntdllZwCallbackReturn,targetFunction,userStack)

{

}

voidGenerateRopGadgets(

__in PARAM param

)

{

m_input[ROP_RCX]=*reinterpret_cast<ULONG_PTR*>(&param);//param to targetFunction

Seems ROP comes handy not just in exploit case, and its main pros is that it is non-invasive method, where you use already present ring3 code. This method is transparent, but on the other side, when it comes to developement, it is needed to keep eye on different versions of OS, where binaries are changed to implement correct ROP gadgets. This can be pain in the ass – maybe when you have available ROP tool at runtime it can solve this problem more genericaly.

MDL & PTE :

Another option how to obtain our goal, is developing more friendly method – share kernel mode code with usermode. This can be done by documented methods – memory descriptor list (MDL).

C++

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

PMDL IoAllocateMdl(

_In_opt_ PVOID VirtualAddress,

_In_ ULONG Length,

_In_ BOOLEANSecondaryBuffer,

_In_ BOOLEANChargeQuota,

_Inout_opt_ PIRP Irp

);

VOIDMmProbeAndLockPages(

_Inout_ PMDLX MemoryDescriptorList,

_In_ KPROCESSOR_MODE AccessMode,

_In_ LOCK_OPERATION Operation

);

PVOID MmMapLockedPagesSpecifyCache(

_In_ PMDLX MemoryDescriptorList,

_In_ KPROCESSOR_MODE AccessMode,

_In_ MEMORY_CACHING_TYPE CacheType,

_In_opt_ PVOID BaseAddress,

_In_ ULONG BugCheckOnFailure,

_In_ MM_PAGE_PRIORITY Priority

);

MmProbeAndLockPages performs the following operations:

If the specified memory range is paged to a backing store (disk, network, and so on),MmProbeAndLockPages makes it resident.

The routine then confirms that the pages permit the operation specified by the Operation parameter.

If the memory range permits the specified operation, the routine locks the pages in memory so that they cannot be paged out. Use the MmUnlockPages routine to unlock the pages.

Finally, the routine updates the page frame number (PFN) array in the MDL to describe the locked physical pages.

Cool, almost done! But one problem is present here, and that you can share this code with cpl3 – but with no exec privilages!

But when we look at 4th point of processing MmProbeAndLockPages routine, we can get an inspiration, and alter PTE and its NoExec flag itself!

* also other apis include call to address stored at inputBuffer, but this api have almost no processing, is really straightforward and do not move RSP to far from original so inputBuffer is on the shot for ROP technique!

2 Comments.

Nice concept with ROP in callbacks
But about sharing user buffer and modifying PTE’s NX bit – isn’t it easier to just use documented ZwAllocateVirtualMemory() to allocate executable pages in user process?