X猫免费小说安卓逆向分析

x猫免费小说安卓逆向分析 难度(♥)

1. 查壳

我这边使用的是ApkTool, 拖进去显示未加壳。

2. 抓包分析

POST https://xiaoshuo.xxxx.com/api/v1/login/index HTTP/1.1
net-env: 1
channel: qm-cbcpa056_wm
is-white: 0
platform: android
app-version: 50500
reg: 
application-id: com.xxxx.reader
AUTHORIZATION: 
qm-params: cLGZ4CG-uloLp3U1paHWHzpzpzpzpzpztqRTgeKQNlN2N5UzpzpztqpzpzpT4hG2Nhk-A3HjHSRUmqF5A5HwgI9wgI9wgaMMNeoTth0lgIHQpzpzp5Uzpzpz4TKMpqFnAIg5taG-pCp14lfQmqF5A5HLgIHwgh0LNh9nge4wgI0rNe9eNz4lpI0E4zFENyfepzpzNhsxgIfrNzFwgIkTgIox4egegIx5Nq4Lghgwg5HjHzUx4LHWH-oIATgEATFeA-fwA-FrAT0MH5w5OE2etCp2O5HWHTK7g3rwH5w5u_GUOEk2paU1paHWH-kUhRkokTfEBzn_k-N4OIpeczfYpExjNToWuS27uEuYhzpA3hGHm-rEgMNym_-rR-unhTxnRhHL3LQ53C2QpqnQ3ynHNhurtMJSN-QipTsiAyjLBEoAc205taG1BqR1HTZ5gefLNhgngIKrNh0rghOrH5w5BqJ-pqw5A5GFmCx2BaHjHSuj45U1BqR1HTZ5gefLNhgngIKrNh0rghOrH5w5uln5tCR1paHWHTgUgTfegh9rAIfnAI0EAaHjHzNjmqR7uaU1paHWHzN24lp54qk-gqHEglfM4qg5taG5Ozo7paHWHzuDBlujp3HjHzJxmqF5A5H56F==
sign: b27fb3051c9ee919bb7143a8f856a31f
QM-it: 1606239365
QM-ii: 1901732340
no-permiss: 0
User-Agent: webviewversion/50500
Content-Type: application/x-www-form-urlencoded
Content-Length: 124
Host: xiaoshuo.wtzw.com
Connection: Keep-Alive
Accept-Encoding: gzip

cancell_check=1&encrypt_phone=ghgUNIFENeKrNhf=&gender=2&open_push=1&type=1&verify=1111&sign=3a7c6107895fe47d8cfb7647d21a8c86

发现如果我们想要登录, 至少需要解决 qm-paramsencrypt_phone,sign

由于没有加固, 那么我们从提交数据开始.

3. sign

jadx反编译, 一些魅族华为的包都不用看, 就看可以的。 可以定位到

f.f.e.b.c.a.c这个class.

if (this.f32692a.a(url.host())) {
                HashMap<String, String> b2 = this.f32692a.b();
                TreeMap treeMap = new TreeMap();
                if (b2 != null) {
                    for (String str : b2.keySet()) {
                        String str2 = b2.get(str);
                        if (str2 != null) {
                            newBuilder.addHeader(str, str2);
                            treeMap.put(str, str2);
                        }
                    }
                }
                String y = f.f.e.b.d.a.m().y();
                if (!TextUtils.isEmpty(y)) {
                    newBuilder.addHeader("qm-params", y);
                    treeMap.put("qm-params", y);
                }
                if (!treeMap.isEmpty()) {
                    StringBuilder sb = new StringBuilder();
                    for (String str3 : treeMap.keySet()) {
                        sb.append(str3);
                        sb.append("=");
                        sb.append((String) treeMap.get(str3));
                    }
                    newBuilder.addHeader("sign", Encryption.sign(sb.toString()));
                }
                try {
                    if (!(this.f32693b == null || (m = this.f32693b.m()) == null || m.length <= 0)) {
                        for (String str4 : m) {
                            String string = this.f32693b.getString(str4, "");
                            if (!TextUtils.isEmpty(str4) && !TextUtils.isEmpty(string)) {
                                newBuilder.addHeader(TextUtil.appendStrings("QM-", str4), string);
                            }
                        }
                    }
                    newBuilder.addHeader("no-permiss", a());
                } catch (Exception unused) {
                }

向下跟踪

package com.qimao.qmsdk.tools.encryption;

import com.km.encryption.api.Security;
import java.io.UnsupportedEncodingException;
import org.geometerplus.fbreader.book.Encoding;

public class Encryption {
    public static void init() {
    }

    public static String sign(String str) {
        try {
            return Security.sign(str.getBytes(Encoding.UTF8_NATIVE));
        } catch (UnsupportedEncodingException unused) {
            return "";
        }
    }
}

最后跟到

package com.km.encryption.api;

import android.content.Context;
import com.km.encryption.generator.KeyGenerator;

public class Security {
    public static void a(Context context, String str) {
        KeyGenerator.assetManager = context.getAssets();
        KeyGenerator.key = str;
        KeyGenerator.context = context;
    }

    public static native byte[] decode(String str);

    public static native String decrypt(String str, String str2);

    public static native String encrypt(String str, String str2);

    public static native void init();

    public static native String sign(byte[] bArr);

    public static native SecurityEntity token(String str);
}

然后使用objection来hook一下。

android hooking watch class_method com.km.encryption.api.Security.sign --dum
p-args --dump-return --dump-backtrace

发现的确是这边,但是objection不能显示byte[]类型,因此需要自己写frida代码


function byteToString(arr) {
    if(typeof arr === 'string') {
        return arr;
    }
    var str = '',
        _arr = arr;
    for(var i = 0; i < _arr.length; i++) {
        var one = _arr[i].toString(2),
            v = one.match(/^1+?(?=0)/);
        if(v && one.length == 8) {
            var bytesLength = v[0].length;
            var store = _arr[i].toString(2).slice(7 - bytesLength);
            for(var st = 1; st < bytesLength; st++) {
                store += _arr[st + i].toString(2).slice(2);
            }
            str += String.fromCharCode(parseInt(store, 2));
            i += bytesLength - 1;
        } else {
            str += String.fromCharCode(_arr[i]);
        }
    }
    return str;
}


function startHook() {
    Java.perform(function() {
        var Security = Java.use('com.km.encryption.api.Security'); //要hook的类名完整路径
        Security.sign.implementation = function(x) { // 重写要hook的方法Sign,当有多个重名函数时需要重载,function括号为函数的参数个数
            console.log("---> Security.Sign Start")
            console.log("---> Arg[0]:" + byteToString(x) );
            console.log("---> Result:" + this.sign(x));
            console.log("---> Security.Sign end")
            return this.sign(x)
        };
    })
}

setImmediate(startHook)

于是乎执行命令

frida -U -f com.kmxs.reader -l demo01.js --no-pause
---> Security.Sign Start
---> Arg[0]:cancell_check=1encrypt_phone=ghgUNIFENeKrAIK=gender=2open_push=1type=1verify=1111
---> Result:1b75f39c2c27315b38ef5926a4beaac3
---> Security.Sign end
---> Security.Sign Start
---> Arg[0]:cancell_check=1encrypt_phone=ghgUNIFENeKrAIK=gender=2open_push=1type=1verify=1111
---> Result:1b75f39c2c27315b38ef5926a4beaac3
---> Security.Sign end
---> Security.Sign Start
---> Arg[0]:cancell_check=1encrypt_phone=ghgUNIFENeKrAIK=gender=2open_push=1type=1verify=1111
---> Result:1b75f39c2c27315b38ef5926a4beaac3
---> Security.Sign end

此时,我们已经用frida hook的方式, 可以拿到提交参数和返回结果.此时就可以衍生出方案1:Frida -rpc

1. Frida-rpc

参考肉丝姐姐的代码

原文地址:https://www.anquanke.com/post/id/195215

可以把我们的js文件改成


function byteToString(arr) {
    if(typeof arr === 'string') {
        return arr;
    }
    var str = '',
        _arr = arr;
    for(var i = 0; i < _arr.length; i++) {
        var one = _arr[i].toString(2),
            v = one.match(/^1+?(?=0)/);
        if(v && one.length == 8) {
            var bytesLength = v[0].length;
            var store = _arr[i].toString(2).slice(7 - bytesLength);
            for(var st = 1; st < bytesLength; st++) {
                store += _arr[st + i].toString(2).slice(2);
            }
            str += String.fromCharCode(parseInt(store, 2));
            i += bytesLength - 1;
        } else {
            str += String.fromCharCode(_arr[i]);
        }
    }
    return str;
}

function stringToByte(str) {
    var bytes = new Array();
    var len, c;
    len = str.length;
    for(var i = 0; i < len; i++) {
        c = str.charCodeAt(i);
        if(c >= 0x010000 && c <= 0x10FFFF) {
            bytes.push(((c >> 18) & 0x07) | 0xF0);
            bytes.push(((c >> 12) & 0x3F) | 0x80);
            bytes.push(((c >> 6) & 0x3F) | 0x80);
            bytes.push((c & 0x3F) | 0x80);
        } else if(c >= 0x000800 && c <= 0x00FFFF) {
            bytes.push(((c >> 12) & 0x0F) | 0xE0);
            bytes.push(((c >> 6) & 0x3F) | 0x80);
            bytes.push((c & 0x3F) | 0x80);
        } else if(c >= 0x000080 && c <= 0x0007FF) {
            bytes.push(((c >> 6) & 0x1F) | 0xC0);
            bytes.push((c & 0x3F) | 0x80);
        } else {
            bytes.push(c & 0xFF);
        }
    }
    return bytes;


}


function getsign(args){
    var result = ''
    Java.perform(function(){
        var Security = Java.use('com.km.encryption.api.Security');
        result = Security.sign(stringToByte(args));
        console.log("rpc getSign:" + result);
         
    })
    return result;
}

rpc.exports = {
    sign : getsign
}
//setImmediate(startHook)

创建一个python文件

import frida

def on_message(message, data):
    if message['type'] == 'send':
        print(message['payload'])
    elif message['type'] == 'error':
        print(message['stack'])



source = ""
with open('demo01.js',encoding= 'utf-8') as f:
    source = f.read()

print(source)
session = frida.get_usb_device().attach('com.kmxs.reader')
script = session.create_script(source)
script.on('message', on_message)
script.load()
print(script.exports.sign('cancell_check=1encrypt_phone=ghgUNIFENeKrAIK=gender=2open_push=1type=1verify=1111'))

session.detach()

测试没问题以后, 我们配合flask 编写一个api接口

from flask import Flask,jsonify,request
import frida
app = Flask(__name__)
tasks = {
        'taskId':1,
        'encryptData' : u'xiaopang',
        'sign':'sign'
    }

def on_message(message, data):
    if message['type'] == 'send':
        print(message['payload'])
    elif message['type'] == 'error':
        print(message['stack'])



source = ""
with open('demo01.js',encoding= 'utf-8') as f:
    source = f.read()

print(source)
session = frida.get_usb_device().attach('com.kmxs.reader')
script = session.create_script(source)
script.on('message', on_message)
script.load()


@app.route('/getSign',methods=['POST'])
def index():
    if request.method == 'POST':
        encryptData = request.form.get('encryptData')
        data = tasks
        data['sign'] = script.exports.sign(encryptData)
        data['encryptData'] = encryptData
        return jsonify({'data':tasks})

if __name__ == '__main__':
    app.run(host='0.0.0.0',debug=True)

此时其他的其实就和这个sign差不多了, 找到位置,rpc就好了!能用就好!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值