<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    .NET下繞過任意反Dump的方法

    一顆小胡椒2022-03-16 13:31:05

    前言

    .NET下的反Dump手段比較單一,無非是在運行后對PE頭中的.NET部分進行抹除。由于CLR在加載程序集時已經保存了所有.NET元數據的偏移和大小,抹除這部分.NET頭對程序的運行沒有任何影響。但是如果我們直接Dump內存中的程序集,由于.NET頭已經被抹除,Dump得到的文件是無法被dnSpy、ILSpy等工具讀取的。利用CLR的內部對象,我們可以從中讀取.NET的元數據信息,從而計算恢復出PE頭中的.NET部分。本文將介紹如何通過這個辦法,達到繞過任意反Dump技術的目的。

    在本文開始前,必須要明確的一點是,繞過反Dump不意味著可以直接Dump下來可以運行和使用的文件!繞過反Dump的目的是恢復出必要的信息讓dnSpy、ILSpy等工具可以直接反編譯,從而快速分析這個.NET程序集,而不是恢復出和原始的一摸一摸的沒有信息丟失的.NET頭!如果想要脫殼,并不能依賴于這個方法!

    文中CLR源碼來自CoreCLR v1.0

    恢復.NET頭的思路

    在前言中已經大致介紹,通過讀取CLR內部對象,可以獲取必要的信息來恢復.NET頭。所以我們需要先了解PE頭中的.NET部分和反Dump可以抹除的部分。

    首先是Data Directories中的.NET元數據目錄。這一項記錄了.NET目錄(IMAGE_COR20_HEADER)的偏移和大小。一般來說偏移是0x2008,也就是.text段的第8個字節,這是由C#和VB.NET編譯器決定的。大小是sizeof(IMAGE_COR20_HEADER),也就是固定的0x48。

    CFF Explorer中的.NET Directory就是IMAGE_COR20_HEADER。

    通過上一步的解析,我們可以得到IMAGE_COR20_HEADER的所在位置。其中IMAGE_COR20_HEADER的定義如下,重要的部分我給了注釋。

     復制代碼 隱藏代碼
    typedef struct IMAGE_COR20_HEADER
    {
        DWORD                   cb;                  // sizeof(IMAGE_COR20_HEADER)
        WORD                    MajorRuntimeVersion;
        WORD                    MinorRuntimeVersion;
        IMAGE_DATA_DIRECTORY    MetaData;            // .NET元數據
        DWORD                   Flags;               // 標志位,指示程序集類型,比如是否可執行,是否純IL
        union {
            DWORD               EntryPointToken;     // Main方法的MDToken
            DWORD               EntryPointRVA;       // 入口點的RVA(如果入口是本機代碼)
        } DUMMYUNIONNAME;
        IMAGE_DATA_DIRECTORY    Resources;           // .NET資源
        IMAGE_DATA_DIRECTORY    StrongNameSignature; // .NET強名稱
        IMAGE_DATA_DIRECTORY    CodeManagerTable;
        IMAGE_DATA_DIRECTORY    VTableFixups;
        IMAGE_DATA_DIRECTORY    ExportAddressTableJumps;
        IMAGE_DATA_DIRECTORY    ManagedNativeHeader;
    } IMAGE_COR20_HEADER, *PIMAGE_COR20_HEADER;
    

    此結構中的大部分是可以被清除的(我知道的不能清除的有Resources,因為每次獲取資源都要重新讀取IMAGE_COR20_HEADER::Resources),但是必要的部分其實只有MetaData這個成員,其它部分如.NET資源只是作為附加項。為了讓反編譯器可以盡可能顯示全部信息,我們恢復MetaData,EntryPointToken這三個成員即可

    EntryPointToken和Resources的恢復比較簡單,只要恢復IMAGE_COR20_HEADER中的成員。而MetaData比較復雜,需要恢復MetaData指向的.NET元數據頭。.NET元數據頭的第一個結構是STORAGESIGNATURE,緊接著的是STORAGEHEADER,然后跟著STORAGESTREAM數組。這里給出它們在CFF Explorer中的顯示和在CLR中的定義。

    CFF Explorer中的MetaData Header就是STORAGESIGNATURE + STORAGEHEADER,MetaData Streams就是緊跟的STORAGESTREAM數組。

     復制代碼 隱藏代碼
    struct STORAGESIGNATURE
    {
        ULONG       lSignature;             // "Magic" signature.
        USHORT      iMajorVer;              // Major file version.
        USHORT      iMinorVer;              // Minor file version.
        ULONG       iExtraData;             // Offset to next structure of information 
        ULONG       iVersionString;         // Length of version string
        BYTE        pVersion[0];            // Version string
    };
    
    struct STORAGEHEADER
    {
        BYTE        fFlags;                 // STGHDR_xxx flags.
        BYTE        pad;
        USHORT      iStreams;               // How many streams are there.
    };
    
    struct STORAGESTREAM
    {
        ULONG       iOffset;                // Offset in file for this stream.
        ULONG       iSize;                  // Size of the file.
        char        rcName[MAXSTREAMNAME];  // Start of name, null terminated.
    };
    

    STORAGESIGNATURE中的iVersionString成員表示pVersion的真實長度,也就是說STORAGESIGNATURE結構體的實際大小是sizeof(STORAGESIGNATURE) + iVersionString。STORAGEHEADER的iStreams成員表示STORAGESTREAM數組的元素數量。一般來說iStreams為5,5個STORAGESTREAM結構分別對應了#~、#Strings、#US、#GUID、#Blob這5個元數據流。

    在反Dump中,STORAGESIGNATURE的lSignature的成員是一定會被抹除的,它和PE頭的"MZ"類似,值恒為0x424A5342,也就是"BSJB"。如果不抹除這個成員,通過搜索特征BSJB可以非常容易地定位到.NET元數據頭從而繞過反Dump。和上面提到的結構IMAGE_COR20_HEADER一樣,這三個結構的所有成員也都是可以抹除的。恢復的時候,我們主要關注STORAGESTREAM這個結構中的所有三個成員,保存了指向.NET元數據流的信息,和這些元數據流對應的名稱。其它兩個結構體相對而言沒那么重要,可以直接填預設值。

    在上面提到的5個元數據流#~、#Strings、#US、#GUID、#Blob中,#~是表流,必須存在的。如果表流是未壓縮的,它的名稱也可以是#-,元數據結構上和#~是一致的。表流的頭部是CMiniMdSchemaBase結構,這里給出它在CFF Explorer中的顯示和在CLR中的定義。

     復制代碼 隱藏代碼
    class CMiniMdSchemaBase
    {
        ULONG       m_ulReserved;           // Reserved, must be zero.
        BYTE        m_major;                // Version numbers.
        BYTE        m_minor;
        BYTE        m_heaps;                // Bits for heap sizes.
        BYTE        m_rid;                  // log-base-2 of largest rid.
        unsigned __int64    m_maskvalid;    // Bit mask of present table counts.
        unsigned __int64    m_sorted;       // Bit mask of sorted tables.
    };
    

    在CMiniMdSchemaBase結構后面,緊跟著一個UINT32數組,數組長度是m_maskvalid成員bit為1的數量。這個數組的元素按順序表示了每個存在的表的行數。

    CLR加載.NET程序集時,這些成員都會被保存到CLR內部,所以這些成員也都是可以抹除的。恢復時,我們主要關注哪些表是存在的,并且它們的行數分別是多少。通過這些數據我們可以恢復出m_maskvalid成員和行數數組

    關鍵的CLR內部對象

    有了恢復.NET頭的思路,我們可以開始了解CLR內部對象了,通過它們來恢復.NET頭。這部分內容將展開介紹關鍵的CLR內部對象作為鋪墊。這些CLR內部對象我會省略掉很多無關的部分,而且不同版本CLR的定義也略有區別,所以列出的成員在結構體中的偏移是不一定的。關于具體如何使用,將會在下一部分詳細說明。

    Module

    Module類對應mscorlib里System.Reflection.RuntimeModule類的本機對象布局,定義在ceeload.h里。

     復制代碼 隱藏代碼
    class Module
    {
        PTR_CUTF8               m_pSimpleName;
        PTR_PEFile              m_file;
        MethodDesc              *m_pDllMain;
        Volatile          m_dwTransientFlags;
        Volatile          m_dwPersistedFlags;
        VASigCookieBlock        *m_pVASigCookieBlock;
        PTR_Assembly            m_pAssembly;
        mdFile                  m_moduleRef;
    };
    
    • m_pSimpleName是模塊名,值等于C#代碼的assembly.Module.Assembly.GetName().Name,在.NET Framework 4.5.3之前不存在這個成員。
    • m_file是指向PEFile結構的指針,可以用來獲取模塊基址和大小等信息,非常重要。
    • m_pDllMain是指向DllMain方法的指針,僅對C++/CLI生成的程序集有效。
    • m_pAssembly是指向Assembly結構的指針,這里不需要使用它。

    PEFile

    PEFile類CLR加載器的輸入,表示一個抽象的PE文件。它的子類是PEAssembly和PEModule。如果被加載為程序集,那就創建PEAssembly,如果用Assembly.LoadModule方法加載為模塊,那么就創建PEModule。在.NET Core里面,多模塊程序集特性被移除了。所以.NET Core里面只有PEAssembly,沒有PEModule了。

    PEFile有多種加載方式:

    1. HMODULE - PEFile是在響應“自發的”系統回調時加載的。只有通過LoadLibrary加載exe主模塊和IJW dll,或非托管代碼中存在靜態導入才會出現這種情況。
    2. Fusion loads - 這是最常見的情況。從Fusion中獲得路徑,并通過PEImage加載PEFile。
    3. Display name loads - 這些是基于元數據的綁定。
    4. Path loads - 從完整的絕對路徑加載
    5. Byte arrays - 由用戶代碼顯式加載。這也是通過PEImage加載的。
    6. Dynamic - 此時PEFile不是實際的PE映像,而是基于反射的模塊的占位符。
     復制代碼 隱藏代碼
    class PEFile
    {
        PTR_PEImage              m_identity;
        PTR_PEImage              m_openedILimage;
        PTR_PEImage              m_nativeImage;
        BOOL                     m_fCanUseNativeImage;
        BOOL                     m_MDImportIsRW_Debugger_Use_Only;
        Volatile           m_bHasPersistentMDImport;
        IMDInternalImport       *m_pMDImport;
        IMetaDataImport2        *m_pImporter;
        IMetaDataEmit           *m_pEmitter;
    };
    
    • m_identity是作為標識符的指向PEImage結構的指針。一般情況下不用這個成員,而是使用m_openedILimage。在PEFile::GetILimage函數里,如果m_openedILimage為空,m_identity的值會賦給m_openedILimage。
    • m_openedILimage是作為提供元數據的指向PEImage結構的指針。我們恢復.NET頭會使用這個成員獲取信息。
    • m_nativeImage是用于NGEN等情況的指向PEImage結構的指針。比如mscorlib.ni.dll這種NGEN創建的預編譯的模塊,就會加載并保存到m_nativeImage成員。
    • m_pMDImport是指向IMDInternalImport接口的指針,我們可以用這個接口讀取一些元數據信息。

    PEFile的子類PEAssembly和PEModule我們不需要過于關心,里面沒有什么可用的信息。通過觀察PEFile的成員,我們可以大概認為PEFile是對PEImage的包裝,封裝了各種情況下.NET程序集加載的結果。CLR只需要使用抽象的IMDInternalImport接口來獲取元數據即可,不需要關心PE映像的具體細節。

    PEImage

    PEImage是由CLR的“模擬LoadLibrary”機制加載的PE文件。PEImage可以加載為FLAT(與磁盤上的文件布局相同)或MAPPED(PE區段映射到虛擬地址)。

     復制代碼 隱藏代碼
    class PEImage
    {
        SString     m_path;
        LONG        m_refCount;
        SString     m_sModuleFileNameHintUsedByDac;
        BOOL        m_bIsTrustedNativeImage;
        BOOL        m_bIsNativeImageInstall;
        BOOL        m_bPassiveDomainOnly;
        SimpleRWLock *m_pLayoutLock;
        PTR_PEImageLayout m_pLayouts[IMAGE_COUNT];
        BOOL      m_bInHashMap;
        IMDInternalImport* m_pMDImport;
        IMDInternalImport* m_pNativeMDImport;
    };
    
    • m_path是PE映像的路徑。如果PEImage是通過文件加載的,那么m_path就是這個文件的路徑。如果PEImage是通過內存加載的,也就是使用了Assembly.Load(byte[])等方法加載,那么m_path就是空。
    • m_pLayouts保存了PEImageLayout指針的數組。PEImageLayout提供了具體的PE映像的布局信息,包括模塊基址和模塊大小。所以m_pLayouts是一個很重要的成員。
    • m_pMDImport是指向IMDInternalImport接口的指針,我們可以用這個接口讀取一些元數據信息。這個成員和PEFile的m_pMDImport可以認為是一樣的。

    PEImageLayout

    PEImageLayout是指具體的PE映像布局,有MappedImageLayout、LoadedImageLayout、FlatImageLayout等子類。子類的成員不需要關心,重要的部分都在基類PEImageLayout中。

     復制代碼 隱藏代碼
    class PEDecoder
    {
        TADDR               m_base;
        COUNT_T             m_size;
        ULONG               m_flags;
        PTR_IMAGE_NT_HEADERS   m_pNTHeaders;
        PTR_IMAGE_COR20_HEADER m_pCorHeader;
        PTR_CORCOMPILE_HEADER  m_pNativeHeader;
        PTR_READYTORUN_HEADER  m_pReadyToRunHeader;
    };
    
    class PEImageLayout : public PEDecoder
    {
        Volatile m_refCount;
        PEImage* m_pOwner;
        DWORD m_Layout;
    };
    
    • m_base是模塊基址。
    • m_size是模塊大小。
    • m_pCorHeader是指向IMAGE_COR20_HEADER結構的指針。被反Dump保護抹除的偏移就可以使用這個成員恢復。
    • m_Layout表示當前布局是什么類型,比如FLAT、MAPPED、LOADED。

    MDInternalRO && MDInternalRW

    這兩個類是CLR內部元數據接口IMDInternalImport的實現類。獲取了IMDInternalImport接口的指針意味著拿到了這兩個類的實例。通過這兩個類,我們可以獲取關于元數據表流和堆流的所有信息。

     復制代碼 隱藏代碼
    class MDInternalRO : public IMDInternalImport, IMDCommon
    {
        CLiteWeightStgdb   m_LiteWeightStgdb;
        CMethodSemanticsMap *m_pMethodSemanticsMap; // Possible array of method semantics pointers, ordered by method token.
        mdTypeDef           m_tdModule;         //  typedef value.
        LONG                m_cRefs;            // Ref count.
    };
    
    • m_LiteWeightStgdb是保存了元數據信息的成員,通過它可以讀取元數據信息從而恢復.NET頭。
     復制代碼 隱藏代碼
    class MDInternalRW : public IMDInternalImportENC, public IMDCommon
    {
        CLiteWeightStgdbRW  *m_pStgdb;
        mdTypeDef           m_tdModule;         //  typedef value.
        LONG                m_cRefs;            // Ref count.
        bool                m_fOwnStgdb;
        IUnknown            *m_pUnk;
        IUnknown            *m_pUserUnk;        // Release at shutdown.
        IMetaDataHelper     *m_pIMetaDataHelper;// pointer to cached public interface
        UTSemReadWrite      *m_pSemReadWrite;   // read write lock for multi-threading.
        bool                m_fOwnSem;          // Does MDInternalRW own this read write lock object?
    };
    
    • m_pStgdb和上面的MDInternalRO::m_LiteWeightStgdb一樣,是保存了元數據信息的成員,通過它可以讀取元數據信息從而恢復.NET頭。

    CLiteWeightStgdb && CLiteWeightStgdbRW

    這兩個是對CMiniMd和CMiniMdRW的包裝。CLiteWeightStgdbRW這個類不是非常重要,沒有恢復.NET頭需要的信息。實際上我們只需要CLiteWeightStgdb這個類。它們的定義如下。

     復制代碼 隱藏代碼
    template <class MiniMd>
    class CLiteWeightStgdb
    {
        MiniMd      m_MiniMd;               // embedded compress meta data schemas definition
        const void  *m_pvMd;                // Pointer to meta data.
        ULONG       m_cbMd;                 // Size of the meta data.
    }
    
    class CLiteWeightStgdbRW : public CLiteWeightStgdb
    {
        UINT32      m_cbSaveSize;               // Size of the saved streams.
        int         m_bSaveCompressed;          // If true, save as compressed stream (#-, not #~)
        VOID*       m_pImage;                   // Set in OpenForRead, NULL for anything but PE files
        DWORD       m_dwImageSize;              // On-disk size of image
        DWORD       m_dwPEKind;                 // The kind of PE - 0: not a PE.
        DWORD       m_dwMachine;                // Machine as defined in NT header.
        STORAGESTREAMLST *m_pStreamList;
        CLiteWeightStgdbRW *m_pNextStgdb;
        FILETYPE m_eFileType;
        WCHAR *  m_wszFileName;     // Database file name (NULL or non-empty string)
        DWORD    m_dwDatabaseLFT;   // Low bytes of the database file's last write time
        DWORD    m_dwDatabaseLFS;   // Low bytes of the database file's size
        StgIO *  m_pStgIO;          // For file i/o.
    }
    
    • m_MiniMd是CMiniMd和CMiniMdRW,下一小節會提到這兩個類。
    • m_pvMd是指向元數據的指針,對應CFF Explorer中.NET Directory的MetaData RVA。
    • m_cbMd是元數據的大小,對應CFF Explorer中.NET Directory的MetaData Size。值得注意的一點是對于CMiniMdRW,也就是未壓縮的表流,m_cbMd是無效的,我們需要自己計算元數據總大小。

    CMiniMd & CMiniMdRW

    CMiniMd是CLR內部的元數據Provider實現,與其類似的還有一個CMiniMdRW。兩者不同之處是,CMiniMd是用于#~這種已壓縮的表流,而CMiniMdRW是用于#-這種未壓縮的表流。

    從結構上來說它們有一個共同的基類CMiniMdBase。

     復制代碼 隱藏代碼
    class CMiniMdBase
    {
        CMiniMdSchema   m_Schema;                       // data header.
        ULONG           m_TblCount;                     // Tables in this database.
        BOOL            m_fVerifiedByTrustedSource;     // whether the data was verified by a trusted source
        CMiniTableDef   m_TableDefs[TBL_COUNT];
        ULONG           m_iStringsMask;
        ULONG           m_iGuidsMask;
        ULONG           m_iBlobsMask;
    };
    
    • m_Schema是上面提到的CMiniMdSchemaBase結構的子類,是用來恢復表流頭部的關鍵之一。

    CLR會為壓縮的表流使用CMiniMd,因為它不可擴充,結構體積更小,運行速度也更快。

     復制代碼 隱藏代碼
    class CMiniMd : public CMiniMdBase
    {
        MetaData::TableRO m_Tables[TBL_COUNT];
        struct MetaData::HotTablesDirectory * m_pHotTablesDirectory;
        MetaData::StringHeapRO m_StringHeap;
        MetaData::BlobHeapRO   m_BlobHeap;
        MetaData::BlobHeapRO   m_UserStringHeap;
        MetaData::GuidHeapRO   m_GuidHeap;
    };
    
    • m_Tables是保存了每一個元數據表的數組。數組元素類型TableRO內部保存了指向每一個元數據表起始地址的指針。用來恢復#~。
    • m_StringHeap是字符串流,保存了方法名、類名等元數據字符串。類型StringHeapRO的最終基類是StgPoolSeg,下文會介紹。用來恢復#Strings。
    • m_BlobHeap是二進制對象流。類型BlobHeapRO的最終基類是StgPoolSeg,下文會介紹。用來恢復#Blob。
    • m_UserStringHeap是用戶字符串流,保存了用戶定義的字符串,如'string s = "Hello World"'。類型BlobHeapRO的最終基類是StgPoolSeg,下文會介紹。用來恢復#US。
    • m_GuidHeap是GUID流。類型GuidHeapRO的最終基類是StgPoolSeg,下文會介紹。用來恢復#GUID。

    對于未壓縮的表流#-,CLR會使用CMiniMdRW。它是可以擴充追加數據的。下面列出的只是一部分成員,還有很多沒列出的。總之就是比CMiniMd大而且復雜了不少。

     復制代碼 隱藏代碼
    class CMiniMdRW : public CMiniMdBase
    {
        CMemberRefHash *m_pMemberRefHash;
        CMemberDefHash *m_pMemberDefHash;
        CLookUpHash * m_pLookUpHashs[TBL_COUNT];
        MapSHash m_StringPoolOffsetHash;
        CMetaDataHashBase *m_pNamedItemHash;
        ULONG       m_maxRid;               // Highest RID so far allocated.
        ULONG       m_limRid;               // Limit on RID before growing.
        ULONG       m_maxIx;                // Highest pool index so far.
        ULONG       m_limIx;                // Limit on pool index before growing.
        enum        {eg_ok, eg_grow, eg_grown} m_eGrow; // Is a grow required? done?
        MetaData::TableRW m_Tables[TBL_COUNT];
        VirtualSort *m_pVS[TBL_COUNT];      // Virtual sorters, one per table, but sparse.
        MetaData::StringHeapRW m_StringHeap;
        MetaData::BlobHeapRW   m_BlobHeap;
        MetaData::BlobHeapRW   m_UserStringHeap;
        MetaData::GuidHeapRW   m_GuidHeap;
        IMapToken  *m_pHandler;     // Remap handler.
        ULONG m_cbSaveSize;         // Estimate of save size.
    };
    
    • m_Tables是保存了每一個元數據表的數組。數組元素類型TableRW內部是一個StgPoolSeg的子類。用來恢復#~。
    • m_StringHeap是字符串流,保存了方法名、類名等元數據字符串。類型StringHeapRW的最終基類是StgPoolSeg,下文會介紹。用來恢復#Strings。
    • m_BlobHeap是二進制對象流。類型BlobHeapRW的最終基類是StgPoolSeg,下文會介紹。用來恢復#Blob。
    • m_UserStringHeap是用戶字符串流,保存了用戶定義的字符串,如'string s = "Hello World"'。類型BlobHeapRW的最終基類是StgPoolSeg,下文會介紹。用來恢復#US。
    • m_GuidHeap是GUID流。類型GuidHeapRW的最終基類是StgPoolSeg,下文會介紹。用來恢復#GUID。

    這里RW和RO的區別就是,RW是可寫的,可以在數據段后再追加數據段,而RO是只讀的,初始化之后就不能更改了。

    CMiniTableDef

    CMiniTableDef是表示元數據表定義的結構,里面保存了表的字段、大小、行數,其中行數是我們用來恢復.NET頭的。

     復制代碼 隱藏代碼
    struct CMiniColDef
    {
        BYTE        m_Type;                 // Type of the column.
        BYTE        m_oColumn;              // Offset of the column.
        BYTE        m_cbColumn;             // Size of the column.
    };
    
    struct CMiniTableDef
    {
        CMiniColDef *m_pColDefs;            // Array of field defs.
        BYTE        m_cCols;                // Count of columns in the table.
        BYTE        m_iKey;                 // Column which is the key, if any.
        USHORT      m_cbRec;                // Size of the records.
    };
    
    • m_pColDefs是表示表內有哪些字段的數組。
    • m_cCols是表內字段數量,也就是m_pColDefs數組的長度。
    • m_cbRec是表的行數,這個是用來恢復.NET頭中表流頭部的關鍵之一。

    StgPoolSeg

    上面提到的StringHeapRO、BlobHeapRO、GuidHeapRO、StringHeapRW、BlobHeapRW、GuidHeapRW都是最終繼承自StgPoolSeg的子類。關鍵的保存數據位置和大小的成員就在基類StgPoolSeg中。所以了解StgPoolSeg的機構即可。

     復制代碼 隱藏代碼
    class StgPoolSeg
    {
        BYTE       *m_pSegData;     // Pointer to the data.
        StgPoolSeg *m_pNextSeg;     // Pointer to next segment, or NULL.
        // Size of the segment buffer. If this is last segment (code:m_pNextSeg is NULL), then it's the 
        // allocation size. If this is not the last segment, then this is shrinked to segment data size 
        // (code:m_cbSegNext).
        ULONG       m_cbSegSize;
        ULONG       m_cbSegNext;    // Offset of next available byte in segment. Segment relative.
    };
    

    通過CLR內部對象恢復.NET頭

    在大致了解反Dump保護可能抹除的數據和CLR內部對象后,我們就可以通過代碼定位CLR內部對象,然后恢復.NET頭了。這里我們做最極端的假設,反Dump技術抹除了所有可能的數據,我們要依賴CLR內部對象恢復它們。我們按順序,從外向內地一層一層恢復。

    以下提到的代碼在文末都有完整地實現。

    定位IMAGE_COR20_HEADER

    對于Data Directories的.NET MetaData Directory。

    我們可以使用反射API得到System.Reflection.RuntimeModule。然后使用反射API獲取它的私有字段m_pData。這個字段的值是指向CLR內部對象Module的指針。

    獲取Module對象后,我們使用Module::m_file,得到PEFile對象,這個PEFile是PEAssembly和PEModule,但是實際上只需要使用基類PEFile的內容。

    然后我們找到PEFile::m_openedILimage,用來拿到作為PEFile后端的PEImage。

    最后我們從PEImage中,獲取PEImageLayout即可拿到IMAGE_COR20_HEADER,也就是Data Directories的.NET MetaData Directory。但是PEImage中有好幾個PEImageLayout,我們需要的布局是LOADED。LOADED指用來提供IL代碼的那一個,并不是一個具體的布局如FLAT、MAPPED,而是一個抽象的。CLR會從已有的布局里面選取一個已經打開的,作為LOADED布局。

    簡單地用C#代碼表示就是:

     復制代碼 隱藏代碼
    var module = assembly.Module.m_pData;
    // Get native Module object
    var pCorHeader = module->m_file->m_openedILimage.m_pLayouts[IMAGE_LOADED]->m_pCorHeader;
    // Get IMAGE_COR20_HEADER
    

    然后搜索是這些成員偏移的關鍵代碼:

     復制代碼 隱藏代碼
    static Pointer ScanLoadedImageLayoutPointer(out bool isMappedLayoutExisting) {
        const bool InMemory = true;
    
        var assemblyFlags = InMemory ? TestAssemblyFlags.InMemory : 0;
        var assembly = TestAssemblyManager.GetAssembly(assemblyFlags);
        nuint module = assembly.ModuleHandle;
        Utils.Check((Module*)module, assembly.Module.Assembly.GetName().Name);
        // Get native Module object
    
        uint m_file_Offset;
        if (RuntimeEnvironment.Version >= RuntimeVersion.Fx453)
            m_file_Offset = (uint)((nuint)(&Module_453.Dummy->m_file) - (nuint)Module_453.Dummy);
        else
            m_file_Offset = (uint)((nuint)(&Module_20.Dummy->m_file) - (nuint)Module_20.Dummy);
        nuint m_file = *(nuint*)(module + m_file_Offset);
        Utils.Check((PEFile*)m_file);
        // Module.m_file
    
        uint m_openedILimage_Offset = (uint)((nuint)(&PEFile.Dummy->m_openedILimage) - (nuint)PEFile.Dummy);
        nuint m_openedILimage = *(nuint*)(m_file + m_openedILimage_Offset);
        Utils.Check((PEImage*)m_openedILimage, InMemory);
        // PEFile.m_openedILimage
    
        nuint m_pMDImport = MetadataImport.Create(assembly.Module).This;
        uint m_pMDImport_Offset;
        bool found = false;
        for (m_pMDImport_Offset = 0x40; m_pMDImport_Offset < 0xD0; m_pMDImport_Offset += 4) {
            if (*(nuint*)(m_openedILimage + m_pMDImport_Offset) != m_pMDImport)
                continue;
            found = true;
            break;
        }
        Utils.Check(found);
        // PEFile.m_pMDImport (not use, just for locating previous member 'm_pLayouts')
        isMappedLayoutExisting = false;
        uint m_pLayouts_Loaded_Offset = m_pMDImport_Offset - 4 - (uint)sizeof(nuint);
        uint m_pLayouts_Offset_Min = m_pLayouts_Loaded_Offset - (4 * (uint)sizeof(nuint));
        nuint actualModuleBase = ReflectionHelpers.GetNativeModuleHandle(assembly.Module);
        found = false;
        for (; m_pLayouts_Loaded_Offset >= m_pLayouts_Offset_Min; m_pLayouts_Loaded_Offset -= 4) {
            var m_pLayout = *(RuntimeDefinitions.PEImageLayout**)(m_openedILimage + m_pLayouts_Loaded_Offset);
            if (!Memory.TryReadUIntPtr((nuint)m_pLayout, out _))
                continue;
            if (!Memory.TryReadUIntPtr(m_pLayout->__vfptr, out _))
                continue;
            if (actualModuleBase != m_pLayout->__base.m_base)
                continue;
            Debug2.Assert(InMemory);
            var m_pLayout_prev1 = *(RuntimeDefinitions.PEImageLayout**)(m_openedILimage + m_pLayouts_Loaded_Offset - (uint)sizeof(nuint));
            var m_pLayout_prev2 = *(RuntimeDefinitions.PEImageLayout**)(m_openedILimage + m_pLayouts_Loaded_Offset - (2 * (uint)sizeof(nuint)));
            if (m_pLayout_prev2 == m_pLayout)
                isMappedLayoutExisting = true;
            else if (m_pLayout_prev1 == m_pLayout)
                isMappedLayoutExisting = false; // latest .NET, TODO: update comment when .NET 7.0 released
            found = true;
            break;
        }
        Utils.Check(found);
        nuint m_pLayouts_Loaded = *(nuint*)(m_openedILimage + m_pLayouts_Loaded_Offset);
        Utils.Check((RuntimeDefinitions.PEImageLayout*)m_pLayouts_Loaded, InMemory);
        // PEImage.m_pLayouts[IMAGE_LOADED]
    
        uint m_pCorHeader_Offset = (uint)((nuint)(&RuntimeDefinitions.PEImageLayout.Dummy->__base.m_pCorHeader) - (nuint)RuntimeDefinitions.PEImageLayout.Dummy);
        nuint m_pCorHeader = *(nuint*)(m_pLayouts_Loaded + m_pCorHeader_Offset);
        Utils.Check((IMAGE_COR20_HEADER*)m_pCorHeader);
        // PEImageLayout.m_pCorHeader
    
        var pointer = new Pointer(new[] {
            m_file_Offset,
            m_openedILimage_Offset,
            m_pLayouts_Loaded_Offset
        });
        Utils.Check(Utils.Verify(pointer, null, p => Memory.TryReadUIntPtr(p + (uint)sizeof(nuint), out nuint @base) && (ushort)home.php?mod=space&uid=1282447 == 0));
        Utils.Check(Utils.Verify(Utils.WithOffset(pointer, m_pCorHeader_Offset), null, p => Memory.TryReadUInt32(p, out uint cb) && cb == 0x48));
        return pointer;
    }
    

    定位CLiteWeightStgdb

    在定位MetaData之前,我們獲取元數據相關信息需要先定位到CLiteWeightStgdb結構。

    簡單表示:

     復制代碼 隱藏代碼
    var pMDImport = GetMetadataImport(assembly.Module);
    // Get IMDInternalImport
    var pStgdb = null;
    if (table_stream_is_compressed)
        pStgdb =  &(((MDInternalRO*)pMDImport)->m_LiteWeightStgdb);
    else
        pStgdb =  ((MDInternalRW*)pMDImport->m_pStgdb;
    // Get CLiteWeightStgdb
    

    關鍵代碼:

     復制代碼 隱藏代碼
    static Pointer ScanLiteWeightStgdbPointer(bool uncompressed, out nuint vfptr) {
        const bool InMemory = false;
    
        var assemblyFlags = InMemory ? TestAssemblyFlags.InMemory : 0;
        if (uncompressed)
            assemblyFlags |= TestAssemblyFlags.Uncompressed;
        var assembly = TestAssemblyManager.GetAssembly(assemblyFlags);
        nuint module = assembly.ModuleHandle;
        Utils.Check((Module*)module, assembly.Module.Assembly.GetName().Name);
        // Get native Module object
    
        uint m_file_Offset;
        if (RuntimeEnvironment.Version >= RuntimeVersion.Fx453)
            m_file_Offset = (uint)((nuint)(&Module_453.Dummy->m_file) - (nuint)Module_453.Dummy);
        else
            m_file_Offset = (uint)((nuint)(&Module_20.Dummy->m_file) - (nuint)Module_20.Dummy);
        nuint m_file = *(nuint*)(module + m_file_Offset);
        Utils.Check((PEFile*)m_file);
        // Module.m_file
    
        var metadataImport = MetadataImport.Create(assembly.Module);
        vfptr = metadataImport.Vfptr;
        nuint m_pMDImport = metadataImport.This;
        uint m_pMDImport_Offset;
        bool found = false;
        for (m_pMDImport_Offset = 0; m_pMDImport_Offset < 8 * (uint)sizeof(nuint); m_pMDImport_Offset += 4) {
            if (*(nuint*)(m_file + m_pMDImport_Offset) != m_pMDImport)
                continue;
            found = true;
            break;
        }
        Utils.Check(found);
        // PEFile.m_pMDImport
    
        uint m_pStgdb_Offset = 0;
        if (uncompressed) {
            if (RuntimeEnvironment.Version >= RuntimeVersion.Fx45)
                m_pStgdb_Offset = (uint)((nuint)(&MDInternalRW_45.Dummy->m_pStgdb) - (nuint)MDInternalRW_45.Dummy);
            else
                m_pStgdb_Offset = (uint)((nuint)(&MDInternalRW_20.Dummy->m_pStgdb) - (nuint)MDInternalRW_20.Dummy);
        }
        // MDInternalRW.m_pStgdb
    
        var pointer = new Pointer(new[] {
            m_file_Offset,
            m_pMDImport_Offset
        });
        if (m_pStgdb_Offset != 0)
            pointer.Add(m_pStgdb_Offset);
        Utils.Check(Utils.Verify(pointer, uncompressed, p => Memory.TryReadUInt32(p, out _)));
        return pointer;
    }
    

    定位元數據

    在定位了IMAGE_COR20_HEADER之后,里面有一個最關鍵的成員MetaData需要定位。

    簡單表示:

     復制代碼 隱藏代碼
    var pMDImport = GetMetadataImport(assembly.Module);
    // Get IMDInternalImport
    var m_pvMd = null;
    if (table_stream_is_compressed)
        m_pvMd =  ((MDInternalRO*)pMDImport)->m_LiteWeightStgdb.m_pvMd;
    else
        m_pvMd =  ((MDInternalRW*)pMDImport->m_pStgdb->m_pvMd;
    // Get metadata address
    

    關鍵代碼:

     復制代碼 隱藏代碼
    static void ScanMetadataOffsets(Pointer stgdbPointer, bool uncompressed, out uint metadataAddressOffset, out uint metadataSizeOffset) {
        const bool InMemory = false;
    
        var assemblyFlags = InMemory ? TestAssemblyFlags.InMemory : 0;
        if (uncompressed)
            assemblyFlags |= TestAssemblyFlags.Uncompressed;
        var assembly = TestAssemblyManager.GetAssembly(assemblyFlags);
        nuint module = assembly.ModuleHandle;
        Utils.Check((Module*)module, assembly.Module.Assembly.GetName().Name);
        // Get native Module object
    
        nuint pStgdb = Utils.ReadUIntPtr(stgdbPointer, module);
        var peInfo = PEInfo.Create(assembly.Module);
        var imageLayout = peInfo.MappedLayout.IsInvalid ? peInfo.LoadedLayout : peInfo.MappedLayout;
        var m_pCorHeader = (IMAGE_COR20_HEADER*)imageLayout.CorHeaderAddress;
        nuint m_pvMd = imageLayout.ImageBase + m_pCorHeader->MetaData.VirtualAddress;
        uint m_cbMd = uncompressed ? 0x1c : m_pCorHeader->MetaData.Size;
        // *pcb = sizeof(STORAGESIGNATURE) + pStorage->GetVersionStringLength();
        // TODO: we should calculate actual metadata size for uncompressed metadata
        uint start = uncompressed ? (sizeof(nuint) == 4 ? 0x1000u : 0x19A0) : (sizeof(nuint) == 4 ? 0x350u : 0x5B0);
        uint end = uncompressed ? (sizeof(nuint) == 4 ? 0x1200u : 0x1BA0) : (sizeof(nuint) == 4 ? 0x39Cu : 0x5FC);
        uint m_pvMd_Offset = 0;
        for (uint offset = start; offset <= end; offset += 4) {
            if (*(nuint*)(pStgdb + offset) != m_pvMd)
                continue;
            if (*(uint*)(pStgdb + offset + (uint)sizeof(nuint)) != m_cbMd)
                continue;
            m_pvMd_Offset = offset;
            break;
        }
        Utils.Check(m_pvMd_Offset != 0);
    
        Utils.Check(Utils.Verify(Utils.WithOffset(stgdbPointer, m_pvMd_Offset), uncompressed, p => Memory.TryReadUInt32(p, out uint signature) && signature == 0x424A5342));
        metadataAddressOffset = m_pvMd_Offset;
        metadataSizeOffset = m_pvMd_Offset + (uint)sizeof(nuint);
    }
    

    定位元數據表流頭部

    表流相對來說麻煩一些,有更多的數據要填。首先是獲取表流Schema。

    簡單表示:

     復制代碼 隱藏代碼
    var pMDImport = GetMetadataImport(assembly.Module);
    // Get IMDInternalImport
    var pMiniMd = null;
    if (table_stream_is_compressed)
        pMiniMd =  &(((MDInternalRO*)pMDImport)->m_LiteWeightStgdb.m_MiniMd);
    else
        pMiniMd =  &(((MDInternalRW*)pMDImport->m_pStgdb->m_MiniMd);
    // Get CMiniMd
    var m_Schema = pMiniMd->m_Schema;
    // Get metadata schema
    

    關鍵代碼:

     復制代碼 隱藏代碼
    static void ScanSchemaOffset(Pointer stgdbPointer, MiniMetadataInfo info, bool uncompressed, out uint schemaOffset) {
        const bool InMemory = false;
    
        var assemblyFlags = InMemory ? TestAssemblyFlags.InMemory : 0;
        if (uncompressed)
            assemblyFlags |= TestAssemblyFlags.Uncompressed;
        var assembly = TestAssemblyManager.GetAssembly(assemblyFlags);
        nuint module = assembly.ModuleHandle;
        Utils.Check((Module*)module, assembly.Module.Assembly.GetName().Name);
        // Get native Module object
    
        nuint pStgdb = Utils.ReadUIntPtr(stgdbPointer, module);
        for (schemaOffset = 0; schemaOffset < 0x30; schemaOffset += 4) {
            if (*(ulong*)(pStgdb + schemaOffset) != info.Header1)
                continue;
            if (*(ulong*)(pStgdb + schemaOffset + 0x08) != info.ValidMask)
                continue;
            if (*(ulong*)(pStgdb + schemaOffset + 0x10) != info.SortedMask)
                continue;
            break;
        }
        Utils.Check(schemaOffset != 0x30);
        // CMiniMdBase.m_Schema
    }
    

    在獲取Schema后,我們還要獲取目標模塊存在哪些元數據表,行數分別是多少。由于CLR內部沒有保存行數,而是直接保存了指向每個元數據表的指針,所以我們需要獲取每個元數據表的地址,然后計算通過表的大小除以每行大小,計算求出每個元數據表的行數。

    簡單表示:

     復制代碼 隱藏代碼
    var pMDImport = GetMetadataImport(assembly.Module);
    // Get IMDInternalImport
    var pMiniMd = null;
    if (table_stream_is_compressed)
        pMiniMd =  &(((MDInternalRO*)pMDImport)->m_LiteWeightStgdb.m_MiniMd);
    else
        pMiniMd =  &(((MDInternalRW*)pMDImport->m_pStgdb->m_MiniMd);
    // Get CMiniMd
    var m_TableDefs = pMiniMd->m_TableDefs;
    // Get metadata table definitions (to get row size)
    var m_Tables = pMiniMd->m_Tables;
    // Get metadata tables (to get table address)
    

    關鍵代碼:

     復制代碼 隱藏代碼
    static void ScanTableDefsOffsets(Pointer stgdbPointer, bool uncompressed, uint schemaOffset, out uint tableCountOffset, out uint tableDefsOffset) {
        const bool InMemory = false;
    
        var assemblyFlags = InMemory ? TestAssemblyFlags.InMemory : 0;
        if (uncompressed)
            assemblyFlags |= TestAssemblyFlags.Uncompressed;
        var assembly = TestAssemblyManager.GetAssembly(assemblyFlags);
        nuint module = assembly.ModuleHandle;
        Utils.Check((Module*)module, assembly.Module.Assembly.GetName().Name);
        // Get native Module object
    
        nuint pSchema = Utils.ReadPointer(Utils.WithOffset(stgdbPointer, schemaOffset), module);
        nuint p = pSchema + (uint)sizeof(CMiniMdSchema);
        uint m_TblCount = *(uint*)p;
        tableCountOffset = schemaOffset + (uint)(p - pSchema);
        Utils.Check(m_TblCount == TBL_COUNT_V1 || m_TblCount == TBL_COUNT_V2);
        // CMiniMdBase.m_TblCount
    
        if (RuntimeEnvironment.Version >= RuntimeVersion.Fx40)
            p += (uint)((nuint)(&CMiniMdBase_40.Dummy->m_TableDefs) - (nuint)(&CMiniMdBase_40.Dummy->m_TblCount));
        else
            p += (uint)((nuint)(&CMiniMdBase_20.Dummy->m_TableDefs) - (nuint)(&CMiniMdBase_20.Dummy->m_TblCount));
        tableDefsOffset = schemaOffset + (uint)(p - pSchema);
        var m_TableDefs = (CMiniTableDef*)p;
        for (int i = 0; i < TBL_COUNT; i++)
            Utils.Check(Memory.TryReadUInt32((nuint)m_TableDefs[i].m_pColDefs, out _));
        // CMiniMdBase.m_TableDefs
    }
    
    static void ScanTableOffset(Pointer stgdbPointer, MiniMetadataInfo info, bool uncompressed, out uint tableAddressOffset, out uint nextTableOffset) {
        const bool InMemory = false;
    
        var assemblyFlags = InMemory ? TestAssemblyFlags.InMemory : 0;
        if (uncompressed)
            assemblyFlags |= TestAssemblyFlags.Uncompressed;
        var assembly = TestAssemblyManager.GetAssembly(assemblyFlags);
        nuint module = assembly.ModuleHandle;
        Utils.Check((Module*)module, assembly.Module.Assembly.GetName().Name);
        // Get native Module object
    
        tableAddressOffset = 0;
        nextTableOffset = 0;
        nuint pStgdb = Utils.ReadUIntPtr(stgdbPointer, module);
        uint start = uncompressed ? (sizeof(nuint) == 4 ? 0x2A0u : 0x500) : (sizeof(nuint) == 4 ? 0x200u : 0x350);
        uint end = uncompressed ? (sizeof(nuint) == 4 ? 0x4A0u : 0x800) : (sizeof(nuint) == 4 ? 0x300u : 0x450);
        for (uint offset = start; offset < end; offset += 4) {
            nuint pFirst = pStgdb + offset;
            if (*(nuint*)pFirst != info.TableAddress[0])
                continue;
    
            uint start2 = 4;
            uint end2 = uncompressed ? 0x100u : 0x20;
            uint offset2 = start2;
            for (; offset2 < end2; offset2 += 4) {
                if (*(nuint*)(pFirst + offset2) != info.TableAddress[1])
                    continue;
                if (*(nuint*)(pFirst + (2 * offset2)) != info.TableAddress[2])
                    continue;
                break;
            }
            if (offset2 == end2)
                continue;
    
            tableAddressOffset = offset;
            nextTableOffset = offset2;
            break;
        }
        Utils.Check(tableAddressOffset != 0);
        Utils.Check(nextTableOffset != 0);
        // CMiniMd.m_Tables
    }
    

    定位元數據堆流

    最后我們定位元數據堆流,也就是#Strings、#US、#GUID、#Blob這四個堆流。還原的時候比較簡單,我們只需要把這4個堆流的偏移、大小和名字寫入到.NET頭。

    簡單表示:

     復制代碼 隱藏代碼
    var pMDImport = GetMetadataImport(assembly.Module);
    // Get IMDInternalImport
    var pMiniMd = null;
    if (table_stream_is_compressed)
        pMiniMd =  &(((MDInternalRO*)pMDImport)->m_LiteWeightStgdb.m_MiniMd);
    else
        pMiniMd =  &(((MDInternalRW*)pMDImport->m_pStgdb->m_MiniMd);
    // Get CMiniMd
    var m_StringHeap = pMiniMd->m_StringHeap;
    // Get #Strings
    var m_BlobHeap = pMiniMd->m_BlobHeap;
    // Get #Blob
    var m_UserStringHeap = pMiniMd->m_UserStringHeap;
    // Get #US
    var m_GuidHeap = pMiniMd->m_GuidHeap;
    // Get #GUID
    

    關鍵代碼:

     復制代碼 隱藏代碼
    static void ScanHeapOffsets(Pointer stgdbPointer, MiniMetadataInfo info, bool uncompressed, out uint[] heapAddressOffsets, out uint[] heapSizeOffsets) {
        const bool InMemory = false;
    
        var assemblyFlags = InMemory ? TestAssemblyFlags.InMemory : 0;
        if (uncompressed)
            assemblyFlags |= TestAssemblyFlags.Uncompressed;
        var assembly = TestAssemblyManager.GetAssembly(assemblyFlags);
        nuint module = assembly.ModuleHandle;
        Utils.Check((Module*)module, assembly.Module.Assembly.GetName().Name);
        // Get native Module object
    
        nuint pStgdb = Utils.ReadUIntPtr(stgdbPointer, module);
        uint start = uncompressed ? (sizeof(nuint) == 4 ? 0xD00u : 0x1500) : (sizeof(nuint) == 4 ? 0x2A0u : 0x500);
        uint end = uncompressed ? (sizeof(nuint) == 4 ? 0x1000u : 0x1900) : (sizeof(nuint) == 4 ? 0x3A0u : 0x600);
        heapAddressOffsets = new uint[4];
        heapSizeOffsets = new uint[heapAddressOffsets.Length];
        int found = 0;
        for (uint offset = start; offset < end; offset += 4) {
            nuint address = *(nuint*)(pStgdb + offset);
            uint size = *(uint*)(pStgdb + offset + (2 * (uint)sizeof(nuint)));
            if (address == info.StringHeapAddress) {
                Utils.Check(info.StringHeapSize - 8 < size && size <= info.StringHeapSize);
                Utils.Check(heapAddressOffsets[0] == 0);
                heapAddressOffsets[StringHeapIndex] = offset;
                heapSizeOffsets[StringHeapIndex] = offset + (2 * (uint)sizeof(nuint));
                found++;
            }
            else if (address == info.UserStringHeapAddress) {
                Utils.Check(info.UserStringHeapSize - 8 < size && size <= info.UserStringHeapSize);
                Utils.Check(heapAddressOffsets[1] == 0);
                heapAddressOffsets[UserStringsHeapIndex] = offset;
                heapSizeOffsets[UserStringsHeapIndex] = offset + (2 * (uint)sizeof(nuint));
                found++;
            }
            else if (address == info.GuidHeapAddress) {
                Utils.Check(info.GuidHeapSize - 8 < size && size <= info.GuidHeapSize);
                Utils.Check(heapAddressOffsets[2] == 0);
                heapAddressOffsets[GuidHeapIndex] = offset;
                heapSizeOffsets[GuidHeapIndex] = offset + (2 * (uint)sizeof(nuint));
                found++;
            }
            else if (address == info.BlobHeapAddress) {
                Utils.Check(info.BlobHeapSize - 8 < size && size <= info.BlobHeapSize);
                Utils.Check(heapAddressOffsets[3] == 0);
                heapAddressOffsets[BlobHeapIndex] = offset;
                heapSizeOffsets[BlobHeapIndex] = offset + (2 * (uint)sizeof(nuint));
                found++;
            }
        }
        Utils.Check(found == 4);
        // Find heeap info offsets
    
        for (int i = 0; i < heapAddressOffsets.Length; i++)
            Utils.Check(Utils.Verify(Utils.WithOffset(stgdbPointer, heapAddressOffsets[i]), uncompressed, p => Memory.TryReadUInt32(p, out _)));
    }
    

    恢復.NET頭

    在尋找完需要的CLR內部對象的成員偏移后,我們就可以通過這些信息來還原.NET頭了。

     復制代碼 隱藏代碼
    static unsafe void FixDotNetHeaders(byte[] data, MetadataInfo metadataInfo, PEImageLayout imageLayout) {
        fixed (byte* p = data) {
            var pNETDirectory = (IMAGE_DATA_DIRECTORY*)(p + GetDotNetDirectoryRVA(data));
            pNETDirectory->VirtualAddress = (uint)imageLayout.CorHeaderAddress;
            pNETDirectory->Size = (uint)sizeof(IMAGE_COR20_HEADER);
            // Set Data Directories
            var pCor20Header = (IMAGE_COR20_HEADER*)(p + (uint)imageLayout.CorHeaderAddress);
            pCor20Header->cb = (uint)sizeof(IMAGE_COR20_HEADER);
            pCor20Header->MajorRuntimeVersion = 0x2;
            pCor20Header->MinorRuntimeVersion = 0x5;
            pCor20Header->MetaData.VirtualAddress = (uint)metadataInfo.MetadataAddress;
            pCor20Header->MetaData.Size = GetMetadataSize(metadataInfo);
            // Set .NET Directory
            var pStorageSignature = (STORAGESIGNATURE*)(p + (uint)metadataInfo.MetadataAddress);
            pStorageSignature->lSignature = 0x424A5342;
            pStorageSignature->iMajorVer = 0x1;
            pStorageSignature->iMinorVer = 0x1;
            pStorageSignature->iExtraData = 0x0;
            pStorageSignature->iVersionString = 0xC;
            var versionString = Encoding.ASCII.GetBytes("v4.0.30319");
            for (int i = 0; i < versionString.Length; i++)
                pStorageSignature->pVersion[i] = versionString[i];
            // versionString僅僅占位用,程序集具體運行時版本用dnlib獲取
            // Set StorageSignature
            var pStorageHeader = (STORAGEHEADER*)((byte*)pStorageSignature + 0x10 + pStorageSignature->iVersionString);
            pStorageHeader->fFlags = 0x0;
            pStorageHeader->pad = 0x0;
            pStorageHeader->iStreams = 0x5;
            // Set StorageHeader
            var pStreamHeader = (uint*)((byte*)pStorageHeader + sizeof(STORAGEHEADER));
            var tableStream = metadataInfo.TableStream;
            if (!tableStream.IsInvalid) {
                *pStreamHeader = (uint)tableStream.Address;
                *pStreamHeader -= (uint)metadataInfo.MetadataAddress;
                pStreamHeader++;
                *pStreamHeader = tableStream.Length;
                pStreamHeader++;
                *pStreamHeader = tableStream.IsCompressed ? 0x00007E23u : 0x000002D23;
                pStreamHeader++;
            }
            // Set #~ or #-
            var stringHeap = metadataInfo.StringHeap;
            if (!stringHeap.IsInvalid) {
                *pStreamHeader = (uint)stringHeap.Address;
                *pStreamHeader -= (uint)metadataInfo.MetadataAddress;
                pStreamHeader++;
                *pStreamHeader = stringHeap.Length;
                pStreamHeader++;
                *pStreamHeader = 0x72745323;
                pStreamHeader++;
                *pStreamHeader = 0x73676E69;
                pStreamHeader++;
                *pStreamHeader = 0x00000000;
                pStreamHeader++;
            }
            // Set #Strings
            var userStringHeap = metadataInfo.UserStringHeap;
            if (!userStringHeap.IsInvalid) {
                *pStreamHeader = (uint)userStringHeap.Address;
                *pStreamHeader -= (uint)metadataInfo.MetadataAddress;
                pStreamHeader++;
                *pStreamHeader = userStringHeap.Length;
                pStreamHeader++;
                *pStreamHeader = 0x00535523;
                pStreamHeader++;
            }
            // Set #US
            var guidHeap = metadataInfo.GuidHeap;
            if (!guidHeap.IsInvalid) {
                *pStreamHeader = (uint)guidHeap.Address;
                *pStreamHeader -= (uint)metadataInfo.MetadataAddress;
                pStreamHeader++;
                *pStreamHeader = guidHeap.Length;
                pStreamHeader++;
                *pStreamHeader = 0x49554723;
                pStreamHeader++;
                *pStreamHeader = 0x00000044;
                pStreamHeader++;
            }
            // Set #GUID
            var blobHeap = metadataInfo.BlobHeap;
            if (!blobHeap.IsInvalid) {
                *pStreamHeader = (uint)blobHeap.Address;
                *pStreamHeader -= (uint)metadataInfo.MetadataAddress;
                pStreamHeader++;
                *pStreamHeader = blobHeap.Length;
                pStreamHeader++;
                *pStreamHeader = 0x6F6C4223;
                pStreamHeader++;
                *pStreamHeader = 0x00000062;
                pStreamHeader++;
            }
            // Set #GUID
            switch (GetCorLibVersion(data).Major) {
            case 2:
                versionString = Encoding.ASCII.GetBytes("v2.0.50727");
                break;
            case 4:
                versionString = Encoding.ASCII.GetBytes("v4.0.30319");
                break;
            default:
                throw new NotSupportedException();
            }
            for (int i = 0; i < versionString.Length; i++)
                pStorageSignature->pVersion[i] = versionString[i];
            // Re set Version
        }
    }
    

    源碼與成品下載

    這個方法已經在我最新的ExtremeDumper里面實現了,可以實現對.NET程序集反Dump保護繞過。

    元數據定位的代碼:https://github.com/wwh1004/MetadataLocator

    通過CLR內部對象還原.NET頭:https://github.com/wwh1004/ExtremeDumper/tree/master/ExtremeDumper.AntiAntiDump

    元數據sizeof
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    .NET下的反Dump手段比較單一,無非是在運行后對PE頭中的.NET部分進行抹除。由于CLR在加載程序集時已經保存了所有.NET數據的偏移和大小,抹除這部分.NET頭對程序的運行沒有任何影響。
    Il2Cpp介紹unity作為兩大游戲引擎之一,其安全性也值得被關注。另一個文件是global-metadata.dat,該文件保存了一些重要的字符串信息和一些數據,用于il2cpp的動態特性,例如反射等。
    musl libc 是一個專門為嵌入式系統開發的輕量級 libc 庫,以簡單、輕量和高效率為特色。
    快速定位windows堆溢出
    2022-08-05 16:21:14
    為了防止堆塊的管理信息被覆蓋,除了在堆塊的用戶數據區前面儲存堆塊信息,頁堆還會在節點池中為每個堆塊記錄一個DPH_HEAP_BLOCK結構,簡稱為DPH節點結構。
    Windows SMB Ghost CVE-2020-0796漏洞分析與利用(二)
    跟php pwn一樣,以前遇到這樣的pwn直接都不看的,經過了解之后發現,老版本的Musl libc和新版本之間差距還比較大。結合最近幾次比賽中出現的Musl pwn,學習一下新老版本的Musl libc姿勢。
    在這個層中,來自upper層的更改會覆蓋lower層的相應文件。對于同名文件upper層中的文件優先級更高。一個Overlay文件系統可以有一個或多個lower層。此層擁有寫時復制特性,當想要修改lower層不可修改的fileC時,先拷貝副本到upper層,修改后呈現到merged視圖。OverlayFS 操作流程掛載OverlayFS文件系統mkdir lower upper work merged. sudo mount -t overlay -o lowerdir=lower,upperdir=upper,workdir=work overlay merged # 掛載OverlayFS
    ETW的攻與防
    2022-06-07 16:11:58
    前言ETW全稱為Event Tracing for Windows,即windows事件跟蹤,它是Windows提供的原生的事件跟蹤日志系統。ETW Provider會預先注冊到ETW框架上,提供者程序在某個時刻觸發事件,并將標準化定義的事件提供給ETW框架
    在云SQL上獲取shell
    2022-07-18 17:00:27
    云上的關系數據庫服務,它是由 Google 保護、監控和更新的SQL、PostgreSQL 或 MySQL的服務器。托管 MySQL 實例的限制由于Cloud SQL是一項完全托管的服務,因此用戶無權訪問某些功能。在MySQL中,SUPER權限保留用于系統管理相關任務,FILE權限用于讀取/寫入運行 MySQL服務器上的文件。
    在常見滲透過程中我們拿到了一個pc權限,目標pc的mstsc可能保存了其他機器的密碼。所以獲取它保存的密碼是非常有利用價值的。
    一顆小胡椒
    暫無描述
      亚洲 欧美 自拍 唯美 另类