今日内容

1 抓包案例–爱安丘(旧版本)

1.1 目标

1
2
# 发送短信和短信登录 
# app版本:爱安丘-v228.apk

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
49
50
# 1 配置手机代理
# 2 打开charles
# 3 操作手机--》点击发送短信--》抓包查看
# 4 抓到的包
-请求地址:
https://app-auth.iqilu.com/member/phonecode
-请求方式:
post
-请求头:尝试改包,去掉这几个参数尝试能不能发送成功,如果能成功,就可以不携带
user-agent:带上
cq-agent:可以不带{"os":"android","imei":"69b80588244673ed","osversion":"11","network":"none","version":"0.0.28.108","core":"1.6.4"}
orgid:137
-请求体:
{"phone":"18953675221"}

# 5 使用代码,发送短信
import requests
phone = input('请输入您的手机号:')
data = {"phone": phone}
headers = {
'user-agent': 'chuangqi.o.137.com.iqilu.app137/0.0.28.108',
'cq-agent': '{"os":"android","imei":"69b80588244673ed","osversion":"11","network":"none","version":"0.0.28.108","core":"1.6.4"}',
'orgid': '137',

}
res = requests.post("https://app-auth.iqilu.com/member/phonecode", json=data, verify=False, headers=headers)
print(res.text) # {"code":1,"data":"z1SuIyAZmfWiLGnha/0MRVf8ji+QXdkXHjNiSVhVs9M="}



# 6 短信登录接口
-请求地址:
https://app-auth.iqilu.com/member/login
-请求方式:
post
-请求体数:
{"phone":"18953675221","code":"502524","key":"c/vtWbmZRjozFTwFIOQR9IIpHwNcy+JiU98RLnXdUFA=","password":"","captcha":"","captchaKey":""}
-请求头:
跟之前一样

# 7 python代码实现

# 补充:
-同学们如果抓http的包正常[爱学生app],但是如果抓https包[手机必须root],乱码,原因是手机证书安装有问题--》重新装一下证书即可


# 抓包和改包
-咱们抓到包后,想修改数据包--》尝试删除某些请求头中,请求体中 字段,再发包,看看能不能顺利请求,如果能,这写字段就不需要破了---》如果不能,这些字段必须破
-全带上不就行了
-如果请求头中有个sign,你也不知道它的加密方式,你也破解不了,对于sign是动态变化的,

image-20240517154739686

image-20240517154749572

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

phone = input('请输入您的手机号:')
data = {"phone": phone}
headers = {
'user-agent': 'chuangqi.o.137.com.iqilu.app137/0.0.28.108',
'cq-agent': '{"os":"android","imei":"69b80588244673ed","osversion":"11","network":"none","version":"0.0.28.108","core":"1.6.4"}',
'orgid': '137',

}
res = requests.post("https://app-auth.iqilu.com/member/phonecode", json=data, verify=False, headers=headers)
print(res.json()) # {"code":1,"data":"z1SuIyAZmfWiLGnha/0MRVf8ji+QXdkXHjNiSVhVs9M="}

# 登录
code=input('请输入您的验证码:')
data2 = {
"phone": phone,
"code": code,
"key":res.json().get('data') ,
"password": "",
"captcha": "",
"captchaKey": ""}
res1 = requests.post('https://app-auth.iqilu.com/member/login', verify=False, headers=headers,json=data2)
print(res1.text)

# {"code":1,"data":{"orgid":137,"openid":"em-BZvxBrDdoGVgLsueGEQ","phone":"","password":"","salt":"","nickname":"网友c7f6857be8","avatar":"https://img11.iqilu.com/avatar.png","id":62180313,"isVirtual":0,"parentid":0,"groupid":0,"menu":["tv","radio","party","zhengwu","service","share","member","comment","osps","shop","signup","vote","snapshot","live","practice","invoice","healthy_two","govask_two","lottery_two"],"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJnaWQiOjAsInQiOjE3MDA3NDI1NzIsIm9wZW5pZCI6ImVtLUJadnhCckRkb0dWZ0xzdWVHRVEiLCJpc3MiOiJjcSIsIm5pY2tuYW1lIjoi572R5Y-LYzdmNjg1N2JlOCIsImF2YXRhciI6Imh0dHBzOi8vaW1nMTEuaXFpbHUuY29tL2F2YXRhci5wbmciLCJ0aW1lIjoxNzAwNzQyNTcyLCJvcmdpZCI6MTM3LCJwbGF0Zm9ybSI6ImNodWFuZ3FpIn0.wzYw2cDVpyaeUNMpLytRaKR6FaW8OQlFfau6uuFnMH4","memberToken":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJnaWQiOjAsInQiOjE3MDA3NDI1NzIsIm9wZW5pZCI6ImVtLUJadnhCckRkb0dWZ0xzdWVHRVEiLCJpc3MiOiJjcSIsIm5pY2tuYW1lIjoi572R5Y-LYzdmNjg1N2JlOCIsImF2YXRhciI6Imh0dHBzOi8vaW1nMTEuaXFpbHUuY29tL2F2YXRhci5wbmciLCJ0aW1lIjoxNzAwNzQyNTcyLCJvcmdpZCI6MTM3fQ.oskij2pQQ3tfWagN31c_KMabNqEZYr-JCwLt8zzZkp4","totalScore":50,"score":50,"needPassword":true,"islock":false,"isslient":false,"has_set_invite":1,"invite_code":"110QMX","invite_url":"https://app.iqilu.com/share/aS0xMzctNjIxODAzMTM.html"}}

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

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

# 后期做爬虫,批量操作,很有可能对手机唯一id做限制,这个东西,咱们一般自动生成

# 方案一:简单方案--》如果它真的要验证这个码是否正确,可能会不正确
"".join(random.choices('0123456789abcdef', k=15))

# 方案二:
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

1.5 接码平台

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1 刚刚收短信,是自己手机收的---》后期做爬虫--》可能需要大量接收验证码
# 2 可以借助于接码平台--》他们会给我们提供手机号---》我们向它的手机号发送短信---》我们调用它的api--》获取收到的验证码---》这样就可以完全自动实现登录或注册



# 3 这个东西违法---》国内搜不到--》也不允许用
# 4 只能用国外的
-免费的---》非常不稳定---》不一定有中国手机号
-收费的--》稳定--》价格高--》很多骗子网站--》骗你充钱--》其实它没有接码的功能

# 5 免费收费接码平台大全
https://w3h5.com/post/619.html





image-20240517154807940

image-20240517154816719

2 抓包和逆向案例–爱安丘新版本

2.1 目标

1
# 手机号密码登录

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
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
82
83
84
85
86
87
88
89
# 1 登录包
请求地址:
https://app-auth.iqilu.com/member/login?e=1
请求方式:
post
请求头:
encrypt 1
version 1.0.5
orgid 137
User-Agent null chuangqi.o.137.com.iqilu.app137/1.0.5
platform android
imei 69b80588244673ed
CQ-AGENT
cq-token
Content-Type
Content-Length
Host
Connection
Accept-Encoding gzip
Cookie
请求体:
{
"codeKey": "",
"password": "Y8rl9EvasBy6rozuyUytsw==", # 密码是加密的--》明显不知道加密方式是什么
"code": "",
"phone": "18953675221",
"key": ""
}

# 2 破解密码加密方式--》反编译apk--》读java代码--》用python复现出加密

# 3 jadx打开,把新版爱安丘 拖入进去,根据关键字搜索
-根据关键字搜索:password "password" "password
-优先用url搜索:会唯一 member/login

# 4 看到如下---》java代码你们看不懂---》现在只需要听
@Headers({"encrypt:1"})
@POST("member/login")
Call<ApiResponse<UserEntity>> login(@Body Map<String, String> map);
# 5 查找用例 login 查找login谁用了,右键--》查找用例,发现一条---》双击过来
# 6 看到代码
public void login(Map<String, String> map, BaseCallBack<ApiResponse<UserEntity>> baseCallBack) {
requestData(ApiLogin.getInstance().login(map), baseCallBack);
}
# 7 找到谁调用了 login 函数,传入了map--》在上面的login 上点查找用例

# 8 找到了
public void login(String str, String str2, String str3, String str4, String str5) {
HashMap hashMap = new HashMap();
hashMap.put("phone", str); # map中添加手机号
hashMap.put("code", str2);# map中添加code,空的
if (TextUtils.isEmpty(str3)) {
str3 = "";
}
hashMap.put("key", str3); # key是空的
if (TextUtils.isEmpty(str4)) {
str4 = "";
}
hashMap.put("codeKey", str4); # map添加codekey,抓包是空的
hashMap.put(Constants.Value.PASSWORD, TextUtils.isEmpty(str5) ? "" : EncryptUtil.aesPassword(str5));
LoginRepository.getInstance().login(hashMap, new BaseCallBack<ApiResponse<UserEntity>>()

# 9 逻辑就是,如果密码不为空,就使用EncryptUtil.aesPassword(str5) 对密码加密

# 10 继续查看加密逻辑,跳到aesPassword的声明
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);
}

#11 通过EncryptUtils.encryptAES 加密后,转了base64--》猜测到是aes加密
-aes加密需要确定的:
-明文是什么
-秘钥是什么
-iv
-加密模式
# 12 调用EncryptUtils.encryptAES(str明文密码,用md5加密的东西,加密模式,0000000000000000)
-点到函数内部发现:第一个参数就是明文,第二个参数是秘钥,第三个参数是加密模式,第四个参数是iv

# 13 一般aes加密,秘钥和iv是固定的,咱们可以看出iv是 0000000000000000
-目前秘钥不知道:咱们可以通过hook,查看它的返回值,返回值直接当秘钥

# 14 通过hook知道了
-明文是什么 lqz12345
-秘钥是什么 6d6656a37cdb7977c10f6d83cab168e9
-iv 0000000000000000
-加密模式 AES/CBC/PKCS7Padding

# 15 python复现密码加密

image-20240517154835715

image-20240517154846444

image-20240517154858089

image-20240517154908000

2.3 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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()

### 手机端启动frida-server
adb shell
su
cd /data/local/tmp/
./frida-server-16.1.7-an
### 端口转发
import subprocess
subprocess.getoutput("adb forward tcp:27042 tcp:27042")
subprocess.getoutput("adb forward tcp:27043 tcp:27043")

### 运行hook脚本,得到结果

'''
明文: c058579161250b3748dce77cf43eb6c3APP137
md5加密结果= 6d6656a37cdb7977c10f6d83cab168e9 aes加密的秘钥

'''

2.4 python实现密码加密

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

import random
# pip3 install pycryptodome
from Crypto.Cipher import AES
import base64

###爱安丘密码加密逻辑
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__':
# print(generate_imei2())
# print(generate_imei())

# 密码抓包抓到的:Y8rl9EvasBy6rozuyUytsw==
res=encrypt_data('lqz1234567') # Y8rl9EvasBy6rozuyUytsw== 跟抓包抓到一样
print(res)

2.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
import requests
from utils import encrypt_data,generate_imei,header_str_to_dict

session = requests.session()
imei = generate_imei() # 随机生成imei
header_s = '''
encrypt 1
version 1.0.5
orgid 137
User-Agent null chuangqi.o.137.com.iqilu.app137/1.0.5
platform android
imei 69b80588244673ed
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
Content-Length 93
Host app-auth.iqilu.com
Connection Keep-Alive
Accept-Encoding gzip
Cookie redirectToken=85cdb948d8fc42af90ddd2c21124aad8-95719440; redirect=
''' % 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)
#{"code":1,"data":{"orgid":137,"openid":"1wGndnMFYCAMrItDGH8pyg","phone":"","nickname":"网友06615538f5","avatar":"https://img11.iqilu.com/avatar.png","id":62118901,"isVirtual":0,"parentid":0,"groupid":0,"menu":["tv","radio","party","zhengwu","service","share","member","comment","osps","shop","signup","vote","snapshot","live","practice","invoice","healthy_two","govask_two","lottery_two"],"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJnaWQiOjAsInQiOjE3MDA3NDU2NTQsIm9wZW5pZCI6IjF3R25kbk1GWUNBTXJJdERHSDhweWciLCJpc3MiOiJjcSIsIm5pY2tuYW1lIjoi572R5Y-LMDY2MTU1MzhmNSIsImF2YXRhciI6Imh0dHBzOi8vaW1nMTEuaXFpbHUuY29tL2F2YXRhci5wbmciLCJ0aW1lIjoxNzAwNzQ1NjU0LCJvcmdpZCI6MTM3LCJwbGF0Zm9ybSI6ImNodWFuZ3FpIn0.G9nIkr7YS9uG7zdZwCjnu1r-1tNWs8OM73D9uUeemX4","memberToken":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJnaWQiOjAsInQiOjE3MDA3NDU2NTQsIm9wZW5pZCI6IjF3R25kbk1GWUNBTXJJdERHSDhweWciLCJpc3MiOiJjcSIsIm5pY2tuYW1lIjoi572R5Y-LMDY2MTU1MzhmNSIsImF2YXRhciI6Imh0dHBzOi8vaW1nMTEuaXFpbHUuY29tL2F2YXRhci5wbmciLCJ0aW1lIjoxNzAwNzQ1NjU0LCJvcmdpZCI6MTM3fQ.8z1BonCa6ps9lIcXewhDOqdc3QB4Z6XZQRDGZDB9UTk","totalScore":50,"score":50,"needPassword":false,"islock":false,"isslient":true,"has_set_invite":1,"invite_code":"10ZF91","invite_url":"https://app.iqilu.com/share/aS0xMzctNjIxMTg5MDE.html"}}

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82

import random
# pip3 install pycryptodome
from Crypto.Cipher import AES
import base64
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

def generate_imei2():
return "".join(random.choices('0123456789abcdef', k=15))


## 请求头字符串--》转字典
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__':
# print(generate_imei2())
# print(generate_imei())

# 密码抓包抓到的:Y8rl9EvasBy6rozuyUytsw==
# res=encrypt_data('lqz1234567') # Y8rl9EvasBy6rozuyUytsw== 跟抓包抓到一样
# print(res)

header_s = '''
encrypt 1
version 1.0.5
orgid 137
User-Agent null chuangqi.o.137.com.iqilu.app137/1.0.5
platform android
imei 69b80588244673ed
CQ-AGENT {"os":"android","brand":"google","imei":"%asdfasd","osversion":"11","network":"wifi","version":"1.0.5","core":"2.2.1.1"}
cq-token
Content-Type application/json; charset=UTF-8
Content-Length 93
Host app-auth.iqilu.com
Connection Keep-Alive
Accept-Encoding gzip
Cookie redirectToken=85cdb948d8fc42af90ddd2c21124aad8-95719440; redirect=
'''

res=header_str_to_dict(header_s)
print(res)

3 抓包和逆向案例–X大夫

3.1 目标

1
# 登录

3.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
49
50
51
52
53
#1 登录请求包
-请求地址:
https://api.niaodaifu.cn/v4/site/loginnew
-请求头:
没有特殊
-请求方式:
post
-请求体:
#devisetoken 18071adc020e7a14fe5 # 可能需要破,可以不带
password lqz1234567 #密码
mobile 18953675221 #手机号
channel android # 固定的
sign 7b92b8fbdd234a97a0ca9fa80eb569a9 #很像签名,必须破
time 1700747108 # 时间戳
mechanism 0 # 固定
platform 1 # 固定的

# 2 目标--》破解sign
# 3 反编译apk--》jadx打开--》搜索 "sign" 搜到很多,连猜测加看,找到了
requestParams.put("sign", SafeUtils.getSign(currentTimeMillis));
# 4 SafeUtils.getSign(currentTimeMillis)) 源码
# currentTimeMillis 当前毫秒级时间戳--》long类型
# long currentTimeMillis = System.currentTimeMillis() / 1000;

public static String getSign(long j) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
String substring = HexDump.toHex(messageDigest.digest(("niaodaifu" + j).getBytes())).substring(12, 30);
String substring2 = HexDump.toHex(messageDigest.digest((channel + j).getBytes())).substring(12, 26);
return substring + substring2;
} catch (Exception unused) {
return "";
}
}
# 5 核心逻辑
substring=转16进制(md5(niaodaifu+时间戳毫秒))[12:30]
substring2=转16进制(md5(android+时间戳毫秒))[12:26]

# 6 python 实现 sign

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

image-20240517154929792

image-20240517154937969

3.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)
# password=lqz1234567&mobile=18953675221&channel=android&sign=7b92b8fbdd234a97a0ca9fa80eb569a9&time=1700747108&mechanism=0&platform=1
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) # 转成字典类型
print(data)
res = requests.post('https://api.niaodaifu.cn/v4/site/loginnew', json=data, verify=False)
print(res.text)


4 抓包和逆向案例–合伙人

4.1 目标

1
# 登录

4.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
49
50
51
52
53
54
55
# 1 登录包
-请求地址
https://chinayltx.com/app/api/v1/partnerLogin/login
-请求头:
X-App native
X-Noncestr 123456
X-OS partnerApp_android
X-Req-Time 1700748495950
X-Sign #需要破 ,把它去掉,发现不行,必须要破
X-Token
X-UserID
-请求方式:
post
-请求体:
phone 18953675221
password 166acc691782f077c5c7c7c10fa39b1c # 需要破 很像md5或sha1加密


# 2 破解目标
X-Sign
密码加密

# 3 破密码--》反编译--搜索--》已经知道密码是md5
-搜索:partnerLogin/login
# 4 登录位置:--》查找用例
@FormUrlEncoded
@POST("api/v1/partnerLogin/login")
Observable<HttpResult<LoginInfo>> submitLogin(@Field("phone") String str, @Field("password") String str2);


# 5 找到
public Observable<HttpResult<LoginInfo>> loginWithToken(String str, String str2) {
return this.networkApi.submitLogin(str, str2) # str就是手机号,str2就是密码
}
# 6 str2哪里来的?别的位置调用loginWithToken 传入的第二个参数
-查找loginWithToken用例

# 7 找到
protected Observable<HttpResult<LoginInfo>> buildObservable() {
return this.mRepository.loginWithToken(this.name, this.pwd);
}

# 8 this.pwd 如何来的
public void setPwd(String str) {
this.pwd = str;
}
# 9 找谁调用了 setPwd --》查找用例
public void submitLogin(String str, String str2) {
this.mLoginUseCase.setName(str);
this.mLoginUseCase.setPwd(Md5.md5(str2));
this.mLoginUseCase.execute(new LoginSubscriber(this.view));
}
# 10 找到了是 md5加密,确认了


image-20240517155006303

image-20240517155014436

4.3 x-sign破解

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
# 1 搜索 "X-Sign
private static final String PARAM_SIGN = "X-Sign";
# 2 查找常量的用例,右键查找用例搜不到,就全局搜,或者在当前页面中找
# 3 找到位置
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是如何赋值的
this.sign = sign(sb.toString());
# 5 看sign()
private String sign(String str) {
return Md5.md5(this.token + this.reqTime + this.noncestr.substring(2) + str).toLowerCase();
}


# 6 需要做的---》通过hook得到
this.token 是什么 空
this.reqTime # 时间戳
this.noncestr.substring(2) #123456[2:]
str传入的 phone=手机号&password=密文密码


# 7 python实现 sign的签名
import hashlib
import time
token = ""
reqTime = str(int(time.time() * 1000))
nonce_str = "123456"
nonce_str_sub_2 = nonce_str[2:]
body_string = "phone=18953675221&password=166acc691782f077c5c7c7c10fa39b1c"

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)

4.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)

5 Java 介绍

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
# 咱么发现--》反编译apk后--》有大量的java代码---》代码看不懂---》以后破解就非常有难度---》接下来几天--》学习java
-java的逻辑要看懂,复杂的java加密---》可以借助于chatgpt--》让它写--》自己手调

# java是编译型语言---》区别于python--》它需要编译

# java体系
-java se:java基础--》如果做java web方向开发和 安卓开发,这个必学
-java ee:java web方向--》只有做web方向开发,才需要学它,咱们安卓开发,安卓逆向不需要学它
-如果你找java工作---》90%都是javaee工程师
-SpringBoot,springCloud--》java web
-java me:过时了,java手机方向开发,但不是安卓, 山寨机---》打开一个应用 --->弹出java咖啡图标


# java概念
-向下包含:jdk包含jre,jre包含jvm

-jdk:Java Development Kit ,java集成开发工具包--》java开发者必须装这个,才能做java开发
-jre:Java Runtime Environment 即Java运行环境--》如果只想运行java应用--》只需要装它即可
但是,好多人运行java应用,也装jdk
-jvm:java虚拟机,java的字节码文件,必须运行在jvm上

-正因为jvm的存在,java代码才夸平台,一处编码处处运行
-可以在不同平台(win,mac,linux)安装不同平台的jvm,java字节码文件就可以运行在不同平台



# 跨平台
-跨平台的意思是--》我们使用编程语言写的代码,可以无缝的运行在多种操作系统平台(win,mac,linux)
-你之前在win上的软件,那到mac上运行不了,这些软件用 c,c++写的,不支持夸平台
-jadx既能在win上运行,又能在mac上运行,是用java写的,可以夸平台


-正常来讲--》编译型语言,都不能夸平台
-早些年,没有java时候,只有c,c++这些语言--》他们写的软件--》编译完后,只能在当前平台运行
-java是编译型语言的一个特例,它可以夸平台
-本质原理就是因为 java编译后的代码,不能直接运行在操作系统上,不是可执行文件,是个中间态---》这个编译后的东西,只能运行在jvm上---》因为不同平台装了不同平台的jvm---》java编译后的代码,夸平台

-c,c++编译后的代码---》就是可执行文件---》可执行文件分平台--》需要在不同平台运行

-python 解释性语言,不需要编译---》运行在python解释器上--》不同平台装解释器--》可以天然夸平台
-所有解释型语言---》天然夸平台 nodejs,php,python 天然夸平台


-go:编译型语言,强大之处--》跨平台编译
-在win上能编译出linux,mac的可执行文件

-docker,k8s,区块链,号称高并发,特别适合写后端服务
-go抢占的是java的市场,并没有太多python的市场
-时下国内 go 比较不错的
-go的生态差:2009年出的,十几年时间,go的第三方,基本上国内的
beego
gorm 国内人开发的


# java -version jdk已经装好了
java version "1.8.0_371"
Java(TM) SE Runtime Environment (build 1.8.0_371-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.371-b11, mixed mode)

__END__