大半年忙东忙西,很少有时间弄技术上的东西了,前几天和一个学长闲聊,他说之前做了一个小东西,就是分析010editor的注册算法,然后就找了一个"最新"的来试试。弄了几天基本算完成了,后面一直偷懒没有写文档,这会儿慢慢补充好吧。 代码: .text:00550267 cmp edi, 0DBh ; 同时,要来到这里进行判断,有三条路线 .text:0055026D jnz short loc_5502A7 ; 只有这条路线是正确的,此时edi必须要等于0xDB .text:0055026F push 0FFFFFFFFh .text:00550271 push offset aPasswordAccept ; "Password accepted. Thank you for purcha"... .text:00550276 call ebp ; QString::fromAscii_helper(char const *,int) ; QString::fromAscii_helper(char const *,int) .text:00550278 mov [esp+40h+var_18], eax 代码: loc_550267: ; 同时,要来到这里进行判断,有三条路线 cmp edi, 0DBh jnz short loc_5502A7 ; 只有这条路线是正确的,此时edi必须要等于0xDB 总原则: Edi必须等于0xDB 代码: push 3C70h push 5 call sub_403963 ; 有三种返回情况:0xE7,0x4E,0x2D mov ecx, dword_7D6E24 ; 关键算法点之一 push 3C70h push 5 mov ebx, eax call sub_40897C ; 这个函数会影响到edi的值(返回值赋值给edi) mov ecx, dword_7D6E24 ; 尽管后面有三条路线,但是不管怎么说edi都必须为0xDB xor edx, edx ; '关键算法点之一'如果返回的是0x2D,那么edi就为0xDB mov edi, eax ; '关键算法点之一’如果返回的是0xE7,那么edi为0x177 mov eax, [ecx+34h] test eax, eax setz dl mov [esp+38h+var_10], edx cmp ebx, 0E7h jz loc_550267 ; 如果这一条路想走通,必须满足:ebx为0xE7,edi为0xDB 如果jz跳转成立,则跳转到了图2处,则此时满足ebx为0x2d,edi为0xDB既可实现注册。 下面,分析sub_616050函数 代码: .text:00616081 jz loc_6163DF ; 类中的标志位比较 .text:00616087 mov ecx, [esi+8] .text:0061608A cmp [ecx+8], ebp .text:0061608D jz loc_6163DF ; 类中的标志位比较 .text:00616093 push edi .text:00616094 lea edx, [esp+30h+var_18] ; edx的值就是存放处理之后的密码串的地址 .text:00616098 push edx .text:00616099 mov ecx, esi .text:0061609B call sub_40889B ; 这个函数里面对输入的密码进行了操作 .text:006160A0 mov edi, offset off_7D58B8 .text:006160A5 .text:006160A5 loc_6160A5: ; CODE XREF: sub_616050+71j .text:006160A5 mov eax, [edi] .text:006160A7 push eax .text:006160A8 mov ecx, ebx .text:006160AA call ds:??8QString@@QBE_NPBD@Z ; QString::operator==(char const *) .text:006160B0 test al, al .text:006160B2 jnz loc_616279 .text:006160B8 add edi, 4 .text:006160BB cmp edi, offset unk_7D58BC .text:006160C1 jl short loc_6160A5 现在,我们假设到这里的时候,我们输入的密码串转换成了一个byte数组,称其为数组a。 比如,输入的用户名:loongzyd,密码:0fb6bcace7870c1fe696 数组a: 注意到这个函数有三种返回值:0xE7,0x4E,0x2D,而依据总原则:”edi必须等于0xDB”,加上后面的逆向倒推分析可知:该函数的返回值必须为0x2D。 那现在往下分析,怎么样才能让程序流程执行下去使得函数返回0x2D. 代码: .text:006160C3 mov al, [esp+30h+var_15] ; al = a[3] .text:006160C7 mov bl, byte ptr [esp+30h+var_14+1] ; bl = a[5] .text:006160CB cmp al, 9Ch ; 判断a[3]是否为0x9c .text:006160CD jnz short loc_616149 ; 判断7,8位是不是F,C 代码: .text:00616149 cmp al, 0FCh ; 判断7,8位是不是F,C .text:0061614B jnz short loc_616165 代码: .text:00616165 cmp al, 0ACh .text:00616167 jnz loc_616279 ; 判断7,8位是不是A,C 结论:a[3]必须为0x9c,0xfc,,0xac这三个值之一,否则就会失败。 当a[3]为0x9c时: 代码: .text:006160CF mov dl, byte ptr [esp+30h+var_14+3] ; 第8位 a[7] .text:006160D3 xor dl, [esp+30h+var_17] ; dl = a[7] ^ a[1] (第二位) .text:006160D7 mov cl, byte ptr [esp+30h+var_14+2] ; 第7位 a[6] .text:006160DB xor cl, [esp+30h+var_18] ; cl = a[6] ^ a[0] (第一位) .text:006160DF movzx ax, dl ; ax = a[7] ^ a[1] .text:006160E3 mov byte ptr [esp+30h+var_1C], cl ; [esp+14] = a[6] ^ a[0] .text:006160E7 mov ecx, 100h .text:006160EC imul ax, cx ; ax = (a[7] ^ a[1]) << 8 .text:006160F0 mov dl, bl ; dl = a[5] (第6位) .text:006160F2 xor dl, [esp+30h+var_16] ; dl = a[5] ^ a[2] (第三位) .text:006160F6 movzx cx, dl ; cx = a[5] ^ a[2] .text:006160FA mov edx, [esp+30h+var_1C] .text:006160FE add ax, cx ; ax = (a[7] ^ a[1]) << 8 + a[5] ^a[2] .text:00616101 push edx ; edx的值来源于[esp+14],等于a[6] ^ a[0] .text:00616102 movzx edi, ax ; edi = (a[7] ^ a[1]) << 8 + a[5] ^a[2] .text:00616105 call sub_406D6B ; eax = ((arg0 ^ 0x18) + 0x3D) ^ 0xA7 .text:0061610A movzx eax, al ; eax = (([esp+14] ^ 0x18) + 0x3D) ^ 0xA7 .text:0061610D push edi .text:0061610E mov [esi+1Ch], eax ; [esi+1c] = (([esp+14] ^ 0x18) + 0x3D) ^ 0xA7 .text:00616111 call sub_4077D4 ; eax = (((arg0 ^ 0x7892) + 0x4d30) ^ 0x3421) / 0xB .text:00616116 mov ecx, [esi+1Ch] ; 上一步中,除以0xB必须要整出,否则eax = 0,失败 .text:00616119 movzx eax, ax .text:0061611C add esp, 8 .text:0061611F mov [esi+20h], eax ; 假设得到的eax恰好为0x3E8 .text:00616122 test ecx, ecx ; 坚决不能转 [esi+0x1C]的值不能为0 .text:00616124 jz loc_616279 1.((a[6] ^ a[0] ^ 0x18) + 0x3D ) ^ 0xA7 != 0 (令商为dev1) 2.arg0 = (a[7] ^ a[1]) << 8 + a[5] ^ a[2] ((arg0 ^ 0x7892 + 0x4d30 ) ^ 0x3421) &&0x0ffff必须整除0xB,并且商不能大于0x3E8 (令商为dev2) edi的值影响后面的计算流程 代码: .text:006162BA loc_6162BA: ; CODE XREF: sub_616050+24Cj .text:006162BA mov cl, [esp+30h+var_15] ; 取a[3],判断标志位 .text:006162BE cmp cl, 9Ch .text:006162C1 jnz short loc_6162D2 ; 判断a[3]是否为0x9c 代码: .text:006162C3 mov eax, [esp+30h+arg_0] ; 参数固定为5 .text:006162C7 or edx, 0FFFFFFFFh .text:006162CA cmp eax, [esi+1Ch] ; (pass[6] ^ pass[0] ^ 0x18 + 0x3D ) ^ 0xA7的值必须要大于等于5 .text:006162CD jmp loc_616352 ((a[6] ^ a[0] ^ 0x18) + 0x3D ) ^ 0xA7 >= 5 代码: .text:00616220 mov eax, [ecx+0Ch] ; 此时eax的值,为用户名字符串的地址 .text:00616223 mov edx, [esi+20h] ; 上面除以0xB后的商 .text:00616226 xor ecx, ecx .text:00616228 cmp [esp+30h+var_15], 0FCh ; 根据a[3]是否为0xFC,来决ecx为0,还是1 .text:0061622D push edx .text:0061622E setnz cl ; a[3]等于0xfc,则ecx=0,否则ecx=1 .text:00616231 push edi .text:00616232 push ecx .text:00616233 push eax ; 根据用户名,进行一系列的运算 .text:00616234 call sub_40263F ; 计算出来的'hash'值,决定了a[4],a[5],a[6],a[7]的值 a[4] = hash & 0xff; a[5] = (hash >> 8) &0xff a[6] = (hash >> 16) &0xff; a[7] = (hash >> 24) &0xff 将hash获取之后,再根据条件1,2,3我们就可以确定所有的密码字符(情况不唯一),具体参看源代码。 当a[3]为0xac时: 这个和前面的条件2是相同的: arg0 = (a[7] ^ a[1]) << 8 + a[5] ^ a[2] ((arg0 ^ 0x7892 + 0x4d30 ) ^ 0x3421) &&0x0ffff必须整除0xB,并且商不能大于0x3E8 代码: .text:006161BB movzx ecx, [esp+30h+var_F] ; ecx = A[9] .text:006161C0 movzx eax, byte ptr [esp+30h+var_14] ; eax = a[4] .text:006161C5 movzx edx, bl ; edx = a[5] .text:006161C8 xor ecx, edx ; ecx = A[9] ^ a[5] .text:006161CA movzx edx, [esp+30h+var_10] ; edx = A[8] .text:006161CF xor eax, edx ; eax = a[4] ^ A[8] .text:006161D1 movzx edx, [esp+30h+var_18] ; edx = a[0] .text:006161D6 shl ecx, 8 .text:006161D9 add ecx, eax ; ecx = (A[9] ^ a[5]) << 8 + a[4] ^ A[8] .text:006161DB movzx eax, byte ptr [esp+30h+var_14+2] ; eax = a[6] .text:006161E0 shl ecx, 8 ; ecx = ((A[9] ^ a[5]) << 8 + a[4] ^ A[8]) << 8 .text:006161E3 xor eax, edx ; eax = a[6] ^ a[0] .text:006161E5 add ecx, eax ; ecx = ((A[9] ^ a[5]) << 8 + a[4] ^ A[8]) << 8 + a[6] ^ a[0] .text:006161E7 push (offset loc_5B8C26+1) .text:006161EC push ecx .text:006161ED call sub_403882 .text:006161F2 mov ebp, eax ; 不为0,且要大于等于0x3c70 代码: .text:0061634B or edx, 0FFFFFFFFh ; sub_403882的返回值必须大于0x3c70 .text:0061634E cmp [esp+30h+arg_4], ebp ; 参数固定为0x3c70 代码: .text:00616220 mov eax, [ecx+0Ch] ; 此时eax的值,为用户名字符串的地址 .text:00616223 mov edx, [esi+20h] ; 上面除以0xB后的商 .text:00616226 xor ecx, ecx .text:00616228 cmp [esp+30h+var_15], 0FCh ; 根据a[3]是否为0xFC,来决ecx为0,还是1 .text:0061622D push edx .text:0061622E setnz cl ; a[3]等于0xfc,则ecx=0,否则ecx=1 .text:00616231 push edi .text:00616232 push ecx .text:00616233 push eax ; 根据用户名,进行一系列的运算 .text:00616234 call sub_40263F ; 计算出来的'hash'值,决定了a[4],a[5],a[6],a[7]的值 大致的逻辑就可以确定了,详细见源代码。 当a[3]为0xfc时,这种情况不能满足条件。 下面关注一下获取hash的函数sub_614560,总体来看是根据用户名字符串依次进行循环操作,因为设计很多的移位,特别是imul指令,不太好用C语言表示,故直接用内联汇编来编写: hash获取函数 代码: int get_hash(char *name, int arg4, int arg8, int argc) { int name_length; int argc_tmp; int arg8_tmp; int eax_tmp; int ecx_tmp; int edx_tmp; int index_i; int index_j; int index; int hash; name_length = strlen(name); argc_tmp = argc << 4; argc_tmp -= argc; //edi arg8_tmp = arg8 << 4; arg8_tmp += arg8; //esi index_i = 0; index_j = 0; hash = 0; if (arg4 != 0) { for(index = 0; index < name_length; index++) { if (name[index] >= 'a' && name[index] <= 'z') { eax_tmp = name[index] - 0x20; } else { eax_tmp = name[index]; } _asm { mov eax, eax_tmp mov ecx, hash_table[eax*4] lea edx, [eax+0xd] and edx, 0xff add ecx, hash xor ecx, hash_table[edx*4] add eax, 0x2f and eax, 0xff imul ecx, hash_table[eax*4] mov edx, arg8_tmp and edx, 0xff add ecx, hash_table[edx*4] mov edx, index_j mov eax, argc_tmp and eax, 0xff add ecx, hash_table[eax*4] and edx, 0xff add ecx, hash_table[edx*4] mov hash, ecx } index_j += 0x13; argc_tmp += 0xd; arg8_tmp += 9; } } else { for(index = 0; index < name_length; index++) { if (name[index] >= 'a' && name[index] <= 'z') { eax_tmp = name[index] - 0x20; } else { eax_tmp = name[index]; } _asm { mov eax, eax_tmp mov ecx, hash_table[eax*4] lea edx, [eax+0x3f] add edx, hash add eax, 0x17 and ecx, 0xff xor ecx, hash_table[ecx*4] and eax, 0xff imul ecx, hash_table[eax*4] mov eax, arg8_tmp and eax, 0xff add edx, hash_table[eax*4] mov eax, index_i mov ecx, argc_tmp and ecx, 0xff add edx, hash_table[ecx*4] and eax, 0xff add edx, hash_table[eax*4] mov hash, edx } index_i += 0x7; arg8_tmp += 0x9; argc_tmp += 0xd; } } return hash; } 其中,要涉及一个数组表,这里采用IDA中的IDC脚本语言来获取(具体参加代码),运行之后在C盘根目录中会出现一个1.txt文件,里面就是需要的数组。 IDC脚本: 代码: auto address; auto num; auto str; auto index; auto file_handle; address = 0x7D53E8; index = 0; file_handle = fopen("c:\\1.txt", "w"); writestr(file_handle, "hash_table[] = {"); while(1) { num = Dword(address); if (num != 0) { str = ltoa(num, 16); str = "0x" + str + ", "; Message("%s\n", str); writestr(file_handle, str); address = address + 4; index = index + 1; if (index % 4 == 0) { writestr(file_handle, "\n"); } } else { break; } } writestr(file_handle, "};"); fclose(file_handle); Message("over\n", num); (不是考虑了所有的情况) 代码: for (i = 0x40370; i < 0xffffff; i += 0x11) { _asm { mov eax, 0x0f0f0f0f1 mov ecx, i mul ecx shr edx, 4 mov t, edx } if (t < 0x3c70) { continue; } if (t * 0x11 == i) { 符合条件 } } 不过因为软件有联网的检测,注册一段时间之后不能重新注册,应该是和MAC地址绑定了,在虚拟机注册之后回滚了,然后几天之后发现同样的用户名和密码就不能用了。 有两处是需要注意的: 代码: .text:00617B53 cmp dword ptr [esi+2Ch], 0 ; 如果[esi+0x2C]不为0,则返回的是0x113,应该就失败了 代码: .text:005500D8 cmp dword ptr [ecx+2Ch], 0 ; 这里ecx应该是某个对象的指针 要搞清楚[ecx+0x2C]的含义 简易注册机: 010editor.txt 注册机算法里面,简略了很多数学的操作,从严谨上来说,忽略了很多情况,只是能找到符合算法的用户名和密码。从使用角度上来说,爆破即可。 赶着末日重生第一天,有些措辞或者介绍的不详细,慢慢修复,敬请谅解 |
|