加菲猫 邀请协议-算法分析
本帖最后由 yuanyxh 于 2021-12-25 17:44 编辑1.加菲猫影视1.6.2
2.小黄鸟
3.MT管理器
4.IDA
软件是别人发的,且被修改过,应该是服务器验证,所以没有尝试本地破解,直接进行算法分析;
本人技术有限,有说的不对的地方请大佬们指正{:1_936:} 。
一.黄鸟抓包
打开黄鸟选择目标为加菲猫影视,打开加菲猫影视并输入邀请码提交,黄鸟抓到包后,打开 "http://jk.b557b8.com/App/AppUserInvitation/beOne" 这一条封包信息,
从响应信息来看就是这一条,再看提交信息,我们要分析的有 "token","token_id"和"request_key",其中"request_key"明显是经过加密处理的,且每次都不一样,那我们就先分析这个。
二.MT静态分析
使用MT反编译软件,搜索字符串"request_key",只有两个结果,打开第二个,转成java看一下。
可以看到在f方法内进行了加密处理,key和iv来自c层。
三.IDA静态分析
用IDA打开 "libnative-lib.so",在导出列表里搜索Java,找到对应java层的函数,
点进去看发现都是明文
找个在线解密网站试一下,加密方式为"AES/CBC/PKCS5"(ps:找的解密网站不支持,不过通过试验"AES/CBC/PKCS7"也能正常解密),密文编码为"HEX",输出结果如下:
其中,"code"为邀请码,"nt"很明显是时间戳,"ns"看起来也是加密过的,那我们继续使用MT进行分析。
四.ns密文分析
继续使用MT反编译,搜索字符串"ns"并勾选区分大小写及完全匹配,搜索到5个,打开最后一个,转成java,
nt确实是时间戳,ns则是"com.video.test.utils.EncryptUtils"类"getEncryptKey"方法的返回值,传了两个参数:"Context"和当前时间戳的字符串;从前面的分析得知这个方法是一个native方法,具体实现在c层,所以继续使用IDA分析,
可以看到就是获取一系列字符串然后进行MD5加密,由于本人技术有限,能静态分析出来的只有:包名,传进来的时间戳,"&z4Y!s!2br";"GetSignatureString"函数的返回值实在是分析不出来,所以采用动态调试的方式实时观察每个寄存器的值,在关键地方下断点后运行APP,再观察寄存器的值,得到结果如下:
那么"ns"就是由:包名 + "1A060008D770327E3BC1521FAB2114C788B77435749590CBF4DA5B97512AC7FA" + " &z4Y!s!2br " + 时间戳 MD5加密得来的,功能就是验证请求是否有效。
五.token及token_id的分析
MT反编译后搜索"token"和"token_id",可以搜索到,但是一直分析不出来源头,不过分析代码知道每次使用时是从"SharedPreferences"获取的,那我们直接在软件的数据目录"shared_prefs"文件夹进行搜索,
这样就有思路了,有获取就有写入,只需要找到写入的地方就能分析出来,继续反编译软件搜索,搜索目标换成"userToken"及"userTokenid",但是通过分析依旧找不到源头,
查找"setToken"及"setToken_id"的调用无法找到,那就只能换一个思路了,能看到上图中有"login success"的字样,猜测是发送登录请求后返回的"token"和"token_id",继续使用黄鸟抓包,并找到 "http://jk.256537.com/App/User/newLogin" 这一条封包,信息如下:
其中请求信息中的"token"和"token_id"新用户应该为"no",但是我在写教程的时候抓不到包了,所以用的是以前的抓包信息,我们继续使用解密网站解密"request_key"和"response_key",第一张图片是响应的,第二张是请求,
现在明确了"token"和"token_id"是通过发送登录请求后返回的,而登录请求中:"new_key"是设备id(通过之前的截图能看到),"old_key"通过多次试验是固定不变的,"ns"已经分析过,"nt"是时间戳,其他的不需要改变;到这里邀请协议所需要的信息全部分析完成。
2021/12/25补充
ns密文中的"GetSignatureString"返回值应该是软件的SHA256签名,而登录请求中的"old_key"也不是固定不变的,而是阿里的库生成的UTDID,每个设备都不同,具体实现在com.ta.utdid2.device.c.h方法内,感兴趣的大佬可以去研究一下,最后感谢@低调(d-iao) 大佬的提醒;
七.结语
写了一下午,越写越糊涂,感觉写的好烂,希望看到的大佬们轻点骂{:301_980:}
下面是从网上搜索,拼拼补补写的一个py脚本,经过大佬@正己 优化的:
```
# -*- coding: utf-8 -*-
import binascii
import re
import requests
import time
import random
import hashlib
from Cryptodome.Cipher import AES
AESKEY = '8jhM5h6dezq4QifP'# 请修改 一定是 16位的字符串
AESIV = 'tho3aAHJyZCWAfTG'# 和KEY保持一致即可
class AESTool:
def __init__(self):
self.key = AESKEY.encode('utf-8')
self.iv = AESIV.encode('utf-8')
def pkcs7padding(self, text):
"""
明文使用PKCS7填充
"""
bs = 16
length = len(text)
bytes_length = len(text.encode('utf-8'))
padding_size = length if (bytes_length == length) else bytes_length
padding = bs - padding_size % bs
padding_text = chr(padding) * padding
self.coding = chr(padding)
return text + padding_text
def aes_encrypt(self, content):
"""
AES加密
"""
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
# 处理明文
content_padding = self.pkcs7padding(content)
# 加密
encrypt_bytes = cipher.encrypt(content_padding.encode('utf-8'))
# 重新编码
result = binascii.b2a_hex(encrypt_bytes).upper()
return result
def aes_decrypt(self, content):
"""
AES解密
"""
generator = AES.new(self.key, AES.MODE_CBC, self.iv)
content += (len(content) % 4) * '='
# decrpyt_bytes = base64.b64decode(text) #输出Base64
decrpyt_bytes = binascii.a2b_hex(content)# 输出Hex
meg = generator.decrypt(decrpyt_bytes)
# 去除解码后的非法字符
try:
result = re.compile('[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f\n\r\t]').sub('', meg.decode())
except Exception:
result = '解码失败,请重试!'
return result
def invite():
#随机手机型号
model_name = ["oppo-pedm00","oppo-peem00","oppo-peam00","oppo-x907","oppo-x909t",
"vivo-v2048a","vivo-v2072a","vivo-v2080a","vivo-v2031ea","vivo-v2055a",
"huawei-tet-an00","huawei-ana-al00","huawei-ang-an00","huawei-brq-an00","huawei-jsc-an00",
"xiaomi-mi 10s","xiaomi-redmi k40 pro+","xiaomi-mi 11","xiaomi-mi 6","xiaomi-redmi note 7",
"meizu-meizu 18","meizu-meizu 18 pro","meizu-mx2","meizu-m355","meizu-16th plus",
"samsung-sm-g9910","samsung-sm-g9960","samsung-sm-w2021","samsung-sm-f7070","samsung-sm-c7000",
"oneplus-le2120","oneplus-le2110","oneplus-kb2000","oneplus-hd1910","oneplus-oneplus a3010",
"sony-xq-as72","sony-f8132","sony-f5321","sony-i4293","sony-g8231",
"google-pixel","google-pixel xl","google-pixel 2","google-pixel 2 xl","google-pixel 3"]
random_model_name = random.choice(model_name)
#调用aes加密
aes = AESTool()
#注册账户
t = str(int(round(time.time() * 1000)))
#随机设备id
device_id = "".join(random.choice("0123456789ABCDEF") for i in range(32))
ns = 'com.jfm2110152DAA94115DC5C48038693654FFCC3AA095CBD093165B47BD7F15C8F83CA1BC9B&z4Y!s!2br' + t
MD5ns = hashlib.md5(ns.encode(encoding='UTF-8')).hexdigest()
Request_key = '{"new_key":"' + device_id + '","phone_type":"1","ns":"' + MD5ns + '","nt":"' + t + '","old_key":"YYqIkUniVrkDAACxRfIkIvsY","recommend":""}'
MD5Request_key = aes.aes_encrypt(Request_key)
HexMD5Request_key = MD5Request_key.decode('unicode_escape')
body = 'token=no&token_id=no&phone_type=1&versions_code=1402&phone_model='+ random_model_name +'&request_key=' + HexMD5Request_key + '&app_id=1&ad_version=1'
header = {
'Cache-Control': 'no-cache',
'Version': '210930',
'channel_code': 'xc_tg18',
'Referer': 'http://jk.b557b8.com',
'Content-Type': 'application/x-www-form-urlencoded',
'Host': 'jk.b557b8.com',
'User-Agent': 'okhttp/3.12.0'
}
urls = 'http://jk.256537.com/App/User/newLogin'
Response_body = requests.post(url=urls, data=body, headers=header).text
Response_body_Encrypt = Response_body
Response_body_Decrypt = aes.aes_decrypt(Response_body_Encrypt)
token_id = Response_body_Decrypt
token = Response_body_Decrypt
#邀请
Request_key2 = '{"code":"2UDYUB","ns":"' + MD5ns + '","nt":"' + t + '"}'#code后填入你的邀请码
Request_key_Encrypt2 = aes.aes_encrypt(Request_key2)
HexRequest_key_Encrypt2 = Request_key_Encrypt2.decode('unicode_escape')
body2 = 'token=' + token + '&token_id=' + token_id + '&phone_type=1&versions_code=1402&phone_model=' + random_model_name + '&request_key=' + HexRequest_key_Encrypt2 + '&app_id=1&ad_version=1'
urls2 = 'http://jk.b557b8.com/App/AppUserInvitation/beOne'
Response_body = requests.post(url=urls2, data=body2, headers=header) # 返回200即邀请成功
print(Response_body.text)
if __name__ == '__main__':
j = 0
for i in range(50):# 五十次即可永久去广告
time.sleep(random.randint(1, 15))# 设置随机延时
invite()
print("已刷{}次".format(i))
j += 1
if j == 50:
break
``` 一脸懵逼的进,一脸懵逼的出:lol 感谢分享,以前碰到算法类的直接战术后退了,看了大佬的分享和解密过程受益匪浅 这就是简仙人的实力吗? 正己 发表于 2021-12-2 22:30
这就是简仙人的实力吗?
被大佬摩擦的实力{:301_988:} 简仙人厉害,学习了{:301_997:} littleWhiteDuck 发表于 2021-12-2 22:37
简仙人厉害,学习了
日常催更新{:301_991:} 简仙人加油!向简大佬学习 lianquke 发表于 2021-12-2 23:01
简仙人加油!向简大佬学习
向大佬学习{:1_887:} 加油,向大佬学习 实力爆棚