对一个手机群控软件的授权流程的分析过程
0x01 抓包这个软件对连接数进行了限制,要输入授权码进行网络验证。
通过抓包,分析软件提交的数据,serialNumberKey后面跟着一大串字符,看起来很像base64编码,猜测加密后使用了自定义的base64编码。
0x02 调试分析
2.1 发送流程
软件使用了OpenSSL加密库,通过查看那些地方使用了加解密的函数,即可快速定位到发送密文和接受处理的地方,然后就是调试下整个流程。
首先把获取到的mac,序列号及其本地时间组成一个json,加密后添加到serialNumberKey再发送出去。
来看下加密使用的算法,设置对应的秘钥QN01R3M7WPKKORBK
调用OpenSSL里面的库函数进行加密,加密算法为aes_128_ecb,注意IV为16个字节的0,pad方式为PKCS7
将加密后的二进制数据进行编码,编码方式为base64的变形
所谓变形就是使用的编码字符串不同,但是具体算法还是一样的,第一行是软件使用的字符串,第二行是标准base64采用的字符串序列,这就解释了编码后的字符串出现了'@ ! -'的原因。
pri_base64_str="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@!-"
base64_str="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
2.2接收流程
通过分析代码可以知道,返回内容应该是个json,code的值应该是1,才能进行下一步处理
从encodeStr提取出字符串进行解密
先把字符串进行base64解码转为二进制数据,在进行解密
解密的秘钥是发送的时候的time字段的值,解密算法和发送的加密算法是相同的
解密之后还是一个json,接下来就是json解析,connectNum是最大的连接数,他转化成数字之后对一个全局变量进行赋值,如果想破解的话可以改掉这个全局变量的初值(把5改成其他的)
endTime是到期时间,当为forever的时候是用不过期
最后应该构造一个类似{"connectNum": "50", "endTime": "forever"}的json获取对应的时间进行加密------>>>>>{"code":1,"encodeStr":"Eq/GisQkh7zt8iRo7EgFGKiYEmQh1tAPxien5zjDKPjNcUOFuqrU1YnRzzFU8lXC"}
0x03 编写加解密程序
通过分析加解密算法即可编写出对应的程序
import base64
import binascii
import sys
import crypto
import json
import requests
sys.modules['Crypto']=crypto
fromcrypto.Cipher import AES
class validserno_payload(object):
def __init__(self,mac,serialNo,time):
self.mac= mac
self.serialNo = serialNo
self.time=time
class validrep_payload(object):
def __init__(self,connectNum,endTime):
self.connectNum= connectNum
self.endTime = endTime
class lb_crypto():
def __init__(self,key,iv):
self.key=key
self.iv=iv
self.blk_size=16
def lb_encrypt(self,str,pri):
in_len=len(str)
add=self.blk_size-(in_len%self.blk_size)
str=str+add*chr(add)#处理pad
cryptor = AES.new(self.key, AES.MODE_ECB,self.iv)
enc_text = cryptor.encrypt(str)
print binascii.hexlify(enc_text)
base64_str=base64.b64encode(enc_text)
if not pri:
return base64_str
encode_str=base64_str.replace('+','@').replace('/','!').replace('=','-')
return encode_str
def lb_decrypt(self,encode_str):
base64_str=encode_str.replace('@','+').replace('!','/').replace('-','=')
decode_str=base64.b64decode(base64_str)
cryptor = AES.new(self.key, AES.MODE_ECB,self.iv)
plain_text = cryptor.decrypt(decode_str)
str_len=len(plain_text)
pad=ord(plain_text[-1])
return plain_text
payload=validrep_payload("50","forever")
lc=lb_crypto('2017050512453200','\0'*16)#时间需要动态获取
print json.dumps(payload.__dict__)
rep_data="encodeStr="+lc.lb_encrypt(json.dumps(payload.__dict__),False)
print rep_data
0x04 验证结果
为了验证分析结果,可修改http返回内容,看是否达到预期目的。
noname.txt内容如下
注意解密时候是根据时间来动态解密的,要想解密正确应该在解密的时候patch时间值秘钥,验证结果如下:
材鸟 发表于 2017-5-13 14:11
看了下,验证流程大概是
POSTencode{postTime , 一堆机器信息(供服务端验证是否授权其使用)}
其实文中已经说了,最大连接次数是保存到一个全局变量里面,初值为5,把5改成其他的可成功秒破。本文的重点不是破解,而是算法分析 看了下,验证流程大概是
POSTencode{postTime , 一堆机器信息(供服务端验证是否授权其使用)}
如果不授权则返回
{CODE:-1,错误信息}
授权则返回
{CODE:1,"connectNum":“次数”,"endTime":“到期时间”}
那么不推算法,应该可以 bp recv 下断
当包返回后,在步过解密CALL的时候
对返回的{CODE:-1,?????}明文 替换成{CODE:1,"connectNum":“次数”,"endTime":“到期时间”} 。
当然,十分取巧。除非购买过此程序后进行对正确返回值抓包,否则不知道正确的返回值就是{CODE:1,"connectNum":“次数”,"endTime":“到期时间”}
不过如果不知道的话,可以继续跟,在判断CODE字段值时改跳转,那么可能会出错,因为返回的值没有"connectNum""endTime" 字段,那么再分析错误,可以推论出返回值具有"connectNum""endTime" 字段
纸上谈兵,求轻喷!{:1_907:}
66666666666要的就是这个软件 牛人·~~~~~ 软件~~再那里~~~ 太强悍了,正需要这样的软件 破解后的软件发出来啊 这是高手啊!!膜拜!! 就算不懂也要顶一下{:301_993:} 厉害了我的哥, 感谢分享 软件在 哪里呢?