吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 35331|回复: 136
收起左侧

[.NET逆向] .NET下的动态解密与反动态解密

    [复制链接]
wwh1004 发表于 2018-12-9 15:51
本帖最后由 wwh1004 于 2018-12-9 15:56 编辑

.NET下的动态解密与反动态解密

前言

de4dot已经很久没有更新了,脱一些壳没有现成的工具。想要脱掉它们,几乎只能自己写工具。

.NET下解密一般有2种方式,静态解密和动态解密,静态解密速度更快,但是写静态解密工具难度是非常大的,而且兼容性不一定很好(比如de4dot的大部分解密都是静态解密,壳一更新,de4dot就必需更新)。

所以我们需要使用动态解密。动态解密也不是没有缺点的,比如最大的缺点就是要求被解密的程序集必需可以正常运行。但是相较于易开发,易维护,兼容性好等优点,这点缺点算不上什么。本文将介绍一些简单的动态解密与反动态解密。

Agile.NET的字符串加密

分析

我们从最简单的开始,先尝试一下Agile.NET的字符串加密。我之前发了一个UnpackMe,不知是否有人搞定了。我们先看看Agile.NET的字符串加密,这个算是比较简单的动态解密。

我们用dnSpy打开UnpackMe,看看字符串被加密成什么样了。

Agile.NET字符串加密一览

可以看到,所有字符串变成了一串乱码,然后传给了一个特殊的方法,这个特殊的方法会把乱码的字符串转化成正常的字符串,也就是解密。

我们点击一下这个方法,看看这个方法内部是怎么解密字符串的。

Agile.NET字符串解密器方法

Agile.NET字符串解密器方法-去代-理调用

可以看到,这个解密很简单,主要是Xor。为了方便讲解,我把代-理调用先去掉了,不然看不太出这个字符串解密器方法是什么样的。(等下会讲解Agile.NET的代-理调用,这里不用着急。)

如此简单的解密,我们写一个静态解密当然可以,而且也不复杂,效率还更高,但是本文讲解的是动态解密。所以接下来我们讲解如何自己写一个动态解密。

编写解密工具

在之前的图中,我们可以看到Agile.NET的字符串加密非常简单,只是把字符串本身加密了,然后传递给字符串解密器。在C#层面至少是这样,但是IL层面也是这样吗,没有其它混淆吗?我们将dnSpy的反编译模式从C#切换到IL看看。

Agile.NET字符串加密IL层面一览

可以看到,这个真的就是C#显示的那样,把字符串压入栈,然后调用字符串解密器方法。(Agile.NET的是这样,但是不代表别的壳也这样,这个要具体分析的。)这样我们编写解密工具就又更容易了。

这里顺便提一下,还是为了讲解方便,我们写最简单的那种解密工具,不能像de4dot那样自动识别目标程序运行时版本的,即自动适应.NET 2.0 4.0的程序。如果要写成可以自适应的,可以自行阅读de4dot代码。de4dot的代码其实挺复杂,设计模式太多了,所以我也没用de4dot那样使用子进程来实现。我是使用了Loader,让Loader来加载我们的解密工具,而我们手动选择Loader。看不懂这段文字也没关系,解密工具写多了就明白这段文字在说什么了。我们继续。

我们新建一个项目,项目的目标运行时选择和要解密的程序一样的版本。比如我们这个UnpackMe是.NET 4.5,我们选4.5就行了。(其实4.0也可以,因为clr版本一样就行,具体的不过多细说,可以自行研究.NET的一些技术细节。)

添加下面这样的代码,做好框架,初始化字段,接下来代码写在ExecuteImpl()里面。

字符串解密1-1

我们再用dnSpy看看,Agile.NET的字符串解密器方法有什么特征,我们先定位到这个方法。

Agile.NET字符串解密器方法-2

可以看到,字符串解密器方法在命名空间为空的<AgileDotNetRT>类,字符串解密器本身的方法签名应该是string (string)。意思就是字符串解密器只有一个参数并且为string类型,返回值也为string,这样我们就可以使用特征定位到字符串解密器了。

我们这样写定位代码。(当然,和我的不一样也没问题,只要能准确定位到就行。)这些代码都是添加到ExecuteImpl()方法内的。

TypeDef agileDotNetRT;
MethodDef decryptorDef;
MethodBase decryptor;

agileDotNetRT = _moduleDef.Types.First(t => t.Namespace == string.Empty && t.Name == "<AgileDotNetRT>");
// 寻找命名空间为空,类名为"<AgileDotNetRT>"的类
decryptorDef = agileDotNetRT.Methods.First(m => m.Parameters.Count == 1 && m.Parameters[0].Type.TypeName == "String" && m.ReturnType.TypeName == "String");
// 在类中寻找只有一个参数且参数类型为String,返回值类型也为String的方法
decryptor = _module.ResolveMethod(decryptorDef.MDToken.ToInt32());
// 把dnlib的MethodDef转换成.NET反射中的MethodBase

为了更快速的遍历ModuleDefMD中的所有方法,我们需要一个扩展方法。我们这样写。

internal static class ModuleDefExtensions {
    public static IEnumerable<MethodDef> EnumerateAllMethodDefs(this ModuleDefMD moduleDef) {
        uint methodTableLength;

        methodTableLength = moduleDef.TablesStream.MethodTable.Rows;
        // 获取Method表的长度 
        for (uint rid = 1; rid <= methodTableLength; rid++)
            yield return moduleDef.ResolveMethod(rid);
    }
}

上面代码中提到的Method表是.NET元数据表流中的一个表,储存了一个程序集中所有方法的信息,非常重要。Method表中每个元素都是连续的,不要问我为什么,这个是元数据的知识,一时半会也是解释不清的,需要读者自己研究,当然,我们写个字符串解密工具不需要了解那么底层的知识。

也许读者还有疑问,我们为什么要这样写,难道不能这样遍历每一个方法么?

foreach (TypeDef typeDef in _moduleDef.Types)
    foreach (MethodDef methodDef in typeDef.Methods) {
        ...
        ...
    }

看着应该是没问题的,但是,这样会遍历不到嵌套类型中的方法。比如这样就是一个嵌套类型,在一个类里面又声明了一个类B,B就是嵌套类型。

嵌套类型
ModuleDef.Types

所以这样是不行的,ModuleDef.Types不会返回嵌套类型,我们需要使用ModuleDef.GetTypes()。每次遍历方法我们都需要写2个foreach,所以不如直接用一个扩展方法代替。

foreach (MethodDef methodDef in _moduleDef.EnumerateAllMethodDefs()) {
    IList<Instruction> instructionList;

    if (!methodDef.HasBody)
        continue;
    instructionList = methodDef.Body.Instructions;
    for (int i = 0; i < instructionList.Count; i++) {
    }
}

这样我们就可以遍历所有有CliBody的方法的Instruction了。我们再切换到dnSpy,看看Agile.NET是怎么调用字符串解密器方法的。

Agile.NET字符串加密IL层面一览

所以,我们这样定位到要解密的字符串的位置,并且解密字符串,再替换回去。

if (instructionList[i].OpCode.Code == Code.Call && instructionList[i].Operand == decryptorDef && instructionList[i - 1].OpCode.Code == Code.Ldstr) {
    // 这里判断特征
    instructionList[i].OpCode = OpCodes.Nop;
    instructionList[i].Operand = null;
    // i对应的指令是Call XXXX,我们把这一条指令nop掉
    instructionList[i - 1].Operand = decryptor.Invoke(null, new object[] { instructionList[i - 1].Operand });
    // i-1对应的指令是ldstr,我们调用字符串解密器方法,然后把解密后的字符串替换回去
}

这样,我们的字符串解密工具就写完了。

Agile.NET的代-理调用

这个代-理调用的解密是这次讲解中最难的一个,如果读者没看懂上面的字符串解密,强烈建议跳过这一节。

分析

我们还是先用dnSpy打开那个UnpackMe。

Agile.NET代-理调用一览

可以看到部分调用外部方法被混淆了,调用当前程序集的方法不会被混淆。我们再调试看看,这些委托是什么。

Agile.NET代-理调用调试-1

按F11,直接进入了这里,没有什么收获。

Agile.NET代-理调用调试-2

我们看看哪里初始化了这个委托字段,我们可以发现点什么东西。

Agile.NET代-理调用代-理字段初始化-1

我们进入dau方法,dnSpy反编译结果如下。

using System;
using System.Reflection;
using System.Reflection.Emit;

// Token: 0x02000030 RID: 48
public class {FE3C441D-DF9D-407b-917D-0B4471A8296C}
{
    // Token: 0x040000C2 RID: 194
    private static ModuleHandle Fzw=;

    // Token: 0x040000C3 RID: 195
    public static string Cho= = "{FE3C441D-DF9D-407b-917D-0B4471A8296C}";

    // Token: 0x060000B3 RID: 179 RVA: 0x00007984 File Offset: 0x00005B84
    static {FE3C441D-DF9D-407b-917D-0B4471A8296C}()
    {
        {FE3C441D-DF9D-407b-917D-0B4471A8296C}.Fzw= = Assembly.GetExecutingAssembly().GetModules()[0].ModuleHandle;
    }

    // Token: 0x060000B4 RID: 180 RVA: 0x000079A8 File Offset: 0x00005BA8
    [Obfuscation]
    public static void dau(int proxyDelegateTypeToken)
    {
        Type typeFromHandle;
        try
        {
            typeFromHandle = Type.GetTypeFromHandle({FE3C441D-DF9D-407b-917D-0B4471A8296C}.Fzw=.ResolveTypeHandle(33554433 + proxyDelegateTypeToken));
        }
        catch
        {
            return;
        }
        FieldInfo[] fields = typeFromHandle.GetFields(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.GetField);
        int i = 0;
        while (i < fields.Length)
        {
            FieldInfo fieldInfo = fields[i];
            string text = fieldInfo.Name;
            bool flag = false;
            if (text.EndsWith("%"))
            {
                flag = true;
                text = text.TrimEnd(new char[]
                {
                    '%'
                });
            }
            byte[] value = Convert.FromBase64String(text);
            uint num = BitConverter.ToUInt32(value, 0);
            MethodInfo methodInfo;
            try
            {
                methodInfo = (MethodInfo)MethodBase.GetMethodFromHandle({FE3C441D-DF9D-407b-917D-0B4471A8296C}.Fzw=.ResolveMethodHandle((int)(num + 167772161u)));
            }
            catch
            {
                goto IL_1D1;
            }
            goto IL_A7;
            IL_1D1:
            i++;
            continue;
            IL_A7:
            Delegate value2;
            if (methodInfo.IsStatic)
            {
                try
                {
                    value2 = Delegate.CreateDelegate(fieldInfo.FieldType, methodInfo);
                    goto IL_1C4;
                }
                catch (Exception)
                {
                    goto IL_1D1;
                }
            }
            ParameterInfo[] parameters = methodInfo.GetParameters();
            int num2 = parameters.Length + 1;
            Type[] array = new Type[num2];
            array[0] = typeof(object);
            for (int j = 1; j < num2; j++)
            {
                array[j] = parameters[j - 1].ParameterType;
            }
            DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, methodInfo.ReturnType, array, typeFromHandle, true);
            ILGenerator ilgenerator = dynamicMethod.GetILGenerator();
            ilgenerator.Emit(OpCodes.Ldarg_0);
            if (num2 > 1)
            {
                ilgenerator.Emit(OpCodes.Ldarg_1);
            }
            if (num2 > 2)
            {
                ilgenerator.Emit(OpCodes.Ldarg_2);
            }
            if (num2 > 3)
            {
                ilgenerator.Emit(OpCodes.Ldarg_3);
            }
            if (num2 > 4)
            {
                for (int k = 4; k < num2; k++)
                {
                    ilgenerator.Emit(OpCodes.Ldarg_S, k);
                }
            }
            ilgenerator.Emit(flag ? OpCodes.Callvirt : OpCodes.Call, methodInfo);
            ilgenerator.Emit(OpCodes.Ret);
            try
            {
                value2 = dynamicMethod.CreateDelegate(typeFromHandle);
            }
            catch (Exception)
            {
                goto IL_1D1;
            }
            try
            {
                IL_1C4:
                fieldInfo.SetValue(null, value2);
            }
            catch
            {
            }
            goto IL_1D1;
        }
    }

    // Token: 0x060000B5 RID: 181 RVA: 0x00007BD8 File Offset: 0x00005DD8
    public {FE3C441D-DF9D-407b-917D-0B4471A8296C}()
    {
    }
}

这段代码还是比较简单的,传入代-理类型的token,然后遍历类型中每个字段,通过字段名字获取代-理方法的MemberRef Token,然后ReolveMethod。如果是静态方法,直接创建delegate,如果是实例方法,使用DynamicMethod创建一个方法来调用。静态解密可能还会比动态解密简单。

编写解密工具

我们依然这样写一个框架。然后把代码添加到ExecuteImpl()中。

代-理调用解密1-1

我们按特征,找到代-理字段初始化的地方。

TypeDef[] globalTypes;
MethodDef decryptor;

globalTypes = _moduleDef.Types.Where(t => t.Namespace == string.Empty).ToArray();
// 查找所有命名空间为空的类型
decryptor = globalTypes.Where(t => t.Name.StartsWith("{", StringComparison.Ordinal) && t.Name.EndsWith("}", StringComparison.Ordinal)).Single().Methods.Single(m => !m.IsInstanceConstructor && m.Parameters.Count == 1);
// 查找代-理解密方法

由于所有代-理类的静态构造器都会自动解密出真实的方法,我们不需要手动调用代-理方法解密器。我们只需要遍历这些代-理类的字段,找出字段对应的MemberRef。

foreach (TypeDef typeDef in globalTypes) {
    MethodDef cctor;

    cctor = typeDef.FindStaticConstructor();
    if (cctor == null || !cctor.Body.Instructions.Any(i => i.OpCode == OpCodes.Call && i.Operand == decryptor))
        continue;
    // 查找出静态构造器调用了代-理解密方法的类型
}

只要一个类的静态构造器调用了decryptor,就说明这个类是代-理类。我们对代-理类的字段进行遍历。

foreach (FieldInfo fieldInfo in _module.ResolveType(typeDef.MDToken.ToInt32()).GetFields(BindingFlags.NonPublic | BindingFlags.Static)) {
    int proxyFieldToken;
    FieldDef proxyFieldDef;
    MethodBase realMethod;

    proxyFieldToken = fieldInfo.MetadataToken;
    proxyFieldDef = _moduleDef.ResolveField((uint)proxyFieldToken - 0x04000000);
    realMethod = ((Delegate)fieldInfo.GetValue(null)).Method;
}

这里的realMethod还有可能是Agile.NET运行时创建的动态方法,因为要支持callvirt指令。我们写一个方法来判断是不是动态方法。

private static bool IsDynamicMethod(MethodBase methodBase) {
    if (methodBase == null)
        throw new ArgumentNullException(nameof(methodBase));

    try {
        int token;

        token = methodBase.MetadataToken;
        // 获取动态方法的Token会抛出InvalidOperationException异常
    }
    catch (InvalidOperationException) {
        return true;
    }
    return false;
}

我们先判断是否为动态方法,然后再进行替换。

if (IsDynamicMethod(realMethod)) {
    DynamicMethodBodyReader dynamicMethodBodyReader;
    IList<Instruction> instructionList;

    dynamicMethodBodyReader = new DynamicMethodBodyReader(_moduleDef, realMethod);
    dynamicMethodBodyReader.Read();
    instructionList = dynamicMethodBodyReader.GetMethod().Body.Instructions;
    ReplaceAllOperand(proxyFieldDef, instructionList[instructionList.Count - 2].OpCode, (MemberRef)instructionList[instructionList.Count - 2].Operand);
}
else
    ReplaceAllOperand(proxyFieldDef, realMethod.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, (MemberRef)_moduleDef.Import(realMethod));

ReplaceAllOperand的实现如下。

private void ReplaceAllOperand(FieldDef proxyFieldDef, OpCode callOrCallvirt, MemberRef realMethod) {
    if (proxyFieldDef == null)
        throw new ArgumentNullException(nameof(proxyFieldDef));
    if (realMethod == null)
        throw new ArgumentNullException(nameof(realMethod));

    foreach (MethodDef methodDef in _moduleDef.EnumerateAllMethodDefs()) {
        IList<Instruction> instructionList;

        if (!methodDef.HasBody)
            continue;
        // 只遍历有CilBody的方法
        instructionList = methodDef.Body.Instructions;
        for (int i = 0; i < instructionList.Count; i++) {
            // ldsfld    class xxx xxx::'xxx'
            // ...
            // call      instance void xxx::Invoke()
            if (instructionList[i].OpCode != OpCodes.Ldsfld || instructionList[i].Operand != proxyFieldDef)
                continue;
            for (int j = i; j < instructionList.Count; j++) {
                // 从i开始寻找最近的call
                if (instructionList[j].OpCode.Code != Code.Call || !(instructionList[j].Operand is MethodDef) || ((MethodDef)instructionList[j].Operand).DeclaringType != ((TypeDefOrRefSig)proxyFieldDef.FieldType).TypeDefOrRef)
                    continue;
                instructionList[i].OpCode = OpCodes.Nop;
                instructionList[i].Operand = null;
                // 清除 ldsfld    class xxx xxx::'xxx'
                instructionList[j].OpCode = callOrCallvirt;
                instructionList[j].Operand = realMethod;
                // 替换 call      instance void xxx::Invoke()
                break;
            }
        }
    }
}

ConfuserEx的AntiTamper

分析

在好早之前,我也发过一个关于AntiTamper帖子。那个帖子讲的是静态解密,似乎兼容性还是有点问题,这次我们来试试动态解密。我们先打开ConfuserEx这个项目。

ConfuserEx的AntiTamper-1
ConfuserEx的AntiTamper-2

这个是我以前注释的,AntiTamper的原理是把所有方法体单独放到一个Section中,然后利用其它Section的Hash进行解密。所以如果文件本身被篡改了,运行时解密Section肯定会失败。这个Section永远被ConfuserEx插入在其它Section之前,而且算是整体加密,所以动态解密会非常容易。

编写解密工具

还是和以前一样,写个框架,代码放ExecuteImpl()里面。

我们添加个PEInfo类

[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal unsafe struct IMAGE_SECTION_HEADER {
    public static uint UnmanagedSize = (uint)sizeof(IMAGE_SECTION_HEADER);

    public fixed byte Name[8];
    public uint VirtualSize;
    public uint VirtualAddress;
    public uint SizeOfRawData;
    public uint PointerToRawData;
    public uint PointerToRelocations;
    public uint PointerToLinenumbers;
    public ushort NumberOfRelocations;
    public ushort NumberOfLinenumbers;
    public uint Characteristics;
}

internal sealed unsafe class PEInfo {
    private readonly void* _pPEImage;
    private readonly uint _sectionsCount;
    private readonly IMAGE_SECTION_HEADER* pSectionHeaders;

    public void* PEImage => _pPEImage;

    public uint SectionsCount => _sectionsCount;

    public IMAGE_SECTION_HEADER* SectionHeaders => pSectionHeaders;

    public PEInfo(void* pPEImage) {
        byte* p;
        ushort optionalHeaderSize;

        _pPEImage = pPEImage;
        p = (byte*)pPEImage;
        p += *(uint*)(p + 0x3C);
        // NtHeader
        p += 4 + 2;
        // 跳过 Signature + Machine
        _sectionsCount = *(ushort*)p;
        p += 2 + 4 + 4 + 4;
        // 跳过 NumberOfSections + TimeDateStamp + PointerToSymbolTable + NumberOfSymbols
        optionalHeaderSize = *(ushort*)p;
        p += 2 + 2;
        // 跳过 SizeOfOptionalHeader + Characteristics
        p += optionalHeaderSize;
        // 跳过 OptionalHeader
        pSectionHeaders = (IMAGE_SECTION_HEADER*)p;
    }
}

然后,我们读取第一个Section的RVA和Size。调用模块静态构造器,最后还原回去。

PEInfo peInfo;
IMAGE_SECTION_HEADER sectionHeader;
byte[] section;

peInfo = new PEInfo((void*)Marshal.GetHINSTANCE(_module));
sectionHeader = peInfo.SectionHeaders[0];
section = new byte[sectionHeader.SizeOfRawData];
RuntimeHelpers.RunModuleConstructor(_module.ModuleHandle);
Marshal.Copy((IntPtr)((byte*)peInfo.PEImage + sectionHeader.VirtualAddress), _peImage, (int)sectionHeader.PointerToRawData, (int)sectionHeader.SizeOfRawData);

这里的_peImage是一个字节数组,表示要解密的程序集的字节数组形式。动态解密AntiTamper连dnlib都不需要使用,比静态解密方便很多。解密之后,手动Patch掉AntiTamper的运行时就行。

反动态解密

动态解密也有自己的缺点,比如容易被检测。文章写了3个动态解密,其实原理都差不多,最核心的还是反射API。我们可以利用这一点,写出一些反动态解密的代码。

  • 最简单的,我们可以像ILProtector一样,检测调用来源,如果当前方法的调用方是最底层的Invoke方法,那就说明被非法调用了。
  • 我们还可以做得更极端,检测整个调用堆栈,比如调用堆栈里面是否有de4dot的字样。
  • 通过AppDomain.CurrentDomain.GetAssemblies()获取所有已加载程序集,判断里面是否有非法程序集。
  • 如果一个程序是可执行文件,并且不会被其它程序集引用,那么可以使用Assembly.GetEntryAssembly()检测入口程序集是不是本身,如果不是,那说明当前程序集被其它程序集用反射API加载了。

免费评分

参与人数 66吾爱币 +77 热心值 +65 收起 理由
evea + 1 + 1 我很赞同!
晚安说给自己听 + 1 + 1 图片挂了求更新,还想得收藏慢慢学,可惜图片挂了
zsedcbm + 1 + 1 会写c#, 但是看起这个还是有点迷糊.
solleook + 1 + 1 谢谢@Thanks!
wangluo920 + 1 谢谢@Thanks!
油桶 + 1 热心回复!
wade_cww + 1 + 1 谢谢@Thanks!
mlwy + 1 + 1 谢谢@Thanks!
sxhytds + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
gaoyong0713 + 1 好帖,继续,继续
羽月莉音 + 1 + 1 我很赞同!
iNIC + 1 + 1 谢谢@Thanks!
weilai1917 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
zhangchuan6000 + 1 谢谢@Thanks!
珍惜hh + 1 + 1 学习了很多没有见到的东西,谢谢不过不知道DNG怎么弄的
一念苍穹变 + 1 + 1 我很赞同!
looy + 1 + 1 膜拜大神
17634387807 + 1 + 1 谢谢@Thanks!
Zxx_ + 1 + 1 用心讨论,共获提升!
XhyEax + 1 + 1 热心回复!
突突兔 + 1 + 1 用心讨论,共获提升!
我是你老大 + 2 + 1 谢谢@Thanks!
简单单单 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
siuhoapdou + 1 + 1 谢谢@Thanks!
l261178929 + 1 + 1 --------.NET 大神啊
dNp + 1 + 1 谢谢@Thanks!
Tomatoman + 1 + 1 谢谢@Thanks!
jnez112358 + 1 + 1 谢谢@Thanks!
woyucheng + 1 + 1 谢谢@Thanks!
Badlow + 1 + 1 热心回复!
wanttobeno + 2 + 1 谢谢@Thanks!
gongyong728125 + 1 + 1 热心回复!
kenorizon + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
非凡公子 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
jy04468108 + 1 + 1 要是能把工具发出来,就更好了。。。
fgylh + 1 + 1 谢谢@Thanks!
bingtangbing + 1 + 1 我很赞同!
CrazyNut + 3 + 1 用心讨论,共获提升!
洋洋吖 + 1 + 1 热心回复!
fanghongjian + 1 + 1 谢谢@Thanks!
cxp521 + 1 + 1 牛逼
xcrwww + 1 + 1 用心讨论,共获提升!
lookerJ + 1 + 1 用心讨论,共获提升!
redcat + 1 + 1 好文。
drw168 + 1 + 1 谢谢@Thanks!
海盗小K + 3 + 1 我很赞同!
无痕软件 + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
bet + 2 + 2 .net 文章不多
wangjiankai + 1 + 1 热心回复!
Ravey + 1 + 1 谢谢@Thanks!
艾莉希雅 + 2 + 1 膜一波先,考完试再看
howsk + 1 + 1 用心讨论,共获提升!
xinkui + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
笙若 + 1 + 1 谢谢@Thanks!
zjjyl + 1 + 1 谢谢@Thanks!
ycxlsxb + 1 + 1 谢谢@Thanks!
天若幽心 + 3 + 1 好帖子!
千世樱 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
青山依旧在 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
3yu3 + 1 + 1 我很赞同!
农夫山泉老酸奶 + 1 谢谢@Thanks!
朱朱你堕落了 + 1 + 1 看不懂,表示只会做个加CB和热心的咸鱼。膜拜ing...
gsyifan + 1 + 1 厉害了。
盲僧 + 1 + 1 我很赞同!
yAYa + 3 + 1 谢谢@Thanks!
山顶的一棵草 + 2 + 1 精华前留名!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| wwh1004 发表于 2019-5-10 22:47
珍惜hh 发表于 2019-5-10 13:39
方便发一个你编译完的吗

成品不太想发,伸手党太多了
其实抄我文章的代码意义不太大,看懂了才是真的懂了,抄了但不理解为什么这么写也没用的
 楼主| wwh1004 发表于 2018-12-17 22:15
ohacn 发表于 2018-12-17 16:58
简单的混淆这么操作是可以的,直接使用非托管动态解密机制就不行了。
比如DNG,还有Aglie.net的代码加密选 ...

JIT级的脱壳可以自己研究,这个太复杂了。DNGuardHVM, Maxtocode的脱壳可以去研究yck的jitDumper3或者CodeCracker的DNGuardHVM Unpacker。agile.net的代码加密非常简单,de4dot都搞得定
yAYa 发表于 2018-12-9 16:15
nohack 发表于 2018-12-9 16:54
太长了之后慢慢看
biutefo 发表于 2018-12-9 16:55
编辑dll的东西啊
pxhb 发表于 2018-12-9 16:59
一发就是精品分析,感谢
艾莉希雅 发表于 2018-12-9 17:05
哦哦哦这个就是大佬说的要写的文章吗
哦摩西罗伊
IT_保安 发表于 2018-12-9 17:08
虽然我们学C#,但是只是一点点入门。膜拜大佬。
A00 发表于 2018-12-9 17:10
很详细,点赞
浮尘云烟 发表于 2018-12-9 17:12
学习一下
edwinchang 发表于 2018-12-9 17:37
楼主的解析到位~ 但是对于初学者有一定的难度。
感谢分享~ 等有时间继续摸索
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-22 16:25

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表