先按国际惯例辣手摧花
原本想写脚本去花,可是我已经忘记脚本的语法了,只好用C来写:
void riijj_junkfix()
{
DWORD dwTmp;
DWORD Addr;
DWORD AddrE;
DWORD CallAddr;
Addr = 0x401000;
AddrE = 0x403E00;
VirtualProtect((LPVOID)Addr,AddrE-Addr,PAGE_EXECUTE_READWRITE,&dwTmp);
while (Addr < AddrE)
{
if ((*(DWORD *)Addr == 0x058DED39) && (*(BYTE *)(Addr+8) == 0x74))
{
dwTmp = *(BYTE *)(Addr+9);
CallAddr = *(DWORD *)(Addr+4) + *(DWORD *)(Addr+dwTmp+12);
if (*(WORD *)(Addr+dwTmp+16) == 0xD0FF)
{
memset((LPVOID)Addr,0x90,dwTmp+13);
*(BYTE *)(Addr+dwTmp+13) = 0xE8;
*(DWORD *)(Addr+dwTmp+14) = CallAddr - (Addr+dwTmp+18);
Addr += dwTmp+18;
}
else if ((*(BYTE *)(Addr+dwTmp+16) == 0x68) && (*(DWORD *)(Addr+dwTmp+17) == (Addr+dwTmp+23)))
{
memset((LPVOID)Addr,0x90,(dwTmp+18));
*(BYTE *)(Addr+dwTmp+18) = 0xE8;
*(DWORD *)(Addr+dwTmp+19) = CallAddr - (Addr+dwTmp+23);
Addr += (dwTmp+23);
}
else
{
Addr++;
}
}
else if ((*(BYTE *)Addr == 0x74) && (*(BYTE *)(Addr+2) == 0x75) && (*(BYTE *)(Addr+1) - *(BYTE *)(Addr+3) == 2))
{
dwTmp = *(BYTE *)(Addr+3)+4;
memset((LPVOID)Addr,0x90,dwTmp);
Addr += dwTmp;
}
else
{
Addr++;
}
}
}
我是用LPK注进去执行这段代码的来修复的,执行完以后应该把花指令去光了,修好了保存一下文件,只剩下那些jmp来jmp去的东西了,这些jmp应该也是可以修复的,我短时间写不好,所以放弃这个想法。
还有一种选择是直接写idc脚本,我想有这个能力的人没必要看我这个帖子了。
此处无花胜有花应该就是指现在的感受了
找到注册检查函数入口40263B,方法我也不会,我是胡乱下断点命中的,自己尝试,也许可以断在GetDlgItemTextA从堆栈里找返回地址。
把修好的文件用ida打开,在函数40263B处F5,Hex-Rays很强大,无视那些jmp
int __cdecl sub_40263B()
{
int result; // eax@1
unsigned int v1; // ecx@11
signed int v2; // [sp+4Ch] [bp-F0h]@1
signed int v3; // [sp+48h] [bp-F4h]@1
unsigned int v4; // [sp+44h] [bp-F8h]@1
int v5; // [sp+F0h] [bp-4Ch]@1
unsigned int v6; // [sp+40h] [bp-FCh]@1
int v7; // [sp+B0h] [bp-8Ch]@1
UINT v8; // [sp+3Ch] [bp-100h]@1
unsigned int v9; // [sp+2Ch] [bp-110h]@1
unsigned int v10; // [sp+38h] [bp-104h]@2
int v11; // [sp+130h] [bp-Ch]@2
unsigned int v12; // [sp+20h] [bp-11Ch]@9
unsigned int v13; // [sp+30h] [bp-10Ch]@11
unsigned int v14; // [sp+24h] [bp-118h]@11
unsigned int v15; // [sp+28h] [bp-114h]@11
unsigned int v16; // [sp+34h] [bp-108h]@11
unsigned int *v17; // [sp+18h] [bp-124h]@15
int v18; // [sp+50h] [bp-ECh]@24
int v19; // [sp+1Ch] [bp-120h]@31
v2 = 7823;
v3 = 97157;
v4 = 457;
v6 = GetDlgItemTextA((HWND)dword_406078, 1000, (CHAR *)&v5, 50);
v8 = GetDlgItemTextA((HWND)dword_406078, 1001, (CHAR *)&v7, 50);
result = 97157;
v9 = 97157;
if ( v6 && v8 > 0x13 )
{
v10 = 0;
while ( v10 <= 0x31 )
{
v19 = v2 * v9;
v17 = &v6;
v9 = v2 * v9 % v4 + *((_BYTE *)&v5 + v10 % v6);
*((_BYTE *)&v11 + v10++ - 192) = v9;
}
v12 = 1;
v10 = 0;
while ( v10 < v6 )
{
if ( v10 & 1 )
v12 %= (unsigned int)*((_BYTE *)&v11 + v10 - 64);
else
v12 *= *((_BYTE *)&v11 + v10 - 64);
++v10;
}
v9 = v3;
memset(&v18, 0, 0x1Eu);
v10 = 0;
while ( v10 <= 0x13 )
{
v1 = v12 * *((_BYTE *)&v11 + v10 - 192);
v13 = v1 % 0x32;
v14 = *((_BYTE *)&v11 + v1 % 0x32 - 192);
v15 = v14;
v16 = 0;
while ( v16 < v15 )
{
v17 = &v4;
v9 = v2 * v9 % v4 ^ *((_BYTE *)&v11 + v10 - 192);
++v16;
}
*((_BYTE *)&v11 + v10++ - 224) = v9 % 0x1A + 65;
}
v10 = 0;
while ( v10 <= 0x13 )
{
result = *((_BYTE *)&v11 + v10 - 128);
if ( (_BYTE)result != *((_BYTE *)&v11 + v10 - 224) )
return result;
++v10;
}
result = sub_402351();
}
return result;
}
402351处也顺便F5看看:
int __cdecl sub_402351()
{
return MessageBoxA((HWND)dword_406068, "Registration successful !", "Riijj crackme 15b1", 0);
}
这样流程应该可以看清楚了,怎么爆破已经不重要了,看心情自由发挥
如果你是个无赖就这样:
把
4017D4 call 40263B
改成
4017D4 call 402351
如果你比较传统就把
0040266A JNZ 0040302B
整行nop掉
如果你想搞得神秘一点
就把sub_40263B整理一下,重新编译一份贴回去,要调整一下GetDlgItemTextA的地址,call memset和call 402351的偏移,然后再随便你怎么爆
其实这个crackme是明码比较,爆破纯属娱乐。
最后借机说点题外话:
zenghw的不情之请贴已经让版主帮忙移到水区了,首先感谢大家的支持,我只能以具体程序就事论事地与大家交流,我都是现学现卖的,用完就忘的,大部分都是靠google来的,解决一个问题就扔掉了,基本不会花时间去研究原理的。
就比如本例中的crackme,过些天再拿出来,我还是要重新看,也许我会选择用od脚本去花,脚本语法忘了,我就看帮助再学一遍就是了,再过些天再遇到这个crackme也许我会有兴趣学一下idc脚本来去花,所以同一个程序在没开始分析之前,我自己都不知道可能怎么去分析它,而且中途遇到困难也会随时改变想法。
对于我来说基础就是看到一行汇编代码知道它执行完以后寄存器状态会产生哪些变化,尽量记住大部分16进制机器码;几行指令组合起来是什么意思就要每个人花时间去积累了,可能像被单词一样。