首页
社区
课程
招聘
[原创]某游戏封包加解密算法及其算法KEY的算法
发表于: 2009-10-23 13:49 39644

[原创]某游戏封包加解密算法及其算法KEY的算法

2009-10-23 13:49
39644

free Q.744303 (只接受志同道合的技术研究伙伴,验证说明“看雪”,无聊人士勿扰)

潜水很多年,第一次发技术贴,不会排版还请版主帮忙。。

下载了某游戏的客户端,目的就是练习一下算法逆向,并且目标不是汇编代码,是C++代码。

研究了两天,有可能有疏漏,还请大侠批评指教!

用到的调试工具只有OllyDBG。

 

一、获取与运算封包Key

获取很简单,直接连接服务器,会收到服务器返回的包含了SendKey和RecvKey的钥匙包。

经过一定的算法,得到两个Key,在后续的游戏中,将使用这两个Key进行加密通信。

封包[0][1]为封包长度,不进行加解密操作。

封包:

2C 00 20 00 00 0A 00 0A 00 BD AD F2 A5 D5 75 61 6E 78 75 0B 73 D6 7C 38 38 38 40 31 36 33 2E 63 6F 6D 00 00 00 00 00 00 00 00 00 00

下断点在 AD 这个字节上,运行,会断在访问的地址,如下汇编代码:

mov     edx, dword ptr [esi+11]  //取得 0B 73 D6 7C 
mov     esi, dword ptr [esi+8]   //取得 AD F2 A5 D5
mov     ecx, edx        //第二个KEY开始运算
mov     ebx, edx
xor     ecx, 6D23CF
sub     ecx, 6D2399
xor     edx, FFFFFFCF
not     ecx
add     edx, 67
xor     ebx, 2E6D23CF
and     ecx, 0FFFF00
not     edx
sub     ebx, 2E6D2399
shl     edx, 18
not     ebx
shr     ebx, 18
or      ecx, ebx
or      ecx, edx
push    ebp
push    ecx
mov     edx, esi      //第一个KEY开始运算
mov     ecx, esi
xor     edx, 6D23CF
xor     ecx, 2E6D23CF
sub     edx, 6D2399
sub     ecx, 2E6D2399
xor     esi, FFFFFFCF
not     edx
not     ecx
add     esi, 67
and     edx, 0FFFF00
shr     ecx, 18
or      edx, ecx
not     esi
shl     esi, 18
or      edx, esi

 

经过逆向,逆出C++代码如下:

DWORD __stdcall GetRecvKey(DWORD PacketRecvKey)
{

  DWORD sKey = 0;
  sKey = (((~((PacketRecvKey ^ 0x006D23CF) - 0x006D2399)) & 0x0FFFF00)
       | (~((PacketRecvKey ^ 0x2E6D23CF) - 0x2E6D2399)) >> 0x18)
      | ((~((PacketRecvKey ^ 0xFFFFFFCF) + 0x67)) << 0x18);
 return sKey;
}

 

DWORD __stdcall GetSendKey(DWORD PacketSendKey)
{

  DWORD sKey = 0;
  sKey  = (((~((PacketSendKey ^ 0x6D23CF) - 0x6D2399)) & 0xffff00)
       | ((~((PacketSendKey ^ 0x2E6D23CF) - 0x2E6D2399)) >> 0x18))
       | ((~((PacketSendKey ^ 0xFFFFFFCF) + 0x67)) << 0x18);
 return sKey ;
}

 

这两个KEY逆完后,继续逆封包加解密算法,中间碰到一个包,经过分析是心跳包,以07 00 01开头。

其实只是以01开头,刚才说过第一位和第二位是长度,所以07 00 是封包长度而已。心跳包是提交时间的。

这个封包的组成是这样的:

07 00 01 (XX XX XX XX)_time32()

蓝色是长度,红色是封包类型,后面的四位由MSVCR80.DLL里面的_time32() 函数得到结果。

在VS 2005+ 里可以直接使用这个函数,头文件:time.h。

 

通过类似的断点方法,找到解密用的函数,汇编如下:

push    ebx
push    ebp
mov     ebp, dword ptr [esp+14]
mov     ecx, dword ptr [ebp]
push    esi
mov     esi, dword ptr [esp+10]
mov     ebx, esi
and     ebx, 3                //求余数,下面以DWORD(4Byte)操作,如果有余数还要另行操作
shr     esi, 2                
push    edi
mov     edi, dword ptr [esp+18]
je      short 004D5605
lea     ecx, dword ptr [ecx]          //取得传入的经过运算的KEY
sub     esi, 1
lea     eax, dword ptr [ecx+esi]
xor     edx, edx
div     dword ptr [51FEDC]
add     edi, 4
mov     ecx, dword ptr [edx*4+51A620]    //进行完上面的运算,进行异或,注意是以DWORD(4字节)为单位的

add     ecx, 2E6D23C1          
xor     dword ptr [edi-4], ecx
test    esi, esi              //全完成了吗?
ja      short 004D55E0          //没有全完成的话继续
xor     edx, edx
mov     eax, ebx
div     dword ptr [51FEDC]
xor     ecx, dword ptr [edx*4+51A620]
test    ebx, ebx              //有余数吗?
jbe     short 004D562F          //没有的话就不继续处理剩下的字节了
lea     ebx, dword ptr [ebx]
xor     byte ptr [edi], cl
sub     ebx, 1
add     edi, 1
shr     ecx, 8
test    ebx, ebx              //余下的几个字节处理完了吗?
ja      short 004D5620          //没处理完继续
mov     eax, dword ptr [ebp]
mov     ecx, eax
shl     ecx, 5
sub     ecx, eax
pop     edi
add     ecx, 8088405
pop     esi
mov     dword ptr [ebp], ecx
pop     ebp
mov     eax, 1
pop     ebx
retn

 

经过分析,逆出的C++代码如下:

VOID __stdcall EncryptionPacket(DWORD *enbyte,int len,DWORD PacketKey)
{
  int NextLen = len % 4,PacketLength = len / 4;
  int i=0,j=0;
  DWORD NextKey = 0,AddNum = 0;
  BYTE *NewPacketAddr = (BYTE *)enbyte;
  AddNum = PacketKey;
 
  while (PacketLength--)
  {
    AddNum += PacketLength;
    NextKey = Pack_Tabel[AddNum % 0x162F] + 0x2E6D23C1;
    enbyte[i] = enbyte[i] ^ NextKey;
    AddNum = NextKey;
    i++;
  }

 

  j=i*4;
  NextKey ^= Pack_Tabel[NextLen % 0x162F];
 
  if (NextLen != 0)
  {
     while (NextLen--)
    {
      (BYTE)NewPacketAddr[j] = (BYTE)NewPacketAddr[j] ^ (BYTE)NextKey;
      NextKey = NextKey >> 8;
      j++;
    }
  }
}

 

至此封包算法已经逆完了,经过测试,是正确的:

 

取得服务端封包验证KEY...
开始发送帐号密码...
验证正常...
取得人物信息了...
帐号:h3389@vip.qq.com
人物:凌行恶
性别:男
等级:4

 

其实直接套汇编速度非常快,解了此游戏算法的人也不少,这里只是作为真正逆向代码的练习方法罢了。

接下来说一下是如何将算法逆成C++代码的,上面只给出了结果,其实我在个人空间里面分了两个贴子来说的,在这里直接就贴在下面给各位朋友参考,有不妥的地方还请大家批评指教:

寻找修改变量的代码,一直找到计算完毕,废话不多说,拿昨天的代码再来继续做个示例,看看我是如何把它逆成C++代码的:

 

(一)使用变量修改追踪找出单独的计算代码:

 

mov     edx, dword ptr [esi+11]  //取得 0B 73 D6 7C 
mov     esi, dword ptr [esi+8]   //取得 AD F2 A5 D5
mov     ecx, edx        //第二个KEY开始运算
mov     ebx, edx
xor     ecx, 6D23CF
sub     ecx, 6D2399
xor     edx, FFFFFFCF
not     ecx
add     edx, 67
xor     ebx, 2E6D23CF
and     ecx, 0FFFF00
not     edx
sub     ebx, 2E6D2399
shl     edx, 18
not     ebx
shr     ebx, 18
or      ecx, ebx
or      ecx, edx
push    ebp
push    ecx

上面是recvkey的计算方法,很容易可以看得出,第一行取得了recvkey即将运算的封包里的代码,第二行取得的是sendkey的,所以第二行与目前这段程序没有任何关联,删除它就可以了。

继续下来分析代码:

mov     edx, dword ptr [esi+11]  //取得 0B 73 D6 7C 

这一行把取得的KEY放到了EDX里面,继续向下看:

mov     ecx, edx
mov     ebx, edx

可以看到 ecx和ebx都等于 edx了,也就是复制了两个变量,可能要进行其它运算了

先来搞定EDX,看看它最终会怎么样,其它的都不管,向下寻找修改了EDX值的语句:

xor     edx, FFFFFFCF

修改了EDX之后,没有把EDX换到其它寄存器,还是EDX,那再向下找修改了EDX的语句:

add     edx, 67 

看样子还要继续找……

最终,形成的代码如下:

mov     edx, dword ptr [esi+11]
xor     edx, FFFFFFCF
add     edx, 67
not     edx
shl     edx, 18

再来看ECX和EBX,最终ECX形成代码:

mov     ecx, edx
xor     ecx, 6D23CF
sub     ecx, 6D2399
not     ecx
and     ecx, 0FFFF00

寻找EBX的相关代码:

xor     ebx, 2E6D23CF
sub     ebx, 2E6D2399
not     ebx
shr     ebx, 18

 

再继续向下看,最后做了什么操作:

or      ecx, ebx
or      ecx, edx

看样子ECX,EBX,EDX这三个变量,最终形成了ECX的一个结果。

 

(二)寻找并使用等同运算符

 
先从第一段程序开始,我们先做一个函数,以便传入待运算的KEY,返回值是DWORD,以便返回一个运算好的KEY,

选一门能胜任并熟悉的语言,这里以C++代码做为示例,红色的是汇编,等于号代表左右代码执行的结果相等,蓝色的是翻译之后的代码:

DWORD GetSendKey(DWORD SendKey)

{

}


做好之后,向里面开始写代码:

mov     edx, dword ptr [esi+11] = DWORD sKey = SendKey;

上面这句,等同于将一个变量读入另一个变量,这里我们可以先声明一个变量:

接下来用这个sKey进行运算,经过分析,其实这句可以省掉,因为可以直接用实参做运算。

 

下面这一句,我们看到了,将我们的变量进行异或运算,C++里异或运算符是 ^,其它语言跟据定义和语法不同自行改变。

xor     edx, FFFFFFCF = sKey = sKey ^ 0xFFFFFFCF;

好,接下来再看下面:

add     edx, 67 = sKey = sKey + 0x67;

not     edx = sKey = ~sKey;

shl     edx, 18 = sKey = sKey << 0x18;


 

最终,我们形成了代码:

DWORD GetSendKey(DWORD SendKey)

{

  DWORD sKey = SendKey;

  sKey = sKey ^ 0xFFFFFFCF;

  sKey = sKey + 0x67; 
  sKey = ~sKey;
  sKey = sKey << 0x18;

}


 

其实可以看出,前一个运算的返回值是下一个运算要用的运算值,我们再来简化它,让它更接近原始代码:

  

把这句删掉,直接用实参操作:

  DWORD sKey = SendKey;

变成了:

  SendKey = SendKey ^ 0xFFFFFFCF;

  SendKey = SendKey - 0x67; 

  SendKey = ~SendKey;
  SendKey = SendKey << 0x18;

接下来第一句,我们直接取它的运算结果:

(SendKey ^ 0xFFFFFFCF)

第二句直接用第一句的结果,再取它的运算结果:

((SendKey ^ 0xFFFFFFCF) + 0x67)

第三句直接用第二句的运算结果,并取它的运算结果:

(~((SendKey ^ 0xFFFFFFCF) + 0x67))

第四句用第三句的运算结果:

((~((SendKey ^ 0xFFFFFFCF) + 0x67)) << 0x18);

这样,我们把所有的语句整合成了一句:

再定义一个标识返回值的变量,最终,我们形成了代码:

 

DWORD GetSendKey(DWORD SendKey)

{

  DWORD retKey = ((~((SendKey ^ 0xFFFFFFCF) + 0x67)) << 0x18);

}


 

其它两个(EBX,ECX)按照这种方法,加上上面生成的,最终生成三行代码:

((~((SendKey ^ 0xFFFFFFCF) + 0x67)) << 0x18)              //EDX

((~((PacketSendKey ^ 0x2E6D23CF) - 0x2E6D2399)) >> 0x18))    //EBX

(((~((PacketSendKey ^ 0x6D23CF) - 0x6D2399)) & 0xffff00)    //ECX


再看最后两行代码:

or      ecx, ebx
or      ecx, edx

其实就是把最终运算好的ecx代码和ebx代码 或运算之后,再与运算好的edx 代码进行或运算,先来第一步:

((((~((PacketSendKey ^ 0x6D23CF) - 0x6D2399)) & 0xffff00) 

| ((~((PacketSendKey ^ 0x2E6D23CF) - 0x2E6D2399)) >> 0x18)))


 

这样我们就得到了ecx | ebx

然后再把它们的结果与edx的运算结果再进行或运算:

 

((((~((PacketSendKey ^ 0x6D23CF) - 0x6D2399)) & 0xffff00) 

| ((~((PacketSendKey ^ 0x2E6D23CF) - 0x2E6D2399)) >> 0x18)))

| ((~((SendKey ^ 0xFFFFFFCF) + 0x67)) << 0x18)


 

最终,我们得到了这个函数的全部代码:

DWORD GetSendKey(DWORD SendKey)

{

  DWORD retKey =  ((((~((PacketSendKey ^ 0x6D23CF) - 0x6D2399)) & 0xffff00) 

           | ((~((PacketSendKey ^ 0x2E6D23CF) - 0x2E6D2399)) >> 0x18)))

           | ((~((SendKey ^ 0xFFFFFFCF) + 0x67)) << 0x18);

  return retKey;

}


其它的代码也用类似这种方法逆出来整合到一起的,就不再赘述了。
希望大家看过之后,可以提出批评意见,或是指出不合理的地方,或是有更好的方法,都可以一起进行讨论。

声明:本人完全不知道这款游戏是什么游戏,也完全不知道这款游戏是哪家公司运营的,以上内容都是我作梦的时候说的梦话,我老婆记录下来的。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 7
支持
分享
最新回复 (59)
雪    币: 44
活跃值: (24)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
版主帮忙把标题多了好几个的 原创 去掉吧,我编辑贴子编辑不了标题
2009-10-23 13:52
0
雪    币: 375
活跃值: (201)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
od 找关键代码 然后ida上场F5 收工
2009-10-23 14:22
0
雪    币: 44
活跃值: (24)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
4
牛人。。不过只是为了练习。。微软编译器这么强大,还是依然有人在研究编译原理不是。
2009-10-23 14:27
0
雪    币: 113
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
- -...根本沒必要把汇编逆向成C++或者C。。
汇编就是汇编。难不成有人会说把某个软件根据汇编码用C重写出来?
只要能看懂的就是代码。。
希望大家都明白这点。呵呵。
把汇编重写成高级语言我想应该是多此一举。
2009-10-23 16:55
0
雪    币: 44
活跃值: (24)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
6
啊,那请问下,如果能看得懂汇编,又只懂VB或易语言或Java,VB或易语言或Java又不能用内联汇编,是不是要转成高级语言再去使用呢?
如果用C/C++这种操作我已经说了,是浪费的,直接套汇编比什么都快。世界上语言这么多种,不能以C/C++为所有语言的标准来说事吧?
这里我用C++写的代码是因为我只对C++一知半解,其它的语言我不懂。
智者见智的事了。
2009-10-23 17:27
0
雪    币: 1708
活跃值: (586)
能力值: ( LV15,RANK:670 )
在线值:
发帖
回帖
粉丝
7
VB貌似也能内联汇编的,忘记是要加装什么控件了,是收费的
2009-10-23 18:09
0
雪    币: 18
活跃值: (80)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
在帖的精华
2009-10-23 18:26
0
雪    币: 370
活跃值: (52)
能力值: ( LV13,RANK:350 )
在线值:
发帖
回帖
粉丝
9
amwmqj在理 支持+学习 谢谢分享
技术学习和实际应用不能混为一谈的
2009-10-23 18:38
0
雪    币: 213
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
支持,科普知识!支持 free!
2009-10-24 10:22
0
雪    币: 243
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
咋不写出找加密算法的过程..
2009-10-24 11:53
0
雪    币: 433
活跃值: (1870)
能力值: ( LV17,RANK:1820 )
在线值:
发帖
回帖
粉丝
12
强悍!膜拜!!!!!!!
2009-10-24 12:17
0
雪    币: 356
活跃值: (38)
能力值: ( LV9,RANK:220 )
在线值:
发帖
回帖
粉丝
13
强大……顶礼膜拜……收藏了
2009-10-24 12:35
0
雪    币: 0
活跃值: (954)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
14
这个游戏,见过,不想说。
2009-10-24 13:08
0
雪    币: 8835
活跃值: (2404)
能力值: ( LV12,RANK:760 )
在线值:
发帖
回帖
粉丝
15
...路过一下~
2009-10-24 13:20
0
雪    币: 193
活跃值: (1439)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
16
google 一下  xor     ebx, 2E6D23CF
我也不知道是不是那个游戏
没玩过
2009-10-24 13:56
0
雪    币: 22
活跃值: (48)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
等你发现技术比你高的人后。你也会见到这句话-----无聊人士勿扰

因为你以及未来的那个高人都生活在无聊/有聊的世界中

物以类聚,人以群分
2009-10-24 18:49
0
雪    币: 44
活跃值: (24)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
18
有理,要改正。大家都是菜过来的,不过我想仁兄误解我的意思了。比我水平低的,有问题我懂的我也会回答,交流。我指的无聊人士,是那种……嗯。。。以后的事大家都知道了。。。
2009-10-24 20:04
0
雪    币: 213
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
请问老大,Pack_Tabel是什么东西
2009-10-26 10:28
0
雪    币: 44
活跃值: (24)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
20
密码表。

mov     ecx, dword ptr [edx*4+51A620]

就是上面这句获得的数据。

它是一个数组,起始位置是0x51A620,*4是以4递增,因为它是一个DWORD的数组。

EDX 即为数组下标。

长度:

div     dword ptr [51FEDC]  //这个常量是0x162F

由于取了0x162F 的余数来做数组下标

故余数不可能大于0x162F

这就是数组长度了。

Packet_Table[0x162F] = {...这里写上从0x51A620复制出来的内容就可以了}
2009-10-26 12:39
0
雪    币: 103
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
嗯, 强悍, 膜拜, 支持
2009-10-26 13:31
0
雪    币: 213
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
[QUOTE=amwmqj;704115]密码表。

mov     ecx, dword ptr [edx*4+51A620]

就是上面这句获得的数据。

它是一个数组,起始位置是0x51A620,*4是以4递增,因为它是一个DWORD的数组。

EDX 即为数组下标。

长度:

div     dword ...[/QUOTE]

谢谢,老大指点!
2009-10-26 13:44
0
雪    币: 209
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
哈哈,是**三网络版
2009-10-26 14:50
0
雪    币: 259
活跃值: (1704)
能力值: ( LV9,RANK:410 )
在线值:
发帖
回帖
粉丝
24
写的很不错,学习..
2009-10-26 15:49
0
雪    币: 194
活跃值: (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
不顶对不起自已
2009-10-26 21:29
0
游客
登录 | 注册 方可回帖
返回
//