分析160个CrackMe之91
【目标】160个CrackMe之91号【工具】IDA Pro v6.8 | OllyDbg v1.1
【说明】有较低难度的反静态分析,适合练手
一、初试探反静态分析之修复API调用
首先载入OllyDbg,发现有奇怪的CALL。
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进行了赋值为0x401493的操作。
转入0x401493,发现AL是上下文,0x401493实际上是一个Dispatch,对上下文计算出正确的IAT地址,并转向一个stub桩函数调用之。随便看一个stub桩函数。
00401AC6 > \E8 8F020000 call <jmp.&USER32.LoadCursorA> ; \LoadCursorA
00401ACB .EB 44 jmp short crueme.00401B11
再转入0x401B11查看。
00401B11 > \A3 68234000 mov dword ptr ds:,eax
00401B16 .FF35 70234000 push dword ptr ds:
00401B1C .C3 retn
保存了返回值进全局变量,又从全局变量里取出返回地址。返回地址是Dispatch里面填充的。
这样分析很不爽,载入IDA。写一个脚本Fix它。
程序启动的时候,对于上下文的计算用一个时间值填充了一个KEY,然后利用这个KEY初始化了一个数组,Dispatch用AL作索引从数组里面取出来元素进行运算。因为是时间值填充KEY,所以KEY应该可以为任意值。
用IDA载入程序,执行这段IDC脚本即可。
#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
call = LocByName(to);
jmp = LocByName(GetOpnd(call, 0)); //得到jmp 的地址
PatchDword(addr + 2, GetOperandValue(jmp, 0)); //修复为call 的形式
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
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 ; 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脚本。
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。
接着往下面看。
CODE:00401B8E loc_401B8E: ; CODE XREF: Check:loc_401C16j
CODE:00401B8E mov eax,
CODE:00401B90 add eax, 12D687h
CODE:00401B95 add eax,
CODE:00401B98 sub eax, 2B67h
CODE:00401B9D xor edx, edx
CODE:00401B9F mul ds:iLength
CODE:00401BA5 mov edx,
CODE:00401BA8 add edx, 48FF4EAh
CODE:00401BAE xor eax, edx
CODE:00401BB0 mov edx,
CODE:00401BB3 sub edx, 0BC5C01h
CODE:00401BB9 sub edx,
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,
CODE:00401BD2 sub eax, 12D687h
CODE:00401BD7 sub eax,
CODE:00401BDA add eax, 56CEh
CODE:00401BDF xor edx, edx
CODE:00401BE1 div ds:iLength
CODE:00401BE7 mov edx,
CODE:00401BEA sub edx, 48FF4EAh
CODE:00401BF0 xor eax, edx
CODE:00401BF2 mov edx,
CODE:00401BF5 add edx, 0BC5C01h
CODE:00401BFB add edx,
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
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)
#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 = {0};
for (unsigned int i = 0; i <= 0xFFFFFFFF; i++) //确定1-4位
{
memcpy(k1, &i, 4);
if (isprint(k1) && isprint(k1) && isprint(k1) && isprint(k1)) //确定1-4位都是可打印字符
{
if (check(i, 0, 0, 4))
cout << "猜解成功,解为:" << k1;
}
else
continue;
}
return 0;
}
尾部有两张图,是多余的。不用看了。 这么可怖的SMC{:5_117:},感谢沉舟童鞋带来精彩分析 过来学习!! 学习!!!! 多谢大佬分享
支持一下,谢谢了 感谢楼主分享 感谢分享,感谢
学习!!!!
页:
[1]
2