关于“完全”破解99宿舍客户端(找回四六级准考证号)的历程与心得

破解这个程序始于2015年8月27日,那时纯粹是为了“练手”,四六级每年两次,所以客户端也会发布两次,每次发布都会增加软件限制,所以每次都要花更多的时间折腾这玩意儿。

首先自己得熟悉整个软件的使用流程及原理,这样才会有破解思路。这个软件首先是取出电脑的Mac地址然后拼接到Post参数(这个在OllyDebug(反汇编工具)或是在WireShark(抓包)软件里面都可以看到)里面,再发送给服务器,服务器判断你是否查询成功,成功则记录你的Mac,下次查询的时候它就会和记录的Mac地址进行比较,从而达到限制。

第一个版本(2015年8月27日)

我的破解流程当然首先是查壳,拖入PEID结果发现木有加壳而且是用MFC写的;接下来则是用OllyDebug进行反汇编调试了,找到Mac地址相关的汇编指令,修改即可。我在吾爱破解论坛发布了破解方法:链接http://www.52pojie.cn/thread-406156-1-1.html

第二个版本(2016年3月1日 

破解方法与第一个版本一致,但是官方会提示版本更新,所以需要破解掉更新限制,用OllyDebug打开客户端Nop掉0x40370f-0x403714的地址。链接:http://www.52pojie.cn/thread-470798-1-1.html

第三个版本(2016年8月)

这个版本限制增多了,除了限制Mac地址还增加了30秒限制,以及IP限制,30秒限制还好,修改一个跳转就好了,而IP限制则需要花费比较大的代价去破解。我的初次尝试是这样的,自己构造Post参数,设置代理,然后发送到它们的服务器,可无奈Post参数被加密了,逼不得已只能反汇编找到加密函数,然后把加密函数的汇编代码抠出来放在C语言里面编译,抠了很久发现调用关系比较复杂就放弃了这种做法。想了很久,又采用了DLL注入的方法,原理是客户端发送加密之后的Post参数给服务器的时候必定使用了send函数,我想Hook掉这个函数让它跳到我的DLL里面执行我的函数,那么我就可以设置代理访问了,基础思路就是这些,以下是代码,编译成DLL后用远程注入器注入到客户端即可。

#include <Windows.h>
#include <winsock.h>
#include <stdio.h>
#include <time.h>

#pragma comment(lib,"ws2_32.lib")

const char chs[] = {
 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};

BYTE jmpMySend[5] = {0xE9};
BYTE oldSend[5] = {0};
DWORD msend;
DWORD pSend;

BYTE jmpMyMac[5] = {0xE9};
DWORD changeMacAddr;
DWORD jmpAddrOfMac = 0x0040CDB6;
DWORD getMacAddr;

int curLine = 0;

bool DEBUG = true;
char *logFile = "log.txt";

void log(char *fileName, char *msg)
{
 FILE *fp = fopen(fileName, "a+");

 char tmp[4096] = {0};

 sprintf(tmp, "\n%s", msg);

 fputs(tmp, fp);

 fclose(fp);
}

int gbkToUtf8(unsigned char * lpGBKStr,unsigned char * lpUTF8Str,int nUTF8StrLen)
{
 wchar_t * lpUnicodeStr = NULL;
 int nRetLen = 0;

 if(!lpGBKStr) 
 return 0;

 nRetLen = MultiByteToWideChar(CP_ACP,0,(char *)lpGBKStr,-1,NULL,NULL); 
 lpUnicodeStr = new WCHAR[nRetLen + 1]; 
 nRetLen = MultiByteToWideChar(CP_ACP,0,(char *)lpGBKStr,-1,lpUnicodeStr,nRetLen);
 if(!nRetLen)
 return 0;

 nRetLen = WideCharToMultiByte(CP_UTF8,0,lpUnicodeStr,-1,NULL,0,NULL,NULL);
 
 if(!lpUTF8Str)
 {
 if(lpUnicodeStr)
 delete []lpUnicodeStr;
 return nRetLen;
 }
 
 if(nUTF8StrLen < nRetLen)
 {
 if(lpUnicodeStr)
 delete []lpUnicodeStr;
 return 0;
 }

 nRetLen = WideCharToMultiByte(CP_UTF8,0,lpUnicodeStr,-1,(char *)lpUTF8Str,nUTF8StrLen,NULL,NULL);
 
 if(lpUnicodeStr)
 delete []lpUnicodeStr;
 
 return nRetLen;
}

char* rtrim(char *target)
{
 int end = strlen(target) - 1;
 
 if (target[end] == '\n') {
 target[end] = '\0';
 }

 return target;
}

void strrpl(char* strSrc, char* strFind, char* strReplace)
{
 while (*strSrc != '\0')
 {
 if (*strSrc == *strFind)
 {
 if (strncmp(strSrc, strFind, strlen(strFind)) == 0)
 {
 int i = strlen(strFind);
 int j = strlen(strReplace);
 char* q = strSrc+i;
 char* p = q;
 char* repl = strReplace;
 int lastLen = 0;
 while (*q++ != '\0')
 lastLen++;
 char* temp = new char[lastLen+1];
 for (int k = 0; k < lastLen; k++) { *(temp+k) = *(p+k); } *(temp+lastLen) = '\0'; while (*repl != '\0') { *strSrc++ = *repl++; } p = strSrc; char* pTemp = temp; while (*pTemp != '\0') { *p++ = *pTemp++; } delete temp; *p = '\0'; } else strSrc++; } else strSrc++; } } //base64编码 void toBase64(unsigned char *out, const unsigned char *in, int inlen) { const char base64digits[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; for (; inlen >= 3; inlen -= 3) 
 { 
 *out++ = base64digits[in[0] >> 2]; 
 *out++ = base64digits[((in[0] << 4) & 0x30) | (in[1] >> 4)]; 
 *out++ = base64digits[((in[1] << 2) & 0x3c) | (in[2] >> 6)]; 
 *out++ = base64digits[in[2] & 0x3f]; 
 in += 3; 
 } 
 if (inlen > 0) 
 { 
 unsigned char fragment; 
 *out++ = base64digits[in[0] >> 2]; 
 fragment = (in[0] << 4) & 0x30; if (inlen > 1) 
 fragment |= in[1] >> 4; 
 *out++ = base64digits[fragment]; 
 *out++ = (inlen < 2) ? '=' : base64digits[(in[1] << 2) & 0x3c]; 
 *out++ = '='; 
 } 
 *out = '\0'; 
}

char getCh()
{
 return chs[rand()%16];
}

void __stdcall getMac(char *target)
{
 srand((unsigned int)time(0));
 for (int i=0; i<17; i++) { switch (i) { case 2: case 5: case 8: case 11: case 14: target[i] = '-'; break; default: target[i] = getCh(); } } if (DEBUG) { // log(logFile, target); //输出mac到日志 } } void __declspec(naked) changeMac() { __asm { push esi; call getMacAddr; retn; } } void hookMac() { WriteProcessMemory((LPVOID)-1, (LPVOID)jmpAddrOfMac, (LPVOID)jmpMyMac, 5, 0); } void initMac() { changeMacAddr = (DWORD)changeMac; getMacAddr = (DWORD)getMac; DWORD jmpAddr = changeMacAddr - jmpAddrOfMac - 5; //jmp near 自定义函数-jmp所在地址-jmp的指令长度 memcpy(jmpMyMac+1, &jmpAddr, 4); } void showMessageNumber(int number) { char buffer[1024]; ZeroMemory(buffer, 1024); sprintf(buffer, "%d", number); MessageBox(0, buffer, "", 0); } void showMessageText(char *text) { MessageBox(0, text, "", 0); } void hook() { WriteProcessMemory((LPVOID)-1, (LPVOID)pSend, jmpMySend, 5, 0); } void unhook() { WriteProcessMemory((LPVOID)-1, (LPVOID)pSend, oldSend, 5, 0); } bool advanceConnect(SOCKET s, char *ip, int port , int time) { sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = inet_addr(ip); serverAddr.sin_port = htons(port); //非阻塞模式:1 阻塞:0(默认) u_long mode = 1; ioctlsocket(s, FIONBIO, &mode); //得到connect结果 bool conResult = false; if (connect(s, (sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) { timeval tm; tm.tv_sec = time; tm.tv_usec = 0; fd_set set; FD_ZERO(&set); FD_SET(s, &set); if (select(s+1, 0, &set, 0, &tm) > 0) {
 char error = -1;
 int len = 4;
 getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len);
 
 if (!error) conResult = true;
 else conResult = false;
 
 } else conResult = false; 
 
 } else conResult = true; 

 mode = 0;
 ioctlsocket(s, FIONBIO, &mode);
 
 return conResult;
}

int getLine(char *fileName, int line, char *target)
{
 int curLine = 0, result = 0;
 FILE *fp = fopen(fileName, "r");

 while(!feof(fp)) {

 char buffer[100] = {0};
 fgets(buffer, 100, fp);

 if (curLine == line) {
 memcpy(target, buffer, 100);
 result = 1;
 break;
 }

 curLine++;
 }

 fclose(fp);
 return result;
}

int substr(char *src, int pos, char *target)
{
 for (int i=0, j=0; i<strlen(src); i++) { if (i>pos) {
 memcpy(target+j, src+i, 1);
 j++;
 }
 }
 return 1;
}

int getIp(char *src, char *target)
{
 char ip[17] = {0};
 memcpy(ip, src, 16);
 char *tmp = strtok(ip, ":");
 tmp[strlen(tmp)] = '\0';
 memcpy(target, tmp, strlen(tmp));
 return 1;
}

int getPort(char *src, char *target)
{
 char ip[17] = {0};
 getIp(src, ip);
 
 substr(src, strlen(ip), target);
 return 1;
}

int __stdcall mysend(SOCKET s, char *data, int len, int flag) 
{
 //恢复现场
 unhook();

 closesocket(s);
 //设置代理访问
 s = socket(AF_INET, SOCK_STREAM, 0);
 if (s < 0) {

 if (DEBUG) {
 log(logFile, "初始化socket失败...");
 }

 hook();
 return 0;
 }

 if (DEBUG) {
 log(logFile, "初始化socket成功...");
 }
 
 //获取代理服务器ip和端口
 char ipPort[100] = {0};
 if (!getLine("ip.txt", curLine, ipPort)) {
 curLine = 0;
 getLine("ip.txt", curLine, ipPort);
 }

 if (DEBUG) {
 char msg[1024] = {0};
 sprintf(msg, "读取IP文件成功, 地址为:%s", rtrim(ipPort));
 log(logFile, msg);
 }
 
 char ip[17] = {0};
 getIp(ipPort, ip);
 char port[6] = {0};
 getPort(ipPort, port);
 curLine++;

 if (DEBUG) {
 char msg[1024] = {0};
 sprintf(msg, "拆分IP端口, IP为:%s 端口:%s", ip, port);
 log(logFile, msg);
 }

 sockaddr_in proxySever;
 proxySever.sin_family = AF_INET;
 proxySever.sin_addr.s_addr = inet_addr(ip);
 proxySever.sin_port = htons(atoi(port));

 //连接代理服务器
 int con = connect(s, (sockaddr*)&proxySever, sizeof(proxySever));
 if (con < 0) {

 if (DEBUG) {
 log(logFile, "连接代理服务器失败...");
 log(logFile, "-------------------------------------------------------------");
 }

 hook();
 return 0;
 }

 if (DEBUG) {
 log(logFile, "连接代理服务器成功...");
 }

 //代理服务器验证用户
 const char *user = "domain\\xxx"; 
 const char *psw = "xxx";
 
 char postData[10240+1] = {0};
 char base64Data[10240+1] = {0};

 sprintf(postData, "%s:%s", user, psw);

 toBase64((unsigned char*)base64Data, (unsigned char*)postData, strlen(postData));

 sprintf(postData, "CONNECT %s HTTP/1.1\r\nAccept: */*\r\nContent-Type: text/html\r\nHost: %s\r\nProxy-Connection: close\r\nProxy-Authorization: Basic %s\r\nContent-Length: 0\r\n\r\n", 
 "http://find.cet.99sushe.com/search", "find.cet.99sushe.com", base64Data); //http://find.cet.99sushe.com/search

 if (DEBUG) {
 log(logFile, postData);
 }

 int sret = send(s, postData, strlen(postData), 0);
 if (DEBUG) {
 char msg[1024] = {0};
 sprintf(msg, "send返回值:%d", sret);
 log(logFile, msg);
 }

 int sendError = WSAGetLastError();
 if (DEBUG) {
 char msg[1024] = {0};
 sprintf(msg, "send代理服务器返回值:%d", sendError);
 log(logFile, msg);
 }

 memset(postData, 0, sizeof(postData));

 int rret = recv(s, postData, sizeof(postData), 0);
 if (DEBUG) {
 char msg[1024] = {0};
 sprintf(msg, "recv返回值:%d", rret);
 log(logFile, msg);
 }

 int recvError = WSAGetLastError();
 if (DEBUG) {
 char msg[1024] = {0};
 sprintf(msg, "recv代理服务器返回值:%d", recvError);
 log(logFile, msg);
 }
 
 if (DEBUG) {
 char msg[1024] = {0};
 sprintf(msg, "代理服务器返回值:%s", postData);
 log(logFile, msg);
 }

 //访问真正网址
 strrpl(data, "/search", "http://find.cet.99sushe.com/search");
 strrpl(data, "keep-alive\r\n", "close\r\nProxy-Connection: close\r\n");
 
 if (DEBUG) {
 log(logFile, data);
 }

 int result = send(s, data, strlen(data), 0);

 if (DEBUG) {
 log(logFile, "POST结束...");
 log(logFile, "-------------------------------------------------------------");
 }
 
 hook();
 return result;
}

void init()
{
 msend = DWORD(mysend), pSend = (DWORD)GetProcAddress(GetModuleHandle("ws2_32.dll"), "send");
 //先保存现场
 ReadProcessMemory((LPVOID)-1, (LPVOID)pSend, oldSend, 5, 0);
 msend = msend - pSend - 5; //jmp near 自定义函数-jmp所在地址-jmp的指令长度
 memcpy(jmpMySend+1, &msend, 4);
}

BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID lpReserved)
{
 switch (reasonForCall)
 {
 case DLL_PROCESS_ATTACH:
 
 init();
 hook(); 

 initMac();
 hookMac();
 
 break;

 case DLL_PROCESS_DETACH:
 break;

 case DLL_THREAD_ATTACH:
 break;

 case DLL_THREAD_DETACH:
 break;
 }

 return true;
}

 

 

 

尹同学

作者: 尹同学

热爱计算机技术、热爱生活!

发表评论

邮箱地址不会被公开。 必填项已用*标注