コードサイニングした実行ファイルを改ざんする

# 動機
配布する実行ファイルやDLLには署名する。当たり前の常識[^cs]のように考えていましたがなぜ署名の必要があるのか改めて疑問に思いました。
[^cs]:少なくとも有償アプリケーションでは…？

一般的に言われる[^csmerit]コードサイニングのメリットは以下の2つです。
[^csmerit]:https://jp.globalsign.com/service/codesign/knowledge/
1. ファイルの認証元を認証する
1. ファイルが改ざんされていないことを保証する

でもこれって最終的にだれが保証しているんでしょう。発行元が怪しかったり、改ざんされていたりするファイルはだれが止めてくれるんでしょうか。本当に安全なのでしょうか。
というあたりがわかっていないので検証した話です。
# 先に結論
署名してもあんまり意味ない
# 下準備
## 署名されるファイル
適当なプログラムを用意します。筆者が一番使うC#で
```shell
dotnet new console -o HelloWorld
cd HelloWorld
dotnet publish -c Release -r win-x64 -p:PublishSingleFile=true --no-self-contained
```
いっぱいファイルができると署名が面倒なので単一実行ファイルです。これでHelloWorld\bin\Release\net6.0\win-x64\publishフォルダーにHelloWorld.exeが出来上がります。中身は`dotnet new console`が既定で作るHelloWorldです。
# コードサイニング証明書
続いてコードサイニング証明書を作ります。テストなのでオレオレで。OpenSSLをインストールして[^oepnssl]パスを通して、
[^oepnssl]:https://slproweb.com/products/Win32OpenSSL.html
```shell
openssl genrsa 2048 > key.pem
openssl req -new -key key.pem -out csr.pem -subj "/C=JP/ST=Tokyo/L=Chiyoda-Ku/OU=miswil/CN=miswil.test"
openssl x509 -req -days 365 -in csr.pem -signkey key.pem -out cert.crt
openssl pkcs12 -export -out miswil.test.pfx -inkey key.pem -in cert.crt -password pass:
```
これで署名用のmiswil.test.pfxができました。cert.crtは信頼されたルート証明機関にインポートしておきます。
# 署名付け
dotnetらしくsigntoolを使います[^signtool]。VisualStudioをインストールしている人はDeveloper Command Promptから、ない人はWindows SDK[^wsdk]をインストールした後パスを通しておきます。タイムスタンプサーバーにはdigicert[^dt]をお借りしました。
[^signtool]:https://docs.microsoft.com/ja-jp/dotnet/framework/tools/signtool-exe
[^wsdk]:https://developer.microsoft.com/ja-jp/windows/downloads/windows-sdk/
[^dt]:https://knowledge.digicert.com/ja/jp/solution/SO23060.html
```shell
signtool sign -f miswil.test.pfx -t http://timestamp.digicert.com -fd SHA256 HelloWorld.exe
```
# Let’s 改ざん
出来上がったexeを適当なバイナリエディタで開いて改ざんします。今回はVSCodeのHexEditorプラグイン[^hexe]を使いました。
[^hexe]:https://marketplace.visualstudio.com/items?itemName=ms-vscode.hexeditor

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247638/f6ea96f8-7f45-71dc-9c3b-ae15660d0f0a.png)
実行時にコンソール出力される文字列を"Hallo, World!"にします。悪質ですね。
# 下準備まとめ
そういうわけで3種類のファイルを準備しました。
1. HelloWorld.exe：署名されていないファイル
1. HelloWorldSigned.exe：署名されたファイル
1. HelloWorldSignedTampered.exe：署名されたあと改ざんされたファイル

# まずは署名を確認
右クリック→プロパティから署名を確認します。
1. HelloWorld.exe
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247638/3d448f08-f66a-c697-1471-edfcd9e4c6f2.png)
署名はありません
1. HelloWorldSigned.exe
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247638/fed238c3-8e01-3d26-2a4e-f60a91eb6476.png)
署名があります。詳細を選択すると、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247638/b39d2e48-5f28-33a2-2a15-560e244bea71.png)
署名の詳細が表示されます。
1. HelloWorldSignedTampered.exe
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247638/92ff2d04-4b1a-5c73-5269-1be5cb9e939c.png)
おや？改ざんされたのに署名が…？詳細を開くと、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247638/3448e3d4-2af8-883a-9056-7b8dca82f485.png)
ここで初めて署名が有効でないことがわかりました。

え？プロパティを開いて詳細まで見ないとわからないの？exeを実行するたびにそんなことしないでしょ？
# 実行してみる
まあ実行されなければいいんです。そうすれば安全ですから。exeをダブルクリックで起動します。
```HelloWorld.exe
Hello, World!
```
```HelloWorldSigned.exe
Hello, World!
```
```HelloWorldSignedTampered.exe
Hallo, World!
```
……普通に実行できてしまいました。未署名exeはともかく、改ざんexeでも警告すら出ません。
# Microsoft Defender SmartScreenに期待
コードサイニングの検証について調べると、Webブラウザーからのダウンロードをを経由するときには、ダウンロード時、実行時に検証がかかるそうです[^csv]。これはMicrosoft Defender SmartScreen[^mds]が仕事をしています。
[^csv]:https://security.macnica.co.jp/blog/2013/12/exeexe-b21e.html
https://mag.osdn.jp/12/11/19/0634216
https://jp.globalsign.com/service/codesign/knowledge/smartscreen.html
[^mds]:https://docs.microsoft.com/ja-jp/windows/security/threat-protection/microsoft-defender-smartscreen/microsoft-defender-smartscreen-overview
## ダウンロード
というわけで適当なストレージにアップロードしてダウンロードしてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247638/43f09d34-7995-1e2a-6a44-a0167a0133d3.png)
未署名とか、署名のルート証明書を信頼しているかとか、改ざんとか関係なしに全部止められます。これはメッセージにある通り、このファイルのダウンロード実績が少なすぎるからです。アップロードしたばかりですから当然ですね。この警告を表示させないようにするには、EVコードサイニング証明書を使って署名するか、地道にダウンロード実績を重ねて信頼を獲得するかのどちらかのようです。
警告を無視してダウンロードを続けます。するとファイル発行元の詳細が表示されます。
1. HelloWorld.exe
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247638/c5b1c13b-1a23-4964-1d5a-a7128ad89222.png)
1. HelloWorldSigned.exe
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247638/98814952-9b29-82a3-402d-0a1dc105120d.png)
1. HelloWorldSignedTampered.exe
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247638/0615c97c-754a-df02-e157-b72ed340cbe0.png)

うーん、改ざんされているファイルでも発行元が正しいかのように表示されてしまいます。これではユーザーも気が付かない。
## 実行
ダウンロードしたexeを実行します。
1. HelloWorld.exe
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247638/92f756f1-d8ef-956c-76c4-4b9aa0937ec6.png)
1. HelloWorldSigned.exe
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247638/08810f32-87c3-3e6e-5428-4aabb2a7c4f6.png)
1. HelloWorldSignedTampered.exe
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247638/d126c7a9-2599-e521-c5c7-deed0c1f11ad.png)

今度は改ざんされているファイルは不明な発行元になっていますね。でも未署名との区別がつかないです。ダウンロード時には発行元があって実行時には発行元がないという矛盾に気づくユーザーがどれだけいるのか……
いずれのファイルも「実行」を選ぶと普通に実行されました。
# 結論
ここまで検証して得られた事実をまとめると、
* 署名後に改ざんされたファイルは署名が無効になるがかなりわかりづらい
* 署名や改ざんにに関係なくダウンロード時には警告が出るので、安全なファイルと怪しいファイルのファイルの区別がつきづらい
* 署名や改ざんにに関係なく実行時には警告が出るので、安全なファイルと怪しいファイルのファイルの区別がつきづらい
* 署名や改ざんに関係なく実行できる

|  | 署名 | ダウンロード時 | ダウンロード後初回実行時 | 実行 |
|:-:|:-:|:-:|:-:|:-:|
|未署名| なし  | 警告  | 警告  | 可  |
|署名あり|  有効 |  警告 |  警告 | 可  |
|署名後改ざん|  無効 |  警告 |  警告 |  可 |

難しい問題だと思います。安全なファイルが区別できたら世の中からマルウェアは絶滅するわけで、そうなっていない以上すべてに警告を出すのも1つの正解だと思います。
とはいえ改ざんは明らかに怪しいのでそれくらいは止めてくれてもいいのではないでしょうか。セキュリティソフトの設定やグループポリシー次第で止まるんでしょうか？今回の検証環境は特別セキュリティを落としているわけではないので、一般的な普通の環境だと思うのですが……
最終的には、手動でファイルのプロパティから署名の正当性を確認するのが一番安全で、結局ユーザーリテラシーに依存するというあまり好ましくない結論となりました。そうであれば署名をする意味ってないような気もしてきます。みんなそれがわかってるから世の中未署名のアプリケーションばかりなのでしょうか？それはそれで怖い。怪しいアプリにパスワードなんか入力しないよう気をつけましょう。