一 hook框架frida

1
2
3
4
5
6
Hook 框架是一种技术,用于在运行时拦截和修改应用程序的行为。
通过 Hook,你可以劫持应用程序的方法调用、修改参数、篡改返回值等,以达到对应用程序的修改、增强或调试的目的
# 常见的有:
Xposed Framework:Xposed 是一个功能强大的开源 Hook 框架,可以在不修改应用程序源代码的情况下,对应用程序进行各种修改。它允许你编写模块来拦截和修改应用程序的方法调用,修改应用程序的行为和逻辑。

Frida:Frida 是一个跨平台的动态 Hook 框架,支持安卓和其他操作系统。它提供了一个强大的 JavaScript API,可以在运行时对应用程序进行 Hook,包括方法拦截、参数修改、调用注入等。Frida 可以用于安全研究、逆向工程和应用程序调试等方面。

1.1 下载安装

1
# 注意:需要电脑端[电脑端要安装python解释器环境]和手机端同时安装,版本必须对应

1.1.1 电脑端安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 指定版本安装
pip install frida==16.0.1
pip install frida-tools==12.0.1

# 安装最新版
pip install frida # 16.0.9
pip install frida-tools # 12.1.2


# 正常都可以顺利安装,若安装出现错误,下载源码包安装
#### 必须根据 :frida版本 + Python版本 + 操作系统 来选择下载响应的egg或whl文件。
https://pypi.doubanio.com/simple/frida/

pip install frida-16.0.1-cp37-abi3-macosx_10_9_x86_64.whl

image-20240517152708366

1.1.2 手机端安装frida-server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1 先查看手机架构
adb shell getprop ro.product.cpu.abi
# arm64-v8a

# 2 下载frida-server
https://github.com/frida/frida/releases

# 3 解压,上传到手机 /data/local/tmp/ 目录下
-解压得到文件,把文件上传到手机
adb push ./今日软件/frida-server/frida-server-16.0.19-android-arm64 /data/local/tmp/

# 4 赋予可执行权限
adb shell # 进入手机命令行
su # 切换为超级用户
cd /data/local/tmp/
chmod 755 frida-server-16.0.19-android-arm64 # 加入执行权限
ls -al # 查看权限

# 5

image-20240517152730913

1.2 启动并hook应用

1.2.1 手机端启动frida服务端

1
2
3
4
5
# 切换到手机的/data/local/tmp目录下
adb shell
su
cd /data/local/tmp
./frida-server-16.0.19-android-arm64

报错解决

1
2
方案一:重启手机
方案二:运行adb shell setenforce 0

image-20240517152754432

1.2.2 电脑端配置

1.2.2.1 配置端口转发

1
2
3
4
5
6
7
8
9
# 方式一:命令行中敲
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043

# 方式二:使用python执行
import subprocess

subprocess.getoutput("adb forward tcp:27042 tcp:27042")
subprocess.getoutput("adb forward tcp:27043 tcp:27043")

1.2.2.2 编写python代码,打印手机中的进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 枚举手机上的所有进程 & 前台进程
import frida

# 获取设备信息
rdev = frida.get_remote_device()

# 枚举所有的进程
processes = rdev.enumerate_processes()
for process in processes:
print(process)

# 获取在前台运行的APP
front_app = rdev.get_frontmost_application()
print(front_app)

错误

1
2
3
# 没有配置端口转发
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043

image-20240517152806516

1.3 hook 某智赢的加密算法encodeMD5

1
# 下载地址:https://appdownload.che168.com/usedcar/csy/index.html?pvareaid=106101
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
import frida
import sys
# 连接手机设备
rdev = frida.get_remote_device()

# 包名:com.che168.autotradercloud
# 车智赢+
session = rdev.attach("车智赢+")
scr = """
Java.perform(function () {
//找到类 反编译的首行+类名:com.autohome.ahkit.utils下的
var SecurityUtil = Java.use("com.autohome.ahkit.utils.SecurityUtil");

//替换类中的方法
SecurityUtil.encodeMD5.implementation = function(str){
console.log("参数:",str);
var res = this.encodeMD5(str); //调用原来的函数
console.log("返回值:",res);
return str;
}
});
"""

script = session.create_script(scr)

def on_message(message, data):
print(message, data)

script.on("message", on_message)
script.load()
sys.stdin.read()

1.4 Python Hook方式

1
2
3
4
5
6
7
8
9
10
11
12
# Spawn 方式适应场景:Spawn 方式是在目标应用程序启动时直接注入 Frida 的 Agent 代码

需要在应用程序启动的早期阶段进行 Hook。
需要访问和修改应用程序的内部状态,例如应用程序的全局变量、静态变量等。
需要 Hook 应用程序的初始化过程,以实现对应用程序的自定义初始化逻辑。
需要在应用程序的上下文中执行代码,并与其他模块或库进行交互。

# Attach 方式适应场景:Attach 方式是在目标应用程序已经运行的过程中动态地连接并注入 Frida 的 Agent 代码
需要对已经运行的应用程序进行 Hook,即动态地连接到正在运行的进程。
需要在应用程序运行时拦截和修改特定的方法调用。
需要实时监视和修改应用程序的行为,例如参数修改、返回值篡改等。
需要对应用程序进行调试和分析,以查找潜在的问题和漏洞。

1.4.1 attach方式(手动操作)

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
import frida
import sys
# 连接手机设备
rdev = frida.get_remote_device()

# 包名:com.che168.autotradercloud
# 车智赢+
session = rdev.attach("车智赢+")
scr = """
Java.perform(function () {
//找到类 反编译的首行+类名:com.autohome.ahkit.utils下的
var SecurityUtil = Java.use("com.autohome.ahkit.utils.SecurityUtil");

//替换类中的方法
SecurityUtil.encodeMD5.implementation = function(str){
console.log("参数:",str);
var res = this.encodeMD5(str); //调用原来的函数
console.log("返回值:",res);
return str;
}
});
"""

script = session.create_script(scr)

def on_message(message, data):
print(message, data)

script.on("message", on_message)
script.load()
sys.stdin.read()

1.4.2 spawn方式(自动重启app,适用于在应用程序启动的早期阶段进行)

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
import frida
import sys

rdev = frida.get_remote_device()
pid = rdev.spawn(["com.che168.autotradercloud"])
session = rdev.attach(pid)

scr = """
Java.perform(function () {
// 包.类
var SecurityUtil = Java.use("com.autohome.ahkit.utils.SecurityUtil");
SecurityUtil.encodeMD5.implementation = function(str){
console.log("明文:",str);
var res = this.encodeMD5(str);
console.log("md5加密结果=",res);
return "305eb636-eb15-4e24-a29d-9fd60fbc91bf";
}
});
"""
script = session.create_script(scr)


def on_message(message, data):
print(message, data)


script.on("message", on_message)
script.load()
rdev.resume(pid)
sys.stdin.read()

1.5 js Hook方式javaScript+终端

1
2
3
4
5
6
7
8
9
10
11
# 代码 hook.js
Java.perform(function () {
// 包.类
var SecurityUtil = Java.use("com.autohome.ahkit.utils.SecurityUtil");
SecurityUtil.encodeMD5.implementation = function(str){
console.log("明文:",str);
var res = this.encodeMD5(str);
console.log("md5加密结果=",res);
return "305eb636-eb15-4e24-a29d-9fd60fbc91bf";
}
});

1.5.1 attach,先启动app,然后再在终端执行:

1
frida -UF -l hook.js  

1.5.2 spwan,脚本自动重启APP并进行Hook

1
2
3
frida -U -f com.che168.autotradercloud -l hook.js

# 注意:输入q + 再点击回车则退出

二 使用python还原算法

1
2
3
4
5
# 加密分类
1、单向加密 :MD5、sha系列不可逆
2、对称加密:AES、DES
3、非对称加密:RSA、DSA
4、补充算法:base64

2.1 md5

1
2
3
4
import hashlib
m = hashlib.md5()
m.update('helloworld'.encode("utf8"))
print(m.hexdigest())

2.2 sha

1
2
3
4
5
6
import hashlib
sha1 = hashlib.sha1()
data = 'helloword'
sha1.update(data.encode('utf-8'))
sha1_data = sha1.hexdigest()
print(sha1_data)

2.3 DES加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# pip3 install pycryptodomex -i https://pypi.douban.com/simple
# DES是一个分组加密算法,典型的DES以64位为分组对数据加密,加密和解密用的是同一个算法。它的密钥长度是56位(因为每个第8 位都用作奇偶校验),密钥可以是任意的56位的数,而且可以任意时候改变。

from Cryptodome.Cipher import DES
key = b'88888888'
data = "hello world"
count = 8 - (len(data) % 8)
plaintext = data + count * "="
des = DES.new(key, DES.MODE_ECB)
ciphertext = des.encrypt(plaintext.encode())
print(ciphertext)

plaintext = des.decrypt(ciphertext)
plaintext = plaintext[:(len(plaintext)-count)]
print(plaintext)

2.4 非对称加密算法-RSA

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
# 安装模块
pip3 install rsa -i https://pypi.douban.com/simple

import rsa
# 返回 公钥加密、私钥解密
public_key, private_key = rsa.newkeys(1024)
print(public_key)
print(private_key)

# plaintext = b"hello world"
# ciphertext = rsa.encrypt(plaintext, public_key)
# print('公钥加密后:',ciphertext)
# plaintext = rsa.decrypt(ciphertext, private_key)
# print('私钥解密:',plaintext)

### 使用私钥签名
plaintext = b"hello world"
sign_message = rsa.sign(plaintext, private_key, "MD5")
print('私钥签名后:',sign_message)

## 验证私钥签名
plaintext = b"hello world"
# method = rsa.verify(b"hello world", sign_message, public_key)
method = rsa.verify(b"hello world1", sign_message, public_key) # 报错Verification failed
print(method)

2.5 base64

1
2
3
4
5
6
7
8
9
import base64

# 编码
res=base64.b64encode(b'hello world')
print(res)

# 解码
res=base64.b64decode('aGVsbG8gd29ybGQ=')
print(res)

三 抓包逆向案例

3.1 金树林.apk

3.1.1 目标

1
2
3
4
# 发送验证码
# 注册
# 登录
# 登录后查询红酒

3.1.2 发送验证码

1
2
3
4
5
6
7
8
9
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
headers = {
'user-agent': 'Mozilla/5.0 (Linux; Android 11; Pixel 2 XL Build/RP1A.201005.004.A1; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36 uni-app Html5Plus/1.0 (Immersed/28.0)'
}
res = requests.get('https://miappshop.jshulin.com/memberLogin/phoneCode?phone=%s&serviceType=5' % '18953675221',
verify=False, headers=headers)
print(res.text)

3.1.3 注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 请求地址:https://miappshop.jshulin.com/memberLogin/memberRegister
# 请求方式:post
# 请求头:user-agent
# 请求体:
{"phone":"18953675221","fid":"","password":"lqz12345","phoneCode":"802827"}



####代码实现

import requests
import urllib3
### 使用接码平台---》用个手机号接码
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
headers = {
'user-agent': 'Mozilla/5.0 (Linux; Android 11; Pixel 2 XL Build/RP1A.201005.004.A1; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36 uni-app Html5Plus/1.0 (Immersed/28.0)'
}
# 通过上面的发送验证码拿到
data = {"phone": "18953675221", "fid": "", "password": "lqz12345", "phoneCode": "147426"}
res = requests.post('https://miappshop.jshulin.com/memberLogin/memberRegister', json=data, verify=False,headers=headers)
print(res.text)

3.1.4 登录

1
2
3
4
5
6
7
8
9
10
11
import requests
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxNjcxMTMyMDA2MDczNjA2MTQ1IiwiZXhwIjoxNzAwNTY3OTMzLCJpYXQiOjE3MDA1NjQzMzMsInVzZXJJZCI6IjE2NzExMzIwMDYwNzM2MDYxNDUiLCJ1c2VybmFtZSI6IjE3NzE3ODIzMjQ0In0.07NG-NwheOweInARUq_oBc_Mbx2wcFqCHT8O_4YI8GA
headers = {
'user-agent': 'Mozilla/5.0 (Linux; Android 11; Pixel 2 XL Build/RP1A.201005.004.A1; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36 uni-app Html5Plus/1.0 (Immersed/28.0)'
}
data={"password":"lqz12345","username":"17717823244"}
res=requests.post('https://miappshop.jshulin.com/memberLogin/login',json=data,verify=False,headers=headers)
print(res.text)

3.1.5 登录后查询商品

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import requests
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
headers = {
'Mobile-Token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxNjcxMTMyMDA2MDczNjA2MTQ1IiwiZXhwIjoxNzAwNTY3OTMzLCJpYXQiOjE3MDA1NjQzMzMsInVzZXJJZCI6IjE2NzExMzIwMDYwNzM2MDYxNDUiLCJ1c2VybmFtZSI6IjE3NzE3ODIzMjQ0In0.07NG-NwheOweInARUq_oBc_Mbx2wcFqCHT8O_4YI8GA', # 登录成功返回的token
'user-agent': 'Mozilla/5.0 (Linux; Android 11; Pixel 2 XL Build/RP1A.201005.004.A1; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36 uni-app Html5Plus/1.0 (Immersed/28.0)'
}

data = {
"cityNo": "",
"keyword": "石榴",
"limit": 10,
"orderByContent": "",
"page": 1,
"enabled": 1
}

res = requests.post('https://miappshop.jshulin.com/pro/searchByPage', json=data, verify=False, headers=headers)
print(res.text)

3.1.6 补充(请求参数转字典,请求头转字典)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 今日南川app登录

s='appId=32&hashSign=8356ebae71a0aa643f87ad4c5691a456&imgUrl=&lat=29.568295&lng=106.559123&loginName=18953675221&nickName=&openId=&place=%E9%87%8D%E5%BA%86&pwd=25d55ad283aa400af464c76d713c07ad&sessionId=392032c5-09c8-4c3c-bb17-16a1dc49f7fc&token=&type='

def query_to_dict(s):
return { item.split('=')[0]:item.split('=')[1]for item in s.split('&')}

print(query_to_dict(s))



### 请求头转字典
def header_str_to_dict(header_str):
res = [item for item in
header_str.split('\n')] # ['user-agent chuangqi.o.137.com.iqilu.app137/0.0.28.108','accept */*']
# print(res)
res = res[1:len(res) - 1] # 把列表前后空格去掉

d = {item.split('\t')[0]: item.split('\t')[1] for item in res}
return d

3.1.7 补充 url编码解码

1
2
3
from urllib import parse
print(parse.quote('上海'))
print(parse.unquote('%E4%B8%8A%E6%B5%B7'))

3.1.8 补充接码平台

1
2
3
4
5
6
7
8
# 免费收费大全
https://w3h5.com/post/619.html

# 例如 接收中国短信(免费的不稳定)
https://www.goinsms.xyz/cn.php
https://smscoders.com/china_phones
# 自行找收费的,注意别被骗,收费的都会带api接口

image-20230818192133330

image-20240517153052102

image-20240517153109471

image-20240517153125744

image-20240517153134735

3.2 爱安丘.apk(v228)

3.2.1 目标

1
2
3
4
5
6
7
#1 发送验证码登录(抓包)
#2 用户名密码登录(反编译,逆向加密函数)
# 注意:新版爱安丘加入了客户端证书校验,无法抓包,解决方案:LSposed + JustTrustMe
-官网下载:LSposed https://github.com/LSPosed/LSPosed/releases
-使用面具刷入
-安装JustTrustMe
-开启重启即可

3.2.2 IMEI

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
IMEI(International Mobile Equipment Identity)是【国际移动设备识别码】的缩写,它是一个唯一标识符,用于识别移动设备,如手机、平板电脑等。IMEI由15位数字组成,每一位都有特定的含义

# IMEI的规则如下:
前六位(TAC):型号核准号码,用于识别设备的制造商和设备类型。
接下来的两位(FAC):最终装配代码,表示设备的最终装配站。
后面的六位(SNR):串号,表示设备的序列号。
最后一位(SP):校验位,用于验证IMEI的有效性


def generate_imei(): # 跟useragent类似,要变换一下
# # 生成随机的TAC(前六位)
tac = ''.join(random.choices('0123456789', k=6))

# 生成随机的FAC(接下来的两位)
fac = ''.join(random.choices('0123456789', k=2))

# 生成随机的SNR(后面的六位)
snr = ''.join(random.choices('0123456789', k=6))

# 计算校验位
imei_base = tac + fac + snr
imei_list = [int(digit) for digit in imei_base]
check_digit = sum(imei_list[::-2] + [sum(divmod(d * 2, 10)) for d in imei_list[-2::-2]]) % 10

# 生成最终的IMEI
imei = imei_base + str((10 - check_digit) % 10)

return imei
# return "".join(random.choices('0123456789abcdef', k=15))

3.2.3 验证码登录(老版本v228)

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
import random
import requests


from utils import generate_imei


imei = generate_imei()
print(imei)

session = requests.Session()
session.cookies.set("orgid", "137")
session.headers.update({
"cq-agent": '{"os":"android","imei":"%s","osversion":"6.0.1","network":"none","version":"0.0.28.108","core":"1.6.4"}' % imei,
"user-agent": "chuangqi.o.137.com.iqilu.app137/0.0.28.108",
"orgid": "137"
})

phone_num = input("请输入手机号:")

res = session.post(
url="https://app-auth.iqilu.com/member/phonecode",
json={
"phone": phone_num
},
verify=False
)
res_dict = res.json()
print(res_dict)
key = res_dict['data']
code = input("请输入手机接收到的验证码:")

res = session.post(
url="https://app-auth.iqilu.com/member/login",
json={
"phone": phone_num,
"code": code,
"key": key,
"password": "",
"captcha": "",
"captchaKey": ""
},verify=False
)
print("登录结果->", res.text)

3.2.4 验证码接收

1
2
3
4
5
6
7
8
9
10
11
12
# 可以接收手机验证码的平台---》这些东西违法--》小心点
-免费--不稳定
-收费的--靠谱


# 免费收费大全
https://w3h5.com/post/619.html

# 例如 接收中国短信(免费的不稳定)
https://www.goinsms.xyz/cn.php
https://smscoders.com/china_phones
# 自行找收费的,注意别被骗,收费的都会带api接口

image-20240517153155610

image-20240517153204125

image-20240517153213222

image-20240517153224752

image-20230818192534314

image-20240517153253764

3.2.5 验证码登录(v228)

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
import requests
import utils

# 1 发送验证码
phone = input('请输入手机号:')
imei = utils.gen_imei()
data = {"phone": "%s" % phone}
headers = {
'user-agent': 'chuangqi.o.137.com.iqilu.app137/0.0.28.108',
'orgid': '137',
'cq-agent': '{"os":"android","imei":"%s","osversion":"11","network":"none","version":"0.0.28.108","core":"1.6.4"}' % imei,
'Cookie': 'orgid=137',
}
res = requests.post('https://app-auth.iqilu.com/member/phonecode', json=data, verify=False, headers=headers)
res_data = res.json()['data']

# 2 验证码登录-=====》接码平台
code = input('请输入手机收到的验证码:')
data = {
"phone": "%s" % phone,
"code": "%s" % code,
"key": res_data, # 上一次发送手机验证码接口返回的那个data base64编码
"password": "",
"captcha": "",
"captchaKey": ""
}
res = requests.post('https://app-auth.iqilu.com/member/login', json=data, verify=False, headers=headers)

print(res.text)

3.2.6 手机号密码登录(新版本最新版)

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
# 用户名密码登录

# 换成最新版
# 抓包分析
-请求地址:https://app-auth.iqilu.com/member/login?e=1
-请求方式:post
-请求体:
{
"codeKey": "",
"password": "Gq7piXQy+YGPZ7yzBeElEA==", # 加密了,是base64
"code": "",
"phone": "18953675221",
"key": ""
}
-请求头:跟老版本一样


# 咱们得目标:破解密码的加密---》反编译apk---》根据关键字搜索加密方式
-关键词:password "password" "password
-优先用url搜索:会唯一 member/login


#1 搜索---》一路查找,定位到了--》java不太懂,听我讲即可
public static String aesPassword(String str) {
return Base64.encodeToString(EncryptUtils.encryptAES(str.getBytes(), getMD5(PRIVATE_KEY + getSecret() + BaseApp.orgid).getBytes(), "AES/CBC/PKCS7Padding", "0000000000000000".getBytes()), 2);
}

# 1 使用:encryptAES 对传入的字符串加密(明文密码)
# 2 getMD5(PRIVATE_KEY + getSecret() + BaseApp.orgid).getBytes() 得到aes加密的秘钥,固定的
# 3 "AES/CBC/PKCS7Padding" 加密方式
# 4 iv:偏移量 0000000000000000
# 6 转成base64编码


# 2 需要知道aes加密的秘钥,秘钥是固定的--》hook得到---》getMD5
####1 手机端启动frida-serve
adb shell
su
cd /data/local/tmp/
ls
./frida-server

####2 设置端口转发
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043

反编译,找位置(根据地址:member/login)

image-20240517153332067

image-20240517153340537

image-20240517153355217

image-20240517153403857

**image-20240517153413621

hook.py

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
####1 手机端启动frida-serve
adb shell
su
cd /data/local/tmp/
ls
./frida-server-16.0.19-android-arm64

####2 设置端口转发
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043

# 3 前台运行的包名

# 枚举手机上的所有进程 & 前台进程
import frida
# 获取设备信息
rdev = frida.get_remote_device()
# 枚举所有的进程
processes = rdev.enumerate_processes()
for process in processes:
print(process)
# 获取在前台运行的APP
front_app = rdev.get_frontmost_application()
print(front_app)
##############################

# 4 hook

import frida
import sys

# 连接手机设备
rdev = frida.get_remote_device()

session = rdev.attach("爱安丘")

scr = """
Java.perform(function () {

// 包.类
var EncryptUtil = Java.use("com.iqilu.core.util.EncryptUtil");

EncryptUtil.getMD5.implementation = function(str){
console.log("明文:",str);
var res = this.getMD5(str);
console.log("md5加密结果=",res);
return res;
}

});
"""

script = session.create_script(scr)


def on_message(message, data):
print(message, data)


script.on("message", on_message)

script.load()
sys.stdin.read()

用户名密码登录代码

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
import requests
from utils import gen_imei,header_str_to_dict,encrypt_data

session = requests.session()
imei = gen_imei() # 随机生成imei
header_s = '''
encrypt 1
orgid 137
User-Agent null chuangqi.o.137.com.iqilu.app137/1.0.5
platform android
imei bae6495482efee22
CQ-AGENT {"os":"android","brand":"google","imei":"%s","osversion":"11","network":"wifi","version":"1.0.5","core":"2.2.1.1"}
cq-token
Content-Type application/json; charset=UTF-8
Host app-auth.iqilu.com
Accept-Encoding gzip
Cookie orgid=137; redirectToken=18806862d60c48bd9842e6dc2327372a-87343990; redirect=
Content-Length 93
Connection keep-alive
''' % imei
header = header_str_to_dict(header_s)
phone = input('请输入手机号:')
password = input('请输入密码:')

password = encrypt_data(password)
data = {"codeKey": "", "password": password, "code": "", "phone": phone, "key": ""}
res = session.post('https://app-auth.iqilu.com/member/login?e=1', headers=header, json=data, verify=False)
print(res.text)

utils.py

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
import random
# pip3 install pycryptodome

from Crypto.Cipher import AES
import base64
# def gen_imei():
# # 这个不够标准
# return "".join(random.choices('0123456789abcdef', k=15))

def gen_imei():
# # 生成随机的TAC(前六位)
tac = ''.join(random.choices('0123456789', k=6))

# 生成随机的FAC(接下来的两位)
fac = ''.join(random.choices('0123456789', k=2))

# 生成随机的SNR(后面的六位)
snr = ''.join(random.choices('0123456789', k=6))

# 计算校验位
imei_base = tac + fac + snr
imei_list = [int(digit) for digit in imei_base]
check_digit = sum(imei_list[::-2] + [sum(divmod(d * 2, 10)) for d in imei_list[-2::-2]]) % 10

# 生成最终的IMEI
imei = imei_base + str((10 - check_digit) % 10)

return imei


def header_str_to_dict(header_str):
res = [item for item in header_str.split('\n')]
res = res[1:len(res) - 1]
d = {item.split('\t')[0]: item.split('\t')[1] for item in res}
return d


# 加密的字符串,必须是固定长度,处理成固定长度
def pad_data(data):
# 计算需要填充的字节数
pad_len = AES.block_size - (len(data) % AES.block_size)
# 使用填充字节进行填充
padding = bytes([pad_len] * pad_len)
padded_data = data + padding
return padded_data
def encrypt_data(password):
# 创建 AES 密码对象
# cipher = AES.new(key, AES.MODE_CBC, iv)
# 密钥(16 字节)
key = b'6d6656a37cdb7977c10f6d83cab168e9'
# 初始化向量(16 字节)
iv = b'0000000000000000'
cipher = AES.new(key, AES.MODE_CBC, iv)
# 填充数据
padded_data = pad_data(password.encode('utf-8'))
print(padded_data)
# 加密数据
encrypted_data = cipher.encrypt(padded_data)
return base64.b64encode(encrypted_data).decode('utf-8')

if __name__ == '__main__':
res=encrypt_data('lqz12345') # Gq7piXQy+YGPZ7yzBeElEA== 抓包抓到的
print(res) # Gq7piXQy+YGPZ7yzBeElEA== 自己加密

四 抓包反编译案例

4.1 X大夫

4.1.1 目录

1
# 模拟登录

4.1.2 操作步骤

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
#1 请求地址
https://api.niaodaifu.cn/v4/site/loginnew
#2 请求体
devisetoken 1507bfd3f6dd1eafc0f
password lqz12345
mobile 18953675221
channel android
sign afc1963626ccdb43d5b394017b206144
time 1687255476
mechanism 0
platform 1

#3 感觉,需要逆向的请求体中的字段devisetoken和sign,去掉devisetoken重新发包,发现正常响应,所以只需要逆向sign

#4 jadx打开X大夫

#5 搜索内容是:"sign"
#6 核心代码如下
# 获取当前秒级别的时间戳
long currentTimeMillis = System.currentTimeMillis() / 1000;
SafeUtils.getSign(currentTimeMillis)

# 6 代码如下 SafeUtils.getSign
public static String getSign(long j) {
try {
MessageDigest instance = MessageDigest.getInstance("MD5");
String substring = HexDump.toHex(instance.digest(("niaodaifu" + j).getBytes())).substring(12, 30);
String substring2 = HexDump.toHex(instance.digest((channel + j).getBytes())).substring(12, 26);
return substring + substring2;
} catch (Exception unused) {
return "";
}
}
# 7 转成python 代码为:
import hashlib


def md5(data_string):
obj = hashlib.md5()
obj.update(data_string.encode('utf-8'))
return obj.hexdigest()

j = "1687256602"

v1 = md5(f"niaodaifu{j}")[12:30]
v2 = md5(f"android{j}")[12:26]
sign = v1 + v2
print(sign)

image-20240517153436753

image-20240517153445869

image-20240517153503213

4.1.3 实现代码

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
import requests

import time
import hashlib
requests.packages.urllib3.disable_warnings()
def query_to_dict(s):
return {item.split('=')[0]: item.split('=')[1] for item in s.split('&')}
def md5(data_string):
obj = hashlib.md5()
obj.update(data_string.encode('utf-8'))
return obj.hexdigest()


def get_sign():
t = int(time.time())
v1 = md5(f"niaodaifu{t}")[12:30]
v2 = md5(f"android{t}")[12:26]
sign = v1 + v2
return sign


print(get_sign())

phone = input('请输入手机号:')
password = input('请输入密码:')
sign = get_sign()
print('----',sign)
q = 'password=%s&mobile=%s&channel=android&sign=%s&time=%s&mechanism=0&platform=1' % (
password, phone, sign,int(time.time()))
data = query_to_dict(q)

res = requests.post('https://api.niaodaifu.cn/v4/site/loginnew', json=data, verify=False)
print(res.json())


4.2 油联合伙人

4.2.1 目标

1
2
# 登录

4.2.2 密码破解操作步骤

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
# 1 抓包
-请求地址:https://chinayltx.com/app/api/v1/partnerLogin/login
-请求体:
phone 18953675221
password 166acc691782f077c5c7c7c10fa39b1c
-请求头:
X-App native
X-Noncestr 123456
X-OS partnerApp_android
X-Req-Time 1687258564222
X-Sign 869a52194c795e272475b4e50d4e80fa
X-Token
X-UserID
Content-Type application/x-www-form-urlencoded
Content-Length 59
Host chinayltx.com
Connection Keep-Alive
Accept-Encoding gzip
User-Agent okhttp/3.10.0
# 2 逆向
根据 请求地址搜索: partnerLogin/login

# 3 代码如下(使用了Retrofit2 模块,发送请求的,@ 是java的注解)
@FormUrlEncoded
@POST("api/v1/partnerLogin/login")
Observable<HttpResult<LoginInfo>> submitLogin(@Field("phone") String str, @Field("password") String str2);
# 4 只要 类名.submitLogin("18953675221","lqz12345"),就会向api/v1/partnerLogin/login发送请求,使用FormUrlEncoded编码格式
# 5 查找用例submitLogin
# 6 找到loginWithToken---》使用查找用例找不到,直接全局搜索
# 7 找到buildObservable
@Override // com.yltx.oil.partner.mvp.domain.UseCase
public Observable<HttpResult<LoginInfo>> buildObservable() {
return this.mRepository.loginWithToken(this.name, this.pwd);
}

# 8 查找密码设置位置
public void submitLogin(String str, String str2) {
this.mLoginUseCase.setName(str);
this.mLoginUseCase.setPwd(Md5.md5(str2));
this.mLoginUseCase.execute(new LoginSubscriber(this.view));
}
# 9 确认是md5

image-20240517153603914

image-20240517153614203

image-20240517153741588

image-20240517153750039

4.2.3 签名的破解步骤

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
# 1 搜索 X-Sign
# 2 当前类中找哪里使用:private static final String PARAM_SIGN = "X-Sign";
# 3 找到getRequestHeaders方法
public Headers getRequestHeaders(Headers headers) {
Headers.Builder newBuilder = headers.newBuilder();
newBuilder.add(PARAM_APP, this.appType);
newBuilder.add(PARAM_NONCESTR, this.noncestr);
newBuilder.add(PARAM_OS, this.clientType);
newBuilder.add(PARAM_REQ_TIME, this.reqTime);
newBuilder.add(PARAM_SIGN, this.sign);
newBuilder.add(PARAM_TOKEN, this.token);
newBuilder.add(PARAM_USER_ID, this.userId);
return newBuilder.build();
}
# 4 根据this.sign 找到
private String sign(String str) {
return Md5.md5(this.token + this.reqTime + this.noncestr.substring(2) + str).toLowerCase();
}

# 5 使用python编写
import hashlib

token = ""
reqTime = "1657201079926"
nonce_str = "123456"
nonce_str_sub_2 = nonce_str[2:]
body_string = "phone=18630099999&password=4297f44b13955235245b2497399d7a93"

encrypt_string = f"{token}{reqTime}{nonce_str_sub_2}{body_string}"

obj = hashlib.md5()
obj.update(encrypt_string.encode('utf-8'))
res = obj.hexdigest()
print(res)

image-20240517153826586

image-20240517153834490

4.2.4自动登录代码如下

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
import requests
import hashlib
import time

phone = input('请输入手机号')
password = input('请输入密码')


def get_md5(data_string):
obj = hashlib.md5()
obj.update(data_string.encode('utf-8'))
return obj.hexdigest()


password = get_md5(password)
data = {
'phone': phone,
'password': password
}
token = ''
req_time = str(int(time.time() * 1000))
nonce_str = "123456"
nonce_str_sub_2 = nonce_str[2:]
body_string = f"phone={phone}&password={password}"
sign = encrypt_string = f"{token}{req_time}{nonce_str_sub_2}{body_string}"
sign = get_md5(encrypt_string)

header = {
"X-App": "native",
"X-Noncestr": nonce_str,
"X-OS": "partnerApp_android",
"X-Req-Time": req_time,
"X-Sign": sign,
"X-Token": token,
"X-UserID": ""
}
res = requests.post('https://chinayltx.com/app/api/v1/partnerLogin/login', data=data, verify=False, headers=header)

print(res.text)

__END__