【问题标题】:Obfuscation of checksum guards校验和保护的混淆
【发布时间】:2016-07-28 15:35:45
【问题描述】:

作为我项目的一部分,我必须在名为校验和保护的 C 程序中插入小代码。这些守卫所做的是他们使用对指令操作码进行操作的函数(加法、异或等)计算部分代码的校验和值。因此,如果有人篡改了该代码区域中的指令(添加、修改、删除),则校验和值将发生变化,并且将检测到入侵。

这是讨论这种技术的研究论文:

https://www.cerias.purdue.edu/assets/pdf/bibtex_archive/2001-49.pdf

这里是守卫模板:

guard:
      add ebp, -checksum
      mov eax, client_addr

for:
      cmp eax, client_end
      jg end
      mov ebx, dword[eax]
      add ebp, ebx
      add eax, 4
      jmp for
end:

现在,我有两个问题。

  1. 将守卫放在程序集中会比放在源程序中更好吗?

  2. 假设我将它们放在程序集中(在适当的位置)我应该使用什么样的混淆来防止保护模板很容易被看到? (因为当我有超过 1 个守卫时,攻击者不应该轻易找出所有守卫模板并一起禁用所有守卫,因为这样会使代码没有安全性)

提前谢谢你。

【问题讨论】:

    标签: security assembly obfuscation checksum


    【解决方案1】:

    从攻击者(没有来源)的角度来看,第一个问题无关紧要;他正在篡改最终的二进制机器代码,无论它是从 .c 还是 .s 生成的,都会产生零差异。所以我主要担心如何使用适当的校验和生成正确的二进制文件。我不知道如何在 C 源代码中获得正确的校验和。但我可以想象有一些外部工具在 C 编译器创建的汇编文件上运行,以某种后处理方式 - 在将 .s 文件编译成 .o 之前。 但是...请记住,一些调用和地址只是相对偏移量,加载到内存中的二进制文件由操作系统加载程序根据链接器表进行修补,以使它们指向真实内存地址。因此数据字节会改变(操作码将保持不变)。

    您的警卫模板没有考虑到这一点,并且还校验了带有数据字节的整个操作码(一些高级警卫具有操作码定义,并且校验和/加密/解密仅操作码本身而没有操作数字节)。

    否则很简单,结果是损坏的 ebp 值,破坏了 (*) 周围的任何 C 代码与堆栈变量一起使用。但这仍然是人工测试,您可以简单地注释掉 add ebp,-checksum 和 add ebp,ebx 使守卫无害。

    (*) 请注意,您必须在一些经典的 C 代码之间放置防护,以从无效的 ebp 值中获取一些真正的运行时问题。如果你把它放在以 pop ebp 结尾的子程序的末尾,一切都会很好。

    那么第二个问题:

    您肯定想要更多的恶意方法来保护正确的值,而不仅仅是 ebp 损坏。通常最难(去除)的方法是让校验和值成为某些计算的一部分,最终结果只是略微倾斜,因此无法认真使用 SW,但用户需要一段时间才能注意到。

    你也可以使用一些真正的代码循环来添加你的校验和,所以简单地跳过整个循环也会跳过有效的代码(但我可以想象这个只是手动添加到从 C 生成的程序集中,所以你必须在每次新编译特定 C 源代码后重做它。

    然后可以通过任何可以想象的突变(使用不同的寄存器,指令的修改顺序,指令变体)来混淆特定的保护模板,尝试搜索具有突变编码的病毒以获得一些想法。

    我没有读过那篇论文,但从图中我想说,重点是让那些保护区域重叠,所以修补其中一个会影响另一个,这对我来说听起来像是额外的糖使其具有一定的功能性(尽管这仍然看起来像是对 8 位游戏破解者的正常挑战;),甚至不是“硬”级别)。但这也意味着您需要非常狡猾的外部工具来计算依赖关系的循环树,并以正确的顺序插入保护模板,或者完全手动再次执行。

    当然,手动执行时,您必须在每次新的 C 编译后执行此操作,因此只有在非常珍贵和昂贵的东西或坚如磐石的稳定方面才值得付出努力,您不会在接下来的 10 年或所以... :D

    【讨论】:

    • 感谢您的回答。但我有一个疑问。 1. 你说“但是......请记住,一些调用和地址只是相对偏移量,加载到内存中的二进制文件由 OS 加载器根据链接器表进行修补,以使它们指向真实的内存地址。因此数据字节会改变(操作码将保持固定)。”我该如何解决这个问题?
    • 取决于... 从程序集看,它看起来像 x86 平台。你可以查看这篇文章来了解什么被重新定位以及如何重新定位:eli.thegreenplace.net/2011/08/25/… 要解决这个问题,你必须只保护内部代码(不使用外部符号)。但这只是其中的一部分,由于地址空间随机化,内部代码仍然被重新定位。您还想使用 PIC (en.wikipedia.org/wiki/Position-independent_code)。我不是操作系统专家,不能保证这两个足以防止任何二进制加载时修补,但有机会。
    • 另一种方法是创建仅适用于指令操作码而非数据字节的保护代码。所以它只会获取操作码字节,然后根据指令长度增加内存指针(二进制操作码可以以某种逻辑方式解析以计算某些CPU上的指令长度,但x86可能已经如此复杂,查找表可能在这里有用...或两者兼而有之,尤其是如果您知道某些特定的防护将仅在操作码的某些子集上运行,因此您的逻辑可能会因操作码超出此范围而失败=额外的防护测试)。
    • 好的。谢谢你。您是否认为使用加密会比通过更改指令及其顺序进行简单混淆更好(更容易破解)?另外,由于我希望混淆尽可能强,您知道可以评估混淆强度的任何工具/技术吗?
    • 但我有点想知道你为什么要打扰。没有源代码发布的 SW 在发布时几乎 [un] 死了,所以你在浪费你的生命时间来创造一些很快就会消失的东西。
    猜你喜欢
    • 2016-05-16
    • 2023-03-05
    • 1970-01-01
    • 2012-03-27
    • 2018-11-03
    • 1970-01-01
    • 2021-11-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多