传奇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代码:

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)

参考链接