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

抓取登陆请求

POST https://appapi.huazhu.com:8443//local/guest/Login/android/7@5@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方法中

猜你喜欢

转载自blog.csdn.net/g5703129/article/details/85112873
今日推荐