tech.guitarrapc.cóm

Technical updates

PowerShell でsudo っぽいものを内蔵した関数を作る

時々思い出したようにPowerShellの記事を書いてみます。

スクリプトでよくあるのが、sudoで実行時に権限があるスクリプトの許可をしたいというケースです。

Windowsは組み込みsudoがないので面倒でしたが、現状ならscoopでsudoをインストールするといいです。

https://scoop.sh/

scoop install sudo

これでsudo ./your_script.ps1とできるので特権が必要なときに、必要な権限を渡すことができます。

さて今回の記事は、Windowsにおいて実行中のスクリプトや関数にて特権が必要な場合に、sudoを使わずにUACダイアログを出して昇格したPowerShellで同関数を実行し直してほしいというケースです。

通常の特権昇格フロー + Windows Diffender操作のため利用には注意してください。

この2つを自動化できるのは運用で便利ですが、誤った利用は技術を良くない方向に追い込みます。 チーム内での潤滑な運用のための利用に留めることを推奨します。

概要

悪用禁止だけど、チーム内で使うには便利です。 特に、PowerShellのExecution Policyやダブルクリック問題はだいたいこれで解決するのが定番です。(chocolateyやscoopもこの手法)

サンプル

今回は、Windows Defenderによってdotnetのビルドが遅いので除外して対応しようという記事を使ってやってみます。

http://baba-s.hatenablog.com/entry/2019/05/07/090000

サンプルスクリプトを置いておきます。

https://github.com/guitarrapc/PowerShellUtil/tree/master/WindowsDefender

Windows PowerShellで次のコマンドを実行すると、Windows Defenderのスキャン除外パスに指定したパス + Visual Studioのパスが入り、除外プロセスにVisual Studioやdotnet、msbuildが入ります。

実行前に十分に気をつけてください

iex  (new-object net.webclient).downloadstring('https://raw.githubusercontent.com/guitarrapc/PowerShellUtil/master/WindowsDefender/remote_exec.ps1')

Windows PowerShellを特権のない状態で起動して実行すると、昇格するか聞かれます。

UAC昇格が聞かれる

yを押した時だけ、UACダイアログがでて関数がそこで再実行されます。

特権に昇格されたPowerShellで関数が再実行される

実行結果

仕組み

処理の本体は、Add-DefenderExclusionForDevEnvです。

https://github.com/guitarrapc/PowerShellUtil/blob/master/WindowsDefender/Add-DefenderExclusionForDevEnv.ps1

キーはここです。

https://gist.github.com/guitarrapc/96f6259724b723d07ed34f81564fcf99

自分が特権で実行されているかは、これで検出できます。

$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
if (!$currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {

自分の関数名を、自動変数 $MyInvocationから取ります。

$me = $MyInvocation.MyCommand

自分の関数の定義を、Get-Commandから取ります。

$myDefinition = (Get-Command $me).Definition

これで関数文字列が生成できます。

$myfunction = "function $me { $myDefinition }"

新規プロセスでPowerShell.exeを実行するときに、実行するコマンド文字列を組み立てます。 今のパスに移動します。

$cd = (Get-Location).Path
$commands = "Set-Location $cd; $myfunction; Write-Host 'Running $me'; $me; Pause"

さて、生成したコマンド文字列は関数の改行が含まれており、このままではPowerShell.exeの -Command引数に渡せません。 そこで、バイナリにしてBase64文字列を -EncodedCommandにわたすことで解釈させます。

このあたりは、PowerShellを使ったワーム攻撃でもよく使われる手法です。

$bytes = [System.Text.Encoding]::Unicode.GetBytes($commands)
$encode = [Convert]::ToBase64String($bytes)
$argumentList = "-NoProfile","-EncodedCommand", $encode

あとは、PowerShellを特権で起動するため、-Verb RunAsを指定して先程の引数を食わせます。 PowerShell.exeの実行時に、-Waitをつけることで、起動したPowerShell.exeが自動で閉じません。

$p = Start-Process -Verb RunAs powershell.exe -ArgumentList $argumentList -Wait -PassThru

おわりに

最近のほげもげみてると、こういう記事書くのは心配です。 ただ、標準で用意されている方法を用途を限定して使うことまで阻害されるのは望ましくないものです。

攻撃にすでに利用もされている方法でもあるので、UACは自分でプロセスを起動させるほうがパブリックにはいいです。(ChocolateyやScoopがそう)

どうか正しく使われますように。