传奇Unity手游分析
前言 被人邀请搞手游脱机的,主要是分析该手游的登录协议,模拟其发包数据发送给游戏服务器,让游戏服务器相信我这个是手游发送出来的就行 。
分析过程 本人从未分析过手游,很多地方都是先学先卖的。
day 1 下载apk,jadx下载看看大致的逻辑,将apk解压缩,拿到so文件,尝试去分析。
第一天的结果就是:
相当于是收集信息了
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代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 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这个函数中,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 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中代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 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为空,并且上面的校验还不成立就打印日志
1 2 3 4 5 6 7 8 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); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 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); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 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代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 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代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 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 整理发包数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 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大概率是攻击的请求包。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 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中的函数偏移来找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 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)
参考链接