如何执行Powershell的“start-process -Verb RunAs”从批处理内部,提升的命令继承批处理的环境?

发布于 2025-01-12 16:51:48 字数 1538 浏览 0 评论 0 原文

1.问题

我有一个复杂的批处理文件,其中某些部分需要以提升的/管理权限运行(例如与Windows服务交互),我找到了一种Powershell方法来做到这一点:

powershell.exe -command "try {$proc = start-process -wait -Verb runas -filepath '%~nx0' -ArgumentList '<arguments>'; exit $proc.ExitCode} catch {write-host $Error; exit -10}"

但是有一个巨大的警告!我的脚本的提升实例 (%~nx0) 以环境变量的全新副本开始,并且我之前设置“var=content” 的所有内容均不可用。

2.到目前为止我所尝试的

这个Powershell脚本也没有帮助,因为Verb =“RunAs”需要UseShellExecute = $true,而这又与/互斥使用 StartInfo.EnvironmentVariables.Add()

$p = New-Object System.Diagnostics.Process
$p.StartInfo.FileName = "cmd.exe";
$p.StartInfo.Arguments = '/k set blasfg'
$p.StartInfo.UseShellExecute = $true;
$p.StartInfo.Verb = "RunAs";
$p.StartInfo.EnvironmentVariables.Add("blasfg", "C:\\Temp")

$p.Start() | Out-Null
$p.WaitForExit()
exit $p.ExitCode

即使这可行,我仍然需要传输数十个变量...

3. 没有吸引力的半解决方案

因为规避问题并不是正确的解决方案。

  1. hstart 这样的辅助工具 - 因为我无法依赖外部工具。只有 CMD、Powershell,也许还有 VBscript(但看起来 runas 加上 waiterrorlevel/ExitCode 处理在 vbs 中是不可能的) 。
  2. 传递(仅必需的)变量作为参数 - 因为我需要数十个变量并且转义它们是一件丑陋的苦差事(无论是结果还是执行过程)。
  3. 重新启动整个脚本 - 因为所有解析、检查处理和其他任务再次发生(一次又一次......)的效率很低。我希望将提升的部分保持在最低限度,并且稍后可以以普通用户身份运行某些操作(例如服务启动/停止)。
  4. 将环境写入文件并在提升的实例中重新读取它 - 因为这是一个丑陋的黑客,我希望有一个更干净的选项。将可能敏感的信息写入文件甚至比将其临时存储在环境变量中更糟糕。

1. Problem

I have a complicated batch file where some parts need to run with elevated/admin rights (e.g. interacting with Windows services) and I found a Powershell way to do that:

powershell.exe -command "try {$proc = start-process -wait -Verb runas -filepath '%~nx0' -ArgumentList '<arguments>'; exit $proc.ExitCode} catch {write-host $Error; exit -10}"

But there's a huge caveat! The elevated instance of my script (%~nx0) starts with a fresh copy of environment variables and everything I set "var=content" before is unavailable.

2. What I've tried so far

This Powershell script doesn't help either because Verb = "RunAs" requires UseShellExecute = $true which in turn is mutually exclusive to/with StartInfo.EnvironmentVariables.Add()

$p = New-Object System.Diagnostics.Process
$p.StartInfo.FileName = "cmd.exe";
$p.StartInfo.Arguments = '/k set blasfg'
$p.StartInfo.UseShellExecute = $true;
$p.StartInfo.Verb = "RunAs";
$p.StartInfo.EnvironmentVariables.Add("blasfg", "C:\\Temp")

$p.Start() | Out-Null
$p.WaitForExit()
exit $p.ExitCode

And even if that would work I'd still need to transfer dozens of variables...

3. unappealing semi-solutions

because circumventing the problem is no proper solution.

  1. helper tools like hstart - because I can't relay on external tools. Only CMD, Powershell and maybe VBscript (but it looks like runas plus wait and errorlevel/ExitCode processing isn't possible with/in vbs).
  2. passing (only required) variables as arguments - because I need dozens and escaping them is an ugly chore (both the result and doing it).
  3. restarting the whole script - because it's inefficient with all the parsing, checking processing and other tasks happening again (and again and ...). I'd like to keep the elevated parts to a minimum and some actions can later be run as a normal user (e.g service start/stop).
  4. Writing the environment to a file and rereading it in the elevated instance - because it's an ugly hack and I'd hope there's a cleaner option out there. And writing possibly sensitive information to a file is even worse than storing it temporarily in an environment variable.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

2025-01-19 16:51:48

以下是使用以下方法的概念证明:

  • 使powershell调用调用另一个,aux。 powershell 实例作为提升的目标进程。

  • 这允许外部 powershell 实例“烘焙”Set-Item 语句,这些语句重新创建调用者的环境变量(外部实例继承了这些变量,因此可以使用 Get-ChilItem Env: 枚举)到传递给 aux 的 -command 字符串中。实例,然后重新调用原始批处理文件。

警告:此解决方案盲目地重新创建提升进程中调用者进程中定义的所有环境变量 - 考虑进行预过滤,可能通过名称模式,例如通过共享前缀;例如,要将变量重新创建限制为名称以 foo 开头的变量,请将 Get-ChildItem Env: 替换为 Get-ChildItem Env:foo* 在下面的命令中。

@echo off & setlocal

:: Test if elevated.
net session 1>NUL 2>NUL && goto :ELEVATED 

:: Set sample env. vars. to pass to the elevated re-invocation.
set foo1=bar
set "foo2=none      done"
set foo3=3" of snow
:: " dummy comment to fix syntax highlighting
:: Helper variable to facilitate re-invocation.
set "thisBatchFilePath=%~f0"

:: Re-invoke with elevation, synchronously, reporting the exit
:: code of the elevated run.
:: Two sample arguments, ... and "quoted argument" are passed on re-invocation.
powershell -noprofile -command ^
  trap { [Console]::Error.WriteLine($_); exit -667 } ^
  exit ( ^
    Start-Process -Wait -PassThru -Verb RunAs powershell ^
      "\" -noprofile -command `\" $(Get-ChildItem Env: | ForEach-Object { 'Set-Item \\\"env:' + $_.Name + '\\\" \\\"' + $($_.Value -replace '\""', '`\\\""') + '\\\"; ' }) cmd /c '\`\"%thisBatchFilePath:'=''%\`\" ... \`\"quoted argument\`\" & exit'; exit `$LASTEXITCODE`\" \"" ^
  ).ExitCode 

echo -- Elevated re-invocation exited with %ERRORLEVEL%.

:: End of non-elevated part.
exit /b

:ELEVATED

echo Now running elevated...

echo -- Arguments received:
echo [%*]

echo -- Env. vars. whose names start with "foo":
set foo 

:: Determine the exit code to report.
set ec=5

echo -- Exiting with exit code %ec%...
:: Pause, so you can inspect the output before exiting.
pause
exit /b %ec%

注意:

  • trap { [Console]::Error.WriteLine($_); exit -667 } 处理用户拒绝提升提示的情况,这会导致 trap 语句捕获(使用 尝试 / 围绕 Start-Process 调用的 catch 语句也是一种选择,通常是更好的选择,但在这种情况下,trap 在语法上更容易) .

  • 指定传递参数(在 cmd /c '\`\"%thisBatchFilePath:'=''%\`\ 之后直接传递到(提升的)批处理文件重新调用的参数“ 上面部分):

    • 如果参数包含 ',则必须将它们加倍 ('')
    • 如果参数需要双引号,则必须将它们括在 '\`\"...\`\" (sic) 中,如 \`\" 带引号的参数所示上面的\`\"
  • cmd /c '<批处理文件>; &不幸的是,需要重新调用技术来确保稳健退出代码报告 - 请参阅此答案了解详细信息。


  • 重新调用批处理文件后需要显式 exit $LASTEXITCODE 语句,以使 PowerShell CLI 报告批处理文件报告的特定退出代码 - 没有该语句,任何非零值退出代码将映射到1。有关 PowerShell 中退出代码的全面讨论,请参阅此答案

Here's a proof of concept that uses the following approach:

  • Make the powershell call invoke another, aux. powershell instance as the elevated target process.

  • This allows the outer powershell instance to "bake" Set-Item statements that re-create the caller's environment variables (which the outer instance inherited, and which can therefore be enumerated with Get-ChilItem Env:) into the -command string passed to the aux. instance, followed by a re-invocation of the original batch file.

Caveat: This solution blindly recreates all environment variables defined in the caller's process in the elevated process - consider pre-filtering, possibly by name patterns, such as by a shared prefix; e.g., to limit variable re-creation to those whose names start with foo, replace Get-ChildItem Env: with Get-ChildItem Env:foo* in the command below.

@echo off & setlocal

:: Test if elevated.
net session 1>NUL 2>NUL && goto :ELEVATED 

:: Set sample env. vars. to pass to the elevated re-invocation.
set foo1=bar
set "foo2=none      done"
set foo3=3" of snow
:: " dummy comment to fix syntax highlighting
:: Helper variable to facilitate re-invocation.
set "thisBatchFilePath=%~f0"

:: Re-invoke with elevation, synchronously, reporting the exit
:: code of the elevated run.
:: Two sample arguments, ... and "quoted argument" are passed on re-invocation.
powershell -noprofile -command ^
  trap { [Console]::Error.WriteLine($_); exit -667 } ^
  exit ( ^
    Start-Process -Wait -PassThru -Verb RunAs powershell ^
      "\" -noprofile -command `\" $(Get-ChildItem Env: | ForEach-Object { 'Set-Item \\\"env:' + $_.Name + '\\\" \\\"' + $($_.Value -replace '\""', '`\\\""') + '\\\"; ' }) cmd /c '\`\"%thisBatchFilePath:'=''%\`\" ... \`\"quoted argument\`\" & exit'; exit `$LASTEXITCODE`\" \"" ^
  ).ExitCode 

echo -- Elevated re-invocation exited with %ERRORLEVEL%.

:: End of non-elevated part.
exit /b

:ELEVATED

echo Now running elevated...

echo -- Arguments received:
echo [%*]

echo -- Env. vars. whose names start with "foo":
set foo 

:: Determine the exit code to report.
set ec=5

echo -- Exiting with exit code %ec%...
:: Pause, so you can inspect the output before exiting.
pause
exit /b %ec%

Note:

  • trap { [Console]::Error.WriteLine($_); exit -667 } handles the case where the user declines the elevation prompt, which causes a statement-terminating error that the trap statement catches (using a try / catch statement around the Start-Process call is also an option, and usually the better choice, but in this case trap is syntactically easier).

  • Specifying pass-through arguments (arguments to pass directly to the re-invocation of the (elevated) batch file, after the cmd /c '\`\"%thisBatchFilePath:'=''%\`\" part above):

    • If arguments contain ', you must double them ('')
    • If arguments need double-quoting, you must enclose them in '\`\"...\`\" (sic), as shown with \`\"quoted argument\`\" above.
  • The cmd /c '<batch-file> & exit' re-invocation technique is required to ensure robust exit-code reporting, unfortunately - see this answer for details.

  • The explicit exit $LASTEXITCODE statement after the batch-file re-invocation is required to make the PowerShell CLI report the specific exit code reported by the batch file - without that, any nonzero exit code would be mapped to 1. See this answer for a comprehensive discussion of exit codes in PowerShell.

峩卟喜欢 2025-01-19 16:51:48

滚烫的解决方案

源自 mklement0 的工作概念证明和 Jeroen Mostert 的 Base64 建议,我用这个构建了一个解决方案方法:

  • 将批处理内部的数据通过管道传输到外部 Powershell。
  • 让它将管道数据转换为 Base64 字符串。
  • 它被传递到提升的 Powershell 的命令行中。
  • 这又将其转换回来并将其通过管道传输到新批次的实例中。

它更加灵活,因为您不受环境变量的限制(您基本上可以传递任何内容(基于文本)),并且不需要编辑 Powershell 命令来选择通过管道传输的内容。但它有一些限制 mklement0 的实现不会受到影响:

  • 包含换行符的环境变量将无法正确传递,并且可能会导致混乱(取决于 LF 之后的内容,请参阅 barz< /代码>)。
  • 目前,通过管道传输的每一行(第一行除外)都会在其前面添加一个空格(到目前为止我不知道如何解决这个问题)。这通常不是问题并且可以解决(fooDoubleQouting 是一个反例)。
  • 提升的实例不再像往常一样对控制台输入做出反应(请参阅注释)。

示例/测试批次:

@echo off & setlocal EnableDelayedExpansion

::# Test if elevated.
net session 1>NUL 2>NUL && goto :ELEVATED

(set LF=^
%=this line is empty=%
)
::# Set sample env. vars. to pass to the elevated instance.
set foo1=bar
set "foo2=none done"
set foo3=3" of snow
set "barz=  Line1!LF!  foo1=Surprise^! foo1 isn't '%foo1%' anymore. It was unintentionally overwritten."
set barts=1 " 3_consecutive_" "_before_EOL   
set "barl=' sdfs' ´´`` =43::523; \/{[]} 457wb457;; %%%^!2^!11^!^!"
::# ' dummy comment#1 to fix syntax highlighting.

::# Helper variable to facilitate re-invocation (in case %~f0 contains any single quotes).
set "_selfBat=%~f0"

::# DDE - so "!" don't get expanded anymore. Was only needed for "set barz=..."
setlocal DisableDelayedExpansion
::# print variables etc. to console before self invocation & elevation.
call :testPrint

::# Generate pipe input. Be aware of CMD's handicaps of whats allowed in a command block.
::# eg. "REM" is not allowed and neither is echoing an unescaped closing parenthesis: ")" -> "^^^)"
(
    echo[foo_Setting_one=extra-varval.
    set ^^"
    echo[bar_stuff=in between.^^^)^^^"
    set bar
    echo["fooDoubleQouting=testertest"
) | powershell.exe -nologo -noprofile -command ^
    trap { [Console]::Error.WriteLine($_); exit -667 } ^
    exit ( ^
        Start-Process -PassThru -Wait -WindowStyle Maximized -Verb RunAs 'powershell.exe' ^
            "\"-nol -nop -comm `\" $('Write-Output $([Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String(\\\"' + $([Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($(foreach ($i in $input) {\"$i`n\"})))) + '\\\")))') | cmd.exe '/D','/U','/T:4F','/S','/C',' \`\"%_selfBat:'=''%\`\" \`\"quoted argument\`\" nonQtdArg & exit'; exit `$LastExitCode `\" \"" ^
    ).exitCode

echo[
echo[ ---- Returned errorlevel is: %ERRORLEVEL%
pause
endlocal & endlocal & exit /b %ERRORLEVEL%


:testPrint
    echo[
    echo[ ---- WhiteSpaceTest: "%barts%"
    set foo
    set bar
    echo[
    set ^"
exit /B
::# " dummy comment#2 to fix syntax highlighting again.

:ELEVATED
setlocal DisableDelayedExpansion
    ::# Read and parse piped in data.
    ::# (with "delims" & "eol" truly defined as empty so every line is read as-is, even empty lines)
    for /F delims^=^ eol^= %%A in ('findstr.exe "^"') do (
        echo[ Parsing %%~A
        for /F "tokens=1,* delims=="eol^= %%B in ("%%~A") do (
            echo[   into "%%~B"
            echo[     equals "%%~C"
            ::# Convert the piped in data back into environment variables+values.
            set "%%~B=%%~C" 2>NUL
        )
        echo[
    )
    echo[-------- END PIPEREADING --------
    echo[-- Arguments received:
    echo[ [%*]

    call :testPrint

    set "ERR=42"
    echo[
    ::# to actually pause and/or wait for / react to user input(!) one needs to pipe in CON (console).
    <con set /P ERR=Enter arbitrary exitcode / errorlevel: 
endlocal & exit /B %ERR%

注释:

  • 请参阅 mklement0 的注释。
  • CMD /C ' &如果您在批处理文件中始终exit /b X,则不需要“exit”重新调用技术。然后用 &\`\"%_selfBat%\`\" 而不是 CMD /C ... & exit 就足够了(使用单独分隔的参数:'arg1','arg2')。
  • '/D','/T:4F', - 忽略 CMD 的注册表自动运行命令并将前景色/背景色设置为深红底白字。
  • echo[ 而不是 echo 更安全、更快(cmd 不需要搜索名为 echo.* 的实际可执行文件)。
  • 对于需要用户交互的任何操作(例如,pauseset /P ...),在提升的实例中需要 。如果没有 ,现在为空(?) 在标准输入 (pipe#0) 中通过管道传输仍然会将 nul(?) 传递给任何要求的内容它(我的假设)。我确信有一种方法可以从管道中拯救 stdin 并将其重新连接到 con (也许是某种break通过 从这里)。
  • barl 的反引号被破坏了。

逃离地狱

这里是动态的中间和内部命令行来显示正在发生的事情并消除一些逃避的魔法:

powershell.exe -nol -nop -comm "Write-Output $([Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String(\"<<BASE64_BLOB>>\"))) | cmd.exe '/D','/U','/T:4F','/S','/C',' \"<<path\this.cmd_withEscaped'>>\" \"quoted argument\" nonQtdArg & exit'; exit $LastExitCode"

即使在我的默认环境中,命令行也大约有 5kB 大(!),这要归功于<>

cmd.exe /D /U /T:4F /S /C " "<<path\this.cmd_withNormal'>>" "quoted argument" nonQtdArg & exit"

A piping hot solution

Derived from mklement0's working proof of concept and Jeroen Mostert's Base64 suggestion I've built a solution with this approach:

  • Pipe in data from inside the batch to the outer Powershell.
  • Let it convert the piped data into a Base64 string.
  • which is passed on into the command line of the elevated Powershell.
  • which in turn converts it back and pipes it into the new batch's instance.

It is more flexible because you're not limited to environment variables (you can essentially pass on anything (text based)) and the Powershell command doesn't need to be edited to choose what gets piped through. But it has a few limitations mklement0's implementation doesn't suffer from:

  • Environment variables containing newlines will not be passed on correctly and can cause chaos (depending on what comes after the LF, see barz).
  • Currently every line piped through (except for the first one) gets one whitespace prepended to it (so far I couldn't figure out how to fix that). It's usually not a problem and can be worked around (fooDoubleQouting is a negative example).
  • the elevated instance doesn't react to console input as usual any more (see notes).

Example / test batch:

@echo off & setlocal EnableDelayedExpansion

::# Test if elevated.
net session 1>NUL 2>NUL && goto :ELEVATED

(set LF=^
%=this line is empty=%
)
::# Set sample env. vars. to pass to the elevated instance.
set foo1=bar
set "foo2=none done"
set foo3=3" of snow
set "barz=  Line1!LF!  foo1=Surprise^! foo1 isn't '%foo1%' anymore. It was unintentionally overwritten."
set barts=1 " 3_consecutive_" "_before_EOL   
set "barl=' sdfs' ´´`` =43::523; \/{[]} 457wb457;; %%%^!2^!11^!^!"
::# ' dummy comment#1 to fix syntax highlighting.

::# Helper variable to facilitate re-invocation (in case %~f0 contains any single quotes).
set "_selfBat=%~f0"

::# DDE - so "!" don't get expanded anymore. Was only needed for "set barz=..."
setlocal DisableDelayedExpansion
::# print variables etc. to console before self invocation & elevation.
call :testPrint

::# Generate pipe input. Be aware of CMD's handicaps of whats allowed in a command block.
::# eg. "REM" is not allowed and neither is echoing an unescaped closing parenthesis: ")" -> "^^^)"
(
    echo[foo_Setting_one=extra-varval.
    set ^^"
    echo[bar_stuff=in between.^^^)^^^"
    set bar
    echo["fooDoubleQouting=testertest"
) | powershell.exe -nologo -noprofile -command ^
    trap { [Console]::Error.WriteLine($_); exit -667 } ^
    exit ( ^
        Start-Process -PassThru -Wait -WindowStyle Maximized -Verb RunAs 'powershell.exe' ^
            "\"-nol -nop -comm `\" $('Write-Output $([Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String(\\\"' + $([Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($(foreach ($i in $input) {\"$i`n\"})))) + '\\\")))') | cmd.exe '/D','/U','/T:4F','/S','/C',' \`\"%_selfBat:'=''%\`\" \`\"quoted argument\`\" nonQtdArg & exit'; exit `$LastExitCode `\" \"" ^
    ).exitCode

echo[
echo[ ---- Returned errorlevel is: %ERRORLEVEL%
pause
endlocal & endlocal & exit /b %ERRORLEVEL%


:testPrint
    echo[
    echo[ ---- WhiteSpaceTest: "%barts%"
    set foo
    set bar
    echo[
    set ^"
exit /B
::# " dummy comment#2 to fix syntax highlighting again.

:ELEVATED
setlocal DisableDelayedExpansion
    ::# Read and parse piped in data.
    ::# (with "delims" & "eol" truly defined as empty so every line is read as-is, even empty lines)
    for /F delims^=^ eol^= %%A in ('findstr.exe "^"') do (
        echo[ Parsing %%~A
        for /F "tokens=1,* delims=="eol^= %%B in ("%%~A") do (
            echo[   into "%%~B"
            echo[     equals "%%~C"
            ::# Convert the piped in data back into environment variables+values.
            set "%%~B=%%~C" 2>NUL
        )
        echo[
    )
    echo[-------- END PIPEREADING --------
    echo[-- Arguments received:
    echo[ [%*]

    call :testPrint

    set "ERR=42"
    echo[
    ::# to actually pause and/or wait for / react to user input(!) one needs to pipe in CON (console).
    <con set /P ERR=Enter arbitrary exitcode / errorlevel: 
endlocal & exit /B %ERR%

Notes:

  • see mklement0's notes.
  • The CMD /C '<batch-file_withEscaped'> & exit' re-invocation technique isn't required if you consistently exit /b X in your batch file. Then &\`\"%_selfBat%\`\" instead of CMD /C ... & exit is enough (with separately separated arguments: 'arg1','arg2').
  • '/D','/T:4F', - Ignore CMD's registry AutoRun commands and set fore-/background colors to white on dark red.
  • echo[ instead of echo is safer and quicker (cmd doesn't need to search for actual executables named echo.*).
  • <con is required in the elevated instance for anything needing user interaction (eg. pause or set /P ...). Without <con the now empty(?) piped in standard input (pipe#0) still delivers nul(?) to anything asking for it (my assumption). I'm sure there is a way too rescue stdin from the pipe and reattach it to con (maybe some kinde of breakthrou from in here).
  • barl's backticks get mangled.

Escaping hell

Here are the dynamic middle and inner command lines to show whats going on and shave away some escaping magic:

powershell.exe -nol -nop -comm "Write-Output $([Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String(\"<<BASE64_BLOB>>\"))) | cmd.exe '/D','/U','/T:4F','/S','/C',' \"<<path\this.cmd_withEscaped'>>\" \"quoted argument\" nonQtdArg & exit'; exit $LastExitCode"

Even with just my default environment that command line is somewhere around 5kB big(!), thanks to the <<BASE64_BLOB>>.

cmd.exe /D /U /T:4F /S /C " "<<path\this.cmd_withNormal'>>" "quoted argument" nonQtdArg & exit"
揽月 2025-01-19 16:51:48

所以,是的,就像已经说过的那样,由于安全隐患,环境并不是按照设计从一个用户传递到另一个用户的。这并不意味着它不能完成,即使这不是你“应该”做的事情。虽然我确实认为你应该研究一下你真正想要实现的目标,但我绝对讨厌人们告诉你你“通常”应该做什么而不回答实际问题的类型。所以我在这里给你两种选择。

“传递”环境

这里有几个选项

  1. 从提升的子进程中,使用 NtQueryInformationProcessReadProcessMemory API。

    然后使用 WriteProcessMemory 或者像平常一样设置它们。您只需使用 Powershell 即可实现此目的,但需要添加一些 C# 代码来调用所需的 API 函数。

    在我的 Enable-Privilege.ps1 示例中,您可以看到如何实现 NtQueryInformationProcess PowerShell并操作父进程(我编写它的目的是为了修改内部的特权令牌自身/父进程/任何进程)。您实际上想要使用它的一部分,因为您需要启用SeDebugPrivilege才能操作其他进程的内存。

    这个选项可能是最“干净”和最强大的解决方案,没有任何立即明显的警告。有关详细信息,请参阅此 codeproject 文章:读取以下环境字符串远程进程

  2. 在未提升的父进程内,迭代所有环境变量并将它们作为字符串写入单个环境变量。然后在生成提升的子进程时将该单个环境值作为参数传递,您可以在其中解析该字符串并将这些环境值写回。不过,您可能会遇到与此处选项 3 相同的警告。

  3. 将变量从父级传递到子级,就像这里已经提出的那样。这里的问题是批处理器非常挑剔,并且解析和转义的规则非常混乱,因此使用此选项很可能会遇到特殊字符和其他类似警告的问题。

  4. 使用内核模式驱动程序,用提升的进程覆盖未提升进程的安全令牌,完成后写回原始令牌。从表面上看,这似乎是完美的解决方案,因为您实际上可以留在以前未提升的进程中并保留其环境而不更改上下文,只有安全上下文会被替换。与在内核模式中一样,您可以修改所有内容,安全令牌是内核内存中的简单内存结构,您可以更改它们。这种方法的问题在于它完全绕过了 Windows 安全模型,因为它应该不可能更改现有进程的安全令牌。因为它应该是“不可能的”,所以它深入到未记录的领域,并且在内核内部,如果你不知道自己在做什么,你可以很容易地破坏东西,所以这绝对是“高级”选项(即使这个特定的选项)事情并不太复杂,基本上只是写一些内存)。由于这是您不应该做的事情,因此有可能会破坏某些内容,因为 Windows 不希望进程突然具有不同的安全上下文。话虽这么说,我过去使用这种方法没有任何问题。尽管安全设计发生任何变化,但未来它可能会被破坏。您还需要启用 testsigning (又名禁用驱动程序签名强制),对您的驱动程序进行数字签名或使用其他方法来绕过此要求(例如通过管理程序或漏洞利用),但这超出了本讨论的范围回答。

achsually版本

“因为规避问题不是正确的解决方案。”

在这种情况下,我会这样做。由于您的问题性质如此,因此不存在简单的解决方案,因为它不受设计支持。很难提出具体的解决方案,因为缺乏您实际上想要实现的目标的信息。

我将尝试从一般意义上涵盖这一点。首先要考虑一下您实际上想要实现的目标是什么部分。您需要执行哪些需要抬高的操作?有多种方法可以以受支持的方式实现任何目标。

示例:

  • 对于您需要读取/写入/修改的任何内容,您可以更改目标(而不是)的安全设置。这意味着,假设您需要访问特定的注册表项、服务、文件、文件夹等,您可以简单地修改目标的ACL以允许(即用户)执行您需要的任何操作。例如,如果您需要修改单个服务,您可以仅为该单个进程添加启动/停止/修改权限。

  • 如果您需要的是特定于操作类型而不是特定目标,您可以添加所需的 “用户”组的特权。或者创建一个具有所需权限的新组,然后将用户添加到该组。

  • 如果您希望更精细地控制可以/不可以执行的操作和/或特定的操作,您可以编写一个简单的程序并将其作为提升的服务运行。然后,您可以告诉该服务从未提升的批处理脚本中执行所需的操作,因此不需要请求提升和生成新进程。您可以简单地从批处理中执行my-service.exe do-the-thing,然后 my-service 将执行您需要的操作。

  • 您也可以始终在脚本开头请求提升权限,但很明显您不想使用完全管理员权限执行此操作,因此您可以为此目的创建一个新用户,并将其添加到新用户中具有您所需权限的组。请注意,如果不诉诸上述内核模式“黑客”,您无法为用户即时添加新权限,只能启用/禁用/删除现有的。您可以做的是根据您的需要预先添加它们,但这需要在流程开始之前进行。


So yeah, like has been said, the environment is not meant to be passed from a user to another, by design, because of the security implications. It doesn't mean that it can't be done, even if it's not something you're "supposed" to do. While I do think you should look into what do you actually want to achieve, I absolutely hate the type of answers where people tell you what you achsually "should" do and not answer the actual question at all. So I'm giving you both options here.

"Passing" the environment

You have several options here

  1. From the elevated child process, read the environment variables from the unelevated caller parent process' memory using the NtQueryInformationProcess and ReadProcessMemory APIs.

    Then either overwrite the variables on the target process (in your case, the current process) with WriteProcessMemory or just set them as you normally would. You can achieve this with only Powershell, albeit you need to add some C# code to call the required API functions.

    Here in my Enable-Privilege.ps1 example you can see how to implement NtQueryInformationProcess in PowerShell and manipulate a parent process (I wrote it for the purpose of modifying privilege tokens inside self/parent/any process). You would actually want to use a part of it because you need to enable SeDebugPrivilege to be able to manipulate memory of other processes.

    This option would probably be most "clean" and robust solution without any instantly obvious caveats. See this codeproject article for more information: Read Environment Strings of Remote Process

  2. Inside the unelevated parent process, iterate through all the environment variables and write them as a string to a single environment variable. Then pass that single environment value as an argument when spawning the elevated child process, where you can parse that string and write those environment values back. You would likely run to the same caveats as option 3 here though.

  3. Pipe the variables from the parent to the child, like has been proposed here already. The problem here is that the batch processor is really finicky and the rules of parsing and escaping are super janky, so it's very likely you would run to issues with special characters and other similar caveats with this option.

  4. Using a kernel-mode driver, overwrite the security token of the unelevated process with a elevated one, writing back the original token after you are done. On the surface this would seem like the perfect solution, since you could actually stay inside the previously-unelevated process and retain it's environment without changing context, only the security context would be replaced. As in kernel-mode you can modify everything, the security tokens are simple memory structs inside kernel memory which you can change. The problem with this approach is that it completely bypasses the windows security model, as it's supposed to be impossible to change the security token of an existing process. Because it's supposed to be "impossible", it goes deep into undocumented territory and inside the kernel you can easily break stuff if you don't know what you're doing, so this is definitely the "advanced" option (even though this particular thing is not too complicated, it's basically just writing some memory). As it's something you're not supposed to be doing, there is a possibility it breaks something since Windows does not expect a process to suddenly have a different security context. That being said, I've used this approach with no problems in the past. It could be broken in the future though by any change in the security design. You would also need to either enable testsigning (aka Disable Driver Signature Enforcement), digitally sign your driver or use some other method to bypass this requirement (f.ex. through a hypervisor or an exploit), but that is out of the scope of this answer.

The achsually version

"because circumventing the problem is no proper solution."

In this case, I would do exactly that. Since your problem is of such nature that a easy solution for it doesn't exist, because it's not supported by design. It's hard to propose a specific solution since the lack of information of what it is you're actually trying to achieve here.

I'm gonna try to cover this in a general sense. First is to think about the what it is you're actually trying to achieve here part. What are the operations you need to do which require elevation? There would be multiple ways to achieve whatever it is in a supported fashion.

Examples:

  • For whatever you need to read/write/modify, you could change the security security settings of the target (instead of the source). Meaning that let's say you need to access a specific registry key, service, file, folder, whatever, you could simply modify the ACL of the target to allow the source (i.e. the user) to do whatever operation you need. If you need to modify a single service for example, you could add the start/stop/modify right for only that single process.

  • If the thing you need is specific to the types of operations rather than specific targets, you could add the required privileges to the "Users" group. Or make a new group with the required privileges, and then add the user to that group.

  • If you want more granular control on what can/can't be done and/or the operations are specific, you could write a simple program and run it as a elevated service. Then you could just tell that service to do the required operations from the unelevated batch script, so no requesting elevation and spawning new process would be needed. You could simply do my-service.exe do-the-thing from batch, and that my-service would do the operation you need.

  • You could also always ask for the elevation in beginning of the script, but as it's clear you don't want to do this with full administrator rights, you could create a new user for just this purpose which you add to a new group for it which has the required privileges you need. Note that without resorting to the aforementioned kernel-mode ""hacks"", you cannot add new privileges for a user on-the-fly, only enable/disable/remove existing ones. What you can do though is add them beforehand based on what you need, but that will need to happen before the process is started.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文