网页抓取解密(Androidkillerdata,参数怎么产生的?(图))
优采云 发布时间: 2021-09-16 08:02网页抓取解密(Androidkillerdata,参数怎么产生的?(图))
本文以小提琴手为例。配置完成后,可以在fiddler界面中看到页面请求的HTTPS连接。以cos每周清单为例:
如图所示,我们可以看到这个post请求的URL、查询参数、请求体和返回的JSON数据。完整的URL如下所示:
*2160及;dpi=480&;更新版本代码=412&;\rticket=94
这里,请求主体的密钥为数据,加密内容为:
Lc59D72k2R4YFB0XMOPQRgpNKWGco6f1e86WkOur0ZArCiT+R6VLSVHQYEUTFTVXRYPX4TE3WZV5VF043AL8XSKW592ULRAZRH6OECMW9FBDZBRB+l9QIGFengtJ
由于这是一个post请求,如果直接单击此处打开,将出现默认提示,并且不会返回任何数据。因此,我们可以通过postman等工具测试请求,如下所示:
所以问题来了。数据是加密的。我们需要的是动态生成数据,根据参数自由获取数据。我们该怎么办
长征的第二步:反编译
为了查看请求主体中的数据参数是如何生成的,我们需要反编译以分析源代码
首先,从官方网站下载半维APK的安装包。这里的地址是。在这里,您可以直接使用快速方便的Android killer来完成反编译逻辑。反编译成功后,如下所示:
很容易找到类维度的源代码路径。完整的内容是com.banciyuan.bcywebview。因为我们可以直接看到SmalI汇编源代码,所以这里没有太多意义。这里我们通过Java查看器查看Java源代码。考虑到这是一个网络请求,我们重点查找与http相关的内容并进行搜索,我注意到其中一个httputils文件,如下所示:
如图中的红色框所示,可以看到数据在这里被加密并传输到服务器,因此我们调用encrypt来查看
public static String a(String paramString)
{
return a(paramString, 0);
}
在encrypt中有这样一种方法。让我们继续看电话
public static String a(String paramString, int paramInt)
{
return a(b(paramString, paramInt));
}
好吧,继续看方法B
private static byte[] b(String paramString, int paramInt)
{
Object localObject = getRandomString(paramInt);
if (localObject != null)
{
if (paramInt == 0) {}
try
{
if (((String)localObject).length() != 16) {
return null;
}
localObject = new SecretKeySpec(((String)localObject).getBytes(Charset.defaultCharset()), "AES");
Cipher localCipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC");
localCipher.init(1, (Key)localObject);
paramString = localCipher.doFinal(paramString.getBytes("utf-8"));
return paramString;
}
catch (Exception paramString) {}
}
return null;
}
哦,是的,这里应该很清楚,数据是由AES通过密码加密的。关键是什么
长征的第三步:模拟本地呼叫
虽然我们看到了加密逻辑,但我们可以从代码中看到密钥是通过getrandomstring(paramint)方法获得的,这是一种本机方法,在代码中定义
private static native String getRandomString(int paramInt);
好的,因为它是本机方法,所以我们可以在项目中调用并打印键的实际值。因此,我发现本机方法来自library.so
static
{
System.loadLibrary("random");
}
将so文件引入到我自己的项目中,并编写类似的调用代码,如下所示:
public class Encrypt {
static
{
System.loadLibrary("random");
}
public void Test(){
Log.e("Encrypt",getRandomString(0));
}
private static native String getRandomString(int paramInt);
}
外呼:
new Encrypt().Test();
嗯,执行代码的结果没有按预期打印出来。相反,它会提示找不到本机方法getrandomstring。我很困惑。经过研究,发现本机方法的命名格式如下:
Java_uuCOM_uuuCiyuan_uuBcyWebView_Uils_UEncrypt_UEncrypt_U1;getRandomString
如果在任何项目中使用getrandomstring,将找不到它
好的,解决方案是用相同的包名和半维重新创建一个项目。完整的包名是com.banciyuan.bcywebview。您可以通过再次调用它来成功打印它。结果如下:
注:为了防止商业使用,决定隐藏一些关键内容
哈哈,我这里有半维的加密密钥。大多数情况下,我们将键放在常量中,并将其直接写入代码中。对于这一点,半维也是善意的
长征的第四步:数据解密
同样清楚的是,这里写的是半维元素的加密过程和密钥结果。理论上,通过这一关键结果,我们可以伪造任意post请求的数据。真的可以吗
如下所示,我在Android下编写了一段解密逻辑:
private void decryptData() {
Object localObject = "com_banciyuan_AI";
String paramString = "Lc59D72k2R4YFB0XMOPQRgpNKWGco6f1e86WkOur0ZArCiT+R6VlSvHQYEUtFtTVXrYpx4tE3WZV5vf043AL8XwSxskW592ULRAzrh6oEcMW9FBDzBrB+l9QIGFengtJ";
if (((String) localObject).length() != 16) {
return;
}
localObject = new SecretKeySpec(((String) localObject).getBytes(Charset.defaultCharset()), "AES");
Cipher localCipher = null;
try {
localCipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC");
localCipher.init(DECRYPT_MODE, (Key) localObject);
paramString = new String(localCipher.doFinal(Base64.decode(paramString.getBytes("utf-8"), Base64.DEFAULT)), "UTF-8");
Log.e("Main", paramString);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
那么字符串打印出来的究竟是什么呢
{“日期”:“20180719”,“网格类型”:“时间线”,“标记”:“4b98b48cb5b20e72”,“p”:“1”,“类型”:“周”}
呵呵,这些是post的核心数据。同样,您可以通过在线加密和解密网站查看,解密后如下所示:
结果是一样的。最后,我们可能需要伪造数据来请求API实现一些隐藏的秘密==
长征的第五步:尝试在体内伪造数据
如果您在这里使用Java,它应该相对简单。只需反向解密。具体的方法是将uChange mode解密为encrypt uMode,然后在外层使用Base64,内层仍然使用AES进行加密。具体来说,它可以由您自己进行测试。在这里,我将重点介绍通过python的实现
我们使用crytodome实现AES加密和解密。首先,我们导入以下模块:
import base64
from Cryptodome.Cipher import AES
# str不是16的倍数那就补足为16的倍数
def add_to_16(text):
while len(text) % 16 != 0:
text += '5'
return str.encode(text) # 返回bytes
def generate_encrypt_data():
key = 'com_banxxxx' #隐藏部分密钥内容
dict_data = {"date": "20180719", "grid_type": "timeline", "token": "4b98b48cb5b20e72", "p": "1", "type": "week"}
data_json = json.dumps(dict_data).replace(' ', '') # 移除多余的空格
print(data_json)
cipher = AES.new(add_to_16(key), AES.MODE_ECB)
encrypted_text = str(base64.encodebytes(cipher.encrypt(add_to_16(data_json))), encoding='utf8') # 加密
print(encrypted_text)
generate_encrypt_data()
比较Fiddler捕获的数据,如下所示:
Lc59D72k2R4YFB0XMOPQRgpNKWGco6f1e86WkOur0ZArCiT+R6VLSVHQYEUTFTVXRYPX4TE3WZV5VF043AL8XSKW592ULRAZRH6OECMW9FBDZBRB+l9QIGFengtJ
啊,什么鬼?最后几个不同的鬼魂是什么?你不应该去网页解密生成的文件
很好,没问题。编码后会有什么不同
可能的远程原因:
dict生成的原创数据与实际原创数据不同。为了确保它是16位的倍数,使用***'\0'**作为填充
首先,让我们看看DICT转换的数据和Android汇编所生成的数据两次:
{“日期”:“20180719”,“网格类型”:“时间线”,“标记”:“4b98b48cb5b20e72”,“p”:“1”,“类型”:“周”}
{“日期”:“20180719”,“网格类型”:“时间线”,“标记”:“4b98b48cb5b20e72”,“p”:“1”,“类型”:“周”}
这让我说不出话来。绝对正确。让我们看看填充问题。首先,我检查了反编译的源代码,没有找到明确的填充逻辑,因此我考虑通过所有ASIL代码生成相应的加密数据,并将其与正确的数据进行比较
测试后,所有ASCII码都不会生成一致的加密数据(放弃)
查看代码后,发现生成cilper的逻辑如下
Cipher.getInstance("AES/ECB/PKCS7Padding", "BC")
此处使用Pkcs7padding,但在py中没有指定此设置的位置。如何保持一致性?关于PKCS7填充,请参阅PKCS5填充和PKCS7填充之间的区别
在这里我突然有了新的灵感。如果填充不一致,两个原创数据也应不一致,但肉眼看起来完全相同。即使通过文本比较,也是一样的。那么如何检查呢
将在两个位置生成的原创数据转换为列表,然后打印以进行字符级比较。新的代码如下
def generate_encrypt_data():
key = 'com_banciyuan_AI'
dict_data = {"date": "20180719", "grid_type": "timeline", "token": "4b98b48cb5b20e72", "p": "1", "type": "week"}
data_json = json.dumps(dict_data).replace(' ', '') # 移除多余的空格
print(len(data_json))
print(list(data_json))
cipher = AES.new(add_to_16(key), AES.MODE_ECB)
encrypted_text = str(base64.encodebytes(cipher.encrypt(add_to_16(data_json))), encoding='utf8') # 加密
# print(encrypted_text)
encrypted_text = "Lc59D72k2R4YFB0XMOPQRgpNKWGco6f1e86WkOur0ZArCiT+R6VlSvHQYEUtFtTVXrYpx4tE3WZV5vf043AL8XwSxskW592ULRAzrh6oEcMW9FBDzBrB+l9QIGFengtJ" #正确的加密数据
text_decrypted = str(cipher.decrypt(base64.decodebytes(bytes(encrypted_text, encoding='utf8'))).rstrip(b'\0').decode("utf8")) # 解密
print(len(text_decrypted))
print(list(text_decrypted))
结果:
91(dict生成)
“","t,","i","m","e","e","e","e","e","t"o","k"e","n",","p","p","p","p","p","e","t","y","p"","e 34
96(由Java解密)
“,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,”","t","i","m","e","l","i","m","e","e","e","t"o,"k",","p",","p","p","p","e","k","1","","t"y","p","e",""
通过比较,发现第一个长度不一致,还有五个**'\X05',吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼,吼
def generate_encrypt_data():
key = 'com_banciyuan_AI'
dict_data = {"date": "20180719", "grid_type": "timeline", "token": "4b98b48cb5b20e72", "p": "1", "type": "week"}
data_json = json.dumps(dict_data).replace(' ', '') # 移除多余的空格
print(data_json+"\n")
cipher = AES.new(add_to_16(key), AES.MODE_ECB)
encrypted_text = str(base64.encodebytes(cipher.encrypt(add_to_16(data_json))), encoding='utf8') # 加密
print(encrypted_text)
text_decrypted = str(cipher.decrypt(base64.decodebytes(bytes(encrypted_text, encoding='utf8'))).rstrip(b'\x05').decode("utf8")) # 解密
print(text_decrypted)
执行代码:
那么,这篇文章就到此结束了。其实这是一种非常主流的移动加密传输方式。对于HTTPS+AES+本机密钥存储,您可以看到在这方面,半维元素仍然非常充足。如果您有任何疑问,请在评论中指出
参考博客链接:
Android本机方法无法找到问题
在线加密和解密
python3.6执行AES加密和解密方法
郑重声明:本文章仅供学习和交流之用,禁止用于任何商业目的