飄云閣(PYG官方論壇)

 找回密碼
 加入論壇

QQ登錄

只需一步,快速開始

掃一掃,訪問微社區

查看: 1539|回復: 24
打印 上一主題 下一主題

[原創] [.NET]詳解ConfuserEx的Anti Tamper與Anti Dump by Wwh / NCK

[復制鏈接]
  • TA的每日心情
    開心
    2018-9-21 22:58
  • 簽到天數: 2 天

    [LV.1]初來乍到

    跳轉到指定樓層
    樓主
    發表于 2018-8-15 10:29:22 | 只看該作者 |只看大圖 回帖獎勵 |倒序瀏覽 |閱讀模式
    # [.NET]詳解ConfuserEx的Anti Tamper與Anti Dump by Wwh

    許多人都知道利用dnSpy單步調試+Dump+CodeCracker的一系列工具可以脫去ConfuserEx殼,這些在網上都有教程,但是并沒有文章說明過背后的原理。本文講盡可能詳細解說ConfuserEx的Anti Tamper與Anti Dump**(有耐心并且了解一點點的PE結構完全可以看懂)**

    ## ConfuserEx整個項目結構

    在開始講解之前,我們大概了解一下ConfuserEx項目的結構。
    我們用Visual Studio打開ConfuserEx,項目大概是這樣的:

    Confuser.CLI的是命令行版本,類似de4dot的操作方式
    Confuser.Core是核心,把所有部分Protection組合到一起
    Confuser.DynCipher可以動態生成加密算法
    Confuser.Protections里面包含了所有Protection,這是需要研究的部分
    Confuser.Renamer可以對類名、方法名等重命名,包括多種重命名方式,比如可逆的重命名,這些沒有在ConfuserEx的GUI里面顯示就是了
    Confuser.Runtime是運行時,比如Anti Dump的實現,其實就在這個項目里面。上面提到的Confuser.Protections會把Confuser.Runtime中的Anti Dump的實現注入到目標程序集。
    ConfuserEx是GUI,沒必要多說。
    **整個項目幾乎沒什么注釋,下面的中文注釋均為我添加的。**

    ## Anti Dump

    Anti Dump比起Anti Tamper簡單不少,所以我們先來了解一下Anti Dump。
    Anti Dump的實現只有一個方法,非常簡潔。
    我們找到Confuser.Protections項目的AntiDumpProtection.cs。

    [C#] 純文本查看 復制代碼
    protected override void Execute(ConfuserContext context, ProtectionParameters parameters) {
        TypeDef rtType = context.Registry.GetService<IRuntimeService>().GetRuntimeType("Confuser.Runtime.AntiDump");
        // 獲取Confuser.Runtime項目中的AntiDump類
    
        var marker = context.Registry.GetService<IMarkerService>();
        var name = context.Registry.GetService<INameService>();
    
        foreach (ModuleDef module in parameters.Targets.OfType<ModuleDef>()) {
            IEnumerable<IDnlibDef> members = InjectHelper.Inject(rtType, module.GlobalType, module);
            // 將Confuser.Runtime.AntiDump類注入到目標程序集,返回目標程序集中的所有IDnlibDef
    
            MethodDef cctor = module.GlobalType.FindStaticConstructor();
            // 找到<Module>::.cctor
            var init = (MethodDef)members.Single(method => method.Name == "Initialize");
            cctor.Body.Instructions.Insert(0, Instruction.Create(OpCodes.Call, init));
            // 插入call void Confuser.Runtime.AntiDump::Initialize()這條IL指令
    
            foreach (IDnlibDef member in members)
                name.MarkHelper(member, marker, (Protection)Parent);
            // 將這些IDnlibDef標記為需要重命名的
        }
    }
    

    AntiDumpProtection做的只是注入,所以我們轉到Confuser.Runtime中的AntiDump.cs

    [C#] 純文本查看 復制代碼
    static unsafe void Initialize() {
        uint old;
        Module module = typeof(AntiDump).Module;
        var bas = (byte*)Marshal.GetHINSTANCE(module);
        byte* ptr = bas + 0x3c;
        // 存放NT頭偏移的地址
        byte* ptr2;
        ptr = ptr2 = bas + *(uint*)ptr;
        // ptr指向NT頭
        ptr += 0x6;
        // ptr指向文件頭的NumberOfSections
        ushort sectNum = *(ushort*)ptr;
        // 獲取節的數量
        ptr += 14;
        // ptr指向文件頭的SizeOfOptionalHeader
        ushort optSize = *(ushort*)ptr;
        // 獲取可選頭的大小
        ptr = ptr2 = ptr + 0x4 + optSize;
        // ptr指向第一個節頭
    
        byte* @new = stackalloc byte[11];
        if (module.FullyQualifiedName[0] != '<') //Mapped
        {
            // 這里判斷是否為內存加載的模塊(dnSpy里面顯示InMemory的),比如Assembly.Load(byte[] rawAssembly)
            // 如果是內存加載的模塊,module.FullyQualifiedName[0]會返回"<未知>"
            //VirtualProtect(ptr - 16, 8, 0x40, out old);
            //*(uint*)(ptr - 12) = 0;
            byte* mdDir = bas + *(uint*)(ptr - 16);
            // ptr指向IMAGE_COR20_HEADER
            //*(uint*)(ptr - 16) = 0;
    
            if (*(uint*)(ptr - 0x78) != 0) {
                // 如果導入表RVA不為0
                byte* importDir = bas + *(uint*)(ptr - 0x78);
                byte* oftMod = bas + *(uint*)importDir;
                // OriginalFirstThunk
                byte* modName = bas + *(uint*)(importDir + 12);
                // 導入DLL的名稱
                byte* funcName = bas + *(uint*)oftMod + 2;
                // 導入函數的名稱
                VirtualProtect(modName, 11, 0x40, out old);
    
                *(uint*)@new = 0x6c64746e;
                *((uint*)@new + 1) = 0x6c642e6c;
                *((ushort*)@new + 4) = 0x006c;
                *(@new + 10) = 0;
                // ntdll.dll
    
                for (int i = 0; i < 11; i++)
                    *(modName + i) = *(@new + i);
                // 把mscoree.dll改成ntdll.dll
    
                VirtualProtect(funcName, 11, 0x40, out old);
    
                *(uint*)@new = 0x6f43744e;
                *((uint*)@new + 1) = 0x6e69746e;
                *((ushort*)@new + 4) = 0x6575;
                *(@new + 10) = 0;
                // NtContinue
    
                for (int i = 0; i < 11; i++)
                    *(funcName + i) = *(@new + i);
                // 把_CorExeMain改成NtContinue
            }
    
            for (int i = 0; i < sectNum; i++) {
                VirtualProtect(ptr, 8, 0x40, out old);
                Marshal.Copy(new byte[8], 0, (IntPtr)ptr, 8);
                ptr += 0x28;
            }
            // 清零所有節的名稱
            VirtualProtect(mdDir, 0x48, 0x40, out old);
            byte* mdHdr = bas + *(uint*)(mdDir + 8);
            // mdHdr指向STORAGESIGNATURE(開頭是BSJ**B的那個)
            *(uint*)mdDir = 0;
            *((uint*)mdDir + 1) = 0;
            *((uint*)mdDir + 2) = 0;
            *((uint*)mdDir + 3) = 0;
            // 將IMAGE_COR20_HEADER的cb MajorRuntimeVersion MinorRuntimeVersion MetaData清零
    
            VirtualProtect(mdHdr, 4, 0x40, out old);
            *(uint*)mdHdr = 0;
            // 刪除BSJ**B標志,這樣就無法搜索到STORAGESIGNATURE了
            mdHdr += 12;
            // mdHdr指向iVersionString
            mdHdr += *(uint*)mdHdr;
            mdHdr = (byte*)(((ulong)mdHdr + 7) & ~3UL);
            mdHdr += 2;
            // mdHdr指向STORAGEHEADER的iStreams
            ushort numOfStream = *mdHdr;
            // 獲取元數據流的數量
            mdHdr += 2;
            // mdHdr指向第一個元數據流頭
            for (int i = 0; i < numOfStream; i++) {
                VirtualProtect(mdHdr, 8, 0x40, out old);
                //*(uint*)mdHdr = 0;
                mdHdr += 4;
                // mdHdr指向STORAGESTREAM.iSize
                //*(uint*)mdHdr = 0;
                mdHdr += 4;
                // mdHdr指向STORAGESTREAM.rcName
                for (int ii = 0; ii < 8; ii++) {
                    VirtualProtect(mdHdr, 4, 0x40, out old);
                    *mdHdr = 0;
                    mdHdr++;
                    if (*mdHdr == 0) {
                        mdHdr += 3;
                        break;
                    }
                    *mdHdr = 0;
                    mdHdr++;
                    if (*mdHdr == 0) {
                        mdHdr += 2;
                        break;
                    }
                    *mdHdr = 0;
                    mdHdr++;
                    if (*mdHdr == 0) {
                        mdHdr += 1;
                        break;
                    }
                    *mdHdr = 0;
                    mdHdr++;
                }
                // 清零STORAGESTREAM.rcName,因為這個是4字節對齊的,所以代碼長一些
            }
        }
        else //Flat
        {
            // 這里就是內存加載程序集的情況了,和上面是差不多的,我就不再具體分析了
            //VirtualProtect(ptr - 16, 8, 0x40, out old);
            //*(uint*)(ptr - 12) = 0;
            uint mdDir = *(uint*)(ptr - 16);
            //*(uint*)(ptr - 16) = 0;
            uint importDir = *(uint*)(ptr - 0x78);
    
            var vAdrs = new uint[sectNum];
            var vSizes = new uint[sectNum];
            var rAdrs = new uint[sectNum];
            for (int i = 0; i < sectNum; i++) {
                VirtualProtect(ptr, 8, 0x40, out old);
                Marshal.Copy(new byte[8], 0, (IntPtr)ptr, 8);
                vAdrs[i] = *(uint*)(ptr + 12);
                vSizes[i] = *(uint*)(ptr + 8);
                rAdrs[i] = *(uint*)(ptr + 20);
                ptr += 0x28;
            }
    
    
            if (importDir != 0) {
                for (int i = 0; i < sectNum; i++)
                    if (vAdrs[i] <= importDir && importDir < vAdrs[i] + vSizes[i]) {
                        importDir = importDir - vAdrs[i] + rAdrs[i];
                        break;
                    }
                byte* importDirPtr = bas + importDir;
                uint oftMod = *(uint*)importDirPtr;
                for (int i = 0; i < sectNum; i++)
                    if (vAdrs[i] <= oftMod && oftMod < vAdrs[i] + vSizes[i]) {
                        oftMod = oftMod - vAdrs[i] + rAdrs[i];
                        break;
                    }
                byte* oftModPtr = bas + oftMod;
                uint modName = *(uint*)(importDirPtr + 12);
                for (int i = 0; i < sectNum; i++)
                    if (vAdrs[i] <= modName && modName < vAdrs[i] + vSizes[i]) {
                        modName = modName - vAdrs[i] + rAdrs[i];
                        break;
                    }
                uint funcName = *(uint*)oftModPtr + 2;
                for (int i = 0; i < sectNum; i++)
                    if (vAdrs[i] <= funcName && funcName < vAdrs[i] + vSizes[i]) {
                        funcName = funcName - vAdrs[i] + rAdrs[i];
                        break;
                    }
                VirtualProtect(bas + modName, 11, 0x40, out old);
    
                *(uint*)@new = 0x6c64746e;
                *((uint*)@new + 1) = 0x6c642e6c;
                *((ushort*)@new + 4) = 0x006c;
                *(@new + 10) = 0;
    
                for (int i = 0; i < 11; i++)
                    *(bas + modName + i) = *(@new + i);
    
                VirtualProtect(bas + funcName, 11, 0x40, out old);
    
                *(uint*)@new = 0x6f43744e;
                *((uint*)@new + 1) = 0x6e69746e;
                *((ushort*)@new + 4) = 0x6575;
                *(@new + 10) = 0;
    
                for (int i = 0; i < 11; i++)
                    *(bas + funcName + i) = *(@new + i);
            }
    
    
            for (int i = 0; i < sectNum; i++)
                if (vAdrs[i] <= mdDir && mdDir < vAdrs[i] + vSizes[i]) {
                    mdDir = mdDir - vAdrs[i] + rAdrs[i];
                    break;
                }
            byte* mdDirPtr = bas + mdDir;
            VirtualProtect(mdDirPtr, 0x48, 0x40, out old);
            uint mdHdr = *(uint*)(mdDirPtr + 8);
            for (int i = 0; i < sectNum; i++)
                if (vAdrs[i] <= mdHdr && mdHdr < vAdrs[i] + vSizes[i]) {
                    mdHdr = mdHdr - vAdrs[i] + rAdrs[i];
                    break;
                }
            *(uint*)mdDirPtr = 0;
            *((uint*)mdDirPtr + 1) = 0;
            *((uint*)mdDirPtr + 2) = 0;
            *((uint*)mdDirPtr + 3) = 0;
    
    
            byte* mdHdrPtr = bas + mdHdr;
            VirtualProtect(mdHdrPtr, 4, 0x40, out old);
            *(uint*)mdHdrPtr = 0;
            mdHdrPtr += 12;
            mdHdrPtr += *(uint*)mdHdrPtr;
            mdHdrPtr = (byte*)(((ulong)mdHdrPtr + 7) & ~3UL);
            mdHdrPtr += 2;
            ushort numOfStream = *mdHdrPtr;
            mdHdrPtr += 2;
            for (int i = 0; i < numOfStream; i++) {
                VirtualProtect(mdHdrPtr, 8, 0x40, out old);
                //*(uint*)mdHdrPtr = 0;
                mdHdrPtr += 4;
                //*(uint*)mdHdrPtr = 0;
                mdHdrPtr += 4;
                for (int ii = 0; ii < 8; ii++) {
                    VirtualProtect(mdHdrPtr, 4, 0x40, out old);
                    *mdHdrPtr = 0;
                    mdHdrPtr++;
                    if (*mdHdrPtr == 0) {
                        mdHdrPtr += 3;
                        break;
                    }
                    *mdHdrPtr = 0;
                    mdHdrPtr++;
                    if (*mdHdrPtr == 0) {
                        mdHdrPtr += 2;
                        break;
                    }
                    *mdHdrPtr = 0;
                    mdHdrPtr++;
                    if (*mdHdrPtr == 0) {
                        mdHdrPtr += 1;
                        break;
                    }
                    *mdHdrPtr = 0;
                    mdHdrPtr++;
                }
            }
        }
    }
    

    這里面修改導入表的部分其實是可有可無的,這個是可逆的
    清空節名稱也是是可選的
    其中非常重點的是將IMAGE_COR20_HEADER.MetaData清零,CLR已經完成了元數據的定位,并且保存了有關數據(可以使用CE搜索內存驗證,搜索ImageBase+MetaData.VirtualAddress),不再需要這個字段,是可以清零的,但是我們讀取元數據,是需要這個字段的。
    接下來Anti Dump會刪除BSJ**B標志,這樣就無法搜索到STORAGESIGNATURE了。還有元數據流頭的rcName字段,一并清零,這樣也會讓我們無法定位到元數據結構體,但是CLR不再需要這些了。

    解決這個的辦法很簡單,把&lt;Module&gt;::.cctor()的call void Confuser.Runtime.AntiDump::Initialize()這條指令nop掉。我們要如何定位到這條指令呢?
    這里有個投機取巧的辦法,解決Anti Tamper之后,在dnSpy里面找出現了
    [C#] 純文本查看 復制代碼
    Module module = typeof(AntiDump).Module;
    byte* bas = (byte*)Marshal.GetHINSTANCE(module);
    ......
    if (module.FullyQualifiedName[0] != '<'){
    }
    

    這樣的方法,并且這個方法還多次調用了VirtualProtect,原版ConfuserEx是調用了14次。把call 這個方法的地方nop掉,注意顯示模式切換到IL,然后點一下IL所在的FileOffset,用十六進制編輯器改成0,不然容易出問題。

    ## Anti Tamper

    **Anti Tamper稍微麻煩一些,看不懂的地方實際操作一下,到ConfuserEx項目里面調試一下!!!!!!**

    ### 分析

    ConfuserEx里面有2種AntiTamper模式,一種的Hook JIT,另一種是原地解密。Hook JIT算是半成品,還沒法正常使用,所以我們實際上看到的是原地解密模式,強度不是特別高。
    我們轉到Confuser.Protections項目的AntiTamper\NormalMode.cs

    這里我就不注釋了,因為這里也是一個注入器,和AntiDumpProtection.cs是差不多的,看不懂也沒關系,看我后面分析實際實現就能明白了。
    找到AntiTamper的實現AntiTamper.Normal.cs

    [C#] 純文本查看 復制代碼
    static unsafe void Initialize() {
    	Module m = typeof(AntiTamperNormal).Module;
    	string n = m.FullyQualifiedName;
    	bool f = n.Length > 0 && n[0] == '<';
              // f為true代表這是內存加載的程序集
    	var b = (byte*)Marshal.GetHINSTANCE(m);
    	byte* p = b + *(uint*)(b + 0x3c);
              // pNtHeader
    	ushort s = *(ushort*)(p + 0x6);
              // Machine
    	ushort o = *(ushort*)(p + 0x14);
              // SizeOfOptHdr
    
    	uint* e = null;
    	uint l = 0;
    	var r = (uint*)(p + 0x18 + o);
              // pFirstSectHdr
    	uint z = (uint)Mutation.KeyI1, x = (uint)Mutation.KeyI2, c = (uint)Mutation.KeyI3, v = (uint)Mutation.KeyI4;
    	for (int i = 0; i < s; i++) {
    		uint g = (*r++) * (*r++);
                  // SectionHeader.Name => nameHash
                  // 此時r指向SectionHeader.VirtualSize
    		if (g == (uint)Mutation.KeyI0) {
                      // 查看Confuser.Protections.AntiTamper.NormalMode
                      // 這里的Mutation.KeyI0是nameHash
                      // 這個if的意思是判斷是否為ConfuserEx用來存放加密后方法體的節
                      e = (uint*)(b + (f ? *(r + 3) : *(r + 1)));
                      // f為true,e指向RawAddres指向的內容,反之指向VirtualAddress指向的內容
    			l = (f ? *(r + 2) : *(r + 0)) >> 2;
                      // f為true,l等于RawSize >> 2,反之等于VirtualSize >> 2
                      // 不用關心為什么>> 2了,這個到了后面還會<< 2回去
                  }
                  else if (g != 0) {
    			var q = (uint*)(b + (f ? *(r + 3) : *(r + 1)));
                      // f為true,q指向RawAddres指向的內容,反之指向VirtualAddress指向的內容
                      uint j = *(r + 2) >> 2;
                      // l等于VirtualSize >> 2
                      for (uint k = 0; k < j; k++) {
                          // 比如VirtualSize=0x200,那這里就循環0x20次
                          uint t = (z ^ (*q++)) + x + c * v;
    				z = x;
    				x = c;
    				x = v;
    				v = t;
                          // 加密運算本身,不需要做分析
    			}
    		}
    		r += 8;
                  // 讓下一次循環時r依然指向SectionHeader的開頭
    	}
    
    	uint[] y = new uint[0x10], d = new uint[0x10];
    	for (int i = 0; i < 0x10; i++) {
    		y[i] = v;
    		d[i] = x;
    		z = (x >> 5) | (x << 27);
    		x = (c >> 3) | (c << 29);
    		c = (v >> 7) | (v << 25);
    		v = (z >> 11) | (z << 21);
    	}
              // 加密運算本身,不需要做分析
              Mutation.Crypt(y, d);
              // 這里會ConfuserEx替換成真正的加密算法,大概是這樣:
              // data[0] = data[0] ^ key[0];
              // data[1] = data[1] * key[1];
              // data[2] = data[2] + key[2];
              // data[3] = data[3] ^ key[3];
              // data[4] = data[4] * key[4];
              // data[5] = data[5] + key[5];
              // 然后這樣循環下去
    
              uint w = 0x40;
    	VirtualProtect((IntPtr)e, l << 2, w, out w);
    
    	if (w == 0x40)
                  // 防止被重復調用,出現重復解密導致破壞數據
    		return;
    
    	uint h = 0;
    	for (uint i = 0; i < l; i++) {
    		*e ^= y[h & 0xf];
    		y[h & 0xf] = (y[h & 0xf] ^ (*e++)) + 0x3dbb2819;
    		h++;
    	}
    }
    

    上面是我注釋的,實際上的解密寫在了最末尾"*e ^= y[h & 0xf];",前面一大坨代碼都是計算出key和要解密數據的位置。
    為什么可以解密?因為xor 2次相同的值,等于xor 0,比如123 ^ 456 ^ 456 == 123。
    那么這段代碼究竟解密了什么呢?
    我們先了解一下元數據表的Method表

    我用紅框標記的RVA指向了方法體的數據,方法體里面存放了ILHeader ILCode LocalVar EH。
    ConfuserEx會修改RVA,讓RVA指向另一個紅框"章節 #0: 亂碼",這個Section專門存放了方法體(模塊靜態構造器和Anti Tamper本身的方法體不在這個節里面,否則都沒法運行了)。
    ConfuserEx會加密這一個節的內容。因為模塊靜態構造器是比程序集入口點更優先執行的,所以模塊靜態構造器的第一條IL指令就是call void AntiTamper::Initialize()。
    在程序集運行時會首先執行這一條IL指令,其它方法都會被解密,程序就可以正常的運行下去了。這種方法比Hook JIT的兼容性好非常多,幾乎不可能出現無法運行的問題。但是這種方法的強度也是遠不如Hook JIT的,尤其是那種用一個非托管DLL來Hook JIT,還給非托管DLL加個vmp殼的(說的哪幾個殼應該都清楚)。

    ### AntiTamperKiller成品

    剛才我們已經分析完了Anti Tamper,如果你看懂了,你也能寫出一個Anti Tamper的靜態脫殼機(dnSpy Dump法是有可能損壞數據的,靜態脫殼僅僅解密了一個節的數據)

    Anti Tamper脫殼機下載:
    鏈接: [https://pan.baidu.com/s/1IMWk7BywjVX1O2AsJ2qIrA](https://pan.baidu.com/s/1IMWk7BywjVX1O2AsJ2qIrA)密碼: 9ywx

    de4dot怎么用的這個就怎么用,支持ConfuserEx最大保護。

    評分

    參與人數 5威望 +28 飄云幣 +22 收起 理由
    HiPP + 2 + 2 感謝發布原創作品,PYG有你更精彩!
    凉游淺筆深畫眉 + 2 PYG有你更精彩!
    九層樓 + 2 原創精品 感謝分享!
    Rooking + 20 + 20 熱心分享 共同提升!
    零下八度 + 2 厲害了,果然是詳解,支持一下~

    查看全部評分

    分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友 微信微信
    收藏收藏9 轉播轉播 分享分享 分享淘帖 頂 踩 友情贊助 微信分享
  • TA的每日心情
    郁悶
    2017-8-4 11:10
  • 簽到天數: 2 天

    [LV.1]初來乍到

    沙發
    發表于 2018-8-15 10:38:45 | 只看該作者
    支持一個~~

    事實上呢,【dnSpy單步調試+Dump+CodeCracker的一系列工具可以脫去ConfuserEx殼】,很多菜鳥也是很難做到,

    比如CC的一套工具,很多人搞不清什么時候用哪個,什么順序使用有什么區別之類的。

    又比如用 dnSpy 來dump,別的不算,就在 .cctor 那里 NOP 保存那步,,保存出來的就千奇百怪的。。。。。
    回復 支持 反對

    使用道具 舉報

  • TA的每日心情
    開心
    2018-9-21 22:58
  • 簽到天數: 2 天

    [LV.1]初來乍到

    藤椅
     樓主| 發表于 2018-8-15 10:42:45 | 只看該作者
    零下八度 發表于 2018-8-15 10:38
    支持一個~~

    事實上呢,【dnSpy單步調試+Dump+CodeCracker的一系列工具可以脫去ConfuserEx殼】,很多菜鳥 ...

    https://mindlocksite.wordpress.com/2017/02/11/easy-way-to-unpack-confuserex-1-0-max-settings/
    這個教程是說得非常好的,完全可以看懂的。文章后面貼了個自己寫的工具,可以靜態解密Anti Tamper,不需要dnSpy。文章還沒編輯好,有些內容pyg提示是不良信息,發不上
    回復 支持 反對

    使用道具 舉報

  • TA的每日心情
    郁悶
    2017-8-4 11:10
  • 簽到天數: 2 天

    [LV.1]初來乍到

    板凳
    發表于 2018-8-15 10:49:20 | 只看該作者
    wwh1004 發表于 2018-8-15 10:42
    https://mindlocksite.wordpress.com/2017/02/11/easy-way-to-unpack-confuserex-1-0-max-settings/
    這 ...

    發不上可以先草稿?我還以為你發完了呢,正奇怪這"詳解"好像不太對勁兒?

    這博客我也看過,上面幾乎每一篇文章幾乎都都是精品,很多值得研究和參考的東西。值得點個贊~

    但是,個人覺得需要一定的基礎,寫博客當然難以面面俱到,但是我個人還是認為對新手不太容易理解?maybe是我太菜了的原因。。。
    回復 支持 反對

    使用道具 舉報

  • TA的每日心情
    開心
    2018-9-21 22:58
  • 簽到天數: 2 天

    [LV.1]初來乍到

    報紙
     樓主| 發表于 2018-8-15 10:58:02 | 只看該作者
    零下八度 發表于 2018-8-15 10:49
    發不上可以先草稿?我還以為你發完了呢,正奇怪這"詳解"好像不太對勁兒?

    這博客我也看過,上 ...

    回復 支持 反對

    使用道具 舉報

  • TA的每日心情
    開心
    2018-7-9 22:48
  • 簽到天數: 16 天

    [LV.4]偶爾看看III

    地板
    發表于 2018-8-15 11:26:01 | 只看該作者
    我是直接修改IL,還不會dump
    回復 支持 反對

    使用道具 舉報

  • TA的每日心情
    郁悶
    2017-8-4 11:10
  • 簽到天數: 2 天

    [LV.1]初來乍到

    7#
    發表于 2018-8-15 11:39:21 | 只看該作者
    話說這斜體用的,真心不忍直視啊。。。。。
    回復 支持 反對

    使用道具 舉報

  • TA的每日心情
    郁悶
    2017-8-4 11:10
  • 簽到天數: 2 天

    [LV.1]初來乍到

    8#
    發表于 2018-8-15 11:41:37 | 只看該作者
    dryzh 發表于 2018-8-15 11:26
    我是直接修改IL,還不會dump

    dump 跟 修改IL,不沖突,加殼混淆的程序很難直接改IL,dump 一般是作為解密去混淆之類的其中一環,脫了殼就才好編輯IL。

    點評

    嗯嗯,受教了。向表哥學習。  詳情 回復 發表于 2018-8-15 14:02
    回復 支持 反對

    使用道具 舉報

  • TA的每日心情
    開心
    2018-7-9 22:48
  • 簽到天數: 16 天

    [LV.4]偶爾看看III

    9#
    發表于 2018-8-15 14:02:36 | 只看該作者
    零下八度 發表于 2018-8-15 11:41
    dump 跟 修改IL,不沖突,加殼混淆的程序很難直接改IL,dump 一般是作為解密去混淆之類的其中一環,脫了 ...

    嗯嗯,受教了。向表哥學習。
    回復 支持 反對

    使用道具 舉報

  • TA的每日心情
    開心
    2018-11-21 11:47
  • 簽到天數: 44 天

    [LV.5]常住居民I

    10#
    發表于 2018-8-18 09:10:36 | 只看該作者
    嗯嗯,學習學習學習學習學習
    回復 支持 反對

    使用道具 舉報

    您需要登錄后才可以回帖 登錄 | 加入論壇

    本版積分規則

    關閉

    站長推薦上一條 /1 下一條

    快速回復 返回頂部 返回列表
    上海快3开奖结果查询