草庐IT

python - 在没有提升权限的情况下调用二进制文件

coder 2024-06-05 原文

为了从 Python 请求 UAC 提升权限,当调用外部程序时,您可以这样做

ctypes.windll.shell32.ShellExecuteW(None, "runas", my_binary_file_path, "", None, 1)

但是,假设您的 Python 脚本正在 管理员权限下执行,您如何没有管理员权限调用外部程序?

最佳答案

我更喜欢的一种方法是以 shell 用户身份运行。首先打开 shell 进程并复制其 token 。您可以通过调用 GetShellWindow 然后调用 GetWindowThreadProcessId 来获取 shell 进程 PID。通常这是资源管理器。

默认情况下,管理员帐户没有 SeAssignPrimaryTokenPrivilege,在这种情况下您不能直接调用 CreateProcessAsUser。您必须请求一个更有特权的进程来代表您调用电话。 CreateProcessWithTokenW 通过对辅助登录服务进行远程过程调用来为您执行此操作。

PyWin32 不包装 GetShellWindowCreateProcessWithTokenW,因此您需要使用 ctypes 来调用它们。

很少有 Windows 系统可能在没有常规 shell 的情况下运行,或者使用 shell 时无法通过 SetShellWindow[Ex] 注册其窗口。在这种情况下,GetShellWindow 返回 NULL。作为这种情况的回退,您可以使用一种有点可疑(但有效)的方法来获取 session 用户的 token 并调用 CreateProcessAsUser

首先获取 session 的 Windows 子系统进程 csrss.exe 的 PID。最简单的方法是调用未记录(但稳定)的函数 CsrGetProcessId。启用 SeDebugPrivilege 以使用有限的查询访问权限打开此进程。然后打开它的Token,复制,模拟。现在您拥有通过 WTSQueryUserToken 获取 session 用户 token 所需的 SeTcbPrivilege,并且您还拥有能够调用 CreateProcessAsUser 的 SeAssignPrimaryTokenPrivilege。

导入和 ctypes 定义

import os
import contextlib

import win32con
import winerror
import win32api
import win32process
import win32security
import win32ts
import pywintypes

import ctypes
from ctypes import wintypes

ntdll = ctypes.WinDLL('ntdll')
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
advapi32 = ctypes.WinDLL('advapi32', use_last_error=True)
user32 = ctypes.WinDLL('user32', use_last_error=True)

TOKEN_ADJUST_SESSIONID = 0x0100
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
LPBYTE = ctypes.POINTER(wintypes.BYTE)

class STARTUPINFO(ctypes.Structure):
    """https://msdn.microsoft.com/en-us/library/ms686331"""
    __slots__ = ()

    _fields_ = (('cb',              wintypes.DWORD),
                ('lpReserved',      wintypes.LPWSTR),
                ('lpDesktop',       wintypes.LPWSTR),
                ('lpTitle',         wintypes.LPWSTR),
                ('dwX',             wintypes.DWORD),
                ('dwY',             wintypes.DWORD),
                ('dwXSize',         wintypes.DWORD),
                ('dwYSize',         wintypes.DWORD),
                ('dwXCountChars',   wintypes.DWORD),
                ('dwYCountChars',   wintypes.DWORD),
                ('dwFillAttribute', wintypes.DWORD),
                ('dwFlags',         wintypes.DWORD),
                ('wShowWindow',     wintypes.WORD),
                ('cbReserved2',     wintypes.WORD),
                ('lpReserved2',     LPBYTE),
                ('hStdInput',       wintypes.HANDLE),
                ('hStdOutput',      wintypes.HANDLE),
                ('hStdError',       wintypes.HANDLE))

    def __init__(self, **kwds):
        self.cb = ctypes.sizeof(self)
        super(STARTUPINFO, self).__init__(**kwds)

LPSTARTUPINFO = ctypes.POINTER(STARTUPINFO)

class PROCESS_INFORMATION(ctypes.Structure):
    """https://msdn.microsoft.com/en-us/library/ms684873"""
    __slots__ = ()

    _fields_ = (('hProcess',    wintypes.HANDLE),
                ('hThread',     wintypes.HANDLE),
                ('dwProcessId', wintypes.DWORD),
                ('dwThreadId',  wintypes.DWORD))

LPPROCESS_INFORMATION = ctypes.POINTER(PROCESS_INFORMATION)

kernel32.CloseHandle.argtypes = (wintypes.HANDLE,)

# https://msdn.microsoft.com/en-us/library/ms682434
advapi32.CreateProcessWithTokenW.argtypes = (
    wintypes.HANDLE,       # _In_        hToken
    wintypes.DWORD,        # _In_        dwLogonFlags
    wintypes.LPCWSTR,      # _In_opt_    lpApplicationName
    wintypes.LPWSTR,       # _Inout_opt_ lpCommandLine
    wintypes.DWORD,        # _In_        dwCreationFlags
    wintypes.LPCWSTR,      # _In_opt_    lpEnvironment
    wintypes.LPCWSTR,      # _In_opt_    lpCurrentDirectory
    LPSTARTUPINFO,         # _In_        lpStartupInfo
    LPPROCESS_INFORMATION) # _Out_       lpProcessInformation

# https://msdn.microsoft.com/en-us/library/ms633512
user32.GetShellWindow.restype = wintypes.HWND

辅助函数

def adjust_token_privileges(htoken, state):
    prev_state = win32security.AdjustTokenPrivileges(htoken, False, state)
    error = win32api.GetLastError()
    if error == winerror.ERROR_NOT_ALL_ASSIGNED:
        raise pywintypes.error(
                error, 'AdjustTokenPrivileges',
                win32api.FormatMessageW(error))
    return prev_state

def enable_token_privileges(htoken, *privilege_names):
    state = []
    for name in privilege_names:
        state.append((win32security.LookupPrivilegeValue(None, name),
                      win32con.SE_PRIVILEGE_ENABLED))
    return adjust_token_privileges(htoken, state)

@contextlib.contextmanager
def open_effective_token(access, open_as_self=True):
    hthread = win32api.GetCurrentThread()
    impersonated_self = False
    try:
        htoken = win32security.OpenThreadToken(
            hthread, access, open_as_self)
    except pywintypes.error as e:
        if e.winerror != winerror.ERROR_NO_TOKEN:
            raise
        win32security.ImpersonateSelf(win32security.SecurityImpersonation)
        impersonated_self = True
        htoken = win32security.OpenThreadToken(
            hthread, access, open_as_self)
    try:
        yield htoken
    finally:
        if impersonated_self:
            win32security.SetThreadToken(None, None)

@contextlib.contextmanager
def enable_privileges(*privilege_names):
    """Enable a set of privileges for the current thread."""
    prev_state = ()
    with open_effective_token(
            win32con.TOKEN_QUERY |
            win32con.TOKEN_ADJUST_PRIVILEGES) as htoken:
        prev_state = enable_token_privileges(htoken, *privilege_names)
        try:
            yield
        finally:
            if prev_state:
                adjust_token_privileges(htoken, prev_state)

def duplicate_shell_token():
    hWndShell = user32.GetShellWindow()
    if not hWndShell:
        raise pywintypes.error(
                winerror.ERROR_FILE_NOT_FOUND,
                'GetShellWindow', 'no shell window')
    tid, pid = win32process.GetWindowThreadProcessId(hWndShell)
    hProcShell = win32api.OpenProcess(
                    win32con.PROCESS_QUERY_INFORMATION, False, pid)
    hTokenShell = win32security.OpenProcessToken(
                    hProcShell, win32con.TOKEN_DUPLICATE)
    # Contrary to MSDN, CreateProcessWithTokenW also requires
    # TOKEN_ADJUST_DEFAULT and TOKEN_ADJUST_SESSIONID
    return win32security.DuplicateTokenEx(
                hTokenShell,
                win32security.SecurityImpersonation,
                win32con.TOKEN_ASSIGN_PRIMARY |
                win32con.TOKEN_DUPLICATE |
                win32con.TOKEN_QUERY |
                win32con.TOKEN_ADJUST_DEFAULT |
                TOKEN_ADJUST_SESSIONID,
                win32security.TokenPrimary, None)

@contextlib.contextmanager
def impersonate_system():
    with enable_privileges(win32security.SE_DEBUG_NAME):
        pid_csr = ntdll.CsrGetProcessId()
        hprocess_csr = win32api.OpenProcess(
            PROCESS_QUERY_LIMITED_INFORMATION, False, pid_csr)
        htoken_csr = win32security.OpenProcessToken(
            hprocess_csr, win32con.TOKEN_DUPLICATE)
    htoken = win32security.DuplicateTokenEx(
        htoken_csr, win32security.SecurityImpersonation,
        win32con.TOKEN_QUERY |
        win32con.TOKEN_IMPERSONATE |
        win32con.TOKEN_ADJUST_PRIVILEGES,
        win32security.TokenImpersonation)
    enable_token_privileges(
        htoken,
        win32security.SE_TCB_NAME,
        win32security.SE_INCREASE_QUOTA_NAME,
        win32security.SE_ASSIGNPRIMARYTOKEN_NAME)
    try:
        htoken_prev = win32security.OpenThreadToken(
            win32api.GetCurrentThread(), win32con.TOKEN_IMPERSONATE, True)
    except pywintypes.error as e:
        if e.winerror != winerror.ERROR_NO_TOKEN:
            raise
        htoken_prev = None
    win32security.SetThreadToken(None, htoken)
    try:
        yield
    finally:
        win32security.SetThreadToken(None, htoken_prev)

def startupinfo_update(si_src, si_dst):
    for name in ('lpDesktop', 'lpTitle', 'dwX', 'dwY', 'dwXSize',
                 'dwYSize', 'dwXCountChars', 'dwYCountChars',
                 'dwFillAttribute', 'dwFlags', 'wShowWindow',
                 'hStdInput', 'hStdOutput', 'hStdError'):
        try:
            setattr(si_dst, name, getattr(si_src, name))
        except AttributeError:
            pass

主要功能

def runas_session_user(cmd, executable=None, creationflags=0, cwd=None,
                       startupinfo=None, return_handles=False):
    if not creationflags & win32con.DETACHED_PROCESS:
        creationflags |= win32con.CREATE_NEW_CONSOLE
    if cwd is None:
        cwd = os.getcwd()
    si = win32process.STARTUPINFO()
    if startupinfo:
        startupinfo_update(startupinfo, si)
    with impersonate_system():
        htoken_user = win32ts.WTSQueryUserToken(
            win32ts.WTS_CURRENT_SESSION)
        hProcess, hThread, dwProcessId, dwThreadId = (
            win32process.CreateProcessAsUser(
                htoken_user, executable, cmd, None, None, False,
                creationflags, None, cwd, si))
    if return_handles:
        return hProcess, hThread
    return dwProcessId, dwThreadId

def runas_shell_user(cmd, executable=None, creationflags=0, cwd=None,
                     startupinfo=None, return_handles=False):
    if not creationflags & win32con.DETACHED_PROCESS:
        creationflags |= win32con.CREATE_NEW_CONSOLE
    if cwd is None:
        cwd = os.getcwd()
    si = STARTUPINFO()
    if startupinfo:
        startupinfo_update(startupinfo, si)
    pi = PROCESS_INFORMATION()
    try:
        htoken = duplicate_shell_token()
    except pywintypes.error as e:
        if e.winerror != winerror.ERROR_FILE_NOT_FOUND:
            raise
        return runas_session_user(cmd, executable, creationflags, cwd,
                    startupinfo, return_handles)
    with enable_privileges(win32security.SE_IMPERSONATE_NAME):
        if not advapi32.CreateProcessWithTokenW(
                    int(htoken), 0, executable, cmd, creationflags, None,
                    cwd, ctypes.byref(si), ctypes.byref(pi)):
            error = ctypes.get_last_error()
            raise pywintypes.error(
                error, 'CreateProcessWithTokenW',
                win32api.FormatMessageW(error))
    hProcess = pywintypes.HANDLE(pi.hProcess)
    hThread = pywintypes.HANDLE(pi.hThread)
    if return_handles:
        return hProcess, hThread
    return pi.dwProcessId, pi.dwThreadId

关于python - 在没有提升权限的情况下调用二进制文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48051283/

有关python - 在没有提升权限的情况下调用二进制文件的更多相关文章

  1. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  2. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  3. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  4. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  5. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  6. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  7. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  8. ruby - 难道Lua没有和Ruby的method_missing相媲美的东西吗? - 2

    我好像记得Lua有类似Ruby的method_missing的东西。还是我记错了? 最佳答案 表的metatable的__index和__newindex可以用于与Ruby的method_missing相同的效果。 关于ruby-难道Lua没有和Ruby的method_missing相媲美的东西吗?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/7732154/

  9. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  10. ruby-on-rails - rails 目前在重启后没有安装 - 2

    我有一个奇怪的问题:我在rvm上安装了ruby​​onrails。一切正常,我可以创建项目。但是在我输入“railsnew”时重新启动后,我有“程序'rails'当前未安装。”。SystemUbuntu12.04ruby-v"1.9.3p194"gemlistactionmailer(3.2.5)actionpack(3.2.5)activemodel(3.2.5)activerecord(3.2.5)activeresource(3.2.5)activesupport(3.2.5)arel(3.0.2)builder(3.0.0)bundler(1.1.4)coffee-rails(

随机推荐