Skip to content

Commit 0672c18

Browse files
committed
Leaky handles query
1 parent 8509bca commit 0672c18

3 files changed

Lines changed: 211 additions & 0 deletions

File tree

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#include <iostream>
2+
#include <Windows.h>
3+
#include <corecrt_io.h>
4+
5+
int main(int argc, char** argv)
6+
{
7+
if (argc <= 1) {
8+
printf("[-] Please give me a target PID\n");
9+
return -1;
10+
}
11+
12+
HANDLE hUserToken, hUserProcess;
13+
HANDLE hProcess, hThread, hFile;
14+
STARTUPINFOA si;
15+
PROCESS_INFORMATION pi;
16+
17+
ZeroMemory(&si, sizeof(si));
18+
si.cb = sizeof(si);
19+
ZeroMemory(&pi, sizeof(pi));
20+
21+
hFile = CreateFile(L"C:\\Windows\\System32\\version.dll", GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
22+
23+
if (hFile == INVALID_HANDLE_VALUE)
24+
{
25+
printf("[-] Failed to open file: %d\n", GetLastError());
26+
return -1;
27+
}
28+
29+
std::cout << "[+] File handle: " << std::hex << hFile << "\n";
30+
31+
hUserProcess = OpenProcess(PROCESS_QUERY_INFORMATION, false, atoi(argv[1]));
32+
if (!OpenProcessToken(hUserProcess, TOKEN_ALL_ACCESS, &hUserToken)) {
33+
printf("[-] Failed to open user process: %d\n", GetLastError());
34+
CloseHandle(hUserProcess);
35+
return -1;
36+
}
37+
38+
hProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, GetCurrentProcessId());
39+
if (hProcess == NULL)
40+
{
41+
std::cerr << "[-] Failed to open process\n";
42+
return 1;
43+
}
44+
std::cout << "[+] Process handle: " << std::hex << hProcess << "\n";
45+
46+
hThread = OpenThread(THREAD_ALL_ACCESS, TRUE, GetCurrentThreadId());
47+
if (hThread == NULL) {
48+
std::cerr << "[-] Failed to open thread\n";
49+
return 1;
50+
}
51+
52+
std::cout << "[+] Thread handle: " << std::hex << hThread << "\n";
53+
54+
char cmd[] = "C:\\Windows\\System32\\notepad.exe";
55+
if (!CreateProcessAsUserA(hUserToken, NULL,
56+
cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
57+
std::cerr << "[-] Failed to create process as user: " << std::hex << GetLastError() << "\n";
58+
return 1;
59+
}
60+
SuspendThread(hThread);
61+
return 0;
62+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
<overview>
4+
<p>
5+
In Windows environments, handles to processes, threads, and files are used to manage and interact with system resources. These handles can be opened with various permissions, such as process or thread access rights, which control what operations can be performed using the handle.
6+
</p>
7+
<p>
8+
A common issue arises when these handles are not properly closed before creating new processes. If the function
9+
<code>CreateProcessAsUser</code>
10+
or one of its variants is called with handle inheritance enabled, the opened handles may be inherited by the newly created process. This situation can lead to a privileged handle being leaked to a child process, which could potentially be exploited by an attacker to escalate privileges.
11+
</p>
12+
<p>
13+
Ensuring proper management of handles, including closing them with
14+
<code>CloseHandle</code>
15+
before creating new processes, is crucial to prevent these types of vulnerabilities. By doing so, the risk of handle inheritance and the subsequent security implications can be mitigated.
16+
</p>
17+
</overview>
18+
<recommendation>
19+
<p>Ensure that all handles are properly closed using <code>CloseHandle</code> before creating new processes with handle inheritance enabled.</p>
20+
</recommendation>
21+
<example>
22+
<p>The following example demonstrates several erroneous uses of handle management in a Windows application.</p>
23+
<sample src="LeakyHandles.cpp" />
24+
</example>
25+
<references>
26+
<li>
27+
<a href="https://book.hacktricks.xyz/windows-hardening/windows-local-privilege-escalation/leaked-handle-exploitation">Leaked Handle Exploitation</a>
28+
</li>
29+
</references>
30+
</qhelp>
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/**
2+
* @name Memory leak on failed call to realloc
3+
* @description An unprivileged child process may gain access to restricted resources by inheriting sensitive handles
4+
* from a privileged parent process.
5+
* @kind problem
6+
* @id cpp/windows-handle-leak
7+
* @problem.severity error
8+
* @precision low
9+
* @tags security
10+
* experimental
11+
* external/cwe/cwe-403
12+
*/
13+
14+
import cpp
15+
16+
// Base class to identify function calls with required permissions
17+
abstract class HandleOpeningCall extends FunctionCall {
18+
// Method to check if the permissions argument has at least one of the specified permissions
19+
predicate hasRequiredPermissions() { 1 != 1 }
20+
}
21+
22+
// Class to identify OpenProcess calls
23+
class OpenProcessCall extends HandleOpeningCall {
24+
OpenProcessCall() { this.getTarget().hasName("OpenProcess") }
25+
26+
override predicate hasRequiredPermissions() {
27+
exists(Expr permArg |
28+
this.getArgument(0) = permArg and
29+
(
30+
permArg.getValueText().indexOf("PROCESS_ALL_ACCESS") >= 0 or
31+
permArg.getValueText().indexOf("PROCESS_CREATE_PROCESS") >= 0 or
32+
permArg.getValueText().indexOf("PROCESS_CREATE_THREAD") >= 0 or
33+
permArg.getValueText().indexOf("PROCESS_DUP_HANDLE") >= 0 or
34+
permArg.getValueText().indexOf("PROCESS_VM_WRITE") >= 0
35+
)
36+
)
37+
}
38+
}
39+
40+
// Class to identify OpenThread calls
41+
class OpenThreadCall extends HandleOpeningCall {
42+
OpenThreadCall() { this.getTarget().hasName("OpenThread") }
43+
44+
override predicate hasRequiredPermissions() {
45+
exists(Expr permArg |
46+
this.getArgument(0) = permArg and
47+
(
48+
permArg.getValueText().indexOf("THREAD_ALL_ACCESS") >= 0 or
49+
permArg.getValueText().indexOf("THREAD_DIRECT_IMPERSONATION") >= 0 or
50+
permArg.getValueText().indexOf("THREAD_SET_CONTEXT") >= 0
51+
)
52+
)
53+
}
54+
}
55+
56+
// Class to identify CreateFile calls
57+
class CreateFileCall extends HandleOpeningCall {
58+
CreateFileCall() {
59+
this.getTarget().hasName("CreateFile") or
60+
this.getTarget().hasName("CreateFileA") or
61+
this.getTarget().hasName("CreateFileW")
62+
}
63+
64+
override predicate hasRequiredPermissions() {
65+
exists(Expr permArg |
66+
this.getArgument(0) = permArg and
67+
(
68+
permArg.getValueText().indexOf("GENERIC_WRITE") >= 0 or
69+
permArg.getValueText().indexOf("FILE_GENERIC_WRITE") >= 0 or
70+
permArg.getValueText().indexOf("WRITE_OWNER") >= 0 or
71+
permArg.getValueText().indexOf("WRITE_DAC") >= 0
72+
)
73+
)
74+
}
75+
}
76+
77+
// Class to identify CreateProcessAsUser calls
78+
class CreateProcessAsUserCall extends FunctionCall {
79+
CreateProcessAsUserCall() {
80+
this.getTarget().hasName("CreateProcessAsUser") or
81+
this.getTarget().hasName("CreateProcessAsUserA") or
82+
this.getTarget().hasName("CreateProcessAsUserW")
83+
}
84+
85+
// Method to check if the sixth argument is TRUE
86+
predicate isHandleInheritanceEnabled() {
87+
exists(Expr arg6 | this.getArgument(5) = arg6 and arg6.getValueText().toUpperCase() = "TRUE")
88+
}
89+
}
90+
91+
// Class to identify CloseHandle calls
92+
class CloseHandleCall extends FunctionCall {
93+
CloseHandleCall() { this.getTarget().hasName("CloseHandle") }
94+
}
95+
96+
// Function to find if CreateProcessAsUser is preceded by a handle opening call within the same function
97+
// and ensure CloseHandle is not called on the handle before CreateProcessAsUser
98+
predicate hasPrecedingHandleOpeningWithoutClose(
99+
CreateProcessAsUserCall createProcessAsUserCall, HandleOpeningCall handleOpeningCall
100+
) {
101+
createProcessAsUserCall.isHandleInheritanceEnabled() and
102+
createProcessAsUserCall.getEnclosingFunction() = handleOpeningCall.getEnclosingFunction() and
103+
handleOpeningCall.getLocation().getStartLine() <
104+
createProcessAsUserCall.getLocation().getStartLine() and
105+
handleOpeningCall.hasRequiredPermissions() and
106+
not exists(CloseHandleCall closeHandleCall |
107+
closeHandleCall.getEnclosingFunction() = handleOpeningCall.getEnclosingFunction() and
108+
closeHandleCall.getArgument(0) = handleOpeningCall.getAnArgument() and
109+
closeHandleCall.getLocation().getStartLine() <
110+
createProcessAsUserCall.getLocation().getStartLine()
111+
)
112+
}
113+
114+
from CreateProcessAsUserCall createProcessCall, HandleOpeningCall handleOpeningCall
115+
where hasPrecedingHandleOpeningWithoutClose(createProcessCall, handleOpeningCall)
116+
select createProcessCall, handleOpeningCall,
117+
"The " + handleOpeningCall.getTarget().getName() +
118+
" may leak a privileged handle to a child process through the " +
119+
createProcessCall.getTarget().getName() + "."

0 commit comments

Comments
 (0)