-
-
[原创]不问年少的CrackMe
-
发表于: 2017-5-2 08:19 2332
-
注册码说明:原注册码为bwns@pediy!后来根据要求全改为字母加数字,连空格都去掉了!
现注册码为:BwnsAtPediy2017KX9Ok
注册成功截图:
制作思路:
来源于约瑟夫环的变形处理。将一张码表看成一个圈,取用户输入的字符在码表中的下标。然后从此字符的下一字符处开始移动其下标值/5+5(这样做为了大幅度减少循环次数),然后取对应的字符,对此字符进行高5位低3位移位操作后,取其HEX字符进行验证。
验证方式为对每个字符的ASCII值/4的商,余数比较。(可由此进行注册码的反推。)
其他处理:
1、在Button中进行了特殊处理,使用int3异常开始游戏,必须触发异常,否则就退出了。
2、用TLS对OEP进行异或解密。
3、使用了SMC对显示对话框解码,解码的KEY即为用户输入的字符。此处用了try-catch包裹,故输入不正确的key不会崩溃。
4、StartGame函数也进行了SMC加密,运行时解密,完成后再加密。
5、对EXE加入简单自校验,使用MapFileCheckSum(测试发现此处360会误报木马),
若发现改动,直接退出。
6、SMC均采用IDA直接查看地址后精确处理,无其它标记,制作稍麻烦。
关键代码:
//置换游戏,找到随机数组中的字母后按下标再继续置换。
class CZHGame
{
public:
CZHGame();
~CZHGame();
public:
void StartGame(string strInput);
void DecResult();
protected:
bool IsOK();
string GetHexKey() const;
bool VerifyLength(string strInput);
typedef struct _XY
{
_XY() {}
_XY(charx,chary) {this->x =x;this->y =y; }
charx, y;
}XY, *pXY;
protected:
//密文数组,初始化将会被打乱。
vector<char> m_vPwd;
//玩家输入被置换后的数组
vector<char> m_vKey;
vector<XY> m_vXY;
//用户输入的原始数据(将直接用于SMC加解码)
string m_strInput;
};
void __stdcall ShowResult() noexcept
{
char key[] = {'B','W','N','S'};
char msg[] = {0x15,0x32,0x22,0x3f,0x62,0x33,0x21,0x3d,0x27,0x76,0x74,0x7a,0x42};
int i = 0;
for_each (begin(msg), end(msg), [&](char& c) {c ^= key[i++ % 4]; });
MessageBoxA(NULL, msg,"",MB_ICONINFORMATION);
}
CZHGame::CZHGame()
{
m_vPwd.reserve(64);
for (char i ='a'; i <='z'; i++)
{
m_vPwd.push_back(i);
m_vPwd.push_back(toupper(i));
}
m_vPwd.push_back('@');
for (char i ='0'; i <='9'; i++)
m_vPwd.push_back(i);
m_vPwd.push_back('!');
reverse(begin(m_vPwd), end(m_vPwd));
/*注意此处未初始化随机种子,所以数组中的字符每次是不会变化的*/
for (int i = 0; i < 3; i++)
random_shuffle(begin(m_vPwd), end(m_vPwd));
//字母简单加密处理,全部与0xcc异或
for_each (begin(m_vPwd), end(m_vPwd),[](char& c) {c ^= 0xcc; });
m_vXY.reserve(40);
m_vXY.push_back(XY(2, 2));
m_vXY.push_back(XY(0, 3));
m_vXY.push_back(XY(0, 0));
m_vXY.push_back(XY(2, 3));
m_vXY.push_back(XY(0, 1));
m_vXY.push_back(XY(2, 3));
m_vXY.push_back(XY(2, 0));
m_vXY.push_back(XY(0, 2));
m_vXY.push_back(XY(2, 0));
m_vXY.push_back(XY(2, 2));
m_vXY.push_back(XY(2, 3));
m_vXY.push_back(XY(2, 3));
m_vXY.push_back(XY(1, 0));
m_vXY.push_back(XY(2, 2));
m_vXY.push_back(XY(2, 1));
m_vXY.push_back(XY(2, 3));
m_vXY.push_back(XY(2, 2));
m_vXY.push_back(XY(2, 1));
m_vXY.push_back(XY(2, 3));
m_vXY.push_back(XY(2, 3));
m_vXY.push_back(XY(2, 1));
m_vXY.push_back(XY(0, 3));
m_vXY.push_back(XY(2, 2));
m_vXY.push_back(XY(2, 2));
m_vXY.push_back(XY(2, 2));
m_vXY.push_back(XY(0, 3));
m_vXY.push_back(XY(1, 2));
m_vXY.push_back(XY(2, 3));
m_vXY.push_back(XY(2, 0));
m_vXY.push_back(XY(0, 2));
m_vXY.push_back(XY(2, 2));
m_vXY.push_back(XY(2, 2));
m_vXY.push_back(XY(0, 0));
m_vXY.push_back(XY(0, 2));
m_vXY.push_back(XY(1, 0));
m_vXY.push_back(XY(2, 2));
m_vXY.push_back(XY(2, 3));
m_vXY.push_back(XY(0, 2));
m_vXY.push_back(XY(1, 0));
m_vXY.push_back(XY(0, 3));
}
void CZHGame::StartGame(string strInput)
{
if(! VerifyLength(strInput))return;
//通过长度设定后,先保存输入
m_strInput = strInput;
int nLen = strInput.length();
vector<char> vec(nLen, 0);
for_each (begin(strInput), end(strInput), [](char& c) {c ^= 0xcc; });
for (int i = 0; i < nLen; i++)
{
auto it = find(m_vPwd.begin(), m_vPwd.end(),strInput[i]);
//从码表中查找用户输入的内容
if (it != m_vPwd.end())
{
//得到每个字母/数字相对于码表的下标
vec[i] = distance(m_vPwd.begin(), it);
//若是首位的下标,则将其改为1
vec[i] = (vec[i]== 0 ? 1 : vec[i]);
for(int j = 0; j <=i; j++)
{
int k = 0;
while(1)
{
it++;
if (it == m_vPwd.end())
it = m_vPwd.begin();
k++;
//以vec[i]/5+5为计数终点。
if (k == (vec[i]/ 5) + 5)
{
vec[i] = distance(m_vPwd.begin(), it);
vec[i] = (vec[i]== 0 ? 1 : vec[i]);
break;
}
}
}
vec[i] = m_vPwd[vec[i]];
}
}
//得到vec所指向的码表字符,再进行处理
for_each (begin(vec),end(vec), [](char& c) {
char tmp = c^ 0xcc;
c = ((tmp >>5) | (tmp <<3));
});
m_vKey = vec;
}
bool CZHGame::VerifyLength(string strInput)
{
return (5 <strInput.length() && strInput.length() <= 20);
}
string CZHGame::GetHexKey()const
{
string strTmp(begin(m_vKey), end(m_vKey));
return boost::algorithm::hex(strTmp);
}
bool CZHGame::IsOK()
{
// BwnsAtPediy2017KX9Ok
string strKey = GetHexKey();
int nLen = strKey.length();
XY xy;
vector<char> tmp = {'0','1','2','3','4','5',
'6','7','8','9','A','B','C','D','E','F'};
vector<XY> vec;
vec.reserve(40);
for(int i = 0; i < nLen; i++)
{
auto beg = begin(tmp);
auto it = find(beg, end(tmp), strKey[i]);
char dis = distance(beg, it);
xy.x =dis / 4;
xy.y = dis % 4;
vec.push_back(xy);
}
if (vec.size() != m_vXY.size())returnfalse;
return (0 == memcmp(&m_vXY[0], &vec[0], m_vXY.size()*sizeof(XY)));
}
/*编译后查看EXE中ShowResult函数,代码长度为0x94*/
voidCZHGame::DecResult()
{
#ifdef__SMC__
if(IsOK())
{
void(__stdcall*pShowResult)() = ShowResult;
int i = 0, fnLen = 0x94;
LPVOID pFn = VirtualAlloc(0, 0x1000,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
memcpy_s(pFn,0x1000, pShowResult, fnLen);
PBYTE pMyFn = reinterpret_cast<PBYTE>(pFn);
try
{
/*解码显示结果函数*/
int j = 0;
for(; j < fnLen;)
*pMyFn++^= m_strInput[j++ % m_strInput.length()];
*pMyFn ^=m_strInput[j % m_strInput.length()];
PBYTE pCall =reinterpret_cast<PBYTE>(pFn) + 0x60;
DWORD dwCallAddr = *(DWORD*)(pCall + 1);
/*修正CALL的地址,新地址=原CALL地址+跳转距离新CALL地址-5=原CALL地址+跳转距离-新CALL地址*/
*(DWORD*)(pCall + 1) = dwCallAddr + ((DWORD)pShowResult + 0x60) -reinterpret_cast<DWORD>(pCall);
pCall =reinterpret_cast<PBYTE>(pFn) + 0x8b;
dwCallAddr= *(DWORD*)(pCall + 1);
*(DWORD*)(pCall + 1) = dwCallAddr + ((DWORD)pShowResult + 0x8b) -reinterpret_cast<DWORD>(pCall);
__asmcall pFn;
}
catch(...){}
VirtualFree(pFn, 0x1000,MEM_RELEASE);
}
}
#else
//待加密的文件通过IDA查看对应的函数地址和长度,直接进行加密处理
/*ShowResult函数地址,此处函数地址编译后可能会改变!*/
DWORD fnAddr = 0x11a9c;
int fnLen = 0x94;
fstream fs("e:\\crackme.exe", ios::in | ios::out | ios::binary);
if(fs.is_open())
{
fs.seekg(0,ios::end);
int nLen = (int)fs.tellg();
fs.seekg(0,ios::beg);
shared_ptr<char> pDat(newchar[nLen], default_delete<char[]>());
fs.read(&*pDat,nLen);
PBYTE pCur =(PBYTE)&*pDat + fnAddr;
string pwd ="BwnsAtPediy2017KX9Ok";
int nPwdLen = pwd.length();
for(int i = 0; i <fnLen; i++)
*pCur++ ^=pwd[i % nPwdLen];
// StartGame函数地址,编译后可能会有改变,用IDA查看并修改!
fnAddr = 0x11b30;
fnLen = 0x1aa;
pCur =(PBYTE)&*pDat + fnAddr;
pwd ="PEDIY";
nPwdLen =pwd.length();
for(inti = 0; i <fnLen; i++)
*pCur++ ^=pwd[i % nPwdLen];
PIMAGE_DOS_HEADER pDosHdr =reinterpret_cast<PIMAGE_DOS_HEADER>(&*pDat);
PIMAGE_NT_HEADERS pNtHdr =reinterpret_cast<PIMAGE_NT_HEADERS>(&*pDat+ pDosHdr->e_lfanew);
DWORD dwOEPRVA =pNtHdr->OptionalHeader.AddressOfEntryPoint;
/*VC编译的程序第一个节表就是.text,所以不用再搜索了!*/
PIMAGE_SECTION_HEADER pSecText = IMAGE_FIRST_SECTION(pNtHdr);
//计算OEP的文件偏移
DWORD dwOEP = dwOEPRVA - pSecText->VirtualAddress + pSecText->PointerToRawData;
//PBYTE pBeg = reinterpret_cast<PBYTE>(&*pDat + dwOEP), pEnd =reinterpret_cast<PBYTE>(&*pDat + dwOEP + 200);
dwOEP =reinterpret_cast<DWORD>(&*pDat + dwOEP);
//加密OEP
byte smc[] = { 0xeb,0x74, 0x58, 0xcc, 0xe8, 0x75 };
EncryptTarget(dwOEP,200, smc, 6);
//到头部,准备写入。
fs.seekp(0,ios::beg);
fs.write(&*pDat,nLen);
fs.flush();
fs.close();
DWORD dwOldCheckSum,dwNewCheckSum;
if(CHECKSUM_SUCCESS == MapFileAndCheckSum(L"e:\\crackme.exe", &dwOldCheckSum, &dwNewCheckSum))
{
fstream fs("e:\\crackme.exe", ios::in | ios::out | ios::binary);
if(fs.is_open())
{
fs.seekg(0,ios::end);
intnLen = (int)fs.tellg();
fs.seekg(0,ios::beg);
shared_ptr<char> pDat(newchar[nLen], default_delete<char[]>());
fs.read(&*pDat,nLen);
PIMAGE_DOS_HEADER pDosHdr =reinterpret_cast<PIMAGE_DOS_HEADER>(&*pDat);
PIMAGE_NT_HEADERS pNtHdr =reinterpret_cast<PIMAGE_NT_HEADERS>(&*pDat+ pDosHdr->e_lfanew);
pNtHdr->OptionalHeader.CheckSum= dwNewCheckSum;
//到头部,准备写入。
fs.seekp(0,ios::beg);
fs.write(&*pDat,nLen);
fs.flush();
fs.close();
}
}
MessageBox(NULL,L"加密显示结果函数完成!",L"", MB_ICONINFORMATION);
}
}
#endif
}
在Win7 32位,xp下测试通过!(XP下SetUnhandledExceptionFilter只有一次有效,再次点击就崩溃了,WIN7下无问题,多次点击正常!)
由于OnStart放在CMianDialog中,其又会调用到类中的其它函数,不好处理,所以只能这样了。再说现在大部分应该都是用win7以上了吧。
// 顶级异常处理
LONG WINAPI MyTopExceptionFilter(_EXCEPTION_POINTERS* pept)
{
if (pept->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT)
{
// 此处使用PostMessage/SendMessage在xp下会崩溃,异常未处理到。在WIN7下测试正常。
PostMessage(g_hMainDlg, WM_START_GAME, 0, 0);
// 跳过PostQuitMessage,直接返回
pept->ContextRecord->Eip += 9;
return EXCEPTION_CONTINUE_EXECUTION;
}
// 其它异常不做处理
return EXCEPTION_CONTINUE_SEARCH;
}
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
赞赏
- [原创]九重妖塔设计思路 9244
- [原创]Hello-CTF破解_不问年少 2286
- [原创]剧情式CM——九重妖塔前传III 4279
- [原创]剧情式CM——九重妖塔前传II 3695
- [原创]剧情式CM——九重妖塔前传!!! 5295