zaas 发表于 2016-2-27 15:13:42

[Mac]ftosoft PDF Password Remover 算法分析及算法注册机

本帖最后由 zaas 于 2016-2-27 15:19 编辑

【破文标题】ftosoft PDF Password Remover 算法分析及算法注册机【破文作者】zaas【破解工具】hopper disassembler + ida【破解平台】Mac OS X
好几年没写过算法分析的文章了。今天一来为了记录一下开始进入mac的世界,二来因为mac的分析文章太少,抛块砖。mac也没啥好怕的,除了工具不甚趁手。。。。
很容易即可找到关键的验证函数,这一点甚至比windows下更容易。很多函数名都是作为参数传递给Msg_Send的。。。
__ZN7CHATReg9DecodeKeyEPKcS1_RcRl:      // CHATReg::DecodeKey(char const*, char const*, char&, long&)
00089b3f         mov      edi, dword       ; fake code
00089b42         mov      dword , edi      ; argument "s" for method imp___symbol_stub__strlen
00089b45         call       imp___symbol_stub__strlen
00089b4a         xor      bl, bl      ; len code = 0x30
00089b4c         cmp      eax, 0x30      ;长度a不对,死
00089b4f         jne      0x8a01b

00089bbf         mov      dword , edi                                 ; argument "s" for method imp___symbol_stub__strlen
00089bc2         call       imp___symbol_stub__strlen
00089bc7         mov      dword , eax                           ; argument "n" for method imp___symbol_stub__memcpy
00089bcb         mov      dword , edi                           ; argument "src" for method imp___symbol_stub__memcpy
00089bcf         mov      dword , esi                                 ; argument "dst" for method imp___symbol_stub__memcpy
00089bd2         call       imp___symbol_stub__memcpy
00089bd7         mov      al, byte       ;取字符1
00089bd9         mov      byte , al
00089bdb         xor      bl, bl      ;first char = 0x31
00089bdd         cmp      al, 0x31
00089bdf         jne      0x8a01b      不是“1”,死

00089be5         mov      dword , 0x0
00089bec         inc      edi
00089bed         mov      dword , edi                           ; argument "src" for method imp___symbol_stub__strncpy
00089bf1         lea      edi, dword
00089bf4         mov      dword , edi                                 ; argument "dst" for method imp___symbol_stub__strncpy
00089bf7         mov      dword , 0x3                           ;从第二位取三位
00089bff         call       imp___symbol_stub__strncpy
00089c04         mov      dword , edi                                 ; argument "str" for method imp___symbol_stub__atoi
00089c07         call       imp___symbol_stub__atoi      ; 转数值
00089c0c         mov      edi, eax

00089c44         call       imp___symbol_stub___ZNSt18basic_stringstreamIcSt11char_traitsIcESaIcEEC1ESt13_Ios_Openmode ; std::basic_stringstream<char, std::char_traits<char>, std::allocator<char>

00089cb0         call       imp___symbol_stub___ZNKSt15basic_stringbufIcSt11char_traitsIcESaIcEE3strEv ; std::basic_stringbuf<char, std::char_traits<char>, std::allocator<char> >::str() const
00089cb5         sub      esp, 0x4
00089cb8         mov      eax, dword       ; 转为2进制字符串,不足10位补够10位

00089cf2         mov      edx, 0xffffffff                                     ; XREF=__ZN7CHATReg9DecodeKeyEPKcS1_RcRl+468
00089cf7         lock xadddword , edx
00089cfc         test       edx, edx
00089cfe         jg         0x89cec

00089d00         lea      ecx, dword
00089d03         mov      dword , ecx
00089d07         mov      dword , eax
00089d0a         call       imp___symbol_stub___ZNSs4_Rep10_M_destroyERKSaIcE   ; std::string::_Rep::_M_destroy(std::allocator<char> const&)
00089d0f         xor      edi, edi
00089d11         xor      al, al
00089d13         jmp      0x89d30
00089d15         movsx      ecx, byte                          ; XREF=__ZN7CHATReg9DecodeKeyEPKcS1_RcRl+566
00089d1a         lea      edx, dword
00089d1e         call       __ZL14getTableLettercRcb                            ; getTableLetter(char, char&, bool), XREF=__ZN7CHATReg9DecodeKeyEPKcS1_RcRl+575
00089d23         xor      bl, bl
00089d25         test       al, al
00089d27         je         0x8a00d
00089d2d         inc      edi                                                 ; XREF=__ZN7CHATReg9DecodeKeyEPKcS1_RcRl+561
00089d2e         mov      al, bh
00089d30         cmp      edi, 0x9                                          ; ; edi as count
00089d33         jg         0x89d57
00089d35         mov      cl, byte       ; ;get char at 0x1e
00089d39         mov      bh, 0x1
00089d3b         cmp      cl, 0x2d      ; should be “-”
00089d3e         je         0x89d42
00089d40         mov      bh, al
00089d42         cmp      byte , 0x30                   ; get char at 0x1C
00089d47         je         0x89d2d
00089d49         test       bh, 0x1
00089d4c         jne      0x89d15
00089d4e         lea      edx, dword
00089d52         movsx      ecx, cl
00089d55         jmp      0x89d1e
                                       
00089dc1         mov      dword , eax                           ; argument "src" for method imp___symbol_stub__strncpy
00089dc5         mov      dword , edi                                 ; argument "dst" for method imp___symbol_stub__strncpy
00089dc8         mov      dword , 0x17                            ; copy len = 0x17
00089dd0         call       imp___symbol_stub__strncpy
00089dd5         lea      eax, dword
00089dd8         mov      dword , eax                           ; argument "src" for method imp___symbol_stub__strncpy
00089ddc         lea      eax, dword
00089ddf         mov      dword , eax                                 ; argument "dst" for method imp___symbol_stub__strncpy
00089de2         mov      dword , 0x7                           ; argument "n" for method imp___symbol_stub__strncpy
00089dea         call       imp___symbol_stub__strncpy
00089def         lea      eax, dword
00089df2         mov      dword , eax                           ; argument "src" for method imp___symbol_stub__strncpy
00089df6         lea      eax, dword
00089df9         mov      dword , eax                                 ; argument "dst" for method imp___symbol_stub__strncpy
00089dfc         mov      dword , 0x6                           ; argument "n" for method imp___symbol_stub__strncpy
00089e04         call       imp___symbol_stub__strncpy
00089e09         mov      dword , 0x21

00089e58         cmp      dl, 0x2d                                          ; XREF=__ZN7CHATReg9DecodeKeyEPKcS1_RcRl+847
00089e5b         je         0x89e60
00089e5d         mov      byte , dl
00089e5f         inc      ecx
00089e60         inc      eax                                                 ; XREF=__ZN7CHATReg9DecodeKeyEPKcS1_RcRl+837
00089e61         mov      dl, byte                            ; XREF=__ZN7CHATReg9DecodeKeyEPKcS1_RcRl+832
00089e63         test       dl, dl
00089e65         jne      0x89e58
00089e67         lea      eax, dword
00089e6d         mov      dword , eax
00089e71         mov      dword , ebx
00089e75         lea      eax, dword
00089e7b         mov      dword , eax
00089e7e         call       imp___symbol_stub___ZNSsC1EPKcRKSaIcE               ; std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)
00089e83         mov      eax, dword       ; MD5 Name
00089e86         mov      dword , eax                                 ; argument "var_8" for method __Z9MD5StringPc
00089e89         call       __Z9MD5StringPc                                     ; MD5String(char*)

00089ec2         lea      eax, dword                          ; 注册名MD5
00089ec8         mov      dword , eax
00089ecc         lea      eax, dword       ;与截取出来的假码比较,不同就完了
00089ed2         mov      dword , eax
00089ed5         call       imp___symbol_stub___ZNKSs7compareERKSs            ; std::string::compare(std::string const&) const
00089eda         xor      bl, bl
00089edc         test       eax, eax
00089ede         jne      0x89fa5
大致浏览了一下,注册码0x30位,第一位固定为“1”,2-4位不能大于1000,也即只能是0-9的字符;2-4位转了个二进制字符串备用,根据二进制字符串是1还是0,对假码中取出来的子串做处理。子串的处理函数如下:
__ZL14getTableLettercRcb:      // getTableLetter(char, char&, bool)
0008a0a0         xor      esi, esi
0008a0a2         lea      eax, dword       ; "0123456789abcdef"
0008a0a8         xor      bl, bl
0008a0aa         jmp      0x8a0ae
0008a0ac         inc      eax                                                 ; XREF=__ZL14getTableLettercRcb+36
0008a0ad         inc      esi
0008a0ae         cmp      esi, 0xf                                          ; XREF=__ZL14getTableLettercRcb+22
0008a0b1         jg         0x8a0d1
0008a0b3         movzx      edi, byte
0008a0b6         cmp      edi, ecx
0008a0b8         jne      0x8a0ac
0008a0ba         xor      bl, bl
0008a0bc         cmp      esi, 0xffffffff
0008a0bf         je         0x8a0d1
0008a0c1         test       esi, esi
0008a0c3         jne      0x8a0ca   
0008a0c5         mov      byte , 0x66      ; 0x66 = ‘f’
0008a0c8         jmp      0x8a0cf
0008a0ca         mov      al, byte                    ; XREF=__ZL14getTableLettercRcb+47
0008a0cd         mov      byte , al
0008a0cf         mov      bl, 0x1                                             ; XREF=__ZL14getTableLettercRcb+52
0008a0d1         movzx      eax, bl                                             ; XREF=__ZL14getTableLettercRcb+29, __ZL14getTableLettercRcb+43
这么看的话不是太清晰,跳转太多了,到ida里边看一下:bool CHATReg::DecodeKey(int a1, char *name, char *code)
{
    if (strlen(code) == 0x30 )
    {
      CodeStr = new char;
      memcpy((void *)CodeStr, code, strlen(code));
      *(_BYTE *)code_addr = *code;
      
      if ( *code == 0x31 )
      {
            char char2to4 = {0};
            strncpy(char2to4, code + 1, 3);
            value2to4 = atoi(char2to4);
            if ( (unsigned int)(value2to4 - 1) <= 998 )
            {
                char binStr = {0};
                binStr = Num2bin(value2to4);
                v15 = 0;
                count = 0;
               
                while ( count <= 9 )
                {
                  LOBYTE(char_in_codeStr) = *(_BYTE *)(CodeStr + count + 0x1E);
                  v19 = 1;
                  if ( (_BYTE)char_in_codeStr != '-' )
                        v19 = v15;
                  if ( binStr != '0' )
                  {
                        if ( v19 & 1 )
                        {
                            char_in_codeStr = *(_BYTE *)(CodeStr + count + 0x1F);
                            addr_char_in_codeStr = CodeStr + count + 0x1F;
                        }
                        else
                        {
                            addr_char_in_codeStr = CodeStr + count + 0x1E;
                            char_in_codeStr = (char)char_in_codeStr;
                        }
                        
                        getTableLetter(char_in_codeStr, addr_char_in_codeStr);
                  }
                  ++count;
                  v15 = v19;
                }
            }
            
            subStr = new char;
            strncpy((char *)subStr, (const char *)(CodeStr + 4), 0x17);
            strncpy((char *)(subStr + 23), (const char *)(CodeStr + 31), 7);
            strncpy((char *)(subStr + 30), (const char *)(CodeStr + 42), 6);
            
            MD5String(name);
            if ( !std::string::compare(subStr, nameMd5))
            {
                return true;
            }
      }
    }
    return false;
}
bool __fastcall getTableLetter(int aChar, int addr)
{
    signed int pos = 0; // esi@1
    char refStr[] = "0123456789abcdef";
    bool flag = false;
    while ( pos <= 15 )
    {
      if ( *refStr == aChar )
      {
            if ( pos )
                *(_BYTE *)addr = *(refStr + pos);
            else
                *(_BYTE *)addr = 'f';
            return true;
      }
      ++refStr;
      ++pos;
    }
    return flag;
}以上两个函数经过整理了,是c的伪代码。注意到以下部位:
strncpy((char *)subStr, (const char *)(CodeStr + 4), 0x17);
strncpy((char *)(subStr + 23), (const char *)(CodeStr + 31), 7);
strncpy((char *)(subStr + 30), (const char *)(CodeStr + 42), 6);
一共取了23+7+6 = 36个字符和md5的32个字符比较,怎么可能相等呢?不用仔细看代码,也知道里边舍弃了4个“-”连接符。这是这个算法里边比较好的一点,没有去掉连接符重组字符串,那样太容易知道注册码的格式了。但根据以上分析,可以很容易的推测出注册码的格式为:XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX同样可以知道从第四位/第0x31,0x42位是要和注册名的md5值比较的,因此可以进一步得出注册码为:1XXX**-******-******-******-XXX***-***XXX-******* (*表示md5字符串)从char_in_codeStr = *(_BYTE *)(CodeStr + count + 0x1F);
可知,XXX***-***XXX短的字符串是要被变形的,变形后应等于md5字符串。而变形的依据来自第2-4位的二进制值。最简单的方案就是固定这一值,255是最合适的选择,二进制字符串为“0011111111”,也不需要去考虑该位置是不是1的一系列变化了。因此注册码可以固定为:1255**-******-******-******-XXX***-***XXX-******* (*表示md5字符串,X表示随机字符)而那个变形函数,只不过是从固定字符串"0123456789abcdef”中查找字符,由于位置关系减1而已。因此可以很容易的写出注册机:- (IBAction)Calculate:(id)sender;
{
    srand((unsigned)time(NULL));
    NSString *text = ];
   
    char code = {0};
    for(int i = 0 ;i < 0x30; i++)
    {
      *(code + i) = '-';
    }
   
    char head[] = "1255";
    char ref[] = "0123456789abcdef";
    memcpy(code, head, strlen(head));
    const char *md5 = [ UTF8String];
    memcpy(code + 4, md5, 2);
    memcpy(code + 7, md5 + 2, 6);
    memcpy(code + 14, md5 + 8, 6);
    memcpy(code + 21, md5 + 14, 6);
    memcpy(code + 42, md5 + 26, 6);
    *(code + 28) = ref;
    *(code + 29) = ref;
    *(code + 30) = ref;
    *(code + 38) = ref;
    *(code + 39) = ref;
    *(code + 40) = ref;
   
    char seg = {0};
    memcpy(seg, md5 + 20, 6);
    for (int i = 1; i < strlen(seg);i ++)
    {
      if (*(seg + i) == 'f')
      {
            *(seg + i) = '0';
      }
      else if(*(seg + i) == '9')
      {
            *(seg + i) = 'a';
      }
      else
      {
            *(seg + i) += 1;
      }
    }
   
    memcpy(code + 31, seg, 3);
    memcpy(code + 35, seg + 3, 3);
   
    textField_code.stringValue= [ initWithCString:(const char*)code encoding:NSASCIIStringEncoding];
}
最终实现很简单,但分析过程倒是有点乐趣,尤其分析注册码格式的时候。其实格式分析出来之后,其他的都迎刃而解了。
纪念分析的第6款mac软件,以及第一次用xcode写mac下的对话框程序(objectC的方括号把我整崩溃了)。2016.2.27

wkxq 发表于 2016-2-27 15:32:47

看不明白算法,纯支持

tree_fly 发表于 2016-2-27 16:51:04

Z大现在把Mac玩的不要不要的,Hopper联合IDA,分析算法就是一场非常有意思的智力游戏,体验很佳~
期待更多优秀的文章哦,加油加油~

少马石 发表于 2016-2-27 18:35:09

使用过win版的,很好

wgz001 发表于 2016-2-27 21:11:26

羡慕   进步这么快

zaas 发表于 2016-2-28 18:04:14

wgz001 发表于 2016-2-27 21:11
羡慕   进步这么快

52爱盘上有6.8的

wgz001 发表于 2016-2-29 08:08:31

zaas 发表于 2016-2-28 18:04
52爱盘上有6.8的

我说错了是MAC下的IDA
52上的那个6.8的是win吧
你用MAC下用的是哪个来个地址吧谢谢

zaas 发表于 2016-2-29 11:45:25

wgz001 发表于 2016-2-29 08:08
我说错了是MAC下的IDA
52上的那个6.8的是win吧
你用MAC下用的是哪个来个地址吧谢谢

也是52上6.1的。。。没有F5
我是在windows下6.8分析的。。

wgz001 发表于 2016-2-29 11:54:49

zaas 发表于 2016-2-29 11:45
也是52上6.1的。。。没有F5
我是在windows下6.8分析的。。

好的,谢谢了

wzgangwzgang 发表于 2016-3-7 10:09:50

支持z大,学习了!!!
页: [1] 2
查看完整版本: [Mac]ftosoft PDF Password Remover 算法分析及算法注册机