|

本帖最后由 宇智波·佐助 于 2019-4-26 09:41 编辑
调试环境:夜神模拟器、JADX、.NET Reflector、Fiddler、smsniff
apk文件:
1.分析任何app首要第一步:抓包
使用Fiddler抓包你会发现你抓不到任何有关登陆的包、这个时候可以进入logcat看看
发现log里面有很多关键信息:
2.拿到Url之后,开始分析java层,用到jadx,搜索一下关键词比如某个参数:user_name
你会发现java层根本没有这个关键词,再搜索一下log里面的tag(LOGINWORKER)也没有
3.这个时候可以考虑一下so层或者是否为WebView,你会发现,这TM也没有啊!但是别放弃,进去apk里面assets文件夹看看(用winrar软件打开apk文件可以找到),你会发现,app里面为什么会有dll?dll不是在windows上面常用的动态链接库嘛?在Linux系统里着实稀奇,于是便去研究一下是个什么东西,首先由几个dll可以判断出这是一个C#生成的dll比如mscorlib.dll
于是可以尝试一下去反编译这些dll,用到.Net Reflector
4.用.Net Reflector载入dll以后搜索关键词 LOGIN 你会找到在Logcat里看到的tag
这个时候已经知道核心功能都在一个dll上:Assembly-CSharp.dll 所以我们主要研究这个dll
找到几个关键的类:
都是Game开头的,其中,登陆Url的参数可以在GameCenterHttpHelper这个类中找到,那么登陆的分析就完成了
登陆的URL为:https://3dlogin1.jhpangu.com:8388/login?user_name={user_name}&password={password}&mac={mac}&plat_type=1&nickname=&mid=B007&sid=01&deviceType=11&version=138&loginType=0&cid=b6db31ae7f46c8f26261bdd70612c71f&simpleType=004-23
你以为这就完成了吗?
不,这只是登陆的第一步,获取token和tcp服务器的ip和端口
下一步还要用smsniff抓包,根据刚才logcat的信息,比如: Socket Connect Try 找到是在GameCenterLoginWroker.OnInit里面输出的,看代码可以知道Socket的操作在GameCenter.Post里面
进入GameCenter.Post里面分析:
- public void StartNet(ServerInfo server)
- {
- string serverIP = !server.usingIp ? Dns.GetHostEntry(server.serverHost).AddressList[0].ToString() : server.ipAdress;
- Debug.Log("[POSTOFFICE] Server IP : " + serverIP);
- GameCenter.NowGameServerSocketHost = serverIP;
- this.myMessageControl = new TRMessageControl();
- this.mySocket = new TRSocket(this.myMessageControl, serverIP, server.port);
- this.socketStart = true;
- }
复制代码
Socket被封装成了一个叫TRSOCKET的类里,进入分析初始化函数有如下方法和成员,我们研究一下发送和接收:receiveControl和sendControl函数
- public TRSocket(TRMessageControl control, string serverIP, int serverPort)
- {
- this.timeOutSign = new ManualResetEvent(false);
- this.MyServerIP = serverIP;
- this.MyServerPort = serverPort;
- this.MyMessageControl = control;
- this.whenGotoBackStage = DateTime.Now;
- this.CreateReceiveThread();
- this.isClosedOnTimeOut = false;
- }
复制代码
接收函数:- private void receiveControl()
- {
- int count = 0;
- MemoryStream stream = new MemoryStream();
- int @int = 0;
- while (true)
- {
- byte[] buffer = new byte[0x400];
- try
- {
- count = this.MySocket.Receive(buffer);
- }
- catch (Exception exception)
- {
- Debug.Log("[TRSOCKET] on ERROR when RECEIVE_CONTROL : " + exception.ToString());
- if (this.MySocket != null)
- {
- this.MySocket.Close();
- }
- this.MySocket = null;
- this.isClosedOnTimeOut = true;
- return;
- }
- if (count <= 0)
- {
- return;
- }
- stream.Position = stream.Length;
- stream.Write(buffer, 0, count);
- while (stream.Length > 4L)
- {
- stream.Position = 0L;
- byte[] buffer2 = new byte[4];
- stream.Read(buffer2, 0, 4);
- @int = Base.GetInt(buffer2);
- if (stream.Length < @int)
- {
- break;
- }
- byte[] buffer3 = new byte[@int];
- stream.Position = 0L;
- stream.Read(buffer3, 0, @int);
- int sourceIndex = 5;
- byte[] destinationArray = new byte[@int - 5];
- Array.Copy(buffer3, sourceIndex, destinationArray, 0, @int - 5);
- int num4 = ((int) stream.Length) - @int;
- byte[] buffer5 = new byte[num4];
- stream.Position = @int;
- stream.Read(buffer5, 0, num4);
- new MemoryStream { Position = 0L }.Write(buffer5, 0, num4);
- if (this.buildHashTable(destinationArray, out Hashtable hashtable))
- {
- try
- {
- this.MyMessageControl.WriteMessage(hashtable);
- }
- catch (Exception exception2)
- {
- Debug.Log("[TRSOCKET] on ERROR when WRITE_MESSAGE_CONTROL : " + exception2.ToString());
- }
- }
- }
- }
- }
复制代码
可以看出这是一个byteBuffer的操作,也就是操作字节的位置读写 先不具体分析他的逻辑,我们可以从发送函数去了解具体位置的字节是什么意思
发送函数:
- private bool sendControl(Hashtable msg)
- {
- if (this.MySocket == null)
- {
- Debug.Log("[TRSOCKET] on ERROR when SEND_CONTROL : MySocket Null");
- return false;
- }
- try
- {
- byte[] buffer = this.buildByteArray(msg);
- return (this.MySocket.Send(buffer, buffer.Length, SocketFlags.None) == buffer.Length);
- }
- catch (Exception exception)
- {
- Debug.Log("[TRSOCKET] on ERROR when SEND_CONTROL : " + exception.ToString());
- return false;
- }
- }
复制代码
发现他是由一个键值表通过buildByteArray函数解析成一个字节集,进入这个函数分析:
- private byte[] buildByteArray(Hashtable msg)
- {
- try
- {
- byte[] array = new byte[0x2000];
- int index = 0;
- IEnumerator enumerator = msg.Keys.GetEnumerator();
- try
- {
- while (enumerator.MoveNext())
- {
- string current = (string) enumerator.Current;
- byte[] bytes = Encoding.UTF8.GetBytes(current.ToCharArray());
- int length = bytes.Length;
- byte[] buffer5 = Base.ToByte(length);
- byte[] buffer6 = Encoding.UTF8.GetBytes((msg[current] as string).ToCharArray());
- int num3 = buffer6.Length;
- byte[] buffer7 = Base.ToByte(num3);
- buffer5.CopyTo(array, index);
- index += 4;
- bytes.CopyTo(array, index);
- index += length;
- buffer7.CopyTo(array, index);
- index += 4;
- buffer6.CopyTo(array, index);
- index += num3;
- }
- }
- finally
- {
- if (enumerator is IDisposable disposable)
- {
- IDisposable disposable;
- disposable.Dispose();
- }
- }
- byte[] buffer8 = Base.ToByte(index + 5);
- byte[] buffer2 = new byte[index + 5];
- buffer8.CopyTo(buffer2, 0);
- buffer2[4] = 1;
- byte[] destinationArray = new byte[index];
- Array.Copy(array, 0, destinationArray, 0, index);
- destinationArray.CopyTo(buffer2, 5);
- return buffer2;
- }
- catch (Exception exception)
- {
- Debug.LogWarning("[Network] ERROR :" + exception.ToString());
- return null;
- }
- }
复制代码
那么我们来具体分析了:
首先申请一个2000字节大小空间到变量array里面
然后创建一个index整型变量,来记录当前读写字节的位置
enumerator是保存键值表里面键的数组
然后进入循环,这个循环可以看作是计次循环,次数是键的数量,我们来研究其中一轮循环:
current是当前索引下的键名
bytes是键名的utf8编码格式下的字节集
length是bytes的长度
buffer5是length的字节集
buffer6是这个键对应的值的utf8编码格式下的字节集
num3是buffer6的长度
buffer7是num3的字节集
然后开始字节操作array,
当前位置(index)为0,写入buffer5,因为length是整型,所以buffer5长度是4位
当前位置(index)为4,写入bytes,bytes的长度是length
当前位置(index)为4+length,写入buffer7(num3是整型),buffer7的长度是4
当前位置(index)为4+length+4,写入buffer6,buffer6的长度是num3
可以分析出来其实array就是以下这样的格式
跳出循环后,
buffer8是整形(index+5)的字节集,长度为4位
申请了一个大小为index+5字节的变量buffer,这个是最终形成的字节集
将buffer8写入buffer2,现在buffer2的读写位置在4
将字节1写入buffer2,现在buffer2的读写位置在5
又申请了一个字节集变量destionArray,大小为index
将array全部写入到destionArray,即现在destionArray是和array一样的
将destionArray写入buffer2,至此buffer2已经全部写入完成
举个例子:键值表为{"test":"1234"}
buffer2为:
字节集:21{0,0,0,21,1,0,0,0,4,116,101,115,116,0,0,0,4,49,50,51,52}
{0,0,0,21}表示为buffer2的大小,整型表示21
第五位是写入的字节1
然后后面{0,0,0,4,116,101,115,116,0,0,0,4,49,50,51,52}是destionArray,也就是array的字节内容
{0,0,0,4}是键test的字节长度
{116,101,115,116}是test的字节集
{0,0,0,4}是值1234的字节长度
{49,50,51,52}是1234的字节集
那么通过分析,发送内容结构同理,也是这样
发送内容封装函数:
|
buildByteArray | 字节集 | | |
msg | 存取键值表 | | | |
变量名 | 类 型 | 静态 | 数组 | 备 注 | keys | 文本型 | | 0 | index | 整数型 | | | i | 整数型 | | | current | 文本型 | | | keyBytes | 字节集 | | | length | 整数型 | | | valueBytes | 字节集 | | | length2 | 整数型 | | | array | ByteBuffer | | | buffer2 | ByteBuffer | | | destinationArray | 字节集 | | | tmp | 字节集 | | |
keys = msg. 取键数组 () array. allocate (2000 ) 计次循环首 (取数组成员数 (keys ), i ) current = keys [i ] keyBytes = 到字节集 (current ) length = 取字节集长度 (keyBytes ) valueBytes = 到字节集 (msg. 取文本 (keys [i ]))  length2 = 取字节集长度 (valueBytes ) array. putInt (length ) array. put2 (keyBytes, array. getPosition (), length ) array. putInt (length2 ) array. put2 (valueBytes, array. getPosition (), length2 ) 计次循环尾 ()buffer2. allocate (array. getLength () + 5 )buffer2. putInt (array. getLength () + 5 )buffer2. SetPosition (4 )buffer2. putByte (1 )buffer2. SetPosition (5 )tmp = array. ToByteArray ()buffer2. put2 (tmp, 0, 取字节集长度 (tmp )) destinationArray = buffer2. ToByteArray ()buffer2. Dispose ()array. Dispose ()返回 (destinationArray )
附上源码:(键值表用了E2EE支持库,请e2ee,jimstone,com,cn/downloads/下载)
chenlong.rar
(1.85 MB, 下载次数: 63)
|
评分
-
查看全部评分
|