|
【目标】160个CrackMe之91号
【工具】IDA Pro v6.8 | OllyDbg v1.1
【说明】有较低难度的反静态分析,适合练手
一、初试探反静态分析之修复API调用
首先载入OllyDbg,发现有奇怪的CALL。
[Asm] 纯文本查看 复制代码 00401043 . 6A 00 push 0x0
00401045 . 68 80000000 push 0x80
0040104A . 6A 03 push 0x3
0040104C . 6A 00 push 0x0
0040104E . 6A 00 push 0x0
00401050 . 68 000000C0 push 0xC0000000
00401055 . 68 76214000 push crueme.00402176 ; ASCII "CRUEME.DAT"
0040105A . B0 1F mov al,0x1F
0040105C . FF15 64234000 call dword ptr ds:[0x402364]
第一句对0x402364进行了赋值为0x401493的操作。
转入0x401493,发现AL是上下文,0x401493实际上是一个Dispatch,对上下文计算出正确的IAT地址,并转向一个stub桩函数调用之。随便看一个stub桩函数。
[Asm] 纯文本查看 复制代码 00401AC6 > \E8 8F020000 call <jmp.&USER32.LoadCursorA> ; \LoadCursorA
00401ACB . EB 44 jmp short crueme.00401B11
再转入0x401B11查看。
[Asm] 纯文本查看 复制代码 00401B11 > \A3 68234000 mov dword ptr ds:[0x402368],eax
00401B16 . FF35 70234000 push dword ptr ds:[0x402370]
00401B1C . C3 retn
保存了返回值进全局变量,又从全局变量里取出返回地址。返回地址是Dispatch里面填充的。
这样分析很不爽,载入IDA。写一个脚本Fix它。
程序启动的时候,对于上下文的计算用一个时间值填充了一个KEY,然后利用这个KEY初始化了一个数组,Dispatch用AL作索引从数组里面取出来元素进行运算。因为是时间值填充KEY,所以KEY应该可以为任意值。
用IDA载入程序,执行这段IDC脚本即可。
[C] 纯文本查看 复制代码 #include <idc.idc>
extern byte_key;
static main()
{
byte_key = 1; //可随机取
auto addr;
xor();
addr = DfirstB(0x402364);
if (addr == -1)
{
Message("修复失败,找不到0x402364的引用");
return;
}
//第一个引用是写,故无需修复
addr = DnextB(0x402364, addr);
while (addr != - 1)
{
fix(addr);
addr = DnextB(0x402364, addr);
} //循环修复
}
static and(a) //取低8位
{
return a & 0xFF;
}
static xor() //解密数据
{
auto esi, al;
PatchDword(0x402380, 0x401224);
PatchByte(0x402363, byte_key);
al = byte_key;
for (esi = 0x40211C; esi > 0x4020F2; esi--)
{
PatchByte(esi, Byte(esi) ^ al);
al = al + 8;
al = and(al);
}
}
static patch(addr, to) //修复一个call, 传入mov al,xx地址, 以及对应jz loc_xxxxxx的名称
{
auto call, jmp;
PatchByte(addr, 0xFF);
PatchByte(addr + 1, 0x15); //call [xxxxxxxx]
call = LocByName(to);
jmp = LocByName(GetOpnd(call, 0)); //得到jmp [xxxxxxxx]的地址
PatchDword(addr + 2, GetOperandValue(jmp, 0)); //修复为call [xxxxxxxx]的形式
PatchWord(addr + 6, 0x9090);
}
static fix(addr) //修复API调用
{
auto p, al, bl, k;
p = addr - 2;
if (Byte(p) != 0xB0)
{
Message("%X 修复失败", addr);
return;
}
p++;
al = Byte(p);
bl = Byte(0x40211D - al);
al--;
al = and(al);
al = al << 3;
al = and(al);
al = al + byte_key;
al = and(al);
al = al ^ bl;
al = and(al);
//计算出al的初始值,并与0x20匹配,如果不对则往后面继续匹配
if (al == 0x20)
{
patch(addr - 2, "loc_401AC6");
return;
}
bl = al;
p = 0x004014D7;
k = GetOperandValue(p, 1); //得到对比值
if (bl == k)
{
patch(addr - 2, "loc_4019B4");
return;
}
while (bl != k)
{
p = p + 0x1E;
k = GetOperandValue(p, 1);
}
patch(addr - 2, GetOpnd(p + 2, 0));
}
运行完成后,所有API调用被修复了。
二、再分析之可怖SMC
[Asm] 纯文本查看 复制代码 CODE:00401224 loc_401224: ; DATA XREF: DATA:lpBaseAddresso
CODE:00401224 push edx ; lParam
CODE:00401225 push 0Eh ; wParam
CODE:00401227 push 0Dh ; Msg
CODE:00401229 push 3EAh ; nIDDlgItem
CODE:0040122E push [ebp+hDlg] ; hDlg
CODE:00401231 call ds:__imp_SendDlgItemMessageA
CODE:00401237 nop
CODE:00401238 nop
CODE:00401239 call sub_401B73
CODE:0040123E push eax
CODE:0040123F mov edi, 1
CODE:00401244 call SMC
执行到CALL SMC时,从0x401224开始起的代码会被替换掉。
这是用来替换的代码。
走到这里又还原了SMC。
难道这个SMC是为了反动态调试?接着往下面看看。
发现没有?这里不管你成功还是失败都会直接往下面走提示你失败,会把成功覆盖掉。
难道这里是BUG?
我往尾部走了两圈,柳暗花明。
这里又走回去执行了流程。是什么意思?
这次深入看一下SMC。
恐怖如斯。稍有不慎即中招。
现在执行这段IDC脚本。
[C] 纯文本查看 复制代码 static main()
{
auto i, esi, edi;
esi = 0x40238C;
edi = 0x401224;
for (i = 0; i < 25; i++)
{
PatchByte(edi, Byte(esi));
edi++;
esi++;
}
return;
}
执行完后,SMC代码已经覆盖完。
因为只有1个API重定向,我就不用脚本FIX了。直接猜也知道是GetDlgItemTextA。
接着往下面看。
[Asm] 纯文本查看 复制代码 CODE:00401B8E loc_401B8E: ; CODE XREF: Check:loc_401C16j
CODE:00401B8E mov eax, [ebx]
CODE:00401B90 add eax, 12D687h
CODE:00401B95 add eax, [ebx+8]
CODE:00401B98 sub eax, 2B67h
CODE:00401B9D xor edx, edx
CODE:00401B9F mul ds:iLength
CODE:00401BA5 mov edx, [ebx+4]
CODE:00401BA8 add edx, 48FF4EAh
CODE:00401BAE xor eax, edx
CODE:00401BB0 mov edx, [ebx+8]
CODE:00401BB3 sub edx, 0BC5C01h
CODE:00401BB9 sub edx, [ebx]
CODE:00401BBB add edx, 2B67h
CODE:00401BC1 xor eax, edx
CODE:00401BC3 or eax, 29359E2h
CODE:00401BC8 add edi, eax
CODE:00401BCA and edi, ds:dword_402162
CODE:00401BD0 mov eax, [ebx]
CODE:00401BD2 sub eax, 12D687h
CODE:00401BD7 sub eax, [ebx+8]
CODE:00401BDA add eax, 56CEh
CODE:00401BDF xor edx, edx
CODE:00401BE1 div ds:iLength
CODE:00401BE7 mov edx, [ebx+4]
CODE:00401BEA sub edx, 48FF4EAh
CODE:00401BF0 xor eax, edx
CODE:00401BF2 mov edx, [ebx+8]
CODE:00401BF5 add edx, 0BC5C01h
CODE:00401BFB add edx, [ebx]
CODE:00401BFD sub edx, 56CEh
CODE:00401C03 xor eax, edx
CODE:00401C05 and eax, 29359E2h
CODE:00401C0A add esi, eax
CODE:00401C0C or esi, ds:dword_402166
CODE:00401C12 loop loc_401C16
CODE:00401C14 jmp short loc_401C1B
CODE:00401C16 ; ---------------------------------------------------------------------------
CODE:00401C16
CODE:00401C16 loc_401C16: ; CODE XREF: Check+9Fj
CODE:00401C16 jmp loc_401B8E
CODE:00401C1B ; ---------------------------------------------------------------------------
CODE:00401C1B
CODE:00401C1B loc_401C1B: ; CODE XREF: Check+A1j
CODE:00401C1B add edi, 911h
CODE:00401C21 sub esi, 911h
CODE:00401C27 dec ds:byte_402133
CODE:00401C2D cmp ds:byte_402133, 0
CODE:00401C34 jnz loc_401B89
描述这段算法。
我输入的注册码是12345678
[C] 纯文本查看 复制代码 int i, j;
unsigned int edi = 0, esi = 0;
unsigned int k1 = '4321', k2 = '8765', k3 = 0;
//k1是1-4位,k2是5-8位,k3是9-12位
unsigned int eax = 0, edx = 0;
unsigned iLen = 8; //实际位数
for (i = 0xFF; i != 0; i--)
{
for (j = 0xFF; j != 0; j--)
{
eax = k1 + 0x12D687 + k3 - 0x2B67;
eax *= iLen;
eax ^= (k2 + 0x48FF4EA);
eax ^= (k3 - 0xBC5C01 - k1 + 0x2B67);
eax |= 0x29359E2;
edi += eax;
edi &= 0x15263748;
eax = (k1 - 0x12D687 - k3 + 0x56CE) / iLen;
eax ^= (k2 - 0x48FF4EA);
eax ^= (k3 + 0xBC5C01 + k1 - 0x56CE);
eax &= 0x29359E2;
esi += eax;
esi |= 0x596A7B8C;
}
edi += 0x911;
esi -= 0x911;
}
最后运算结果,EDI = 0xEFFDE3AF,ESI = 0xA4948D23。若不成立,则失败。
到了这一步,我真的是有点怕怕了。本高一数学渣渣看到这玩意已是不知所措。我只能想到猜解的办法。如果有数学大神,还请赐教啊。
我猜解4位看看。可打印字符有95个,95*95*95*95,千万级别运算量。
看来运气比较好,我没跑完,跑了几组解出来。
每一组解后面都有空格。
测试第一组解。
注意解后面是空格,重要的事说3遍。
比如<B2s >,直接复制尖括号内即可。
附上我的猜解源码(VC6)
[C] 纯文本查看 复制代码 #include <iostream>
#include <string>
bool check(unsigned int k1, unsigned int k2, unsigned int k3, unsigned iLen)
{
int i, j;
unsigned int edi = 0, esi = 0;
unsigned int eax = 0, edx = 0;
for (i = 0xFF; i != 0; i--)
{
for (j = 0xFF; j != 0; j--)
{
eax = k1 + 0x12D687 + k3 - 0x2B67;
eax *= iLen;
eax ^= (k2 + 0x48FF4EA);
eax ^= (k3 - 0xBC5C01 - k1 + 0x2B67);
eax |= 0x29359E2;
edi += eax;
edi &= 0x15263748;
eax = (k1 - 0x12D687 - k3 + 0x56CE) / iLen;
eax ^= (k2 - 0x48FF4EA);
eax ^= (k3 + 0xBC5C01 + k1 - 0x56CE);
eax &= 0x29359E2;
esi += eax;
esi |= 0x596A7B8C;
}
edi += 0x911;
esi -= 0x911;
}
return (edi + 0xEFFDE3AF == 0 && esi + 0xA4948D23 == 0);
}
int main()
{
using namespace std;
char k1[5] = {0};
for (unsigned int i = 0; i <= 0xFFFFFFFF; i++) //确定1-4位
{
memcpy(k1, &i, 4);
if (isprint(k1[0]) && isprint(k1[1]) && isprint(k1[2]) && isprint(k1[3])) //确定1-4位都是可打印字符
{
if (check(i, 0, 0, 4))
cout << "猜解成功,解为:" << k1;
}
else
continue;
}
return 0;
}
|
评分
-
参与人数 22 | 威望 +1 |
HB +50 |
THX +14 |
收起
理由
|
禽大师
| |
|
+ 1 |
|
花盗睡鼠
| |
+ 2 |
+ 1 |
[吾爱汇编论坛52HB.COM]-学破解防破解,知进攻懂防守! |
longge188
| |
|
+ 1 |
[吾爱汇编论坛52HB.COM]-吃水不忘打井人,给个评分懂感恩! |
一路走来不容易
| |
+ 1 |
|
|
消逝的过去
| |
|
+ 1 |
|
冷亦飞
| |
+ 1 |
|
|
纯英文
| |
+ 1 |
|
|
temp
| |
+ 1 |
+ 1 |
|
kkk1l
| |
+ 1 |
|
|
凌夏随缘
| |
|
+ 1 |
|
kway
| |
|
+ 1 |
|
hotD
| |
+ 2 |
+ 1 |
|
missaa
| |
+ 1 |
|
|
bnjzzheng
| |
+ 1 |
|
[吾爱汇编论坛52HB.COM]-吃水不忘打井人,给个评分懂感恩! |
EMT
| |
+ 1 |
|
|
jackwuwei
| |
+ 1 |
|
[吾爱汇编论坛52HB.COM]-学破解防破解,知进攻懂防守! |
lies
| |
|
+ 1 |
|
海天一色001
| |
+ 1 |
+ 1 |
[快捷评语] - 评分=感恩!简单却充满爱!感谢您的作品! |
海东
| |
+ 5 |
+ 1 |
[快捷评语] - 吃水不忘打井人,给个评分懂感恩! |
sxin0807
| |
+ 1 |
+ 1 |
[快捷评语] - 2018,狗年发发发,狗年旺旺旺! |
syzh802618
| |
|
+ 1 |
[快捷评语] - 2018,狗年发发发,狗年旺旺旺! |
Shark恒
| + 1 |
+ 30 |
+ 1 |
[快捷评语] - 2018,狗年发发发,狗年旺旺旺! |
查看全部评分
|