传奇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)