プロフィール

髭山髭人(ひげひと)

自分の書いた記事が、一人でも誰かの役に立てば...
活動信条の一つとして「貴方のメモは、誰かのヒント」というのがあります。

このサイトについて

本家HP packetroom.net から切り離した いわゆる技術メモ用のブログで、無料レンタルサーバーにて運用しています。広告表示はその義務なのでご容赦。
XREA さんには長年お世話になっています

ブラウザ上のurlからexeを(引数付きで)実行起動したい

やりたい事 + 概要

ブラウザから、.exe もといアプリケーションを起動させたかった時のメモ記事です。

↓ こういうのをやりたい (やった)

<!-- xxxx 部分は差し替えてください -->
<a href="xxxx:,c:\Windows\explorer.exe,c:\windows\">C:\Windows\ フォルダをエクスプローラで開く</a>
<a href="xxxx:,c:\Windows\notepad.exe,d:\test.txt">notepad.exe に D:\test.txt を渡して開く</a>
<a href="xxxx:,C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe">Edge起動</a>
  • 筆者環境
    • Win10 x64
    • GoogleChrome
  • 常套句
    • やるなら自己責任で ☆彡
  • 補足
    • JavaScript 不要
    • file:/// スキーム(ローカル)で開いている .html を想定

実装手順

xxxx: プロトコルで起動する機構を実現するための 4ステップメニューです😋

1. 独自スキーム名を決める

<a href="xxxx:,c:\Windows\notepad.exe"> における xxxx の部分を決めてください。

命名ルールは以下(多分)

  • レジストリ HKEY_CLASSES_ROOT\ 配下のキー名と被らないようにする
    ※ 他に 予約済みの名前 もあるらしいので念の為
  • アンダーバー _ は使えないっぽい。ドット . はOK
    • OK : foo-bar
    • OK : foo.bar
    • NG : foo_bar
  • url から始まる名前もNG?
    個人的に url-launcher で名付けてみたものの、動作せず。
  • 命名規則的に全て小文字が一般的?
    • 一応、大文字での起動も確認してはいます

=== 以降、出てくるスキーム名 xxxx の部分を、全て独自の物に置き換えて進めてください ===

2. .vbs を作成 & 配置

  • 以下のコードを .vbs で作成
    念のため、テキスト形式を utf-8 ではなく Shift-jis とした作成を推奨?
  • 本例では D:\launcher.vbs への仮配置としました
    .vbsの位置や名前を変更する場合、レジストリ側も編集する必要が出てきます(※後述)
  • この .vbs 内ではスキーム名を扱わないので、書き換え不要です
    xxxx: の部分が出てこない/扱わないので原則コピペでOK、という意
Dim arg_count: arg_count = WScript.Arguments.Count

If arg_count = 0 Then
    WScript.Quit
End If

Dim arg0: arg0 = Wscript.Arguments(0)
Dim paramArr: paramArr = Split(arg0, ",")
DIm paramCount: paramCount = Ubound(paramArr)+1

If paramCount <= 1 Then
    WScript.Quit
End If

Dim prm1: prm1 = DecodeUri(paramArr(1))
Dim WshShell
Set WshShell = WScript.CreateObject("WScript.Shell")

If paramCount = 2 Then
    Dim RunCmd: RunCmd = """" & prm1 & """"
    WshShell.Run RunCmd
ElseIf paramCount = 3 Then
    Dim prm2: prm2 = DecodeUri(paramArr(2))
    Dim RunCmd2: RunCmd2 = """" & prm1 & """" & " " & prm2
    WshShell.Run RunCmd2
ElseIf paramCount >= 4 Then
' ,記号を使ったコマンドが含まれていると想定し、連結して処理を通す
    Dim result: result = ""
    For i = 2 To Ubound(paramArr)
        If i > 2 Then
            result = result & ","
        End If
        result = result & paramArr(i)
    Next
    prm2 = DecodeUri(result)
    RunCmd2 = prm1 & " " & prm2 
    WshShell.Run RunCmd2
End If

Function DecodeUri(encodedString)
    Dim objEncoder
    Set objEncoder = CreateObject("ScriptControl")
    objEncoder.Language = "JScript"
    objEncoder.AddCode "function decodeUri(encodedString) { return decodeURIComponent(encodedString); }"
    DecodeUri = objEncoder.Run("decodeUri", encodedString)
End Function

3. .reg を作成 & 実行してレジストリに登録

  • .txt から手付で .reg を作る場合は保存時の文字コードに注意
    • UTF-16 LE でないと .reg にしたとき弾かれました
    • Windows Registry Editor Version 5.00 の文言も必須
  • HKEY_CLASSES_ROOT\ に続く xxxx の部分(4ヵ所)を、あなたが命名した独自スキーム名へ変更してください
  • 末尾行の .vbs パス値は、実際の配置場所に応じて変えてください
    登録後にレジストリエディター側から編集しなおしても構いません
Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\xxxx]
@="URL:uri app runner"
"URL Protocol"=""

[HKEY_CLASSES_ROOT\xxxx\shell]

[HKEY_CLASSES_ROOT\xxxx\shell\open]

[HKEY_CLASSES_ROOT\xxxx\shell\open\command]
@="\"C:\\Windows\\SysWOW64\\wscript.exe\"  /b \"d:\\launcher.vbs\" \"%1\""
  • レジストリ登録後 regedit から当該箇所を確認したイメージ

4. ハイパーリンク <a> 等から起動

  • xxxx スキーム部分は置き換えてください
  • , は引数の区切り記号として処理しています
    • xxxx: の後に続く、1つ目の , からは、実行したいコマンド(exe)のパス
    • 2つ目の , から続く文字列は、実行時のオプション(引数)に見立てています
  • "xxxx:,c:\Windows\notepad.exe,d:\test.txt"
  • "スキーム名:,実行パス(或いはコマンド),オプション引数"

<a href="xxxx:,c:\Windows\explorer.exe,d:\">Dドライブをエクスプローラで起動</a>
<a href="xxxx:,c:\Windows\explorer.exe,c:\windows\">C:\Windows\ フォルダをエクスプローラで開く</a>
<a href="xxxx:,c:\Windows\notepad.exe">メモ帳をexe指定で起動</a>
<a href="xxxx:,c:\Windows\notepad.exe,d:\test.txt">notepad.exe に d:\test.txt を渡して開く</a>
<a href="xxxx:,C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe">Edge起動</a>
<a href="xxxx:,C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe,https://example.com/?foo=bar">Edge起動 URL引数(クエリ付き)</a>

解説:処理の流れ

  1. リンクとして xxxx: プロトコルを含んだURIを開く
    ちなみに URIハンドラー とか プロトコルハンドラー とか呼ばれているらしい?
  2. レジストリに登録した同名スキーム機構が反応
  3. そのままレジストリに登録されている .vbshref= のURI引数を渡す
  4. 引数を受け取った .vbs が起動
    この時点で一部引数はパーセントエンコーディングされている
  5. .vbs の内部で引数を分解
    一行分の引数を , で区切り、起動用とオプション用に分ける
  6. 引数をデコードし、コマンド或いは指定パスの.exe.vbsから起動
    起動用引数のみなら、そのまま実行。
    オプション引数もあれば、2つを合わせて起動

セキュリティ上の懸念(?)

自由度高め?なぶん、コマンドも実行できてしまいます

<a href="xxxx:,cmd">コマンドプロンプト直接起動</a>
<a href="xxxx:,cmd,/c start c:\Windows\notepad.exe">コマンドプロンプトを\c付きで直接実行</a>

↑ の2行目は Win + R で起動できる ファイル名を指定して実行 機能から、
↓ のコマンドを実行しているのとほぼ変わりません。

cmd /c "start c:\Windows\notepad.exe"

なので、やろうと思えばPC強制終了とかもできちゃいます (※注意※)

<a href="xxxx:,cmd,/c shutdown /s /f /t 0">強制的に即時PCシャットダウン注意</a>

他にも del とか rd で任意ファイル消しちゃったりとかも出来ると思うんで、一応注意は必要かなと。

ただ、攻撃者が居たとしても、以下の条件を全て成立させる必要はあります。

  • このページと全く同じギミックを用いたユーザーをピンポイントターゲットにして、
  • そのユーザーが独自に決めたスキーム( xxxx 相当 )をエスパーで完全に模倣したリンクを用意しつつ
  • 何らかの手段でそれを踏ませる

「住んでる都道府県内のどこかで家の鍵を落とし、それを時間差で拾った赤の他人があなたの家をピンポイントで見つけて侵入する」
くらいの成立度だと思っています(?)

スキーム名が任意って時点で防御率は跳ね上がりますが、
心配なら、追加の引数を義務にしつつ精査する機構を .vbs に追加して、それを二重のパスワード代わりにするとかでもアリ?

「常に開く」チェックボックスを有効にし続けたい

file:// がこのアプリケーションを開く許可を求めています。
file:// でのこのタイプのリンクは常に関連付けられたアプリで開く

最初に記事を書いてた頃は 一度チェック入れれば以降ダイアログ出ることは無かったんですが、
いつからかチェックを入れても再起動やブラウザ立ち上げ直し等で解除されるようになった気がします。

改めて調べてみたところ、どうやらレジストリ登録がポイントっぽい

  • Windows レジストリを使用して Chrome ポリシーを管理する
    ここを参考に Chrome ブラウザバンドル zip をDL
    ファイルに含まれていた chrome.reg を覗いて解析
    HKEY_LOCAL_MACHINE\Software\Policies\Google\Chrome 部分に以下の情報があった

    "AutoLaunchProtocolsFromOrigins"="[{\"allowed_origins\": [\"example.com\", \"http://www.example.com:8080\"], \"protocol\": \"spotify\"}, {\"allowed_origins\": [\"https://example.com\", \"https://.mail.example.com\"], \"protocol\": \"teams\"}, {\"allowed_origins\": [\"*\"], \"protocol\": \"outlook\"}]"

    過去の経験則と照らし合わせて、この部分を踏襲したレジストリ情報を新規登録したらいけました。

  • chrome enterprise - AutoLaunchProtocolsFromOrigins
    こちらにも完全に「それ」な情報があるようです

  • レジストリを作成・登録
    自環境だと以下でいけました

    Windows Registry Editor Version 5.00  
    
    [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome]
    "AutoLaunchProtocolsFromOrigins"="[{\"allowed_origins\": [\"file://*"], \"protocol\": \"xxxx\"}]"
    • 注意点 3つ
      • xxxx 部分は独自のスキームに置き換えてください
      • Windows Registry Editor Version 5.00 の文言は必要
      • .txt から手付で .reg を作る場合は保存時の文字コードに注意
        • UTF-16 LE でないと .reg にしたとき弾かれた

その他メモ

引数のデコードについて

今回のギミックでは、実行したい .exe の path をハイパーリンク内で引数風に指定しているのですが、
外部に渡されるタイミングで一部文字記号がパーセントエンコーディングされる様で、
そのままでは正常にファイルパスとして扱えません。


  • c:%5CWindows%5Cexplorer.exe ( エンコード前 c:\Windows\explorer.exe )
    d:%5C%E3%83%A1%E3%83%A2.txt ( エンコード前 d:\メモ.txt )

そこで、起動用受け皿として使っている .vbs の中で、
ScriptControl の力を借りたデコード処理をひとつ挟んでいます。

.vbs 実行時の諸事情

文字のデコードを行うにあたって CreateObject("ScriptControl") を利用しているが、
最初、 activexコンポーネントはオブジェクトを作成できません なるエラーに遭遇。

適材適所 - VBScriptのCreateObjectにおける「Active X コンポーネントはオブジェクトを作成できません。」のエラーについて

wscript.exe の利用元を変えると良いらしく、
最初のレジストリ登録箇所を

"wscript.exe" /b "d:\launcher.vbs" "%1"

↑ から ↓ に変更する事で対応

"C:\Windows\SysWOW64\wscript.exe" /b "d:\launcher.vbs" "%1"
  • wscript.exe のタイプ
    C:\Windows\System32\wscript.exe
    C:\Windows\SysWOW64\cscript.exe の2つがあり、
    これが "activexコンポーネントはオブジェクトを作成できません" エラーの原因だった模様
    自環境では純粋な wscript.exe 指定だと System32 側が呼ばれてしまうので、
    SysWOW64 側を使うようにフルパスで指定して解決

  • /b オプションについて
    Microsoft - wscript
    ログとか一切出さないようにする「バッチモード」らしいです。
    処理中にデバッグの意図で、
    WScript.Echo "ほにゃららら" とか
    Msgbox "なんちゃら" とか仕込んでたのですが、
    このオプションを入れると、そういう表示系をことごとくガン無視してたような印象があります
    逆に .vbs の内部処理を独自に追いたい場合は、/b を外すと良いかも
    "C:\Windows\SysWOW64\wscript.exe" "d:\launcher.vbs" "%1"

  • ウィンドウが一瞬表示される
    また、どうやっても実行時にコンソールウィンドウが一瞬チラついてしまい、これもググっていたら
    OKWAVE - VBS実行時にコマンドプロンプトが表示される
    wscript.exe ではなく cscript.exe を指定してたのが原因だった模様 ( なので直した )

余談 : 1つのexeのみを引数無しで起動できればOKな場合

本例

独自名 yyyy スキームだけで c:\hoge\fuga.exe を起動するだけ。
引数への対応を切り捨てる事で .vbs の機構を省略できる。

1. .reg を作成 + 実行してレジストリ登録

.vbs 不要なので wscript.exe を使わずに済むほか、
引数受け取りの %1 などが省かれている程度の違い。

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\yyyy]
@="URL:url scheme launcher"
"URL Protocol"=""

[HKEY_CLASSES_ROOT\yyyy\shell]

[HKEY_CLASSES_ROOT\yyyy\shell\open]

[HKEY_CLASSES_ROOT\yyyy\shell\open\command]
@="\"c:\\hoge\fuga.exe\"

2. レジストリ登録が済んだら <a> リンクを開いて exe 起動

引数処理を完全無視する事で yyyy: のみで、決め打ちの .exe を立ちあげられる

<a href="yyyy:">レジストリ登録済みのc:\hoge\fuga.exeを起動</a>