前言:初学逆向 请多多指教 好累 感觉每天这样肝 人有点受不了了...

学习到的内容

1、新学习到IDA的一些分析时候的小技巧

2、算法还原代码实现的练习(有个参数没有分析出来,后面知道了会补上的)

3、在Frida中使用命令行调试的方便方法

分析过程

APP登陆界面:

请求包:

POST /api/adult/check_guest HTTP/1.1Content-Type: application/x-www-form-urlencodedUser-Agent: Dalvik/2.1.0 (Linux; U; Android 8.1.0; Pixel Build/OPM1.171019.011)Host: service.kuyangsh.cnConnection: Keep-AliveAccept-Encoding: gzipContent-Length: 1108a=6ih0KN8TFL5%2FQT%2FN3JY63ZovsWQyeIxHjBLHp1GGwjUNYVaGJLC%2FYZenRFKbeqsgIoI4rD1atROr%0Ajkl1p7eobNMARMel19oiGkl5hRD72vOn9zyNbERMe8Cj3b24Ru4wc2mWbbnamKVKPepkaa2mqpJl%0Akp4%2Fa5udSz0UbDR6cTwLCRWeKb60H%2Fir4vzZv1OfwQF%2FXJQsuBmxH2F6wp9CJkk9WYchx4LQU%2FS3%0AjQfpQY2iZWwsmHAiyZGVsfZIgXTvhJygpT8vH268Py5JspYZoho0RRrx4BjUfs5boif6rEMpd5PC%0AiZTLhIPovbPpoZQJC7d%2BWPFtwjPT7Ljyzgz5QxtctnZqa4qMMkfFwAIeA7lj2wJZeZBD%2F%2BoU5R45%0AMEFK6OMrMXB1M%2BC4dBt7Rd384SYhe%2BAEp0gKNHGkrpxWImFcPAaalajyijs6V4Wl4res9CWuEE5W%0Ayj5ehtmPc6uZGuo5ns6THfDwnho8BiFOnH0QoqJEeyVdTsCzOiMwfBJJldB7qbsTfUlLbSnpq4tf%0AurPbVMVKQbk4ui1XgDH5v%2FptUHirNK0IHT%2BBms8wQ%2BSX3BcMLKFiWI0OBAjUydqcJpIi0sPSWpfh%0A2k1nmMlvPnljfc7P12iF8nFHoFWRYQPVie46K%2Bhd4%2FttkyrZ4Gy3WM6zWdmnED3h6CCgZ4rEe0DB%0AN5Dj8lJJbsOAE%2FxWcYDguyj8WkUET3yLB%2FBZQx%2BsOn9otWzjwROdhfi6V7OObXZ5XoGUIDffKaFu%0ADnvTinsAh%2FvjcSDVq%2BHyD%2FzUeRceMf6uQruBhHRzikSb2Oz0Zfxld7rqmWYZ8aIBe1DMRJuXecB%2F%0AAsBu4VVuVVfQf4hlCpVNmKnX6huuMkHtCptCLaD0pkmuY7X7OEfCsudtFIco%2F7gXaQ5aXgfCs7GJ%0AzAMxfpCRm4vnF0kc8nQ4OWlexOV5t65k%2B4eDt8wY91%2BIFHcq%2BIwZPR3e41oeKriHlbsPdocNOkeg%0AeyUw%2FXlAY97IpZA%3D%0A

返回包:

{  "ret": 0,  "msg": "没有错误",  "data": {    "needs_check": "0",    "needs_bind": "0",    "needs_guest_bind": 0,    "user_is_guest": -1  }}

这里定位很简单,直接全局搜索关键词:"a" 就可以了,因为可以看到POST数据包中只有一个字段就是a,但是这里需要带上双引号,来到如下

可以看到最关键的加密代码就是:String a = m3385a((Map) hashMap, initFromXML.getAppKey());

initFromXML.getAppKey则正常返回一个定值

跟进m3385a函数可以发现,定义如下:

    public static String m3385a(Map<String, String> map, String str) {        if (C1074a.f2394c || TivicloudController.f2343a) {            map.put("testing", "1");        }        try {            JSONObject jSONObject = new JSONObject(map);            Debug.m3346d("params : " + jSONObject.toString());            String encode = URLEncoder.encode(jSONObject.toString(), "UTF-8");            String encryptString = EncryptUtil.encryptString(encode + str);            JSONObject jSONObject2 = new JSONObject();            try {                jSONObject2.put("sign", encryptString);                jSONObject2.put(C0882di.C0883a.DATA, encode);            } catch (JSONException e) {                Debug.m3354w((Exception) e);            }            return new String(Base64.encode(EncryptUtil.nativeAES(jSONObject2.toString()), 0), "UTF-8");        } catch (UnsupportedEncodingException e2) {            Debug.m3354w((Exception) e2);            return null;        }    }

注意:这里发现JSONObject是处于org.json.JSONObject,系统自带的库中的类,这里也能作为一种hook的方法定位

这里可以直接对m3385a这个函数进行观察,流程就是将传入的数据先进行一个url编码,然后拼接initFromXML.getAppKey作为参数,调用EncryptUtil.encryptString

继续跟到encryptString中去,代码如下,那么也就需要进libgavesec.so中的nativeEncrypt函数进行分析

这里遇到了一个问题,比如下面的两个函数和注入代码,一个函数会调用native中的函数,但是如果我hook encryptString这个函数,返回值写的是调用native层的nativeEncrypt函数,这样的写法就会导致程序结束,具体原因不知道

    public static String encryptString(String str) {        return nativeEncrypt(str);    }    private static native String nativeEncrypt(String str);    Java.perform(function () {        var EncryptUtil = Java.use("com.tivicloud.utils.EncryptUtil");        EncryptUtil.encryptString.implementation = function (a) {            send("EncryptUtil.encryptString args[0]: " + a);            var result = this.nativeEncrypt(a);            return result;        }    });

因为上面那样写就导致程序结束,所以这里就直接hook nativeEncrypt,惊奇的发现这样子就不会导致程序结束了

setImmediate(function(){    Java.perform(function () {        var EncryptUtil = Java.use("com.tivicloud.utils.EncryptUtil");        EncryptUtil.nativeEncrypt.implementation = function (a) {            send("EncryptUtil.nativeEncrypt args[0]: " + a);            var result = this.nativeEncrypt(a);            return result;        }    });});

这里可以对EncryptUtil.encryptString(encode + str),java层进行一次hook来获取对应的参数先

[*] EncryptUtil.encryptString args[0]: {"mobile_operator":"","app_versioncode":"4","app_version":"2.1.0.22277","connection_type":"WIFI","os_version":"8.1.0","version_code":"4","version_name":"2.1.0.22277","os_lang":"zh","sdk_version":"3.1.4","package_name":"com.lm.lm","imei":"359906070277673","os_name":"Android","lang":"zh","udid":"10e1ef4509a2340eb0276bb99d186506","nudid":"155c2b70-fc97-4005-abc7-6d6c4329ed23","app_id":"10054","channel_id":"10006","tdid":"39fb282a761c17e80c069332fc6a2ffa9"}fff18b83431fa3a83b9de80c1e413bde

那么的initFromXML.getAppKey值就为fff18b83431fa3a83b9de80c1e413bde

然后接着继续跟native层进行分析,将libgavesec.so拖入到IDA中,并且找到对应的函数

1、在通过导出library库之后,有些jni的函数无法识别参数,你可以直接右键该函数选择Force call type来进行重新分析,一般都可以成功识别参数

2、有时候ida中的强制转换类型太多,可以右键选择Show casts来隐藏强制转换,然后进行分析

主要进行了encrypt函数的调用

接着就是来到加密函数encrypt中进行分析

先是进行一次sha1加密

所以这里要hook两个地方

encrypt:Base + 0x1C8C ,获取第一个参数

SHA1::Result:Base + 0x2AA4,获取该函数调用完之后的第二个参数的结果

这里分享的frida的调试方法,通过命令行注入js脚本进入到frida的命令行中进行操作

frida -RF -l hooktest.js

1、通过主动调用获取对应的类

2、调用类对应的静态/非静态方法调试数据的时候会很方便,代码如下:

function data_test() {    Java.perform(function () {        var EncryptUtil = Java.use("com.tivicloud.utils.EncryptUtil");        var res = EncryptUtil.nativeEncrypt('%7B%22mobile_operator%22%3A%22%22%2C%22app_versioncode%22%3A%224%22%2C%22app_version%22%3A%222.1.0.22277%22%2C%22connection_type%22%3A%22WIFI%22%2C%22os_version%22%3A%228.1.0%22%2C%22source%22%3A%22SDK%22%2C%22user_id%22%3A%2216073535%22%2C%22os_lang%22%3A%22zh%22%2C%22sdk_version%22%3A%223.1.4%22%2C%22imei%22%3A%22359906070277673%22%2C%22os_name%22%3A%22Android%22%2C%22login_token%22%3A%2285700bcdf4a41164ab7406e8445479ed%22%2C%22lang%22%3A%22zh%22%2C%22udid%22%3A%2210e1ef4509a2340eb0276bb99d186506%22%2C%22nudid%22%3A%22155c2b70-fc97-4005-abc7-6d6c4329ed23%22%2C%22app_id%22%3A%2210054%22%2C%22channel_id%22%3A%2210006%22%2C%22tdid%22%3A%2239fb282a761c17e80c069332fc6a2ffa9%22%7Dfff18b83431fa3a83b9de80c1e413bde');        console.log(res);    });}

接着上面的分析,打印了sha1加密过后的数据v12变量和最终的Sign值(这里也就是nativeEncrypt的返回值),你会发现结果是不一样的

接着你会看到下面的循环的操作,所以sha1加密的结果应该被二次修改了,这里有两个大循环的操作

首先第一段循环:

SHA1::Result((SHA1 *)&v13, (unsigned int *)v12);// sha1加密的结果v4 = 0LL; // long long v5 = 7;do    // 进行循环操作  {    v6 = *(_DWORD *)&v12[v4]; // 0X0C    v7 = &dest_cstr_1[v5];    while ( 1 )    {      v8 = v6 & 0xF; // 0X0C      v6 >>= 4; // 0X00      *v7-- = hexDigits[v8]; //       if ( !(v5 & 7) )        break;      --v5;    }    *(_DWORD *)&v12[v4] = v6;    // 赋值操作    v4 += 4LL;    v5 += 15;  }  while ( v4 != 20 );

你会发现hexDigits是data段的一段数据:

最后分析,其实就是将其原封不动的转换为16进制的字符,然后一起拼接起来,分析注释如下:

然后就是第二段循环了,循环次数也可以看出来会对每个16进制字符进行处理,循环次数为40次

hook了char2hexInt处理过后的数据会发现,char2hexInt这个函数才是二次处理的函数

  do  {    v10 = char2hexInt(dest_cstr_1[v9]);    dest_cstr_1[v9] = hexDigits[(signed int)((unsigned __int64)char2hexInt(a211034f8af4e6b[v9]) ^ v10)]; //异或的值为v10    ++v9;  }

还需要char2hexInt的参数来观察,该地址为0x1B34,HOOK结果如下

其实就是两个返回值进行异或处理,比如0x2^0x2就为0,最后还会通过hexDigits数组转成对应的16进制在内存中保存为0x30,因为字符 '0' 对应的ASCII 十六进制就是 0x30 是相等的!

最终的sign值分析的注释:

sign这里也分析完了,还有个整体加密的分析,发现整体会进行一次Base64.encode,但是nativeAES可以跟进去观察

发现该函数依旧是native层的函数,所以这里继续去看分析

来到如下进行分析,看到名字就知道是AES算法加密了,这里的话用findcrypt插件通过特征码也可以进行识别

然后接着就是先对传入的数据进行字符串复制

然后就是AES对象和密钥的初始化,这里的AES需要hook,来确认v19的值,还有该函数最终的返回值dest_array_bytes的地址来进行打印

AES::AES:0x3274,Cipher:0x397C,然后hook到的数据如下,一个是AES密钥,最终要进行AES字符串加密的字符串

最终hook的代码:

function hook_test() {    Java.perform(function () {        var EncryptUtil = Java.use("com.tivicloud.utils.EncryptUtil");        EncryptUtil.nativeEncrypt.implementation = function (a) {            console.log("=============================")            // console.log("EncryptUtil.nativeEncrypt args[0] 被加密的字符串: ", a);            var result = this.nativeEncrypt(a);            console.log("Sign的结果:", result);            return result;        }        var b64 = Java.use("android.util.Base64");        var str = Java.use("java.lang.String");        EncryptUtil.nativeAES.implementation = function (a) {            console.log("=============================")            // console.log("EncryptUtil.nativeEncrypt args[0] 被加密的字符串: ", a);            var result = this.nativeAES(a);            console.log("base64数据", str.$new(b64.encode(result, 0)));            return result;        }        var libgavesec = Module.findBaseAddress("libgavesec.so");        // encrypt        Interceptor.attach(libgavesec.add(0x1C8C), {            onEnter: function (args) {                console.log("encrypt args[0] 被加密的字符串: ", Memory.readCString(args[0]));                this.args1 = args[1];            },            onLeave: function (retVal) {                console.log("encrypt args[1] 处理过后的数据:", hexdump(this.args1, {                    offset: 0,                    length: 32,                    header: true,                    ansi: false                }));            }        })        // SHA1::Result        Interceptor.attach(libgavesec.add(0x2AA4), {            onEnter: function (args) {                // console.log("SHA1::Result args[1] 加密前的数据", hexdump(args[1], {                //     offset: 0,                //     length: 64,                //     header: true,                //     ansi: false                // }));                this.args1 = args[1];            },            onLeave: function (retVal) {                console.log("SHA1::Result args[1] 加密后的数据", hexdump(this.args1, {                    offset: 0,                    length: 32,                    header: true,                    ansi: false                }));            }        })        // char2hexInt        Interceptor.attach(libgavesec.add(0x1B34), {            onEnter: function (args) {                // console.log("char2hexInt 参数:", args[0]);                this.args1 = args[1];            },            onLeave: function (retVal) {                // console.log("char2hexInt 返回值:", retVal);            }        })        // AES::AES        Interceptor.attach(libgavesec.add(0x3274), {            onEnter: function (args) {                console.log("AES密钥:", hexdump(args[1], {                    offset: 0,                    length: 32,                    header: true,                    ansi: false                }));            },            onLeave: function (retVal) {            }        })        // AES::Cipher        Interceptor.attach(libgavesec.add(0x397C), {            onEnter: function (args) {                console.log("AES::Cipher args[1]:", Memory.readCString(args[1]));                this.args1 = args[1];            },            onLeave: function (retVal) {            }        });    });};setImmediate(function () {    Java.perform(function () {        hook_test();    });});

简单的加密代码的实现:

from Crypto.Cipher import AESfrom urllib import parsefrom binascii import b2a_hex, a2b_hex, b2a_base64import hashlib"""aes加密算法ECB模式"""class Aes128_(object):    def __init__(self):        self.key = b"14ca829f017c0357"        self.mode = AES.MODE_ECB    def add_to_16(self, text):        if len(text.encode('utf-8')) % 16:            add = 16 - len(text.encode('utf-8')) % 16        else:            add = 0        text = text + ("\0"*add)  # 明文 + \00填充        return text.encode('utf-8')    def encrypt(self, text):        text = self.add_to_16(text)        cryptos = AES.new(self.key, self.mode)        cipher_text = cryptos.encrypt(text)        # return b2a_hex(cipher_text)        return b2a_base64(cipher_text)    def decrypto(self, text):        cryptor = AES.new(self.key, self.mode)        plain_text = cryptor.decrypt(a2b_hex(text))        return bytes.decode(plain_text).rstrip('\0')def getSign(data):    sign = ''    a211034f8af4e6b = '211034f8af4e6b9546c19ae13ed099553319b6c3'    for i in range(40):        print(hex(int(data[i], 16) ^ int(a211034f8af4e6b[i], 16)))        sign += str(hex(int(data[i], 16) ^ int(a211034f8af4e6b[i], 16)))[2:3]    return signif __name__ == "__main__":    data = bytes(parse.quote('{"mobile_operator":"","app_versioncode":"4","app_version":"2.1.0.22277","connection_type":"WIFI","os_version":"8.1.0","source":"SDK","user_id":"16073535","os_lang":"zh","sdk_version":"3.1.4","imei":"359906070277673","os_name":"Android","login_token":"9f6037c931db9a9cfcbb991966fad614","lang":"zh","udid":"10e1ef4509a2340eb0276bb99d186506","nudid":"155c2b70-fc97-4005-abc7-6d6c4329ed23","app_id":"10054","channel_id":"10006","tdid":"39fb282a761c17e80c069332fc6a2ffa9"}fff18b83431fa3a83b9de80c1e413bde'), encoding="utf-8")    sha1 = hashlib.sha1(data)    sha1_data = sha1.hexdigest()    sign = getSign(sha1_data)    aes_data = str(Aes128_().encrypt('{"sign":"'+sign+'","data": "' + parse.quote('{"mobile_operator":"","app_versioncode":"4","app_version":"2.1.0.22277","connection_type":"WIFI","os_version":"8.1.0","source":"SDK","user_id":"16073535","os_lang":"zh","sdk_version":"3.1.4","imei":"359906070277673","os_name":"Android","login_token":"9f6037c931db9a9cfcbb991966fad614","lang":"zh","udid":"10e1ef4509a2340eb0276bb99d186506","nudid":"155c2b70-fc97-4005-abc7-6d6c4329ed23","app_id":"10054","channel_id":"10006","tdid":"39fb282a761c17e80c069332fc6a2ffa9"}"}')), encoding='utf8').replace("\n", "")    print(aes_data)

这个其实不是最终的代码,因为login_token没有分析出来,感觉有点难,我继续试试,可以的话再写一篇!

wifi定位算法 java_记一次APP的so层算法逆向(七)相关推荐

  1. 红米k30 允许调用gpu调试层_记一次APP的so层算法逆向(六)

    " 前言:初学逆向 请多多指教" 学习到的内容 - 1.在java层,对容器类的对象进行hook来进行快速定位 2.ida的findcrypt插件对so层的算法快速识别 3.文章分 ...

  2. 逆向so_记一次APP的so层算法逆向(七)

    " 前言:初学逆向 请多多指教 好累 感觉每天这样肝 人有点受不了了..." 学习到的内容 - 1.新学习到IDA的一些分析时候的小技巧 2.算法还原代码实现的练习(有个参数没有分 ...

  3. 使用frida破解native层算法

    使用frida破解native层算法(原文) 使用frida破解native层算法 案例:就不放了 抓包分析 这里抓包分析的过程不详细说了 可以看到主要有两个参数 sign和signV1 反编译 1. ...

  4. wifi定位算法 java_几种室内定位技术方案对比,室内定位种类的优缺点一目了然...

    最近几年,随着物联网技术的发展,定位技术也随之发展起来,室内定位技能非常实用,运用广泛,如图书馆,养老院,体育馆,地下车库,学校,仓库等都能够完成对人员及物品的快速定位.目前,在苏州新导室内定位体系中 ...

  5. android wifi定位不了,h5网页使用高德地图定位正常,网页嵌入安卓app后wifi定位正常,4g网络无法定位?...

    问题描述 h5网页使用高德地图定位正常,网页嵌入安卓app后wifi定位正常,4g网络无法定位 问题出现的环境背景及自己尝试过哪些方法 h5网页手机浏览器打开没有问题,嵌入app后打包,安卓9.0系统 ...

  6. android 网络wifi定位服务器,基于位置指纹算法的Android平台WiFi定位系统

    近年来,随着城域无线基础网络的发展,热点(AP)的覆盖范围已大大增加. 由于对定位服务的需求增加以及WiFi应用的扩展,WiFi定位已成为一种有效的定位方法. GPS卫星定位是最重要的定位方法. 它需 ...

  7. ios wifi 定位_一种IOS设备的集中式Wifi室内定位方法

    一种IOS设备的集中式Wifi室内定位方法 [技术领域] [0001] 本发明属于无线通信室内定位技术领域,具体涉及一种IOS设备的集中式Wifi室 内定位方法. [背景技术] [0002] 近年来, ...

  8. LBS位置服务中GPS定位、基站定位、wifi定位的区别

    1.卫星定位 其原理是接收机接收卫星广播,通过解析可见卫星的位置.距离等信息以及相应算法得出自己的位置信息,误差在15米范围内,当前可视卫星数量将影响定位精度,可见卫星数量越多,精度越高,实际测试中在 ...

  9. 定位技术GPS/A-GPS/LBS/WIFI定位介绍

    定位技术GPS/A-GPS/LBS/WIFI定位介绍 来源:本站整理 作者:08LR.CN 更新时间:2013年05月03日人气:本日:9 本周:19 本月:34 总数:2134 次 生活中,我们经常 ...

最新文章

  1. shell中的数值判断
  2. java 实现部门树_(java实现)哈夫曼(Huffman)树编码(自编压缩项目基础)
  3. 『码蛋』Android 周刊第1期
  4. 不是内部或外部命令也不是可运行的程序?
  5. MySQL主从复制的原理:IO用于交互 SQL用于解析执行;主bin-log 从relay-log;
  6. 通过WM_COPYDATA消息完成进程间通信
  7. SAP OData Total = 80是这样计算出来的
  8. 信息学奥赛一本通 1020:打印ASCII码 | OpenJudge NOI 1.2 07
  9. php操作pdo,PHP PDO操作API
  10. sql azure 语法_Azure Data Studio中SQL Server Profiler
  11. mac terminal ssh client shuttle 免输密码
  12. android 类似按键精灵脚本_脚本编辑器 -- 按键精灵 #Android
  13. EasyUI-基本框架
  14. CRITIC权重指标如何计算?
  15. 浅谈 JNIEnv 和 JavaVM
  16. SLAM 中evo的使用(二) (evaluation of odometry) evo_traj/ape rpe/evo_ape说明与示例
  17. HTML做一个传统节日端午节 带设计报告4500字
  18. 1521 一维战舰(区间)
  19. 深度学习需要的显卡配置
  20. 反思:安全需要新体系

热门文章

  1. 在SQL Server上该做的和不该做的
  2. 【CyberSecurityLearning 44】iptables包过滤与网络地址转换
  3. npm package.json中的dependencies和devDependencies的区别
  4. document.ready和onload的区别——JavaScript文档加载完成事件
  5. 9.png(9位图)在android中作为background使用导致居中属性不起作用的解决方法
  6. git reset 命令详解(二)—— Git 学习笔记 08
  7. Unity3D 深度图
  8. 「 每日一练,快乐水题 」258. 各位相加
  9. 日常生活 -- 数据结构与算法告一段落
  10. linux搜狗输入法配置,liunx----配置搜狗输入法