Cond0r 发布的文章

无需重新编译OpenSSH,通过ptrace注入记录SSHD密码

本来想自己研究一下然后写一个,在查资料的时候发现别人已经研究写出了成型的demo,原文可以查看
https://blog.xpnsec.com/linux-process-injection-aka-injecting-into-sshd-for-fun/
国内缓存:https://buaq.net/go-25514.html
工具链接:https://github.com/xpn/ssh-inject

原文就不翻译了,说一下用法和几个需要修改地方
他的逻辑大概是先通过hook libdl.so实现注入一个so文件到sshd里面,然后在通过这个so去hook auth_password实现密码记录。

首先需要先查看当前机器的sshd的libdl版本,

ps aux|grep sshd #获取pid


查看其调用的libdl版本

修改ssh-inject/inject.c中的libdl部分代码

...
void *libdlAddr = NULL;

    libdlAddr = dlopen("libdl-2.19.so", RTLD_LAZY);
    ....
    remoteLib = findLibrary("libdl-2.19", atoi(argv[1]));
   ...
    ...
    localLib = findLibrary("libdl-2.19", -1); //

...

把这三个位置的libdl-2.19改成之前查看出来的libdl-2.17即可
然后临时关闭SElinux,否则会导致注入失败

setenforce 0 #临时关闭SELinux
./run.sh 23189 #sshd的pid

记录到的密码默认写到tmp目录下

先抓包获得提交订单的请求如下

可以看到签名的参数在header里面,名字叫做x-sign,jadx搜索一下


然后在接着搜索X_SIGN
然后根据名字猜测到在这个位置

函数如下

public String getSign(HashMap<String, String> params, String appKey) {
        String str;
        UnsupportedEncodingException e;
        SecException e2;
        String str2 = "";
        String str3 = (String) mo28972a((Map<String, String>) params, appKey).get("INPUT");
        try {
            if (this.f13463e == null || StringUtils.isBlank(str3)) {
                TBSdkLog.m3641e("mtopsdk.InnerSignImpl", mo44553c() + " [getSign]middleTier null or data data ", "appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode);
                return "";
            }
            HashMap hashMap = new HashMap();
            hashMap.put("data", str3.getBytes("UTF-8"));
            hashMap.put("env", Integer.valueOf(m13472d()));
            hashMap.put("appkey", appKey);
            HashMap sign = this.f13463e.getSign(hashMap);
            if (sign == null || sign.isEmpty()) {
                TBSdkLog.m3641e("mtopsdk.InnerSignImpl", mo44553c() + " [getSign]get sign failed with no output ", "appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode);
                return "";
            }
            str = (String) sign.remove(HttpHeaderConstant.X_SIGN);
            try {
                if (!StringUtils.isNotBlank(str)) {
                    return str;
                }
                params.putAll(sign);
                return str;
            } catch (UnsupportedEncodingException e3) {
                e = e3;
                TBSdkLog.m3642e("mtopsdk.InnerSignImpl", mo44553c() + " [getSign]your input data transfer to byte utf-8 failed ", "appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode, e);
                return str;
            } catch (SecException e4) {
                e2 = e4;
                TBSdkLog.m3643e("mtopsdk.InnerSignImpl", mo44553c() + " [getSign]get sign failed and SecException errorCode " + e2.getErrorCode() + ",appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode, (Throwable) e2);
                return str;
            }
        } catch (UnsupportedEncodingException e5) {
            UnsupportedEncodingException unsupportedEncodingException = e5;
            str = str2;
            e = unsupportedEncodingException;
            TBSdkLog.m3642e("mtopsdk.InnerSignImpl", mo44553c() + " [getSign]your input data transfer to byte utf-8 failed ", "appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode, e);
            return str;
        } catch (SecException e6) {
            SecException secException = e6;
            str = str2;
            e2 = secException;
            TBSdkLog.m3643e("mtopsdk.InnerSignImpl", mo44553c() + " [getSign]get sign failed and SecException errorCode " + e2.getErrorCode() + ",appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode, (Throwable) e2);
            return str;
        }
    }

不用去关心他到底怎么计算签名,直接写一段hook看一下是否用的这个函数进行签名

    setTimeout(function() {
    Java.perform(function() {


        var HttpRequestEntity = Java.use('mtopsdk.security.b'); //要hook的类名完整路径
        // var func = new HttpRequestEntity();
        // func.signature('1', '13123', false);
        HttpRequestEntity.getSign.implementation = function(arg1, arg2) { // 重写要hook的方法getSign,当有多个重名函数时需要重载,function括号为函数的参数个数

            var Sign = this.getSign(arg1, arg2); //调用原始的函数实现并且获得返回值,如果不写的话我们下面的代码会全部替换原函数

            send("arg1:" + arg1); //打印参数值
            send("arg2:" + arg2);


            send("result:" + Sign); //打印返回值

            return Sign; //函数有返回值时需要返回
        };

    })

});
➜  x frida-ps -U |grep damai
16738  cn.damai
17005  cn.damai:channel
➜  x frida -l damai.js -U -f cn.damai --no-pause
     ____
    / _  |   Frida 12.7.22 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://www.frida.re/docs/home/
Spawned `cn.damai`. Resuming main thread!
message: {'type': 'send', 'payload': 'arg1:{data={"itemId":"613369982548","scenario":"itemsku","channel_from":"damai_market","bizCode":"ali.china.damai","appType":"1","osType":"2","exParams":"{}","source":"10101","version":"6000076"}, lng=118.818906, utdid=XCquBhVOGfwDAMHCRzpMcJHX, ttid=10005890@damai_android_7.7.2, deviceId=Auw_p33fvcb-qYhQnFUd4IAw1zJfUPYAfdVSWlF8OCH7, sid=149c5f8c17bf88a759f4ec5ebc6ae27a, uid=2206462755405, x-features=27, t=1584889999, v=2.0, appKey=23781390, api=mtop.alibaba.detail.subpage.getdetail, lat=32.107127}'} data: None
message: {'type': 'send', 'payload': 'arg2:23781390'} data: None
message: {'type': 'send', 'payload': 'result:ab25b000902a1cb73174adb1f6ffd602c2b885dd8e6efbc4ec'} data: None

看参数基本就是这个函数了,我们再把之前抓包抓到的参数拿去签名然后对比一下结果是否一样校验一下
在看一下sign的这个类,其实还有一个init初始化函数

   public void init(@NonNull MtopConfig mtopConfig) {
        super.init(mtopConfig);
        String c = mo44553c();
        try {
            SignStatistics.m23759a(mtopConfig.uploadStats);
            long currentTimeMillis = System.currentTimeMillis();
            this.f13461c = SecurityGuardManager.getInstance(this.f23497a.context);
            m13470a(StringUtils.isEmpty(mtopConfig.appKey) ? m13468a(mtopConfig.appKeyIndex, mo44552b()) : mtopConfig.appKey, mo44552b());
            MtopSDKThreadPoolExecutorFactory.submit(new InnerSignImpl$1(this, this.f23497a.context, c));
            m13471a(mtopConfig);
            if (TBSdkLog.isLogEnable(LogEnable.InfoEnable)) {
                TBSdkLog.m3644i("mtopsdk.InnerSignImpl", c + " [init]ISign init SecurityGuard succeed.init time=" + (System.currentTimeMillis() - currentTimeMillis));
            }
        } catch (SecException e) {
            int errorCode = e.getErrorCode();
            SignStatistics.m23758a(SignStatsType.TYPE_SG_MANAGER, String.valueOf(errorCode), "");
            TBSdkLog.m3643e("mtopsdk.InnerSignImpl", c + " [init]ISign init SecurityGuard error.errorCode=" + errorCode, (Throwable) e);
        } catch (Exception e2) {
            TBSdkLog.m3643e("mtopsdk.InnerSignImpl", c + " [init]ISign init SecurityGuard error.", (Throwable) e2);
        }
    }

我们在写一个hook看一下传进来的参数是什么

setTimeout(function() {
    Java.perform(function() {


        var HttpRequestEntity = Java.use('mtopsdk.security.b'); //要hook的类名完整路径
        // var func = new HttpRequestEntity();
        // func.signature('1', '13123', false);
        HttpRequestEntity.init.implementation = function(arg1) { // 重写要hook的方法getSign,当有多个重名函数时需要重载,function括号为函数的参数个数

            var Sign = this.init(arg1); //调用原始的函数实现并且获得返回值,如果不写的话我们下面的代码会全部替换原函数

            send("arg1:" + arg1); //打印参数值
            //mtopsdk.mtop.global.MtopConfig


            send("result:" + Sign); //打印返回值

            return Sign; //函数有返回值时需要返回
        };

    })

});

执行结果如下

➜  x frida -l init.js -U -f cn.damai --no-pause
     ____
    / _  |   Frida 12.7.22 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://www.frida.re/docs/home/
Spawned `cn.damai`. Resuming main thread!
[Samsung SM-G9550::cn.damai]-> message: {'type': 'send', 'payload': 'arg1:mtopsdk.mtop.global.MtopConfig@c1b5599'} data: None

可以看到传进来的是mtopsdk.mtop.global.MtopConfig这么一个类,那么我们来看一下这个类
这个类初始化的时候也有一个参数

  public MtopConfig(String instanceId2) {
        this.instanceId = instanceId2;
    }

再写一个hook看一下

setTimeout(function() {
    Java.perform(function() {


        var HttpRequestEntity = Java.use('mtopsdk.mtop.global.MtopConfig'); //要hook的类名完整路径
        // var func = new HttpRequestEntity();
        // func.signature('1', '13123', false);
        HttpRequestEntity.$init.implementation = function(arg1) { // 重写要hook的方法getSign,当有多个重名函数时需要重载,function括号为函数的参数个数

            var Sign = this.$init(arg1); //调用原始的函数实现并且获得返回值,如果不写的话我们下面的代码会全部替换原函数

            send("arg1:" + arg1); //打印参数值
            //mtopsdk.mtop.global.MtopConfig


            send("result:" + Sign); //打印返回值

            return Sign; //函数有返回值时需要返回
        };

    })

});

结果如下

➜  x frida -l config.js -U -f cn.damai --no-pause
     ____
    / _  |   Frida 12.7.22 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://www.frida.re/docs/home/
Spawned `cn.damai`. Resuming main thread!
[Samsung SM-G9550::cn.damai]-> message: {'type': 'send', 'payload': 'arg1:INNER'} data: None
message: {'type': 'send', 'payload': 'result:undefined'} data: None
message: {'type': 'send', 'payload': 'arg1:OPEN'} data: None
message: {'type': 'send', 'payload': 'result:undefined'} data: None
message: {'type': 'send', 'payload': 'arg1:INNER'} data: None
message: {'type': 'send', 'payload': 'result:undefined'} data: None
message: {'type': 'send', 'payload': 'arg1:OPEN'} data: None

可以看到要么是OPEN,要么是INNER,那么就非常明了了,我们写一个脚本把参数带进去签名一下对比一下结果
这是原始参数

message: {'type': 'send', 'payload': 'arg1:{data={"itemId":"613369982548","scenario":"itemsku","channel_from":"damai_market","bizCode":"ali.china.damai","appType":"1","osType":"2","exParams":"{}","source":"10101","version":"6000076"}, lng=118.818906, utdid=XCquBhVOGfwDAMHCRzpMcJHX, ttid=10005890@damai_android_7.7.2, deviceId=Auw_p33fvcb-qYhQnFUd4IAw1zJfUPYAfdVSWlF8OCH7, sid=149c5f8c17bf88a759f4ec5ebc6ae27a, uid=2206462755405, x-features=27, t=1584889999, v=2.0, appKey=23781390, api=mtop.alibaba.detail.subpage.getdetail, lat=32.107127}'} data: None
message: {'type': 'send', 'payload': 'arg2:23781390'} data: None
message: {'type': 'send', 'payload': 'result:ab25b000902a1cb73174adb1f6ffd602c2b885dd8e6efbc4ec'} data: None

脚本如下

import frida, sys
from urllib import parse
from time import sleep

def on_message(message, data):
    global result
    result=message
    print('----------------------------------------------------------------')
    print(message,data)
        
jscode = """
function sign(args) {
    Java.perform(function () {
        var HashMap = Java.use('java.util.HashMap');
        var hm = HashMap.$new();
        var hm1=HashMap.$new();

        var config = Java.use('mtopsdk.mtop.global.MtopConfig');
        var mconfig=config.$new("INNER");
        console.log(args)
        hm1.put("buyNow","true");
        hm1.put("exParams",args);
        hm1.put("buyParam","613369982548_1_4314464576860")

        hm.put("data",args);
        hm.put("lng","118.818906");
        hm.put("utdid","XCquBhVOGfwDAMHCRzpMcJHX");
        hm.put("ttid","10005890@damai_android_7.7.2")
        hm.put("deviceId","Auw_p33fvcb-qYhQnFUd4IAw1zJfUPYAfdVSWlF8OCH7")
        hm.put("sid","149c5f8c17bf88a759f4ec5ebc6ae27a")
        hm.put("uid","2206462755405")
        hm.put("x-features","27")
        hm.put("t","1584889999")
        hm.put("v","2.0")
        hm.put("appKey","23781390")
        hm.put("api","mtop.alibaba.detail.subpage.getdetail")
        hm.put("lat","32.107127")
        console.log(hm)
        var HttpRequestEntity = Java.use('mtopsdk.security.b');
        var func = HttpRequestEntity.$new()
        func.init(mconfig);
        var result = func.getSign(hm,"23781390")
        send("sign:" + result);
        return result;
    })
}
function onMessage(pokeMessage) {
    sign(pokeMessage['line']);
    recv('sign', onMessage);
}
recv('sign', onMessage);
"""
process = frida.get_usb_device().attach('cn.damai')
result=[]
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
# params='''{"buyNow":"true","exParams":"{\"umpChannel\":\"10001\",\"coupon\":\"true\",\"websiteLanguage\":\"zh_CN_#Hans\",\"channel\":\"damai_app\",\"atomSplit\":\"1\",\"seatInfo\":\"\",\"coVersion\":\"2.0\"}","buyParam":"613369982548_1_4314464576860"}'''
params='''{"buyNow":"true","exParams":"{\\"umpChannel\\":\\"10001\\",\\"coupon\\":\\"true\\",\\"websiteLanguage\\":\\"zh_CN_#Hans\\",\\"channel\\":\\"damai_app\\",\\"atomSplit\\":\\"1\\",\\"seatInfo\\":\\"\\",\\"coVersion\":\\"2.0\\"}","buyParam":"613369982548_1_4314464576860"}'''
# params='''"{\\"umpChannel\\":\\"10001\\",\\"coupon\\":\\"true\\",\\"websiteLanguage\\":\\"zh_CN_#Hans\\",\\"channel\\":\\"damai_app\\",\\"atomSplit\\":\\"1\\",\\"seatInfo\\":\\"\\",\\"coVersion\":\\"2.0\\"}"'''
# print(params)
params ='''{"itemId":"613369982548","scenario":"itemsku","channel_from":"damai_market","bizCode":"ali.china.damai","appType":"1","osType":"2","exParams":"{}","source":"10101","version":"6000076"}'''
script.post({"type": "sign",'line':params})

'''
lng = 118.818534, utdid = XCquBhVOGfwDAMHCRzpMcJHX, 
ttid = 10005890 @damai_android_7 .7 .2, 
deviceId = Auw_p33fvcb - qYhQnFUd4IAw1zJfUPYAfdVSWlF8OCH7, 
sid = 149 c5f8c17bf88a759f4ec5ebc6ae27a, 
uid = 2206462755405, x - features = 27, t = 1584888568, v = 4.0, appKey = 23781390, 
api = mtop.trade.order.build, lat = 32.106986
'''
while True:
    if result!=[]:
        print(result)
        print('over')
        break
    sleep(1)

'''
message: {'type': 'send', 'payload': 'arg1:{data={"itemId":"613369982548","scenario":"itemsku","channel_from":"damai_market","bizCode":"ali.china.damai","appType":"1","osType":"2","exParams":"{}","source":"10101","version":"6000076"}
, lng=118.818906,
 utdid=XCquBhVOGfwDAMHCRzpMcJHX, ttid=10005890@damai_android_7.7.2, deviceId=Auw_p33fvcb-qYhQnFUd4IAw1zJfUPYAfdVSWlF8OCH7, 
 sid=149c5f8c17bf88a759f4ec5ebc6ae27a, uid=2206462755405, x-features=27, t=1584889999, v=2.0, appKey=23781390, api=mtop.alibaba.detail.subpage.getdetail, lat=32.107127}'} data: None
message: {'type': 'send', 'payload': 'arg2:23781390'} data: None
message: {'type': 'send', 'payload': 'result:ab25b000902a1cb73174adb1f6ffd602c2b885dd8e6efbc4ec'} data: None
'''

最新结果如下

➜  x python3 test.py
{"itemId":"613369982548","scenario":"itemsku","channel_from":"damai_market","bizCode":"ali.china.damai","appType":"1","osType":"2","exParams":"{}","source":"10101","version":"6000076"}
{data={"itemId":"613369982548","scenario":"itemsku","channel_from":"damai_market","bizCode":"ali.china.damai","appType":"1","osType":"2","exParams":"{}","source":"10101","version":"6000076"}, lng=118.818906, utdid=XCquBhVOGfwDAMHCRzpMcJHX, ttid=10005890@damai_android_7.7.2, deviceId=Auw_p33fvcb-qYhQnFUd4IAw1zJfUPYAfdVSWlF8OCH7, sid=149c5f8c17bf88a759f4ec5ebc6ae27a, uid=2206462755405, x-features=27, t=1584889999, v=2.0, appKey=23781390, api=mtop.alibaba.detail.subpage.getdetail, lat=32.107127}
----------------------------------------------------------------
{'type': 'send', 'payload': 'sign:ab25b000902a1cb73174adb1f6ffd602c2b885dd8e6efbc4ec'} None
{'type': 'send', 'payload': 'sign:ab25b000902a1cb73174adb1f6ffd602c2b885dd8e6efbc4ec'}
over
➜  x

对比一下之前的签名,一模一样,证明找对地方了

下面还需要解决x-mini-wua与wua
其实和sign都在同一个类里面,按照上面的方法在进行hook即可,

public String getWua(HashMap<String, String> params, String appKey) {
        String str;
        UnsupportedEncodingException e;
        SecException e2;
        String str2 = "";
        String str3 = (String) params.get("sign");
        try {
            if (this.f13463e == null || StringUtils.isBlank(str3)) {
                TBSdkLog.m3641e("mtopsdk.InnerSignImpl", mo44553c() + " [getWua]middleTier null or data data ", "appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode);
                return "";
            }
            HashMap hashMap = new HashMap();
            hashMap.put("data", str3.getBytes("UTF-8"));
            hashMap.put("env", Integer.valueOf(m13472d()));
            HashMap wua = this.f13463e.getWua(hashMap);
            if (wua == null || wua.isEmpty()) {
                TBSdkLog.m3641e("mtopsdk.InnerSignImpl", mo44553c() + " [getWua]get wua failed with no output ", "appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode);
                return "";
            }
            str = (String) wua.remove(ApiConstants.WUA);
            try {
                if (!StringUtils.isNotBlank(str)) {
                    return str;
                }
                params.putAll(wua);
                return str;
            } catch (UnsupportedEncodingException e3) {
                e = e3;
                TBSdkLog.m3642e("mtopsdk.InnerSignImpl", mo44553c() + " [getWua]your input data transfer to byte utf-8 failed ", "appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode, e);
                return str;
            } catch (SecException e4) {
                e2 = e4;
                TBSdkLog.m3643e("mtopsdk.InnerSignImpl", mo44553c() + " [getWua]get wua failed and SecException errorCode " + e2.getErrorCode() + ",appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode, (Throwable) e2);
                return str;
            }
        } catch (UnsupportedEncodingException e5) {
            UnsupportedEncodingException unsupportedEncodingException = e5;
            str = str2;
            e = unsupportedEncodingException;
            TBSdkLog.m3642e("mtopsdk.InnerSignImpl", mo44553c() + " [getWua]your input data transfer to byte utf-8 failed ", "appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode, e);
            return str;
        } catch (SecException e6) {
            SecException secException = e6;
            str = str2;
            e2 = secException;
            TBSdkLog.m3643e("mtopsdk.InnerSignImpl", mo44553c() + " [getWua]get wua failed and SecException errorCode " + e2.getErrorCode() + ",appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode, (Throwable) e2);
            return str;
        }
    }

    public String getMiniWua(HashMap<String, String> params, HashMap<String, String> ext) {
        String str;
        SecException e;
        String str2 = "";
        try {
            if (this.f13463e == null) {
                TBSdkLog.m3641e("mtopsdk.InnerSignImpl", mo44553c() + " [getMiniWua]middleTier ", "appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode);
                return "";
            }
            HashMap hashMap = new HashMap();
            hashMap.put("env", Integer.valueOf(m13472d()));
            if (ext == null) {
                ext = new HashMap<>();
            }
            ext.put(C3934e.f8409i, params.get("api"));
            hashMap.put("extend_paras", ext);
            HashMap miniWua = this.f13463e.getMiniWua(hashMap);
            if (miniWua == null || miniWua.isEmpty()) {
                TBSdkLog.m3641e("mtopsdk.InnerSignImpl", mo44553c() + " [getMiniWua]get miniwua failed with no output ", "appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode);
                return "";
            }
            str = (String) miniWua.remove("x-miniwua");
            try {
                if (!StringUtils.isNotBlank(str)) {
                    return str;
                }
                params.putAll(miniWua);
                return str;
            } catch (SecException e2) {
                e = e2;
                TBSdkLog.m3643e("mtopsdk.InnerSignImpl", mo44553c() + " [getMiniWua]get miniwua failed and SecException errorCode " + e.getErrorCode() + ",appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode, (Throwable) e);
                return str;
            }
        } catch (SecException e3) {
            SecException secException = e3;
            str = str2;
            e = secException;
            TBSdkLog.m3643e("mtopsdk.InnerSignImpl", mo44553c() + " [getMiniWua]get miniwua failed and SecException errorCode " + e.getErrorCode() + ",appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode, (Throwable) e);
            return str;
        }
    }

一次失败的签名算法逆向过程

某电商App
首先抓包看到请求如下(magisk+edxposed+trustmealready屏蔽ssl验证)

直接jadx打开apk搜索functionId关键字

先定位到了第三个函数

public final String mo15331a(JSONObject jSONObject, String str) { //json对象
        try {
            String b = mo15238b();
            TreeMap treeMap = new TreeMap(new Comparator<String>() {
                /* renamed from: a */
                public int compare(String str, String str2) {
                    return str.compareTo(str2);
                }
            });
            if (jSONObject != null) {
                Iterator keys = jSONObject.keys();
                while (keys.hasNext()) {
                    String str2 = (String) keys.next();
                    if (!TextUtils.isEmpty(jSONObject.optString(str2))) {
                        treeMap.put(str2, jSONObject.optString(str2)); //遍历json对象
                    }
                }
            }
            if (!TextUtils.isEmpty(str)) {
                treeMap.put(JshopConst.JSKEY_JSBODY, str); //加入body
            }
            if (!TextUtils.isEmpty(b)) {
                treeMap.put("functionId", b);
            }
            StringBuilder sb = new StringBuilder();
            for (Entry value : treeMap.entrySet()) {
                sb.append((String) value.getValue()).append("&"); //拼接数据 k=v&
            }
            if (sb.toString().endsWith("&")) { //删除最后一个&
                sb.setLength(sb.length() - 1);
            }
            String c = MantoCryptoUtils.m4776c(C2656c.m2509a() + "7D6D16CC3D2BE89108F9DCFC9A855253", "616E746F"); //获取key C2656c.m2509a() = 2AA64BD44C4381F31D9DA68EFE377874
            MantoLog.m4788d("sign  ", "secretKey --->  " + c); // 02ad4470d1a24880b6d3aca1988694bf
            MantoLog.m4788d("sign  ", "data --->  " + sb); //需要签名的数据
            String a = MantoCryptoUtils.m4772a(sb.toString().getBytes("UTF-8"), c.getBytes("UTF-8")); //签名 HmacSHA256
            MantoLog.m4788d("sign  ", "sign --->  " + a);
            if (!TextUtils.isEmpty(a)) {
                return a.toLowerCase();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

发现大概加密流程是
先把url参数传递进来,然后拼接成k=v&格式,然后使用MantoCryptoUtils.m4776c获取到hash_hmac的key,最后进行hash_hmac sha256加密,

MantoCryptoUtils.m4776c
  public static String getsecretKey(String str, String str2) {
    try{
        byte[] bytes = ("6A642D6D" + str2).getBytes("UTF-8");
        for (int i = 0; i < 16; i++) {
            bytes[i] = (byte) (bytes[i] ^ f3228d[i]);
        }
        String str3 = str + "D4F1E5BBBE321897A0F4BDEC91197EE0";
        SecretKeySpec secretKeySpec = new SecretKeySpec(bytes, f3226b);
        Cipher instance = Cipher.getInstance(f3227c);
        instance.init(2, secretKeySpec);
        return new String(instance.doFinal(xorData(str3.getBytes("UTF-8"))), "UTF-8");
    }catch (Exception e){
        return "";
    }
    }

直接把方法提取出来,写一个脚本获取到完整的key

然后在带入参数进行直接加密测试即可

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import main.util.ReplyCode;
import java.util.TreeMap;
/* renamed from: jd.wjlogin_sdk.util.ReplyCode */
/* compiled from: Proguard */

public class Main {
    private static byte[] f3228d = {ReplyCode.reply0x26, ReplyCode.reply0x38, 11, ReplyCode.reply0x64, -92, -85, 114, -41, -63, 30, ReplyCode.reply0x7b, ReplyCode.reply0x88, ReplyCode.reply0xaa, 102, 10, -32};
    private static String f3226b = "AES";
    private static String f3227c = "AES/ECB/PKCS5Padding";
    public static void main(String[] args) {
        System.out.println("Hello World!");
        System.out.println(getsecretKey("2AA64BD44C4381F31D9DA68EFE3778747D6D16CC3D2BE89108F9DCFC9A855253","616E746F"));
        String c=getsecretKey("2AA64BD44C4381F31D9DA68EFE3778747D6D16CC3D2BE89108F9DCFC9A855253","616E746F");
        String sb="asdsadasd";

        try{
        
            String a = Sign(sb.getBytes("UTF-8"), c.getBytes("UTF-8")); //签名 HmacSHA256
            System.out.println(a);
        }catch (Exception e){

        }

    }

    public static String Sign(byte[] bArr, byte[] bArr2) {
        try {
            SecretKeySpec secretKeySpec = new SecretKeySpec(bArr2, "HmacSHA256");
            Mac instance = Mac.getInstance("HmacSHA256");
            instance.init(secretKeySpec);
            return toHex(instance.doFinal(bArr));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e2) {
            e2.printStackTrace();
        }
        return null;
    }
    private static String toHex(byte[] bArr) {
        StringBuilder sb = new StringBuilder();
        int i = 0;
        while (bArr != null && i < bArr.length) {
            String hexString = Integer.toHexString(bArr[i] & 255);
            if (hexString.length() == 1) {
                sb.append('0');
            }
            sb.append(hexString);
            i++;
        }
        return sb.toString().toUpperCase();
    }
    private static byte[] xorData(byte[] bArr) {
        if (bArr.length % 2 != 0) {
            return null;
        }
        byte[] bArr2 = new byte[(bArr.length / 2)];
        for (int i = 0; i < bArr.length; i += 2) {
            bArr2[i / 2] = (byte) Integer.parseInt(new String(bArr, i, 2), 16);
        }
        return bArr2;
    }
    public static String getsecretKey(String str, String str2) {
    try{
        byte[] bytes = ("6A642D6D" + str2).getBytes("UTF-8");
        for (int i = 0; i < 16; i++) {
            bytes[i] = (byte) (bytes[i] ^ f3228d[i]);
        }
        String str3 = str + "D4F1E5BBBE321897A0F4BDEC91197EE0";
        SecretKeySpec secretKeySpec = new SecretKeySpec(bytes, f3226b);
        Cipher instance = Cipher.getInstance(f3227c);
        instance.init(2, secretKeySpec);
        return new String(instance.doFinal(xorData(str3.getBytes("UTF-8"))), "UTF-8");
    }catch (Exception e){
        return "";
    }


    }
}

最后发现使用这个加密方式的接口已经废弃不再使用,所以试一次失败的逆袭过程,在花点时间找一下新接口的加密算法了,记录一下过程。

前言

之前写了一个小程序,映思圈,一个提供直接访问Instagram的网站,后来发现其实还是有一些用户需求的,就花了2-3天时间将网站写成了微信小程序。
目前过了3-4个月,没事推广推广,现在也有个2w多一点的用户了,分享一下过程

其采用的技术栈如下:

入门

说实话,第一次写微信小程序,前面也没接触过任何前端框架,都说微信小程序类似vue,看的也是一头雾水,因为前期的网站和爬虫写好了,所以后端其实没啥好赘述的,无非就是怎么抓包得到instagram的api,怎么获取用户资料和帖子,并且图片的存储什么的,没啥技术含量。

首先就是学习了一下小程序的结构和基本的语法,发现一个小程序基本的目录结构如下:

其中主要逻辑都是在pages目录下完成,有点类似MVC,代码与识图是分离的,一个完成的page包含四个文件 index.js,index.wxml,index.wxss,index.json
大概我们常用的文件/目录如下:

语法方面分俩块,一块是代码层面,一块是模版视图

语法

代码方面其实没太多好说的,因为他采用的是js进行开发,一个js的初始代码大概如下:

// pages/me/index.js
const app = getApp();
Page({

  /**
   * 页面的初始数据
   */
  data: {
    message: "none",
  
  },
    /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function(options) {
  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function() {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function() {

  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function() {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function() {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function() {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function() {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function() {

  }
})

所有的操作都会有一个触发事件,只要在触发事件里面写逻辑就可以了,然后一些常见的内置函数可以去看小程序的官方文档,基本都是在wx这个对象下面

关于模版的语法其实也比较容易上手
我的首页是这样的,一个卡片式的图文列表

代码就这么一些
先通过js的onload事件加载数据

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function(options) {
    var openid = wx.getStorageSync('openid');
    openid = openid ? openid : ''
    var that = this
    wx.request({
      url: 'https://api.com/get_myfeed/' + openid,
      success: res => {
        wx.hideToast()
        that.setData({
          searchlist: res.data

        })
      }
    })
  },
<view class="page">
  <view class="page__bd">
    <view class="weui-cells__title">我的关注</view>
    //这里wx:for 就是一个for循环用来遍历数组的数据
    <view class="weui-cells weui-cells_after-title" wx:for="{{searchlist}}" wx:key="pk">
      <view class="weui-cell weui-cell_access" hover-class="weui-cell_active">
        <navigator url="/pages/user/index?url={{item.uploader_id}}" class="weui-cell__hd">
          <image src="{{item.profile_img}}" style="margin-right: 16px;vertical-align: middle;width:30px; height: 30px; float:left; border-radius: 50%; border: 3px solid #eee; overflow: hidden;"></image>
        </navigator>
        <navigator url="/pages/user/index?url={{item.uploader_id}}" class="weui-cell__bd">{{item.uploader_id}}</navigator>
        <view class="weui-cell__ft weui-cell__ft_in-access" bindtap="onUnfeed" data-ownerid="{{item.owner_id}}">点我取关</view>
      </view>
    </view>
  </view>
</view>

其实还是非常简单的,然后最后写好的程序如下,其实还是非常简单的,前后开发小程序也就用了2天左右的时间,不得不说小程序真的非常容易上手。



Frida 脱壳与Hook笔记

环境搭建

安装Frida

pip3 install frida
pip3 install frida-tools

模拟器配置

我的使用环境在Mac上,Mac上的模拟器都不太稳定,所以我选择在Parallels Desktop虚拟机中安装了一台WIndows,并在Windows中安装了夜神模拟器.
夜神模拟器默认的adb端口是62001,但是只绑定在了127.0.0.1上

我们借助一款小工具tcpmapping,把62001转发到0.0.0.0上,方便我们用物理机或其他虚拟机进行连接。

然后就可以通过链接虚拟机的内网IP进行连接了

运行Frida-server

adb push frida-server /data/local/tmp/
adb shell chmod 777 /data/local/tmp/frida-server
adb shell /data/local/tmp/frida-server

应用

脱壳

现在很多安卓的壳都是动态加载dex文件进行执行的,如果正常解压apk得到的dex一般只是一个加载器,但是最终一定会加载真实的dex文件,我们只需要Hook系统打开Dex文件的函数,最后将dex文件导出即可。

在Android 4 上是通过libdvm.so中的dvmDexFileOpenPartial函数实现打开dex文件的。
Android > 5 以上时通过libdvm.so中的OpenCommon函数进行操作的。
当然你实际把这俩个so文件拖到ida里面去搜函数名字,出来的函数名称是很长的,例如

_ZN3art7DexFile10OpenCommonEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_PNS0_12VerifyResultE

这里直接使用一个别人开发好的脚本进行脱壳就可以了,有兴趣的可以仔细看一下脚本,还是很值得学习的。
使用方法:

frida -l dexDump.js -f com.sskj.com -U --no-pause

最后导出的dex文件会在这个目录下:

"/data/data/" + processName + "/" + dex_size + ".dex";

https://github.com/GuoQiang1993/Frida-Apk-Unpack

Hook指定API

我这里用老版本的[微博绿洲 1.4.2]进行实验,因为没有加壳直接使用jadx就可以查看源码了,先找到了Java实现的签名算法的调用类

package com.weibo.xvideo.util;

import android.content.Context;
import com.tencent.open.SocialOperation;

/* compiled from: SignatureUtil.kt */
public final class SignatureUtil {
    public static final SignatureUtil a = new SignatureUtil();

    static {
        System.loadLibrary(SocialOperation.GAME_SIGNATURE);
    }

    public final native String signature(Context context, String str, boolean z);
}

发现其是通过加载so文件实现签名的,其实按照正常破解加密算法的操作,应该去逆向so文件,然后重写加密算法,但是既然用了frida就不需要那么复杂,几行hook就可以实现了。

setTimeout(function() {
    Java.perform(function() {

        var HttpRequestEntity = Java.use('com.weibo.xvideo.util.SignatureUtil'); //要hook的类名完整路径
        // var func = new HttpRequestEntity();
        // func.signature('1', '13123', false);
        HttpRequestEntity.signature.implementation = function(arg1, arg2, arg3) { // 重写要hook的方法getSign,当有多个重名函数时需要重载,function括号为函数的参数个数

            var Sign = this.signature(arg1, arg2, arg3); //调用原始的函数实现并且获得返回值,如果不写的话我们下面的代码会全部替换原函数

            send("arg1:" + arg1); //打印参数值
            send("arg2:" + arg2);
            send("arg3:" + arg3);

            send("result:" + Sign); //打印返回值
            return Sign; //函数有返回值时需要返回
        };
        var currentApplication = Java.use('android.app.ActivityThread').currentApplication();
        var context = currentApplication.getApplicationContext();
        var func = HttpRequestEntity.$new()
        send("runit:" + func.signature(context, '13123', false));
    })

});

然后执行

frida -U -l hook.js com.sina.oasis

然后在app中随便操作一些需要使用到签名函数的功能,将会看到下列输出

可以发现已经hook到了签名函数的参数和返回值,下面我们就是需要把我们自己的参数传递进去让他进行签名,然后获取签名值了。
写一个python脚本,frida支持消息的传递,我们通过写一个recv监听事件,然后在通过frida的接口发送我们需要签名的内容去触发这个事件,然后在通过frida主动去调用这个签名类并取出返回值

import frida, sys
from urllib import parse
#接受message信息,可以把签名提交到其他功能上
def on_message(message, data):
    if message['type'] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)

jscode = """
function sign(args) {
    Java.perform(function () {
        send('send:'+args)
        var HttpRequestEntity = Java.use('com.weibo.xvideo.util.SignatureUtil');
        var currentApplication = Java.use('android.app.ActivityThread').currentApplication();
        var context = currentApplication.getApplicationContext();
        var func = HttpRequestEntity.$new()
        var result = func.signature(context, args, false)
        send("sign:" + result);
        return result;
    })
}
#接受到信号就调用签名函数
recv('sign', function onMessage(pokeMessage) { sign(pokeMessage['line']); });

"""
#处理参数 按照key的字母进行排序
dicts='source_cid=0&source_uid=0&ua=OPPO-OPPO%20R11_oasis_1.4.2_Android_5.1.1&sid=4441027085236588&cuid=7341062735&device_id=5959f99a7cb193c2c053ee4977ab46fc&timestamp=1574308247514&version=1.4.2&channel=ViVo&comment=1111&noncestr=425gbJ1zuDWh15618n5ThvN685QR95&platform=ANDROID'
dicts=dicts.split('&')
dicts_info={}
dicts_info_result={}
for d in dicts:
    d=d.split('=')
    dicts_info.update({d[0]:d[1]})

keys=sorted(dicts_info)
for k in keys:
    dicts_info_result.update({k:dicts_info[k]})
line='&'
for k,v in dicts_info_result.items():
    line+=k+'='+v+'&'
    
line=line[0:-1]
line=parse.unquote_plus(line)
print(line)
print('--------------------------------')
process = frida.get_usb_device().attach('com.sina.oasis')
print(123123)
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
#发送信号
script.post({"type": "sign",'line':line})
sys.stdin.read()