申请会员ID:DeathMemory【申请通过】
1、申 请 I D:DeathMemory2、个人邮箱:DeathMemory@163.com
3、原创技术文章:2016游戏安全技术竞赛题PC第一题逆向分析详解
最近看到腾讯的 2016游戏安全技术竞赛,咱不是大学生只能以凑热闹的形式参与一下。
遂下载第一题进行分析由于要求写注册机,所以这里不考虑暴破先看一下注册机成功结果:http://img.blog.csdn.net/20160328165324329?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
正式开始分析:从图中可以看出注册成功,失败都以 static text 控件来显示
用OD载入,搜索注册关键字符都没有找到
在 GetWindowTextW GetDlgItemTextWGetDlgItem 下断,在GetDlgItem 处断下,向上返回到程序领域就是注册算法主体了。单步向下调试,先走一遍,最大程度的浏览一遍有用信息,这里可以发现,注册信息是在代码段拼接的http://img.blog.csdn.net/20160328170738850?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
view plain copy
https://code.csdn.net/assets/CODE_ico.pnghttps://code.csdn.net/assets/ico_fork.svg
[*]00CA1F90 .8D47 FA lea eax, dword ptr
[*]00CA1F93 .83F8 0E cmp eax, 0E
[*]00CA1F96 .0F87 4B020000 ja 00CA21E7
此外是用户名长度判断,用户长度在 6-20 之间
接下来是一系列加密算法,用IDA载入并整理下面是用户名的第一次加密OD:
view plain copy
https://code.csdn.net/assets/CODE_ico.pnghttps://code.csdn.net/assets/ico_fork.svg
[*]00CA1FE0 > /8BC1 mov eax, ecx
[*]00CA1FE2 . |99 cdq
[*]00CA1FE3 . |F7FF idiv edi
[*]00CA1FE5 . |8DB40C 88000000 lea esi, dword ptr
[*]00CA1FEC . |41 inc ecx
[*]00CA1FED . |0FBE8414 9C000000 movsx eax, byte ptr
[*]00CA1FF5 . |8D142E lea edx, dword ptr
[*]00CA1FF8 . |0FAFC2 imul eax, edx
[*]00CA1FFB . |0FAFC7 imul eax, edi
[*]00CA1FFE . |0106 add dword ptr , eax
[*]00CA2000 . |83F9 10 cmp ecx, 10
[*]00CA2003 .^\7C DB jl short 00CA1FE0
IDA:
view plain copy
https://code.csdn.net/assets/CODE_ico.pnghttps://code.csdn.net/assets/ico_fork.svg
[*]do // 加密用户名 ↓
[*]{
[*]tmp_i = i % namelen;
[*]ptrTmp = &name_encrypt;
[*]*(_DWORD *)ptrTmp += namelen * (_DWORD)&ptrTmp * username;
[*]}
[*]while ( i < 16 );
接下来的两个Call是跟密码加密相关的,回头再看,先把用户名的加密跑完
到第二次用户名加密的地方OD:
view plain copy
https://code.csdn.net/assets/CODE_ico.pnghttps://code.csdn.net/assets/ico_fork.svg
[*].text:00402102 mov ecx, dword ptr
[*].text:00402109 mov eax, 66666667h
[*].text:0040210E imul ecx
[*].text:00402110 sar edx, 2
[*].text:00402113 mov ecx, edx
[*].text:00402115 shr ecx, 1Fh
[*].text:00402118 add ecx, edx
[*].text:0040211A mov edx,
[*].text:0040211E sub edx, edi
[*].text:00402120 mov , ecx ; 计算结果
[*].text:00402124 cmp esi, edx
[*].text:00402126 jb short loc_402131
[*].text:00402128 call __invalid_parameter_noinfo
[*].text:0040212D mov edi,
[*].text:00402131
[*].text:00402131 loc_402131: ; CODE XREF: sub_401E60+2C6j
[*].text:00402131 mov eax,
[*].text:00402134 mov , eax
[*].text:00402138 add esi, 4
[*].text:0040213B cmp esi, 14h
[*].text:0040213E jl short loc_402102
IDA:
view plain copy
https://code.csdn.net/assets/CODE_ico.pnghttps://code.csdn.net/assets/ico_fork.svg
[*]do
[*]{
[*]v12 = *(_DWORD *)&name_encrypt / 10;
[*]len1 = val_1 - (_DWORD)val_0_1;
[*]name_encrypt_2 = v12; // 用户名第二次加密
[*]if ( index >= len1 )
[*]{
[*] _invalid_parameter_noinfo(v12);
[*] val_0_1 = val_0;
[*]}
[*]pass_encrypt = *(_DWORD *)((char *)val_0_1 + index);// 密码解密编码后的密码
[*]index += 4;
[*]}
[*]while ( (signed int)index < 20 );
再往下就是加密后的用户名和密码进行验证:
view plain copy
https://code.csdn.net/assets/CODE_ico.pnghttps://code.csdn.net/assets/ico_fork.svg
[*]if ( pass_encrypt + name_encrypt_2 != pass_encrypt
[*] || pass_encrypt + name_encrypt_2 != 2 * pass_encrypt
[*] || pass_encrypt + name_encrypt_2 != pass_encrypt
[*] || pass_encrypt + name_encrypt_2 != 2 * pass_encrypt
[*] || name_encrypt_2 + pass_encrypt != 3 * name_encrypt_2 )// 验证
以上验证如果有一条为真,则跳转到失败。
分析到这里,我们还没有分析密码的加密,这个是关键点,我们先设为 X则就有了以下这条假设的公式: 用户名 + x = 验证所以在已知用户名和验证关系后,我们可以推导出加密后 X 的值
view plain copy
https://code.csdn.net/assets/CODE_ico.pnghttps://code.csdn.net/assets/ico_fork.svg
[*]void EncryptUsername(char* username) {
[*] BYTE name_encrypt = { NULL };
[*] int namelen = strlen((char*)username);
[*] int i = 0;
[*] do// 第一次加密
[*] {
[*] int tmp_i = i % namelen;
[*] BYTE* tmp_ptr = &name_encrypt;
[*] *(DWORD *)tmp_ptr += namelen * (DWORD)&tmp_ptr * username;
[*] } while (i < 16);
[*] i = 0;
[*] BYTE name_encrypt_2 = { NULL };
[*] do// 第二次加密
[*] {
[*] int tmp = *(int*)&name_encrypt / 10;
[*] *(int*)&name_encrypt_2 = tmp;
[*] i += 4;
[*] } while (i < 20);
[*]
[*] /************************************************************************/
[*] /* pass_encrypt + name_encrypt_2 != pass_encrypt
[*] || pass_encrypt + name_encrypt_2 != 2 * pass_encrypt
[*] name_encrypt_2 + name_encrypt_2 = pass_encrypt
[*] || pass_encrypt + name_encrypt_2 != pass_encrypt
[*] || pass_encrypt + name_encrypt_2 != 2 * pass_encrypt
[*] name_encrypt_2 + name_encrypt_2 = pass_encrypt
[*] || name_encrypt_2 + pass_encrypt != 3 * name_encrypt_2 )// 验证
[*] pass_encrypt = 3 * name_encrypt_2 - name_encrypt_2
[*] */
[*] /************************************************************************/
[*]
[*] DWORD * ptrdwNameEncrypt = (DWORD*)name_encrypt_2;
[*] DWORD ptrdwPassEncrypt = { NULL };// 注意这里是 DWORD
[*]
[*] ptrdwPassEncrypt = ptrdwNameEncrypt + ptrdwNameEncrypt;
[*] ptrdwPassEncrypt = ptrdwNameEncrypt + ptrdwNameEncrypt;
[*] ptrdwPassEncrypt = 3 * ptrdwNameEncrypt - ptrdwNameEncrypt;
[*] ptrdwPassEncrypt = ptrdwPassEncrypt + ptrdwNameEncrypt;
[*] ptrdwPassEncrypt = 2 * ptrdwPassEncrypt - ptrdwNameEncrypt;
[*] memcpy_s(username, 20, (char*)ptrdwPassEncrypt, 20);
[*]}
接下来重点分析密码加密的部分
发现有两处加密第一处:
view plain copy
https://code.csdn.net/assets/CODE_ico.pnghttps://code.csdn.net/assets/ico_fork.svg
[*].text:00401A72 mov al,
[*].text:00401A76 lea ebx,
[*].text:00401A7A mov byte ptr , al
[*].text:00401A7E call selectFromBaseCode
[*].text:00401A83 mov , al
[*].text:00401A87 inc edi
[*].text:00401A88 cmp edi, 4
[*].text:00401A8B jl short loc_401A72
这里将密码以四字节为单位,逐字节传入 selectFromBaseCode 函数中返回的密码基串所在位置的索引简单讲一下 selectFromBaseCode(自定义名称) 函数机制:参数:BYTE (例如:=> C)从密码基串中计算出 C 的位置索引密码基串为: view plain copy
https://code.csdn.net/assets/CODE_ico.pnghttps://code.csdn.net/assets/ico_fork.svg
[*]char* BASE_CODE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@%";
返回:位置索引(例如:=> 2 错误返回 -1)
第二处(关键算法):OD:
view plain copy
https://code.csdn.net/assets/CODE_ico.pnghttps://code.csdn.net/assets/ico_fork.svg
[*].text:00401A91 mov al, cl ; val
[*].text:00401A93 add al, al ; val *= 2
[*].text:00401A95 mov dl, ch ; val
[*].text:00401A97 shr dl, 4 ; val >>= 4
[*].text:00401A9A and dl, 3 ; val &= 3
[*].text:00401A9D add al, al ; val *= 2
[*].text:00401A9F add dl, al ; val += val
[*].text:00401AA1 mov al, ; val
[*].text:00401AA5 mov , dl ; key0 = val
[*].text:00401AA9 mov dl, al ; val
[*].text:00401AAB shr dl, 2 ; val >>= 2
[*].text:00401AAE mov cl, ch ; val
[*].text:00401AB0 shl al, 6 ; y = val << 6
[*].text:00401AB3 add al, ; y += val
[*].text:00401AB7 and dl, 0Fh ; val &= 0xF
[*].text:00401ABA shl cl, 4 ; val <<= 4
[*].text:00401ABD xor dl, cl ; val ^= val
[*].text:00401ABF mov , dl ; key1 = val
[*].text:00401AC3 mov , al ; key2 = y
HAND: (这里IDA给出的整理不容易观察我们自己手动整理)
view plain copy
https://code.csdn.net/assets/CODE_ico.pnghttps://code.csdn.net/assets/ico_fork.svg
[*]key = (val >> 4) & 3 + (val << 2)
[*]key = (val >> 2) & 0xF ^ (val << 4)
[*]key = val << 6 + val
到这里核心算法就完成了,你会发现下面还有一部分加密,经过分析后会知道下面的加密跟这里的相同,只是对密码后3位进行了一下特殊处理我们再假设一个公式:x(加密后的密码已知) + 逆算 = 注册码接下来就是重点了,如何将算法逆回去就可以得出注册码了
从上面看是将 val => key, 逆算就是要将 key => val正算时是将4字节转成3字节,而逆算是将3字节转成4字节,这相当于有4个未知数,而只有3条关系式,乍看上去不好逆算,这里也是我花费时间比较长的地方,解决方法主要是得开窍哈哈,下面贴上主要分析过程:
view plain copy
https://code.csdn.net/assets/CODE_ico.pnghttps://code.csdn.net/assets/ico_fork.svg
[*]key0 = (val >> 4) & 3 + (val << 2)
[*]/**
[*]因为(val >> 4) & 3=> 0000 0011b
[*] + (val << 2) => 1111 1100b
[*] = key0
[*]所以
[*] val = key0 >> 2
[*] val = (key0 & 3) << 4
[*]**/
[*]key1 = (val >> 2) & 0xF ^ (val << 4)
[*]/**
[*]同理(val >> 2) & 0xF=> 0000 1111b
[*] ^ (val << 4) => 1111 0000b
[*] = key1
[*]所以
[*] val = key1 >> 4
[*] val = (key1 & 0xF) << 2
[*]**/
[*]key2 = val << 6 + val
[*]/**
[*]因为val << 6 => 1100 0000b
[*] + val => val < 0x40
[*] = key3
[*]所以
[*] val = key3 >> 6
[*] val = key3 & 0x3F
[*]**/
[*]/********************************************************
[*]因为 val = (key0 & 3) << 4
[*]和 val = key1 >> 4
[*]均为真
[*]所以是或关系
[*]则 val = (key0 & 3) << 4 | key1 >> 4
[*]同理 val = (key1 & 0xF) << 2 | key3 >> 6
[*]**/
之后再以公式的形式展现一下现在的进度:验证用户名串(已知)+ 逆算(已知)= 注册码算法函数:
view plain copy
https://code.csdn.net/assets/CODE_ico.pnghttps://code.csdn.net/assets/ico_fork.svg
[*]void calcSN(char* str) {
[*] BYTE* curptr = (BYTE*)str;
[*] BYTE idxArr = { NULL };
[*] char sn = { NULL };
[*] for (int i = 0, j = 0; i < 20; i += 3, j += 4, curptr += 3){
[*] idxArr = curptr >> 2;
[*] idxArr = (curptr & 3) << 4 | curptr >> 4;
[*] idxArr = (curptr & 0xF) << 2 | curptr >> 6;
[*] idxArr = curptr & 0x3F;
[*]
[*] for (int idx = 0; idx < ((i == 18) ? 3 : 4); ++idx){// 最后3位稍做字符串上的处理
[*] sn = BASE_CODE];
[*] }
[*] }
[*] strcpy_s(str, MAX_PATH, sn);
[*]}
完整调用:
view plain copy
https://code.csdn.net/assets/CODE_ico.pnghttps://code.csdn.net/assets/ico_fork.svg
[*]int _tmain(int argc, _TCHAR* argv[]) {
[*] char username = { NULL };
[*] printf("input username: ");
[*] scanf_s("%s", username, MAX_PATH);
[*] int namelen = strlen(username);
[*] if (namelen < 6 || namelen > 20){
[*] printf("username length must be between 6 and 20\n");
[*] }
[*] else {
[*] EncryptUsername(username);
[*] //EncryptPassword(NULL);
[*] calcSN(username);
[*] printf("sn: %s\n", username);
[*] }
[*] system("pause");
[*] return 0;
[*]}
到这里就结束了,在逆向过程中验证逆算的正确性可以结合OD调试结果,或直接修改内存来验证一下。
是原创吗?看起来是从其他地方复制来的,原文出处哪? 有点像,因为还有标志性的东西没有去除 我眼睛花了. 原版可能是这个http://blog.csdn.net/deathmemory/article/details/50999149 大兄弟下次记得去水印 我怎么看不懂啊 ppszxc 发表于 2016-5-19 15:14
原版可能是这个http://blog.csdn.net/deathmemory/article/details/50999149
应该是这个,如果是作者本人来申请,麻烦在博客中发一个内容,确认本帖是本人申请。 人呢???
页:
[1]
2