golang服务启动应用程序 以管理员身份运行

思路

作为windows服务,要想在用户桌面端启动一个程序,而且是以管理员身份运行的,这个思路其实跟其他语言一样的.

步骤

  • 自身权限要高(这个应该没问题,服务的运行身份都是System)
  • winlogon.exe (根据快照遍历所有进程,匹配名称,得到这个进程)
  • 获取它的访问令牌
  • 复制访问令牌或者直接用
  • 构建运行环境 WinSta0\Default
  • 用Win32 API:CreateProcessAsUserW启动

golang 实现

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
)

// 先来两个API,这个貌似使用syscall也可以.
// 刚刚开始写,不知道syscall已经实现了一部分API,就自己动手写了

// Win32进程结构体 
type PROCESSENTRY32 struct {
    dwSize              uint32    // 结构大小
    cntUsage            uint32    // 此进程的引用计数
    th32ProcessID       uint32    // 进程id
    th32DefaultHeapID   uintptr   // 进程默认堆id
    th32ModuleID        uint32    // 进程模块id
    cntThreads          uint32    // 进程的线程数
    th32ParentProcessID uint32    // 父进程id
    pcPriClassBase      uint32    // 线程优先权
    dwFlags             uint32    // 保留
    szExeFile           [260]byte // 进程全名
}


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
}


// 这个Error请不要作为判断return依据.因为你有可能是获取到了,但是会给你来个提示
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") // 获得接口函数
    pHandle, _, err := openProcess.Call(uintptr(dwDesiredAccess), uintptr(unsafe.Pointer(&bInheritHandle)), uintptr(dwProcesssId))

    return pHandle, err
}

// 关闭句柄 服务开发必须在使用完令牌句柄之后关闭它们。
func CloseHandle(handle uintptr) {
    if runtime.GOOS != "windows" {
        return
    }
    kernel32 := syscall.NewLazyDLL("kernel32.dll") // 加载dll
    closeHandle := kernel32.NewProc("CloseHandle") // 获得接口函数
    closeHandle.Call(handle)
}


//
// GetProcessByName 根据pid获取windows系统的某一个进程
//  参数:
//  name    string  进程名称, 建议加上.exe结尾
//  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 进程的句柄
    winlogonProcess, err := OpenProcess(MAXIMUM_ALLOWED, false, winlogonEntry.PID())
    // 此处可能会返回异常,但是不用担心,只要成功获取到进程就可以
    // 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
}

调用


func StartProcess(bySilence bool) error {
    application := `C:\xtcp.exe` // 程序路径
    cmdLine := `--port 808`       // 参数
    cmdLineAll := "\"" + application + "\"" + " " + cmdLine // 程序路径.exe args...
    // 这里其实没有使用API的路径,使用了命令行的方式.所以在上面的API函数里面使用apppath使用nil,路径是也nil
    return StartProcessByPassUAC(cmdLineAll)    
}

吐槽

其实开始实现时时不知道syscall这个模块实现了不少API的调用,也不知道windows.call.毕竟才刚刚使用go不久
所以自己动手实现了不少Win32API的接口,StartProcessByPassUAC是自己手写了API的,
在调试的时候,无论怎么传参数,API总是返回错误:

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.

上面这四个错误,我换了很多参数,换了短地址,换了windows.call,换了其他的API,换了各种数据类型,代码写出来一天,调试错误倒是用了4天时间.
最后各种方式,总算实验出来了上面的代码可行性.

注意

如果你使用上面的代码写出程序,使用以管理员身份运行,但是,依旧不会启动指定的程序.这是因为启动的权限还是较低.
所以你需要创建成服务,然后启动它.

预感

这段代码调试成功时的场景,隐约以前经历过.
嗯,海马效应....
有种不好的感觉浮现.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容