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 :
- You Failed ! by @Ivanlef0u
- Kernel Attacks through User-Mode Callbacks by @kernelpool
- Analyzing local privilege escalations in win32k by @j00ru
- Kernel exploitation – r0 to r3 transitions via KeUserModeCallback by SkyWing
- USER-MODE CALLBACKS IN WINDOWS
Seems it is no so well documented function, and it is used primary by win32k to call user32 methods. But still it is exported and available to use!
Nice feature to call to user mode, but seems it have some preprocessing :
1 2 3 4 5 6 7 8 |
NTSTATUS KeUserModeCallback ( __in ULONG apiNumber, __in void* inputBuffer, __in ULONG inputLength, __out void** outputBuffer, __out ULONG* outputLength ) |
nt!KeUserModeCallback expect apiNumber! Next interesting information for us will be what kind of api calls can be invoked via this callback.
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 |
kd> dt _PEB ntdll!_PEB +0x000 InheritedAddressSpace : UChar +0x001 ReadImageFileExecOptions : UChar +0x002 BeingDebugged : UChar +0x003 BitField : UChar +0x003 ImageUsesLargePages : Pos 0, 1 Bit +0x003 IsProtectedProcess : Pos 1, 1 Bit +0x003 IsLegacyProcess : Pos 2, 1 Bit +0x003 IsImageDynamicallyRelocated : Pos 3, 1 Bit +0x003 SkipPatchingUser32Forwarders : Pos 4, 1 Bit +0x003 IsPackagedProcess : Pos 5, 1 Bit +0x003 IsAppContainer : Pos 6, 1 Bit +0x003 SpareBits : Pos 7, 1 Bit +0x008 Mutant : Ptr64 Void +0x010 ImageBaseAddress : Ptr64 Void +0x018 Ldr : Ptr64 _PEB_LDR_DATA +0x020 ProcessParameters : Ptr64 _RTL_USER_PROCESS_PARAMETERS +0x028 SubSystemData : Ptr64 Void +0x030 ProcessHeap : Ptr64 Void +0x038 FastPebLock : Ptr64 _RTL_CRITICAL_SECTION +0x040 AtlThunkSListPtr : Ptr64 Void +0x048 IFEOKey : Ptr64 Void +0x050 CrossProcessFlags : Uint4B +0x050 ProcessInJob : Pos 0, 1 Bit +0x050 ProcessInitializing : Pos 1, 1 Bit +0x050 ProcessUsingVEH : Pos 2, 1 Bit +0x050 ProcessUsingVCH : Pos 3, 1 Bit +0x050 ProcessUsingFTH : Pos 4, 1 Bit +0x050 ReservedBits0 : Pos 5, 27 Bits +0x058 KernelCallbackTable : Ptr64 Void +0x058 UserSharedInfoPtr : Ptr64 Void |
KernelCallbackTable
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 |
kd> dqs 0x000007fa`da7e9780 L60 000007fa`da7e9780 000007fa`da7ddf70 user32!_fnCOPYDATA 000007fa`da7e9788 000007fa`da800c54 user32!_fnCOPYGLOBALDATA 000007fa`da7e9790 000007fa`da7b2920 user32!_fnDWORD 000007fa`da7e9798 000007fa`da7b34d0 user32!_fnNCDESTROY 000007fa`da7e97a0 000007fa`da7c5b30 user32!_fnDWORDOPTINLPMSG 000007fa`da7e97a8 000007fa`da800b78 user32!_fnINOUTDRAG 000007fa`da7e97b0 000007fa`da7be9b0 user32!_fnGETTEXTLENGTHS 000007fa`da7e97b8 000007fa`da7dd220 user32!_fnINCNTOUTSTRING 000007fa`da7e97c0 000007fa`da800870 user32!_fnINCNTOUTSTRINGNULL 000007fa`da7e97c8 000007fa`da800a08 user32!_fnINLPCOMPAREITEMSTRUCT 000007fa`da7e97d0 000007fa`da7ba9e0 user32!_fnINLPCREATESTRUCT 000007fa`da7e97d8 000007fa`da7dcd80 user32!_fnINLPDELETEITEMSTRUCT 000007fa`da7e97e0 000007fa`da7ce610 user32!_fnINLPDRAWITEMSTRUCT 000007fa`da7e97e8 000007fa`da8009a4 user32!_fnPOPTINLPUINT 000007fa`da7e97f0 000007fa`da8009a4 user32!_fnPOPTINLPUINT 000007fa`da7e97f8 000007fa`da7d42d0 user32!_fnINLPMDICREATESTRUCT 000007fa`da7e9800 000007fa`da7dd6c0 user32!_fnINOUTLPMEASUREITEMSTRUCT 000007fa`da7e9808 000007fa`da7babb0 user32!_fnINLPWINDOWPOS 000007fa`da7e9810 000007fa`da7bab10 user32!_fnINOUTLPPOINT5 ... 000007fa`da7e9960 000007fa`da7c9250 user32!_ClientFreeLibrary ... 000007fa`da7e9990 000007fa`da7cae30 user32!_ClientLoadImage 000007fa`da7e9998 000007fa`da7bd2f0 user32!_ClientLoadLibrary ... 000007fa`da7e99d8 000007fa`da7eb700 user32!_ClientAddFontResourceW ... 000007fa`da7e9a50 000007fa`da7eb9d0 user32!_ClientLoadStringW 000007fa`da7e9a58 000007fa`da81e844 user32!_ClientLoadOLE ...T 000007fa`da7e9a78 000007fa`da7dc4f0 user32!_ClientPrinterThunk |
lets take a look at one of them, my favorite user32!_fnDWORD
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
kd> u user32!_fnDWORD L20 user32!_fnDWORD: 000007fa`da7b2920 4883ec58 sub rsp,58h 000007fa`da7b2924 4c8b4918 mov r9,qword ptr [rcx+18h] 000007fa`da7b2928 4c8b4110 mov r8,qword ptr [rcx+10h] 000007fa`da7b292c 8b5108 mov edx,dword ptr [rcx+8] 000007fa`da7b292f 33c0 xor eax,eax 000007fa`da7b2931 4c8bd1 mov r10,rcx 000007fa`da7b2934 89442438 mov dword ptr [rsp+38h],eax 000007fa`da7b2938 4889442440 mov qword ptr [rsp+40h],rax 000007fa`da7b293d 488b4120 mov rax,qword ptr [rcx+20h] 000007fa`da7b2941 488b09 mov rcx,qword ptr [rcx] 000007fa`da7b2944 4889442420 mov qword ptr [rsp+20h],rax 000007fa`da7b2949 41ff5228 call qword ptr [r10+28h] 000007fa`da7b294d 4533c0 xor r8d,r8d 000007fa`da7b2950 488d4c2430 lea rcx,[rsp+30h] 000007fa`da7b2955 418d5018 lea edx,[r8+18h] 000007fa`da7b2959 4889442430 mov qword ptr [rsp+30h],rax 000007fa`da7b295e ff15ccb70a00 call qword ptr [user32!_imp_NtCallbackReturn (000007fa`da85e130)] 000007fa`da7b2964 4883c458 add rsp,58h 000007fa`da7b2968 c3 ret |
You can 3 times guess why is this api interesing to me so much – an interesting call on the board!*
As definition of nt!KeuserModeCallback routine uncover, you provide to choosen api your own buffer and also its length. Interesting is also how is inputBuffer provided to usermode api itself :
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 |
/* for (size_t i = 0; i < _countof(m_input); i++) m_input[i] = (static_cast<ULONG_PTR>(MAKEFOURCC('S', 'T', 'C', 'K')) << 32) | (i + 1); */ kd> bp user32!_fndword kd> g Breakpoint 0 hit user32!_fnDWORD: 0033:000007fc`b2c42920 4883ec58 sub rsp,58h kd> dqs rsp L20 0000001b`21438908 000007fc`b5714b67 ntdll!KiUserCallbackDispatcherContinue 0000001b`21438910 0000001b`00000000 0000001b`21438918 00000000`00000000 0000001b`21438920 00000208`00000003 0000001b`21438928 000007fc`b5719803 ntdll!TpPostTask+0x73 0000001b`21438930 0000001b`21438968 0000001b`21438938 00000002`00000800 0000001b`21438940 000007fc`b571375b ntdll!ZwCreateUserProcess+0xa 0000001b`21438948 0000001b`00000104 0000001b`21438950 00000000`00000000 0000001b`21438958 0000001b`21439168 0000001b`21438960 00000001`00010000 0000001b`21438968 4b435453`00000001 0000001b`21438970 4b435453`00000002 0000001b`21438978 4b435453`00000003 0000001b`21438980 4b435453`00000004 0000001b`21438988 4b435453`00000005 0000001b`21438990 4b435453`00000006 0000001b`21438998 4b435453`00000007 0000001b`214389a0 4b435453`00000008 0000001b`214389a8 4b435453`00000009 0000001b`214389b0 4b435453`0000000a 0000001b`214389b8 4b435453`0000000b 0000001b`214389c0 4b435453`0000000c 0000001b`214389c8 4b435453`0000000d 0000001b`214389d0 4b435453`0000000e 0000001b`214389d8 4b435453`0000000f 0000001b`214389e0 4b435453`00000010 0000001b`214389e8 4b435453`00000011 0000001b`214389f0 4b435453`00000012 0000001b`214389f8 4b435453`00000013 0000001b`21438a00 4b435453`00000014 |
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 :
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<class TYPE, class PARAM> class CRop : public CCallback2User<TYPE> { public: CRop( __in void* ntdllZwCallbackReturn, __in void* targetFunction, __in ULONG_PTR* userStack ) : CCallback2User(ntdllZwCallbackReturn, targetFunction, userStack) { } void GenerateRopGadgets( __in PARAM param ) { m_input[ROP_RCX] = *reinterpret_cast<ULONG_PTR*>(¶m); //param to targetFunction m_input[ROP_R13] = (ROP_OUTPUT_FROM_FUNCTION + 1) * sizeof(void*); //output size to NtCallbackReturn m_input[ROP_R14] = 0; //ERROR_SUCCESS to NtCallbackReturn //needs hardcoded offsets, user interaction or ROP builder DbgPrint("\n\n\ eq %p user32!gSharedInfo; \ eq %p user32!GetAsyncKeyState+0x99; eq %p ntdll!LdrpGetProcedureAddress+0x171; eq %p KERNELBASE!_GSHandlerCheckCommon+0x8c; \ eq %p ntdll!LdrpVerifyAlternateResourceModule+0xd5; eq %p ntdll!RtlpWalkLowFragHeapSegment+0x5c; \n\n", m_userStack, //exported &m_input[ROP_SETRCX], //pattern find &m_input[ROP_setVars1], //pattern find &m_input[ROP_setVars2], //pattern find &m_input[ROP_SET_STACK1], //pattern find &m_input[ROP_SET_STACK2] //pattern find ); m_input[ROP_R9] = reinterpret_cast<ULONG_PTR>(m_userStack - 6);//or qword [R9 + 0x30], BYTE(qword [R9 + 0x30]) KeBreak(); m_input[ROP_ntCallbackReturn] = reinterpret_cast<ULONG_PTR>(m_ntdllZwCallbackReturn); m_input[ROP_targetFunctionAddr] = reinterpret_cast<ULONG_PTR>(m_targetFunction); m_input[ROP_RBP] = m_input[ROP_ntCallbackReturn];//rbp needs to be readable!! } _IRQL_requires_max_(PASSIVE_LEVEL) __checkReturn TYPE CallOneParamApi( __inout_bcount(size) void** output, __inout size_t* size ) { if (InvokeCall(output, size)) { if (*output) { const ULONG_PTR* res = reinterpret_cast<const ULONG_PTR*>(*output); if (*size == m_input[ROP_R13]) return *reinterpret_cast<const TYPE*>(&res[ROP_OUTPUT_FROM_FUNCTION]); } } return false; } }; |
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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
#ifndef __ROP_H__ #define __ROP_H__ //NOTE : WIN8CP offsets for ROP!!! #define CALLBACK_PARAM0 0 #define CALLBACK_PARAM1 1 #define CALLBACK_PARAM2 2 #define CALLBACK_PARAM3 3 #define CALLBACK_PARAM4 4 #define CALLBACK_FUNCTION 5 //ntdll!LdrpVerifyAlternateResourceModule+0xd5 #define ROP_SET_STACK1 CALLBACK_PARAM4 //ntdll!RtlpWalkLowFragHeapSegment+0x64 #define ROP_SET_STACK2 CALLBACK_FUNCTION //ntdll!LdrpVerifyAlternateResourceModule+0xd5 #define ROP_SETRCX 1 //user32!GetAsyncKeyState+0x99 #define ROP_user32DrawText_STACKSET 7 // will be used for KernelCallbackTable /*user32!DrawTextW+0x57*/ #define ROP_targetFunctionAddr (ROP_SETRCX + ROP_user32DrawText_STACKSET) #define ROP_setVars1 (ROP_targetFunctionAddr + 1) //ntdll!LdrpGetProcedureAddress+0x171 #define ROP_R9 (0x18 / sizeof(void*)) //user32!gSharedInfo #define ROP_RCX (ROP_SETRCX + 1) #define ROP_R11 (ROP_setVars1 + 1 + (0x40 / sizeof(void*))) #define ROP_R13 (ROP_setVars1 + 1 + (0x78 / sizeof(void*))) #define ROP_R14 (ROP_R11 + 1) #define ROP_RBP (ROP_R11 + (0x48 / sizeof(void*))) #define ROP_setVars2 (ROP_R11 + 5) //KERNELBASE!_GSHandlerCheckCommon+0x8c #define ROP_ntCallbackReturn (ROP_R11 + (0xE8 / sizeof(void*))) //ntdll!ZwCallbackReturn #define ROP_OUTPUT_FROM_FUNCTION 0xD /* ROP GADGETS INFO : //PEB::KernelCallbackTale[2] used for KeUserModeCallback : __fnDWORD proc near var_38= qword ptr -38h var_28= qword ptr -28h var_20= dword ptr -20h var_18= qword ptr -18h sub rsp, 58h mov rax, [rcx+20h] mov r9, [rcx+18h] mov r8, [rcx+10h] mov edx, [rcx+8] and [rsp+58h+var_20], 0 and [rsp+58h+var_18], 0 mov r10, rcx mov rcx, [rcx] mov [rsp+58h+var_38], rax call qword ptr [r10+28h] //->ntdll!RtlpWalkLowFragHeapSegment+0x5c xor r8d, r8d lea edx, [r8+18h] lea rcx, [rsp+58h+var_28] mov [rsp+58h+var_28], rax call cs:__imp_NtCallbackReturn add rsp, 58h retn __fnDWORD endp //set RAX (on behalf of R9 - setted in _fnDWORD from input buffer) //R10 setted in _fnDWORD to &input //set RSP after _fnDWORD call [r10 + ROP_STACKSET2] ntdll!RtlpWalkLowFragHeapSegment+0x5c: 000007fd`96f0b5e4 498b4130 mov rax,qword ptr [r9+30h] 000007fd`96f0b5e8 49894228 mov qword ptr [r10+28h],rax 000007fd`96f0b5ec 4883c428 add rsp,28h 000007fd`96f0b5f0 c3 ret //->ntdll!LdrpVerifyAlternateResourceModule+0xd5 set RSP to input buffer! + start ROPING on input buffer!! ntdll!LdrpVerifyAlternateResourceModule+0xd5: 000007fa`62c61ee9 4883c470 add rsp,70h 000007fa`62c61eed 415f pop r15 000007fa`62c61eef 415e pop r14 000007fa`62c61ef1 5f pop rdi 000007fa`62c61ef2 5e pop rsi 000007fa`62c61ef3 5d pop rbp 000007fa`62c61ef4 c3 ret //->user32!GetAsyncKeyState+0x99 set rcx as HWN to window! user32!GetAsyncKeyState+0x99: 000007fd`3d2a3b09 59 pop rcx 000007fd`3d2a3b0a 0800 or byte ptr [rax],al 000007fd`3d2a3b0c 33c0 xor eax,eax 000007fd`3d2a3b0e 488b5c2430 mov rbx,qword ptr [rsp+30h] 000007fd`3d2a3b13 4883c420 add rsp,20h 000007fd`3d2a3b17 5f pop rdi 000007fd`3d2a3b18 c3 ret //->target func->ntdll!LdrpGetProcedureAddress+0x171 set r13 (size - 0x100); set r14 (0), set r11 (relative rsp), move rsp (near to r11) ntdll!LdrpGetProcedureAddress+0x171: 000007fd`3fc053c1 4c8b6c2478 mov r13,qword ptr [rsp+78h] 000007fd`3fc053c6 4c8d5c2440 lea r11,[rsp+40h] 000007fd`3fc053cb 498b5b40 mov rbx,qword ptr [r11+40h] 000007fd`3fc053cf 498b6b48 mov rbp,qword ptr [r11+48h] 000007fd`3fc053d3 498be3 mov rsp,r11 000007fd`3fc053d6 415f pop r15 000007fd`3fc053d8 415e pop r14 000007fd`3fc053da 415c pop r12 000007fd`3fc053dc 5f pop rdi 000007fd`3fc053dd 5e pop rsi 000007fd`3fc053de c3 ret //->KERNELBASE!_GSHandlerCheckCommon+0x8c r8 <- r14; edx <- r13d; rcx <- r11; [r11 + smth] <- res; call [rsp + smth] KERNELBASE!_GSHandlerCheckCommon+0x8c//KERNELBASE!LCMapStringEx - 0x211C: 000007fd`3cb72d00 4889442438 mov qword ptr [rsp+38h],rax 000007fd`3cb72d05 488b442460 mov rax,qword ptr [rsp+60h] 000007fd`3cb72d0a 4d8bc6 mov r8,r14 000007fd`3cb72d0d 4889442430 mov qword ptr [rsp+30h],rax 000007fd`3cb72d12 8b85b8000000 mov eax,dword ptr [rbp+0B8h] 000007fd`3cb72d18 418bd5 mov edx,r13d 000007fd`3cb72d1b 89442428 mov dword ptr [rsp+28h],eax 000007fd`3cb72d1f 498bcb mov rcx,r11 000007fd`3cb72d22 4c89642420 mov qword ptr [rsp+20h],r12 000007fd`3cb72d27 41ff93e8000000 call qword ptr [r11+0E8h] //->ntdll!ZwCallbackReturn->back to driver CPL0 code */ #endif //__ROP_H__ |
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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 |
kd> t KernelGate2User!CCallback2User<bool>::InvokeCall+0x47: fffff880`09255647 488b4c2460 mov rcx,qword ptr [rsp+60h] kd> t nt!KeUserModeCallback: fffff802`af2c5d30 4c894c2420 mov qword ptr [rsp+20h],r9 kd> t kd> bp user32!_fndword kd> g Breakpoint 0 hit user32!_fnDWORD: 0033:000007fa`5cb72920 4883ec58 sub rsp,58h kd> dqs rsp L20 00000032`633985f8 000007fa`5f1a4b67 ntdll!KiUserCallbackDispatcherContinue 00000032`63398600 00000032`00000000 00000032`63398608 00000000`00000000 00000032`63398610 00000208`00000003 00000032`63398618 000007fa`5f1a98df ntdll!TpPostTask+0x143 00000032`63398620 00000032`63398658 00000032`63398628 00000002`00000800 00000032`63398630 000007fa`5f1a375b ntdll!ZwCreateUserProcess+0xa 00000032`63398638 00000032`00000104 00000032`63398640 00000000`00000000 00000032`63398648 00000032`63398e58 00000032`63398650 00000000`00000001 00000032`63398658 00000000`01f0070a 00000032`63398660 000007fa`5cb73b09 user32!GetAsyncKeyState+0x99 00000032`63398668 00000000`12345678 00000032`63398670 00000032`67b4ffd0 00000032`63398678 000007fa`5f1d1ee9 ntdll!LdrpVerifyAlternateResourceModule+0xd5 00000032`63398680 000007fa`5f28b5e4 ntdll!RtlpWalkLowFragHeapSegment+0x5c 00000032`63398688 fffffa80`05de7180 00000032`63398690 fffffa80`08738850 00000032`63398698 000007fa`5c4328e8 KERNELBASE!FreeLibrary 00000032`633986a0 000007fa`5f1b53c1 ntdll!LdrpGetProcedureAddress+0x19a 00000032`633986a8 00000000`00000000 00000032`633986b0 fffff880`099cb610 00000032`633986b8 fffff880`099cb670 00000032`633986c0 fffff880`099cb670 00000032`633986c8 fffff880`099cb680 00000032`633986d0 fffff880`099cb680 00000032`633986d8 00000000`00000000 00000032`633986e0 00000000`00000000 00000032`633986e8 00000000`00000000 00000032`633986f0 00000000`00000000 kd> t user32!_fnEMPTY+0x4: 0033:000007fa`5cb72924 4c8b4918 mov r9,qword ptr [rcx+18h] kd> t user32!_fnEMPTY+0x8: 0033:000007fa`5cb72928 4c8b4110 mov r8,qword ptr [rcx+10h] kd> t user32!_fnEMPTY+0xc: 0033:000007fa`5cb7292c 8b5108 mov edx,dword ptr [rcx+8] kd> t user32!_fnEMPTY+0xf: 0033:000007fa`5cb7292f 33c0 xor eax,eax kd> t user32!_fnEMPTY+0x11: 0033:000007fa`5cb72931 4c8bd1 mov r10,rcx kd> t user32!_fnEMPTY+0x14: 0033:000007fa`5cb72934 89442438 mov dword ptr [rsp+38h],eax kd> t user32!_fnEMPTY+0x18: 0033:000007fa`5cb72938 4889442440 mov qword ptr [rsp+40h],rax kd> t user32!_fnEMPTY+0x1d: 0033:000007fa`5cb7293d 488b4120 mov rax,qword ptr [rcx+20h] kd> t user32!_fnEMPTY+0x21: 0033:000007fa`5cb72941 488b09 mov rcx,qword ptr [rcx] kd> t user32!_fnEMPTY+0x24: 0033:000007fa`5cb72944 4889442420 mov qword ptr [rsp+20h],rax kd> t user32!_fnEMPTY+0x29: 0033:000007fa`5cb72949 41ff5228 call qword ptr [r10+28h] kd> t ntdll!RtlpWalkLowFragHeapSegment+0x5c: 0033:000007fa`5f28b5e4 498b4130 mov rax,qword ptr [r9+30h] kd> t ntdll!RtlpWalkLowFragHeapSegment+0x60: 0033:000007fa`5f28b5e8 49894228 mov qword ptr [r10+28h],rax kd> t ntdll!RtlpWalkLowFragHeapSegment+0x64: 0033:000007fa`5f28b5ec 4883c428 add rsp,28h kd> t ntdll!RtlpWalkLowFragHeapSegment+0x68: 0033:000007fa`5f28b5f0 c3 ret kd> t ntdll!LdrpVerifyAlternateResourceModule+0xd5: 0033:000007fa`5f1d1ee9 4883c470 add rsp,70h kd> t ntdll!LdrpVerifyAlternateResourceModule+0xd9: 0033:000007fa`5f1d1eed 415f pop r15 kd> t ntdll!LdrpVerifyAlternateResourceModule+0xdb: 0033:000007fa`5f1d1eef 415e pop r14 kd> t ntdll!LdrpVerifyAlternateResourceModule+0xdd: 0033:000007fa`5f1d1ef1 5f pop rdi kd> t ntdll!LdrpVerifyAlternateResourceModule+0xde: 0033:000007fa`5f1d1ef2 5e pop rsi kd> t ntdll!LdrpVerifyAlternateResourceModule+0xdf: 0033:000007fa`5f1d1ef3 5d pop rbp kd> t ntdll!LdrpVerifyAlternateResourceModule+0xe0: 0033:000007fa`5f1d1ef4 c3 ret kd> t user32!GetAsyncKeyState+0x99: 0033:000007fa`5cb73b09 59 pop rcx kd> t user32!GetAsyncKeyState+0x9a: 0033:000007fa`5cb73b0a 0800 or byte ptr [rax],al kd> t user32!GetAsyncKeyState+0x9c: 0033:000007fa`5cb73b0c 33c0 xor eax,eax kd> t user32!GetAsyncKeyState+0x9e: 0033:000007fa`5cb73b0e 488b5c2430 mov rbx,qword ptr [rsp+30h] kd> t user32!GetAsyncKeyState+0xa3: 0033:000007fa`5cb73b13 4883c420 add rsp,20h kd> t user32!GetAsyncKeyState+0xa7: 0033:000007fa`5cb73b17 5f pop rdi kd> t user32!GetAsyncKeyState+0xa8: 0033:000007fa`5cb73b18 c3 ret kd> t KERNELBASE!FreeLibrary: 0033:000007fa`5c4328e8 48895c2408 mov qword ptr [rsp+8],rbx kd> p KERNELBASE!FreeLibrary+0x5: 0033:000007fa`5c4328ed 4889742410 mov qword ptr [rsp+10h],rsi kd> p KERNELBASE!FreeLibrary+0xa: 0033:000007fa`5c4328f2 57 push rdi kd> p KERNELBASE!FreeLibrary+0xb: 0033:000007fa`5c4328f3 4883ec20 sub rsp,20h kd> p KERNELBASE!FreeLibrary+0xf: 0033:000007fa`5c4328f7 488bf9 mov rdi,rcx kd> p KERNELBASE!FreeLibrary+0x12: 0033:000007fa`5c4328fa f6c102 test cl,2 kd> p KERNELBASE!FreeLibrary+0x15: 0033:000007fa`5c4328fd 0f8526c50100 jne KERNELBASE!FreeLibrary+0x3f (000007fa`5c44ee29) kd> p KERNELBASE!FreeLibrary+0x17: 0033:000007fa`5c432903 f6c101 test cl,1 kd> p KERNELBASE!FreeLibrary+0x1a: 0033:000007fa`5c432906 0f851dc50100 jne KERNELBASE!FreeLibrary+0x3f (000007fa`5c44ee29) kd> p KERNELBASE!FreeLibrary+0x1c: 0033:000007fa`5c43290c ff15ee610e00 call qword ptr [KERNELBASE!_imp_LdrUnloadDll (000007fa`5c518b00)] kd> p KERNELBASE!FreeLibrary+0x22: 0033:000007fa`5c432912 85c0 test eax,eax kd> t KERNELBASE!FreeLibrary+0x24: 0033:000007fa`5c432914 0f88be880400 js KERNELBASE!FreeLibrary+0xd7 (000007fa`5c47b1d8) kd> t KERNELBASE!FreeLibrary+0xd7: 0033:000007fa`5c47b1d8 8bc8 mov ecx,eax kd> p KERNELBASE!FreeLibrary+0xd9: 0033:000007fa`5c47b1da e80164fbff call KERNELBASE!BaseSetLastNTError (000007fa`5c4315e0) kd> p KERNELBASE!FreeLibrary+0xde: 0033:000007fa`5c47b1df 33c0 xor eax,eax kd> t KERNELBASE!FreeLibrary+0xe0: 0033:000007fa`5c47b1e1 e93977fbff jmp KERNELBASE!FreeLibrary+0x2f (000007fa`5c43291f) kd> t KERNELBASE!FreeLibrary+0x2f: 0033:000007fa`5c43291f 488b5c2430 mov rbx,qword ptr [rsp+30h] kd> t KERNELBASE!FreeLibrary+0x34: 0033:000007fa`5c432924 488b742438 mov rsi,qword ptr [rsp+38h] kd> t KERNELBASE!FreeLibrary+0x39: 0033:000007fa`5c432929 4883c420 add rsp,20h kd> t KERNELBASE!FreeLibrary+0x3d: 0033:000007fa`5c43292d 5f pop rdi kd> t KERNELBASE!FreeLibrary+0x3e: 0033:000007fa`5c43292e c3 ret kd> t ntdll!LdrpGetProcedureAddress+0x19a: 0033:000007fa`5f1b53c1 4c8b6c2478 mov r13,qword ptr [rsp+78h] kd> t ntdll!LdrpGetProcedureAddress+0x19f: 0033:000007fa`5f1b53c6 4c8d5c2440 lea r11,[rsp+40h] kd> t ntdll!LdrpGetProcedureAddress+0x1a4: 0033:000007fa`5f1b53cb 498b5b40 mov rbx,qword ptr [r11+40h] kd> t ntdll!LdrpGetProcedureAddress+0x1a8: 0033:000007fa`5f1b53cf 498b6b48 mov rbp,qword ptr [r11+48h] kd> t ntdll!LdrpGetProcedureAddress+0x1ac: 0033:000007fa`5f1b53d3 498be3 mov rsp,r11 kd> t ntdll!LdrpGetProcedureAddress+0x1af: 0033:000007fa`5f1b53d6 415f pop r15 kd> t ntdll!LdrpGetProcedureAddress+0x1b1: 0033:000007fa`5f1b53d8 415e pop r14 kd> t ntdll!LdrpGetProcedureAddress+0x1b3: 0033:000007fa`5f1b53da 415c pop r12 kd> t ntdll!LdrpGetProcedureAddress+0x1b5: 0033:000007fa`5f1b53dc 5f pop rdi kd> t ntdll!LdrpGetProcedureAddress+0x1b6: 0033:000007fa`5f1b53dd 5e pop rsi kd> t ntdll!LdrpGetProcedureAddress+0x1b7: 0033:000007fa`5f1b53de c3 ret kd> t KERNELBASE!LCMapStringEx+0x5cc: 0033:000007fa`5c432d00 4889442438 mov qword ptr [rsp+38h],rax kd> t KERNELBASE!LCMapStringEx+0x5d1: 0033:000007fa`5c432d05 488b442460 mov rax,qword ptr [rsp+60h] kd> t KERNELBASE!LCMapStringEx+0x5d6: 0033:000007fa`5c432d0a 4d8bc6 mov r8,r14 kd> t KERNELBASE!LCMapStringEx+0x5d9: 0033:000007fa`5c432d0d 4889442430 mov qword ptr [rsp+30h],rax kd> t KERNELBASE!LCMapStringEx+0x5de: 0033:000007fa`5c432d12 8b85b8000000 mov eax,dword ptr [rbp+0B8h] kd> t KERNELBASE!LCMapStringEx+0x5e4: 0033:000007fa`5c432d18 418bd5 mov edx,r13d kd> t KERNELBASE!LCMapStringEx+0x5e7: 0033:000007fa`5c432d1b 89442428 mov dword ptr [rsp+28h],eax kd> t KERNELBASE!LCMapStringEx+0x5eb: 0033:000007fa`5c432d1f 498bcb mov rcx,r11 kd> t KERNELBASE!LCMapStringEx+0x5ee: 0033:000007fa`5c432d22 4c89642420 mov qword ptr [rsp+20h],r12 kd> t KERNELBASE!LCMapStringEx+0x5f3: 0033:000007fa`5c432d27 41ff93e8000000 call qword ptr [r11+0E8h] kd> t ntdll!ZwCallbackReturn: 0033:000007fa`5f1a2c30 4c8bd1 mov r10,rcx kd> r rcx rcx=00000032633986e8 kd> r rdx rdx=0000000000000070 kd> r r8 r8=0000000000000000 kd> dqs rcx L(@rdx / 8) 00000032`633986e8 00000000`00000000 00000032`633986f0 00000000`00000000 00000032`633986f8 00000000`00000000 00000032`63398700 00000000`00000000 00000032`63398708 00000000`00000000 00000032`63398710 000007fa`5c432d00 KERNELBASE!LCMapStringEx+0x5cc 00000032`63398718 00000000`00000000 00000032`63398720 00000000`00000070 00000032`63398728 fffff880`099cb6d8 00000032`63398730 000007fa`5f1a2c30 ntdll!ZwCallbackReturn 00000032`63398738 00000000`00000000 00000032`63398740 00000000`0fc3050f 00000032`63398748 00000000`00000000 00000032`63398750 00000000`00000000 |
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).
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_ BOOLEAN SecondaryBuffer, _In_ BOOLEAN ChargeQuota, _Inout_opt_ PIRP Irp ); VOID MmProbeAndLockPages( _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!
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 |
template<size_t FLAG, BOOL VAL> __forceinline static void SetPage( __in const void* addr, __in size_t size ) { const BYTE* end_addr = static_cast<const BYTE*>(PAGE_ALIGN(reinterpret_cast<ULONG_PTR>(addr) + size + PAGE_SIZE)); for (addr = static_cast<const BYTE*>(addr); addr < end_addr; addr = reinterpret_cast<const BYTE*>(reinterpret_cast<ULONG_PTR>(addr) + PAGE_SIZE)) { CMMU mmu(addr); PAGE_TABLE_ENTRY pte; if (mmu.GetPTE(pte))// && (pte.Accessed || pte.Valid))//alter only used pages!! { switch (FLAG) { case Write: pte.Write = VAL; break; case Exec: pte.NoExecute = !VAL; break; case NoAccess: pte.Valid = !VAL; break; default: break; } mmu.SetPTE(pte); } } } |
OK, everything is ready, Poc of MDL/PTE based method:
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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
//invasive, but universal template<class TYPE, class PARAM> class CCpl3Code : public CCallback2User<TYPE> { #define SIZE_OF_CODE 0x30 #define TOP_OF_STACK_4__fnDWORD 0x7 //move to top of stack at user32!_fnDWORD #define DEEP_OF_INPUT_STACK 0xC //is deep of input from original stack public: CCpl3Code( __in void* ntdllZwCallbackReturn, __in void* targetFunction, __in ULONG_PTR* userStack ) : CCallback2User(ntdllZwCallbackReturn, targetFunction, userStack), m_sharedCode(reinterpret_cast<const void*>(UserCallback), SIZE_OF_CODE) { CApcLvl irql; m_cpl3Code = m_sharedCode.ReadPtrToUser(); } ~CCpl3Code() { if (m_cpl3Code) { CDispatchLvl irql; if (irql.SufficienIrql()) CMMU::SetUnExecutable(m_cpl3Code, SIZE_OF_CODE); } } _IRQL_requires_max_(PASSIVE_LEVEL) __checkReturn TYPE CallOneParamApi( __inout_bcount(size) void** output, __inout size_t* size ) { if (InvokeCall(output, osize)) return (sizeof(TYPE) == *size && *output && *static_cast<const TYPE*>(*output)); return false; } __checkReturn bool InjectCpl3Code( __in PARAM param ) { if (m_cpl3Code) { CDispatchLvl irql; if (irql.SufficienIrql()) { CMMU::SetExecutable(m_cpl3Code, SIZE_OF_CODE); PARAM_SET(0, m_ntdllZwCallbackReturn); m_input[CALLBACK_PARAM1] = GetInputBufferSize();//PARAM_SET(1, PARAM_SET(2, m_targetFunction); PARAM_SET(3, param); FUNCTION_SET(m_cpl3Code); } } return !!m_cpl3Code; } private: //CODE WITH CPL3, executed in ring3!! static void UserCallback( __in void* ntdllZwCallbackReturn, __in ULONG inputSize, __in void* targetFunction, __in HANDLE window, __in void* inputFindHelper ) { void* input = &inputFindHelper + TOP_OF_STACK_4__fnDWORD + DEEP_OF_INPUT_STACK; TYPE res = ((*(TYPE (*)(HANDLE))(targetFunction))(window)); (*(void (*)(void*, ULONG, NTSTATUS))(ntdllZwCallbackReturn))( &res, static_cast<ULONG>(sizeof(res)), WIN32_ERROR_SUCCESS ); } protected: const void* m_cpl3Code; private: CMdl m_sharedCode; }; |
Final implementation of invoking cpl3 callback and using ring3 api – PoC for invoking one parameter API “KERNELBASE!FreeLibrary” :
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 |
void UnloadLibrary( __in HANDLE libHndl ) { if (CIRQL::RunsOnPassiveLvl()) { CEthread ethread(PsGetCurrentThreadId()); if (ethread.Resolve() && ethread.IsResolved()) { ULONG_PTR* user_stack = NULL; { CApcLvl irql; if (irql.SufficienIrql()) { //needs APC_LEVEL -> lock CMdl auto_user_stack(ethread.CommitedStackBottom(), reinterpret_cast<size_t>(ethread.StackPtr()) - reinterpret_cast<size_t>(ethread.CommitedStackBottom())); //necessary to call NtUserBuildHwndList with ptr to usermode buffer, and ptr to usermode result counter user_stack = static_cast<ULONG_PTR*>(auto_user_stack.WritePtrUser()); } } if (user_stack) { size_t countof = (ethread.StackPtr() - ethread.CommitedStackBottom()); if (countof) { void* ntdll_zwCallbackReturn; void* kernelbase_freeLibrary; DbgPrint("\n\neq %p ntdll!ZwCallbackReturn; eq %p KERNELBASE!FreeLibrary;\n\n", &ntdll_zwCallbackReturn, &kernelbase_freeLibrary); DbgBreakPoint(); if (ntdll_zwCallbackReturn && kernelbase_freeLibrary) { #define ROP_GADGETS_TECH//CPL3_MDL_TECH// #ifdef ROP_GADGETS_TECH CRop<bool, HANDLE> call2user(ntdll_zwCallbackReturn, kernelbase_freeLibrary, user_stack); call2user.GenerateRopGadgets(libHndl); #else ifdef CPL3_MDL_TECH CCpl3Code<bool, HANDLE> call2user(ntdll_zwCallbackReturn, kernelbase_freeLibrary, user_stack); if (call2user.InjectCpl3Code(libHndl)) #endif { void* output = NULL; size_t osize = 0; bool res = !!call2user.CallOneParamApi(&output, &osize); DbgPrint("\n\n$$$$$ LIBRARY [%p] %s! $$$$$$$", libHndl, res ? "is UNLOADED!" : "- unloading FAILED"); KeBreak(); } } } } } } } |
* 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!
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?
ops, i totaly ommit this option in blog post yup, it is easy to use too, inject memory to process with appropriate rights, and no need to additional NX bit altering.
ROP – do not alter space of object at all
MDL – share ring0 code
Zw* – inject code
added https://github.com/zer0mem/Common/blob/master/Kernel/UserModeMemory.hpp, and in update of Callback2user.hpp is included zw* approach