【文章标题】: 看雪CTF2016-5-CrackMe-冰怜泯灭.rar破解报告
【文章作者】: SilentGamb@pediy
【作者主页】: http://bbs.pediy.com/member.php?u=492631
【软件名称】: 5-CrackMe-冰怜泯灭.rar
【软件大小】: 40.0 KB (40,960 字节)
【下载地址】: http://ctf.pediy.com/?game-fight-6.htm
【加壳方式】: 无
【保护方式】: 无
【编写语言】: VC6
【使用工具】: OD1.1 + IDA6.1
【操作平台】: Win7X64
【软件介绍】: 冰怜泯灭给大家做的cm
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
<<5-CrackMe-冰怜泯灭.rar>>破解调试记录
侦查
注册码输入错误之后没提示
VC6编译器生成.
单步跟一下
00401172 . FFD3 call ebx ; |\GetDlgItem
00401174 . 8B2D A4504000 mov ebp,dword ptr ds:[<&USER32.SendMessageA>] ; |user32.SendMessageA
0040117A . 50 push eax ; |hWnd = 0x9
0040117B . FFD5 call ebp ; \SendMessageA
0040117D . 33C9 xor ecx,ecx ; 取用户输入
0040117F . 85C0 test eax,eax
00401181 . 76 17 jbe short CrackMe.0040119A
00401183 > 8A540C 20 mov dl,byte ptr ss:[esp+ecx+0x20]
00401187 . 80FA 30 cmp dl,0x30
0040118A . 7C 0C jl short CrackMe.00401198
0040118C . 80FA 39 cmp dl,0x39
0040118F . 7F 07 jg short CrackMe.00401198 ; 判断是否是合规字符集(0~9)
00401191 . 41 inc ecx
00401192 . 3BC8 cmp ecx,eax
00401194 .^ 72 ED jb short CrackMe.00401183
00401196 . EB 02 jmp short CrackMe.0040119A
00401198 > 33FF xor edi,edi
0040119A > 83F8 06 cmp eax,0x6
0040119D . 75 56 jnz short CrackMe.004011F5 ; 注册码是6个数字
0040119F . 85FF test edi,edi
004011A1 . 74 52 je short CrackMe.004011F5
启用测试输入 987654
004010DE |. 81FF 79290000 cmp edi,0x2979 ; 注册码结果比对
sub_401000 将用户输入进行运算A, 结果为B
sub_4010C0 将B做校验,看是否正确, 结果为bRc
如果bRc正确,用sub_4010C0的运算结果去做任务,显示正确注册信息。
如果爆破,程序崩了.
sub_401000((int)szBuf_len_0x3f, 6);
LOBYTE(v9) = sub_4010C0();
将 sub_401000, sub_4010C0反推出正确的注册码,就可以了.
确认运算过程
0018f85c = 0x100 缓冲区长度
0018f864长度0x100, 初始化为0x00~0xff
0018F854 FE 71 6A 75 5A 37 6A 75 00 01 00 00 04 00 34 00 juZ7ju....4.
0018F864 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F .0018F874 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
...
0018F944 E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF 噌忏溴骁栝觌祉铒
0018F954 F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF 瘃蝮趱鲼?
制作穷举注册机
sub_401000是更新Key.
sub_4010C0 是注册码比对.
IDA翻译的内容摘出来直接用,有问题, 表达不了cm的算法,只能参考。
人肉翻译了这2个函数,整理出了注册机.
注册机实现
// KeyGen.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.h>
int g_iRetryCnt = 0;
const char ucAryPwdCharSet[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
BYTE g_ucAryKey[128] = {'\0'};
BYTE g_ucAryKeyBk[128] = {
0xF4, 0x12, 0x9D, 0x60, 0x45, 0xF8, 0x20, 0x6A, 0x6F, 0x67, 0x04, 0x71, 0xC0, 0x9B, 0x0C, 0x5A,
0x1D, 0x18, 0x6C, 0x96, 0x69, 0x01, 0x1C, 0xF4, 0x7F, 0x28, 0x5A, 0xFB, 0x29, 0x07, 0x40, 0x8B,
0xD3, 0xE1, 0xB1, 0x12, 0xFB, 0xCA, 0x7C, 0x89, 0xB9, 0x5A, 0x30, 0x70, 0x9D, 0x95, 0x2B, 0x95,
0x3C, 0x8D, 0x2E, 0x45, 0xEF, 0x70, 0xC6, 0xA3, 0xB9, 0xB2, 0x5A, 0x63, 0x5F, 0x03, 0x33, 0xB8,
0x64, 0x4A, 0x8F, 0xBC, 0xF7, 0x91, 0x69, 0x6A, 0x56, 0x2E, 0xD4, 0x6E, 0x82, 0x93, 0xE9, 0x76,
0xDC, 0xA3, 0x6C, 0x5E, 0x6B, 0x72, 0x64, 0x37, 0xE7, 0x15, 0x17, 0xAC, 0x64, 0x78, 0xD5, 0x4A,
0x60, 0x2D, 0xF0, 0x54, 0xA6, 0xF3, 0xE8, 0xE0, 0xE0, 0xB9, 0x8F, 0x85, 0x90, 0xE4, 0xEA, 0xD6,
0xBB, 0xB7, 0x15, 0x9E, 0x2A, 0x44, 0xE7, 0x31, 0x63, 0xAC, 0x80, 0x6C, 0x34, 0x82, 0xE9, 0xCF
};
int fnUpdateKeyByUserInput(char* pcSnInput, int iLenSn)
{
int iLoopCnt = 0;
BYTE ucIndex = 0;
int iIndex = 0;
BYTE ucTotal = 0;
BYTE ucTmp = 0;
char szBuf[0x100] = {'\0'};
BYTE* pBufCur = (BYTE*)&szBuf[0];
BYTE* pBufBegin = (BYTE*)&szBuf[0];
BYTE* pBufEnd = (BYTE*)&szBuf[0xff];
memcpy(g_ucAryKey, g_ucAryKeyBk, sizeof(g_ucAryKeyBk)); // !
for (iIndex = 0; iIndex < sizeof(szBuf); iIndex++) {
szBuf[iIndex] = iIndex;
}
// 根据输入计算索引交换缓冲区字节内容
for (iLoopCnt = sizeof(szBuf); iLoopCnt > 0; iLoopCnt--) {
ucTotal += (*pBufCur + * ((BYTE*)pcSnInput + ucIndex++));
if (ucIndex >= 6) {
ucIndex = 0;
}
ucTmp = *pBufCur;
*pBufCur = szBuf[ucTotal];
szBuf[ucTotal] = ucTmp;
pBufCur++;
}
// 用计算后的缓冲区内容更新预留密钥
// szBuf size的一半是0x128
for (iIndex = 0; iIndex < 128; iIndex++) {
g_ucAryKey[iIndex] ^= (*pBufBegin++ + *pBufEnd--);
}
return iIndex;
}
bool RegSnVerify()
{
int iSum = 0;
int iIndex = 0;
do {
iSum += g_ucAryKey[iIndex++];
} while (iIndex < 128);
return (iSum == 10617);
}
int main(int argc, char* argv[])
{
// 6位的口令, 用接口穷举吧^_^
int iLenAryPwdCharSet = sizeof(ucAryPwdCharSet);
int iLoop1 = 0;
int iLoop2 = 0;
int iLoop3 = 0;
int iLoop4 = 0;
int iLoop5 = 0;
int iLoop6 = 0;
char szBuf[7] = {'\0'};
for (iLoop1 = 0; iLoop1 < iLenAryPwdCharSet; iLoop1++) {
for (iLoop2 = 0; iLoop2 < iLenAryPwdCharSet; iLoop2++) {
for (iLoop3 = 0; iLoop3 < iLenAryPwdCharSet; iLoop3++) {
for (iLoop4 = 0; iLoop4 < iLenAryPwdCharSet; iLoop4++) {
for (iLoop5 = 0; iLoop5 < iLenAryPwdCharSet; iLoop5++) {
for (iLoop6 = 0; iLoop6 < iLenAryPwdCharSet; iLoop6++) {
szBuf[0] = ucAryPwdCharSet[iLoop1];
szBuf[1] = ucAryPwdCharSet[iLoop2];
szBuf[2] = ucAryPwdCharSet[iLoop3];
szBuf[3] = ucAryPwdCharSet[iLoop4];
szBuf[4] = ucAryPwdCharSet[iLoop5];
szBuf[5] = ucAryPwdCharSet[iLoop6];
if (g_iRetryCnt++ > 10000) {
g_iRetryCnt = 0;
OutputDebugString("find... 注册码 : ");
OutputDebugString(szBuf);
OutputDebugString("\r\n");
}
fnUpdateKeyByUserInput(szBuf, 6);
if (true == RegSnVerify()) {
// 注册码是 : 771535 ^_^
::MessageBox(NULL, szBuf, "找到注册码!", MB_OK);
OutputDebugString("找到注册码!\r\n");
OutputDebugString(szBuf);
OutputDebugString("\r\n");
}
}
}
}
}
}
}
::MessageBox(NULL, "END", "穷举结束", MB_OK);
return 0;
}
得到注册码
注册机大概跑了10秒,得到注册码”771535”
因为只有9位数字的注册码,又验证了还有没有多解,符合题意,只有这一个解.
cm实际验证
输入注册码 771535, 成功了
--------------------------------------------------------------------------------
【经验总结】
逆向分析出注册接口,然后使用针对性的穷举,这招还蛮好使.
如果是蛮力穷举,穷举的效果就不可预测了.
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!
2016年11月10日 19:39:08
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!