思路
作為windows服務(wù),要想在用戶桌面端啟動(dòng)一個(gè)程序,而且是以管理員身份運(yùn)行的,這個(gè)思路其實(shí)跟其他語(yǔ)言一樣的.
步驟
- 自身權(quán)限要高(這個(gè)應(yīng)該沒(méi)問(wèn)題,服務(wù)的運(yùn)行身份都是System)
- 找
winlogon.exe(根據(jù)快照遍歷所有進(jìn)程,匹配名稱,得到這個(gè)進(jìn)程) - 獲取它的訪問(wèn)令牌
- 復(fù)制訪問(wèn)令牌或者直接用
- 構(gòu)建運(yùn)行環(huán)境
WinSta0\Default - 用Win32 API:
CreateProcessAsUserW啟動(dòng)
golang 實(shí)現(xiàn)
import (
"strconv"
"strings"
"github.com/axgle/mahonia"
)
const (
CREATE_UNICODE_ENVIRONMENT = 0x00000400
CREATE_NO_WINDOW = 0x08000000
NORMAL_PRIORITY_CLASS = 0x20
INVALID_SESSION_ID = 0xFFFFFFFF
WTS_CURRENT_SERVER_HANDLE = 0
TOKEN_DUPLICATE = 0x0002
MAXIMUM_ALLOWED = 0x2000000
CREATE_NEW_CONSOLE = 0x00000010
IDLE_PRIORITY_CLASS = 0x40
HIGH_PRIORITY_CLASS = 0x80
REALTIME_PRIORITY_CLASS = 0x100
GENERIC_ALL_ACCESS = 0x10000000
)
// 先來(lái)兩個(gè)API,這個(gè)貌似使用syscall也可以.
// 剛剛開(kāi)始寫(xiě),不知道syscall已經(jīng)實(shí)現(xiàn)了一部分API,就自己動(dòng)手寫(xiě)了
// Win32進(jìn)程結(jié)構(gòu)體
type PROCESSENTRY32 struct {
dwSize uint32 // 結(jié)構(gòu)大小
cntUsage uint32 // 此進(jìn)程的引用計(jì)數(shù)
th32ProcessID uint32 // 進(jìn)程id
th32DefaultHeapID uintptr // 進(jìn)程默認(rèn)堆id
th32ModuleID uint32 // 進(jìn)程模塊id
cntThreads uint32 // 進(jìn)程的線程數(shù)
th32ParentProcessID uint32 // 父進(jìn)程id
pcPriClassBase uint32 // 線程優(yōu)先權(quán)
dwFlags uint32 // 保留
szExeFile [260]byte // 進(jìn)程全名
}
type SW struct {
SW_HIDE uint16 // 0,
SW_SHOWNORMAL uint16 // 1,
SW_NORMAL uint16 // 1,
SW_SHOWMINIMIZED uint16 // 2,
SW_SHOWMAXIMIZED uint16 // 3,
SW_MAXIMIZE uint16 // 3,
SW_SHOWNOACTIVATE uint16 // 4,
SW_SHOW uint16 // 5,
SW_MINIMIZE uint16 // 6,
SW_SHOWMINNOACTIVE uint16 // 7,
SW_SHOWNA uint16 // 8,
SW_RESTORE uint16 // 9,
SW_SHOWDEFAULT uint16 // 10,
SW_MAX uint16 // 10
}
var ISW = SW{
SW_HIDE: 0,
SW_SHOWNORMAL: 1,
SW_NORMAL: 1,
SW_SHOWMINIMIZED: 2,
SW_SHOWMAXIMIZED: 3,
SW_MAXIMIZE: 3,
SW_SHOWNOACTIVATE: 4,
SW_SHOW: 5,
SW_MINIMIZE: 6,
SW_SHOWMINNOACTIVE: 7,
SW_SHOWNA: 8,
SW_RESTORE: 9,
SW_SHOWDEFAULT: 10,
SW_MAX: 10,
}
func (p *PROCESSENTRY32) Name() string {
// string(process.szExeFile[0:]
decoder := mahonia.NewDecoder("gbk") //"github.com/axgle/mahonia" 為了中文名稱
name := decoder.ConvertString(string(p.szExeFile[0:])) //string(p.szExeFile[0:])
name = name[:strings.LastIndex(name, ".exe")+4]
return name
}
func (p *PROCESSENTRY32) ModuleID() string {
return strconv.Itoa(int(p.th32ModuleID))
}
func (p *PROCESSENTRY32) PID() uint32 {
return p.th32ProcessID
}
// 這個(gè)Error請(qǐng)不要作為判斷return依據(jù).因?yàn)槟阌锌赡苁谦@取到了,但是會(huì)給你來(lái)個(gè)提示
func OpenProcess(dwDesiredAccess uint, bInheritHandle bool, dwProcesssId uint32) (uintptr, error) {
if runtime.GOOS != "windows" {
return 0, fmt.Errorf("Only Support Windows")
}
kernel32 := syscall.NewLazyDLL("kernel32.dll") // 加載dll
openProcess := kernel32.NewProc("OpenProcess") // 獲得接口函數(shù)
pHandle, _, err := openProcess.Call(uintptr(dwDesiredAccess), uintptr(unsafe.Pointer(&bInheritHandle)), uintptr(dwProcesssId))
return pHandle, err
}
// 關(guān)閉句柄 服務(wù)開(kāi)發(fā)必須在使用完令牌句柄之后關(guān)閉它們。
func CloseHandle(handle uintptr) {
if runtime.GOOS != "windows" {
return
}
kernel32 := syscall.NewLazyDLL("kernel32.dll") // 加載dll
closeHandle := kernel32.NewProc("CloseHandle") // 獲得接口函數(shù)
closeHandle.Call(handle)
}
//
// GetProcessByName 根據(jù)pid獲取windows系統(tǒng)的某一個(gè)進(jìn)程
// 參數(shù):
// name string 進(jìn)程名稱, 建議加上.exe結(jié)尾
// return Process
func GetProcessByName(name string) (PROCESSENTRY32, error) {
var targetProcess PROCESSENTRY32
targetProcess = PROCESSENTRY32{
dwSize: 0,
}
if runtime.GOOS != "windows" {
return targetProcess, fmt.Errorf("Not On Windows OS")
}
kernel32 := syscall.NewLazyDLL("kernel32.dll")
CreateToolhelp32Snapshot := kernel32.NewProc("CreateToolhelp32Snapshot")
pHandle, _, _ := CreateToolhelp32Snapshot.Call(uintptr(0x2), uintptr(0x0))
if int(pHandle) == -1 {
return targetProcess, fmt.Errorf("error:Can not find any proess.")
}
defer CloseHandle(pHandle)
Process32Next := kernel32.NewProc("Process32Next")
for {
var proc PROCESSENTRY32
proc.dwSize = uint32(unsafe.Sizeof(proc))
if rt, _, _ := Process32Next.Call(uintptr(pHandle), uintptr(unsafe.Pointer(&proc))); int(rt) == 1 {
pname := proc.Name()
xpoint := strings.LastIndex(pname, ".exe")
if pname == name || (xpoint > 0 && pname[:xpoint] == name) {
return proc, nil
}
} else {
break
}
}
return targetProcess, fmt.Errorf("error:Can not find any proess.")
}
func StartProcessByPassUAC(applicationCmd string) error {
winlogonEntry, err := GetProcessByName("winlogon.exe")
if err != nil {
return err
}
// 獲取winlogon 進(jìn)程的句柄
winlogonProcess, err := OpenProcess(MAXIMUM_ALLOWED, false, winlogonEntry.PID())
// 此處可能會(huì)返回異常,但是不用擔(dān)心,只要成功獲取到進(jìn)程就可以
// if err != nil { // The operation completed successfully
// Ilog.Debug("OpenProcess:", err)
// return err
// }
defer CloseHandle(winlogonProcess)
// flags that specify the priority and creation method of the process
dwCreationFlags := CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT
// func() uint32 {
// if visible {
// return CREATE_NEW_CONSOLE
// } else {
// return CREATE_NO_WINDOW
// }
// }() | CREATE_UNICODE_ENVIRONMENT
var syshUserTokenDup syscall.Token
syscall.OpenProcessToken(syscall.Handle(winlogonProcess), MAXIMUM_ALLOWED, &syshUserTokenDup)
defer syshUserTokenDup.Close()
var syslpProcessInformation syscall.ProcessInformation //= &syscall.ProcessInformation{}
var syslpStartipInfo syscall.StartupInfo = syscall.StartupInfo{
Desktop: windows.StringToUTF16Ptr(`WinSta0\Default`),
ShowWindow: ISW.SW_SHOW, // func() uint16 {
// if visible {
// return ISW.SW_SHOW
// } else {
// return ISW.SW_HIDE
// }
// }()
}
syslpStartipInfo.Cb = uint32(unsafe.Sizeof(syslpStartipInfo))
var syslpProcessAttributes *syscall.SecurityAttributes
starterr := syscall.CreateProcessAsUser(
syshUserTokenDup,
nil,
windows.StringToUTF16Ptr(applicationCmd),
syslpProcessAttributes,
syslpProcessAttributes,
false,
uint32(dwCreationFlags),
nil,
nil,
&syslpStartipInfo,
&syslpProcessInformation)
return starterr
}
調(diào)用
func StartProcess(bySilence bool) error {
application := `C:\xtcp.exe` // 程序路徑
cmdLine := `--port 808` // 參數(shù)
cmdLineAll := "\"" + application + "\"" + " " + cmdLine // 程序路徑.exe args...
// 這里其實(shí)沒(méi)有使用API的路徑,使用了命令行的方式.所以在上面的API函數(shù)里面使用apppath使用nil,路徑是也nil
return StartProcessByPassUAC(cmdLineAll)
}
吐槽
其實(shí)開(kāi)始實(shí)現(xiàn)時(shí)時(shí)不知道syscall這個(gè)模塊實(shí)現(xiàn)了不少API的調(diào)用,也不知道windows.call.畢竟才剛剛使用go不久
所以自己動(dòng)手實(shí)現(xiàn)了不少Win32API的接口,StartProcessByPassUAC是自己手寫(xiě)了API的,
在調(diào)試的時(shí)候,無(wú)論怎么傳參數(shù),API總是返回錯(cuò)誤:
The system cannot find the path specified.
The directory name is invalid.
The filename, directory name, or volume label syntax is incorrect.
The filename or extension is too long.
上面這四個(gè)錯(cuò)誤,我換了很多參數(shù),換了短地址,換了windows.call,換了其他的API,換了各種數(shù)據(jù)類型,代碼寫(xiě)出來(lái)一天,調(diào)試錯(cuò)誤倒是用了4天時(shí)間.
最后各種方式,總算實(shí)驗(yàn)出來(lái)了上面的代碼可行性.
注意
如果你使用上面的代碼寫(xiě)出程序,使用以管理員身份運(yùn)行,但是,依舊不會(huì)啟動(dòng)指定的程序.這是因?yàn)閱?dòng)的權(quán)限還是較低.
所以你需要?jiǎng)?chuàng)建成服務(wù),然后啟動(dòng)它.
預(yù)感
這段代碼調(diào)試成功時(shí)的場(chǎng)景,隱約以前經(jīng)歷過(guò).
嗯,海馬效應(yīng)....
有種不好的感覺(jué)浮現(xiàn).