网页抓取解密(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加密和解密方法

  郑重声明:本文章仅供学习和交流之用,禁止用于任何商业目的

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线