啃得起 APP 数据传输 内部加密签名signatureCode方法分析
【注意:此文章为博主原创文章!转载需注意,请带原文链接,至少也要是txt格式!】
因对方APP已经更换加密方式、证书,所以本文方法已失效,所以公布过程大家学习。
问题的重点在某APP进行了数据包签名加密的验证,然后就是没办法更改数据包,没办法重放数据包。那针对如何加密的过程分析如下:
首先针对此款APP进行数据抓包,当然了,正常你是没办法抓包的,所以想看抓包过程请参考:Frida Burp抓包APP ,下面是抓取的数据包,请仔细看图,如图分析:
既然这样,那么就收集到了几个关键字
kbappkwle8K1Mhlc、kbcts、kbck、kbsv
既然这样已经收集这4个关键字,我们反编译APP,然后解包其中最重要的文件:classes.dex、classes2.dex、classes3.dex 解包之后我们搜索这几个关键词。
通过这里呢,发现了APP的核心配置文件,里面有很多关键的东西呢。
路径是:com.hp.smartmobile.config.ServiceConfig
我们再次搜索其它关键词,就发现了重要的传输过程中的加密方法及函数。如下图,请仔细看图:
所有的加密函数,传输方法都在com.smartmobilevpay.android.http.HttpTools 这个路径下的文件中,全局的传输方法是doHttpGetPost、doHttpGetPost,而在这个传输方法中,使用了加密验证的方式,下面我们拿出重点的加密方法函数如下,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | //这里是Get方法的加密函数 public static Map<String, String> getSignGetMap(final String s, JSONObject jsonObject) { final HashMap<String, String> hashMap = new HashMap<String, String>(); JSONObject jsonObject2; final Object o = jsonObject2 = (JSONObject)""; if (jsonObject != null) { TreeMap<String, String> treeMap; try { final Iterator keys = jsonObject.keys(); treeMap = new TreeMap<String, String>(); while (keys.hasNext()) { final String s2 = keys.next(); treeMap.put(s2, jsonObject.getString(s2)); } } catch (Exception ex) { ex.printStackTrace(); return hashMap; } final Iterator iterator = treeMap.keySet().iterator(); jsonObject = (JSONObject)o; while (true) { jsonObject2 = jsonObject; if (!iterator.hasNext()) { break; } final String s3 = iterator.next(); try { final String s4 = treeMap.get(s3); if (((String)jsonObject).equals("")) { jsonObject = (JSONObject)(s3 + "=" + s4); } else { jsonObject = (JSONObject)((String)jsonObject + "&" + s3 + "=" + s4); } } catch (Exception ex2) { ex2.printStackTrace(); } } } final long time = new Date().getTime(); hashMap.put("kbck", SmartSdkManager.getInstance().getClientKey()); hashMap.put("kbcts", time + ""); hashMap.put("kbsv", EncryptUtils.stringToMD5(EncryptUtils.signatureGetCode(time, s, (String)jsonObject2))); return hashMap; } private void removeRequest(final long n) { synchronized (this) { if (this.mRequestIds == null) { this.mRequestIds = new HashSet(); } this.mRequestIds.remove(n); } } //这里是POST方式签名加密 public Map<String, String> getSignPostMap(final String s, final JSONObject jsonObject) { final HashMap<String, String> hashMap = new HashMap<String, String>(); try { final String string = jsonObject.toString(); final long time = new Date().getTime(); hashMap.put("kbck", SmartSdkManager.getInstance().getClientKey()); hashMap.put("kbcts", time + ""); hashMap.put("kbsv", EncryptUtils.stringToMD5(EncryptUtils.signaturePostCode(time, s, string))); return hashMap; } catch (Exception ex) { ex.printStackTrace(); return hashMap; } } |
到这里我们就明白了很多,首先kbck的值仅仅是一个配置文件的字符串,kbcts是当前时间的时间戳,重点的就是kbsv。
kbsv这里首先是执行 EncryptUtils.signaturePostCode(l, paramString, str) l是时间戳,其它两个还需要继续看。先看一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | //这里是Get方法的kbsv加密 public static String signatureGetCode(long paramLong, String paramString1, String paramString2) { paramString1 = SmartSdkManager.getInstance().getClientKey() + '\t' + SmartSdkManager.getInstance().getClientSec() + '\t' + paramLong + '\t' + paramString1 + '\t' + paramString2; if (SmartSdkManager.getInstance().isOpenLog()) Log.i("applog", "------forMD5," + paramString1); return paramString1; } //这里是Post方法的kbsv加密 注意上面的函数调用的就是这里signaturePostCode public static String signaturePostCode(long paramLong, String paramString1, String paramString2) { paramString1 = SmartSdkManager.getInstance().getClientKey() + '\t' + SmartSdkManager.getInstance().getClientSec() + '\t' + paramLong + '\t' + paramString1 + '\t' + '\t' + paramString2; if (SmartSdkManager.getInstance().isOpenLog()) Log.i("applog", "------forMD5," + paramString1); return paramString1; } //这里是最外层的MD5加密EncryptUtils.stringToMD5(EncryptUtils.signaturePostCode(l, paramString, str))) public static String stringToMD5(String paramString) { try { MessageDigest messageDigest = MessageDigest.getInstance("MD5"); try { byte[] arrayOfByte = messageDigest.digest(paramString.getBytes("UTF-8")); StringBuffer stringBuffer = new StringBuffer(); for (int i = 0; i < arrayOfByte.length; i++) { int j = arrayOfByte[i] & 0xFF; if (j < 16) stringBuffer.append("0"); stringBuffer.append(Integer.toHexString(j)); } if (SmartSdkManager.getInstance().isOpenLog()) Log.i("applog", "------MD5iS," + stringBuffer.toString()); return stringBuffer.toString(); } catch (Exception exception) { exception.printStackTrace(); return ""; } } catch (Exception exception) { exception.printStackTrace(); return ""; } } } |
其实走到这里就可以看出来了,最重要的就是:SmartSdkManager.getInstance().getClientKey() + '\t' + SmartSdkManager.getInstance().getClientSec() + '\t' + paramLong + '\t' + paramString1 + '\t' + paramString2;
通过刚刚的核心配置文件我们知道了
SmartSdkManager.getInstance().getClientKey() 值是 kbappkwle8K1Mhlc
SmartSdkManager.getInstance().getClientSec() 值是 WYjEbpFholuphDuO
paramLong 值是 当前时间戳
那么把上面的解密就是:
kbappkwle8K1Mhlc + '\t' + WYjEbpFholuphDuO + '\t' + 当前时间戳 + '\t' + paramString1 + '\t' + paramString2;
下一步,只要我们弄清楚paramString1、paramString2就完全弄清楚这个传输方式过程中kbsv的加密方式。我们就可以通过修改数据包然后来重新加密,重新发包。
既然想弄清paramString1、paramString2那么我们就要梳理整个加密解密逻辑关系,什么地方传入这两个参数。
我们先看下图,找到他们俩的来源
其实到这里基本就可以明白了。下图是更详细的 paramString1、paramString2参数来源
还有更多详细的分析需要梳理,这里就不梳理的那么仔细了。
paramString1 = 提交路径
例如 http://seo.baidu.com/Web/Page/Mainframe/Mainframe.aspx
paramString1 就是:/Web/Page/Mainframe/Mainframe.aspx 具体可能有所不同,我看到有的API接口是取部分值,有可能还是/Mainframe/Mainframe.aspx
paramString2 = 提交参数
例如 某GET请求是 https://woj.app/post.php?post=6731&action=edit
paramString2 就是 post=6731&action=edit
例如 某POST请求 https://seo.baidu.com/validOvertimeVoucher
DATA: {"customerId":null,"userUniqueId":"d020434d-0c0f-4357-b2b8-e834a4390009"}
paramString2 就是 {"customerId":null,"userUniqueId":"d020434d-0c0f-4357-b2b8-e834a4390009"}
那所有的全部都梳理出来拉,整体的就是:
1 2 3 4 5 6 | def do_POST_signatureCode(n, s, s2): return 'kbappkwle8K1Mhlc' + '\t' + 'WYjEbpFholuphDuO' + '\t' + str(时间戳) + '\t' + paramString1 + '\t' + '\t' + paramString2 def do_GET_signatureCode(n, s, s2): return 'kbappkwle8K1Mhlc' + '\t' + 'WYjEbpFholuphDuO' + '\t' + str(时间戳) + '\t' + paramString1 + '\t' + paramString2 |
就这么简单哦。
最后赠送一个神秘接口https://authlogin.baidu.com.cn/api/user/token?token=10232d845aa0fd70_1123123123a_1749ae5894a_w123123w 就可以查询这个用户的信息。
我试了一下,md5结果不对.能不能有偿帮我破解KFC APP的API?
KFC 每次加密都要对应路径的。 而且不同的路径还要用不同的加密key 较为繁琐。
刚刚小打赏,望回复一下
什么情况???
有问题想咨询下,想私聊一下
哥们希望帮忙一下有偿
你这边需要做什么事情呢? 可以直接给我发邮件聊。
我这边只做技术研究、分享,不做软件破解、破坏,不做黑灰产。
这边不知道您的邮箱0.0
admin@woj.app
哥们收到回一下下,谢谢
你需要把admin@woj.app 加入白名单,不然我回你也收不到,还有 请问有什么事情吗?
其实我有一个接口卡壳了,我朋友用易语言能访问通,但是我java链路和他一样参数也是一样,我这边就是返回verify failed,我只是换了个UA就不行了
你这个问题很简单,只需要抓包,对比一下你朋友易语言发出的https请求数据包与你java请求的数据包有什么区别即可,因为语言不同,https请求包也可能有不同。 (之前朋友用go、python同一个功能,一个能实现,一个不能实现)
就是拿抓包工具对比工具对比了还是有问题
对比了协议头和发送的东西,一样的还是有问题
所以想您帮我这边分析一下
老哥
呜呜呜,老哥help