More servicesWindows Live
HomeHotmailSpacesOneCare
 
MSN
Sign in
 
 
Spaces home  信息产业部专业鉴定室PhotosProfileFriendsMore Tools Explore the Spaces community
Updated 4/8/2008
Updated 4/5/2008
Updated 3/20/2008
Updated 11/29/2007
Updated 10/30/2007
Updated 10/14/2007
Updated 10/16/2007
Updated 10/4/2007
Updated 10/11/2007
Updated 10/11/2007
Updated 10/11/2007

信息产业部专业鉴定室

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 16

早晨路上联诗

笑死俺了,原来室友们都这么有才:
 
上海四月雨茫茫,
吃喝嫖赌诸事忙。
三把小伞上班去,
到了路上变色狼。
牛仔包臀弹性好,
黑发披肩瀑布长。
两只手套玩细腻,
一根宽粉笑疯狂。
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 的开发帮助:
 
这个对破解很有作用。
 
先恶补一下基础知识:
 
一、Sparc 平台的寄存器架构与汇编规则
 
Sparc 的 CPU 有很多寄存器,但针对任一进程的任一时刻来说只开放 32 个,以寄存器窗口的方式切换。一般它的寄存器可命名为 r0 到 r31,但一般以其别名来命名,如下:
1. 全局寄存器(8个) -对所有程序可见。命名为 %g0 ~ %g7 = %r0 ~ %r7
2. 输出寄存器(8个) -函数返回值,输出寄存器是下一个窗口的输入寄存器。命名为 %o0 ~ %o7,等同于 %r8 ~ %r15
3. 局部寄存器(8个) -仅本函数可见。命名为 %l0 ~ %l7,等同于 %r16 ~ %r23
4. 输入寄存器(8个) -本函数的输入参数,来自于上一窗口的输出寄存器。命名为 %i0 ~ %i7,等同于 %r24 ~ %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 命令有以下:
 
b main
b *0x27aaa
在某命名函数处或在某地址处设置断点。
 
bt
查看此刻的调用堆栈。
 
info reg o0
查看某寄存器值,如果只敲 info reg,则打印所有寄存器值。
 
stepi/nexti
单步执行机器代码。注意不是 step/next,后者是单步执行源码。
 
disable 1
禁用第一个断点。如只敲 disable 则禁用所有断点。
 
enable 1
使能第一个断点。如只敲 enable 则使能所有断点。
 
x /8xb 0x45678
从地址 0x45678 处开始查看 8 个 byte,以 HEx 的方式显示。
 
x /s $o1
查看 o1 寄存器内容所指内存处的内容,以字符串方式显示。
 
set $o1=0
将寄存器 o1 值设置为 0。
 
r、c、k
这三个命令的功能分别为:运行被调试程序、中断后继续运行、中止被调试程序。
三、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 的解密文章,lc_checkout 内部最终会调用 l_string_key 函数来进行比较,但资料里说 9.2 版的 FlexLM SDK 已经在此函数上做了部分手脚,会用伪造的签名来蒙混跟踪者,只有无签名返回时才是正确的场合。根据此点,咱在 l_string_key 函数中还真绕了不少弯路(弯路部分略)。后来跟踪的事实证明,FlexLM 4.1 版本中还没有此狡猾的机制,而是非常直白的调用 l_string_key,并在 l_crypt_private 返回时便能生成正确的明文签名。获得 l_crypt_private 的结果后,上级的 good_lic_key 再调用 strncmp 进行比较,从而得到比对的结果,这就是 FlexLM 4.1 的核心对比。
FlexLM 9.2 或者其他版本估计是察觉到了此问题,便将比对隐藏起来了,但仍然保留着 strncmp 来混淆视听。
 
中断在 l_string_key 中时,用bt查看调用堆栈得到以下信息:
 
#0  0x000277d8 in l_string_key ()
#1  0x00027770 in l_crypt_private ()
#2  0x0001ce98 in good_lic_key ()
#3  0x0001c4a4 in lm_start_real ()
#4  0x0001b61c in lc_checkout ()
#5  0x00017358 in main ()
 
在此,无需刻舟求剑地在 l_string_key 处直接下断以企图获取正确签名值,而应该返回至 l_crypt_private 被调用的地方:
 
.text:0001CE80                 mov     %o0, %o2
.text:0001CE84                 add     %fp, var_20, %o3
.text:0001CE88                 ld      [%fp+arg_48], %o1
.text:0001CE8C                 ld      [%fp+arg_44], %o0
.text:0001CE90                 call    l_crypt_private
.text:0001CE94                 nop
.text:0001CE94
.text:0001CE98                 st      %o0, [%fp+s1]
.text:0001CE9C                 ld      [%fp+arg_48], %o1
.text:0001CEA0                 inc     0x48, %o1       ! s2
.text:0001CEA4                 ld      [%fp+s1], %o0   ! s1
.text:0001CEA8                 mov     0x14, %o2       ! n
.text:0001CEAC                 call    _strncmp
.text:0001CEB0                 nop
.text:0001CEB0
.text:0001CEB4                 tst     %o0
.text:0001CEB8                 bne     loc_1CEC8
.text:0001CEBC                 nop
 
这里,strncmp 的两个参数 s1 和 s2,一个是我们输入的错误 license,一个是它计算出来的正确 license。在 0001CEAC 处中断时用 GDB 的 x /s $o0 和 x /s $o1 命令可看到正确的签名值 8503E11B1950D9B5AA12。
 
最后,无限制的 License 文件生成如下:
 
FEATURE STACK lic.SUNW 8.0 1-jan-0000 uncounted 8503E11B1950D9B5AA12 HOSTID=ANY
 
保存此文件后退出 DDD,再次运行程序,一切 OK。
 
四、总结
 
FlexLM 4.1 的保护方式很薄弱,估计其低版本如 2.6 等也一样。如果 Unix 平台下编译的程序符号表还在的话,简直就是中门大开引狼入室。破解者无需 FlexLM 4.1 的 SDK,也不用寻找什么 seed 什么 key,找到 Feature、版本号和 Vendor Name 后就能在程序中直接跟踪到明文的签名,所以本文写到后来也就变成了初级水平了。
February 13

新春对诗

连绵阴雪终放晴,怀揣忐忑我出门,有惊无险终上车,和谐社会和谐行。
小样扛包往西行,不怕天灾不怕冰,只盼火车不晚点,一路开走就不停。
车轮滚滚终开启,正点发车不足喜,若能来日准时到,烧香三炷拜总理。
恭送科长返家乡,一路山高水又长,若能安然准时到,来日熊猫大烧香。
熊猫烧香固然强,我的本领胜一场,周老能拍华南虎,我让老虎端着香。
老虎端香不足奇,众人和谐泰山移,保您拽得像城管,一天能叫三回鸡。
一梦关河度如飞,穿山越岭鬼难追,晨来欲知君到处,究竟准点还是亏?
醒来车停小站处,打听已离鹰潭多。今天定在宜春落,乐观估计还不错。
千里返家百里停,近乡咫尺不得行。若有拔山填海力,扛着火车跑宜春。
白雪皑皑铺大地,本是美景今不是,如牛列车终又启,爬行十分却再停。
冬雪片片扑面寒,稳健列车开漫长,要是实在不想等,跳下车去继续扛。
绵延铁轨本是路,如今改做停车铺,我若能扛火车走,翻山越岭加速度。
工会貌似又发卡,补贴俺们做牛马,为何还没轮到咱?难道认为俺太傻?
做牛马不如卖傻,发张卡不够塞牙,要轮你飞黄腾达,踢阿朗高谋他家。
斯马特卡又一千,店子虽多不沾边,可惜打折不提现,否则现金多安全。
有卡总比没卡好,消费多了省汉堡,细水长流一核算,天上人间都能搞。
吃饱盒饭虽不饥,车停路顿人更疲,此刻已知乡路近,安心打盹可将息。
天寒地冻车行阻,吃东喝西价钱高,幸有先知早预见,袋里藏着小面包。
行百里者半九十,为了目的要坚持。没准高速已封路,去坐汽车会更迟。
千难万险终抵达,艰辛不比长征差。晚点七时不用怕,坚持最后必到家。
长路漫漫见曙光,熬到白头终返乡。春来苦尽得硕果,家在村头大树旁。
蜜酒淡泊色正娇,半斤下肚腹中烧。已知难躲席后吐,抓杯再向众人邀。
曲径入山天如洗,青竹听雨夜已高。掐指酒程到何日,还剩六