强命名的延迟与关联在.net程序集保护中的作用及其逆向方法

一、老调重谈强命名
    强命名的定义这里就不重复了,不妨就把他看作一个文件的hash,而如果文件被修改的话,计算出的hash将与最被程序设计者给定的强命名不一致,程序将拒绝加载。这可怜的一点点安全特性被人用多种方法证实原来靠强命名保护程序集只是纸老虎。
至少有三种方法可以去除单独的可执行文件的强命名:
1、  ildasm反编译,在il源代码中删除该assembly对强命名的引用,再编译回去。在.net初期时,这种方法还是很好用 的,codeproject上也介绍过。但是现在的程序对于ildasm的anti越来越强,想完整的反编译再完整地编译回去,有时还不太容易。
http://bbs.pediy.com/upload/2006/4/image/1.gif_774.gif 

2、  利用工具,原理是将CLI Header的标志置0
CLI Header的结构如下:

RVA  Field                       Contents
0x2008  Cb(结构的大小)            0x48
0x200C  MajorRuntimeVersion            2
0x200E  MinorRuntimeVersion            0
0x2010  MetaData                       0x2060
0x2014  Size of the Metadata            0x148 =(RVA of Import Table) ? (RVA of MetaData)
0x2018  Flags                       1
0x201C  EntryPointToken             0x06000001 (Method #1 in TypeDef table)
0x2020  Resources                        0
0x2028  StrongNameSignature             0
0x2030  CodeManagerTable             0
0x2038  VTableFixups             0
0x2040  ExportAddressTableJumps  0
0x2048  ManagedNativeHeader             0

包括flags标志要减去COMIMAGE_FLAGS_STRONGNAMESIGNED (0x00000008) ,以及 StrongNameSignature处表示的强命名的偏移位置和大小要置0。这还没完,还要将Metadata表中AssemblyDef处的强命名 标志删除。
来看一下AssemblyDef的定义:

• HashAlgId (a 4-byte constant of type AssemblyHashAlgorithm).
• MajorVersion, MinorVersion, BuildNumber, RevisionNumber
(2-byte constants).
• Flags (a 4-byte bit mask of type AssemblyFlags).
• PublicKey (index into Blob heap).
• Name (index into String heap).
• Culture (index into String heap).

其中Flags项要减去afPublicKey = 0x0001。

3、就是利用Patch系统文件的形式。这种形式不推荐,毕竟强命名还有判别版本差异及文件完整性的作用,整个Patch了不大方便。


二、延迟强命名
    现在的.net程序都被混淆过了。比如你原来写了个计算注册码的程序叫CalRegCode(),但是混淆过的程序名可能是 x0ab23ff10(),或者干脆是不可打印的ASCII字符。但如果你在程序中使用了强命名,混淆不会出错吗?这里其实就要用到延迟强命名。
    在VisualStudio中,点击项目的属性的sign选项,选择延迟强命名:
 

如果不想在窗口中设置,可以直接在AssemblyInfo.cs中设置如下:
   [assembly:AssemblyKeyFileAttribute("key.snk")]
    [assembly:AssemblyDelaySignAttribute(true)]
    延迟强命名就是在编译程序时设置好标志,并将存储空间留出,在编译完成后人工加上。比如,我们可以将strings流中的一些敏感名称,比如crypt()、crypcal()改为不可见:

http://bbs.pediy.com/upload/2006/4/image/3.gif_888.gif

    保存后在命令行用:sn ?Ra xxxx.exe key.snk重新给文件签属强命名便OK了。
    修改名称有个注意事项,如果你修改的方法名称,在别的程序集中对此方法有调用的话,必须在调用的文件中也把相应的方法改为相同的名字,应为这种调 用是按名称来的,否则会报找不到method的错误。但总的来说,如果仅仅对一个文件使用这种延迟强命名的方法,仍然是非常容易修改的。但如果一个主程序 a.exe要调用b.dll和c.dll时(甚至更多,或者b与c间存在调用),而b.dll和c.dll都被加上强命名的话,这样如果修改 b.dll(比如网络验证在b.dll中,我们要对其进行patch),整个程序则会报错。下面一节就讲讲这种整个程序中存在多个文件,且每个文件都有强 命名的情况,看看他的保护强度和破解。我暂时给它起名叫关联强命名。


三、关联强命名
    我们来建一个C#工程,名叫example,其中存在两个dll(a.dll和b.dll),主程序对这两个程序分别存在调用,dll的代码只是弹出窗口显示被调用了,且每个dll都被加上强命名。并在主项目中添加对a和b的引用。
http://bbs.pediy.com/upload/2006/4/image/4.gif_906.gif

    a与b中的代码如图所示。主程序中建两个button控件,分别调用这两个Class Library中的方法。

代码如下:
        

代码:
private void button1_Click(object sender, EventArgs e)
        {
            a.Class1 newa=new a.Class1();
            newa.showMsg();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            b.Class1 newb=new b.Class1();
            newb.showMsg();
        }

单击不同的button会显示不同的窗口,提示当前是哪个lib被调用。如,单击checkLib1的窗口如下:
 http://bbs.pediy.com/upload/2006/4/image/5.gif_922.gif 

    由于a.dll与b.dll中已被加上强命名,我们试着修改一下,看看能否运行。比如将a.dll中窗口的显示字符改为“ClassLibrary A is called”(UserString的存储格式为Unicode):
00001590h: 53 68 6F 77 00 00 00 00 00 31 43 00 6C 00 61 00 ; Show.....1C.l.a.
000015a0h: 73 00 73 00 4C 00 69 00 62 00 72 00 61 00 72 00 ; s.s.L.i.b.r.a.r.
000015b0h: 79 00 20 00 41 00 20 00 69 00 73 00 20 00 63 00 ; y. .A. .i.s. .c.
000015c0h: 61 00 6C 00 6C 00 65 00 64 00 00 00 E5 79 88 40 ; a.l.l.e.d...妁?

运行后单击checkLib1,会报错无法载入a.dll:
http://bbs.pediy.com/upload/2006/4/image/6.gif_940.gif 

    这样看来,如果我们将关键代码分布在多个dll中,且每个dll加上强命名,并且存在一定的相互调用,就可以保护我们的程序集了。是不是这样呢?我们来看看example.exe中对a.dll的引用,看为什么它会认出我们修改了文件。
    Example.exe中的AssemblyRef表中存储了关于a.dll和b.dll的信息,AssemblyRef表的结构如下:
The AssemblyRef table has the following columns:
•  MajorVersion, MinorVersion, BuildNumber, RevisionNumber (2-byte constants)
•  Flags (a 4-byte bitmask of type AssemblyFlags; see Partition II, section 22.1.2)
•  PublicKeyOrToken (index into Blob heap―the public key or token that identifies the author of this assembly)
•  Name (index into String heap)
•  Culture (index into String heap)
•  HashValue (index into Blob heap)

    其中第三项就规定了是否有PublicKeyOrToken,也就是是否有强命名。看看example.exe中的数据:

http://bbs.pediy.com/upload/2006/4/image/7.gif_957.gif

其中PublicKeyOrToken项清清楚楚地写的0x00be,这就是有强命名的标志,强命名存储在#Blog表中第0x00be项。再来看一下#Blog表,第0x00be项如下:

 http://bbs.pediy.com/upload/2006/4/image/8.gif_979.gif

到这一步,我们就应该知道怎么修改了。首先,将example.exe中对a的引用表中的PublicKeyOrToken项的值改为0,然后第二步是去除a.dll中的强命名。修改完成后,我们再运行,果然,系统已经不再报错,并正确显示我们所修改的字符串了:

http://bbs.pediy.com/upload/2006/4/image/9.gif_994.gif 

四、小结
    到这里,强命名的几种应用形式基本介绍完。试想,如果一个有数十个文件的程序每个文件都被加个强命名,其保护强度也是很一般的。我们只须编写程序 自动去除每个文件的本身强命名定义与AssemblyRef中的强命名定义(注意,不要把系统文件包含在内),就可以很方便地进行修改。因此,用 StrongName做为程序的保护手段实在是不可取,还应另寻别路。

附件中是example.exe、a.dll和b.dll,其中a.dll已经修改过,而剩下的修改b.dll留给各位自己动手实践一下。

from :http://bbs.pediy.com/showthread.php?threadid=31970

原文地址:https://www.cnblogs.com/develop/p/3560938.html