讲一下逆向分析在编程中的应用吧
本文适合还没有入门的新朋友上手用的,所以,各位大牛就可以飘过不看它了!游戏的服务器列表更新了,而存服务器信息的XML在游戏的更新服务器上加密保存的,没能力分析它的解密算法,就想写个程序,从内存里读出来,工作量就是那么多,不如索性做成教程,分享给像我一样刚刚入门的朋友,说不定就又能混一篇精华,哈哈……
其实真的不知道该给这个破烂起个什么名字了,怎么看怎么感觉在教人做外挂¥%#¥%
不多废话,进入主题,我现在要找服务器列表,当然就要先从内存里搜一个服务器的名字(别的信息不知道,只能搜名字了!)
在OD里的数据区中看一下这几个地址,确定,第一个地址也就是:01329F6C
就是我们像要的地址了,至于为什么,你看一下就可以知道了(因为第一个在一个结构体数组里,其他的都是独立的,所以第一个是!)。
好了,在OD里,来到01329F6C这个地址,在第一个DWORD上下内存访问断点:
来到这里:
现在我们需要知道的是,EAX的内容是从哪里来的,所以向上看:0040CA3B|> \8BC2 |mov eax, edx ;1是EDX给的EAX,再向上:0040CA17|.8B83 18010000 |mov eax, dword ptr
0040CA1D|.8B34A8 |mov esi, dword ptr
0040CA20|.8D04A8 |lea eax, dword ptr
0040CA23|.8D57 04 |lea edx, dword ptr ;1
0040CA26|.85D2 |test edx, edx
0040CA28|.C746 3C 01000>|mov dword ptr , 1
0040CA2F|.8D9E 8C000000|lea ebx, dword ptr
0040CA35|.75 04 |jnz short 0040CA3B
这个流程应该是比较简单的,我就从头分析一下:0040C8C0/$6A FF push -1
0040C8C2|.68 08184600 push 00461808 ;咐|g; SE 处理程序安装
0040C8C7|.64:A1 0000000>mov eax, dword ptr fs:
0040C8CD|.50 push eax
0040C8CE|.64:8925 00000>mov dword ptr fs:, esp
0040C8D5|.83EC 24 sub esp, 24
0040C8D8|.53 push ebx
0040C8D9|.8BD9 mov ebx, ecx
0040C8DB|.8B83 78010000 mov eax, dword ptr ;取出返回值,如果是-1就不做处理!
0040C8E1|.83F8 FF cmp eax, -1
0040C8E4|.895C24 08 mov dword ptr , ebx
0040C8E8|.0F84 89000000 je 0040C977
0040C8EE|.55 push ebp
0040C8EF|.57 push edi
0040C8F0|.8D4C24 2C lea ecx, dword ptr
0040C8F4|.51 push ecx
0040C8F5|.8D5424 2C lea edx, dword ptr
0040C8F9|.52 push edx
0040C8FA|.50 push eax
0040C8FB|.8D8B 58010000 lea ecx, dword ptr
0040C901|.E8 0ACAFFFF call 00409310
0040C906|.33ED xor ebp, ebp
0040C908|.3BC5 cmp eax, ebp ;如果函数返回值是0,就走人~~~
0040C90A|.74 69 je short 0040C975
0040C90C|.8B78 04 mov edi, dword ptr
0040C90F|.3BFD cmp edi, ebp
0040C911|.74 62 je short 0040C975
0040C913|.56 push esi ;ESI和EBX的值都是004812A8
0040C914|.8BB3 DC000000 mov esi, dword ptr
跟进ESI看下:
011A21E80D F0 AD BA 70 5C 32 01 58 7D 32 01 2C 7F 32 01.瓠簆\2 X}2 ,2
011A21F80D F0 AD BA 88 31 32 01 A8 31 32 01 A8 31 32 01.瓠簣12 ?2 ?2
011A22080D F0 AD BA 28 01 30 01 42 01 30 01 42 01 30 01.瓠? 0 B 0 B 0
011A22180D F0 AD BA 28 23 1A 01 00 00 00 00 0D F0 AD BA.瓠?#.....瓠
011A222880 23 1A 01 00 00 00 00 00 F0 AD BA 0D F0 AD BA
继续:0040C91A|.8B46 04 mov eax, dword ptr ;取出了各个大区的序号•~~
0040C91D|.3BC5 cmp eax, ebp
到这里可以知道,大区的地址指针+偏移的形式应该在:+4]
看下EAX的内容:01325C7001 00 00 00 B9 E3 B6 AB C7 F8 00 00 58 F8 17 04 ...广东区..X?
01325C8000 00 00 00 58 F8 17 04 EA 76 94 7C 00 00 1A 01....X? 陃攟..
01325C9064 77 94 7C D0 31 30 01 00 00 1A 01 D8 31 30 01dw攟?0 ..?0
01325CA000 00 00 00 64 77 94 7C 50 32 30 01 00 00 1A 01....dw攟P20 ..
01325CB098 83 1A 01 C0 30 30 01 00 00 1A 01 00 00 00 00槂?0 ......
01325CC006 00 00 00 B0 4E 31 01 0F 00 00 00 A8 4E 31 01 ...癗1...∟1
01325CD0A8 4E 31 01 30 18 00 00 78 01 1A 01 28 03 1A 01∟1 0 ..x (
01325CE001 00 00 00 40 05 1A 01 38 00 00 00 48 32 30 01 ...@ 8...H20
01325CF078 01 1A 01 30 2E 38 32 30 34 2E 30 36 00 00 00x 0.8204.06...
这个就是服务器大区的大概的结构了,里面各个数据的含义还不是很清楚,不过不用着急,通过代码,我们都会弄明白的!
继续回到代码中分析:0040C91F|.C74424 1C FFF> mov dword ptr , -1
0040C927|.75 04 jnz short 0040C92D
0040C929|.33C0 xor eax, eax
0040C92B|.EB 18 jmp short 0040C945
0040C92D|>8B4E 08 mov ecx, dword ptr
看一下ECX的内容吧:0132D0480D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA.瓠?瓠?瓠?瓠
0132D0580D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA.瓠?瓠?瓠?瓠
0132D0680D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA.瓠?瓠?瓠?瓠
0132D0780D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA.瓠?瓠?瓠?瓠
都是同一个数字,暂且不多考虑,估计接下来应该是遍历大区的信息啊,想下,我们在写程序的时候,循环需要什么来着,对,就是循环多少次啊,接下来的代码就是计算有多少个大区,以控制循环多少次了!0040C930|.2BC8 sub ecx, eax
0040C932|.B8 8DC0088C mov eax, 8C08C08D
0040C937|.F7E9 imul ecx
0040C939|.03D1 add edx, ecx
0040C93B|.C1FA 08 sar edx, 8
0040C93E|.8BC2 mov eax, edx
0040C940|.C1E8 1F shr eax, 1F
0040C943|.03C2 add eax, edx ;这里就是大区的数量了OD显示的是0x12,即有0x12个大区
好了,到这里,EAX里存的就是有多少个大区了
继续看0040C945|>33C9 xor ecx, ecx
0040C947|.3BC5 cmp eax, ebp
0040C949|.76 29 jbe short 0040C974
0040C94B|.8B97 84000000 mov edx, dword ptr ;取出我们选择的大区的序号
0040C951|.8B76 04 mov esi, dword ptr ;取出大区的首个元素:序号
下面开始循环,遍历各个大区,我们取大区的信息也主要就依靠这个循环了~~~0040C954|.33FF xor edi, edi
0040C956|.897424 24 mov dword ptr , esi
0040C95A|.897C24 10 mov dword ptr , edi
0040C95E|.8BFF mov edi, edi
0040C960|>391437 / cmp dword ptr , edx ;循环比较,看看取那个区下的服务器
0040C963|.74 26 |je short 0040C98B ;只要是我们选的区,拿就跳走,不是就继续循环
0040C965|.41 | inc ecx
0040C966|.81C7 D4010000 | add edi, 1D4 ;在内存里,每个大区信息的结构体大小为0x1D4
0040C96C|.3BC8 | cmp ecx, eax
0040C96E|.^ 72 F0 \ jb short 0040C960
0040C970|.897C24 10 mov dword ptr , edi
根据这个循环,我们可以很轻松的写出遍历所有大区的代码了,不过不用太着急,在这里标记一下,因为,这个大区的结构中很多的数据成员,我们都不清楚,现在只知道第一个成员是大区的ID,第二个是名字,而且结构的大小要凑够0x1D4,其他的都还未知,继续分析,或许等下就清晰了,嘿嘿!
继续看代码:0040C98B|> \8B8437 C80100> mov eax, dword ptr ;看到了吧,大区数构中偏移第1C8的成员是个最小值
0040C992|.3BC5 cmp eax, ebp
0040C994|.897C24 10 mov dword ptr , edi
0040C998|.75 06 jnz short 0040C9A0
0040C99A|.896C24 20 mov dword ptr , ebp
0040C99E|.EB 1E jmp short 0040C9BE
0040C9A0|>8B8C37 CC0100> mov ecx, dword ptr ;大区数构中偏移第1CC的成员是个最大值
0040C9A7|.2BC8 sub ecx, eax ;同算大区的数量一样,计算服务器的数量
0040C9A9|.B8 E1830F3E mov eax, 3E0F83E1
0040C9AE|.F7E9 imul ecx ;3E0F83E1*(最大值-最小值)
0040C9B0|.C1FA 06 sar edx, 6 ;取其积的高32位值右移6位
0040C9B3|.8BC2 mov eax, edx
0040C9B5|.C1E8 1F shr eax, 1F ;再移动0x1F位
0040C9B8|.03C2 add eax, edx ;取他们的和OD显示是4,即4个服务器,循环4次
0040C9BA|.894424 20 mov dword ptr , eax ;把结果保存一下
哈哈,结构越来越清晰,思路越来越明确,大家猜下,接下来是要干什么了啊~~~~0040C9BE|> \8B83 1C010000 mov eax, dword ptr ; 还不清楚它的作用,OD显示是6,先不管它
0040C9C4|.33D2 xor edx, edx
0040C9C6|.85C0 test eax, eax
0040C9C8|.894424 18 mov dword ptr , eax
0040C9CC|.0F8E 95010000 jle 0040CB67
0040C9D2|.33C9 xor ecx, ecx
0040C9D4|.EB 0A jmp short 0040C9E0 ;下面的代码应该很眼熟吧,典型的For循环
0040C9D6|>8B4C24 2C /mov ecx, dword ptr
0040C9DA|.8B5424 28 |mov edx, dword ptr
0040C9DE|.8BFF |mov edi, edi
0040C9E0|>3B5424 20 cmp edx, dword ptr ;将计数器EDX跟服务器的数量进行比较,看看循环是否结束
0040C9E4|.0F83 42010000 |jnb 0040CB2C
0040C9EA|.8B8437 C80100>|mov eax, dword ptr ;取出第一个服务器的信息结构的首地址
[ 本帖最后由 bester 于 2009-2-17 13:07 编辑 ]
真郁闷,这一个帖子发了N遍了·~~
好了,现在我们正式的开始写程序,程序可以写成DLL的方式,也可以写成EXE的方式,由于DLL的调试不是很方便,我就只贴一下DLL方式的代码,至于EXE方式的,大家可以看附件的程序,不多说了,大家看代码就好:/************************************************************************//* 函数名:GetAreaInfo
/* 参数:无
/* 返回值:返回一个指向大区结构的指针
/* 功能:获取服务器列表中大区的结构体指针
/* 作者:bester @ 2/17/2009
/************************************************************************/
__declspec(naked) PGAME_AREA_INFO WINAPI GetAreaInfo()
{
__asm
{
mov eax, dword ptr
test eax, eax
je NULL_POINT
mov eax, dword ptr
test eax, eax
je NULL_POINT
mov eax, dword ptr
NULL_POINT:
retn;
}
}
/************************************************************************/
/* 函数名:GetAreaNum
/* 参数:无
/* 返回值:返回游戏更新列表中大区的个数
/* 功能:获取游戏更新列表中大区的个数
/* 作者:bester @ 2/17/2009
/************************************************************************/
__declspec(naked) int WINAPI GetAreaNum()
{
_asm
{
mov eax, dword ptr
test eax, eax
je NULL_POINT
mov eax, dword ptr
test eax, eax
je NULL_POINT
mov eax, dword ptr
test eax, eax
je NULL_POINT
mov ecx, dword ptr
test ecx, ecx
je NULL_POINT
mov ecx, dword ptr
test ecx, ecx
je NULL_POINT
mov ecx, dword ptr
test ecx, ecx
je NULL_POINT
sub ecx,eax
mov eax, 0x8C08C08D
imul ecx
add edx, ecx
sar edx, 0x8
mov eax, edx
shr eax, 0x1F
add eax, edx
NULL_POINT:
retn;
}
}
/************************************************************************/
/* 函数名:GetServerInfo
/* 参数:无
/* 返回值:返回一个指向服务器结构体的指针
/* 功能:获取服务器列表中服务器的结构体指针
/* 作者:bester @ 2/17/2009
/************************************************************************/
PGAME_SERVERLIST_INFO CMyForm::GetServerInfo(PGAME_AREA_INFO pAreaInfo)
{
return pAreaInfo->pServerListOfFirst;
}
/************************************************************************/
/* 函数名:GetServerNum
/* 参数:指定的大区结构体指针
/* 返回值:返回指定的大区中服务器的数量
/* 功能:获取指定的大区中服务器的个数
/* 作者:bester @ 2/17/2009
/************************************************************************/
int WINAPI GetServerNum(PGAME_AREA_INFO pAreaInfo)
{
DWORD TheServerNum = 0;
TheServerNum = (DWORD)pAreaInfo->pTheEndData - (DWORD)pAreaInfo->pServerListOfFirst;
_asm
{
mov ECX,TheServerNum
mov eax, 0x3E0F83E1
imul ecx ;3E0F83E1*(最大值-最小值)
sar edx, 0x6 ;取其积的高32位值右移6位
mov eax, edx
shr eax, 0x1F ;再移动0x1F位
add eax, edx ;OD显示是4,即有4个服务器,循环次数为4次
mov TheServerNum,eax
}
return TheServerNum;
}
/************************************************************************/
/* 函数名:GetTheMaxServerNum
/* 参数:无
/* 返回值:返回游戏指定的每个区最多可拥有服务器的数量
/* 功能:如果取到的服务器个数大于等于这个数就说明出错了
/* 作者:bester @ 2/17/2009
/************************************************************************/
__declspec(naked) int WINAPI GetTheMaxServerNum()
{
__asm
{
mov eax, dword ptr
test eax, eax
je NULL_POINT
mov eax, dword ptr
NULL_POINT:
retn;
}
}
/************************************************************************/
/* 函数名:OnBtnRefurbish
/* 参数:无
/* 返回值:无
/* 功能:获取游戏更新程序的服务器列表中的数据并更新到程序的列表视图中
/* 作者:bester @ 2/17/2009
/************************************************************************/
void CMyForm::OnBtnRefurbish()
{
CString szTemp = "";
int AreaNum = GetAreaNum();
szTemp.Format("获取到大区的数量是0x%08X",AreaNum);
MessageBox(szTemp);
try
{
PGAME_SERVERLIST_INFO pServerListInfo = NULL;
PGAME_AREA_INFO pAreaInfo = GetAreaInfo();
if (pAreaInfo != NULL)
{
for (int x=0; x<=AreaNum; x++,pAreaInfo++)
{
pServerListInfo = pAreaInfo->pServerListOfFirst;
if (pServerListInfo != NULL)
{
for (int y=0; y<=GetServerNum(pAreaInfo); y++, pServerListInfo++)
{
//序号
szTemp.Format(_T("%.2d"), y+1);
m_ServerList.InsertItem(y,szTemp);
//大区的名字
m_ServerList.SetItemText(y,1,pServerListInfo->szName);
//服务器的名字
m_ServerList.SetItemText(y,2,pServerListInfo->szName);
//IP和Port
m_ServerList.SetItemText(y,3,pServerListInfo->sServerIpPort);
}
}
}
}
UpdateData();
delete pServerListInfo;
delete pAreaInfo;
}catch(...)
{
::AfxMessageBox("遍历服务器列表时,出现异常……");
}
}
最后看下效果吧:
总的来说,还是不错的,是吗?
哈哈,本人才疏学浅,也只能讲点这些简单的东西了,仅希望能以此抛砖引玉,引来大牛的分享……
[ 本帖最后由 bester 于 2009-2-18 02:25 编辑 ]
发一个帖子里,就自动给截断了,不知道什么原因,希望管理员查一下~~~
不如我们跟进去看看,服务器结构是什么样子,哈哈0132A5B80A 00 00 00 BA F9 C2 AB C9 BD 00 40 A0 F9 17 04....葫芦山.@狔
0132A5C894 FA 17 04 00 00 00 00 00 E9 92 7C 40 00 93 01旡.....閽|@.?
不多介绍了,继续看下面的代码:
0040C9F1|.8D3C01 |lea edi, dword ptr ;ECX是结构偏移的计数器,大家都应该能看明白的
0040C9F4|.42 |inc edx ;循环计数器了,不用我讲的•~~
0040C9F5|.81C1 08010000 |add ecx, 108 ;每个服务器信息结构的大小是0x108,指到数组的下个成员
0040C9FB|.85ED |test ebp, ebp
0040C9FD|.895424 28 |mov dword ptr , edx ;保存服务器的序号信息
0040CA01|.894C24 2C |mov dword ptr , ecx ;保存服务器信息结构的偏移信息
0040CA05|.0F8C A9020000 |jl 0040CCB4
0040CA0B|.3BAB 1C010000 |cmp ebp, dword ptr ;看来这个0x11C很关键哦,
0040CA11|.0F8D 9D020000 |jge 0040CCB4 ;大于等于就跳走了~~~
这里用到了这个0x11C这个变量,根据上面代码的最后两行,如果服务器数量大于等于6就跳走,估计是个用来防止异常的,有兴趣的朋友可以跟一下,这里就不多说了!
继续看代码:
0040CA17|.8B83 18010000 |mov eax, dword ptr ;取出第一个服务器结构的首地址
0040CA1D|.8B34A8 |mov esi, dword ptr
0040CA20|.8D04A8 |lea eax, dword ptr ;dword ptr 不会被看糊涂吧,取结构体成员的基本方法!
0040CA23|.8D57 04 |lea edx, dword ptr ;还记得EDI里存的是什么吧,哈哈,偏移+4就是取服务器名字了~
0040CA26|.85D2 |test edx, edx
0040CA28|.C746 3C 01000>|mov dword ptr , 1
0040CA2F|.8D9E 8C000000 |lea ebx, dword ptr
0040CA35|.75 04 |jnz short 0040CA3B
0040CA37|.33C0 |xor eax, eax
0040CA39|.EB 14 |jmp short 0040CA4F
0040CA3B|>8BC2 |mov eax, edx ;1
0040CA3D|.8D48 01 |lea ecx, dword ptr
0040CA40|.894C24 30 |mov dword ptr , ecx
0040CA44|>8A08 |/mov cl, byte ptr ;断在了这里,也就是说,这里取了服务器的列表信息!
0040CA46|.40 ||inc eax ;眼熟不?哈哈
0040CA47|.84C9 ||test cl, cl
0040CA49|.^ 75 F9 |\jnz short 0040CA44
好了,分析就基本上到此结束了,没有必要再继续分析了,接下来就是分析一下,我们需要什么东西,然后就是怎么把我们分析得到的东西转换成代码!
先想想我们现在需要的东西:各个大区的序号,还有大区的名字,服务器的名字,服务器的IP和端口号,需要的数据就这么多了,接下来看看我们现在分析到了什么数据~
先看下服务器的结构中,我们已经知道了哪些数据
1. 每个服务器信息结构的大小是0x108
2. 直接引用服务器的数据,如下:
0132A5B80A 00 00 00 BA F9 C2 AB C9 BD 00 40 A0 F9 17 04....葫芦山.@狔
0132A5C894 FA 17 04 00 00 00 00 00 E9 92 7C 40 00 93 01旡.....閽|@.?
我们需要的数据很少,只需要名字和IP及端口就OK了,所以直接看数据段,来肉眼来定一下就可以了
这样,我们定义的结构体,如下:
// 游戏服务器的数据结构定义
typedef struct _GAME_SERVERLIST_INFO
{
DWORD dwServerNo; // 服务器的序号 0
char szName; // 名字offset 4
DWORD dwUnknow1; // 未知offset C
char sServerIpPort; // IP和端口 offset 1C8
DWORD dwUnknow2; // 结尾的数据 offset 1CC 用来补齐0x108的结构大小
} GAME_SERVERLIST_INFO, *PGAME_SERVERLIST_INFO;
再看大区的数据结构中,我们知道的数据:
1. 现在只知道第一个成员是大区的ID,第二个是名字,而且结构的大小要凑够0x1D4
2. 看到了吧,大区数构中偏移第1C8的成员是个最小值
3. 大区数构中偏移第1CC的成员是个最大值
好了,现在我们来定义大区的结构体,如下:// 游戏大区的数据结构定义
typedef struct _GAME_AREA_INFO
{
DWORD dwTheAreaNo; // 大区的序号 0
char szName; // 名字offset 4
DWORD dwUnknow1; // 未知offset C 这些数据对我们不重要,直接掠过
_GAME_SERVERLIST_INFO *pServerListOfFirst; // 首个服务器 offset 1C8
PDWORD pTheEndData; // 结尾的数据 offset 1CC
PDWORD pTheEndData2; // 结尾的数据 1D0 用来补齐0x1D4的结构大小
} GAME_AREA_INFO, *PGAME_AREA_INFO;
到现在,数据已经整理好了,应该可以写代码了,在写代码以前呢,我们整理总结一下我们收集到的数据:
1. 这个程序的基址是:004812A8
2. 大区的地址指针+偏移的形式应该在:+4]
3. 计算大区数量的算法如下:
+DC]+8] 减去 +DC]+4] 然后在:
0040C932|.B8 8DC0088C mov eax, 8C08C08D
0040C937|.F7E9 imul ecx
0040C939|.03D1 add edx, ecx
0040C93B|.C1FA 08 sar edx, 8
0040C93E|.8BC2 mov eax, edx
0040C940|.C1E8 1F shr eax, 1F
0040C943|.03C2 add eax, edx
4. 计算每个区服务器数量的算法如下:
用GAME_AREA_INFO:: pTheEndData 减去GAME_AREA_INFO::pServerListOfFirst 然后再如下计算:
0040C9A9|.B8 E1830F3E mov eax, 3E0F83E1
0040C9AE|.F7E9 imul ecx ;3E0F83E1*(最大值-最小值)
0040C9B0|.C1FA 06 sar edx, 6 ;取其积的高32位值右移6位
0040C9B3|.8BC2 mov eax, edx
0040C9B5|.C1E8 1F shr eax, 1F ;再移动0x1F位
0040C9B8|.03C2 add eax, edx ;OD显示是4,即有4个服务器,循环次数为4次
0040C9BA|.894424 20 mov dword ptr , eax ;把结果保存一下
5. 基址:004812A8 加上0x11C 中的值是最大的服务器数量,若算出来的服务器数量大于等于这个数,就是出错了,应该加容错处理!
[ 本帖最后由 bester 于 2009-2-17 12:54 编辑 ] 学习了。。。哈哈。。好文。。 相当不错的文章hoho~~~
支持啊....
内存读。
省的解密了~ 学习了
谢谢楼主 粗粗的看了一边,能理解个40%还的努力啊 :funk: 我再努力几年
追上你;P 我晕.好东西.. :loveliness: 支持原创:D :D