Tech

传奇Unity手游分析

传奇Unity手游分析

传奇Unity手游分析

前言

被人邀请搞手游脱机的,主要是分析该手游的登录协议,模拟其发包数据发送给游戏服务器,让游戏服务器相信我这个是手游发送出来的就行

分析过程

本人从未分析过手游,很多地方都是先学先卖的。

day 1

下载apk,jadx下载看看大致的逻辑,将apk解压缩,拿到so文件,尝试去分析。

第一天的结果就是:

  • il2cpp开发的手游,使用Il2CppDumper去dump出结构来
  • 另外在web端使用的是7roud的sdk7road

相当于是收集信息了

day 2

il2cppdumper工具去dump出的dump.cs和script.json两个文件,将这ida.py导入到ida中查看具体的结构,可以看到大概的逻辑,但是自己ida对这种反编译后的处理不太了解,只会f5尝试,南山巨巨将他弄好的idb文件发给我,我接着机会看看分析下。

整个登录流程如下:

如果本地有登录过的痕迹且有凭证就自动登录,没有则跳转到登录界面。登录成功就使用*UnityPlayer.UnitySendMessage *发送登录成功的信息到游戏服务器(il2cpp),然后服务器知道了该玩家已上线。

研究的点也就是该过程中,客户端构造的数据包和格式,这样就能够做到脱机登录的功能了。

day3

libunity.so

UnityPlayer.UnitySendMessage中调用这个函数去执行:

private static native void nativeUnitySendMessage(String str, String str2, byte[] bArr);

nativeUnitySendMessage这个函数,皮卡丘巨巨告诉我,搜索(ALT+T) nativeUnitySendMessage跳转到这个地方,然后函数就是这个这一行

dd offset aLjavaLangStrin_37 ; “(Ljava/lang/String;Ljava/lang/String;[B”…data:0102180C dd offset nativeUnitySendMessage

点进去,看到函数逻辑

然后f5看到反编译代码,需要自己修改参数和函数名

nativeUnitySendMessage代码:

int __cdecl nativeUnitySendMessage(JNIEnv *a1, jobject *a2, jstring *a3, jstring *a4, jstring *a5)

{

  int v5; // edi

  _BYTE v7[12]; // [esp+0h] [ebp-3Ch] BYREF

  char *v8; // [esp+Ch] [ebp-30h]

  size_t v9; // [esp+10h] [ebp-2Ch]

  const char *v10; // [esp+14h] [ebp-28h]

  const char *v11; // [esp+18h] [ebp-24h]

  JNIEnv *v12; // [esp+1Ch] [ebp-20h]

  _BYTE *v13; // [esp+20h] [ebp-1Ch]

  int *v14; // [esp+24h] [ebp-18h]



  v14 = (int *)&_stack_chk_guard;

  v5 = sub_2F84D8();

  sub_2F8660(v5);

  if ( !sub_2F89FE(v5) && !setjmp((struct __jmp_buf_tag *)(v5 + 4)) )

  {

    v12 = (JNIEnv *)(*a1)->GetStringUTFChars(a1, a3, 0);

    v11 = (*a1)->GetStringUTFChars(a1, a4, 0);

    v9 = (*a1)->GetArrayLength(a1, a5);

    v10 = (*a1)->GetByteArrayElements(a1, a5, 0);

    v13 = v7;

    v8 = &v7[-((v9 + 16) & 0xFFFFFFF0)];

    strncpy(v8, v10, v9);

    v8[v9] = 0;

    ((void (__cdecl *)(JNIEnv *, jstring *, const char *))(*a1)->ReleaseByteArrayElements)(a1, a5, v10);

    UnitySendMessage(v12, (int)v11, v8);

    (*a1)->ReleaseStringUTFChars(a1, a3, (const char *)v12);

    (*a1)->ReleaseStringUTFChars(a1, a4, v11);

  }

  sub_2F8690(v5);

  return *v14;

}

a3为UnityPluginClass.this.gameObjectName,a4为 “onAutoLoginSuccess”,a5为loginResult.toJsonString()

整个逻辑就是讲a3的gameObjectName(v12)和 “onAutoLoginSuccess”字符串(v11)以及loginResult这个数组长度(应该是一个json形式的字符串)(v8)传给了UnitySendMessage

UnitySendMessage(v12, (int)v11, v8);

UnitySendMessage(char *a1, int a2, const char *a3),其中v11是一个指针

最后的底层转发还是在UnitySendMessage这个函数中,代码如下:

void *__cdecl UnitySendMessage(char *a1, int a2, const char *a3)

{

  size_t n; // eax

  int v4; // eax

  void *result; // eax

  int v6[3]; // [esp+10h] [ebp-5Ch] BYREF

  int v7; // [esp+1Ch] [ebp-50h] BYREF

  void *v8; // [esp+20h] [ebp-4Ch]

  _DWORD v9[3]; // [esp+24h] [ebp-48h] BYREF

  int v10; // [esp+30h] [ebp-3Ch] BYREF

  void *v11; // [esp+34h] [ebp-38h]

  void *ptr; // [esp+3Ch] [ebp-30h]

  void *v13; // [esp+50h] [ebp-1Ch]

  char v14[24]; // [esp+54h] [ebp-18h] BYREF



  n = strlen(a3);

  v6[0] = 4;

  v6[1] = -1;

  v6[2] = -1082130432;

  sub_4F9AB8((int)&v7, (char *)a3, n);

  v9[0] = 0;

  v9[1] = 0;

  sub_4F9F90((int)&v10, a1, (char *)a2, (int)v6);

  v4 = dword_108144C;

  if ( !dword_108144C )

  {

    sub_4FA0D0();

    v4 = dword_108144C;

  }

  sub_4FA070(v4, 1, &v10);

  sub_4F9E24(v14);

  if ( v13 )

    sub_4A550F(v13);

  if ( ptr )

    sub_4A550F(ptr);

  if ( v11 )

    sub_4A550F(v11);

  sub_4F9E24(v9);

  result = v8;

  if ( v8 )

    result = (void *)sub_4A550F(v8);

  return result;

}

用到了额外的函数有sub_4F9F90、sub_4F9AB8、sub_4FA0D0、sub_4FA070、sub_4F9E24、sub_4A550F

这里用到的函数比较少不去处理了。

libil2cpp.so

南山巨巨分享了他的idb文件,autoLogin在il2cpp.so中代码如下:

void __fastcall road7unitysdk_Road7UnitySDK__autoLogin(road7unitysdk_Road7UnitySDK_o *this, const MethodInfo *method)

{

  UnityEngine_AndroidJavaObject_o *v3; // r4

  System_Object_array *v4; // r2



  if ( !byte_16D9F82 )

  {

    sub_1F2480("i_memmove4", method);

    byte_16D9F82 = 1;

  }

  v3 = this->fields.pluginObject;

  if ( v3 )

  {

    v4 = (System_Object_array *)System_Array__Empty_string_((const MethodInfo_12707C8 *)Method_System_Array_Empty_object___);

    UnityEngine_AndroidJavaObject__Call(v3, (System_String_o *)StringLiteral_9445, v4, 0);

  }

  else

  {

    if ( (UnityEngine_Debug_TypeInfo->_2.bitflags2 & 2) != 0 && !UnityEngine_Debug_TypeInfo->_2.cctor_started )

      il2cpp_runtime_class_init_0();

    UnityEngine_Debug__Log((Il2CppObject *)StringLiteral_9444, 0);

  }

}

road7unitysdk_Road7UnitySDK__autoLogin这个中做了什么操作搞懂了再去深究其他的。

v3 = this->fields.pluginObject;

如果v3不会为空执行下面的

v4 = (System_Object_array *)System_Array__Empty_string_((const MethodInfo_12707C8 *)Method_System_Array_Empty_object___);

UnityEngine_AndroidJavaObject__Call(v3, (System_String_o *)StringLiteral_9445, v4, 0);

如果v3不为空

(UnityEngine_Debug_TypeInfo->_2.bitflags2 & 2) != 0 && !UnityEngine_Debug_TypeInfo->_2.cctor_started 成立执行

il2cpp_runtime_class_init_0(); // 猜测:这个是不是登陆成功初始化的

不成立执行

UnityEngine_Debug__Log((Il2CppObject *)StringLiteral_9444, 0); // v3为空,并且上面的校验还不成立就打印日志

void __fastcall UnityEngine_AndroidJavaObject__Call(UnityEngine_AndroidJavaObject_o *this, System_String_o *methodName, System_Object_array *args, const MethodInfo *method)

{

  UnityEngine_AndroidJavaObject___Call(this, methodName, args, method);

}

void __fastcall UnityEngine_AndroidJavaObject___Call(UnityEngine_AndroidJavaObject_o *this, System_String_o *methodName, System_Object_array *args, const MethodInfo *method)

{

  struct UnityEngine_GlobalJavaObjectRef_o *v7; // r7

  intptr_t v8; // r7

  UnityEngine_jvalue_array *v9; // r0

  const MethodInfo *v10; // r3

  struct UnityEngine_GlobalJavaObjectRef_o *v11; // r5

  UnityEngine_jvalue_array *v12; // r6



  if ( !byte_16D97FC )

  {

    sub_1F2480(254, methodName);

    byte_16D97FC = 1;

  }

  if ( !args )

    args = (System_Object_array *)il2cpp_array_new_specific_0(object___TypeInfo, 1);

  v7 = this->fields.m_jclass;

  if ( !v7 )

    sub_21DA9C(0);

  v8 = UnityEngine__AndroidJNIHelper__GetMethodID(v7->fields.m_jobject, methodName, args, 0, 0);

  v9 = UnityEngine__AndroidJNIHelper__CreateJNIArgArray(args, 0);

  v11 = this->fields.m_jobject;

  v12 = v9;

  if ( !v11 )

    sub_21DA9C(0);

  UnityEngine_AndroidJNISafe__CallVoidMethod(v11->fields.m_jobject, v8, v9, v10);

  UnityEngine__AndroidJNIHelper__DeleteJNIArgArray(args, v12, 0);

}

void __fastcall UnityEngine_AndroidJavaObject___Call(UnityEngine_AndroidJavaObject_o *this, System_String_o *methodName, System_Object_array *args, const MethodInfo *method)

{

  struct UnityEngine_GlobalJavaObjectRef_o *v7; // r7

  intptr_t v8; // r7

  UnityEngine_jvalue_array *v9; // r0

  const MethodInfo *v10; // r3

  struct UnityEngine_GlobalJavaObjectRef_o *v11; // r5



  if ( !byte_16D97FC )

  {

    sub_1F2480(254, methodName);

    byte_16D97FC = 1;

  }

  if ( !args )

    args = (System_Object_array *)il2cpp_array_new_specific_0(object___TypeInfo, 1);

  v7 = this->fields.m_jclass;

  if ( !v7 )

    sub_21DA9C(0);

  v8 = UnityEngine__AndroidJNIHelper__GetMethodID(v7->fields.m_jobject, methodName, args, 0, 0);

  v9 = UnityEngine__AndroidJNIHelper__CreateJNIArgArray(args, 0);

  v11 = this->fields.m_jobject;

  if ( !v11 )

    sub_21DA9C(0);

  UnityEngine_AndroidJNISafe__CallVoidMethod(v11->fields.m_jobject, v8, v9, v10);

  UnityEngine__AndroidJNIHelper__DeleteJNIArgArray(args, v9, 0);

}

逻辑如下:

如果byte_16D97FC为空,就执行sub_1F2480(254, methodName);并将byte_16D97FC赋值为1

args如果是空的,就会通过il2cpp_array_new_specific_0函数再次赋值

v7应该是赋值了当前的引用this了

如果v7为空,执行sub_21DA9C(0);

v8通过UnityEngine__AndroidJNIHelper__GetMethodID赋值,获得methodID,如果v7为空的话,那应该为0吧

v9是通过UnityEngine__AndroidJNIHelper__CreateJNIArgArray创建赋值了一个数组,但绝不是0大小

v11赋值了当前的对象

如果v11为空,执行sub_21DA9C(0);

然后执行UnityEngine_AndroidJNISafe__CallVoidMethod,应该是调用v11的对象中methodID为v8的值,v9和v10都为该函数的传参

最后一步清楚缓存

sub_1F2480代码:

int __fastcall sub_1F2480(int a1, System_String_o *methodName)

{

  int v2; // r0

  int *v3; // r1

  int v4; // r1

  int v6[5]; // [sp+4h] [bp-14h] BYREF



  v3 = (int *)(dword_16BF908 + *(_DWORD *)(dword_16BF90C + 192) + 8 * a1);

  v2 = *v3;

  v4 = v3[1];

  v6[1] = 0;

  v6[0] = 0;

  v6[2] = 0;

  sub_1F2330(v2, v4, v6);

  return sub_2553D4(0);

}

这块代码有问题,回去继续修复下,ida要会使用,然后在对相应的数据进行修复也就是引导ida变成一个正确的反编译效果。

sub_1ED1D4代码:

int __fastcall sub_1ED1D4(int a1, void *buf, size_t n, int a4, int a5)

{

  int v5; // r7

  int result; // r0

  ssize_t v10; // r0



  v5 = 0;

  *(_DWORD *)a5 = 0;

  if ( a4 )

  {

    if ( (a4 & 0xFFFF7FE8) == 0 )

    {

      v5 = a4 & 0x8007;

      goto LABEL_5;

    }

    *(_DWORD *)(a1 + 20) = 10045;

    result = -3;

  }

  else

  {

LABEL_5:

    while ( 1 )

    {

      v10 = send(*(_DWORD *)(a1 + 4), buf, n, v5);

      if ( v10 != -1 )

        break;

      if ( *(_DWORD *)_errno() != 4 )

      {

        sub_1EC88C(a1);

        return -3;

      }

    }

    *(_DWORD *)a5 = v10;

    result = 0;

  }

  return result;

}

day4

通过httpcanary抓包分析了下那个游戏的协议分析,在保存数据格式,会有几个bin,在175.24.154.216这个ip地址下可以看到一些发包信息,主要是tcp发送的所以http抓包工具是看不到的(charles)。

在175.24.154.216:9607是链接游戏服务器的地址,也就是游戏服务器和游戏客户端的对话地址,会发送心跳包以及一些参数信息。

参数顺序: appid ,pakeageid,userid,token,devicemode,devicename,cpuinfo,中间有一段(不变),imei(加密后的) ,graphicsmode,oemssystem,childchannelid。

格式如下:

在so文件中,System_Net_Sockets_NetWorkStream_BeginWrite是客户端发送给服务器的函数。研究下就over。

利用objection hook手游

android hooking watch class_method com.road7.sdk.utils.CryptogramUtil.encryptMD5 —dump-args —dump-return —dump—backtarce

在初次打开游戏的时候,会将这些数据,将android_id这个值md5加密,结果为b2d6d9293518f797,分析发现抓包的AES后面的值就是android_id这个值算成的md5值,然后去找这个android_id是怎么来的,使用jadx反编译apk找到如下:

day5

整理发包数据:

import socket

import datetime

import time

import hexdump



def file2hex():

    with open("./1236/item.bin", "rb")as fp:

        data = fp.read()

    result = data.hex()

    print(f"file to hex result:{ result}")

    return result



def net_print(data,nwt_flag):

    now_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')

    print(f"{now_str}------------------{nwt_flag}----------------------")

    hexdump.hexdump(data)

    print("\n")



class gameNetWork():

    def __init__(self, host, port):

        self.host = host

        self.port = port

        self.session = socket.socket()

        self.session.connect((self.host, self.port))

        print('Start to send connection from {}'.format(host))



    def _send(self, data):

        data = bytes.fromhex(data)

        net_print(data, "send")

        self.session.send(data)



    def _recv(self):

        result = self.session.recv(1024);

        net_print(result, "recv")



    def _close(self):

        self.session.close()



def data_init():

    # 第一个

    first_data = "06000000010007000000730000"

    # 第二个

    second_data = "0e00000007000400000055537678"

    second_data1 = "0e0000008b01074368696e657365060000000400"

    # 第三个

    #third_data = "F4000000DE007CEA0000F002000008363937343633313220356164653738376437343765346230633830616465346666346133336566396300010E3232302E3234392E3136322E38390E476F6F676C6520506978656C203207506978656C20321041524D7637205646507633204E454F4E2035393462373765643861343461356162393631633665663039393066613966340F416472656E6F2028544D292035343036416E64726F6964204F5320382E312E30202F204150492D323720284F504D342E3137313031392E3032312E51312F34383230333436291A7B226368696C644368616E6E656C4964223A223130303033227D"

    

    third_data = "11010000de007cea0000f002000008363939383338383220636130326233633463383464343166336236643730333666623234323134623400000d3132352e3132312e36302e363116676f6f676c6520414f5350206f6e206d736d383939360f414f5350206f6e206d736d383939361241524d3634204650204153494d44204145532062373932343730353839333864623363653831303861306637393531346366380f416472656e6f2028544d292035333042416e64726f6964204f5320382e312e30202f204150492d323720284f504d312e3137313031392e3031312f656e672e74672e32303231303232332e303530353336291a7b226368696c644368616e6e656c4964223a223130303033227d"

    fourth_data = "060000000400"

    fourth_data1 = "060000000400"

    # fourth_data = "060000001100"



    fifth_data = "0a0000009001a53f0000"



    sixth_data = "0a0000005701a53f00"

    sixth_data1 = "0b0000001b01012f0900"



    seventh_data = "1c000000750001010000006ab601000102000000594d0b006ab60100"



    data_list = [first_data, second_data, second_data1, third_data, fourth_data, fourth_data1, fifth_data, sixth_data, sixth_data1, seventh_data]



    return data_list







def main():

    # 初始化信息

    fenqu_data = "08000000030000000e0000000c000000160000000f00000012000000200000000f0000000200000042000000020000000a00000036393938333838326768683138332e3135382e38322e313334e9bb98e8aea4e7babfe8b7af676f6f676c6520414f5350206f6e206d736d38393936414f5350206f6e206d736d3839393641524d3634204650204153494d44204145536237393234373035383933386462336365383130386130663739353134636638416472656e6f2028544d29203533303147416e64726f6964204f5320382e312e30202f204150492d323720284f504d312e3137313031392e3031312f656e672e74672e32303231303232332e30353035333629344732303231303230353031"

    game_fenqu = gameNetWork("110.43.51.231", 24007)

    game_fenqu._send(fenqu_data)





    host = "110.43.51.231"

    port = 9607



    game = gameNetWork(host, port)

    # 前期需要发送的数据

    for data in data_init():

        game._send(data)

        game._recv()

    game._send("060000000400")

    # game._send("060000000400")

    # game._send("0a0000009001be340000")

    game._send("0a0000009001a53f0000")



    # 后期持续等待接收消息

    i = 0

    while True:

        i = i+1

        if i%10 == 0:

            game._send("060000000400")

        game._recv()



if __name__ == '__main__':

    main()

    # third_data = "EF000000DE007CEA0000F00200000836393734363331322035616465373837643734376534623063383061646534666634613333656639630101093132372E302E302E310E476F6F676C6520506978656C203207506978656C20321041524D7637205646507633204E454F4E2035393462373765643861343461356162393631633665663039393066613966340F416472656E6F2028544D292035343036416E64726F6964204F5320382E312E30202F204150492D323720284F504D342E3137313031392E3032312E51312F34383230333436291A7B226368696C644368616E6E656C4964223A223130303033227D060000000400"

    # hexdump.hexdump(bytes.fromhex(third_data))



    # 5ade787d747e4b0c80ade4ff4a33ef9c

​ # 5ade787d747e4b0c80ade4ff4a33ef9c

地址从175.24.154.216变成了110.43.51.231。

发送数据包格式,通过httpcanary将upstream下载下来通过ue工具来查看。

目前可以做到脱机登录的效果,不过不是很理想,需要继续优化。脱机登录后不知道游戏服务器怎么处理,不知道游戏服务器怎么判断我这个用户是否为脱机用户。另外如果这种方式可以的话,那么打怪的数据包是不是也可以?

继续抓包分析,人物攻击怪物(随便攻击那个),可以看到YD这个标记很明显,而且是在0C00001400020300000000之后一定会有,其次在YD下方会有恭喜升级,这就很明显了,YD可能是打怪成功加经验或者是打怪成功,然后0C00001400020300000000大概率是攻击的请求包。

import socket

import time







def file2hex():

    with open("./1236/item.bin", "rb")as fp:

        data = fp.read()



    result = data.hex()

    print(f"file to hex result:{ result}")

    return result



def init(s):



    first_data = "06000000010007000000730000"



    second_data = "0e00000007000400000055537678"



    second_data1 = "0e0000008b01074368696e657365060000000400"

    # third_data="060000000300060000000400"

    # 第四个个

    fourth_data ="10010000de007cea0000f002000008363939383338383220633932306166663639346438343236333963613438356664336661306665333600000c33362e32342e3134342e313316676f6f676c6520414f5350206f6e206d736d383939360f414f5350206f6e206d736d383939361241524d3634204650204153494d44204145532062373932343730353839333864623363653831303861306637393531346366380f416472656e6f2028544d292035333042416e64726f6964204f5320382e312e30202f204150492d323720284f504d312e3137313031392e3031312f656e672e74672e32303231303232332e303530353336291a7b226368696c644368616e6e656c4964223a223130303033227d"



    fifth_data = "060000001100"



    fourth_data1 = "060000000400"

    #

    sixth_data = "0a0000005701BE340000"







    # bytes.fromhex hex2bytes

    s.send(bytes.fromhex(first_data))

    print('send: {}  '.format(first_data))

    resp = s.recv(1024)

    print('recv: {}  '.format(resp.hex()))



    # time.sleep(1)

    s.send(bytes.fromhex(second_data))

    print('send: {}  '.format(second_data))

    resp = s.recv(1024)

    print('recv: {}  '.format(resp.hex()))

    # time.sleep(1)



    s.send(bytes.fromhex(second_data1))

    print('send: {}  '.format(second_data1))

    resp = s.recv(1024)

    print('recv: {}  '.format(resp.hex()))

    resp = s.recv(1024)

    print('recv: {}  '.format(resp.hex()))

    resp = s.recv(1024)

    print('recv: {}  '.format(resp.hex()))



    s.send(bytes.fromhex(fourth_data1))

    print('send: {}  '.format(fourth_data1))

    resp = s.recv(1024)

    print('recv: {}  '.format(resp.hex()))

    resp = s.recv(1024)

    print('recv: {}  '.format(resp.hex()))

    resp = s.recv(1024)

    print('recv: {}  '.format(resp.hex()))



    s.send(bytes.fromhex(fourth_data1))

    print('send: {}  '.format(fourth_data1))

    resp = s.recv(1024)

    print('recv: {}  '.format(resp.hex()))

    resp = s.recv(1024)

    print('recv: {}  '.format(resp.hex()))

    resp = s.recv(1024)

    print('recv: {}  '.format(resp.hex()))



    s.send(bytes.fromhex(fourth_data))

    print('send: {}  '.format(fourth_data))

    resp = s.recv(1024)

    print('recv: {}  '.format(resp.hex()))

    resp = s.recv(1024)

    print('recv: {}  '.format(resp.hex()))

    resp = s.recv(1024)

    print('recv: {}  '.format(resp.hex()))





    # s.send(bytes.fromhex(fifth_data))

    # print('send: {}  '.format(fifth_data))

    # resp = s.recv(1024)

    # print('recv: {}  '.format(resp.hex()))

    # # # # time.sleep(1)

    # s.send(bytes.fromhex(fourth_data1))

    # resp = s.recv(1024)

    # print('recv: {}  '.format(resp.hex()))

    # resp = s.recv(1024)

    # print('recv: {}  '.format(resp.hex()))

    # resp = s.recv(1024)

    # print('recv: {}  '.format(resp.hex()))

    #

    # s.send(bytes.fromhex(sixth_data))

    # print('send: {}  '.format(sixth_data))

    # resp = s.recv(1024)

    # print('recv: {}  '.format(resp.hex()))

    # resp = s.recv(1024)

    # print('recv: {}  '.format(resp.hex()))

    # resp = s.recv(1024)

    # print('recv: {}  '.format(resp.hex()))







def main():

    # 初始化信息

    host = "110.43.51.231"

    port = 9607

    session = socket.socket()

    session.connect((host, port))

    print('Start to send connection from {}'.format(host))



    init(session)

    # 一直监听返回信息



    port = 24007

    session2 = socket.socket()

    session2.connect((host, port))

    data="08000000050000000c0000000c000000160000000f00000012000000200000000f0000000200000042000000020000000a0000003639393833383832666472656533362e32342e3134342e3133e9bb98e8aea4e7babfe8b7af676f6f676c6520414f5350206f6e206d736d38393936414f5350206f6e206d736d3839393641524d3634204650204153494d44204145536237393234373035383933386462336365383130386130663739353134636638416472656e6f2028544d29203533303147416e64726f6964204f5320382e312e30202f204150492d323720284f504d312e3137313031392e3031312f656e672e74672e32303231303232332e30353035333629344732303231303230353031"



    session2.send(bytes.fromhex(data))



    while True:

        session.send(bytes.fromhex("060000000400"))

        time.sleep(1)

        session.send(bytes.fromhex("0a000000900106350000"))

        resp = session.recv(1024)

        time.sleep(1)

        print(resp.hex())



    #session.send(bytes.fromhex("060000000400"))

    #session.send(bytes.fromhex("0a0000009001a65b0000"))

    #resp = session.recv(1024)

    #print(resp.hex())

if __name__ == '__main__':

    # file2hex()

    main()

device信息:

11010000de007cea0000f002000008363939383338383220636130326233633463383464343166336236643730333666623234323134623400000d3132352e3132312e36302e363116676f6f676c6520414f5350206f6e206d736d383939360f414f5350206f6e206d736d383939361241524d3634204650204153494d44204145532062373932343730353839333864623363653831303861306637393531346366380f416472656e6f2028544d292035333042416e64726f6964204f5320382e312e30202f204150492d323720284f504d312e3137313031392e3031312f656e672e74672e32303231303232332e303530353336291a7b226368696c644368616e6e656c4964223a223130303033227d

初始化信息:

08000000050000000c0000000c000000160000000f00000012000000200000000f0000000200000042000000020000000a0000003639393833383832666472656533362e32342e3134342e3133e9bb98e8aea4e7babfe8b7af676f6f676c6520414f5350206f6e206d736d38393936414f5350206f6e206d736d3839393641524d3634204650204153494d44204145536237393234373035383933386462336365383130386130663739353134636638416472656e6f2028544d29203533303147416e64726f6964204f5320382e312e30202f204150492d323720284f504d312e3137313031392e3031312f656e672e74672e32303231303232332e30353035333629344732303231303230353031

获取自己信息的指令:

0a000000900106350000

day6

分析device信息是怎么来的

参数顺序: appid ,pakeageid,userid,token,devicemode,devicename,cpuinfo,中间有一段(不变),imei(加密后的) ,graphicsmode,oemssystem,childchannelid

包开头:11010000de007cea0000f00200000836

appid:39393833383832

空格:20

packageid:6361303262336334

userid:633834643431663362366437303336666232343231346234

空格:00000d

ip:3132352e3132312e36302e363116

device:676f6f676c6520414f5350206f6e206d736d383939360f414f5350206f6e206d736d383939361241524d3634204650204153494d44

20

AES+md5:414553206237393234373035383933386462336365383130386130663739353134

device_info:6366380f416472656e6f2028544d292035333042416e64726f6964204f5320382e312e30202f204150492d323720284f504d312e3137313031392e3031312f656e672e74672e32303231303232332e303530353336291a

childchannelid:7b226368696c644368616e6e656c4964223a223130303033227d

import socket

import datetime

import time

import hexdump



def file2hex():

    with open("./1236/item.bin", "rb")as fp:

        data = fp.read()

    result = data.hex()

    print(f"file to hex result:{ result}")

    return result



def net_print(data,nwt_flag):

    now_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')

    print(f"{now_str}------------------{nwt_flag}----------------------")

    hexdump.hexdump(data)

    print("\n")



def parse_data(userid, token, device_md5):

    data = "f4000000de007cea0000f002000008363937343633313220333736653564373865626663343166366237363666343462613764356531346300010e3232302e3234392e3136322e38390e476f6f676c6520506978656c203207506978656c20321041524d7637205646507633204e454f4e2035393462373765643861343461356162393631633665663039393066613966340f416472656e6f2028544d292035343036416e64726f6964204f5320382e312e30202f204150492d323720284f504d342e3137313031392e3032312e51312f34383230333436291a7b226368696c644368616e6e656c4964223a223130303033227d"

    data = bytes.fromhex(data)

    # userid

    userid = data[:15] + userid

    # token

    token = data[23:24] + token + data[56:59]

    # device md5

    device_md5 = data[59:114] + device_md5 + data[146:]



    # 拼接出新的数据

    new_data = userid  + token + device_md5



    return new_data.hex()



class gameNetWork():

    def __init__(self, host, port):

        self.host = host

        self.port = port

        self.session = socket.socket()

        self.session.connect((self.host, self.port))

        print('Start to send connection from {}'.format(host))



    def _send(self, data):

        data = bytes.fromhex(data)

        net_print(data, "send")

        self.session.send(data)



    def _recv(self):

        result = self.session.recv(1024);

        net_print(result, "recv")



    def _close(self):

        self.session.close()



def data_init(third_data, fifth_data):

    # 第一个

    first_data = "06000000010007000000730000"

    # 第二个

    second_data = "0e00000007000400000055537678"

    second_data1 = "0e0000008b01074368696e657365060000000400"

    # 第三个

    # third_data = "f4000000de007cea0000f002000008363937343633313220333736653564373865626663343166366237363666343462613764356531346300010e3232302e3234392e3136322e38390e476f6f676c6520506978656c203207506978656c20321041524d7637205646507633204e454f4e2035393462373765643861343461356162393631633665663039393066613966340f416472656e6f2028544d292035343036416e64726f6964204f5320382e312e30202f204150492d323720284f504d342e3137313031392e3032312e51312f34383230333436291a7b226368696c644368616e6e656c4964223a223130303033227d"



    fourth_data = "060000000400"



    # fifth_data = "0a0000009001a53f0000"



    data_list = [first_data,second_data, second_data1,fourth_data,fourth_data,third_data,fifth_data]



    return data_list



def main():

    # 初始化信息

    # fenqu_data = "08000000080000000E0000000C0000000E0000000700000010000000200000000F0000000200000036000000020000000A00000036393734363331326C736C736C736B7A3232302E3234392E3136322E3839E9BB98E8AEA4E7BABFE8B7AF476F6F676C6520506978656C2032506978656C203241524D7637205646507633204E454F4E3539346237376564386134346135616239363163366566303939306661396634416472656E6F2028544D29203534303147416E64726F6964204F5320382E312E30202F204150492D323720284F504D342E3137313031392E3032312E51312F3438323033343629344732303231303230353031"

    # game_fenqu = gameNetWork("110.43.51.231", 24007)

    # game_fenqu._send(fenqu_data)



    host = "110.43.51.231"

    port = 9607



    userid = "69746312".encode()

    token = "4b3e2b9dd7304e77966bfe84a1bf0b51".encode()

    device_md5 = "594b77ed8a44a5ab961c6ef0990fa9f4".encode()

    third_data = parse_data(userid, token, device_md5)

    fifth_data = "0a0000009001a53f0000"



    game = gameNetWork(host, port)

    # 前期需要发送的数据

    for data in data_init(third_data, fifth_data):

        game._send(data)

        game._recv()



    while True:

        game._recv()



if __name__ == '__main__':

    main()



    # data = "f4000000de007cea0000f002000008363937343633313220333736653564373865626663343166366237363666343462613764356531346300010e3232302e3234392e3136322e38390e476f6f676c6520506978656c203207506978656c20321041524d7637205646507633204e454f4e2035393462373765643861343461356162393631633665663039393066613966340f416472656e6f2028544d292035343036416e64726f6964204f5320382e312e30202f204150492d323720284f504d342e3137313031392e3032312e51312f34383230333436291a7b226368696c644368616e6e656c4964223a223130303033227d"

    # hexdump.hexdump(bytes.fromhex(data))

day7

自动攻击和清空背包

继续抓包分析,人物攻击怪物(随便攻击那个),可以看到YD这个标记很明显,而且是在0C00001400020300000000之后一定会有,其次在YD下方会有恭喜升级,这就很明显了,YD可能是打怪成功加经验或者是打怪成功,然后0C00001400020300000000大概率是攻击的请求包。

抓了几次包发现,0B00000010010302000000,0C0000001400020300000000

day8

通过hook libil2cpp.so中的函数偏移来找

function hook_native(base_addr) {

    var ReceivePacket = ptr(base_addr.toInt32() + 0x10EA038);

    Interceptor.attach(ReceivePacket, {

        onEnter: function (args) {

            console.log("------------------------start-------------------------------");

            var byte_lenth=ptr(args[0].toUInt32()+0xc).readInt();

            console.log("byte_lenth:",byte_lenth)

            var byte_array_ptr=ptr(args[0].toUInt32()+0x10);

            console.log(hexdump(byte_array_ptr,{ offset: 0,

                length: byte_lenth,

                header: true,

                ansi: false}));

        },

        onLeave: function (retval) {

            console.log("------------------------end-------------------------------\n");

        }

    })

}



function hook_ObjectPlayer_setLocation(base_addr) {

    var func_addr = ptr(base_addr.toInt32() + 0x10D55D8);



    Interceptor.attach(func_addr, {

        onEnter: function (args) {

            var x = args[1].toUInt32();

            var y = args[2].toUInt32();

            console.log("Player Location x(uint32): 0x" + x.toString(16));

            console.log("Player Location y(uint32): 0x" + y.toString(16));

        },

        onLeave: function (retval) {},

    });

}



function hook_ObjectMonster_setLocation(base_addr) {

    var func_addr = ptr(base_addr.toInt32() + 0x10d43a0);

    Interceptor.attach(func_addr, {

      onEnter: function (args) {

        var x = args[1].toInt32();

        var y = args[2].toInt32();

        console.log(

          "Monster Location: x=0x" + x.toString(16) + " y=0x" + y.toString(16)

        );

      },

      onLeave: function (retval) {},

    });

  }



function main() {

    var libil2cpp_addr = Module.findBaseAddress("libil2cpp.so");



    hook_ObjectPlayer_setLocation(libil2cpp_addr);

    hook_ObjectMonster_setLocation(libil2cpp_addr);

    //hook_native(libil2cpp_addr);

}





setImmediate(main)

参考链接