记一次某加速器APP算法解密实现刷邀请

0x00 工具准备

工具打包下载:https://lanzoui.com/b0eknupng 密码:4vpf
各工具使用方法介绍等详细内容可参考各自文档或。

0x01 发现问题

目标APP:蚂蚁加速器(不提供下载)

打开Fiddler,模拟器中设置好代{过}{滤}理,安装证书,打开APP,具体步骤自行百度。

Fiddler中可以看到,所有请求接口发送的数据都是差不多的,看不出发送和返回的数据是什么内容,这次的目标就是将其转为可读的明文,也就是其中的datasign参数生成、返回数据的解密。
fiddler抓包

0x02 用hook插件简单分析

需提前安装Xposed框架。

对简单的APP,如果只用了很常用的标准加密算法(如明文字符串拼接后取MD5、使用明文key进行对称加密),那只要用InspeckageCryptoFucker算法助手其中一个就可以解决了,这三个工具都是用来hook常用加密算法的,如SHAMD5AESDES等,详见各自文档。

三个工具大同小异,本次测试过程中发现的优缺点如下:

  • Inspeckage
    • 优点
      1. 各参数会base64编码,不会导致显示乱码。
      2. 功能很多,加密hook只是其中之一。
    • 缺点
      1. 使用方法比其他两个麻烦一点。
      2. AES的IV参数有时候会漏掉。
      3. 有时不显示加密模式。
      4. sha-256字符串显示错误。
  • CryptoFucker
    • 优点
      1. 会显示HEX编码和解码后内容,不会导致只显示乱码。
      2. 加密模式全部能显示。
      3. 开源,可以自己修改源码。
    • 缺点
      1. 所有加解密都放在同一个文件,看着很乱。
      2. AES的IV参数有时候会漏掉。
  • 算法助手
    • 优点
      1. AES的IV参数不会漏掉,加密模式全部能显示。
      2. 加解密参数一一对应,浏览方便。
    • 缺点
      1. 参数显示乱码。

多个工具可以同时开启。配置好一个或多个hook插件后,打开目标APP,随意切换两个界面后,可以在查看各工具结果。本次测试以算法助手Inspeckage为例。

  • Inspeckage的Crypto结果中一部分json内容可以确定加密算法是AES,keytJb4MRKOT+HeM/S3osRuDdR3JSSkgm/kSG/MB+PiuH8=(base64编码),没有拿到iv,解密模式是AES/CFB/NoPadding,加密模式应该是一样的。
    Inspeckage AES结果
    Inspeckage的Hash结果中可以看到sign的生成是SHA-256后再取MD5,但是在测试SHA-256加密时结果对不上,暂放。
    Inspeckage Hash结果
  • 算法助手结果中可以看到hook到多种类型加密,点进AES,可以看到keyiv都是乱码,也就是说这两个参数都是不可见字符,不过可以确定加密模式是AES/CFB/NoPadding
    算法助手的结果

如果是简单点的APP(像前面说的,明文字符串拼接后取MD5、使用明文key进行对称加密),这个时候可能就结束了,直接拿到明文keyiv以及MD5拼接字符串模板,直接去写代码就完事了。

0x03 脱壳

上面用hook插件得到的结果:

  • 请求data加密和返回数据解密模式为AES/CFB/NoPadding
  • keytJb4MRKOT+HeM/S3osRuDdR3JSSkgm/kSG/MB+PiuH8=(base64编码,可转为HEX编码)。
  • sign生成是SHA-256后再取MD5,但是SHA-256结果对不上。

由于没有拿到iv、SHA-256结果对不上,此时需要进一步反编译分析。

GDA是一个国产免费安卓反编译工具,只有一个几兆的可执行文件,不用配置环境,反编译速度极快,功能比较强大,整体使用体验较好。这里没有选择一些老牌反编译工具,只用GDA就够了。

直接用GDA打开APK,发现提示有360加固,需要脱壳。

GDA中也有拖dex的功能,不过没弄明白怎么用,所以还是用反射大师来无脑拖dex(步骤:Xposed启用模块/反射大师内选择目标APP并打开/点击六芒星图标/点击当前Activity/长按写出DEX)。

此时拿到两个dex,第一个文件比较大,第二个很小,尝试用GDA打开,发现第一个dex无法反编译,而第二个dex内搜不到什么有用信息,所以要用MT管理器或NP管理器修复第一个dex,再用GDA打开。

0x04 用GDA反编译分析

GDA使用比较方便,上方一排工具栏很明确,还有快捷键清单,上手教程见https://zhuanlan.zhihu.com/p/28354064

因为前面确定了加密模式为AES/CFB/NoPadding,所以GDA反编译完成后,直接全局搜索(快捷键S)字符串AES/CFB/NoPaddingCFB来定位加密点(也可搜其他字符串/类名/方法名/变量名,一般从请求接口中来寻找搜索的字符串,可以多尝试)。
GDA搜索结果

搜索结果只有两个,双击进入,发现没有混淆,应该比较简单。这里直接定位到了加密、解密的方法中,左侧看方法列表,可以大致确定算法就是这个了。
加密方法

先稍微读一下Java代码,不会的函数就百度,弄懂这几句还是比较简单的。

public static String decrypt(String p0,String p1){  
   // p0是明文key,p1是返回数据包的16进制字符串data值(交叉引用查看)
   String str = "UTF-8";
   byte[] obyteArray = null;
   try{ 
      // 待解密data值16进制字符串转字节数组
      byte[] bhex2byte = Cfb_256crypt.hex2byte(p1);
      // 初始化实例,加密模式为"AES/CFB/NoPadding"
      Cipher cInstance = Cipher.getInstance("AES/CFB/NoPadding");
      // AES实例参数初始化,key由明文经过EVP_BytesToKey算法生成,iv为data值的前16字节
      cInstance.init(2, new SecretKeySpec(Cfb_256crypt.EVP_BytesToKey(32, 16, obyteArray, p0.getBytes(str), 0)[0], "AES"), new IvParameterSpec(Arrays.copyOfRange(bhex2byte, 0, 16)));
      // 待解密数据为为data值16字节以后的数据
      return new String(cInstance.doFinal(Arrays.copyOfRange(bhex2byte, 16, bhex2byte.length)), str);
   }catch(java.lang.Exception e6){  
      e6.printStackTrace();
      return obyteArray;
   }    
}
public static String encrypt(String p0,String p1){  
   // p0是明文key,p1是待加密数据(交叉引用查看)
   String str = "UTF-8";
   byte[] obyteArray = null;
   try{ 
      // 初始化实例,加密模式为"AES/CFB/NoPadding"
      Cipher cInstance = Cipher.getInstance("AES/CFB/NoPadding");
      // AES实例参数初始化,key由明文经过EVP_BytesToKey算法生成,iv未指定,为随机生成
      cInstance.init(1, new SecretKeySpec(Cfb_256crypt.EVP_BytesToKey(32, 16, obyteArray, p0.getBytes(str), 0)[0], "AES"));
      // 随机生成的iv+加密后的数据,合并后转为16进制字符串,发送给服务器
      return Cfb_256crypt.byte2hex(Cfb_256crypt.byteMerger(cInstance.getIV(), cInstance.doFinal(p1.getBytes(str))));
   }catch(java.lang.Exception e6){  
      e6.printStackTrace();
      return obyteArray;
   }    
}

光标放在encryp/decrypt方法上,可以查看是哪里调用的(即交叉引用,快捷键X),直接定位到了请求接口的封装类,顺便直接拿到了key的明文。
交叉引用
接口封装类

同时在左边发现了sign方法,进去看一下发现果然是接口中的sign(也可以搜索字符来定位,比如搜索前面看到的appVersion=2.1.8

public static String sign(String p0,String p1){ 
   // p0是data值,p1是时间戳
   return MD5Util.getMD5(Cfb_256crypt.getSHA256StrJava(new StringBuilder()+"appId=android&appVersion=2.1.8&data="+p0+"×tamp="+p1+"2d5f22520633cfd5c44bacc1634a93f2"));
}

分析部分到这里就可以结束了,有key(明文fjeldkb4438b1eb36b7e244b37dhg03j传入EVP_BytesToKey生成,HEX编码为B496F831128E4FE1DE33F4B7A2C46E0DD4772524A4826FE4486FCC07E3E2B87F)、有iv(加密iv随机生成,解密iv是数据包前16字节,即前32位字符),可以直接调用AES算法,有SHA-256字符串拼接模板,可以直接拼接调用SHA-256和MD5算法。

注:1byte(字节)=8bit=8位二进制=2位16进制(2的4次方=16,即每4位2进制等于1位16进制)

0x05 验证结果

用数据加解密工具来验证一下,注意key是由算法生成的,base64解码后是乱码,所以这里只能填hex编码转换后的了(工具有网页在线版,GDA内也有,这里用的是Fiddler的一个插件,因为其他的基本不能使用HEX)。

// 发送数据
appId=android&appVersion=2.1.8×tamp=1621007748&data=B5A74CCEA048E1AE91FB4C5BD5A6FA27CCD86F24ABD094763247C15100FA2129469EF559AEA2086A0FA28E78910180580993AE35CCDEBA6C7AD1E07E2508AF9307AD2179B3068D63B2B9260B91E0E79BAE26B36F7CD1824496EB26326AAA76831872EE49998292CDC0D2D7B011C340558D384F01212E9C335BD4CB337E2D72974748B7586050BDB45708930E7512F9AE88BD33FCCFD5257DAFB13D7F5766F5000AA3968DF5ED8434F27384226AE111343DB670A7C7014FD5BB96898E5621E1E0&sign=656c9fb2f0d00b9fcf3fa2d38fa2488d

// 返回数据
{"errcode":0,"timestamp":1621007686,"data":"645328E9B5A19FDCC309EE6067BB3F56E04D05060C268D8B8F0CC850C2DD14117158D6C28D2EAB5C5E27C10690FBAE7729CDE74A20A48CC59BE6FD6C6533DB6D3D120AB160B46A594324F65C2135EB5C9C00FF1EE0CE5682DD62EECDD8BD2E0697DD35AC49DC2735F16C878A46B4C810D1A850A5A80FA85F02833752F7224B9460340B62D20CD1E177CCA878463FA7F76FB0798B2E35B0B75EE1580E0515115670C3E1F504E34F268767BE9A32601D29538724EA6CDDE3FD1D16C605C83A2C30E0AF1F05F6ABEC631CABB3491990FD0623B91466D2E36F4806F7549E839ACF21485AED3EEB768753AF952ED52399A38B1FD4FC42319CA83452F8B76F62B46CEB64E8BB78D3CF28E8A75C045C2D18DE595046584EB37CD3A8FE15831807827ECABB028A3C77334C1FF726B5B075087AE2908A0308188A0D21D604EB11CE00D85FFF8F70C3AF2F4339463D1A93782EB4A7A0AEF880B024419846B207015B4A541E9A57D8F0B0E7FCE055C82DAF90720106862C5DC6F11471E86347AC2B17BC9ED3C60BB9C29043FF838F71F9E8CFA8F9579CF1CFA821F8388A5CE3868C6AE6992FB6D69AD85B8672AE682AEE9A5BD8F2D86640B7167B26BC3B67493B21FAD7D5FA6D670082F9E669B1FDC02FA6007E440453B5FDD0193BB99494B33786407883DFCD881E076205EF8929BD33CEA2357A3F1F02EAFF20FD3D2449011E6A728FE02F4C7109A27A066631D238F5B84BC75977201D36ABDE343D6C7C626987AE66232BB918870FF6F35C4EEEFE5D728D4069B89D6F0099E92F3B1E1D13A4BE9D5DF9EF66B35223105EAC37EEB15E37F7CA77533FCEBF9329983D625BCE2B4690E87EDEAEFC0C1AF145474435A8D322781FFCCE13113189764A65079D281B2FC7066AC5201AA2C191A076C9C55B89E198C1FAEAD690CF4D4F1D441A926EAEBAFC2F6ED1ECECF8571F9C60CBBD0A509EF07CDB3492F9DAB27F6EBE397CA1DB558B8C28A7C6517AEAEA0E57062702D27B4B217A862C80211E92A4B436991707120782E859242A2A95746A5514F0EC1FFFA409300A92D95A3D6B8496EEC86126EF98159AD56A5F45CCE9D0AB7AF582B0B80AB2DC65141B777307E8CA47418F9546C353E422688AB3F3F53C12E10277F8ABAC09ED4E6B3A44C90740A4147488E647E9D7C9105DF3B9D104872112380CC9D46B563ADAB09C9D815FE310084FEBFA253F8A728D3F0CB64CEA1E99FD0EDE2C7FABB7A9C915142E2B6E110C046D838019008B98A66F4CC0142460105C42C407287E629D5A77A1EFBD6CD212BFD1E8D2AC2CB446FF54C8ED111D14D8C6BF060AC9B623154D2793DC4893176C89D25B22BE39231CB3C2915803F76DEA27D828BB95B32E42439415231AC3BEF11B510061F03C60F8DBEB13941C1E3368EC13445A82EC1360EC7FEE82E260F31D1D3D6DA1E59C1ECA4FCF03DCCC7FA787DCD779662F60CE8310021B7A7A2090F9499FE96F6F1A165B4171049220DF2CA60F1A57F7A7EED72F711CB1B1BD823765A2843AB1271BBD452498DBF9614E0E2FBAC4D3222560461259EC2BA22A054B5AAA74715D11C93253F9D27C3A2B530DD3421E5A4809E17F361E05F1BB875EDFA7614743BA1B940934057E0398867992D6F47F7C713C2C927231EDA322118E37C162E3640A914CC13B194502ED8461591946A0F0B2E26D332E27F677B5986C94B06FD5C8ED8B9C0C7CA2AF3E13F00101E83D0C71370626A89D783253CBA409786C8CF1D3AF4AEE2BFC1E47185F37D2A6DAF25AD0BC2D0C60FFE8F2F8BDE3CA61A6D3841A3936B0B8B3EC0F7D2F6F524D4EBF07195AEF8625781A758D47190F14B264ABFD36D9D7151E036D770001F0F4B0E14C6E5AB1D3A69EA9414006B3C8B4AC5F94D4B481DD82CD58DEBFD00896A9266157B31B18C6682ED30119B75BD74B23242BEE09E5E35E3A8721B6CECB2349611858501CEFD8B10335AD8883A85FECDA647379E6F5CB5F7E9C211EE38F4562AF19E79904FE3CCFC7D912B43563EE20ABA37BBD5E0971887511AA2A3218FF12321B3AAD5CA8635FB38A2D59D0FAF03A04747B7DC06E45EB447161099173B0B31B1FCE03B5496797B261183FB6E1C467B64A02B44A78680D7A9DEC731A5A4DE89300F99C8A850D806E13E596A7882D140409E3EA30BD87584BE35594F89109C7F95F30D92B8CDC589DAAF121C5604FD1243911A135336ED7C97CFD0EE1CBDE5997DA3778C72827327FD38556AC417E0C1F4BBD82AC34D8F742C211C472411EA4C16C37176B973419CF85FBCFAFA400D916350ED6BFBD4F5DB5695865010BFB6B4D39DA6DCC4346DA8E2D186E5E633E7B4D57044373105590F8507D897F508604568B055EBF86023A2B273A765104CE1A616BFAAF31560EA96169255644D1A57EC91DBC07A2029FFABFCD910458192C30D1506D79BC383991A2FCB65029CECDE540E70E2734F18DF04A71616A6229BCE80082AA9114C7670F1A126AEFFB30375B99840A65792631106211A352AFCB54FF2EA687D771709FECCA08FF6254FBED743F78E9528D7255AF87EE6276C658D67CBD788460526896DFEE9CCF57EF23F5FC116CFD59414465FA05094AEA80D4687459982C12C5B98BF8A3DF38F13E24F7DAB4D85E7FB4910C0992B9E14285E2CDE40C619244A4ACC1A261A5883C4E504FF549EC43776964ABFF10016D380919CD2AB62FAF312412702E1C9075941BD5791B756B951C539A86871D92C36CA033BF41EF31B21EE35582947B20AA1D00A65A6290E9FAC14CD21B11DE0BB6CDFB906B28F859FBCD9AC2588C5CCD0D06A49DC6DC80603EDE4F6CF709EF7BD4D5F79968664C502282B51F78F1E61FCBB10FC7D7E2747AADA81397C459772936925048E689EF4E681C75F3D6222DA8DC90","sign":"9a0ab084be6f0bc9415b0c204f7fdee5"}

将发送包或返回包data数据分割为两部分,前32位是iv,后面是待解密数据(需要将待解密数据HEX转为base64编码)。
验证解密结果

sign字符串拼接模板为"appId=android&appVersion=2.1.8&data="+p0+"×tamp="+p1+"2d5f22520633cfd5c44bacc1634a93f2,其中p0是data值,p1是时间戳,经SHA-256和MD5加密后结果正确。
SHA-256加密
MD5加密

0x06 刷邀请

数据包解密完成,就可以写代码了。

这时会意识到,抓包不能看到明文,那怎么写代码?答案还是对照着Inspeckage等工具查看数据明文。当然也可以是手动hook刚才定位到的加解密方法(Inspeckage中有自定义hook,但测试无效,估计要重新写个xp插件或者找其他工具替代),实现自动输出数据包明文。再或者根据源码中封装请求包的格式去手动还原。
提交邀请码的明文

Python代码

# -*- encoding: utf-8 -*-
'''
home.php?mod=space&uid=267492    :   main.py
home.php?mod=space&uid=238618    :   2021年05月16日 15:55:51 星期天
home.php?mod=space&uid=686208  :   erma0
home.php?mod=space&uid=1248337 :   1.0
@Desc    :   蚂蚁加速器刷邀请
'''

import requests
import time
import json
from base64 import b64decode
from hashlib import sha256, md5
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
# from Crypto.Hash import SHA256, MD5  # 和hashlib库一样

class Ant(object):
    """
    蚂蚁加速器刷邀请
    """
    def __init__(self, aff):
        self.aff = aff
        self.oauth_id = ''
        self.timestamp = ''
        self.url = 'http://xxx.xxx.com/api.php'
        self.headers = {  # 加不加header都可以
            # 'User-Agent':
            # 'Mozilla/5.0 (Linux; U; Android 7.1.2; zh-cn; E6533 Build/N2G48H) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30'
        }
        # 明文key,再经EVP_BytesToKey方法生成最终key,最终HEX为:B496F831128E4FE1DE33F4B7A2C46E0DD4772524A4826FE4486FCC07E3E2B87F
        self.key = 'fjeldkb4438b1eb36b7e244b37dhg03j'  # 没发现哪个加密库中有EVP_BytesToKey算法
        self.hexkey = 'B496F831128E4FE1DE33F4B7A2C46E0DD4772524A4826FE4486FCC07E3E2B87F'
        self.b64key = 'tJb4MRKOT+HeM/S3osRuDdR3JSSkgm/kSG/MB+PiuH8='

    @staticmethod
    def get_timestamp(long=10):
        """
        取时间戳,默认10位
        """
        return str(time.time_ns())[:long]

    def decrypt(self, data: str):
        """
        aes解密
        """
        ct_iv = bytes.fromhex(data[:32])
        ct_bytes = bytes.fromhex(data[32:])
        ciper = AES.new(
            b64decode(self.b64key), AES.MODE_CFB, iv=ct_iv,
            segment_size=128)  # CFB模式,iv指定,块大小为128(默认为8,需填8的倍数,貌似AES标准区块大小就是128,和密钥大小128/192/256无关)
        plaintext = ciper.decrypt(ct_bytes)
        return plaintext.decode()

    def encrypt(self, data: str):
        """
        aes加密
        """
        # cipher = AES.new(bytes.fromhex(self.hexkey), AES.MODE_CFB)
        cipher = AES.new(b64decode(self.b64key), AES.MODE_CFB, segment_size=128)  # CFB模式,iv自动随机,块大小为128
        ct_bytes = cipher.iv + cipher.encrypt(data.encode())  # iv+加密结果合并
        return ct_bytes.hex().upper()  # hex编码

    def get_sign(self):
        """
        生成sign
        """
        template = 'appId=android&appVersion=2.1.8&data={}×tamp={}2d5f22520633cfd5c44bacc1634a93f2'.format(
            self.encrypt_data, self.timestamp)
        # sha256
        sha = sha256()
        sha.update(template.encode())
        res = sha.hexdigest()
        # nd5
        m = md5()
        m.update(res.encode())
        res = m.hexdigest()
        return res

    def request(self, d):
        """
        请求封包
        """
        plaintext = {"version": "2.4.3", "app_type": "ss_proxy", "language": 0, "bundleId": "com.dd.antss"}
        d.update(plaintext)
        self.timestamp = self.get_timestamp(10)
        self.encrypt_data = self.encrypt(json.dumps(d, separators=(',', ':')))
        sign = self.get_sign()
        data = {
            "appId": "android",
            "appVersion": "2.1.8",
            "timestamp": self.timestamp,
            "data": self.encrypt_data,
            "sign": sign
        }
        res = requests.post(url=self.url, data=data, headers=self.headers)
        resj = res.json()
        res = self.decrypt(resj.get('data'))
        print(res)
        return res

    def get_user(self):
        """
        生成新用户
        """
        # 取随机md5
        m = md5()
        m.update(get_random_bytes(16))
        oauth_id = m.hexdigest()

        data = {"oauth_id": oauth_id, "oauth_type": "android", "mod": "user", "code": "up_sign"}
        self.request(data)
        self.oauth_id = oauth_id
        print(oauth_id)

    def invite(self):
        """
        刷邀请,邀请码:self.aff
        """
        self.get_user()
        data = {
            "oauth_id": self.oauth_id,
            "oauth_type": "android",
            "aff": self.aff,
            "mod": "user",
            "code": "exchangeAFF"
        }
        self.request(data)

if __name__ == "__main__":
    ant = Ant('a6aVx')
    ant.invite()

备注:

本文由吾爱破解用户ermao原创,本站只做原样转载

原文链接:

https://www.52pojie.cn/thread-1441989-1-1.html

© 版权声明
THE END
喜欢就支持一下吧
点赞12
分享
评论 抢沙发

请登录后发表评论