飘云阁

 找回密码
 加入我们

QQ登录

只需一步,快速开始

查看: 9077|回复: 16

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

[复制链接]
  • TA的每日心情
    无聊
    2024-1-15 22:57
  • 签到天数: 3 天

    [LV.2]偶尔看看I

    发表于 2016-2-27 15:13:42 | 显示全部楼层 |阅读模式
    本帖最后由 zaas 于 2016-2-27 15:19 编辑

    【破文标题】[Mac]ftosoft PDF Password Remover 算法分析及算法注册机
    【破文作者】zaas[PYG]
    破解工具】hopper disassembler + ida
    【破解平台】Mac OS X

    好几年没写过算法分析的文章了。今天一来为了记录一下开始进入mac的世界,二来因为mac的分析文章太少,抛块砖。
    mac也没啥好怕的,除了工具不甚趁手。。。。

    很容易即可找到关键的验证函数,这一点甚至比windows下更容易。很多函数名都是作为参数传递给Msg_Send的。。。

    [Asm] 纯文本查看 复制代码
    __ZN7CHATReg9DecodeKeyEPKcS1_RcRl:        // CHATReg::DecodeKey(char const*, char const*, char&, long&)
    00089b3f         mov        edi, dword [ss:ebp+arg_8]        ; fake code
    00089b42         mov        dword [ss:esp], 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 [ss:esp], edi                                 ; argument "s" for method imp___symbol_stub__strlen
    00089bc2         call       imp___symbol_stub__strlen
    00089bc7         mov        dword [ss:esp+0x8], eax                             ; argument "n" for method imp___symbol_stub__memcpy
    00089bcb         mov        dword [ss:esp+0x4], edi                             ; argument "src" for method imp___symbol_stub__memcpy
    00089bcf         mov        dword [ss:esp], esi                                 ; argument "dst" for method imp___symbol_stub__memcpy
    00089bd2         call       imp___symbol_stub__memcpy
    00089bd7         mov        al, byte [ds:edi]        ;取字符1
    00089bd9         mov        byte [ds:ebx], al
    00089bdb         xor        bl, bl        ;first char = 0x31
    00089bdd         cmp        al, 0x31
    00089bdf         jne        0x8a01b        不是“1”,死
    
    00089be5         mov        dword [ss:ebp+var_64], 0x0
    00089bec         inc        edi
    00089bed         mov        dword [ss:esp+0x4], edi                             ; argument "src" for method imp___symbol_stub__strncpy
    00089bf1         lea        edi, dword [ss:ebp+var_64]
    00089bf4         mov        dword [ss:esp], edi                                 ; argument "dst" for method imp___symbol_stub__strncpy
    00089bf7         mov        dword [ss:esp+0x8], 0x3                             ;从第二位取三位
    00089bff         call       imp___symbol_stub__strncpy
    00089c04         mov        dword [ss:esp], 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 [ss:ebp+var_130]        ; 转为2进制字符串,不足10位补够10位
    
    00089cf2         mov        edx, 0xffffffff                                     ; XREF=__ZN7CHATReg9DecodeKeyEPKcS1_RcRl+468
    00089cf7         lock xadd  dword [ds:ecx+0xfffffffc], edx
    00089cfc         test       edx, edx
    00089cfe         jg         0x89cec
    
    00089d00         lea        ecx, dword [ss:ebp+var_60]
    00089d03         mov        dword [ss:esp+0x4], ecx
    00089d07         mov        dword [ss:esp], 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 [ds:esi+edi+0x1f]                         ; XREF=__ZN7CHATReg9DecodeKeyEPKcS1_RcRl+566
    00089d1a         lea        edx, dword [ds:esi+edi+0x1f]
    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 [ds:esi+edi+0x1e]        ; ;get char at 0x1e
    00089d39         mov        bh, 0x1
    00089d3b         cmp        cl, 0x2d        ; should be “-”
    00089d3e         je         0x89d42
    00089d40         mov        bh, al
    00089d42         cmp        byte [ss:ebp+edi+var_1C], 0x30                   ; get char at 0x1C 
    00089d47         je         0x89d2d
    00089d49         test       bh, 0x1
    00089d4c         jne        0x89d15
    00089d4e         lea        edx, dword [ds:esi+edi+0x1e]
    00089d52         movsx      ecx, cl
    00089d55         jmp        0x89d1e
                                           
    00089dc1         mov        dword [ss:esp+0x4], eax                             ; argument "src" for method imp___symbol_stub__strncpy
    00089dc5         mov        dword [ss:esp], edi                                 ; argument "dst" for method imp___symbol_stub__strncpy
    00089dc8         mov        dword [ss:esp+0x8], 0x17                            ; copy len = 0x17
    00089dd0         call       imp___symbol_stub__strncpy
    00089dd5         lea        eax, dword [ds:esi+0x1f]
    00089dd8         mov        dword [ss:esp+0x4], eax                             ; argument "src" for method imp___symbol_stub__strncpy
    00089ddc         lea        eax, dword [ds:edi+0x17]
    00089ddf         mov        dword [ss:esp], eax                                 ; argument "dst" for method imp___symbol_stub__strncpy
    00089de2         mov        dword [ss:esp+0x8], 0x7                             ; argument "n" for method imp___symbol_stub__strncpy
    00089dea         call       imp___symbol_stub__strncpy
    00089def         lea        eax, dword [ds:esi+0x2a]
    00089df2         mov        dword [ss:esp+0x4], eax                             ; argument "src" for method imp___symbol_stub__strncpy
    00089df6         lea        eax, dword [ds:edi+0x1e]
    00089df9         mov        dword [ss:esp], eax                                 ; argument "dst" for method imp___symbol_stub__strncpy
    00089dfc         mov        dword [ss:esp+0x8], 0x6                             ; argument "n" for method imp___symbol_stub__strncpy
    00089e04         call       imp___symbol_stub__strncpy
    00089e09         mov        dword [ss:esp], 0x21
    
    00089e58         cmp        dl, 0x2d                                            ; XREF=__ZN7CHATReg9DecodeKeyEPKcS1_RcRl+847
    00089e5b         je         0x89e60
    00089e5d         mov        byte [ds:ecx], dl
    00089e5f         inc        ecx
    00089e60         inc        eax                                                 ; XREF=__ZN7CHATReg9DecodeKeyEPKcS1_RcRl+837
    00089e61         mov        dl, byte [ds:eax-0x89b27]                           ; XREF=__ZN7CHATReg9DecodeKeyEPKcS1_RcRl+832
    00089e63         test       dl, dl
    00089e65         jne        0x89e58
    00089e67         lea        eax, dword [ss:ebp+var_140]
    00089e6d         mov        dword [ss:esp+0x8], eax
    00089e71         mov        dword [ss:esp+0x4], ebx
    00089e75         lea        eax, dword [ss:ebp+var_138]
    00089e7b         mov        dword [ss:esp], 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 [ss:ebp+arg_4]        ; MD5 Name
    00089e86         mov        dword [ss:esp], eax                                 ; argument "var_8" for method __Z9MD5StringPc
    00089e89         call       __Z9MD5StringPc                                     ; MD5String(char*)
    
    00089ec2         lea        eax, dword [ss:ebp+var_148]                         ; 注册名MD5
    00089ec8         mov        dword [ss:esp+0x4], eax
    00089ecc         lea        eax, dword [ss:ebp+var_138]        ;与截取出来的假码比较,不同就完了
    00089ed2         mov        dword [ss:esp], 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,对假码中取出来的子串做处理。
    子串的处理函数如下:

    [Asm] 纯文本查看 复制代码
    __ZL14getTableLettercRcb:        // getTableLetter(char, char&, bool)
    0008a0a0         xor        esi, esi
    0008a0a2         lea        eax, dword [ds:eax-0x8a09f+__ZL12md5CodeTable]      ; "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 [ds:eax-0x8a09f]
    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 [ds:edx], 0x66        ; 0x66 = ‘f’
    0008a0c8         jmp        0x8a0cf
    0008a0ca         mov        al, byte [ds:eax-0x8a09f+0x8a09e]                   ; XREF=__ZL14getTableLettercRcb+47
    0008a0cd         mov        byte [ds:edx], al
    0008a0cf         mov        bl, 0x1                                             ; XREF=__ZL14getTableLettercRcb+52
    0008a0d1         movzx      eax, bl                                             ; XREF=__ZL14getTableLettercRcb+29, __ZL14getTableLettercRcb+43

    这么看的话不是太清晰,跳转太多了,到ida里边看一下:
    [C++] 纯文本查看 复制代码
    bool CHATReg::DecodeKey(int a1, char *name, char *code)
    {
        if (strlen(code) == 0x30 )
        {
            CodeStr = new char[50];
            memcpy((void *)CodeStr, code, strlen(code));
            *(_BYTE *)code_addr = *code;
            
            if ( *code == 0x31 )
            {
                char char2to4[4] = {0};
                strncpy(char2to4, code + 1, 3);
                value2to4 = atoi(char2to4);
                if ( (unsigned int)(value2to4 - 1) <= 998 )
                {
                    char binStr[10] = {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[count] != '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[50];
                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;
    }

    [C++] 纯文本查看 复制代码
    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的伪代码。注意到以下部位:

    [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字符串)
    [C++] 纯文本查看 复制代码
    char_in_codeStr = *(_BYTE *)(CodeStr + count + 0x1F);
    [align=left]
    可知,XXX***-***XXX短的字符串是要被变形的,变形后应等于md5字符串。而变形的依据来自第2-4位的二进制值。
    最简单的方案就是固定这一值,255是最合适的选择,二进制字符串为“0011111111”,也不需要去考虑该位置是不是1的一系列变化了。
    因此注册码可以固定为:
    1255**-******-******-******-XXX***-***XXX-******* (*表示md5字符串,X表示随机字符)
    而那个变形函数,只不过是从固定字符串"0123456789abcdef”中查找字符,由于位置关系减1而已。
    因此可以很容易的写出注册机:
    [Objective-C] 纯文本查看 复制代码
    - (IBAction)Calculate:(id)sender;
    {
        srand((unsigned)time(NULL));
        NSString *text = [textField_name.stringValue  stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet ]];
        
        char code[0x31] = {0};
        for(int i = 0 ;i < 0x30; i++)
        {
            *(code + i) = '-';
        }
        
        char head[] = "1255";
        char ref[] = "0123456789abcdef";
        memcpy(code, head, strlen(head));
        const char *md5 = [[text md5HexDigest] 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[rand()%16];
        *(code + 29) = ref[rand()%16];
        *(code + 30) = ref[rand()%16];
        *(code + 38) = ref[rand()%16];
        *(code + 39) = ref[rand()%16];
        *(code + 40) = ref[rand()%16];
        
        char seg[7] = {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  = [[NSString alloc] initWithCString:(const char*)code encoding:NSASCIIStringEncoding];
    }

    最终实现很简单,但分析过程倒是有点乐趣,尤其分析注册码格式的时候。
    其实格式分析出来之后,其他的都迎刃而解了。

    纪念分析的第6款mac软件,以及第一次用xcode写mac下的对话框程序(objectC的方括号把我整崩溃了)。
    2016.2.27

    评分

    参与人数 5威望 +64 飘云币 +64 收起 理由
    creantan + 20 + 20 PYG有你更精彩!
    wgz001 + 8 + 8 老Z来个IDA的下载地址用用吧,感谢
    gagmeng + 8 + 8 膜拜Z神
    0xcb + 8 + 8 赞一个!中括号习惯就好了,
    tree_fly + 20 + 20 文章写的很好!OC之外Swift语言很赞!

    查看全部评分

    PYG19周年生日快乐!
  • TA的每日心情
    开心
    昨天 20:48
  • 签到天数: 1976 天

    [LV.Master]伴坛终老

    发表于 2016-2-27 15:32:47 | 显示全部楼层
    看不明白算法,纯支持
    PYG19周年生日快乐!
  • TA的每日心情
    开心
    2019-3-17 22:44
  • 签到天数: 132 天

    [LV.7]常住居民III

    发表于 2016-2-27 16:51:04 | 显示全部楼层
    Z大现在把Mac玩的不要不要的,Hopper联合IDA,分析算法就是一场非常有意思的智力游戏,体验很佳~
    期待更多优秀的文章哦,加油加油~
    PYG19周年生日快乐!
  • TA的每日心情
    开心
    4 天前
  • 签到天数: 638 天

    [LV.9]以坛为家II

    发表于 2016-2-27 18:35:09 | 显示全部楼层
    使用过win版的,很好
    PYG19周年生日快乐!
  • TA的每日心情
    开心
    2019-2-26 11:14
  • 签到天数: 459 天

    [LV.9]以坛为家II

    发表于 2016-2-27 21:11:26 | 显示全部楼层
    羡慕   进步这么快

    点评

    52爱盘上有6.8的  详情 回复 发表于 2016-2-28 18:04
    PYG19周年生日快乐!
  • TA的每日心情
    无聊
    2024-1-15 22:57
  • 签到天数: 3 天

    [LV.2]偶尔看看I

     楼主| 发表于 2016-2-28 18:04:14 | 显示全部楼层
    wgz001 发表于 2016-2-27 21:11
    羡慕   进步这么快

    52爱盘上有6.8的

    点评

    我说错了 是MAC下的IDA 52上的那个6.8的是win吧 你用MAC下用的是哪个 来个地址吧 谢谢  详情 回复 发表于 2016-2-29 08:08
    PYG19周年生日快乐!
  • TA的每日心情
    开心
    2019-2-26 11:14
  • 签到天数: 459 天

    [LV.9]以坛为家II

    发表于 2016-2-29 08:08:31 | 显示全部楼层
    zaas 发表于 2016-2-28 18:04
    52爱盘上有6.8的

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

    点评

    也是52上6.1的。。。没有F5 我是在windows下6.8分析的。。  详情 回复 发表于 2016-2-29 11:45
    PYG19周年生日快乐!
  • TA的每日心情
    无聊
    2024-1-15 22:57
  • 签到天数: 3 天

    [LV.2]偶尔看看I

     楼主| 发表于 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分析的。。

    点评

    好的,谢谢了  详情 回复 发表于 2016-2-29 11:54
    PYG19周年生日快乐!
  • TA的每日心情
    开心
    2019-2-26 11:14
  • 签到天数: 459 天

    [LV.9]以坛为家II

    发表于 2016-2-29 11:54:49 | 显示全部楼层
    zaas 发表于 2016-2-29 11:45
    也是52上6.1的。。。没有F5
    我是在windows下6.8分析的。。

    好的,谢谢了
    PYG19周年生日快乐!
  • TA的每日心情
    开心
    2019-9-11 08:24
  • 签到天数: 614 天

    [LV.9]以坛为家II

    发表于 2016-3-7 10:09:50 | 显示全部楼层
    支持z大,学习了!!!
    PYG19周年生日快乐!
    您需要登录后才可以回帖 登录 | 加入我们

    本版积分规则

    快速回复 返回顶部 返回列表