![]() |
|
Spaces home 信息产业部专业鉴定室PhotosProfileFriends | ![]() |
|
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,1,2)), // idx 05 // (shiftvals: (2,7,4,0,6,5,3,1)), // idx 06 // (shiftvals: (4,5,1,7,0,3,6,2)), // idx 07 // (shiftvals: (1,0,5,4,3,6,7,2)), // idx 08 // (shiftvals: (2,7,5,3,0,6,1,4)), // idx 09 // (shiftvals: (3,5,7,0,6,4,2,1)), // idx 10 // (shiftvals: (7,6,5,0,4,3,2,1)), // idx 11 // (shiftvals: (3,4,5,6,0,2,1,7)), // idx 12 // (shiftvals: (0,4,6,3,5,2,1,7)), // idx 13 // (shiftvals: (1,5,0,2,6,3,4,7)), // idx 14 // (shiftvals: (2,6,5,7,4,3,1,0)), // idx 15 // (shiftvals: (2,3,0,5,1,7,6,4)), // idx 16 // (shiftvals: (1,7,4,2,3,0,6,5)), // idx 17 // (shiftvals: (1,5,2,3,4,7,6,0)), // idx 18 // (shiftvals: (3,0,6,7,4,5,2,1)) // idx 19 // ); 如 (shiftvals: (0,4,5,3,1,2,6,7)), // idx 00 //,表示对于index为0的字符,其bit0位置换到bit0位(两位可以相同),bit1置换到bit4,bit2置换到bit5,等等以此类推。
另外还有一次异或,此异或的数字也是常数,根据index不同而不同,是上文代码中的x_0等形式的变量。总结得出一个异或数组:
var
xorvals: array[0..19] of Integer = ( $1E, $16, $3E, $24, $04, $1E, $1E, $13, $15, $0C, $2D, $33, $3D, $21, $26, $2E, $12, $34, $01, $2B ); 然后写一个解密函数与bit置换函数(参考了:Nolan Blender的思想)
// 传入计算来的原始的签名字符与位置,返回变换后的正确签名字符
function user_crypt_filter_gen(inchar: Char; idx: Integer): Char; var tmpchr: Char; begin tmpchr := Chr(Ord(inchar) xor xorvals[idx]); // 先异或还原 Result := Permute(tmpchr, PerTab[idx].shiftvals); // 再置换位置 end; // 将传入字符按大数组表内的规则进行置换位置
function permute(inchar: Char; shiftvals: TShiftVals): Char; var outval: Integer; i: Integer; shbit: Integer; // Test bit */ begin outval := 0; shbit := 1; for i := 0 to 7 do begin if ((Ord(inchar) and shbit) <> 0) then begin outval := outval or (1 shl (shiftvals[i])); end; shbit := shbit shl 1; end; Result := Chr(outval and $FF); end; 然后,利用上面两个写好的函数对抓出的未变换签名值C1B53723B248进行运算:
procedure TForm1.FormCreate(Sender: TObject);
begin Edit1.Text := IntToHex(Ord(user_crypt_filter_gen(#$C1, 0)), 2)+ IntToHex(Ord(user_crypt_filter_gen(#$B5, 1)), 2) + IntToHex(Ord(user_crypt_filter_gen(#$37, 2)), 2) + IntToHex(Ord(user_crypt_filter_gen(#$23, 3)), 2) + IntToHex(Ord(user_crypt_filter_gen(#$B2, 4)), 2) + IntToHex(Ord(user_crypt_filter_gen(#$48, 5)), 2); end; 运行程序后,Edit1.Text中得到生成的正确签名值FB999098AEAA。拿这串签名填入license文件中,测试通过。
再次跟踪user_crypt_filter可知,比如对index为0的情况,传入原始字符C1与index 0,C1与1E异或后的值经过八次位置换得到FB,和我们传入的FB相等,因此通过,返回FB(只要有一位不等,就会返回垃圾值,外部的比较必然通不过)。user_crypt_filter外部再用返回的FB与我们传入的FB比较,自然相等,也就通过了。
四、总结
user_crypt_filter的核心是字符的位置换规则,需要通篇阅读代码以总结出一百六十个位置换规则来,这一点比较的耗体力并且不能出错,否则通不过的情况下再次调试就需要跟入七千多行的user_crypt_filter以找到出错点,这体力耗费的就不是总结位置换规则所能比的了。俺在总结过程中还好没在位置换规则中出错,但搞错了一位xorvals中的异或值,事后查起来也耗了一点力气,等到所幸最后还是找出来的时候已经快累趴下了。
参考资料:
http://www.woodmann.com/crackz/Tutorials/Nbufilt.htm laoqian的《制作Flexlm license总结》的文章 laowanghai的《LabWindows CVI 8.0》 其他看雪论坛的精华文章 http://www-curri.u-strasbg.fr/documentation/calcul/doc/ProPack/3SP1/docs/doc/lmsgi-9.2.3/flexref/chap21.htm April 23 骂一通无知的粪青和无耻的冷漠者最近网络不平静的很,垃圾消息充斥着QQ与MSN,一群群粪青的本性在垃圾中暴露无遗:冲动、无知、排异、意淫。喊抵制是弱者的叫嚣,比弱者的叫嚣更可悲的是认识不到自己是弱者。敏感的自尊受了刺激却又找不到藏独分子出气,于是拿一家本地化已经相当严重的超市开刀,且不说打砸抢的提议本身就可笑,即使有那么一批稍许清醒的人提倡静静的抵制,这种提议也被淹没在积极购物不付款等损毁性的馊主意中。随着恶意降临的还有无知与意淫,什么捏造的令人呕吐的李白的诗,什么法国政府拨款二千万美金,无数缺乏起码思考能力的粪青们被利用了还不自知,还在积极无偿地宣传家乐福的五一促销活动。更无知的还有老一套的网站攻击,先是南联盟时期发动网民Ping美国,再是现在发动网民刷新CNN网站,中国那点儿可怜的出口带宽被挤得一塌糊涂,只平白无故替对手增加了访问量,没准其Alexa排名还会因此上升。——我们的网民啊,我们的年轻人啊,我们什么时候才能冷静一点地思考?有些人MSN/QQ好友栏里头明明没一个外国人,却叫嚣着改昵称改头像让全世界看华人的团结?中国在世界上的地位很低四处是敌,要获得敌人的尊重比从敌人处得到嘲弄和蔑视难得多。现在的闹剧,你们以为得到的是什么?一盘散沙是粪青的最大特征,从来没法凝聚起来做实事,从来以为一时的口眼之快就是爱国。中国这位母亲需要的是每一个位儿女的踏实奉献,而不是粉墨登场地——事实上,登场的档次根本算不上,除非真去打砸抢——出演让国内外都感到可笑的闹剧。一边体育不要和政治挂钩,一边经济和政治又在挂钩,中国是美国国债的第二大股东,中东地区有没闹着抵制俺们的加班中兴与跳楼华为?如果你说抵制日货是习惯没法改了,那么以前为抵制日货而买美法等货的人怎么现在也成了粪青的眼中钉人中奸?和自己的切身利益无关的人容易跟着瞎起哄,起哄完毕一转身继续屁颠屁颠地照样吃吃买买地亲美亲法,这就是俺们的抵制?
第二类该骂的是那些貌似冷静实则冷漠的无耻者。国家不关你的事,民族不关你的事,奥运不关你的事,就钱关你的事。如果您是低保户无法糊口,这种现象俺能理解,可你丫还不是装白领上班上网玩MSN?你可以问国家民族是什么,可以问如果是一块面包它有多大,也可以问如果是一件衣服它有多暖和,更可以问如果是一间房子能不能为我们挡住风雨,但你又是什么?你是游荡的无业民?你是吃喝的啃老族?还是说你是只会索取不会奉献的寄生虫?一个国家不能只靠雇佣军战斗,一个青年也不能只靠冷漠来活着。别告诉你丫不想做青年,五四歇半天丫还不是屁颠屁颠地羡慕得捶胸顿足?死水一样的冷漠者和洪水一样的粪青同样可恶,后者是无知,前者则是无耻。不是一般的无耻,还是很黄很暴力很傻很天真很不和谐的无耻!
骂完了,该干啥还干啥。请随便对号入座来着。 April 02 迎风喝豆花的恶果近日早餐比较喜欢吃摊韭菜煎蛋饼与咸豆腐花,时常伙同俩室友晚起坐在那儿饕餮而不管是否迟到。只是卖豆花的老板不知道没钱租场地了还是怎么的,原先的店面愣是锁着空着,只在外头路边摆下一长条木桌供人挤坐着匆忙地吃。这天一早风很大,照例俺们仨挤在桌子角边,吃得差不多时,对面的一女的没吃完就不厚道的起身离开,那挡风的身板一走,她没喝完的豆花碗被风一吹哗啦就朝这边盖过来洒了俺们一腿,我靠!悻悻然地放下餐具找纸来擦,刚放下,室友自己没喝完的豆花碗被风一吹哗啦又朝这边盖过来又洒了俺们一身!我靠!俺们赶紧站起身来胡乱擦,这时,第三个俺的没喝完的豆花碗被风一吹哗啦啦地又盖了过来…… March 26 Sparc平台下的FlexLM 4.1明文比对破解网上关于破解 FlexLM 的文章大多是 Windows 平台的,而且版本很高。但近日俺碰到的一款 SunOS Sparc 平台上的软件也是 FlexLM 保护,这可是个希罕物件,又加上整个公司只买了 2 个 License,一块用的时候俺基本轮不上,于是就带着尝试的想法拿它开刀。IDA 反了反这个 ELF 格式的主程序文件,字符串中看见是 FlexLM 4.1 版的,而且程序的字符串块没被 strip 掉,FlexLM 的相关函数名居然都在,省掉了找 Unix 下的 FlexLM SDK 的Signature 的麻烦。而且,这个 4.1 的版本由于相当低,其密文字符串的对比居然是明码标价,实在不符合其商业产品的特性(不过这也是事后才知道的)。
工具:IDA,DDD/GDB;
方法:静态阅读+动态跟踪。 网上找不到 FlexLM 4.1 的 SDK,但能找到的比较全的 FlexLM 4.0 的开发帮助:
http://wwweic.eri.u-tokyo.ac.jp/computer/manual/lx/SGI_Developer/books/FLEXlm_PG/sgi_html/index.html 这个对破解很有作用。
先恶补一下基础知识:
一、Sparc 平台的寄存器架构与汇编规则
Sparc 的 CPU 有很多寄存器,但针对任一进程的任一时刻来说只开放 32 个,以寄存器窗口的方式切换。一般它的寄存器可命名为 r0 到 r31,但一般以其别名来命名,如下:
通过 call 指令进行函数调用时,函数开始可调用 save 指令分配相应的栈空间并进行窗口切换,这个窗口切换会将切换前的 o 系列寄存器映射为切换后的 i 系列寄存器,函数返回时通过 restore 指令切换回来。这条规则要记住,否则进入函数前后参数和返回值都不知道去哪儿找。
一般进入函数之前给 o0 到 o5 寄存器赋值作为传入参数(不够则用堆栈),进入函数之后访问对应的 i0 到 i5 便可访问对应的传入参数,函数内部给 o0 赋值可作为返回值使用。
此外 Sparc 还有一些其他特点:访存用 ld/st 指令,和寄存器之间操作数据不同;跳转指令是 b(ranch),后缀表示各种条件,和 80x86 平台下的 j 系列一样;call 与 b 跳转指令后都有一个 nop,估计是给 Sparc 的指令预取机制或者跳转预判断机制准备的。
二、GDB/DDD 常用命令:
Data Display Debugger(DDD)是 Unix 平台下基于 GDB 的一款图形化调试工具,虽然用起来没 OllyDbg 等方便,但毕竟比 gdb 的纯命令行好得多,至少不需要频繁敲 disas 命令来查看反汇编代码,而且还可方便设置多个 display 来查看寄存器值,也不需要频繁敲 info reg o0 之类的命令。因此凑合着用它了。
对调试汇编代码比较有用的 gdb 命令有以下:
三、FlexLM 解密过程
用 IDA 反汇编看,程序一开始经过必要的初始化后,便会调用 l_init 开始 FlexLM 的检查,此间暴露了 Vendor Name、Feature 名,版本号等(见下)。然后程序逐步会进入核心的 lc_checkout 函数。如需要爆破,则修改 lc_checkout 的返回值即可,但俺的目的是想获得真正能用的 License,因此继续。
.text:00017194 loc_17194: ! <suspicious> ! CODE XREF: main+5DCj
.text:00017194 sethi %hi(0x50800), %i0 ! <suspicious> .text:00017198 sethi %hi(0x50800), %i1 ! <suspicious> .text:0001719C st %g2, [prog] .text:000171A0 mov 1, %g2 .text:000171A4 sethi %hi(0x4C000), %o0 ! <suspicious> .text:000171A8 st %g2, [num_lic] .text:000171AC set lm_job, %l5 .text:000171B0 set s_Lic_sunw, %o1 ! "lic.SUNW" .text:000171B8 add %l5, 4, %o2 ! int .text:000171BC ld [lm_job], %o0 ! int .text:000171C0 call lc_init lc_init 函数调用前需要传入 Vendor Name,此处得知是“lic.SUNW”。
.text:0001732C loc_1732C: ! CODE XREF: main+714j
.text:0001732C ! main:loc_172D0j .text:0001732C st %g2, [%sp+0xB10+var_AB4] .text:00017330 set s_Stack_1, %o1 ! "STACK" .text:00017338 sethi %hi(sccsid_4), %g2 .text:0001733C ld [%l5], %o0 .text:00017340 set s_8_0, %o2 ! "8.0" .text:00017344 sethi %hi(0x4C000), %g2 ! <suspicious> .text:00017348 ld [%i1+0x13C], %o3 .text:0001734C set code, %o5 .text:00017350 call lc_checkout Feature 名是“STACK”,版本号(Version Number)是“8.0”。
然后可根据看到的 Feature 名、Vendor Name 和 Version Number 构造了一个就一行的 license.dat 文件: FEATURE STACK lic.SUNW 8.0 1-jan-0000 uncounted 1234567890ABCDEF HOSTID=ANY
构造此 license 文件的目的是构造针对此 Feature 的无时间限制、无数量限制、无运行主机限制的完全 license。当然,其中签名部分是瞎凑的,目的是为了跟踪到和正确签名的比对的部分。
构造完文件后,将环境变量 LM_LICENSE_FILE 指向此文件 /tmp/license.dat
export LM_LICENSE_FILE=/tmp/license.dat
根据网上翻译的 FlexLM 9.2 的解密文章,l |