golang服務(wù)啟動(dòng)應(yīng)用程序 以管理員身份運(yùn)行

思路

作為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).

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容