上集《主动防御》既是存活系列笔记的最后一篇,也是实战系列笔记的第一篇。 所谓存活,是希望能在比赛中胜出。 所谓实战,是希望能够向实际软件保护迈进一步。 在实战商业环境中,软件供应商想保护自己的产品,不受非法盗版,自己能够合理合法地牟取商业利益。 一种常用手段是:用序列号把自己的软件保护起来,每个用户配一个不同的序列号。只有用户名和序列号匹配的,才能使用软件;不允许未授权者使用该软件。 但是实际应用时,供应商不太可能只卖出去一份copy。而一旦卖出去了一份license,就会面临如下问题: 1)买了这个license的使用者,将license转售/转让他人,使得原版供应商的利益受到伤害; 2)买了这个license的使用者,通过分析license的工作机制,找出KeyGen,产生出其它license,然后销售/转让他人。 在上述2种情形下,要想保护原版的利益 1)主要靠“追溯市场上盗版的license”,然后利用法律手段来保护原版的利益。这种方法理论上是可行的,同时也还需要一些技术手段来防止软件摆脱license而独立运行。这件事情,我们以后再讨论。 2)需要尝试在软件中建立起一种保护机制,使得攻击者即便得到了一组(或者多组)合法用户名和序列号,也不能推导出更多用户名和序列号使得软件运行达到相同的结果。这一点,就是现在笔者想挑战的实战场景。
假设攻击者不会去修改软件,如何设计软件保护方案,使得即使攻击者获得了一组(或多组)用户名和序列号,依然不能找出更多组用户名和序列号?(而软件的原版作者却可以生成任意多组用户名和序列号) (可能有人会迫不及待地来攻击这个条件:“假设攻击者不会去修改软件”,认为这在实战中是不成立的。关于这一点,我们以后再讨论。这里,我们暂且先接受这个条件) 这个问题的答案很简单:IBC 基于IBC算法,原版作者掌握着一个系统私钥。他能够为任意用户名(用户公钥)生成一个序列号(用户私钥),把序列号发给此用户。 用户运行软件时,提供自己的用户名和序列号,软件验证它们之间的对应关系(验证算法是可以公开的),验证成功了才能正常执行后续功能。 对于IBC保护方案,可能的攻击有: 1)攻击者对任意用户公钥,寻找对应的用户私钥。 应对:这个企图会被IBC算法所阻止。没有系统私钥是无法完成这个计算的(详情请问度娘) 2)攻击者跳过验证算法,直接执行后续指令。 应对:前面说了,假设攻击者不修改软件。这里面也包括了:在运行时动态修改IP指针等。总之,我们现在假设攻击者不会干扰软件的执行。 3)攻击者在正常的序列号验证通过之后,在软件正常运行过程中,留下系统快照,将来重现快照,就能在不需要序列号的情况下直接运行软件。 应对:前面说了,假设有技术手段防止攻击者摆脱license而独立运行软件。至于这是一种什么技术手段,我们以后再讨论,OK? 所以,IBC方案是有效的。 但就算是上述所有攻击手段都能被阻止,这种保护方式也不能用于KCTF比赛。因为解题方法中使用到了一个攻击者所不拥有的系统私钥。 所以,为了满足KCTF的规则,以便于与雪友们切磋技术,我们把挑战目标改为: 在软件代码对攻击者公开,且运行时不被干扰的情况下,给攻击者若干组用户名和序列号,然后考验攻击者能否找出新用户名和序列号,能通过软件对用户名与序列号匹配关系的判定。
《部落冲突》是对一人一密模式的第一次尝试。 虽然此题在比赛时间内无人破解,算是存活成功,但是却不算是理想的一人一密。原因是:此题只能为“狂场”和“你”,这2个用户名提供正确序列号,却不能对任意用户名提供序列号。 ccfer慧眼,早在比赛期间就指出了这个问题,赛后还公布了一个“水浒版”的crackme,给出了108将的序列号,并指明了“not only 108”,表示还可以设计更多个序列号,显然对此题的序列号设计机制已经完全了解并运用自如了。然而,这种机制必须是在“首先确定了用户名列表”的前提下,才能嵌入各用户名的序列号。要想支持更多的用户名,就必须修改软件。 如果是在实际软件保护的场景下,往往软件作者难以在发布软件之前就能获得完整的用户名列表。而一旦有新的用户前来购买License,那么此方案将迫不得已修改软件。这样不太合理。 因此,进一步规范了一下挑战规则。
防守方在发布CrackMe时,应向大众公开一组用户名和序列号,即“Name/Serial”,其中公开的这个用户名“Name”,必须是该CrackMe文件的hash值。hash算法指定为SHA256,用户名为hash结果的前64bit的16进制大写文字 例如:参赛CrackMe.exe文件的hash结果是 50be38745d82d93f3a974701e86c1cafcbc2ec83d1f1913d216079022ba7317f 则用户名“Name”应为 50BE38745D82D93F 如果CrackMe不止一个文件的话,计算hash时应包含CrackMe的所有文件(第三方共享库除外)。 参考hash计算工具:http://www.atool9.com/file_hash.php
1)若攻击方找出特定用户名(“KCTF”,不含引号)的序列号,经KCTF系统自动确认,将认定攻击方获胜; 2)若攻击方找出特定用户名(“KCTF”,不含引号) 的第二个序列号,经KCTF官方确认,将认定攻击方获胜,且此题多解; 3)若攻击方找出任何一组或多组其它用户名及序列号,KCTF不接受,但会赢得防守方更多的尊重:)
1)干净环境中,10秒内出提示且不能虚假提示; 2)KeyGen算法不能基于“未在CrackMe中公开的秘密信息”。如果需要穷举,则穷举时间必须小于5分钟; 3)不能依赖网络、不能依赖硬件; 4)禁止使用第三方保护工具、禁止恶意破坏机器; 5)不超过10M; 6)同一用户名不应有多个序列号,否则罚分。 7)放宽技术限制 i. 不限制使用套娃。可以使用任何数据和代码变换; ii. 不限制线索隐藏方式。可以将线索以任何形式置于CrackMe的任何位置; iii. 序列号字符集限定为['!','~'],即ASCII码范围是[33,126],共94个可选字符。
此题原名《蜗牛的传家宝》,参赛时改名为《传家之宝》。
此题一改看场雪过去的出题风格,没有在算法上设卡,而是转战代码流。不过作者并不擅长这个领域,这算是个处女作吧。有设计不妥,还望各位大佬指正! 此题设计特征如下: 所有数据都是明的,没有需要攻击者破解的数据; 所有数据的相互关系也是明的,没有隐晦的数学关系; 所有解题所需的信息都在明处,没有隐藏任何线索; 所有攻击者看到的线索都是真的,没有设置误导线索; 所有指令都是会自动执行到的,没有需要寻找的代码; 所有流程都很直白,没有异常,没有中断,没有回调; 所有代码都是完整的,没有调用任何第三方API(接口部分除外)。
这样的防守题,有什么难点吗? 有的,它只有一个难点:
长!
然而,比赛已经限制了crackme的长度。蜗牛又能比别人长出来多少呢? 况且过去也有防守题codesize较大的。并未见到有哪道题是因为代码太长而无法被及时破解的。就算是市面上10+M,100+M的软件也都经常被破解。 长,能有多大用呢?
其实,对于破解者来说,代码长不是问题。 因为破解者并无必要真的去阅读这些代码!大多数时候,破解者只需要“眼看手跳”就能快速pass大量无关破解的代码,直捣黄龙。
所以,要想挡住破解者,不仅需要代码长,更重要的是:需要阻止“眼看手跳”!
如何才能阻止眼看呢? 很简单,把代码加密了就行了。 代码一边运行一边解密(SMC),就能阻止眼看。
如何阻止手跳呢? 所谓手跳,本质上就是下断点。
调试方和反调试方围绕‘断点’展开的斗争,由来已久。简单地说: 调试方的目标是: 1)能断下; 2)能继续; 3)下次还能断下。 为了做到这3点,调试方想出了很多招。在此不一一列举了。
反调试方的目标只有1个:破坏调试条件。 只要破坏了调试方的调试条件,就能瞒天过海,不让调试方知道程序在干什么。同时,还不能影响正常业务流。 (这也是“薅羊毛”和“反薅羊毛”的主要战场)
此题,为了阻止眼看手跳,引入了一种新加壳方式。我们称之为:
蜗牛壳
始祖蜗牛以序列号作为起点,上路了。 蜗牛露头不多,但是背着一个大蜗牛壳。 蜗牛露出来的部分,肉很少,只负责往前走一小步,随即展开自己的蜗牛壳,抛出自己的下一代蜗牛。 下一代蜗牛,也背着一个蜗牛壳(稍微小一点) 它露出来的部分,也有一点肉,也只负责往前走一小步,随即展开自己的蜗牛壳,再抛出自己的下一代蜗牛 ... 最后一代蜗牛,会检查自己是否走到了正确的目的地(是否命中了用户名)? 如果正确,则显示‘序列号正确’;否则显示‘错误’。
用户输入的序列号,如同传家宝,在蜗牛家族里世代相传。 每一代蜗牛都坚守自己的使命,孵化序列号向前演进一小步,直到到达最终理想状态(输入的用户名)。 如果破解者能够看清每一代蜗牛在序列号上做的那么一小点孵化动作(露出来的那一片肉),并且全部拼装起来(变成一整块肉),就能知道如何从最终的用户名反向逆推出输入的序列号。
反之,如果蜗牛家族能够不让对手看清某些蜗牛的动作(缺少几片肉),那么攻击者就很难找出逆推算法。 那么,蜗牛家族是如何让对手看不清的呢?
SMC技术能够有效防止破解者看到未来太远的代码。 解密算法和密钥,都在上一代的蜗牛体内,是破解者可以获得的。所以加密算法不必太强(强也没用)。只是一层障眼法而已,挡住肉眼就行了。
比赛实践证明,这个技术对防护来说,贡献不大。至多只能当做一种辅助手段。
每一代蜗牛展开蜗牛壳,抛出下一代蜗牛时,随机指定下一代蜗牛的执行地址。 其目的是破坏调试目标3:“下次还能断下”
比赛实践证明,这个地址随机化没能有效阻止高手的跟踪。要么被锁定了随机数,要么被动态跟踪。
壳归壳,肉才是需要保护、不想被破解者看清的核心内容。 此题中所谓肉就是:从序列号到用户名的计算过程,原本是一段魔改AES代码(简称:AES_X)。 (注:这个计算过程一定要存在高效逆算法,否则出题方难以设计注册机) 需要将AES_X的代码切成切片,然后将每个切片贴到每层壳上。每层壳被执行的时候,相应的切片也会被执行。且需要保证: 1)切片代码可以运行于任何内存地址(因为地址随机化了) 2)每相邻2层切片之间的执行现场能够平顺衔接(不能被壳干扰) 3)切片不可以循环,也最好不要分支(无分支编程技术,在这里发挥着重要作用) 有多少层壳,就切成多少片切片。如果层数大于指令数,就先对指令膨胀,然后再切片。 力图做到:破解者任意缺失了某个切片,都难以找出AES_X的逆运算。
比赛实践证明,此题壳与肉的特征明显。对手找到后分离了壳和肉,去壳取肉。
蜗牛壳在运作的时候,不调用任何外部的API,不使用外部的堆栈。代码执行时,不离开蜗牛壳。 这样设计的目的是:防止破解者在蜗牛壳之外设置断点,截获壳内信息。
比赛实践证明,虽然我们试图回避壳内代码与外界的交互,但是却没提防破解者在壳内种植代码并逐层hook关键操作。而且,我们还是留下了一下操作外部数据的痕迹。
就算是我们采用了内存校验,也难以保证校验代码不被patch掉。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2019-9-25 13:19
被kanxue编辑
,原因: