本帖最后由 crazylin 于 2013-7-22 17:13 编辑
【软件名称】: LOLLastHit
【作者邮箱】: crazylin@msn.com
【下载地址】: http://lollasthit.com/
【软件语言】: vc++
【使用工具】: OD IDA VS2010
【操作平台】: xp sp3 win7 64
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教! |
很多人想知道lollasthit(lol 英雄联盟辅助) 是怎么破解的,我就来写下教程 我现在这里讲两种方法: 方法1:用抓包工具分析,很容易这个软件网络验证都是明码发送,而且只要返回一个成功的标志和一个到期时间就验证成功了,所以把正确的包截下来,用本地验证的方式肯定能破解的,如果你们是用易语言而且是用现成模块做本地验证的就不需要看下面的内容了,直接看方法2即可。 原理:hook send函数,使软件链接转向到本地,然后依次返回正确的封包,我汇编比较菜,所以这边介绍一个Hook库,微软的开源平台上有,地址是http://easyhook.codeplex.com/ 优点是他可以hook 64位的程序,这个库本来是供C#来hook api用的,但是底层都是用c++和汇编写的,当然c++也是能调用的,支持ring3和ring0的,当然我们只要用ring3 下的inline hook 就行。 下面看代码(代码比较繁琐,如果不想用这种方法直接跳到方法2):
[C] 纯文本查看 复制代码 typedef int (WINAPI *PFConnect)(
__in SOCKET s,
__in_bcount(namelen) const struct sockaddr FAR * name,
__in int namelen
);
int WINAPI MyConnect(
__in SOCKET s,
__in_bcount(namelen) const struct sockaddr FAR * name,
__in int namelen
);
//定义一些变量
PFConnect realpfconnect = NULL;
TRACED_HOOK_HANDLE hHook_Connect = new HOOK_TRACE_INFO();
ULONG Hook_Connect_ACLEntries[1] = {0};
//获取源函数地址
void GetFunctionRealAdderss()
{
OutputDebugString("PrepareRealApiEntry()\n");
// 获取真实函数地址
HMODULE hWS2_32 = LoadLibrary("WS2_32.DLL");
if (hWS2_32 == NULL)
{
OutputDebugString("LoadLibrary(\"WS2_32.DLL\") Error\n");
return GetLastError();
}
realpfconnect = (PFConnect)GetProcAddress(hWS2_32, "connect");
if (realpfconnect == NULL)
{
OutputDebugString("GetProcAddress(hWS2_32, \"connect\") Error\n");
return GetLastError();
}
OutputDebugString("GetProcAddress(hWS2_32, \"connect\") OK\n");
}
//安装钩子
void InstallHook()
{
OutputDebugString("InstallHook()\n");
NTSTATUS statue = LhInstallHook(realpfconnect,
MyConnect,
NULL,
hHook_Connect);
if(!SUCCEEDED(statue))
{
OutputDebugString("LhInstallHook Connect failed\n");
return;
}
OutputDebugString("Hook Connect OK\n");
// 一定要调用这个函数,否则注入的钩子无法正常运行。
statue = LhSetInclusiveACL(Hook_Connect_ACLEntries, 1, hHook_Connect);
if(statue >=0)
OutputDebugString("Set Connect On\n");
}
//卸载钩子
void UnInstallHook()
{
OutputDebugString("UnInstallHook()\n");
LhUninstallAllHooks();
LhUninstallHook(hHook_Connect);
delete hHook_Connect;
hHook_Connect = NULL;
LhWaitForPendingRemovals();
}
//关键函数
int WINAPI MyConnect(
__in SOCKET s,
__in_bcount(namelen) const struct sockaddr FAR * name,
__in int namelen)
{
//定义IP和端口
sockaddr_in addr;
WSAStringToAddress("127.0.0.1:9898",AF_INET, NULL, (LPSOCKADDR)&addr, sizeof(addr));
//将定义好的IP和端口拷贝到原地址
memcpy((void*)name,&addr,namelen);
//然后连接
return connect(s,name,namelen);
}
//定义TCP服务器,当然这里谢了一个很简单的,能处理软件的封包就可以了,大家用易语言有本地验证模块的话可以略过,我不懂易语言…所以这里用c++写了,网络协议都是基于socket的,TCP协议是基于socket的 ,http协议是基于TCP的
int StartTcpServer()
{
WSADATA wsd; //WSADATA变量
SOCKET sServer; //服务器套接字
SOCKADDR_IN addrServ; //服务器地址
int retVal;
//初始化套结字动态库
if (WSAStartup(MAKEWORD(2,2), &wsd) != 0)
{
OutputDebugString("WSAStartup failed!");
return 1;
}
//创建套接字
sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(INVALID_SOCKET == sServer)
{
OutputDebugString("socket failed!");
WSACleanup();//释放套接字资源;
return -1;
}
//服务器套接字地址
addrServ.sin_family = AF_INET;
addrServ.sin_port = htons(9898);
addrServ.sin_addr.s_addr = INADDR_ANY;
//绑定套接字
retVal = bind(sServer, (LPSOCKADDR)&addrServ, sizeof(SOCKADDR_IN));
if(SOCKET_ERROR == retVal)
{
OutputDebugString("bind failed!");
closesocket(sServer); //关闭套接字
WSACleanup(); //释放套接字资源;
return -1;
}
//开始监听
retVal = listen(sServer, 10);
if(SOCKET_ERROR == retVal)
{
OutputDebugString("listen failed!");
closesocket(sServer); //关闭套接字
WSACleanup(); //释放套接字资源;
return -1;
}
//为防开主线程卡住,这里另开一个线程开接收客户端
DWORD lpThreadId;
HANDLE hThread = CreateThread(
NULL,
NULL,
thTcpServer,
(LPVOID)sServer,
NULL,
&lpThreadId
);
CloseHandle(hThread);
}
//接收客户端线程
DWORD WINAPI thTcpServer(LPVOID lparam)
{
SOCKET sClient; //客户端套接字
int retVal; //返回值
SOCKET sServer = (SOCKET)lparam;
while(true)
{
//接受客户端请求
sockaddr_in addrClient;
int addrClientlen = sizeof(addrClient);
sClient = accept(sServer,(sockaddr FAR*)&addrClient, &addrClientlen);
if(INVALID_SOCKET == sClient)
{
OutputDebugString("accept failed!");
closesocket(sServer); //关闭套接字
WSACleanup(); //释放套接字资源;
return -1;
}
//当然这里你可以建个队列什么的把客户端的队列保存起来,因为http端连接,一次发送就收完以后就断开了,所以这里我们不保存
DWORD lpThreadId;
HANDLE hThread = CreateThread(
NULL,
NULL,
thProcessDdata,
(LPVOID)sClient,
NULL,
&lpThreadId
);
CloseHandle(hThread);
}
return 0;
}
//处理数据线程
DWORD WINAPI thProcessDdata(LPVOID lparam)
{
const int BUF_SIZE = 4096;
SOCKET sClient = (SOCKET)lparam;//客户端套接字
char buf[BUF_SIZE]; //接收数据缓冲区
int retVal; //返回值
while(true)
{
if(sClient!=NULL)
{
//接收客户端数据
ZeroMemory(buf, BUF_SIZE);
retVal = recv(sClient, buf, BUF_SIZE, 0);
if (SOCKET_ERROR == retVal)
{
OutputDebugString("recv failed!");
closesocket(sClient); //关闭套接字
WSACleanup(); //释放套接字资源;
return -1;
}
//这里处理数据
//buf是什么封包,你就把对应的封包发回去
//send(sClient,封包,封包长度, 0);
}
}
}
方法2:先查壳,UPX的壳,怎么脱我就不说了,各位搜索一下论坛应该就能找到,我们打开软件帐号密码随便输,点LOGIN(登录),出现INVALID VIP ACCOUNT OR PASSWORD(无效的账户或密码),那好我们就直接搜索字符串,我个人比较喜欢先用IDA分析,有插件可以直接生成C源码,因为不懂汇编嘛。打开IDA搜索字符串 如下图:
第一个在函数sub_403370里面,当然是点开第一个进去了
大家仔细看INVALID VIP ACCOUNT OR PASSWORD 附近有很多验证成功和失败的不同字符串,我们发现“"%s IS VALID UNTIL %s"”(XX是有效的直到XX时候) 这个应该就是验证成功的字符串,我们按F5 看看sun_403370这个函数生成的C代码
打开OD,载入软件,ctrl+G 输入00403370 回车,来到这里,然后在这个地址下断点,F9运行程序,随便输入帐号密码点登录,然后断下来了,看下图
我们看到EDI 点数据窗口跟随,看左下角,看到是完整的登录返回封包的数据,但根据经验判断,这个应该是主程序接收tcp数据的缓冲区,我们再找其他地址(数据窗口跟随),最后发现ECI地址上的数据比较可疑
看上图,我们可以确定这些是存放验证信息的地址,然后我们要做的就是修改这个内存中的数据,怎么修改? 先讲第一种方法,先找一个没有数据的代码段,我找了一个0x004A6913,然后0x00403370处修改代码jmp 0x004A6913(使用了五个字节),0x004A6913处修改如图所示,修改的数据是“4\n9999/11/11 11:11:11\n0”再补回原0x00403370处的五个字节,然后跳回(0x00403370+0x5)
修改完保存,运行软件,随便输入帐号密码,点登录果断登录成功,界面显示XXX is vailID until 9999/11/11 11:11:11 别高兴太早,这软件还有一个定时检测线程,如果检测不通过,会显示LOGIN TO YOUR VIP ACCOUNT TO ACTIVATE THE TOOL,我们超找一下字符串就好了,出来三个,我们没个都下断点,然后登录软件,一会儿,其中一处就断下来了,我们过去看到上面有哥jnz,改成jmp就可以了地址是0x004032EA,然后保存,软件就破解成功了。
但是还有方法2,我不会写汇编,上面写的也是突发奇想写出来的- -。,我来介绍下用hook的方式来解决这个问题。
为了代码简单点,我直接写了,已经验证是可以的 [C] 纯文本查看 复制代码 typedef void (_fastcall *PFSub_3370)(DWORD ecx, DWORD edx,DWORD a1, DWORD a2, DWORD a3);
void _fastcall MySub_3370(DWORD ecx, DWORD edx,DWORD a1, DWORD a2, DWORD a3);
PFSub_3370 realSub_3370 = NULL;
TRACED_HOOK_HANDLE hHook_Sub_3370 = new HOOK_TRACE_INFO();
NTSTATUS statue;
ULONG Hook_Sub_3370_ACLEntries[1] = {0};
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
hModule = GetModuleHandle(NULL);
realSub_3370 = (PFSub_3370)((DWORD)hModule+0x3370);
statue = LhInstallHook(realSub_3370, MySub_3370, NULL, hHook_Sub_3370);
if(!SUCCEEDED(statue))
{
OutputDebugString("LhInstallHook Sub_3370 failed\n");
}
OutputDebugString("Hook Sub_3370 OK\n");
LhSetExclusiveACL(Hook_Sub_3370_ACLEntries, 1, hHook_Sub_3370);
//X掉检测
DWORD checkAddr = ((DWORD)hModule + 0x32EA);
unsigned char checkAddrData[2] ={0xEB,0x7E};
VirtualProtect((LPVOID)checkAddr,0x1000,PAGE_EXECUTE_READWRITE,NULL);
WriteProcessMemory((void*)-1, (LPVOID)checkAddr, checkAddrData, sizeof(checkAddrData), NULL);
}
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
LhUninstallAllHooks();
LhUninstallHook(hHook_Sub_3370);
delete hHook_Sub_3370;
hHook_Sub_3370 = NULL;
LhWaitForPendingRemovals();
break;
}
return TRUE;
}
void _fastcall MySub_3370(DWORD ecx, DWORD edx,DWORD a1, DWORD a2, DWORD a3)
{
char* tmpData = "4\n2999/09/09 09:09:09\n0";
memcpy((void*)(ecx+0xB8),tmpData,strlen(tmpData));
return (*realSub_3370)(ecx,edx,a1,a2,a3);
}
看了@半斤八两的初级教程 “27.Patch机器码” 其实这个就是inline hook的原理 活学活用 我也来实现下
[C] 纯文本查看 复制代码 // dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
DWORD g_dwSrcAddr = 0;
DWORD g_dwJmpAddr = 0;
DWORD g_dwSrcAddrNext = 0;
//32位程序远jmp+地址是5个字节 64是12个字节
unsigned char szJmp[5] = {0xE9};
char* tmpData = "4\n2999/09/09 09:09:09\n0";
void __declspec (naked) PatchData()
{
_asm
{
//不会汇编,只会这样写 - -。
mov byte ptr ds:[ecx+0xB8],0x34
mov byte ptr ds:[ecx+0xB9],0xA
mov byte ptr ds:[ecx+0xBA],0x32
mov byte ptr ds:[ecx+0xBB],0x39
mov byte ptr ds:[ecx+0xBC],0x39
mov byte ptr ds:[ecx+0xBD],0x39
mov byte ptr ds:[ecx+0xBE],0x2F
mov byte ptr ds:[ecx+0xBF],0x31
mov byte ptr ds:[ecx+0xC0],0x31
mov byte ptr ds:[ecx+0xC1],0x2F
mov byte ptr ds:[ecx+0xC2],0x31
mov byte ptr ds:[ecx+0xC3],0x31
mov byte ptr ds:[ecx+0xC4],0x20
mov byte ptr ds:[ecx+0xC5],0x31
mov byte ptr ds:[ecx+0xC6],0x31
mov byte ptr ds:[ecx+0xC7],0x3A
mov byte ptr ds:[ecx+0xC8],0x31
mov byte ptr ds:[ecx+0xC9],0x31
mov byte ptr ds:[ecx+0xCA],0x3A
mov byte ptr ds:[ecx+0xCB],0x31
mov byte ptr ds:[ecx+0xCC],0x31
mov byte ptr ds:[ecx+0xCD],0xA
mov byte ptr ds:[ecx+0xCE],0x30
//还原5个字节
push ebp
mov ebp,esp
push -0x1
//跳回
jmp g_dwSrcAddrNext
}
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
//获取基址
hModule = GetModuleHandle(NULL);
//源地址
g_dwSrcAddr = (DWORD)hModule + 0x3370;
//目标地址
g_dwJmpAddr = (DWORD)PatchData - g_dwSrcAddr - 5;
//跳回地址
g_dwSrcAddrNext = g_dwSrcAddr + 5;
//组合jmp地址 例 jmp 0x0041000
memcpy(&szJmp[1],&g_dwJmpAddr,4);
DWORD dwOldProtect;
VirtualProtect((void*)g_dwSrcAddr,0x1000,PAGE_EXECUTE_READWRITE,&dwOldProtect);
//把jmp跳转赋值到源地址上面
memcpy((void*)g_dwSrcAddr,szJmp,sizeof(szJmp));
//======================
//====X掉检测============
//======================
DWORD checkAddr = ((DWORD)hModule + 0x32EA);
unsigned char checkAddrData[2] ={0xEB,0x7E};
VirtualProtect((LPVOID)checkAddr,0x1000,PAGE_EXECUTE_READWRITE,NULL);
WriteProcessMemory((void*)-1, (LPVOID)checkAddr, checkAddrData, sizeof(checkAddrData), NULL);
}
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
} |