[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
看不明白算法,纯支持 Z大现在把Mac玩的不要不要的,Hopper联合IDA,分析算法就是一场非常有意思的智力游戏,体验很佳~
期待更多优秀的文章哦,加油加油~ 使用过win版的,很好 羡慕 进步这么快 wgz001 发表于 2016-2-27 21:11
羡慕 进步这么快
52爱盘上有6.8的
zaas 发表于 2016-2-28 18:04
52爱盘上有6.8的
我说错了是MAC下的IDA
52上的那个6.8的是win吧
你用MAC下用的是哪个来个地址吧谢谢
wgz001 发表于 2016-2-29 08:08
我说错了是MAC下的IDA
52上的那个6.8的是win吧
你用MAC下用的是哪个来个地址吧谢谢
也是52上6.1的。。。没有F5
我是在windows下6.8分析的。。
zaas 发表于 2016-2-29 11:45
也是52上6.1的。。。没有F5
我是在windows下6.8分析的。。
好的,谢谢了
支持z大,学习了!!!
页:
[1]
2