如题本文主要讲某app登录算法还原并实现发包的全过程,此app非常适合小白入门时拿来练手,
一个无壳无混淆的app,软件链接我会放在文章最下面,建议先看一下文章后,再去下载上手。
首先将apk拖入jadx进行关键字LoginActivity搜索,然后进行分析得到下图代码, 
是手机号加密码的发包位置,其中可见手机号和密码都被传入了login,跟踪进去后,可见的相关代码如下图。 
这里login又调用的私有方法doLogin,将手机号和密码传给了UserService里面,我们进行追踪,得到如下图代码。

这里可以看到了密码登录参数,手机号和密码被传入了这里,密码被传进了getMD5后,加密后存入了params又传入了getRSAParams,然后传入request,先继续追踪getMD5,

是一个普通的MD5,用python进行一下复现,代码如下。
|
1
2
3
4
5
6
7
8
9
10
|
import
hashlib
def
get_md5(val):
m
=
hashlib.md5()
m.update(val.encode(
'utf-8'
))
result
=
m.hexdigest()
return
result
print
(get_md5(
"qwerty"
))
# 结果:d8578edf8458ce06fbc5bb76a58c5ca4
|
然后返回追进getRSAParams里面看到下图代码,一层base64加密,还有RSA加密。 
先用frida来对这个对象动刀子,得结果如下
|
1
2
3
4
5
6
7
|
*
getRSAParams
is
called, params: {password
=
d8578edf8458ce06fbc5bb76a58c5ca4, os
=
android, mobile
=
15536263522
, version
=
2.2
.
3
}
getRSAParams ret value
is
{data
=
eyJwYXNzd29yZCI6ImQ4NTc4ZWRmODQ1OGNlMDZmYmM1YmI3NmE1OGM1Y2E0Iiwib3MiOiJhbmRyb2lkIiwibW9iaWxlIjoiMTU1MzYyNjM1MjIiLCJ2ZXJzaW9uIjoiMi4yLjMifQ
=
=
,
sign
=
DmxjCCvf8aJnZNve4BQkcy6turIGzkE13DkIu9JSnJF7yUDp3ZUxANRnSn6
+
BCN2nEogZsHFOm0fzzTU
/
NMnEqijA8lklHoxZspzVOe6Hkp8jYRrzvf0PQIh25lEL2GGWSslgzEK710opNDoQUVHA95ArOv9FQN95HxZuj7ywio
=
,
timestamp
=
1687264102
}
*
|
传入之前的密码,python复现的MD5结果相同,传入getRSAParams后成了后面的三项,data sign 和 timestamp,然后这些数据和charles抓包得到的login数据相同。
需要先解决data,进行追踪得到下图代码。Base64加密与解密。

对其进行python复现得到以下代码,运行结果与frida打印信息相同,
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import
base64
import
json
def
encode(data, charset
=
'UTF-8'
):
datas
=
json.dumps(data,separators
=
(
','
,
':'
))
try
:
return
base64.b64encode(datas.encode(charset)).decode(charset)
except
Exception as e:
print
(
'Error'
, e)
return
None
data
=
{
"password"
:
"d8578edf8458ce06fbc5bb76a58c5ca4"
,
"os"
:
"android"
,
"mobile"
:
"15536263522"
,
"version"
:
"2.2.3"
}
result
=
encode(data)
print
(result)
#结果:eyJwYXNzd29yZCI6ImQ4NTc4ZWRmODQ1OGNlMDZmYmM1YmI3NmE1OGM1Y2E0Iiwib3MiOiJhbmRyb2lkIiwibW9iaWxlIjoiMTU1MzYyNjM1MjIiLCJ2ZXJzaW9uIjoiMi4yLjMifQ==
|
这里用frida进行hook,得到信息如下,可见这里是data与timestamp的拼接被传入了sign,被privateKey进行了加密,然后得到的sign。
|
1
2
3
|
sign
is
called, content: data
=
eyJwYXNzd29yZCI6ImQ4NTc4ZWRmODQ1OGNlMDZmYmM1YmI3NmE1OGM1Y2E0Iiwib3MiOiJhbmRyb2lkIiwibW9iaWxlIjoiMTU1MzYyNjM1MjIiLCJ2ZXJzaW9uIjoiMi4yLjMifQ
=
=
×tamp
=
1687276955
,
privateKey: MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMNGABIfN
+
iron2hbwB7mLK1Dm05V1qLZBILTDj7dypr
+
GJzQ9fk0V7gIIchpFG7pDQEXMbb2nj8VkNAIIDBaw7UY1h9n
+
sCBT
+
Xzz6BB2UxLBBMQVwOwv55tJkZ2YBcHFQDGz51HjxAonKJdHwGpjIp7bwdx375gybn2ic4qNuFAgMBAAECgYEAmcha7eqgASCKCx5DaMHtc2
+
bOPFblfcIjB1Rnd6L7mCxb
/
cOisutB2bCtykLW0LHAiAdYI5r87Ply3iJIF0yjU35I8aieDVmeaQXXQfpisimXLOmz6p4VlBzAkz493oXPEH81cHqbwnFkiFE3VVtHbCNoZqXlFWthIdae2kpjlECQQDyCMl09eyDBNGuzg1r4tAQ4CeZe7aCkEFwK2at76Raqz9NKrynBiZHsKLU3JedRm2eZ7JimUhsuKbbkS
/
mcxBrAkEAzop7
/
PyddSXGDFDECyuXtuEKyzzUvdGiyNmOexhSwTmTZ7QdQqe5p382yCQcY8RXxZ6W9CLjuukfa9I6Tcz
/
zwJAQbjpG318D8fLOHBzbIxWe36iwia51JJfcpoWc7zTIFvIAKhOOfyNgIISdULBWM
+
7DHyUD
/
oXlI4
/
oPe3zhgIqQJAQd3gFJnrDQTy19KZ8oYAaA30h0PrBG3qX
+
shiRgErCJUY
+
oIus0KY
+
Qp8EGz3A0tgJRGx6you17E6nmspksN
+
QJBAKhaBGeHqs0
+
Z5wtFcunuqc6hV7WlhBYCe5YSxBNSiaohXDr6nQwjiOY22Q3m8aInp9KS
+
lDwW0o4C58VARKyo8
=
sign ret value
is
Z5dDahgsBp06lh76v1fos8GSqX4HdcB
+
vJBJgw4qWN6wJ52T2LkX9e
/
WqtLrb
+
biSg10J23
/
f0KIQW57GFLIWEgLwQRWgKaacvJJVMtoeETAXhgMvYU9LWmjFzrtd1EuRLyHLVkWik7IbJM5mySllm
+
bG
/
nfcPvohXp6ATRqy7k
=
|
然后sign的python还原,生成的结果与frida的hook结果相同,sign完成,然后就是将已经实现的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
|
from
Crypto.
Hash
import
SHA
from
Crypto.Signature
import
pkcs1_15
from
Crypto.PublicKey
import
RSA
import
base64
def
sign(content, private_key):
private_key_value
=
base64.b64decode(private_key)
key
=
RSA.import_key(private_key_value)
# 计算 SHA-1 哈希值
hash_value
=
SHA.new(content.encode(
'utf-8'
))
# RSA 的钥长度为 1024 位
k
=
1024
# 计算最大签名长度,根据 Java 的实现方式计算
em_len
=
k
/
/
4
h_len
=
20
t_len
=
3
s_len
=
em_len
-
h_len
-
t_len
-
1
# 进行签名
signature_value
=
pkcs1_15.new(key).sign(hash_value)[:s_len]
# 返回 Base64 编码的结果
return
base64.b64encode(signature_value).decode()
content
=
"data=eyJwYXNzd29yZCI6ImQ4NTc4ZWRmODQ1OGNlMDZmYmM1YmI3NmE1OGM1Y2E0Iiwib3MiOiJhbmRyb2lkIiwibW9iaWxlIjoiMTU1MzYyNjM1MjIiLCJ2ZXJzaW9uIjoiMi4yLjMifQ==×tamp=1687276955"
private_key
=
'MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMNGABIfN+iron2hbwB7mLK1Dm05V1qLZBILTDj7dypr+GJzQ9fk0V7gIIchpFG7pDQEXMbb2nj8VkNAIIDBaw7UY1h9n+sCBT+Xzz6BB2UxLBBMQVwOwv55tJkZ2YBcHFQDGz51HjxAonKJdHwGpjIp7bwdx375gybn2ic4qNuFAgMBAAECgYEAmcha7eqgASCKCx5DaMHtc2+bOPFblfcIjB1Rnd6L7mCxb/cOisutB2bCtykLW0LHAiAdYI5r87Ply3iJIF0yjU35I8aieDVmeaQXXQfpisimXLOmz6p4VlBzAkz493oXPEH81cHqbwnFkiFE3VVtHbCNoZqXlFWthIdae2kpjlECQQDyCMl09eyDBNGuzg1r4tAQ4CeZe7aCkEFwK2at76Raqz9NKrynBiZHsKLU3JedRm2eZ7JimUhsuKbbkS/mcxBrAkEAzop7/PyddSXGDFDECyuXtuEKyzzUvdGiyNmOexhSwTmTZ7QdQqe5p382yCQcY8RXxZ6W9CLjuukfa9I6Tcz/zwJAQbjpG318D8fLOHBzbIxWe36iwia51JJfcpoWc7zTIFvIAKhOOfyNgIISdULBWM+7DHyUD/oXlI4/oPe3zhgIqQJAQd3gFJnrDQTy19KZ8oYAaA30h0PrBG3qX+shiRgErCJUY+oIus0KY+Qp8EGz3A0tgJRGx6you17E6nmspksN+QJBAKhaBGeHqs0+Z5wtFcunuqc6hV7WlhBYCe5YSxBNSiaohXDr6nQwjiOY22Q3m8aInp9KS+lDwW0o4C58VARKyo8='
signature
=
sign(content, private_key)
print
(signature)
#Z5dDahgsBp06lh76v1fos8GSqX4HdcB+vJBJgw4qWN6wJ52T2LkX9e/WqtLrb+biSg10J23/f0KIQW57GFLIWEgLwQRWgKaacvJJVMtoeETAXhgMvYU9LWmjFzrtd1EuRLyHLVkWik7IbJM5mySllm+bG/nfcPvohXp6ATRqy7k=
|

先实现上图的login,实现手机号与密码的输入,传入parems,形成相同的login。
代码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import
hashlib
def
login(mobile, word):
params
=
{
"password"
: get_md5(word),
"os"
:
"android"
,
"mobile"
: mobile,
"version"
:
"2.2.3"
,
}
return
params
def
get_md5(val):
m
=
hashlib.md5()
m.update(val.encode(
'utf-8'
))
result
=
m.hexdigest()
return
result
mobile
=
input
(
"请输入16手机号:"
)
parem
=
input
(
"请输入密码:"
)
params
=
login(mobile,parem)
print
(params)
#运行结果:{'password': 'a7026e07535acfd00f0a8a4a97992291', 'os': 'android', 'mobile': '13685446446', 'version': '2.2.3'}
|
接着实现下图代码:

代码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
def
jiaparams(params):
# 将参数转换为 JSON 字符串,并进行 Base64 编码
params_json
=
json.dumps(params,separators
=
(
','
,
':'
))
print
(
"json转成功:"
,params_json)
data
=
base64.b64encode(params_json.encode(
'utf-8'
)).decode(
'utf-8'
)
print
(
"data成功:"
,data)
# 对加密后的参数进行签名
signstr
=
f
"data={data}×tamp={times()}"
print
(
"时间戳:"
,times())
print
(
"拼接成功"
,signstr)
signss
=
sign(signstr, pey)
#sign加密的地方
print
(
"sign成功"
,signss)
# 创建一个空字典用于存储加密后的参数
result
=
{}
result[
'data'
]
=
data
result[
'sign'
]
=
signss
result[
'timestamp'
]
=
times()
return
result
|
最后代码拼接一起,得到下面代码:
|
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
import
requests
import
json
import
time
import
hashlib
from
Crypto.
Hash
import
SHA
from
Crypto.Signature
import
pkcs1_15
from
Crypto.PublicKey
import
RSA
import
base64
pey
=
'MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMNGABIfN+iron2hbwB7mLK1Dm05V1qLZBILTDj7dypr+GJzQ9fk0V7gIIchpFG7pDQEXMbb2nj8VkNAIIDBaw7UY1h9n+sCBT+Xzz6BB2UxLBBMQVwOwv55tJkZ2YBcHFQDGz51HjxAonKJdHwGpjIp7bwdx375gybn2ic4qNuFAgMBAAECgYEAmcha7eqgASCKCx5DaMHtc2+bOPFblfcIjB1Rnd6L7mCxb/cOisutB2bCtykLW0LHAiAdYI5r87Ply3iJIF0yjU35I8aieDVmeaQXXQfpisimXLOmz6p4VlBzAkz493oXPEH81cHqbwnFkiFE3VVtHbCNoZqXlFWthIdae2kpjlECQQDyCMl09eyDBNGuzg1r4tAQ4CeZe7aCkEFwK2at76Raqz9NKrynBiZHsKLU3JedRm2eZ7JimUhsuKbbkS/mcxBrAkEAzop7/PyddSXGDFDECyuXtuEKyzzUvdGiyNmOexhSwTmTZ7QdQqe5p382yCQcY8RXxZ6W9CLjuukfa9I6Tcz/zwJAQbjpG318D8fLOHBzbIxWe36iwia51JJfcpoWc7zTIFvIAKhOOfyNgIISdULBWM+7DHyUD/oXlI4/oPe3zhgIqQJAQd3gFJnrDQTy19KZ8oYAaA30h0PrBG3qX+shiRgErCJUY+oIus0KY+Qp8EGz3A0tgJRGx6you17E6nmspksN+QJBAKhaBGeHqs0+Z5wtFcunuqc6hV7WlhBYCe5YSxBNSiaohXDr6nQwjiOY22Q3m8aInp9KS+lDwW0o4C58VARKyo8='
url
=
'https://api.langshiyu.com/user/v2/account/login'
def
md5(pwd_str):
md5_obj
=
hashlib.md5()
md5_obj.update(pwd_str.encode(
'utf-8'
))
return
md5_obj.hexdigest()
def
times():
return
str
(
int
(time.time()))
def
sign(content, private_key):
private_key_value
=
base64.b64decode(private_key)
key
=
RSA.import_key(private_key_value)
# 计算 SHA-1 哈希值
hash_value
=
SHA.new(content.encode(
'utf-8'
))
# RSA 的密钥长度为 1024 位
k
=
1024
# 计算最大签名长度,根据 Java 的实现方式计算
em_len
=
k
/
/
4
h_len
=
20
t_len
=
3
s_len
=
em_len
-
h_len
-
t_len
-
1
# 进行签名
signature_value
=
pkcs1_15.new(key).sign(hash_value)[:s_len]
# 返回 Base64 编码的结果
return
base64.b64encode(signature_value).decode()
def
login(mobile, param):
params
=
{
"password"
: md5(param),
"os"
:
"android"
,
"mobile"
: mobile,
"version"
:
"2.2.3"
,
}
return
params
def
jiaparams(params):
# 将参数转换为 JSON 字符串,并进行 Base64 编码
params_json
=
json.dumps(params,separators
=
(
','
,
':'
))
print
(
"json转成功:"
,params_json)
data
=
base64.b64encode(params_json.encode(
'utf-8'
)).decode(
'utf-8'
)
print
(
"data成功:"
,data)
# 对加密后的参数进行签名
signstr
=
f
"data={data}×tamp={times()}"
print
(
"时间戳:"
,times())
print
(
"拼接成功"
,signstr)
signss
=
sign(signstr, pey)
#sign加密的地方
print
(
"sign成功"
,signss)
# 创建一个空字典用于存储加密后的参数
result
=
{}
result[
'data'
]
=
data
result[
'sign'
]
=
signss
result[
'timestamp'
]
=
times()
return
result
mobile
=
input
(
"请输入16位手机号:"
)
parem
=
input
(
"请输入密码:"
)
params
=
login(mobile,parem)
paramss
=
jiaparams(params)
print
(
"发包内容:"
,paramss)
fb
=
requests.post(url, paramss)
print
(
"结果:"
,fb.text)bjsdm
#结果:{"code":0,"message":"该账户不存在","reqdata":{"data":0},"reqtime":"0.015238"}
|
有部分简单的代码实现,以及开头通过objection的SSL屏蔽后charles进行抓包进行找的关键点,都没有在文章里面写,不过这些都是不必要的,所以文章是有一小部分省略。
整体对小白非常友好的软件,难度不算很高,只要花时间就可以掌握关键点,进行复现。
app放在有道云文章里面,想尝试一下的小白可以进去拿一下。
算法还原过程有道云文章链接:
更多【入门级,无壳app登录算法还原并实现发包】相关视频教程:www.yxfzedu.com