本帖最后由 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
|