![]() |
|
Spaces home 信息产业部专业鉴定室PhotosProfileFriendsMore ![]() | ![]() |
|
信息产业部专业鉴定室August 05 又被点名了又被考察团团长点名了,而且问题多达三十八个,真三八啊。 没的说,捧场。 1.当你不高兴的时候,你喜欢做什么? July 31 乱捏联话之一国画大师徐悲鸿当年曾在浙江丽水作画顺便寻访故人,后未逢而怅然远游。然故人亦病,后托信于徐,信辗转至藏外大吉岭,彼时大雪封山人难行,且信无地址,回复亦不能,唯无可奈何尔。 当时,好事者曾以“悲鸿绘丽水”出联,后闻大吉岭之事,遂对“喜马拉雅山”,无情流水,严丝合缝,真他妈的牛。 July 25 大饼油条四明治三个角的是三明治,四个角的自然就是四明治了,但一个自产的四明治吃不饱,又补了一份大饼油条,然而后者吃着总没前者爽。早上七点提早起床折腾面包香肠榨菜黄瓜色拉酱凑成四明治,这样新奇的口味以前是没尝过的。人要会感恩会知足,要提前历练做房奴的穷困日子,早晚这四明治会被磨光棱角变成∞明治的。 June 20 转贴:歪批《三国》歪批《三国》 甲: 说相声的谈今论古,《三国演义》、“东、西汉”、“红楼”、“聊斋”。《水浒传》,什么书都看。 June 02 诡异!前段时间台式机出毛病了,偶尔跑着跑着就死机,死机的症状一般是硬盘灯持续亮着,然后就蓝屏。开始以为中毒了或者中木马了,杀了一回没见着。后来逐渐感觉到是进行比较大的硬盘操作时死机的概率大些,比如复制一电影等,便怀疑杀软的监视文件的功能有问题,于是卸掉卡巴裸奔,情况果然好些,但仍然会死。接着怀疑是硬盘文件系统坏了,用CHKDSK能查出几个问题,也能修复,但下回仍旧会死机。于是凄凉地怀疑硬盘可能坏了,只有凑合着继续用。终于有一次死机后Windows少了n多系统文件,CHKDSK也找不回来了,只有重装系统。重装后好了很多,但陆续装上一堆应用软件后,死机的概率越来越大,总怀疑有木马病毒,用IceSword查内核,有几个挂接NTDLL的,是DaemonTool虚拟光驱与360安全卫士。尝试着卸掉360再裸奔,果然几天不死机了,以为找到了原因,沾沾自喜的同时还很专业地抱怨360的内核驱动兼容性差。岂料过几天又开始死了,晕!再卸掉DaemonTool,又安静了几天,这回不敢乱抱怨了,还是静观其变为好。果然过了几天老毛病又开始犯,而且越来越厉害,大有又要重装之势。幸亏上回刚重装好一堆东西比较稳定时做了一个几个G的Ghost,花十几分钟恢复回来也不太费事。一恢复又好些,但跑着跑着又有老毛病重犯的趋势,又猜可能天热了CPU过热引发死机,下了个CPU COOL,看温度也不过20多度,不像,不过为保险起见还是拆开机箱拿风扇吹,但仍旧无效。快抓狂时在敞开的机箱里瞎捣腾,发现主板的串口硬盘线略有松动,难道是它???于是插紧了一下,重启,没死机;等了等,还没死;折腾折腾,仍然不死;陆续玩了半个月了,死机蓝屏的迹象一次都没复现。神啊,老子以后要是被查出有啥神经方面的毛病,就是这个硬盘线插头折腾的! May 23 转贴韩寒的一段话转贴。
“另外,我还想继续说一个事情,我依然坚持我的向有关部门捐款为0,这是我个人的选择。我也依然非常反对逼捐和搞捐款的排名,很多人在背后冷言冷语,有些个人和公司出于善心,追加了捐款,那些人就自以为是自己的功劳,并把这些钱下意识记在自己的账上。他们都是道德的小人,自己制定道德的准绳。在大家都忙着做善事没空的时候,他们闲着,指指点点,我朋友说,他发现这些人都喜欢重复发言,反复变换马甲,在各个地方说一样的风凉话,足以见得他们是闲到一种什么样的程度。他们体力和脑力都非常充沛活跃,网监部门应该把他们登记在案,以后有灾难的时候派他们上场。
前一阵子盛传的肯德基没捐款,麦当劳没捐款,我当时就和朋友说,首先,他们如果没捐款,也不应该受到指责,这是他们的权利,其次,肯德基可能是以百胜的名义捐的。因为这些我么熟知的国际快餐都属于百胜集团。事实证明,的确是以百胜集团的名义捐款了一千多万,而麦当劳也是。但是,一些丐帮人员居然在某一个城市,去肯德基麦当劳门前闹事,这才是添乱和丢脸。
一旦捐款变味,会让做善事者心里很不舒服。比如这次地震,第一批捐款的人应该是最积极的,但到最后,他们都会因为数额少而被人骂。很多人也会借机对自己不喜欢的人传播谣言,这些都是对好心人的打击。
做善事者,内心一定要得到宽慰,如果做了善事还要备受一些什么都没做的人的指责,好人会越来越少,做事会越来越谨慎。” April 25 Sparc 平台上的 FlexLM 7.0 用户滤波函数分析Sparc 平台上的 FlexLM 7.0 用户滤波函数分析
作为一个比较老旧的保护工具,FlexLM的初级保护已经被分析的差不多了,默认情况下内存中能截到明文的签名,这点很多高手的文章中都有提到。但俺近日在分析一款SunOS Sparc平台下的FlexLM 7.0时遇上了 user_crypt_filter 机制,这种机制会对签名加以变形运算,导致内存中不会出现明文,给破解的捕获增加了一些难度。后来参考Nolan Blender大牛的文章,总算破掉了这个用户滤波函数的保护,在此写出来总结一下供有相同需要的朋友参考与讨论。
本文没有提到到具体的软件名称,也不涉及到Feature、Vendor与版本号的跟踪捕获,甚至也和抓取VendorKey和加密种子等没有关系。另外Sparc平台的汇编可能大伙都不熟悉,这儿尽量多加以解释。
工具:DDD+GDB、IDA
一、按传统方法定位明文比较处。
参考laoqian的《制作Flexlm license总结》的文章,以及laowanghai的《LabWindows CVI 8.0》中列出来的详尽x86汇编代码,通过在IDA中搜索常数66D8B337,可以定位到几个地方。以下是laowanghai的《LabWindows CVI 8.0》文章中列出的部分x86的代码供参考:
105C9CDB 894D F4 mov dword ptr ss:[ebp-C],ecx
105C9CDE ^ E9 EEFAFFFF jmp CVI_1.105C97D1 105C9CE3 817D 18 37B3D866 cmp dword ptr ss:[ebp+18],66D8B337 105C9CEA 0F85 96000000 jnz CVI_1.105C9D86 105C9CF0 33D2 xor edx,edx …… 105C9D86 C785 78FEFFFF 08000000 mov dword ptr ss:[ebp-188],8 105C9D90 817D 18 37B3D866 cmp dword ptr ss:[ebp+18],66D8B337 105C9D97 75 0F jnz short CVI_1.105C9DA8 …… 105C9EB0 8B95 30FEFFFF mov edx,dword ptr ss:[ebp-1D0] 105C9EB6 81E2 FF000000 and edx,0FF 105C9EBC 8B85 7CFEFFFF mov eax,dword ptr ss:[ebp-184] 105C9EC2 33C9 xor ecx,ecx 105C9EC4 8A88 98358A10 mov cl,byte ptr ds:[eax+108A3598] 105C9ECA 3BD1 cmp edx,ecx ; 与正确SIGN逐字节的比较 这里,比较之处前面一个and edx,0FF比较的显目,可以作为一个小标志。
类似的,在我们对付的SPARC平台下的软件,其比较代码也类似:
loc_3515CC: ! CODE XREF: sub_3508E4+6B0j
.text:003515CC ! sub_3508E4+CE0j .text:003515CC ld [%fp+arg_54], %o5 .text:003515D0 set 0x66D8B337, %o7 ! 绝对是这儿的常数 .text:003515D8 cmp %o5, %o7 .text:003515DC be loc_3515EC .text:003515E0 nop …… loc_351684: ! CODE XREF: sub_3508E4+D00j .text:00351684 mov 8, %o7 ! 也是这儿的常数 .text:00351688 st %o7, [%fp+var_8] .text:0035168C ld [%fp+arg_54], %l0 .text:00351690 set 0x66D8B337, %l1 .text:00351698 cmp %l0, %l1 .text:0035169C be loc_3516AC .text:003516A0 nop …… .text:003517F8 loc_3517F8: ! CODE XREF: sub_3508E4+EE0j .text:003517F8 ld [%fp+var_4_idx], %o3 .text:003517FC set byte_4DD515, %o4 .text:00351804 ldub [%o3+%o4], %o5 ! ldub取一位字节,相当于同时做了上面的and 0xff了 .text:00351808 ldub [%fp+var_180_oursign+3], %o7 .text:0035180C cmp %o7, %o5 ! 是这儿的比较 .text:00351810 bne loc_351820 .text:00351814 nop .text:00351814 由x86与Sparc的汇编代码对比可以看出,.text:0035180C处确实是我们传入的伪造签名值(我传的是1234567890AB)与正确签名值的比较之处,byte_4DD515这个地址所指的一片内存区域,应该是我们需要的正确签名。于是在0035180C处下断,第一次来到此处时查看0x4DD515地址的内容,得到六个字节EEB53723B248。想当然地拿它放到license文件里头一试,很不幸,通不过。
于是开始了摸索,在0035180C之前下断,逐步来,发现0035180C之前有一处很关键的代码:
.text:003517CC loc_3517CC: ! CODE XREF: sub_3508E4+ED8j
.text:003517CC ld [%fp+arg_44_job], %o0 .text:003517D0 ld [%o0+0x234], %g1 ! Job偏移234处是啥?原来是user_crypt_filter函数地址。 .text:003517D4 ld [%fp+var_4_idx], %o1 .text:003517D8 set byte_4DD515, %o2 .text:003517E0 add %o1, %o2, %o1 ! char*,指向正确的未filter的内容 .text:003517E4 ldub [%fp+var_180_oursign+3], %o3 ! 我们的sign的字符 .text:003517E8 ld [%fp+arg_44_job], %o0 ! Job! .text:003517EC ld [%fp+var_4_idx], %o2 ! Index? .text:003517F0 call %g1 ! 3323a0,user_crypt_filter,结果放在o1指的内容 .text:003517F4 nop 那个call %g1是对解出的明文密码进行的一次变换。刚才抓到的六个字节EEB53723B248,其中EE是已经经过了第一次变换的结果,变换出来的结果和我们传入的伪造签名的第一个字节12不一致,因此出错。如果在003517F0之前下断查看byte_4DD515的内容,则可得到未经变换的第一个字节值。抓出的初始的正确签名值是C1B53723B248,但如果拿这个签名值去license文件里头试的话仍然通不过,因为这串签名值还会经过一次变换,这个变换便是臭名昭著的user_crypt_filter:用户滤波函数。
二、分析用户滤波函数
查FlexLM的资料得知,user_crypt_filter是对签名字符进行的进一步可逆变换机制,包括生成时的user_crypt_filter解码函数和user_crypt_filter_gen编码函数。我们的任务,是根据user_crypt_filter函数的实现,反推出user_crypt_filter_gen函数来。
从代码中以及从大牛们的破解资料中可得知,user_crypt_filter 附近的调用机制基本上是:
for (i = 0; i < j; i++) /* compare user-input and real checksums */
{ ...... if (user_crypt_filter) (*user_crypt_filter)(job, &x, i, y[i]); // 使用了滤波函数进行进一步的对比! if (x != y[i]) return 0; } 而网上查到的很多中文文章中的FlexLM都是没用user_crypt_filter的,也就是user_crypt_filter为false而跳过(*user_crypt_filter)指针所指函数的变换,因此下面的x != y[i]便是正确签名字符与我们输入的伪造签名字符的明文比较。而我们碰到的情况是会调用user_crypt_filter的函数的。
user_crypt_filter 的函数原型为:
user_crypt_filter(job, char* KeySign, int index, char OurChar); 其中job在函数体内基本无用。KeySign为传入的未变换的签名的地址(下面称为原始签名),index为此字符在密文中的位置,如签名为1234567890AB,则12的index为0,34的为1,以此类推。
user_crypt_filter是逐个处理字符的,它根据未变换的原始签名字符、此字符的位置、以及我们传入的伪造签名字符进行一系列的运算返回一个字符,这个字符必须等于我们传入的伪造签名字符。这么说可能有点绕,换种说法就是:未变换的原始签名字符、此字符的位置、以及变换后的签名字符三者必须符合一定的运算法则,user_crypt_filter函数拿我们传入的伪造签名字符作为验证来和原始签名字符参与运算,如果运算通过,则表示伪造的签名字符是正确的变换后的签名字符,那么就将此正确的签名字符返回供上层代码(x != y[i])比较,其实此时已经无需比较了。
可以看出,user_crypt_filter函数中不会出现我们需要的正确签名字符,只有当我们传入的伪造签名字符等于正确签名字符时才会验证通过,才会返回正确签名字符。下面我们要搞清楚的是user_crypt_filter函数究竟对未变换的原始签名字符、此字符的位置、以及我们传入的伪造签名字符三者进行了怎样的变换,从而努力反推出来如何根据未变换的原始签名字符以及此字符的位置计算正确的签名字符。
user_crypt_filter函数非常长,从.text:003323A0 到.text:003374B8,反汇编代码有七千多行,很是吓人。不过经过大致浏览,可以看出它是根据传入的Idx的范围0到19有比较重复的20大段代码,20大段里头每大段会对字符进行异或并对其8位进行处理,共20加160处变换,搞明白了它做啥就好办了。
首先是将传入的字符异或,比如我们传入的Idx是0的话:
.text:003324E0 ld [$XADqrkBrM9j3y7__num0], %l0
.text:003324E4 cmp %l1, %l0 .text:003324E8 bne loc_332504 .text:003324EC nop .text:003324EC .text:003324F0 ldub [%fp+var_2_GoodChar_xor], %l2 .text:003324F4 sethi %hi(unk_496800), %l0 .text:003324F8 ldub [$XADqrkBrM9j3y7__x_0], %l1 ! // 取出num0对应的某常量值(x_0)和GoodChar一异或,存入局部变量 GoodChar_Xor。 .text:003324FC xor %l2, %l1, %l0 .text:00332500 stb %l0, [%fp+var_2_GoodChar_xor] 根据传入的Idx的不同,将未变换的原始签名字符和某一值进行异或,得到临时变量GoodChar_xor。
然后启用另外一个Char变量,这里命名为TmpChar,用来挨个检查GoodChar_xor的位,比如这儿是检查其中一位:
loc_332740: ! CODE XREF: user_crypt_filter+384j
.text:00332740 ld [%fp+arg_4C_Idx], %l1 .text:00332744 sethi %hi(unk_496800), %l0 .text:00332748 ld [$XADqrkBrM9j3y7__num8], %l0 .text:0033274C cmp %l1, %l0 .text:00332750 bne loc_3327BC // 如果此字符的index是8,那么检查 .text:00332754 nop .text:00332754 .text:00332758 ldub [%fp+var_2_GoodChar_xor], %l2 .text:0033275C sethi %hi(unk_496800), %l0 .text:00332760 ld [$XADqrkBrM9j3y7__bit3], %l1 .text:00332764 andcc %l2, %l1, %l0 .text:00332768 be loc_332784 // 如果异或后的字符的bit3位是1 则执行下面的, .text:0033276C nop .text:0033276C .text:00332770 ldub [%fp+var_1_TmpChar], %l2 .text:00332774 sethi %hi(unk_496800), %l0 .text:00332778 ld [$XADqrkBrM9j3y7__bit4], %l1 // 如果异或后的字符的bit3位是1,则将临时变量TmpChar的bit4置为1, .text:0033277C or %l2, %l1, %l0 .text:00332780 stb %l0, [%fp+var_1_TmpChar] .text:00332780 .text:00332784 .text:00332784 loc_332784: ! CODE XREF: user_crypt_filter+3C8j .text:00332784 ldub [%fp+var_1_TmpChar], %l1 .text:00332788 sethi %hi(unk_496800), %l0 .text:0033278C ld [$XADqrkBrM9j3y7__bit4], %l2 .text:00332790 and %l1, %l2, %l1 .text:00332794 ldub [%fp+var_3_OurChar], %l0 .text:00332798 and %l0, %l2, %l0 .text:0033279C cmp %l1, %l0 .text:003327A0 be loc_3327BC // 然后检查我们伪造的签名字符的bit4,和TmpChar的bit4是否相同。 .text:003327A4 nop .text:003327A4 .text:003327A8 ld [%fp+arg_48_charX], %l1 .text:003327AC ldsb [%l1], %l0 .text:003327B0 btog -0x3E, %l0 // 在俩bit不同的情况下,返回垃圾值3E .text:003327B4 ba locret_3374B8 .text:003327B8 stb %l0, [%l1] .text:003327B8 以上是对Index为8时的未签名字符bit3位的判断检查过程:如果GoodChar_xor的bit3是1,则TmpChar的bit4也置一,因为TmpChar原始为0,因此bit4原始也为0,所以这一步就是把GoodChar_xor的bit3搬移到了TmpChar的bit4上。
然后TmpChar的bit4和我们传入的伪造签名字符OurChar的bit4进行比较,相同的话这位检查通过,继续检查下一位,否则返回一个垃圾值-0x3E用以迷惑俺们。总的来说,这步就是检查异或后的GoodChar_xor的bit3是否等于伪造签名字符的bit4,等的话才通过。
参考Nolan Blender的文章,这里检查一个bit的过程用C代码描述如下(这里是index为0时检查GoodChar_xor的bit2与TmpChar的bit5是否相等的情况):
if (idx == num0)
{ if (GoodChar_xor & bit2) TmpChar |= bit5; // 如果inc_c的2置位,那么把另外一个C的5置位,相当于2位换到5位去 if ((TmpChar & bit5) != (OurChar & bit5)) { *KeySign = 0xxx; return; // 不等则返回垃圾} // 其实就是GoodChar_xor的bit2要和输入签名字符的bit5相等。 } 以下还有很多类似的检验,对于每一个index,都要检查GoodChar_xor的8个bit和OurChar的8个bit是否相同,但这8个bit并不是顺序上一一对应,而是乱序(permute)了。比如GoodChar_xor的bit0应该等于OurChar的bit3、GoodChar_xor的bit2必须等于OurChar的bit7,等等。
由此可知,只要将GoodChar_xor按user_crypt_filter中特定的index处所指明的bit置换规则,把各个bit换一下,则可得到解密后的明文签名字符。这也就是说,如果我们在代码中找到了针对一个index的字符的八条位置换规则,如GoodChar_xor的bit0应该等于OurChar的bit3、GoodChar_xor的bit2应该等于OurChar的bit7等共八条,那么我们只要新起一个变量TmpChar,将GoodChar_xor的bit0放到变量TmpChar的bit3,GoodChar_xor的bit2换到TmpChar的bit7,换过8个bit后,这个TmpChar的每一位必然等于解密后的明文签名字符OurChar。这就是user_crypt_filter检查运算的逆运算!
三、编写逆运算程序
逆运算分析出来了,就可以写还原程序了。
这儿参考了Nolan Blender的思想,将0到19个index,每个index所对应的8位bit置换规则写成一个大数组,共一百六十项,从七千多行汇编代码中整理出来相当费力,但所幸只是体力活儿。而且如果是短的签名的话,只要搜集0到5的index所对应的四十八条bit置换规则即可。
以下代码用Delphi实现:
type
TShiftVals = array[0..7] of Integer; TPermute = packed record shiftvals: TShiftVals; end; PerTab: array[0..19] of TPermute = // 搜集得到的位置换规则数组 ( // BIT 0 1 2 3 4 5 6 7 (shiftvals: (0,4,5,3,1,2,6,7)), // idx 00 // (shiftvals: (4,0,5,2,1,3,6,7)), // idx 01 // (shiftvals: (7,1,3,4,0,5,2,6)), // idx 02 // (shiftvals: (4,7,3,6,1,5,2,0)), // idx 03 // (shiftvals: (0,3,7,4,2,5,6,1)), // idx 04 // (shiftvals: (4,3,5,6,7,0 | ||||||||||||||||||||||