在xp里调试运行上传的攻击脚本,然后在00401257这个地址(push "WELL DONE!")下普通断点,等待程序跑到这个地址断下来就穷举出结果
说明:这个攻击脚本是在原来程序上,直接修改exe内容,通过汇编代码修改程序流程,来达到优化后穷举的目的,
主要分2大步骤,第一步:修改exe使得程序可以自行穷举,第二步:优化使得穷举变快,其中优化是关键
优化的地方:
1)nop掉GetTickCount,跟GetTickCount相关的保存结果,和结果保存后,将来再进行运算的代码都可以nop掉,因为分析代码得出结论GetTickCount并没有什么影响到注册码的判断
2)sub_401A60里的从(00401A8B到00401AA8)的这个循环可以被优化掉,完全是废代码,此处费代码会在循环里调用sub_4019E0,此处浪费时间非常多,将循环优化掉后,就完全不计算GetTickCount相关的代码了,IDA分析如下:
int __thiscall sub_401A60(void *this) { void *v1; // esi@1 signed int v2; // eax@1 char *v3; // edi@1 char *v4; // ecx@1 int v5; // edx@2 signed int v6; // ebp@3 int v7; // ebx@4 int v8; // eax@4 int result; // eax@5 int v10; // ecx@5 signed int v11; // edx@5 int v12; // esi@6
v1 = this; v2 = 0; v3 = (char *)this + 4104; v4 = (char *)this + 4104; do { v4 += 4; v5 = v2++ & 0x3FF; *((_DWORD *)v4 - 1) = v5; } while ( v2 < 1024 ); v6 = 1024;
//下面紧接着的这个循环可以完全干掉,毫无用处
do { v7 = *(_DWORD *)v3; v8 = sub_4019E0(v1); *(_DWORD *)v3 = *((_DWORD *)v1 + v8 + 1026); v3 += 4; --v6; *((_DWORD *)v1 + v8 + 1026) = v7; } while ( v6 ); result = (int)((char *)v1 + 8); v10 = (int)((char *)v1 + 8196); v11 = 1024; do { v12 = *(_DWORD *)v10; v10 -= 4; *(_DWORD *)result = v12; result += 4; --v11; } while ( v11 ); return result; }
上面的代码,第二处也就是中间的循环可以完全干掉,节约大量穷举的时间
3)在00401168处,修改jnz跳转的地址到准备开始下次穷举的位置,此处用IDA分析是
while ( 1 ) { CObj5_CObj5_szKey(&obj5_2, szKey); LOBYTE(v23) = 1; v8 = sub_401840((int)&obj5_2, (int)&obj5, (int)&obj5_2); v9 = sub_401730(v8, (int)&obj5, 9) + v8; nullsub_1(); if ( v9 || sub_4013A0(&obj5) % 2 != 1 ) goto LABEL_16; v10 = sub_4013A0(&obj5); v11 = sub_4013B0((int)&obj5, v10 >> 1); v12 = sub_4013B0((int)&obj5_2, 0); v13 = &obj5_2; if ( v11 == v12 ) break; LABEL_17: LOBYTE(v23) = 0; CObj5_Destructor(v13); if ( v9 ) { printf("WRONG KEY...\n"); goto LABEL_19; } } v14 = sub_4013A0(&obj5_2) - 1; v15 = 1 - sub_4013A0(&obj5_2); v16 = sub_4013A0(&obj5); v17 = sub_4013E0(&obj5, (int)&obj5_2, v16 + v15, 1, v14, 0); v18 = sub_4013A0(&obj5_2); if ( sub_4013E0(&obj5, (int)&obj5_2, 0, 1, v18 - 1, 1) + v17 ) { v9 = 0; LABEL_16: v13 = &obj5_2; goto LABEL_17; }
上面分析代码里,经过调试发现如果sub_4013A0(&obj5) % 2 != 1,while(1)循环还将继续尝试运算,这个模2等不等于1的判断总是失败,也就是总是等于0,导致while(1)循环无故循环了很多次,此处将goto LABEL_16;改成跳转jnz到将注册码自加1,直接尝试下一个注册码的地方,减少大量浪费时间的循环操作
主要做了上面3处优化
在上面3处优化的基础上,让程序可以自己进行穷举尝试,然后调试运行,在push "WELL DONE!"处下断点等待结果,对注册码自加1的代码如下:
004012A1 |> 8038 31 cmp byte ptr [eax], 31 004012A4 |. |7D 03 jge short 004012A9 004012A6 |. |C600 31 mov byte ptr [eax], 31 004012A9 |> |8038 39 cmp byte ptr [eax], 39 004012AC |. |7C 06 jl short 004012B4 004012AE |. |C600 31 ||mov byte ptr [eax], 31 004012B1 |. |40 inc eax 004012B2 |.^\EB ED jmp short 004012A1 004012B4 |> FE00 inc byte ptr [eax] 004012B6 |. BC 34BE1200 mov esp, 12BE34 004012BB \.^ E9 93FDFFFF jmp 00401053 004012C0 /$ 56 push esi 004012C1 |. 8BF1 mov esi, ecx 004012C3 |. C706 C8804000 mov dword ptr [esi], 004080C8
最后还附上了自己还原的大部分代码,还原的代码可以供参考,某些函数发现可以优化就优化了
[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界
上传的附件: