Golang在windows下使用当前用户身份调用其他程序

Golang在Windows下以系统服务运行时其运行身份是SYSTEM,此时使用exec包启动其他程序时,默认也会是SYSTEM身份,而由于windows的隔离机制,启动的程序是不会显示界面的。故在此情况下不能使用exec包,需要调用windows api使用当前用户身份打开其他程序。

通过调用CreateProcessAsUserW这个api可以用其他用户身份创建进程,所以我们可以通过获取当前用户的Token并用它来调用这个API。
Golang中的实现已有相关代码在https://gist.github.com/LiamHaworth/1ac37f7fb6018293fc43f86993db24fc,但此代码只能以当前用户普通身份调用程序,不能以当前用户管理员身份调用程序,所以我稍微修改了以下,可以使以当前用户管理员身份调用其他程序,相关代码如下:

package main

import (
    "fmt"
    "unsafe"

    "golang.org/x/sys/windows"
)

var (
    modwtsapi32                      *windows.LazyDLL  = windows.NewLazySystemDLL("wtsapi32.dll")
    modkernel32                      *windows.LazyDLL  = windows.NewLazySystemDLL("kernel32.dll")
    modadvapi32                      *windows.LazyDLL  = windows.NewLazySystemDLL("advapi32.dll")
    moduserenv                       *windows.LazyDLL  = windows.NewLazySystemDLL("userenv.dll")
    procWTSEnumerateSessionsW        *windows.LazyProc = modwtsapi32.NewProc("WTSEnumerateSessionsW")
    procWTSGetActiveConsoleSessionId *windows.LazyProc = modkernel32.NewProc("WTSGetActiveConsoleSessionId")
    procWTSQueryUserToken            *windows.LazyProc = modwtsapi32.NewProc("WTSQueryUserToken")
    procDuplicateTokenEx             *windows.LazyProc = modadvapi32.NewProc("DuplicateTokenEx")
    procCreateEnvironmentBlock       *windows.LazyProc = moduserenv.NewProc("CreateEnvironmentBlock")
    procCreateProcessAsUser          *windows.LazyProc = modadvapi32.NewProc("CreateProcessAsUserW")
    procGetTokenInformation          *windows.LazyProc = modadvapi32.NewProc("GetTokenInformation")
)

type WTS_CONNECTSTATE_CLASS int
type SECURITY_IMPERSONATION_LEVEL int
type TOKEN_TYPE int
type SW int
type WTS_SESSION_INFO struct {
    SessionID      windows.Handle
    WinStationName *uint16
    State          WTS_CONNECTSTATE_CLASS
}
type TOKEN_LINKED_TOKEN struct {
    LinkedToken windows.Token
}

const (
    WTS_CURRENT_SERVER_HANDLE uintptr = 0
)
const (
    WTSActive WTS_CONNECTSTATE_CLASS = iota
    WTSConnected
    WTSConnectQuery
    WTSShadow
    WTSDisconnected
    WTSIdle
    WTSListen
    WTSReset
    WTSDown
    WTSInit
)
const (
    SecurityAnonymous SECURITY_IMPERSONATION_LEVEL = iota
    SecurityIdentification
    SecurityImpersonation
    SecurityDelegation
)
const (
    TokenPrimary TOKEN_TYPE = iota + 1
    TokenImpersonazion
)
const (
    SW_HIDE            SW = 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                = 1
)
const (
    CREATE_UNICODE_ENVIRONMENT uint16 = 0x00000400
    CREATE_NO_WINDOW                  = 0x08000000
    CREATE_NEW_CONSOLE                = 0x00000010
)

//获得当前系统活动的SessionID
func GetCurrentUserSessionId() (windows.Handle, error) {
    sessionList, err := WTSEnumerateSessions()
    if err != nil {
        return 0xFFFFFFFF, fmt.Errorf("get current user session token: %s", err)
    }
    for i := range sessionList {
        if sessionList[i].State == WTSActive {
            return sessionList[i].SessionID, nil
        }
    }
    if sessionId, _, err := procWTSGetActiveConsoleSessionId.Call(); sessionId == 0xFFFFFFFF {
        return 0xFFFFFFFF, fmt.Errorf("get current user session token: call native WTSGetActiveConsoleSessionId: %s", err)
    } else {
        return windows.Handle(sessionId), nil
    }
}

// WTSEnumerateSession will call the native
// version for Windows and parse the result
// to a Golang friendly version
func WTSEnumerateSessions() ([]*WTS_SESSION_INFO, error) {
    var (
        sessionInformation windows.Handle      = windows.Handle(0)
        sessionCount       int                 = 0
        sessionList        []*WTS_SESSION_INFO = make([]*WTS_SESSION_INFO, 0)
    )
    if returnCode, _, err := procWTSEnumerateSessionsW.Call(WTS_CURRENT_SERVER_HANDLE, 0, 1, uintptr(unsafe.Pointer(&sessionInformation)), uintptr(unsafe.Pointer(&sessionCount))); returnCode == 0 {
        return nil, fmt.Errorf("call native WTSEnumerateSessionsW: %s", err)
    }
    structSize := unsafe.Sizeof(WTS_SESSION_INFO{})
    current := uintptr(sessionInformation)
    for i := 0; i < sessionCount; i++ {
        sessionList = append(sessionList, (*WTS_SESSION_INFO)(unsafe.Pointer(current)))
        current += structSize
    }
    return sessionList, nil
}

// DuplicateUserTokenFromSessionID will attempt
// to duplicate the user token for the user logged
// into the provided session ID
func DuplicateUserTokenFromSessionID(sessionId windows.Handle, runas bool) (windows.Token, error) {
    var (
        impersonationToken windows.Handle = 0
        userToken          windows.Token  = 0
    )

    if returnCode, _, err := procWTSQueryUserToken.Call(uintptr(sessionId), uintptr(unsafe.Pointer(&impersonationToken))); returnCode == 0 {
        return 0xFFFFFFFF, fmt.Errorf("call native WTSQueryUserToken: %s", err)
    }

    if returnCode, _, err := procDuplicateTokenEx.Call(uintptr(impersonationToken), 0, 0, uintptr(SecurityImpersonation), uintptr(TokenPrimary), uintptr(unsafe.Pointer(&userToken))); returnCode == 0 {
        return 0xFFFFFFFF, fmt.Errorf("call native DuplicateTokenEx: %s", err)
    }
    if runas {
        var admin TOKEN_LINKED_TOKEN
        var dt uintptr = 0
        if returnCode, _, _ := procGetTokenInformation.Call(uintptr(impersonationToken), 19, uintptr(unsafe.Pointer(&admin)), uintptr(unsafe.Sizeof(admin)), uintptr(unsafe.Pointer(&dt))); returnCode != 0 {
            userToken = admin.LinkedToken
        }
    }
    if err := windows.CloseHandle(impersonationToken); err != nil {
        return 0xFFFFFFFF, fmt.Errorf("close windows handle used for token duplication: %s", err)
    }
    return userToken, nil
}

func StartProcessAsCurrentUser(appPath, cmdLine, workDir string, runas bool) error {
    var (
        sessionId windows.Handle
        userToken windows.Token
        envInfo   windows.Handle

        startupInfo windows.StartupInfo
        processInfo windows.ProcessInformation

        commandLine uintptr = 0
        workingDir  uintptr = 0

        err error
    )

    if sessionId, err = GetCurrentUserSessionId(); err != nil {
        return err
    }

    if userToken, err = DuplicateUserTokenFromSessionID(sessionId, runas); err != nil {
        return fmt.Errorf("get duplicate user token for current user session: %s", err)
    }

    if returnCode, _, err := procCreateEnvironmentBlock.Call(uintptr(unsafe.Pointer(&envInfo)), uintptr(userToken), 0); returnCode == 0 {
        return fmt.Errorf("create environment details for process: %s", err)
    }

    creationFlags := CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE
    startupInfo.ShowWindow = SW_SHOW
    startupInfo.Desktop = windows.StringToUTF16Ptr("winsta0\\default")

    if len(cmdLine) > 0 {
        commandLine = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(cmdLine)))
    }
    if len(workDir) > 0 {
        workingDir = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(workDir)))
    }
    if returnCode, _, err := procCreateProcessAsUser.Call(
        uintptr(userToken), uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(appPath))), commandLine, 0, 0, 0,
        uintptr(creationFlags), uintptr(envInfo), workingDir, uintptr(unsafe.Pointer(&startupInfo)), uintptr(unsafe.Pointer(&processInfo)),
    ); returnCode == 0 {
        return fmt.Errorf("create process as user: %s", err)
    }
    return nil
}

调用方式

StartProcessAsCurrentUser(程序路径, 启动参数, 工作目录 string, 是否以管理员身份运行) error

需要注意的是,若使用cmdLine传入启动参数,则需要加上传入文件路径,否则可能会有不可预期的错误。
例:

StartProcessAsCurrentUser(`C:\test\test.exe`,`C:\test\test.exe hello world`,`C:\test`,true)
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
使用 MinGW-w64 工具链在 Windows 上编译 Linux 程序,需要使用 CGO 编译器。CGO 是 Go 语言的 C 语言调用接口,它可以让 Go 语言调用 C 语言的函数和库。通过 CGO 编译器,我们可以在 Windows 上编写 Go 代码,然后使用 MinGW-w64 工具链编译 C 语言代码,最后将 Go 代码和 C 代码链接起来生成 Linux 可执行文件。 具体操作步骤如下: 1. 下载并安装 MinGW-w64 工具链,下载地址:https://sourceforge.net/projects/mingw-w64/ 2. 配置 MinGW-w64 工具链的环境变量,将 MinGW-w64 的 bin 目录添加到系统 PATH 环境变量中。 3. 在 Go 代码中使用 CGO,例如: ``` package main /* #include <stdio.h> void sayHello() { printf("Hello, World!\n"); } */ import "C" func main() { C.sayHello() } ``` 这个例子演示了如何在 Go 代码中调用 C 语言的函数。在 Go 代码中,我们需要使用 `import "C"` 导入 C 语言的库和函数。 4. 编译 C 语言代码,生成静态库文件,例如: ``` x86_64-w64-mingw32-gcc -c -o mylib.o mylib.c x86_64-w64-mingw32-ar rcs mylib.a mylib.o ``` 这个命令会生成静态库文件 `mylib.a`,它包含了 C 语言代码的编译结果。 5. 编译 Go 代码,使用 CGO 编译器链接 C 语言的静态库文件,例如: ``` CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=x86_64-w64-mingw32-gcc go build -o myprogram main.go ``` 这个命令会生成 Linux 平台的可执行文件 `myprogram`,它包含了 Go 代码和 C 语言代码的链接结果。 6. 将生成的 Linux 可执行文件从 Windows 复制到 Linux 环境中运行,例如: ``` scp myprogram user@server:/path/to/myprogram ssh user@server "/path/to/myprogram" ``` 其中,`user` 是 Linux 系统的用户名,`server` 是 Linux 系统的 IP 地址或域名,`/path/to/myprogram` 是 Linux 系统中的路径。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值