|
package main |
|
|
|
import ( |
|
"unsafe" |
|
|
|
"golang.org/x/sys/windows" |
|
"golang.org/x/sys/windows/svc" |
|
) |
|
|
|
const svcName = "UiShellSvc" |
|
|
|
var ( |
|
modwtsapi32 = windows.NewLazySystemDLL("wtsapi32.dll") |
|
moduserenv = windows.NewLazySystemDLL("userenv.dll") |
|
modkernel32 = windows.NewLazySystemDLL("kernel32.dll") |
|
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") |
|
|
|
procWTSGetActiveConsoleSessionId = modkernel32.NewProc("WTSGetActiveConsoleSessionId") |
|
procCreateEnvironmentBlock = moduserenv.NewProc("CreateEnvironmentBlock") |
|
procDestroyEnvironmentBlock = moduserenv.NewProc("DestroyEnvironmentBlock") |
|
procCreateProcessAsUser = modadvapi32.NewProc("CreateProcessAsUserW") |
|
) |
|
|
|
const ( |
|
createNewConsole = 0x00000010 |
|
createUnicodeEnvironment = 0x00000400 |
|
tokenSessionId = 12 // TokenSessionId enum value |
|
) |
|
|
|
func activeConsoleSession() uint32 { |
|
r, _, _ := procWTSGetActiveConsoleSessionId.Call() |
|
return uint32(r) |
|
} |
|
|
|
// launchShellInSession duplicates the SYSTEM token, retargets it to the given |
|
// session, and spawns cmd.exe on the interactive desktop. |
|
func launchShellInSession(sessionId uint32) error { |
|
var self windows.Token |
|
err := windows.OpenProcessToken( |
|
windows.CurrentProcess(), |
|
windows.TOKEN_DUPLICATE|windows.TOKEN_QUERY| |
|
windows.TOKEN_ASSIGN_PRIMARY|windows.TOKEN_ADJUST_DEFAULT| |
|
windows.TOKEN_ADJUST_SESSIONID, |
|
&self, |
|
) |
|
if err != nil { |
|
return err |
|
} |
|
defer self.Close() |
|
|
|
var dup windows.Token |
|
err = windows.DuplicateTokenEx( |
|
self, |
|
windows.MAXIMUM_ALLOWED, |
|
nil, |
|
windows.SecurityImpersonation, |
|
windows.TokenPrimary, |
|
&dup, |
|
) |
|
if err != nil { |
|
return err |
|
} |
|
defer dup.Close() |
|
|
|
// Retarget the token to the interactive session. |
|
if err := windows.SetTokenInformation( |
|
dup, |
|
tokenSessionId, |
|
(*byte)(unsafe.Pointer(&sessionId)), |
|
uint32(unsafe.Sizeof(sessionId)), |
|
); err != nil { |
|
return err |
|
} |
|
|
|
// Build an environment block for the duplicated token. |
|
var env uintptr |
|
procCreateEnvironmentBlock.Call(uintptr(unsafe.Pointer(&env)), uintptr(dup), 0) |
|
if env != 0 { |
|
defer procDestroyEnvironmentBlock.Call(env) |
|
} |
|
|
|
desktop, _ := windows.UTF16PtrFromString(`winsta0\default`) |
|
cmdline, _ := windows.UTF16PtrFromString(`cmd.exe`) |
|
|
|
si := windows.StartupInfo{ |
|
Cb: uint32(unsafe.Sizeof(windows.StartupInfo{})), |
|
Desktop: desktop, |
|
} |
|
var pi windows.ProcessInformation |
|
|
|
r, _, e := procCreateProcessAsUser.Call( |
|
uintptr(dup), |
|
0, |
|
uintptr(unsafe.Pointer(cmdline)), |
|
0, |
|
0, |
|
0, |
|
uintptr(createNewConsole|createUnicodeEnvironment), |
|
env, |
|
0, |
|
uintptr(unsafe.Pointer(&si)), |
|
uintptr(unsafe.Pointer(&pi)), |
|
) |
|
if r == 0 { |
|
return e |
|
} |
|
windows.CloseHandle(pi.Thread) |
|
windows.CloseHandle(pi.Process) |
|
return nil |
|
} |
|
|
|
type uiShellService struct{} |
|
|
|
func (uiShellService) Execute(args []string, r <-chan svc.ChangeRequest, s chan<- svc.Status) (bool, uint32) { |
|
s <- svc.Status{State: svc.StartPending} |
|
s <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown} |
|
|
|
if sid := activeConsoleSession(); sid != 0xFFFFFFFF { |
|
launchShellInSession(sid) |
|
} |
|
|
|
// One-shot: stop after spawning. Drain any stop request in the meantime. |
|
for { |
|
select { |
|
case c := <-r: |
|
switch c.Cmd { |
|
case svc.Stop, svc.Shutdown: |
|
s <- svc.Status{State: svc.StopPending} |
|
return false, 0 |
|
case svc.Interrogate: |
|
s <- c.CurrentStatus |
|
} |
|
default: |
|
s <- svc.Status{State: svc.StopPending} |
|
return false, 0 |
|
} |
|
} |
|
} |
|
|
|
func main() { |
|
svc.Run(svcName, uiShellService{}) |
|
} |