吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 8386|回复: 70
收起左侧

[Android 原创] 某免费小说APP sign参数逆向分析与实现

  [复制链接]
S11ence 发表于 2022-12-8 10:41
本帖最后由 S11ence 于 2022-12-8 10:47 编辑

Overview

闲来无事,随便下载了个免费小说软件,对其中的登录时的参数进行了分析

Java层分析

抓包定位函数

在登录发送验证码时使用Charles抓包

sendSms_packet

sendSms_packet

根据关键词在Java层搜索

sendSms_search

sendSms_search

查找用例,定位到函数

sendSms_h

sendSms_h

可以看到将一些参数存入ArrayMap中传入j.addSign()函数中,这里的addSign是我自己手动修改的名字

传入的Key值对照数据包中的参数也都能够对的上

sendSms_content

sendSms_content

跟进分析

继续跟进addSign函数中

addSign

addSign

又添加了一个时间戳键值对后,经过getSortedParamStr后传给hash得到sign

getSortedParamStr.png

getSortedParamStr将键值对按键进行排序后,转成字符串返回。不同键值对之间用&连接,键与值之间用=连接

比如,对如下键值对进行转换

arraymap = {
    "flag": "1",
    "channelId": "1240202",
    "imei": "____7548",
    "device": "Nexus 5X",
}

channelId=1240202&device=Nxus 5X&flag=1&imei=___7548

Securityhash函数调用了JNISecurityhash2函数,参数有SignatureSHA1WithRSAKeyFactoryRSA,字符串转成的字节

Security_hash

Security_hash

JNISecurity里的hash2是一个native函数,可以看到类里加载了UiControl库,大概率就在libUiControl.so

JNISecurity_hash2

JNISecurity_hash2

So层分析

定位到动态注册函数

解压后在lib文件夹里找到libUiControl.so

先在Function搜索hash2,不出所料没有结果,所以这是动态注册的函数

search1

search1

那么应该分析JNI_OnLoad函数

JNI_OnLoad1.png

GetEnv得知参数v26类型为JNIEnv*,另外看下面有调用固定常数偏移函数大概率都是JNIEnv*参数,重设类型后就能分析出JNI函数了

JNI_OnLoad2.png

往下分析,在sub_7A5A8函数中找到了调用了RegisterNatives函数,可能是我们要找的hash函数

sub_7A5A8

sub_7A5A8

sub_87890.png

RegisterNatives的定义可知,

第一个参数clazz是注册的函数所在的类

第二个参数methodsJNINativeMethod类型,里面包含了函数名、函数签名以及函数指针

jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,jint nMethods)
typedef struct {
    const char* name;
    const char* signature;
    void* fnPtr;
} JNINativeMethod;

不过这里的各个参数当前都是空的数据,需要先经过sub_78F54解密才能得到原本的数据。第二个参数是我们的结果

sub_78F54.png

sub_78F54函数的逻辑也比较简单,主要运算的部分也就只有中间这一句而已

2F5008

2F5008

v9是第一个参数,结合具体值分析可以得到是取三位一组然后转成十进制数,和v12异或

v12是字符串"8080"

后面的v8 - (v10 & 0xFFFFFFFC)其实就相当于v8&3,只取了最后两位

可以得到一个简单的idapython解密脚本

def decrypt(start,size=0):
    if size == 0:
        size = get_item_end(start)-start
    data = get_bytes(start,size)
    key = [56,48]
    out = []
    for i in range(0,len(data),3):
        tmp  = data[i:i+3].decode()
        if tmp.startswith('\x00'):
            return out
        t = int(tmp)
        tmp = t^key[i//3%2]
        out.append(chr(tmp))
        s = ''.join(out)
    return s
addrs = [0x2F5008,0x2F5084,0x2F5130,0x2F513D,0x2F51EC]
for i in range(len(addrs)):
    if i !=len(addrs)-1:
        size = addrs[i+1]-addrs[i]
    else:
        size = 0x10

    s = decrypt(addrs[i])
    print(hex(addrs[i]),''.join(s))

输出结果发现果然是我们想要找到的hash2函数

decrypt_output.png

重命名参数后,可以知道sub_877EChash函数的函数指针

sub_87324hash2函数的函数指针

sub_87890_2.png

另外有个坑点,一开始分析的是arm64的so文件,可以看到反编译的结果不是很好分析,头脑有点迷糊没有对上哪个函数指针对应哪个函数

后面换了32位的so文件才发现反编译效果好的太多了,不仅methods数组各个参数排列的很整齐,甚至hash2函数名的符号表都还在:cry:

sub_49B8C

sub_49B8C

让我想到了之前打的一个比赛中,两个不同架构的so文件,arm的反编译有问题,反而x86反编译的结果非常清晰

分析hash2函数

点进来同样发现调用了指针加偏移的函数,估计也是JNI函数,不过还是跟进分析一下sub_78EEC函数

sub_87324_1.png

sub_78EEC也是这样的调用函数,惯性的改类型成JNIEnv*发现得到的是FindClass函数,显然不太对

sub_78EEC

sub_78EEC

FindClass显然得不到JNIEnv*类型的变量

sub_78EEC_2

sub_78EEC_2

交叉引用一下,发现在JNI_OnLoad里调用了这个变量,才发现原来是JavaVM*类型

qword_3E2860

qword_3E2860

改成JavaVM*就得到了GetEnv函数

sub_78EEC_3.png

sub_87324里的各个env变量修正后,JNI函数就都能正常显示了,

将其中的几个字符串写在了旁边的注释中

函数逻辑也比较清晰了

sub_87324_2

sub_87324_2

还原算法

比较常见的JNI层调用Java算法的过程

  1. FindClass获取类对象
  2. NewObjectV获取类的实例
  3. 然后GetMethodID获取所要调用的methodID
  4. 调用CallObjectMethodVCallVoidMethodV等函数来调用对应的方法

翻译成Java代码,一个比较常规的签名算法

byte[] key = new byte[]{};
byte[] bArr = str.getBytes(StandardCharsets.UTF_8);
PKCS8EncodedKeySpec pkcs8EncodedKeySpec =  new PKCS8EncodedKeySpec(key);

KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
Signature signature = Signature.getInstance("SHA1WithRSA");

signature.initSign(privateKey);
signature.update(bArr);
byte[] result = signature.sign();
retrun result;

typehash2的第一个参数,传入的是固定的2,因此这里使用的私钥是off_3BFC50+1=unk_2F5477

off_3BFC50

off_3BFC50

长度为0x279

arr_size.png

hash2签名后再Base64一下即为sign参数值

代码验证

使用python还原算法后验证,同时实现了发包请求

class DeJian:
    def __init__(self, phone):
        self.phone = phone
                self.logger = self.init_log()

    def init_log(self):

        # 创建logger实例
        logger = logging.getLogger('DeJian')
        # 设置日志级别
        logger.setLevel(logging.DEBUG)
        # 流处理器
        ch = logging.StreamHandler()
        ch.setLevel(logging.DEBUG)
        # 日志打印格式
        formatter = logging.Formatter('%(message)s')
        # 添加格式配置
        ch.setFormatter(formatter)
        # 添加日志配置
        logger.addHandler(ch)
        return logger

    def req(self, method, url, **kwargs):
        if kwargs.get("headers"):
            # 如果传递过来的请求有头信息,那么我们就在头信息中做追加
            kwargs["headers"].update = {
                "content-type": "application/x-www-form-urlencoded",
                "Host": "dj.palmestore.com",
                "user-agent": "Dalvik/2.1.0 (Linux; U; Android 8.1.0; Nexus 5X Build/OPM1.171019.011)"
            }
        else:
            # 如果传递过来的请求没有header
            kwargs["headers"] = {
                "content-type": "application/x-www-form-urlencoded",
                "Host": "dj.palmestore.com",
                "user-agent": "Dalvik/2.1.0 (Linux; U; Android 8.1.0; Nexus 5X Build/OPM1.171019.011)"
            }
        for k, v in kwargs["data"].items():
            kwargs["data"][k] = self.url_encode(v)
        kwargs["data"] = self.get_sorted_param_str(kwargs["data"])
        self.logger.debug(f"请求的参数为{method},url为{url}, 其他参数为{kwargs}")
        r = requests.request(method=method, url=url, **kwargs)
        self.logger.info(f"响应内容为{r.json()}")
        return r.json()

    def url_encode(self, s):
        r = ['+', '/', '=']
        res = s
        for i in r:
            res = res.replace(i, '%' + hex(ord(i))[2:].upper())
        return res

    def sign(self, content):
             private_key = b""
        pri_key = RSA.importKey(private_key)
        signer = PKCS1_v1_5.new(pri_key)
        hash_obj = SHA1.new(content.encode())
        sig1 = signer.sign(hash_obj)
        signature = base64.b64encode(sig1).decode()
        res = self.url_encode(signature)
        return res

    def get_sorted_param_str(self, dic):
        content = ''.join([f'{k}={dic[k]}&' for k in sorted(dic)])[:-1]
        return content

    def sendSms(self):
        url = ''
        dic = {
            "versionId": "20005056",
            "device": "Nexus 5X",
            "flag": "1",
            "imei": "",
            "phone": self.phone,
            "times": "1",
            "sendType": "0",
            "channelId": "1240202",
            "timestamp": "1669439016670",
        }
        dic["sign"] = self.sign(self.get_sorted_param_str(dic))

        r = self.req('post', url, data=dic)

可以看到计算得到的sign值和抓包得到的sign值是一致的

sign_send

sign_send

同时返回包也是成功

sns_reponses

sns_reponses

手机上也是成功收到了短信

同样地该APP在登陆时的sign参数也是类似的逻辑

login_packet

login_packet

login_content

login_content

将之前的代码添加了登录的功能,实现了从获取验证码到登录的过程

output

output

免费评分

参与人数 19威望 +2 吾爱币 +118 热心值 +17 收起 理由
procurve + 1 + 1 谢谢@Thanks!
lgc81034 + 1 谢谢@Thanks!
qinwang + 1 + 1 我很赞同!
junjia215 + 1 + 1 谢谢@Thanks!
4nfu + 1 热心回复!
mudboy + 1 + 1 热心回复!
努力加载中 + 1 + 1 热心回复!
Cerem + 1 + 1 我很赞同!
shizhijun + 1 + 1 谢谢@Thanks!
KKBon + 1 谢谢@Thanks!
fengbolee + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
allspark + 1 + 1 用心讨论,共获提升!
Demigod_Ah + 1 + 1 我很赞同!
xuanshixh + 1 + 1 用心讨论,共获提升!
log1992 + 1 + 1 学习了
正义天下 + 1 + 1 谢谢@Thanks!
正己 + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
fyj0811 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
18895411830 + 1 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Aswind 发表于 2022-12-9 22:25
夜泉 发表于 2022-12-9 03:26
我想知道的是这个app是啥?想上手试试

得间------------
lysmbetter 发表于 2022-12-8 12:15
dailexing 发表于 2022-12-8 13:24
dzqlzy1212 发表于 2022-12-8 13:43

感谢分享,学习了
bad55 发表于 2022-12-8 14:05
谢谢分享,学习了。
Heat 发表于 2022-12-8 15:11
挺深入的,学习一下
wzg01 发表于 2022-12-8 15:16
感谢楼主的分享
一个板栗 发表于 2022-12-8 15:24
感谢分享,书龄十年,这个很需要
正义天下 发表于 2022-12-8 18:10
谢谢分享思路~
xiaosafenger 发表于 2022-12-8 18:33

感谢分享,学习了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 13:35

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表