• xxxx签名算法逆向&&python脚本实现


    前言

    有一段时间没看安卓了,找几个软件练练手。

    这是一个考驾照用的 app.

    官方网址: http://www.******baodian.com/

    本文就分析一下在 重置密码时对 数据包 进行签名来防篡改的方案。

    正文

    burp抓取htttps数据

    首先导入 burp 证书到手机, 装上 xposed , 然后安装

    https://github.com/WooyunDota/DroidSSLUnpinning
    

    这个插件应该就可以抓到 https 数据了。

    然后触发 找回密码 逻辑,抓包

    image.png

    这里有一个 sign 的参数,可以推测这个是用来 保存签名的参数。

    如果正常发送的数据包得到的响应类似于下面(这里是验证码输错的情况)

    image.png

    如果重放数据包

    image.png

    经过测试发现 _r 参数用于标识数据包的唯一性,如果修改其他的字段会提示 重复的 URL, 但是如果修改了 _r, 则会提示 URL签名错误

    image.png

    于是大概可以推测服务端校验请求的流程

    • 拿到  _r 判断是否是重复的请求
    • 计算请求的 签名,与签名字段进行校验
    • 如果前面两步均通过,服务端则认为数据包没有被篡改,于是开始校验 验证码。

    定位关键代码

    我列举一下我用了的方法

    搜参数名

    程序中可能会用到参数的名称来设置参数的值,因为我的目标是分析签名算法,sign 这个参数名这么的明显,就搜他了。

    image.png

    发现太多的文件里面有这个关键字,不可用

    搜 url 路径

    观察抓到的数据包,发现它是向 /api/open/v2/forgot-password/check.htm 发起了 POST 请求。

    image.png

    这个貌似靠谱,可以追踪到进行请求的代码,于是我从第一个结果开始分析,一路往下,追踪,大概搞清楚了发送 请求的流程,以及一些参数的得到方式,但是貌似是看花眼了,而且程序的混淆强度比较大,没用找到 _rsign 设置位置。 于是开始了其他的定位尝试

    监控 系统层的 加密类和方法

    进行签名常用的方案就是 对待签名数据,算 hash 或者 用一些加密算法。于是想着是不是可以通过监控  java 层常用的 加密 api 来定位 算法。

    https://github.com/Chenyuxin/CryptoFucker
    
    

    找到了这个插件, 通过 hook 的方式来监控, 同时会在 /sdcard/ydsec/包名.txt 留下日志。

    image.png

    试了试,发现也没有找到相关的数据,通过这样的方式被加密,于是推测应该是自己实现了相关的加密算法。

    再次搜参数名

    忽然想起,参数名如果在程序中被使用的话应该是直接存在于程序中的, 然后 smali 代码引用字符串 会加上 " 来包裹字符串

    开始了另一种搜索尝试

    image.png

    发现搜 "sign"  和 "sign  得到的结果不多,但是貌似都不太相关,倒是 "_r" ,就两个结果。

    想起 既然 _r 用于标识请求的唯一性,那么签名肯定是要用到它的,而且 _r 应该没次都是随机生成的,那么用到它的位置,应该离 生成 sign 的位置不远了。

    进入第一个结果的代码(程序太大了,jeb打不开之前的我分析时的数据,只能重新看没有命名函数名的了)

    image.png

    可以知道 _r 通过

    UUID.randomUUID().toString().replace("-", "")
    

    生成随机字符串,来保证数据包不被重放

    image.png

    生成 _r 后 ,会调用 a.N ,其中会调用 aa.ao ,跟进去看看

    image.png

    根据 URL(url) 可以确定 ,第一个参数是一个 url, 接着对 url 上的参数进行了处理。

    然后定位 sign 参数的起始位置

    image.png

    接着删除了 sign 参数

    paste image

    然后重新生成 sign 参数

    image.png

    调用了 Riddle.s 生成了 sign 参数,传入的参数是 处理后的 url_path 和 签名用的 key

    Riddle.s 是一个 native 方法, 在 libtnpn.so 里面,去 libarmeabi 目录下找就是了。

    image.png

    image.png

    拖进 ida 发现 so 倒是挺友好的 ,没有混淆。

    image.png

    进入 Java_cn_mucang_android_core_jni_Riddle_s, 这就是  Riddle.s 方法在 so 中的命名,jni 函数的第一个参数类型为 JNIEnv_ * , 首先 需要导入 jni.h , 然后设置一下类型便于分析。

    image.png

    首先就是把传入的 参数变成 c 语言中的字符串类型,然后根据 key 的格式判断, 使用哪种签名方案,经过调试重置密码用的 key*#06#i3lrRYudcZZ2fIx9fI6VqJV8

    所以会调用

    ver = j_j_SignUrl1(path, sign_key_1 + 5, &v13);
    

    跟进到 SignUrl1

    image.png

    首先对 key 进行解密,然后进入 j_j_SignUrl0

    image.png

    流程很清晰,就是 拼接 path 和 解密后的 key, 然后计算 md5, 存在 a3 地址,回到 SignUrl1

    image.png

    接着又对传入的 没有被解密的key 来了一个求和,然后进入

    calc_sum(key_sum, 19);
    

    这个函数有点复杂,我是直接抠了出来,用 python 重写了。先继续看后面。

    image.png

    v11 的值 附加到 之前刚刚计算好的 md5 的数值后面,v11 来自于 v14, 而 v14 并没有设置的地方,这里是 IDA F5 识别错了。直接看汇编把。

    image.png

    实际上 v14 就是调用 calc_sum 后的 r1 寄存器。

    跟进到 sub_788640D4

    image.png

    首先对传入的参数和 lr 进行了保存,然后调用 sub_78864000 , 然后用之前保存的参数值 与 sub_78864000 的返回值进行运算,结果保存到 r1(专门用来迷惑 ida _).

    伪代码:

      a1 = sub_78864000(a1,a2)
      t = save_a2 * a1
      ret = save_a1 - t
      return ret
    
    

    sub_78864000 看起来比较复杂,我就直接用 python, 照着重写了一遍。

    image.png

    其中有一个有意思的地方: ida 无法 对 clz 指令进行转换,所以用

    v5 = __clz(a2) - __clz(v4);
    

    表示

    CLZ     R2, R1
    CLZ     R0, R3
    SUB     R0, R2, R0
    

    这使得重写造成了困扰,我的解决办法是,根据 clz 的作用,自己实现。

    image.png

    实现的 python 代码如下:

    def calc_clz(reg):
       return 32 - len(str(bin(reg))[2:])
       
    

    最后的脚本:

    from urllib.parse import urlparse
    from pprint import pprint
    import base64
    import hashlib
    
    
    #  示例数据包
    # POST /api/open/v2/forgot-password/check.htm?_platform=android&_srv=t&_appName=jiakaobaodian&_product=%E9%A9%BE%E8%80%83%E5%AE%9D%E5%85%B8&_vendor=xiaomi&_renyuan=XYX&_version=6.9.8&_system=LMY48Z&_manufacturer=samsung&_systemVersion=5.1.1&_device=SM-G9350&_imei=862514326681779&_productCategory=jiakaobaodian&_operator=T&_androidId=0086049272635872&_mac=02%3A00%3A00%3A00%3A00%3A00&_appUser=ffd830178df6460e8b401c1421b1e148&_pkgName=com.handsgo.jiakao.android&_screenDpi=1.2&_screenWidth=720&_screenHeight=1280&_network=wifi&_launch=4&_firstTime=2018-03-09%2009%3A28%3A53&_apiLevel=22&_userCity=320100&_p=464D555059511A4751565E5F475A&_ipCity=320100&_j=1.0&schoolName=%E6%9C%AA%E6%8A%A5%E8%80%83%E9%A9%BE%E6%A0%A1&schoolCode=-1&_webviewVersion=4.7&_mcProtocol=4.0&_r=78ae8eb0a00b4d6d9451eecd3ee169a6&sign=8b5c95d8e839cce7588db0c3ee315c0501 HTTP/1.1
    # User-Agent: Mozilla/5.0 (Linux Android 5.1.1 SM-G9350 Build/LMY48Z) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Safari/537.36
    # Accept-Encoding: gzip, deflate
    # Accept-Encoding: gzip, deflate
    # Content-Type: application/x-www-form-urlencoded
    # Content-Length: 83
    # Host: auth.mucang.cn
    # Connection: close
    #
    # phoneNumber=13333333333&captchaId=e5410186e8afa433e4a4e8019901ff40&captchaCode=5659
    
    
    def calc_sum(a1, a2):
     save_a1 = a1
     save_a2 = a2
     v3 = a1 ^ a2
     if a2 < 0:
       a2 = -a2
     if a2 == 1:
       if (v3 ^ a1) < 0:
         a1 = -a1
     else:
       v4 = a1
       if a1 < 0:
         v4 = -a1
       if v4 <= a2:
         if v4 < a2:
           a1 = 0
         if v4 == a2:
           a1 = (v3 >> 31) | 1
       elif (a2 & (a2 - 1)):
         v5 = calc_clz(a2) - calc_clz(v4)
         v6 = a2 << v5
         v7 = 1 << v5
         a1 = 0
         while True:
           if v4 >= v6:
             v4 -= v6
             a1 |= v7
           if v4 >= v6 >> 1:
             v4 -= v6 >> 1
             a1 |= v7 >> 1
           if v4 >= v6 >> 2:
             v4 -= v6 >> 2
             a1 |= v7 >> 2
           if v4 >= v6 >> 3:
             v4 -= v6 >> 3
             a1 |= v7 >> 3
           v8 = v4 == 0
           if v4:
             v7 >>= 4
             v8 = v7 == 0
           if v8:
             break
           v6 >>= 4
         if v3 < 0:
           a1 = -a1
       else:
         a1 = v4 >> (31 - calc_clz(a2))
         if v3 < 0:
           a1 = -a1
     t = save_a2 * a1
     ret = save_a1 - t
     return ret
    
    
    def decode_key(key):
       key = base64.b64decode(key)
       de_key = ''
       for c in key:
           de_key += chr((c - 42) ^ 0x2a)
       return de_key
    
    def get_md5(src):
       m = hashlib.md5()
       m.update(src.encode('UTF-8'))
       return m.hexdigest()
    
    
    def calc_clz(reg):
       return 32 - len(str(bin(reg))[2:])
    
    def sign_url(url, key):
       o = urlparse(url)
       key = key[5:]
       # target = o.path + "?" + o.query
    
       query_list = o.query.split("&")
       for q in query_list:
           if "sign=" in q:
               query_list.remove(q)
       target = o.path + "?" + "&".join(query_list)
       # target = "/api/open/v2/forgot-password/check.htm?_platform=android&_srv=t&_appName=jiakaobaodian&_product=%E9%A9%BE%E8%80%83%E5%AE%9D%E5%85%B8&_vendor=xiaomi&_renyuan=XYX&_version=6.9.8&_system=KRT16S&_manufacturer=LGE&_systemVersion=4.4&_device=AOSP%20on%20Mako&_imei=355136058307672&_productCategory=jiakaobaodian&_operator=&_androidId=c8af131c36bb666c&_mac=40%3Ab0%3Afa%3Ac1%3Aec%3A24&_appUser=99cfb9442b804b819ddfbc5a3f71d5fb&_pkgName=com.handsgo.jiakao.android&_screenDpi=2.0&_screenWidth=768&_screenHeight=1184&_network=unknown&_launch=3&_firstTime=2018-03-09%2012%3A23%3A26&_apiLevel=19&_userCity=440300&_p=&_j=1.0&schoolName=%E6%9C%AA%E6%8A%A5%E8%80%83%E9%A9%BE%E6%A0%A1&schoolCode=-1&_webviewVersion=4.7&_mcProtocol=4.0&_r=76aba50fd2fa46c292bbd6b6885cc886"
       decoded_key = decode_key(key)
       md5 = get_md5(target + decoded_key)
       key_sum = 0
       for k in key:
           key_sum += ord(k)
    
       sum = hex(calc_sum(key_sum, 0x13))[2:]
       if len(sum) % 2:
           sum = "0" + sum
       sign = md5 + sum
       print(sum)
       print(sign)
    
    
    
    if __name__ == '__main__':
       sign_key = "*#06#i3lrRYudcZZ2fIx9fI6VqJV8"
       url = "https://auth.mucang.cn/api/open/v2/forgot-password/check.htm?_platform=android&_srv=t&_appName=jiakaobaodian&_product=%E9%A9%BE%E8%80%83%E5%AE%9D%E5%85%B8&_vendor=xiaomi&_renyuan=XYX&_version=6.9.8&_system=LMY48Z&_manufacturer=samsung&_systemVersion=5.1.1&_device=SM-G9350&_imei=862514326681779&_productCategory=jiakaobaodian&_operator=T&_androidId=0086049272635872&_mac=02%3A00%3A00%3A00%3A00%3A00&_appUser=ffd830178df6460e8b401c1421b1e148&_pkgName=com.handsgo.jiakao.android&_screenDpi=1.2&_screenWidth=720&_screenHeight=1280&_network=wifi&_launch=4&_firstTime=2018-03-09%2009%3A28%3A53&_apiLevel=22&_userCity=320100&_p=464D555059511A4751565E5F475A&_ipCity=320100&_j=1.0&schoolName=%E6%9C%AA%E6%8A%A5%E8%80%83%E9%A9%BE%E6%A0%A1&schoolCode=-1&_webviewVersion=4.7&_mcProtocol=4.0&_r=66ae8eb0a00b4d6d9451eecd3ee169a1&sign=13413413413"
    
       sign_url(url, sign_key)
       # print(calc_sum(0x82b, 0x13))
    
    
  • 相关阅读:
    Apache Solrj EmbeddedSolrServer使用
    Apache Solr实现竞价排名
    【Lucene词汇解释】positionIncrement用法
    Zoie:基于Lucene实时的搜索引擎系统
    Solr的创建者介绍Apache Solr : 基于Lucene的可扩展集群搜索服务器
    Solr的自动完成实现方式(第三部分:Suggester方式续)
    Solr的自动完成实现方式(第二部分:Suggester方式)
    【JAVA基础】HashSet、LinkedHashSet、TreeSet使用区别
    Katta:基于Lucene可伸缩分布式实时搜索方案
    老罗Android开发视频教程(Android入门介绍)九集集合
  • 原文地址:https://www.cnblogs.com/hac425/p/9416910.html
Copyright © 2020-2023  润新知