0x01 环境
python3.6、pycharm、安卓反编译工具jadx、样本apk包、charles抓包工具、电脑安装adb(安卓调试桥)
python使用到的库:frida
一部root的安卓6.0手机:手机上安装frida-server
手机使用usb线连接电脑,且打开usb调试。使用adb的时候,选择授权该电脑。
0x02 抓包分析
配置代理
保证手机和电脑处于同一网络环境下,然后手机上设置电脑IP作为其代理IP。
查询电脑IP的方法:WIN+R打开运行->输入cmd
->回车打开命令提示符->输入ipconfig
,显示的内容中即有电脑IP信息。如:
… …
无线局域网适配器 WLAN:连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::1ea:ac16:a5f4:70dc%23
IPv4 地址 . . . . . . . . . . . . : 172.16.156.26
子网掩码 . . . . . . . . . . . . : 255.255.252.0
默认网关. . . . . . . . . . . . . : 172.16.156.1
则电脑IP为172.16.156.26。
手机上打开当前连接的WIFI(不同手机操作方法略有不同),修改网络->高级选项->代理->手动,代理服务器主机名填上面的172.16.156.26
,端口为charles的默认端口8888
。
抓包
打开charles和手机,在样本apk中搜索。
拿到目标链接:
url:
https://api.xxxx.com/community/search/items/get?protocol=1&appKey=47cda90997074f10&start=0&versionCode=211&token=eed96c88-c110-4faa-bd9b-7fa6d6f0264a&key=%25E5%25A4%25A7%25E7%258E%258B×tamp=1606190200165&count=20&channel=1002&type=3&sign=wUAXTQxOFolZP4FQuSXtZvtnn.E=
请求头:
Accept: application/json
Content-type: application/json
Host: api.xxxx.com
Connection: Keep-Alive
Cookie: acw_tc=ac11000116061900179143931e01610f6f99e840c7cae1c380c9a0a984caa7
Cookie2: $Version=1
返回内容:
{
"rc": 0,
"msgInfo": null,
"errorInfo": null,
"redirect": null,
"redirectSign": null,
"ccdVersion": null,
"serverTime": null,
"serAppInfo": null,
"logTrackInfo": null,
"adList": null,
"resultUserList": [{
"logTrackInfo": "{\"itemId\":18000119,\"itemType\":\"uid\"}",
"algorithmId": null,
"uid": 18000119,
"displayName": "大王",
... ...
}
可以发现url中别的参数都是固定或可知的,只有sign参数未知,且不知构造方法。所以下面需要分析apk包,找到生成sign的方法。
0x03 apk反编译
打开反编译工具jadx,将样本apk包拖进去,ctrl+shift+f
打开搜索,直接搜索sign=
,这里可以看到有两条符合的结果:
而第二条左边有一个可疑的方法名叫getRequestParam
,直接双击进入代码:
可以看到红框的代码是遍历参数,拼接字符串,再拼接一个config.getAppSecret()
方法生成的字符串,最后再传入Sha1Util.calculateRFC2104HMAC
中生成sign。
进入Sha1Util.calculateRFC2104HMAC
可以看到,加密的过程是将传入的字符串和密钥转为bytes类型,使用HMAC_SHA1加密,然后再使用base64加密转为字符串。
这里知道了加密的过程,剩下就需要知道传入的参数:拼接出来的字符串str
和密钥str2
,分别是什么。
方法有三种:
- 静态分析JAVA源码
- 动态调试该apk
- 使用hook的方法拿到传入的参数样式
这里选择使用hook的方法。
0x04 hook
目前主流的hook方法有两种:xposed、frida,这里选择frida。
frida的环境安装可网上查找教程。
frida-server下载地址:https://github.com/frida/frida/releases
运行frida-server
打开cmd->运行adb shell
,进入手机命令行,执行:
su
cd data/local/tmp
./frida-server
若没报错则代表执行成功。
再开一个cmd,执行端口转发:
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043
python运行frida
代码:
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import frida, sys
def message(message,data):
if message["type"] == 'send':
print("[*] {0}".format(message["payload"]))
else:
print(message)
def main():
# com.xxx.xxx.util.Sha1Util是calculateRFC2104HMAC所在的类名
jscode = """
Java.perform(function(){
var hook_cls = Java.use('com.xxx.xxx.util.Sha1Util')
hook_cls.calculateRFC2104HMAC.implementation = function(a,b){
console.log("Hook Start...");
send(arguments);
var ret = this.calculateRFC2104HMAC(a,b);
send(ret);
return ret;
}
}
);
"""
# 这里的com.xxx.xxx是apk的包名
process = frida.get_remote_device().attach('com.xxx.xxx')
script = process.create_script(jscode)
script.on("message", message)
script.load()
sys.stdin.read()
if __name__ == '__main__':
main()
手机打开样本apk后,运行上面代码,注意一定是先在手机打开app后再运行代码,顺序不能错了,否则可能hook失败。
查找apk包名的方法有很多,这里介绍其中一种,手机上运行该app,打开cmd执行:
adb shell dumpsys window | findstr mCurrentFocus
显示:
mCurrentFocus=Window{8d82372 u0 com.xxx.xxx/com.xxx.xxx.MainTabActivity}
其中com.xxx.xxx
即为包名。
开始hook
pycharm中输出的结果:
Hook Start...
[*] {'0': 'demokey大王011002160620003087520211347cda90997074f10eed96c88-c110-4faa-bd9b-7fa6d6f0264ademokey', '1': 'demokey'}
[*] QJHyIV3bywcxuTugNLGz1EfWq0o=
抓包的url:
https://api.xxxx.com/community/search/items/get?protocol=1&appKey=47cda90997074f10&start=0&versionCode=211&token=eed96c88-c110-4faa-bd9b-7fa6d6f0264a&key=%25E5%25A4%25A7%25E7%258E%258B×tamp=1606200030875&count=20&channel=1002&type=3&sign=QJHyIV3bywcxuTugNLGz1EfWq0o=
对比抓包得到的sign和hook得到的sign,可以看到hook成功,而入参str
为:
demokey大王011002160620003087520211347cda90997074f10eed96c88-c110-4faa-bd9b-7fa6d6f0264ademokey
密钥str2
为:
demokey
所以我们看到的sgin就是对demokey大王011002160620003087520211347cda90997074f10eed96c88-c110-4faa-bd9b-7fa6d6f0264ademokey
进行HMAC_SHA1加密,然后再使用base64加密后,就得到了QJHyIV3bywcxuTugNLGz1EfWq0o=
。
python实现:
import base64
import hmac
from hashlib import sha1
key = 'demokey'
data = 'demokey大王011002160620003087520211347cda90997074f10eed96c88-c110-4faa-bd9b-7fa6d6f0264ademokey'
hmac_code = hmac.new(key.encode(), data.encode(), sha1).digest()
print(base64.b64encode(hmac_code, altchars=b'._').decode())
而入参str
就是由url中的各个参数排序后加上密钥demokey
拼接而来:
密钥 + 各参数排序的结果拼接 + 密钥
0x05 请求demo
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import base64
import hmac
import re
from hashlib import sha1
import urllib.request
from urllib.parse import urlparse, parse_qs, unquote, quote
def format_headers(data):
param = {}
pattern = re.compile(r'([A-Za-z\d-]*):(.*?)$', re.M)
items = re.findall(pattern, data)
for key, value in items:
if key.strip() in param:
param[key.strip()] = '{}; {}'.format(param[key.strip()], value.strip())
else:
param[key.strip()] = value.strip()
return param
keyword = '纸尿裤'
start = '20'
timestamp = '1606202497355'
# 如果是第一页,type设置为3
b_type = '3' if start == '0' else '2'
keyword = quote(quote(keyword))
base_url = 'https://www.xxxx.com/community/search/items/get?protocol=1&appKey=47cda90997074f10&start={0}&versionCode=211&token=eed96c88-c110-4faa-bd9b-7fa6d6f0264a&key={1}×tamp={2}&count=20&channel=1002&type={3}'.format(
start, keyword, timestamp, b_type
)
app_key = 'demokey'
query = urlparse(base_url).query
params = parse_qs(query)
param_value = [v[0] for v in params.values()]
param_value.sort()
data = app_key + ''.join([unquote(item) for item in param_value]) + app_key
hmac_code = hmac.new(app_key.encode(), data.encode(), sha1).digest()
sign = base64.b64encode(hmac_code, altchars=b'._').decode()
url = base_url + '&sign={}'.format(sign)
headers = '''
Accept: application/json
Content-type: application/json
Host: api.xxxx.com
Cookie: acw_tc=ac11000116061993369595582e0161fd5568e4a923920244fa6d166c20dfb6
Cookie2: $Version=1
'''
headers = format_headers(headers)
# 发现该app,python3直接使用requests会拒绝访问,所以改用urllib
req = urllib.request.Request(url, headers=headers)
html = urllib.request.urlopen(req).read()
print(bytes.decode(html, encoding='utf-8'))