1. 逆向分析过程
先上手把玩一下, 从外观上看感觉和Chafe.1.exe差不了多少, 还是那个界面
找到RegisterClassEx从而找到其对应的窗口过程
找到对应的WM_COMMAND分支
首先其修改了代码中的4个字节, 将其修改成0x00584554, 然后通过GetDlgItemInt获取了serial值
这里其实原本就是这个值, 只不过作者为了确保是0x00584554, 再次写入
获取了输入的serial值后, 将其压入栈内, 接着获取输入的name值, 将name进行hash运算, 最后获取了一个DWORD值, 这里可以看出有Chafe.2.exe的痕迹。其会将name遍历并每次取4个字节与0x58455443进行16次异或操作后, 得到一个hash值。
接下来他从栈内获取了之前输入的serial值, 并与上一步生成的hash值相加生成了hashX。将hashX与FixCodePlace(也就是直接被修复成0x54, 0x45, 0x58, 0x00的位置, 即0x4012D9)进行异或操作。之后获取了hashX的高位WORD部分并将其与FixCodePlace的低位WORD进行相减。
来看一下FixCodePlace到底在哪:
接着作者把从窗口过程函数开始的248个字节(没错, 是代码段内的代码), 每次以4个字节为步长, 一共执行3E次, 将其与0不断的做异或操作, 最终生成的一个值FinalX与0xAFFCCFFB进行比较, 如果相同那就代表是最终的serial key
注意, 这里从从窗口过程函数开始的248个字节中包含了那个FixCodePlace的位置, 因为这里本身就是窗口过程函数, 也就是说这个最终的值FinalX是会随着FixCodePlace的值变动而不停变化的。
整个算法非常清晰, 下面来写注册机
2. 编写注册机
这个注册机的编写重点在于如何倒推回去。首先可以确定的是我们必须要得到FixCodePlace这个FinalX到底是什么, 由于这个FinalX是由窗口过程函数的前248个字节不停的异或取得的。并且异或是具有还原性的, 所以这个值是可以得到的, 首先我们把这窗口过程的248个字节提取出来。并且将FinalX的值全部设置成0(这是为了取消这个值的影响), 进行还原。
接着编写代码将FinalX的值还原出来, 这里解释一下第123条语句
由于我们的计算机使用的是小端序, 所以高位存放在高地址内存, 低位存放在低地址内存
这里计算FinalX的操作是按照4个字节为1组进行异或的。
这样FinalX的值会被截断, 分成了3字节和1字节, 3字节放在低位内存, 而1字节放在高位内存
我们要将其重新组合并放在一个DWORD里面
所以1字节要放在DWORD的最高位也就是首位, 3字节放在DWORD剩下的3个位置
完成这一步后就对其进行逆推即可, 首先计算出通过name获取的hash这里取名为NameHash, 原本的逻辑是:
DWORD NameAddSerial = NameHash + Serial;
DWORD FixCodePlace = 0x00584554;
FixCodePlace ^= NameAddSerial;
DWORD dwHighPartOfNameAddSerial = (NameAddSerial >> 0x10); // 也就是HIWORD(NameAddSerial)
FixCodePlace -= dwHighPartOfnameAddSerial; // 也就是LOWORD(FixCodePlace) -= HIWORD(NameAddSerial)
// 接着计算窗口过程的248字节的hash值获取FinalX
现在我们要把逻辑改一下, 逆推回去最终计算出NameAddSerial这个值, 这个值被计算出来只要再减去NameHash就能求出Serial了
DWORD dwOrginFixCodePlace = 0x00584554;
// 利用异或的还原性, 由于低位值还进行了减法运算, 所以这里先把高位还原
WORD wHighPart = HIWORD(FinalX) ^ HIWORD(dwOrginFixCodePlace);
// 由于有LOWORD(FixCodePlace ) -= HIWORD(NameAddSerial)存在, 所以要加上(因为还原是逆过程)
// 由于NameAddSerial的高位部分我们已经做过还原了也就是wHighPart, 所以可以直接使用
WORD wLowPart = LOWORD(FinalX) + wHighPart;
// 别忘了还有异或还原
wLowPart ^= LOWORD(dwOrginFixCodePlace);
// 然后把这两部分组合到一起, 别忘了减去Serial值, 这就是最终的serial key了
MAKELONG(wLowPart, wHighpart) - NameHash;
最终核心代码如下:
// 237
BYTE data[248] = {
0x55, 0x8B, 0xEC, 0x83, 0xC4, 0xFC, 0x8B, 0x45, 0x0C, 0x83, 0xF8, 0x10, 0x75, 0x0D, 0x6A, 0x00,
0xE8, 0x6B, 0x02, 0x00, 0x00, 0x33, 0xC0, 0xC9, 0xC2, 0x10, 0x00, 0x83, 0xF8, 0x0F, 0x75, 0x0E,
0x8B, 0x45, 0x08, 0xE8, 0x18, 0x01, 0x00, 0x00, 0x33, 0xC0, 0xC9, 0xC2, 0x10, 0x00, 0x83, 0xF8,
0x01, 0x75, 0x06, 0x33, 0xC0, 0xC9, 0xC2, 0x10, 0x00, 0x3D, 0x11, 0x01, 0x00, 0x00, 0x0F, 0x85,
0xE7, 0x00, 0x00, 0x00, 0x8B, 0x45, 0x14, 0x3B, 0x05, 0x60, 0x31, 0x40, 0x00, 0x75, 0x1A, 0x6A,
0x00, 0x68, 0x96, 0x30, 0x40, 0x00, 0x68, 0xA7, 0x30, 0x40, 0x00, 0xFF, 0x75, 0x08, 0xE8, 0x17,
0x02, 0x00, 0x00, 0x33, 0xC0, 0xC9, 0xC2, 0x10, 0x00, 0x3B, 0x05, 0x58, 0x31, 0x40, 0x00, 0x74,
0x0C, 0x3B, 0x05, 0x54, 0x31, 0x40, 0x00, 0x0F, 0x85, 0xAE, 0x00, 0x00, 0x00, 0xC7, 0x05, 0xD9,
0x12, 0x40, 0x00, 0x54, 0x45, 0x58, 0x00, 0x6A, 0x00, 0x8D, 0x45, 0xFC, 0x50, 0x6A, 0x64, 0xFF,
0x35, 0x50, 0x31, 0x40, 0x00, 0xE8, 0xBC, 0x01, 0x00, 0x00, 0x83, 0x7D, 0xFC, 0x00, 0x74, 0x5F,
0x50, 0x6A, 0x14, 0x68, 0x6C, 0x31, 0x40, 0x00, 0xFF, 0x35, 0x54, 0x31, 0x40, 0x00, 0xE8, 0xAF,
0x01, 0x00, 0x00, 0x85, 0xC0, 0x74, 0x48, 0xA1, 0x0B, 0x30, 0x40, 0x00, 0xBB, 0x6C, 0x31, 0x40,
0x00, 0x03, 0x03, 0x43, 0x81, 0xFB, 0x7C, 0x31, 0x40, 0x00, 0x75, 0xF5, 0x5B, 0x03, 0xC3, 0x31,
0x05, 0xD9, 0x12, 0x40, 0x00, 0xC1, 0xE8, 0x10, 0x66, 0x29, 0x05, 0xD9, 0x12, 0x40, 0x00, 0xBE,
0xEC, 0x11, 0x40, 0x00, 0xB9, 0x3E, 0x00, 0x00, 0x00, 0x33, 0xDB, 0xEB, 0x04, 0x00, 0x00, 0x00, /*Fix Code Place*/
0x00, /*lodsd*/ 0xAD, 0x33, 0xD8, 0x49, 0x75, 0xFA, 0x81
};
DWORD CalcLostCode()
{
// 0x54, 0x45, 0x58, 0x00 original
DWORD dwCompareValue = 0xAFFCCFFB;
DWORD dwTimes = 0x3E;
PDWORD pdwCodeBuf = (PDWORD)data;
do
{
dwCompareValue ^= *pdwCodeBuf;
++pdwCodeBuf;
--dwTimes;
} while (dwTimes);
dwCompareValue = (dwCompareValue >> 8) | (dwCompareValue << 24);
return(dwCompareValue);
}
DWORD GetSerialKey(LPSTR lpszName, DWORD dwFinalCode)
{
DWORD dwCipher = 0x58455443;
for (size_t nIdx = 0; nIdx < 0x10; ++nIdx)
{
dwCipher += *(DWORD *)&lpszName[nIdx];
}
DWORD dwOriginalCode = 0x00584554;
WORD wHighPart = HIWORD(dwFinalCode) ^ HIWORD(dwOriginalCode);
WORD wLowPart = (LOWORD(dwFinalCode) + wHighPart) ^ LOWORD(dwOriginalCode);
DWORD dwRes = MAKELONG(wLowPart, wHighPart);
return(MAKELONG(wLowPart, wHighPart) - dwCipher);
}
void CKeyGenDlg::OnBnClickedBtnGen()
{
// TODO: 在此添加控件通知处理程序代码
char szBuf[20] = {0};
int iLen = 0;
CString strName;
// 获取输入的name值
GetDlgItemText(IDC_EDIT_NAME, strName);
iLen = strName.GetLength();
if (strName.IsEmpty())
{
MessageBox("Name can't be empty!");
return;
}
strcpy_s(szBuf, sizeof(szBuf), strName.GetBuffer());
char* pszName = szBuf;
RtlZeroMemory(szBuf + iLen, sizeof(szBuf) - iLen);
DWORD dwVal = CalcLostCode();
DWORD dwSerialKey = GetSerialKey(szBuf, dwVal);
SetDlgItemInt(IDC_EDIT_SERIAL, dwSerialKey, FALSE);
}
(完)