今日内容

1 今日目标

1
2
3
4
5
# 司小宝---》破解登录
# 手机号+密码登录
# 版本:司小宝 v4.0.9版
# 安装到手机上:
adb install

image-20240220200636852

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
# 1 使用charles的代理---》之前做过很多次了
# 2 抓包:如下图
# 3 分析
-请求地址:
https://g7s.ucenter.huoyunren.com/inside.php
-请求方式:
POST
-请求头:
X-B3-TraceId 3286b0335d984caf8c7c752f9e3c9692 # 可以删除
-请求参数:
t: json # 固定的
m: mobileinfo # 固定的
f: driverLogin # 固定的
accessid: br278dj# 固定,可删除
g7timestamp: 1708431515385 # 时间戳
app 1 # 固定的
ua android # 客户端类型,固定的
appclientversion 4.0.9 # app版本
referer d507c00281c733bd693e5049ea33ad7e # 上一次请求的访问的地址,加密了,固定的,可以删除
sign SoDxh4A0/p3DbQPkh0SG6taqh+E= # 需要破解---》可以删除,为了学习,要破解

-请求体:
mobile # 手机号---加密了
password # 密码---加密了
equipment # google 固定的



# 4 总结:需要破解的
-mobile
-password
-sign

image-20240517183715914

3 逆向sign

3.1 步骤分析

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
# 1 jadx 反编译---》搜索
-可以搜 "sign"
-我们搜索:g7timestamp
# 2 找到位置,发现里面有sign
sb.append("&sign=");
sb.append(new p0().a(cmt.chinaway.com.lite.i.b.d().j, str2, valueOf, str));

# 3 new p0().a(cmt.chinaway.com.lite.i.b.d().j, str2, valueOf, str) 分析
new 类名().a()--->返回结果是sign这个字符串
a()-->方法传入了 4 个参数


# 4 分析new 类名().a() 方法,它传入4个参数,返回值是sign
public String a(String str, String str2, String str3, String str4) {
try {
# url编码---》执行了new s0().a
# 传入了两个参数:str, str2 str3 str4拼接到一起了
return Uri.encode(new s0().a(str, str2 + "\n" + str3 + "\n/" + str4));
} catch (SignatureException unused) {
throw new RuntimeException("sign failed");
}
}
# 5 hook一下 p0 类的a方法---》查看四个参数是什么?
str: 1KMrg0dfufc0wpnXEJacEQX1YEUYA0Ja
str2 : POST
str3: 1708434407419
str4: inside.php

# 6 分析:new s0().a(str,str2 + "\n" + str3 + "\n/" + str4)
第一个参数:1KMrg0dfufc0wpnXEJacEQX1YEUYA0Ja
第二个参数:
"POST
1708434407419
/inside.php"


# 7 new s0().a 源码
public String a(String str, String str2) throws SignatureException {
try {
# 拿到HmacSHA1的秘钥:1KMrg0dfufc0wpnXEJacEQX1YEUYA0Ja
SecretKeySpec secretKeySpec = new SecretKeySpec(str.getBytes(), "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(secretKeySpec);
# 对str2加密,转成base64
return Base64.encodeToString(mac.doFinal(str2.getBytes("UTF-8")), 2);
} catch (Exception e2) {
throw new SignatureException("Failed to generate HMAC : " + e2.getMessage());
}
}
# 8 sign的加密方式:
对字符串:
"POST
1708434407419 时间戳
/inside.php"
使用 HmacSHA1 加密,秘钥是1KMrg0dfufc0wpnXEJacEQX1YEUYA0Ja

image-20240517183727174

3.2 hook-p0-a方法

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
74
75
76
77
78
79
80
# 1手机端启动frida-server
# 2端口转发
# 3运行下面脚本--发现报错

import frida
import sys
# 连接手机设备
rdev = frida.get_remote_device()
session = rdev.attach("司小宝")
scr = """
Java.perform(function () {
var p0 = Java.use("cmt.chinaway.com.lite.q.p0");
p0.a.implementation = function (str, str2, str3,str4) {
console.log("---------------------")
console.log(str, str2, str3,str4);
var res = this.a(str, str2, str3,str4);
console.log(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()





'''
### 如果app运行了,会报这个错--》没hook到
报错了:frida.ProcessNotFoundError: process not found

### 如果app没运行:会包这个错
frida.ProcessNotFoundError: unable to find process with name '司小宝'、



## 司小宝 做了frida的反调试
1 删so文件 :在so文件中做了frida检测
2 使用hulda:做了 frida关键词的检测
3 使用ptrace占坑
'''


###使用spawn的hook方案
#js hook 脚本
Java.perform(function () {
var p0 = Java.use("cmt.chinaway.com.lite.q.p0");
p0.a.implementation = function (str, str2, str3, str4) {
console.log("---------------------")
console.log(str, str2, str3, str4);
var res = this.a(str, str2, str3, str4);
console.log(res);
return res;
};
});

// frida -U -f cmt.chinaway.com.lite -l 3-spawn方案hook.js

/*
str:1KMrg0dfufc0wpnXEJacEQX1YEUYA0Ja
str2 :"POST
str3: 1708434407419
str4: inside.php

1KMrg0dfufc0wpnXEJacEQX1YEUYA0Ja POST 1708434407419 inside.php
I2BPfr1R3%2B9rLwO4deIcMq7Urbg%3D

*/




hook–车智赢案例–看看能不能hook成功

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()
session = rdev.attach(21678)
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()
sys.stdin.read()

### 车智赢能顺利hook成功,但是司小宝hook不成功

3.3 ptrace占坑

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
#### 1 什么是ptrace占坑
ptrace可以让一个进程监视和控制另一个进程的执行,并且修改被监视进程的内存、寄存器等,主要应用于调试器的断点调试、系统调用跟踪等。

在Android app保护中【司小宝】,ptrace被广泛用于反调试【反frida】。因为一个进程只能被ptrace一次,如果先调用了ptrace方法,那就可以防止别人调试我们的程序。这就是传说中的先占坑。

一个进程只能被一个进程附加,为了防止frida的附加,程序中自己先基于ptrace附加自己,我们如果后期使用frida去附加app时,就会报错了。

frida内部会使用ptrace


### 2 如何查看,我们的app,是否被占坑了?--(是否被自己ptrace占坑了)
-查看手机正在运行的进程,命令在手机上运行【不是在电脑上运行】
-adb shell # 进入手机
-ps -A #查看手机上正在运行的进程--》非常多
-# 过滤出我们想要的--》只想看跟司小宝相关的
- ps -A | grep 过滤的关键字[app的包名]
- ps -A |grep lite # 把跟司小宝相关的所有进程过滤出来

-车智赢没有被占坑:只会有一个进程:
ps -A |grep autotradercloud



# 一个app运行,可能启动了多个进程
frida-ps -U -a # 看那个app在运行
ps -A |grep lite # 看跟这个app相关的进程在运行

# 总结:只要发现 某个app,有多个进程,并且有个进程名字叫:__skb_recv_xxx,这就是被占坑了



## 3 查看app的包名---》
-之前学过打印出所有正在运行的app,和前台app--》python脚本做的
-使用命令:frida-ps -U -a # 在电脑端运行---》必须保证手机端的frida-server是运行的
-看到了车智赢在运行: com.che168.autotradercloud
-看到了司小宝在运行: cmt.chinaway.com.lite




## 4 解决ptrace占坑
1 通过杀进程方案---》把ptrace占坑的进程--》直接杀掉--》测试一下
-有的app可以
-有的app,把占坑进程杀掉---》主进程也会结束
-kill -9 28204 # 进程id号

2 app运行--》启动进程站住ptrace的坑---》使用spawn的方案hook--》让hook脚本早于占坑的进程运行--》占坑的进程就运行不了了,只能运行frida的进程和app进程了
-使用spawn的hook方案,可以绕过


3 后期咱们会学习:aosp刷机---》修改源码---》让app自己附件的进程都启动失败--》把系统编译进手机--》避免这个问题

image-20240517183752895

image-20240517183803240

image-20240517183809632

3.4 sign代码整合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import base64
import hashlib
import hmac
from urllib.parse import quote_plus

key = "1KMrg0dfufc0wpnXEJacEQX1YEUYA0Ja"
message = """POST
1708434407419
/inside.php"""
message = message.encode('utf-8') # 加密内容
key = key.encode('utf-8') # 加密的key
result = hmac.new(key, message, hashlib.sha1).digest()
print(result)
_sig = base64.b64encode(result).decode()
print(_sig)

result = quote_plus(_sig) #url编码
print(result)

4 逆向手机号和密码加密

4.1 步骤分析

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
# 1 搜索: "password"--定位到 有 password和mobile的一个
# 2 找到:
@FormUrlEncoded
@POST("inside.php?t=json&m=mobileinfo&f=driverLogin")
e.c.l<LoginResponseEntity> b(@Field("mobile") String str, @Field("password") String str2, @Field("equipment") String str3);
# 3 查找用例---》b的用例
# 三个参数,手机号,密码,手机品牌
# obj 是明文密码
cmt.chinaway.com.lite.l.e.O().b(n1.a(text), n1.a(obj), Build.BRAND)

# 4 查看n1.a---rsa加密
public static String a(String str) throws Exception {
byte[] doFinal;
byte[] bytes = str.getBytes("utf-8");
# 1 这里在获取公钥
PublicKey b = b();
# 2 拿到对象
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(1, b);
int length = bytes.length;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int i = 0;
int i2 = 0;
while (true) {
int i3 = length - i;
if (i3 > 0) {
if (i3 > 117) {
doFinal = cipher.doFinal(bytes, i, 117);
} else {
doFinal = cipher.doFinal(bytes, i, i3);
}
byteArrayOutputStream.write(doFinal, 0, doFinal.length);
i2++;
i = i2 * 117;
} else {
byte[] byteArray = byteArrayOutputStream.toByteArray();
byteArrayOutputStream.close();
# 3 对明文密码进行加密--》转成了base64
# 4 把结果又执行了 c方法---》对加密后的串进行位运算
return c(Base64.encodeToString(byteArray, 0).replace("\n", ""));
}
}
}
private static PublicKey b() throws NoSuchAlgorithmException, InvalidKeySpecException {
return KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(Base64.decode("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsAwGz+E7PEppCUWrM46wf9Sj2Zv+dlSIjwGtMoG9iG36HFYy0BC+uAgNDun0joo4UFW0qejyc2ExjFXBRiYENnseY3jb9qI0c3thAj2qd5iofGCWaRO1DA707xwD7MOjvHur7c7nvBNv+vwWFvMDiZzj4JqaaV8ZNTT2oO9+aJQIDAQAB", 0)));
}

# c方法是对字符串进行位运算
private static String c(String str) {
byte[] bytes;
char[] charArray = "0123456789ABCDEF".toCharArray();
StringBuilder sb = new StringBuilder("");
for (byte b : str.getBytes()) {
sb.append(charArray[(b & 240) >> 4]);
sb.append(charArray[b & 15]);
}
return sb.toString().trim();
}

image-20240517183822075

image-20240517183830842

image-20240517183838713

4.2 hook–n1-a方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function hook_RegisterNatives() {
Java.perform(function () {
var n1 = Java.use("cmt.chinaway.com.lite.q.n1");
n1.a.implementation = function (str) {
console.log("---------------------")
console.log(str);
var res = this.a(str);
console.log(res);
return res;
};
});
}

setImmediate(hook_RegisterNatives);

image-20240517183848830

4.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
from Crypto.PublicKey import RSA
from Crypto.Util.Padding import pad
from Crypto.Cipher import PKCS1_v1_5, AES
from base64 import b64encode


def c(data_text):
array = "0123456789ABCDEF"
data_list = []
for b in data_text.encode('utf-8'):
data_list.append(array[(b & 240) >> 4])
data_list.append(array[b & 15])
return "".join(data_list)


data = "18953675221"

rsa_pub_key = """-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsAwGz+E7PEppCUWrM46wf9Sj2Zv+dlSIjwGtMoG9iG36HFYy0BC+uAgNDun0joo4UFW0qejyc2ExjFXBRiYENnseY3jb9qI0c3thAj2qd5iofGCWaRO1DA707xwD7MOjvHur7c7nvBNv+vwWFvMDiZzj4JqaaV8ZNTT2oO9+aJQIDAQAB
-----END PUBLIC KEY-----"""
key = RSA.importKey(rsa_pub_key)
cipher = PKCS1_v1_5.new(key)
encrypt_bytes = cipher.encrypt(data.encode('utf-8'))
cipher_text = b64encode(encrypt_bytes).decode('utf-8')
cipher_text = cipher_text.replace(r"\n", "")


result = c(cipher_text)
print(result)

5 登录功能代码整合

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
74
75
76
77
78
79
80
81
import time
import requests
import base64
import hashlib
import hmac
from urllib.parse import quote_plus
from Crypto.PublicKey import RSA
from Crypto.Util.Padding import pad
from Crypto.Cipher import PKCS1_v1_5, AES
from base64 import b64encode


def sign(g7timestamp):
key = "1KMrg0dfufc0wpnXEJacEQX1YEUYA0Ja"
message = """POST
{}
/inside.php""".format(g7timestamp)
message = message.encode('utf-8')
key = key.encode('utf-8')
result = hmac.new(key, message, hashlib.sha1).digest()
_sig = base64.b64encode(result).decode()
return quote_plus(_sig)


def encrypt(data_string):
def c(data_text):
array = "0123456789ABCDEF"
data_list = []
for b in data_text.encode('utf-8'):
data_list.append(array[(b & 240) >> 4])
data_list.append(array[b & 15])
return "".join(data_list)

rsa_pub_key = """-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsAwGz+E7PEppCUWrM46wf9Sj2Zv+dlSIjwGtMoG9iG36HFYy0BC+uAgNDun0joo4UFW0qejyc2ExjFXBRiYENnseY3jb9qI0c3thAj2qd5iofGCWaRO1DA707xwD7MOjvHur7c7nvBNv+vwWFvMDiZzj4JqaaV8ZNTT2oO9+aJQIDAQAB
-----END PUBLIC KEY-----"""
key = RSA.importKey(rsa_pub_key)
cipher = PKCS1_v1_5.new(key)
encrypt_bytes = cipher.encrypt(data_string.encode('utf-8'))
cipher_text = b64encode(encrypt_bytes).decode('utf-8')
cipher_text = cipher_text.replace(r"\n", "")
result = c(cipher_text)
return result


def run():
g7timestamp = str(int(time.time() * 1000))

mobile = "18953675221"
password = "lqz12345"

param_dict = {
"t": "json",
"m": "mobileinfo",
"f": "driverLogin",
"g7timestamp": g7timestamp,
"app": "1",
"ua": "android",
"appclientversion": "4.0.9",
"referer": "d507c00281c733bd693e5049ea33ad7e", # 固定的
"sign": sign(g7timestamp)
}

body_dict = {
"mobile": encrypt(mobile),
"password": encrypt(password),
"equipment": "google"
}

res = requests.post(
url="https://g7s.ucenter.huoyunren.com/inside.php",
params=param_dict,
data=body_dict,
verify=False
)
print(res.json())


if __name__ == '__main__':
run()

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
# 免费代理和收费代理
-收费需要花钱
-搭建一个免费代理池--》供测试使用

# 开源的代理池解决方案:https://github.com/jhao104/proxy_pool
# 使用python写的---》直接把代码拉取下来,运行在本地即可
# 如果不想运行在本地---》使用它的:http://demo.spiderpy.cn/get/ 会随机返回一个代理


# 操作步骤---》》需要使用redis---》安装redis软件

#1 拉取代码
#2 pycharm打开
#3 安装依赖:pip3 install -r requirements.txt
#4 修改配置文件
# setting.py 为项目配置文件
# 配置API服务
HOST = "0.0.0.0" # IP
PORT = 5000 # 监听端口
# 配置数据库
DB_CONN = 'redis://127.0.0.1:6379/0'
# 配置 ProxyFetcher
PROXY_FETCHER = [
"freeProxy01", # 这里是启用的代理抓取方法名,所有fetch方法位于fetcher/proxyFetcher.py
"freeProxy02",
# ....
]

# 5 运行调度程序
# 启动调度程序--》爬取免费代理--》放到redis中
python proxyPool.py schedule
# 6 运行web服务
# 启动webApi服务--启动起一个服务--》访问某个接口就能随机获取一个代理ip
python proxyPool.py server

# 7 访问地址:就随机拿一个代理ip
http://127.0.0.1:5010/get/?type=http

7 自定代理工具 mitmproxy

7.1 介绍和安装

1
2
3
4
5
6
7
8
9
10
11
12
13
## mitmproxy 是什么?
实时拦截、修改 HTTP/HTTPS 请求和响应
可保存完整的 http 会话,方便后续分析和重放
支持反向代理模式将流量转发到指定服务器
支持用 Python 脚本对 HTTP 通信进行修改

# 官网
https://mitmproxy.org/

# mitmproxy跟charles区别
mitmproxy 就是用于 MITM 的 proxy,MITM 即中间人攻击(Man-in-the-middle attack)。用于中间人攻击的代理首先会向正常的代理一样转发请求,保障服务端与客户端的通信,其次,会适时的查、记录其截获的数据,或篡改数据,引发服务端或客户端特定的行为。

不同于 charles 抓包工具,mitmproxy 不仅可以截获请求帮助开发者查看、分析,更可以通过自定义脚本进行二次开发。举例来说,利用 charles 可以过滤出浏览器对某个特定 url 的请求,并查看、分析其数据,但实现不了高度定制化的需求,比如:“截获对浏览器对该 url 的请求,将返回内容置空,并将真实的返回内容存到某个数据库,出现异常时发出邮件通知”。而对于 mitmproxy,这样的需求可以通过载入自定义 python 脚本轻松实现。

image-20240517183907546

7.1.2 安装

1
2
3
4
5
6
7
8
9
10
11
12
13
# 方案一:安装包,一路下一步(不建议用)


# 方案二:使用 pip install 安装
pip3 install mitmproxy # 如果是在虚拟环境下执行--》会被安装到虚拟环境中,只能在虚拟环境中使用


# 安装完成后有三个命令
mitmproxy、mitmdump、mitmweb 三个命令,由于 mitmproxy 命令不支持在 windows 系统中运行


# 测试是否安装成功:在命令行中执行
mitmdump --version

image-20240517183917688

image-20240517183928231

7.2 基本使用

1
2
3
4
5
6
7
8
9
10
# 1 查看版本
mitmdump --version

# 2 启动mitmprox
-mitmdump # 命令窗口启动
-mitmweb # web页面启动

# 注意:
- 分析请求包:推荐mitmweb
- 命令行:推荐mitmdump + 代码

7.3 配置手机代理和证书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 跟之前配置charles基本类似

-默认不需要装证书,就可以抓http的包
-需要装了证书,才能抓https的包


# 不需要安装证书---》就能抓取爱学生app的数据包

# 如果抓 其他app,是https的数据包,必须安装证书
-需要把用户证书做成 系统证书
# 安装证书:
1 手机访问:mitm.it
2 下载安卓证书
3 手机端安装证书--》放在用户证书中
4 重启手机--》用户证书被做成系统证书
需要安装证书,使用mitmproxy证书 + move cert + 重启

# 以后就可以抓https 的包了

image-20240517183940976

image-20240517183950731

image-20240517184000615

7.4 python+mitmproxy

1
2
# 使用python代码,控制修改 http请求或响应包

7.4.1 基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from mitmproxy import http


def request(flow: http.HTTPFlow):
if 'inside.php' in flow.request.path:
print(type(flow.request)) # mitmproxy.http.Request
print(flow.request.path)
print(flow.request.query)
print(flow.request.headers)
print(flow.request.data)



def response(flow: http.HTTPFlow):
if 'inside.php' in flow.request.path:
print(type(flow.response)) # mitmproxy.http.Response
print(flow.response.text)
print(flow.response.content)
print(flow.response.json())
print(flow.response.headers)

# mitmdump -s 8-python操作-mitmproxy.py
# mitmdump -s 8-python操作-mitmproxy.py -q # 不输出日志

7.4.2 使用代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from mitmproxy import http
from mitmproxy.net.server_spec import ServerSpec
import requests


def request(flow: http.HTTPFlow):
if 'inside.php' in flow.request.path:
print('使用了代理')
res = requests.get('http://127.0.0.1:5010/get/?type=https').json()
ip, port = res.get('proxy').split(':')
address = (ip, port)
print(address)
flow.server_conn.via = ServerSpec(("http", address))
else:
print('没使用代理')


def response(flow: http.HTTPFlow):
print(flow.response.content)


# mitmdump -s 9-使用代理.py -q

__END__