安卓逆向算法分析之华住会返回字节流分析

抓取登陆请求

POST https://appapi.huazhu.com:8443//local/guest/Login/android/[email protected]@1/zh HTTP/1.1

X-Tingyun-Id: QFaFiyYOAwQ;c=2;r=1107668796;

X-Tingyun-Lib-Type-N-ST: 2;1531900932417

Content-Length: 1103

Content-Type: application/x-www-form-urlencoded

Host: appapi.huazhu.com:8443

Connection: Keep-Alive

User-Agent: HUAZHU/6.0 (Linux; Android LGE; Build/MRA58K)

Accept-Encoding: gzip

Accept-Charset: utf-8

 

data=vr%2BVqcFYQ%2BFuUy6FjwMaUX02ZYHnHTGQqT2LNs6q8lnU02ymMrLGGTZJ5hukj2r84JtL3aWvpXeOuD4YwrTViyCE0FEbWst0RGGZSfVITB95keW7gU6LRNg%2FlcXbASwJTe85ghqxhPr3NFVvbuog0zxqieIJy9UyM7%2BBLc8icH3BBa4fVO4vdpZUfAnxuQcBUQufSfuuBi2sameLWpoMZk%2BhCXuQI9sCutRdhr9fC9WplfYnWRGEpLS5XAg4Rfq7AGKvaNcK%2BhJrELwmR9redk61dAunxtc5dOUokO8Tomxy44FWyC2b3uuuhZ7I70z2AnYknFo2uyPeiA7jZs8tzKzmHQv4FqaIPZZxOm900ez%2BS%2BExq3W1AJFscuRC6t44D2vhz8L4TgFc5405pCy2XI0y16o7gf2la7hsEyMdmBLBfmpaTdDV3hDuhGX0Qi1EaedRlkiiXG%2BS2Rl%2B2tgirOMDSkRIMcpXRO%2B4T25Y6SOgniKaXw1cAVQGWkWNoSoH2vdHQ7MXUUTDgRk5VOxE6f0dg5vobO4eBICQ%2Fqq9zwPr9HxhJ%2FmPa4vAeGGpUJChlo%2F40yLXeIL1OK3vnIi3v3Xl8kMIpwpFUl9ni9FWjY5nEeG%2B8r%2FpWImGk%2FHMjNEfKCGscgoyPi9jAarN0eofROhPk456Z0UOeJxTbN6P0nHvR6Xsg1vLHV%2FZG4RuuZ4AgngQ1uAv8mlv2%2B3ZUud3KtRw3%2FKvJJJ3VP5dKOPmc2zGc2i3UPM42unRHAjRAF%2Bto%2Fw5mFe3dspZAkQ4nPM%3D&sign=LUKJeLdXM4IYGqaJ%2FxVNIQ%3D%3D&APPSIGN=AAjA8gAE4BEACZQxAArJaQAB1PUACMDyAAKx5AAG0AsABSjTAAEIqgAChJgABj8tAASmWgAGDcAA%0ABuKPAArJaQAIwcYABstVAAlpCQAFPh8AAoSYAAoaVgAK3TcACt03AAWOewAE6LcABoFJAAB5hgAH%0AjAMAA1BzAAOgdQAJI4wACSOMAAX%2FZQ%3D%3D%0A&time=20180718160212

 

HTTP/1.1 200 OK

Cache-Control: private

Content-Length: 131

Content-Type: application/octet-stream

Server: Microsoft-IIS/7.5

X-AspNetMvc-Version: 4.0

Api-Key: ok

X-AspNet-Version: 4.0.30319

Date: Wed, 18 Jul 2018 08:02:11 GMT

 

%T)%8?U_
"Uxoˇ{O<.0Ţ\)Jn2p-<[݁
_t

Av_~C%yDM  8<ȰGz">M^$_&U3_0

 

 

安卓逆向算法分析之华住会返回字节流分析

 

请求正文中,一共4个字段

Data, sign, APPSIGN, time

App没壳,直接jadx反编译

 

就搜下sign吧

安卓逆向算法分析之华住会返回字节流分析

安卓逆向算法分析之华住会返回字节流分析

 

右击,选择查看使用

安卓逆向算法分析之华住会返回字节流分析

 

安卓逆向算法分析之华住会返回字节流分析

一共有4处引用,由于使用的是http请求,选第二个看看

直接来到了这里

安卓逆向算法分析之华住会返回字节流分析

可以看到,下面箭头处是构建map

上面箭头处NewGetString.a 是调用了生成sign密文的方法,跟过去看一眼

安卓逆向算法分析之华住会返回字节流分析

可以看出,它进入了这个so库hzsign,的getNewStr方法

好了,这里要求是不去具体分析它的加密流程,我们回到上上图的方法处,查看它的引用

安卓逆向算法分析之华住会返回字节流分析

一共有4处引用,随便挑第一个过去看看

安卓逆向算法分析之华住会返回字节流分析

来到了这个类的上面这个a方法中,红框处就是调用的构建map的方法

 

可见,这个i.a方法,一共有三个参数

Str2,构建map的a方法返回值,后面还有一个b对象

 

双击str2,可看到它是在哪些地方构造的

安卓逆向算法分析之华住会返回字节流分析

 

下面有一句提示没有网络,说明它在这里不会去做网络请求,那么,上面的if语句代码块中,return上面一句,猜测就应该做网络请求的

 

安卓逆向算法分析之华住会返回字节流分析

 

到这里,根据安卓的编写习惯,猜测new b(context2, requestInfo, str, i)就是请求之后的回调函数了

安卓逆向算法分析之华住会返回字节流分析

 

按住Ctrl键,鼠标左键点b,跟过去看看b这个类

安卓逆向算法分析之华住会返回字节流分析

根据上上图,new b 时候带入的参数,可以判断它调用的是第二个构造函数

把相应的数据写入了类成员变量中

 

看到这个b类是继承自d类,看看d类,就更加坚定,这是http请求的回调函数

安卓逆向算法分析之华住会返回字节流分析

 

好了,既然这个b类是回调方法所在的类,那我们就继续往下看看

 

安卓逆向算法分析之华住会返回字节流分析

好吧,这方法,都是对请求状态的判断,以及相应的处理方法

继续往下,到了这个方法,看到它带入的是一个HttpResponse,就是http请求返回对象,这个对象,封装了,http请求返回的所有内容,而我们抓请求的时候,看到它返回的是

Content-Type: application/octet-stream

octet-stream数据流的形式,看来这里有料,

安卓逆向算法分析之华住会返回字节流分析

 

StatusLine statusLine = httpResponse.getStatusLine();

读取返回的状态码

HttpEntity entity = httpResponse.getEntity();

得到返回的Entity

Header firstHeader = httpResponse.getFirstHeader("Api-Key");

读取apiKey

httpResponse.getFirstHeader("Content-Encoding");

读取编码方式

if (firstHeader == null || !firstHeader.getValue().equals("ok")) {

     str = EntityUtils.toString(new BufferedHttpEntity(entity), c());}

如果返回的协议头为空,或者状态码不为OK,执行后面这一句代码

 

那么else语句中就是重头戏了?我们继续看

安卓逆向算法分析之华住会返回字节流分析

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

new 一个output字节流

InputStream content = entity.getContent();

拿到上下文

byte[] bArr = new byte[256];

构建一个256长度的缓冲字节数组

安卓逆向算法分析之华住会返回字节流分析

读取数据,存放到byteArrayOutputStream中

 

最后,byteArrayOutputStream进入了这里

str = j.a(r.a(byteArrayOutputStream.toByteArray(), this.a));

 

 

先看看r.a方法所在的类

package com.htinns.Common;



import com.umetrip.umesdk.helper.ConstNet;



/* compiled from: RC4Factory */

public class r {

    public static byte[] a(byte[] bArr, String str) {

        if (bArr == null || str == null) {

            return null;

        }

        return b(bArr, str);

    }



    public static byte[] a(String str, String str2) {

        if (str == null || str2 == null) {

            return null;

        }

        return b(str.getBytes(), str2);

    }



    public static String b(String str, String str2) {

        if (str == null || str2 == null) {

            return null;

        }

        return c.b(a(str, str2), false);

    }



    private static byte[] a(String str) {

        int i;

        int i2 = 0;

        byte[] bytes = str.getBytes();

        byte[] bArr = new byte[256];

        for (i = 0; i < 256; i++) {

            bArr[i] = (byte) i;

        }

        if (bytes == null || bytes.length == 0) {

            return null;

        }

        i = 0;

        int i3 = 0;

        while (i2 < 256) {

            i = (i + ((bytes[i3] & ConstNet.REQ_GetCheckinURL) + (bArr[i2] & ConstNet.REQ_GetCheckinURL))) & ConstNet.REQ_GetCheckinURL;

            byte b = bArr[i2];

            bArr[i2] = bArr[i];

            bArr[i] = b;

            i3 = (i3 + 1) % bytes.length;

            i2++;

        }

        return bArr;

    }



    private static byte[] b(byte[] bArr, String str) {

        if (bArr == null) {

            return null;

        }

        byte[] a = a(str);

        byte[] bArr2 = new byte[bArr.length];

        int i = 0;

        int i2 = 0;

        for (int i3 = 0; i3 < bArr.length; i3++) {

            int i4;

            byte b;

            i2 = (i2 + 1) & ConstNet.REQ_GetCheckinURL;

            if (a != null) {

                i4 = a[i2];

            } else {

                i4 = 0;

            }

            i = (i + (i4 & ConstNet.REQ_GetCheckinURL)) & ConstNet.REQ_GetCheckinURL;

            if (a != null) {

                b = a[i2];

            } else {

                b = (byte) 0;

            }

            if (a != null) {

                a[i2] = a[i];

                a[i] = b;

                i4 = ((a[i2] & ConstNet.REQ_GetCheckinURL) + (a[i] & ConstNet.REQ_GetCheckinURL)) & ConstNet.REQ_GetCheckinURL;

                bArr2[i3] = (byte) (a[i4] ^ bArr[i3]);

            }

        }

        return bArr2;

    }

}

 

 

再看看外层的j.a方法

安卓逆向算法分析之华住会返回字节流分析

 

可以看出内层的r.a对byteArrayOutputStream进行了一系列处理,然后把结果带入了j.a方法

这个j.a静态方法,可以看出是gzip算法

通过这个算法出来的数据就是String类型的明文了,

它再通过其它方法,对这些消息进行提示或者什么的,就不管它了

 

 

这里就不去调试j.a方法了,直接写插件hook吧,检验一下分析是否正确安卓逆向算法分析之华住会返回字节流分析

 

生成apk,安装重启手机,打开app,随意输入一个帐号密码登陆,

安卓逆向算法分析之华住会返回字节流分析

 

这时候,用as查看一下日志

安卓逆向算法分析之华住会返回字节流分析

可见分析正确,除了登陆这条请求返回的提示信息,打开app其它请求的返回信息也一并打印出来了。至此,分析完毕。

 

总结下它返回的字节流解密流程

 

 

  1. 把字节流带入了com.htinns.Common.r.a方法
  2. 把第一步的结果带入gzip(com.htinns.Common.j.a方法)解压

 

调用处在com.htinns.biz.b类的a方法中

安卓逆向算法分析之华住会返回字节流分析