ジョジョ第三部の途中で亡くなっていて、今描いているのは二代目ではないかと思っている。
https://twitter.com/zetamatta/status/877691788986679296
ちょっと前に「有名漫画家が事故で死んで、編集者とチーフアシスタントがその死を隠蔽しつつ、 続きを描いて、その巨額の財産を奪おうとする」という漫画があったなぁ。名前忘れたが
ジョジョ第三部の途中で亡くなっていて、今描いているのは二代目ではないかと思っている。
https://twitter.com/zetamatta/status/877691788986679296
ちょっと前に「有名漫画家が事故で死んで、編集者とチーフアシスタントがその死を隠蔽しつつ、 続きを描いて、その巨額の財産を奪おうとする」という漫画があったなぁ。名前忘れたが
suというと、通常は UNIX(Linux)で、ログアウトせずにユーザを切り替えるコマンドを指し、主に root (管理ユーザ)に切り替える用途に使われることが多い。そして、sudo は1コマンドだけユーザを切り替えて実行するコマンドだ。
最近、Windows でも su(sudo)的なコマンドが現れたが、これは常に管理ユーザで使われがちの Windows においては、ユーザを切り替えるというよりも、管理者権限を獲得するためのコマンドという位置づけとなっていることが多い。
本文書では、管理者権限を得てコマンドを実行する操作について、手法を紹介してみたい。
advapi32.dll の OpenProcessToken
と GetTokenInformation
という API を組み合わせてチェックするという方法がある。詳しく解説しているページはこちら
自分でも Go 言語で実装してみた。(nyagos'Lua からはnyagos.elevated()
で利用できる)
「net session >nul 2>&1
」は管理者権限でないと ERRORLEVEL=2 で失敗するということを利用して判別できる。このテクニックは「Office 365のデスクトップアプリケーションをバッチファイルで修復」で用いられていたが、なかなか手軽でよいと思われる。
Windows の API で管理者権限を得るのは ShellExecute になる。
Windows ではプロセスを起動する有名どころの API としては CreateProcess と ShellExecute の2種類がある。CreateProcess は「普通」にプロセスを起動する API で、パラメータがやたら多くて難しい(← いいすぎ)他は特筆すべきことはない。たいていの言語の子プロセス起動ライブラリはこれを使用している。一方、ShellExecute はデスクトップからアイコンをクリックしたのと同じような形でコマンドを実行するような API である。
ShellExecute のパラメータは CreateProcess よりむしろ簡単で、次のような形となる。
https://msdn.microsoft.com/ja-jp/library/cc422072.aspx より引用:
HINSTANCE ShellExecute( HWND hwnd, // 親ウィンドウのハンドル LPCTSTR lpVerb, // 操作 LPCTSTR lpFile, // 操作対象のファイル LPCTSTR lpParameters, // 操作のパラメータ LPCTSTR lpDirectory, // 既定のディレクトリ INT nShowCmd // 表示状態 );
lpVerb に入れる文字列で L"open"
を与えると、普通の起動になるが、それを L"runas"
と変えると、管理者として実行となる。これを利用した su/sudo はかなり多く、NYAGOS に内蔵している su / sudo もこれである
Dim pinfo1 As New System.Diagnostics.ProcessStartInfo() pinfo1.Verb = "RunAs" '*** This is for UAC mode *** ' 中略 Dim process As System.Diagnostics.Process = System.Diagnostics.Process.Start(pinfo1)
これを利用した su として以前 wouldyou.exeというものを作ったが、後述の powershell を使った方法の方で用が足りるので、今はほとんど使っていない。
.NET Framework と同じ方法がとれるが、COM を使った方法の方がお手軽。COM を使っているので、当然ながら JScript/VBScript でも同じことができる。が、起動ロゴが出たりするので、やはり、PowerShell が最適解だろう。
@setlocal @if not "%1" == "" @set "ARG=/c %*" powershell "(New-Object -Com Shell.Application).ShellExecute('cmd',$Env:ARG,'','runas')" @endlocal
これのすごくよいところは1行コピペしてしまえば、それだけで用が足りる点だ (バッチファイル1枚の中で完結する)。
これらを利用して、su/sudo 的なものは比較的簡単に実現できる。自分はこれで満足していたが、コマンドプロンプトで実行する際、管理者権限を得たコマンドラインは別のコンソール窓になってしまうという点があり、これを使いにくく感じる向きも多いようだ。
ということで、それを解決した sudo も登場している。
実装の仕方を見ると、管理者権限で動くプロセスを別に立ち上げて、そちらとパイプラインを結んで、それまで使っていたコンソールに管理者権限で実行しているプロセスの標準入出力を引用しているようだ(← あまりソースを真剣に読んでいない)
なお、これらを NYAGOS で動かしたい時は NYAGOS内蔵の sudo
を無効にするため、
~\.nyagos
にnyagos.alias.sudo="sudo.exe"
もしくは
~\_nyagos
に alias sudo=sudo.exe
としよう(.exe
をつければ内蔵コマンドとみなされない)。nyagos の sudo を使わないのは、それが新しいコンソールを起動してしまうためだ(一応、廃止も検討している)
以上
コマンドプロンプト向けの expect を Go 言語で作った。
except()
関数は、本家だと標準出力・標準エラーを監視するが、こちらは「現在カーソルがある行とその上の行」を0.1秒間隔で監視させている
send()
関数は、本家だと標準入力に文字列を流し込むが、こちらはキーボードが叩かれたのコンソールイベントを発生させている実装した関数は spawn
、expect
、send
だけなんだけど、Lua なのでいろいろ柔軟なことが出来る。
if spawn([[c:\Program Files\Git\usr\bin\ssh.exe]],"foo@example.com") then expect("password:") send("PASSWORD\r") expect("~]$") send("exit\r") end
使ってもらえるシーンないな!(まだ技術の無駄遣い)
「Linux なら expect というツールがあるけど、Windows の場合、どうしたらいいんだろう」などと思って、API を調べていたら、WriteConsoleInput
などというコンソールイベントを「捏造」できる API があるのを発見。
ちょっと、ツールを作ってみた。
package main import ( "os" "syscall" "unsafe" ) var kernel32 = syscall.NewLazyDLL("kernel32") var writeConsoleInput = kernel32.NewProc("WriteConsoleInputW") type inputRecordT struct { eventType uint16 _ uint16 bKeyDown int32 wRepeartCount uint16 wVirtualKeyCode uint16 wVirtualScanCode uint16 unicodeChar uint16 dwControlKeyState uint32 } type Handle syscall.Handle func NewHandle() (Handle, error) { handle, err := syscall.Open("CONIN$", syscall.O_RDWR, 0) return Handle(handle), err } const ( KEY_EVENT = 1 ) func (handle Handle) WriteRune(c rune) uint32 { records := []inputRecordT{ inputRecordT{ eventType: KEY_EVENT, bKeyDown: 1, unicodeChar: uint16(c), dwControlKeyState: 0, }, inputRecordT{ eventType: KEY_EVENT, bKeyDown: 0, unicodeChar: uint16(c), dwControlKeyState: 0, }, } var count uint32 writeConsoleInput.Call(uintptr(handle), uintptr(unsafe.Pointer(&records[0])), 2, uintptr(unsafe.Pointer(&count))) return count } func (handle Handle) WriteString(s string) { for _, c := range s { handle.WriteRune(c) } } func main() { console, err := NewHandle() if err != nil { println(err.Error()) return } for _, s := range os.Args[1:] { console.WriteString(s) console.WriteRune('\r') } }
これをビルドして typekeyas.exe というコマンドを作って「typekeyas "dir /w"
」などと打つと、あたかも直後にコマンドプロンプトに対して自分が「dir /w」と打ったかのようにディレクトリが表示される(いや、引数の中で打ってはいるんだけど)
これを使ってパスワードを打たせてみよう(以下、一部伏字)
$ typekeyas (PASSWORD) & "c:\Program Files\Git\usr\bin\ssh.exe" XXXXX@XXXXX.org XXXXX@XXXXX.org's password: Last login: Wed Jun 7 22:09:15 2017 from XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX FreeBSD 9.1-RELEASE-p24 (SAKURA17) #0: Thu Feb 5 10:03:29 JST 2015 Welcome to FreeBSD!
nyagos の場合はコマンド区切り文字の「&
」を「;
」にすればよいが。。。
これパスワードがヒストリに残ってしまうからすごく危険なんだな!
で、「これ画面の出力も拾えれば、完全な expect が出来るのでは?」とか思ったが、最初の XXXXX@XXXXX.org's password
という行だけに限って標準出力・標準エラー出力ともに出ていない(他は出てる)。コンソール(CONOUT$
)に直接出しているんでしょうかね…
最新の External Editor は GitHub からダウンロードできるようです。
Thunderbird 66 向け(v2.0.0-beta)と、Thunderbird 60 向け(v1.0.5-rc0)向けは違うので、ご注意ください。
エディターが起動していないのに、エディターの終了待ちでハングしてしまう。 どうやら Thunderbird が52へアップデートしたタイミングで、動かなくなったアドオンがいろいろ出ているようだ。 (参考:Thunderbird 52.0 Release - とりかごとなり。)
ググってみたところ、External Editor のサイトの掲示板( http://globs.org/articles.php?lng=en&pg=2 )に下記の書き込みがあった。
どうやら、原作者ではなく、ユーザで修正版を作った方がいらっしゃるようだ。ありがたや。
インストールされている External Editor を削除して、リンク先の exteditor_tb52_v101.xpi を アドオン画面にドラッグ&ドロップしたところ、 External Editor が前のように起動されるようになった。よしよし
2023.05.01時点で、試用版 Windows10 のページの URL はリンク切れになってしまっているようです。新ページは未確認です(Windows11 への移行を促すため、削除されたのかもしれません)
OVAイメージ(4GB)をダウンロードできる。 このイメージは一度立ち上がると、90日後に使えなくなるが、立ち上げる前の状態のスナップショットに戻せば、何回でも使える(らしい:未検証)。 ただ、WindowsUpdate の手間暇を考えると、都度、ダウンロードしなおした方がよいかもしれない。
わたしの環境では VirtualBox が「Windows 7 または Windows Vista でプログラムをインストールしようとすると、"Windows インストーラー サービスにアクセスできませんでした」というエラーが発生して、インストールが止まってしまった。 この場合の対処法は Microsoft のサポートサイトに記載されている。
<このあたりでスナップショットを取っておいた方がよいと思われる>
<ここまでが結構な手間なのでスナップショットを取っておいた方がよいと思われる>
以上
こんな感じに
- "../completion" - "../shell" + "github.com/zetamatta/nyagos/completion" + "github.com/zetamatta/nyagos/shell"
これにより:
go get github.com/zetamatta/nyagos
で全ソースをダウンロードできなかった。
git clone http://github.com/zetamatta/nyagos/
は可能)が解消する。そのかわり
$GOPATH/src/github.com/zetamatta/nyagos
と一意になってしまう。
という問題が発生してしまう。
いろいろと検討した結果、とりあえず現行の自分のビルド環境は次のようにすることにした。
~/go/nyagos
から ~/go/src/github.com/zetamatta/nyagos
に変更 (ここでamd64用ビルドする)
lnk ~/go/src/github.com/zetamatta/nyagos ~
でショートカットを張る~/go/386-nyagos
のまま
~/go/src/github.com/zetamatta/nyagos
になってしまうので、ソースの編集はこちらではできないでいこうと思う。
将来的には ~/go/src/github.com/zetamatta/nyagos
のいたまま amd64 も 386 もビルドできるようにしたい。
そのためには、今 ~/go/src/github.com/zetamatta/nyagos
直下にある lua53.dll を適当なサブフォルダーに移動させなくてはいけない。
でも、lua53.dll って git に登録していないバイナリファイルなので、移動させるとなるとソースからビルドしてくれているユーザにそれをアナウンスしなくちゃ
いけないんですよね…むーん。
lua53.dll も git に登録しちうという手もあるが、そうすると git clone
とか go get
でバイナリファイルがダウンロードされるので、
アンチウィルスにひっかかってしまう可能性が出てきてしまうという…
将来的に $GOPATH 以下でも、相対パスの import ができるようにならないですかね (これ、なぜできないようにしたんでしょうね)
「386 と amd64 で $GOPATH を分ける」という方法を思いついた。いずれやってみよう