珠海金山2007逆向分析挑战赛-CrackMe看雪(九连环)(writeup)
这几天在玩看雪上的KCTF,reverse里面有一个觉得有点意思的就是珠海金山2007逆向分析挑战赛-CrackMe看雪
首先,IDA打开,无壳程序还原C代码
INT_PTR start()
{HMODULE ModuleHandleA; // eaxModuleHandleA = GetModuleHandleA(0);return DialogBoxParamA(ModuleHandleA, (LPCSTR)0x65, 0, DialogFunc, 0);
}
首先,start函数注册了回调函数DialogFunc
INT_PTR __stdcall DialogFunc(HWND hDlg, UINT a2, WPARAM a3, LPARAM a4)
{unsigned int v4; // ebxsigned int v5; // edxunsigned int v6; // kr04_4int v7; // esiCHAR v9[4100]; // [esp+Ch] [ebp-1018h] BYREFCHAR String; // [esp+1010h] [ebp-14h] BYREFint v11; // [esp+1011h] [ebp-13h]int v12; // [esp+1015h] [ebp-Fh]int v13; // [esp+1019h] [ebp-Bh]int v14; // [esp+101Dh] [ebp-7h]String = 0;v11 = 0;v12 = 0;memset(v9, 0, 4097);v13 = 0;v14 = 0;v4 = 0x13572468;if ( a2 == 0x111 ){if ( (unsigned __int16)a3 == 2 ){EndDialog(hDlg, 0);}else if ( (unsigned __int16)a3 == 1000&& GetDlgItemTextA(hDlg, 1001, &String, 16)&& GetDlgItemTextA(hDlg, 1002, v9, 4096) ){v5 = 0;v6 = strlen(&String) + 1;if ( (int)(v6 - 1) > 0 ){do{v7 = ((int)(57807475 * (v4 + *(&String + v5)) + 610800471) >> 7) | ((57807475 * (v4 + *(&String + v5))+ 610800471) << 25);++v5;v4 = v7;}while ( v5 < (int)(v6 - 1) );}sub_4002CC(v4, v9);}}return 0;
}
回调函数里,处理程序结束消息和注册按钮按下的消息。
题目要求得到注册码,主要看注册按钮事件。
首先根据输入的用户名计算v4,再根据v4校验注册码。(v9为输入的注册码,String为输入的用户名),其中,题目要求用户名为"KCTF",简化一下,得到
unsigned int v4 = 0x13572468;char userName[] = "KCTF";for(int i = 0; i < 4; i++){v4 = ((int)(57807475 * (v4 + userName[i]) + 610800471) >> 7) | ((57807475 * (v4 + userName[i])+ 610800471) << 25);}printf("%u\n", v4);
注册码校验的核心逻辑就是sub_4002CC
int __cdecl sub_4002CC(unsigned int a1, const char *a2)
{int v2; // esiint i; // ediint v4; // ediunsigned int v5; // edxint v6; // ecxint v7; // eaxCHAR Text[257]; // [esp+Ch] [ebp-128h] BYREF__int16 v10; // [esp+10Dh] [ebp-27h]char v11; // [esp+10Fh] [ebp-25h]char v12; // [esp+110h] [ebp-24h]int v13; // [esp+111h] [ebp-23h]int v14; // [esp+115h] [ebp-1Fh]char v15; // [esp+119h] [ebp-1Bh]char v16[12]; // [esp+11Ch] [ebp-18h] BYREFchar v17[11]; // [esp+128h] [ebp-Ch] BYREFchar v18; // [esp+133h] [ebp-1h]v12 = 0;v13 = 0;memset(Text, 0, sizeof(Text));v14 = 0;v15 = 0;v17[0] = 0xFF;v16[0] = 0xFF;v10 = 0;v11 = 0;v17[1] = 0x63;v17[2] = 0xFB;v17[3] = 0x9A;v17[4] = 3;v17[5] = 0xA3;v17[6] = 0xDA;v17[7] = 0x72;v17[8] = 0xFE;v17[9] = 0xC9;v17[10] = 0xB7;v16[1] = 0x6A;v16[2] = 0xD1;v16[3] = 0xD2;v16[4] = 0x4E;v16[5] = 0x82;v16[6] = 0xDA;v16[7] = 0x72;v16[8] = 0xFE;v16[9] = 0xC9;v16[10] = 0xB7;v2 = strlen(a2);for ( i = 1; i < 9; ++i )*(&v12 + i) = (a1 >> i) & 1;v4 = 0;v15 = 1;if ( v2 <= 0 ){
LABEL_14:v7 = 1;while ( *(&v12 + v7) != 1 ){if ( ++v7 >= 10 ){sub_400240((int)v16, Text);goto LABEL_18;}}}else{while ( 1 ){v18 = a2[v4];if ( v18 < 48 || v18 > 57 )break;v5 = ((a1 >> (v4 % 31)) % 0xA + v18 - 48) % 0xA;if ( v5 == 1 ){LOBYTE(v13) = v13 ^ 1;}else{if ( *(&v11 + v5) != 1 )break;v6 = 1;if ( (int)(v5 - 2) >= 1 ){while ( *(&v12 + v6) != 1 ){if ( ++v6 > (int)(v5 - 2) )goto LABEL_12;}break;}
LABEL_12:*(&v12 + v5) ^= 1u;}if ( ++v4 >= v2 )goto LABEL_14;}}sub_400240((int)v17, Text);
LABEL_18:MessageBoxA(0, Text, Caption, 0);return 1;
}
开始的地方对v16和v17进行了异或解密,v16解密后为OK!!,v17为Fail!
整体逻辑看,需要v7>=10,才是正确的注册码。而V2不可能小于等于0,我们先看else里面的逻辑。
1、检查输入的注册码是否为数字,如果不是数字直接报错。
2、之前计算的v4通过右移0-31位后取个位数(右移的值是注册码对应字符的位置与31取模)+注册码对应位置的数字值
3、2计算的值取个位数。
4、
4-1、如果3的值为1,则v13^1
4-2、如果v5对应的位置值不为1,报错。
4-3、v5-2>=1,遍历前面是否全为0,如果全为0,则v5对应位置的值^1;若不全为0,则报错
4-4、v5-2<1,则v5对应位置^1
上面的逻辑看似没什么规律,x32dbg跟里几步发现,和我高中玩的九连环很像。
九连环的特点就是,第一个环不受限制,如果你想挂上或者拆下某个其它位置的环,必须保正这个环的前一个环是挂上的,且出了前一个环之外,前面没有其它的环挂上,后面的环是否挂上不影响。
根据这个规律可以简单写一个九连环的挂/摘逻辑
unsigned char valueList[4096] = { 0 };
int cnt = 0;
void down(char* nine, int len);
void up(char* nine, int idx)
{if (idx == 0){if (nine[idx] == '0'){nine[idx] = '1';valueList[cnt++] = idx + 1;printf("%s\n", nine);}return;}if (nine[idx - 1] == '0'){up(nine, idx - 1);}if (idx - 2 >= 0){down(nine, idx - 1);}nine[idx] = '1';valueList[cnt++] = idx + 1;printf("%s\n", nine);
}void down(char* nine, int len)
{for (int i = len - 1;i >= 0;i--){if (nine[i] == '1'){if (i == 0){nine[i] = '0';valueList[cnt++] = i + 1;printf("%s\n", nine);return;}if (nine[i - 1] == '1'){nine[i] = '0';valueList[cnt++] = i + 1;printf("%s\n", nine);}else{up(nine, i - 1);if (i - 2 >= 0){down(nine, i - 1);}nine[i] = '0';valueList[cnt++] = i + 1;printf("%s\n", nine);};}}}
根据题目生成9连环的初始状态,调用上面的函数,可以推出注册码,完整代码如下:
unsigned char valueList[4096] = { 0 };
int cnt = 0;
void down(char* nine, int len);
void up(char* nine, int idx)
{if (idx == 0){if (nine[idx] == '0'){nine[idx] = '1';valueList[cnt++] = idx + 1;printf("%s\n", nine);}return;}if (nine[idx - 1] == '0'){up(nine, idx - 1);}if (idx - 2 >= 0){down(nine, idx - 1);}nine[idx] = '1';valueList[cnt++] = idx + 1;printf("%s\n", nine);
}void down(char* nine, int len)
{for (int i = len - 1;i >= 0;i--){if (nine[i] == '1'){if (i == 0){nine[i] = '0';valueList[cnt++] = i + 1;printf("%s\n", nine);return;}if (nine[i - 1] == '1'){nine[i] = '0';valueList[cnt++] = i + 1;printf("%s\n", nine);}else{up(nine, i - 1);if (i - 2 >= 0){down(nine, i - 1);}nine[i] = '0';valueList[cnt++] = i + 1;printf("%s\n", nine);};}}}
int main(void)
{unsigned int v4 = 0x13572468;char userName[] = "KCTF";for(int i = 0; i < 4; i++){v4 = ((int)(57807475 * (v4 + userName[i]) + 610800471) >> 7) | ((57807475 * (v4 + userName[i])+ 610800471) << 25);}printf("%u\n", v4);char Nine[] = "000100001";down(Nine, 9);int idxList[31] = { 0 };char flag[4096] = { 0 };for (int i = 0;i < 31;i++){idxList[i] = (v4 >> i) % 10;}for (int i = 0;i < cnt;i++){if (valueList[i] < idxList[i % 31]){flag[i] = valueList[i] + 10 - idxList[i % 31] + 48;}else{flag[i] = valueList[i] - idxList[i % 31] + 48;}}printf("%s\n", flag);return 0;}
运行结果