レジストリを汚さないためのツールを作りました

#どういうツール
簡易的な仮想環境を作り、アプリケーションの動作を現在の環境から切り離すサンドボックスツールです。
レジストリとCドライブへのアクセスを仮想化することによって、

- アプリケーションからシステム環境に影響を与えない様にする
- アプリケーションを単独で持ち運び可能にする

などなどが可能になります。このツール自体はもちろんレジストリを汚しません。

##どうやって
DLLインジェクションからのIAT書き換えによるWin32 APIのフックにより動作を書き換えています。
IATを経由しない動的呼び出しもLoadLibraryやGetProcAddressを書き換えることで対処しています。

あくまで公開API([※](#内部api))に渡す値を書き換えているだけで、本質的にディスクアクセスに干渉しているわけではない、
例えばWindows APIが内部で呼び出す様なより低レベルな処理を直接呼ばれた場合などは対処出来ません。
書き換え対象のAPIはそれっぽいものを私が目で見て選んでいるだけなので漏れがある可能性が非常に高いです。
そうじゃなくてもCドライブ以外のディスクアクセスは素通しなので、セキュリティのためのツールではありません。
##何をする
###レジストリ
仮想化しており、レジストリには一切書き込みません。
仮想化の方法として、レジストリキーをディレクトリ、エントリをファイルとして値をファイルの中に書き込む方法を取りました。
ファイル名の制限を受けるのが難点ですが他に考えた方法は排他制御が面倒なのでこれにしました。
レジストリのキー名やエントリ名は制限がほとんどないのですが(キーに\は使えないぐらい)
知っての通り、ファイル名には使えない文字や名前が色々あります。
Classesにいきなり*とかありますが、Softwareの中にはすぐには見当たらないのでまあ大丈夫でしょう。
それらの文字に対しては置換する処理を挟んでいます、そのせいで余計な問題が出る可能性もなくはないですが大丈夫でしょう。
駄目な名前(conとか)については特に対処していません。
###ファイルアクセス
ファイルパスを受け取るAPIに対して、
`C:\`をこのツールの動作先(`F:\sandbox\`など)へ書き換える処理を挟んでいます。

レジストリにしろファイルアクセスにしろ、
完全に切り離してしまうとサンドボックス環境下に何もなさ過ぎてアレなので、
読み取りアクセスするだけならオリジナルにもアクセスする様にしています。
サンドボックス環境内に必要なものをちゃんと用意するのが正しいのかもしれないけど、そこまでするなら仮想マシンでいいよね。

###ディレクトリ情報
例えば(マイ)ドキュメントは、`C:\Users\Silica\Documents`とかですが、
根っこが`Users`だったり`Documents and Settings`だったりしましたし、
ユーザー名部分はユーザー名が変われば当然変わりますし、末尾も昔はMy Documentsでした。
この情報がバラバラだと結局どの環境でも同様に動作させるというわけにいきません。
なので、システム系のディレクトリを返すAPIの動作を書き換え、決まった名前を返す様にしようとしたんですが、
[問題が噴出した](#itemidlist)のでユーザープロファイルのユーザー名部分を書き換えるだけで我慢しました。

###システム情報
必要かどうか分からなかったんですけど、環境を同じに見せかけるために一応。
GetVersionとかGetSystemInfoとかです。
あとGetSystemTimeとかもついでにやってます。
この辺は基本的には本来の値を返し、オプションで特定の値を指定出来る様にしました。

Mutexも書き換えてます。(サンドボックスパスを付け足している)
なので同時起動を禁止してるソフトをサンドボックス環境毎に起動するとか出来るはず。

##必要なもの
###C以外のドライブレターを持つ書き込み可能なドライブ
`C:\`をツールパスに書き換える関係上他のドライブレター上で実行しないと死にます。
パスがサンドボックス上かどうか確認してやれば出来ないこともなさそうですが、今のところそうしています。
仮想ディスクでもリムーバブルディスクでもいいのでなんか用意して下さい。
###C++コンパイラとコンパイル力(Visual C++なら多分問題はない)
ソース配布なので各自コンパイルして下さい。
###何が起きても文句を言わない心構え
ツールの性質上何が起きるか分かりません。
いや普通はそんな壊滅的なことはまず起きないですけど。
ゴミファイルが作られたり、インストールしたファイルやレジストリがサンドボックス上とシステム本体に分断されたりするぐらいです多分。
あと単純にこれ上で動かないツールが多いと思うんで、一々文句言わないで下さい。

##使い方
1. sandbox.exeとsandbox.dll(とsandbox64.exeとsandbox64.dll)をどこかに配置する(そこがツール動作のベースパスになる)
2. sandbox.exeを実行すると必要なディレクトリを作成してくれる
3. sandbox.exeの引数として対象の実行ファイルを渡す
 - エクスプローラーからならsandbox.exeに対象exeをドロップ
 - コマンドラインからなら sandbox target みたいにコマンドを打つ

[sandbox.cfgもあると、値を参照します。](#オプション)

##スクリーンショット
Cドライブ以外の場所に必要なファイルを配置します。
![配置](https://qiita-image-store.s3.amazonaws.com/0/293233/9b68de6c-1d20-457a-e14d-fb36ae5585f8.png)

sandbox.exeを実行すると必要なディレクトリを作成してくれます。
![初期設定](https://qiita-image-store.s3.amazonaws.com/0/293233/1fa5629f-e1f0-19af-0fe2-08e1ee816a92.png)

インストール前にソフトを実行しようとしても実行出来ないことを確認します。
![インストール前実行確認](https://qiita-image-store.s3.amazonaws.com/0/293233/b191536c-0c46-c575-b346-bb9e92f9152f.png)
尚、インストーラー付属でインストールしないと実行出来なく容量が手頃なソフトがこれだっただけで他意はありません。

インストーラーをsandbox.exeにドロップします。
![ドロップ](https://qiita-image-store.s3.amazonaws.com/0/293233/9965f73b-c886-85dd-d266-ec24eaf53d5a.png)

インストール先にCドライブ上を指定してインストールを開始します。
![インストール](https://qiita-image-store.s3.amazonaws.com/0/293233/f0f858c8-1345-3051-70de-f79bd566e040.png)
コンソールは消せるのですが、何も出さないとアタッチされてるか不安になります。

メッセージウインドウを消すと乳首が見えます。位置は特に動かしていないのですが、丁度隠してくれました。
![インストール完了](https://qiita-image-store.s3.amazonaws.com/0/293233/e9ae13f6-03f7-880e-79d2-635e376926a8.png)
そのままゲーム起動すると無事に起動します。
![起動](https://qiita-image-store.s3.amazonaws.com/0/293233/a4d143fd-5a1a-b727-2194-21b2c9d2170a.png)
タイトル画面も、ゲーム開始直後の画面も乳首が見えてしまうので中途半端な画面しかお見せできないのが残念です。
私自身は乳首は性器ではない主義者なのですが、全てはQiitaの判断によるので迂闊なことは出来ません。

インストール先にCドライブを指定したファイルは実際にはsandbox以下にあり、直接起動しようとしても最初と同じ様に起動出来ません。
![ツール外起動](https://qiita-image-store.s3.amazonaws.com/0/293233/dce94f9a-011c-7c62-05a7-971401886bae.png)
sandbox.exeにドロップすればちゃんと起動します。

そしてsandboxディレクトリごと他のPCに移してもちゃんとゲームを起動することが可能です。

##オプション
sandbox.cfgを編集することで幾つかの動作をコントロールすることが可能です。
exeとdllと同じディレクトリに配置して下さい。
書式は行区切りで
`(空白)名前(空白)=(空白)値(空白)`
改行はLFでもCRLFでもいい、というか`\n`で切って`\r`は空白として処理している。
名前の大文字小文字は区別しない。
先頭を#にするとコメントとかそんな気の利いた機能はないが、別の名前として認識するので結果的にうまくいく。
値は普通の10進数値か、0xから始まる16進数値かそのまま文字列。
パースが雑なので値に=は使えない。(名前にも)
主な項目は
###logfile
0か1、まあ正確には0以外の数値なら何でもいいんだが、trueならログファイルを書き出す。
###loglevel
0から9、大きいほど沢山ログが出る
###console
これも0か1、trueだとコンソールを勝手に作ってそこにログを流す、
尚、これのONOFFを問わずコンソールへログを流すのは常にやる、のでCUIツールに使うとちょっと困ったことになる。
###TEMP
GetTempPathが返す値と環境変数TEMP、TMPを指定した文字列で書き換える。
どっか外にしておいた方が動作は安定する。
私は元々システム上でZ:\TEMPになっている。
###api-ms-win-core-file = l1-1-0
[詳しくは後述](#内部api)するが結局内部dllを相手にしている。
`api-ms-win-core-file-l1-1-0.dll`みたいなdllを対象に書き換える際にバージョンを指定する。
適当に0とか存在しない名前にしてやれば、kernel32.dllとかを読み込む様になるのでそれでコントロールする。
基本的にデフォルトで内部の方のl1-1-0を読み込む様になっているがlocalregistryだけ例外で指定しなければadvapi32を使う。
何をどう使っているかはソース見ろ。
###GetLogicalDrives = 0x00000004
こうするとGetLogicalDrivesがCドライブしか存在しないみたいな値を返す。
が、サンドボックスを動かしてるドライブも入れておかないと変なエラーが出たりする。
何も指定しない場合は本来の値を返す。
システム情報系APIは大抵そういう方法を取っているが、そもそも必要なのかあんまり分かってないので殆どはまだ対応してない。

他にどう使っているかはソース見ろ

##配布
ソースのみ[github](https://github.com/Silica/sandbox)で配布しています。

アンチウイルスソフトに目をつけられるぐらいは別にいいんですが、
日本の警察に逮捕されたら困るのでシステム動作に干渉する様なツールはバイナリ配布しません。
各自でコンパイルして下さい。
あと動かないこと多いと思うので、コンパイルぐらい出来る人間以外に触らせたくない。
誰かが本人の責任においてバイナリ配布するのは自由にして下さい。

ライセンスはLGPLv3です。
今までは個人的な考え方と作ったものの性質上GPL系は使ってないんですが、単独のプログラムだし、もし誰かが何か改善したなら公開して欲しい。
これ自体は何かに組み込んで使う様なプログラムじゃないと思うけど、このプログラムから他のライブラリの使用に制限が出るとアレなので、LGPLです。
ライセンスの選択に問題がありそうなら教えて欲しいです。
ライセンスの適用範囲は本体のみ(cppとmod以下)
includeとutilityは特に何も要求しないです。用があれば好きにして下さい。大したコードじゃないです。

##開発
Windows 7 上の Visual C++ 2013 で開発しています。
環境が何もかも中途半端なのはこのツールを作らなければならなかったことに関係しています。
新しい分には問題ないと思うのですが、古い環境ではプロトタイプ宣言やdefine値がなかったり、C++機能が使えなかったりするかもしれません。
SDKのバージョンは不明、多分2013についてるやつだと思うんだけど。

##注意点
変な形でファイルを漏らすことがある。
Cドライブ上の本来のパスに作られるのはまあ分かるとして、
`F:\sandbox`でツールを動かしているとして`F:\hoge`みたいに作られることがある。
多分、ディレクトリ情報を正しく取得出来なかった結果、
`C:\Users\Silica\AppData\Local\hoge`みたいにしたかったものが`\hoge`になっているのだと思われる。
まさか本来`C:\hoge`を作りたくて`\hoge`指定しているわけではあるまい。
なので設置場所はそれを考慮した方がいいかも。

##.NET
特別何をしているわけでもないんですが、地道な仕事が功を奏したのかマネージドなプログラムにも対応しているみたいです。

#以下特に読まなくてもいい話
苦労したところとか、解決したものもそうでないものも色々あります。
###64bit
このツール自体を64bitコンパイルし、64bitアプリケーションを対象に動かす分には動くことは動きます。
32bitプロセスから64bitプロセスを呼び出したりした場合も両方のexeとdllを用意しておけば何とかなる様に小細工はしているのですが、
bitの話に関係なくShellExecuteやCreateProcess絡みの処理に問題があるっぽくて、外部プロセスを呼び出した際にうまくアタッチ出来てないことがある。
また、WOW64絡みのアレコレをどうするか。
それと私は数値型の大きさや符号にあまり頓着しないタイプであり、
ツールの性質上ポインタと数値の変換とかもよく行うのでこのコードが64bitで本当に問題がないのかは検証を要する。
###UAC
レジストリにもCドライブにも書き込みしなくなるということはUAC一切必要ないんですが、要求してくるものは仕方がない。
当然ですが、通常プロセスから管理者権限のプロセスに対してはアタッチ出来ません。
小細工をして何となくうまくいっているのですが完全ではないです。
確実にやるなら始めからコマンドプロンプトを管理者として実行したり、batを書いて下さい。
64bit混在の話が絡むと更にややこしい。

###UNICODE
当初よりフック対象のAPIそのものは当然ながらAにもWにも対応していたのですが、このツール自体はANSIメインで書いていて、
パス書き換え処理とかもW系関数に対してはUNICODE→ANSI→パス変換→UNICODEとかやってたんですが、
ANSI(SJIS)よりUNICODEの方が表現力が広いのもあってそれは書き直すことにしました。

###ITEMIDLIST
shell32.dllのパス取得系のAPIにはITEMIDLISTとかいうのを使うものがある。
これに関して、**存在しないフォルダのIDは取得出来ない**、という独自のフォルダ名を使うには致命的な問題がある。
レジストリをそうした様に、ITEMIDLIST自体を完全に独自の値に置き換えてしまうのがいいんだろうが、
関係あるAPIの範囲がどこまで及ぶのか分からなかった(多かったし名前だけ見てもよく分からんかった)

###システム系のディレクトリを取得する方法があり過ぎる
SHGetなんとか系のAPI、その中にも直接文字列でくれるものもあればITEMIDLISTを経由するものもある。
レジストリ、なんか色んなところにある。
環境変数、書き換えてしまえるレベルだが書き換えるとややこしい影響が出たりする。
またそれぞれが独立かというとそうではなく、APIは内部でレジストリを見て、その値を環境変数で展開したりする。
どれをどう書き換えればいいかの指針が立て難く、本来の値を参照して欲しい機構に変に影響が出たりするので独自の名前を使うのはやめることにした。
あと、サンドボックス内にファイルが存在しない場合に本来のファイルを見に行く様にした関係でディレクトリ名の書き戻しが困難だからというのもあった。
因みに独自パスでシステム内のファイルを見ないでやってた時でも割と動くものは動いていた(DLLは名前だけにしてSystem32を見に行ってくれる様に加工したりはしていたが)

###環境変数
環境変数は仮想化していません。
そもそも見るだけなら親プロセスから引き継げるので仮想化というほどのことをする必要もないのです。
なので当初は直接値を書き換えてたんですが、パスを独自のものにするのをやめたので、今はあまり触っていません。

###COM
一番の問題児。それはそれは色々あるんですけど、
分かり易いところではインストーラーが作るショートカットで問題が起きます。
(対象が見付からないといってエラーが出たり、本物のデスクトップやスタートメニューにショートカットを作られてしまう)
結局のところDLL読み込みには違いないので、前後でIAT書き換えをしたりして対処出来そうなところはしてるんですが、
効果が出てるのか出てないのかよく分かりません、うまくいってない気がする
システムに元々登録されてる様なオブジェクトは中で公開APIを呼んでないのでは？という説もある
既知のオブジェクトは独自のものに置き換えてしまった方がいいんだろう。
ヘッダを見るとIShellLinkとか名前の通りインターフェースなので、独自のものに置き換えることは出来るっぽい気がしている。

exeサーバー？
ってどういうところで使われてるんだろう、エクセル？
何にせよそれも、CreateProcessでやってるのと同じことでは？

###DllMain
COMとか考えなくても普通のDLLでもDllMainはLoadLibraryした時点で呼ばれてしまうのでその中の動作は書き換えられない。
初期化処理の中で書き換え対象のAPIをGetProcAddressしてたりしたら、書き換え前のAPIを呼ばれてしまう。

###IAT書き換え範囲
LoadLibraryで新たに読み込んだDLLに対しても書き換えを行うじゃないですか、
それはいいんですが、そのDLLが別のDLLをインポートしてる可能性はあるじゃないですか、
だからLoadLibraryが呼ばれる度に(既に読み込まれたDLLかどうかのチェックはしたけど)全てのIATに対して再度書き換えを行っていたんですが
プラグイン形式で大量のDLLを読み込むタイプのお絵かきソフトや動画プレイヤーで、動作するにはするが、起動に時間が掛かり過ぎるという事態が発生。
あんまりマシな方法も思い付かなかったので、読み込んだDLLのみを対象に書き換えを行うことにしました。(一応オプションで切り替え)
その後特に問題はなさそうでした。

###内部API
本当はNtCreateFileやNtCreateKeyを書き換えてしまうのが正しいのだろう、
しかし私にはそれらの関数の意味が全く理解出来なかった。
まあそれはそれとして、
内部関数を触るといつ変更があるか知れないし、そんなことしなくてもユーザープログラムに対して公開されている口を全部書き換えてしまえば内部処理には関係なく目的を達成出来るだろうと思っていたのですが、実際割とうまくいったんですが、
何を見落としているのか分からないのだがどうにもうまくいかないところが残る。
インポートされてるAPIとGetProcAddressを監視しているだけでは駄目なのか。
ユーザープログラムは内部APIを触らないにしても、COMとか、まあなんかあるんだろう。
なので、名前とシグネチャは同じだけどより内部のDLL(api-ms-win-core-file-l1-1-0とか)のAPIに対して書き換えを行うとうまくいったりしたので、そうすることにしました。
L1-1-0のバージョン部分(現状特に意味はないみたいですが)をオプションで切り替えられる様にして、読み込みに失敗したら元のkernel32を使うみたいな処理になっています。
ただしレジストリだけは内部を書き換えるとわけわからんことになったのでデフォルトではadvapi32を見ています。

###NULL
Win32 API、NULLを渡してもいいパラメーターが多すぎる。まあ使う方ではいらないところNULLにしまくっているのであまり強くも言えないところではあるが。
サンドボックスのDLL内部でそのせいであまりにもクラッシュするので各所にログを仕込みまくることになってしまった。
あと、文字列(char*)を渡してもいいし数字を渡してもいい、みたいな関数あってヤバい。初めて知った。

それとNULL終端も。文字数に含むやら含まないやら、付加して返すやらしないやら、ややこしい。
当初適当にやってたら普通に困ったことになったので、一つ一つ検証することになった。

###仮想マシン
ツールの性質上、主に仮想マシン内で色々試していたが、対象アプリケーションがそもそも仮想マシン内で動かなかったみたいなことがあったりしてぐぬぬ
(必ずしも対応してないわけじゃなくて、ランタイムライブラリが入ってないとかそんな感じだと思う)
そもそも仮想マシン使えばよくない？みたいなのに対しては、違うって、ちょっと見ているところが違うんだよ、
私はOSの移行や再インストールとかを楽にしたかったんだよ。
仮想マシンは結局その仮想マシン自体を維持するコストがかかるじゃんか。
だからこれは**レジストリを汚さないツール**なんだよ。

#よくありそうな質問
###コンパイル出来ない
なんか私の書き方に不備がありそうなら教えて。
そうじゃないなら頑張れ。

###サンドボックス上で○○が動かないんだが？
可能性として、

1. 必要なAPIがまだ書き換え対象ではない
2. 書き換えたAPIが想定通りに動作していない
3. 対象ソフトがなんかややこしいことをしている

とかがある。
1ならインポートリストやGetProcAddressのログを眺めて、これに対応してくれって言えば出来るかもしれない。
2は普通にあるんだが結構困る。
3はなんかもう私には無理。

###○○だが？
https://twitter.com/P9Silica

###もっといいツール既にあるよ
教えてくれ

(2018-09-21追記)
色々教えて下さってありがとうございます。
・sandboxie
今回のツールを作る過程で知ってはいました。
導入時にドライバを入れるというのだけ見て、それでストレージアクセスを横取りする様なツールだと勝手に想像していたのですが、
もっと詳しく調べるとここでやりたい様な目的でも使えるみたいですね？
実際に自分で試してみるべきなのですが、色々重そうでなんか…
ツールそのものの重さは断然こちらの方が軽いとは思います。
上で動かすアプリがどちらの方が重くなるかは何とも言えません。
・App-V
App-Vって個人がそんな気軽に簡単に使える様なものなの？
取り敢えず誰かお姉ちゃん部体験版をApp-Vでインストールして動かす様子を記事か動画にして欲しい、それから考える
・PortableApps
Firefoxのポータブル版は知ってましたがこのプロジェクト(？)は知りませんでした。ありがとうございます。
他のソフトウェアをこれに追加出来るのかどうかがよく分からないのですが、
インストーラーを介さないアプリは中に入れてランチャーから起動出来るというのは調べて分かったのですが、
後は実際自分で試してみようと思います。これは試し易そうだし。

#Detours
https://github.com/Microsoft/Detours
というツールがある。
関数内部を直接書き換えることでIAT書き換えより網羅性の高い方法を提供出来るのだが、

1. APIを書き換えることでこいつ自身にどういう影響が出るか調べるのが面倒だった
2. 64bitにも対応してる風だがコンパイル出来なかった

のでひとまず見送った。
この、何らかのライブラリを使うとそいつにも影響が出るかもしれないというのかなり重大で、オプションファイルに自作言語も使えなかった。
まああれはファイル読み込みぐらいしか関係するところないはずで多分大丈夫なんだけども。

#参考
http://inaz2.hatenablog.com/entry/2015/08/10/193103
https://docs.microsoft.com/en-us/windows/desktop/apiindex/windows-api-list
https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/
https://blogs.mcafee.jp/1021
http://www.candysoft.jp/marble/product/ane_club/main_dow.html