好友
阅读权限 10
听众
最后登录 1970-1-1
工具准备:Pixel XL // 该软件不支持模拟器 雷电APP(脱壳 用,不会脱壳,哭) frIDA
本贴仅作技术交流,如有侵权请在联系我立即删帖
符合360加固的特征,用雷电脱个壳,扔jadx编译一下
尝试一下抓包,抓到两个数据包
只有下面这一个数据包和登录有关
[Asm] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
POST /login_jsonp_active.do HTTP/1.1
common
: {
"uniqueCode"
:
"dd67d0c0-01e5-492d-8b3b-c35c5fa6ba48"
,
"appId"
:
"com.zcool.community"
,
"channel"
:
"zcool"
,
"mobileType"
:
"android"
,
"versionCode"
:4638}
BaseInfo: {
"uniqueCode"
:
"dd67d0c0-01e5-492d-8b3b-c35c5fa6ba48"
,
"appId"
:
"com.zcool.community"
,
"channel"
:
"zcool"
,
"mobileType"
:
"android"
,
"versionCode"
:4638}
Content-
Type
: application/x-www-form-urlencoded
Content-
Length
: 624
Host: passport.zcool.com.cn
Connection: Keep-Alive
Accept-Encoding: gzip
Cookie: HWWAFSESID=a3b2973ef5454da521; HWWAFSESTIME=1741081260467
User-Agent: okhttp/3.12.0
app=android&key=dTdlMmtGQjZIQk45NEN3dTIzamdVb2xYMVp5dDZnYmgrMGRlK2xjbkpwZjJpWGczb1FhYWxUVUx1%250AaVJ1d01ZeG5mZUpXZzUvT0M0WQpuYnF1NG0wdjZEd0NaSldPMkZGQ1loRmZ5NG1PM1dTMmhGd3d0%250AbVNhaG9vMVRBbitzQWRrZVBubWZxNGt6ajIyUHJpdEVxUGhHM0tKCmMzWmExU2FXWDJ0VjA0S0NE%250AL3owNmNOUEUrMjRwcExDR0VqSVViU2RyVDU2a0RmS0dqOFhKYmNyYjljM0lqbE9IWm5Rajl5UmM1%250AdnMKTDlFc2xLaVJNRU5rUVJ6RnVaN1k0OVBPTklkeVpsYUJwaG5UTlpkTy9DcmJDcmhvTjRZUUM2%250AcjZiSU4xcTdieHlIOTBxNjYyT0RCNwpNZVhzQVcwbjI3eEtUTHVDWmxhQnBoblROWmRPL0NyYkNy%250AaG9Od0dTRVo1aGxrNlAzMHpYOFFIaTJRZEhjM2JBQy9ESC9iQXBidko1CnUybDluN3FOYkFaT1ZO%250AajNaYnlHSjJrVmdBPT0KP2tleUlkPTE%253D%250A
看那么长的数据先从 hashmap下手,hook hashmap,因为是登录逻辑,可以打印一下username或者password的字段堆栈
[JavaScript] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Java.perform(
function
() {
function
showStacks() {
console.log(
Java.use(
"android.util.Log"
)
.getStackTraceString(
Java.use(
"java.lang.Throwable"
).$
new
()
)
);
}
var
HashMap = Java.use(
'java.util.HashMap'
);
HashMap.put.implementation =
function
(a, b) {
if
(a.equals(
"username"
)) {
showStacks();
console.log(
"hashMap.put :"
, a, b);
}
console.log(
"put: "
, a, b);
return
this
.put(a, b);
}
}
根据这个堆栈信息去寻找登录加密函数,把没用的东西去掉,看看这几个函数
[Asm] 纯文本查看 复制代码
1
2
3
4
5
6
7
8
java.lang.Throwable
at
com.zcool.community
.data
.api.PassportApi.signIn(PassportApi.java:138)
at
com.zcool.community.module.session.pwdsignin.PwdSigninViewProxy.signin(PwdSigninViewProxy.java:61)
at
com.zcool.community.module.session.pwdsignin.PwdSigninViewFragment
$
Content.onSubmitClick(PwdSigninViewFragment.java:189)
at
com.zcool.community.module.session.pwdsignin.PwdSigninViewFragment
$
Content.access
$
000(PwdSigninViewFragment.java:83)
at
com.zcool.community.module.session.pwdsignin.PwdSigninViewFragment
$
Content
$
1.onClick(PwdSigninViewFragment.java:136)
at
com.zcool.inkstone.util.ViewUtil.lambda
$
onClick
$
0(ViewUtil.java:46)
at
com.zcool.inkstone.util.-$$Lambda
$
ViewUtil
$
AQ3e0vrll-kI-Unlrb6ud-SUkjg.accept(Unknown Source:4)
搜一下 login_jsonp 这个地址先
进去是个接口,定义了一个 signIn 方法
再去寻找一下刚刚堆栈的第一个方法,恰好也是 sigin,使用的就是上图接口
稍微分析了一下 str 是用户名 str2 是用户密码,这个 str3 是处理第三方登录的如qq,微信,测试用的账号密码登录,不管这个从 str2 跳出去找到 PASSWORD_KEY = “password”,石锤了是密码字段,可能为了防止搜明文搜出来吧
然后就是调用方法 buildAndSetKeyParams 进行数据加密,加密完成时候,发起网络请求[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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
Map<String, String> createBaseParams = createBaseParams();
buildAndSetKeyParams(createBaseParams, createBaseKeyParams);
createBaseParams.put(NotificationCompat.CATEGORY_SERVICE,
"https://www.zcool.com.cn"
);
createBaseParams.put(
"appLogin"
,
"https://www.zcool.com.cn/tologin.do"
);
return
this
.mApiInterface.onKeyLoginWithToken(createBaseParams, createBaseHeaders()).map(
new
Function<NetSignInInfo, TrustedResponse<TrustedSignInInfo>>() {
[url=home.php?mod=space&uid=
1892347
]
@Override
[/url]
public
TrustedResponse<TrustedSignInInfo> apply(
@io
.reactivex.annotations.NonNull NetSignInInfo netSignInInfo)
throws
Exception {
com.zcool.community.data.api.entity.net.NetResponse netResponse =
new
com.zcool.community.data.api.entity.net.NetResponse();
netResponse.data = netSignInInfo.toTrustedSignInInfo();
if
(((TrustedSignInInfo) netResponse.data).result) {
netResponse.code =
0
;
}
else
{
netResponse.code = -
1
;
}
Log.d(
"TAG"
,
"response.code:"
+ netResponse.code);
netResponse.msg = ((TrustedSignInInfo) netResponse.data).msg;
return
netResponse.toTrustedResponse(
new
Converter<TrustedSignInInfo, TrustedSignInInfo>() {
@Override
public
TrustedSignInInfo convert(TrustedSignInInfo trustedSignInInfo) {
return
trustedSignInInfo;
}
});
}
}).map(
new
Function<TrustedResponse<TrustedSignInInfo>, TrustedResponse<TrustedSignInInfo>>() {
@Override
public
TrustedResponse<TrustedSignInInfo> apply(
@io
.reactivex.annotations.NonNull TrustedResponse<TrustedSignInInfo> trustedResponse)
throws
Exception {
if
(trustedResponse.code ==
0
&& trustedResponse.data.userId >
0
) {
if
(!TextUtils.isEmpty(trustedResponse.data.SERVER_COOKIE_V1)) {
CookiesHelper.addPassportServerCookieV1(trustedResponse.data.SERVER_COOKIE_V1);
}
else
{
Timber.e(
"sign in success, but cookie not found"
,
new
Object[
0
]);
new
IllegalAccessError(
"SERVER_COOKIE_V1 not found"
).printStackTrace();
}
}
return
trustedResponse;
}
});
进入buildAndSetKeyParams 函数,这个是继承父类的方法,父类方法中只有一句[Asm] 纯文本查看 复制代码
1
map.put(
"key"
,EncryptManager.getInstance().encrypt(map2));
在进入找到 EncryptManager.getInstance().encrypt(map2) ,根据最下面的图 EncryptManager.getInstance() 等效于 LazyInstance.access$100(); 等效于 LazyInstance.get() 返回结果为 private static final EncryptManager instance = new EncryptManager() 这个名为 instance 的 EncryptManager 类
绕了一圈,创建了一个EncryptManager 类 调用这个类的 encrypt 方法,传入map2,map是空的,map2包含用户的各种信息。hook一下这个方法,查看出入值,返回值和抓包的值是否有一致的部分[JavaScript] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
Java.perform(
function
() {
function
showStacks() {
console.log(
Java.use(
"android.util.Log"
)
.getStackTraceString(
Java.use(
"java.lang.Throwable"
).$
new
()
)
);
}
var
EncryptManager = Java.use(
"com.zcool.community.data.api.encrypt.EncryptManager"
);
EncryptManager[
"encrypt"
].implementation =
function
(map) {
console.log(`EncryptManager.encrypt is called: map=${map}`);
let
result =
this
[
"encrypt"
](map);
console.log(`EncryptManager.encrypt result=${result}`);
return
result;
};
});
处理一下着两份数据,不能说大差不差,只能说完全一样,只有间隔符不一样返回的数值的间隔符是 %0A ,抓包的是 %250A,而发包是要使用url编码,%25在url编码中就是%这些东西将密文分成了 76 字符一组。[Asm] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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
result=
dTdlMmtGQjZIQk45NEN3dTIzamdVb2xYMVp5dDZnYmgrMGRlK2xjbkpwZjJpWGczb1FhYWxUVUx1
%0A
aVJ1d01ZeG5mZUpXZzUvT0M0WQpuYnF1NG0wdjZEd0NaSldPMkZGQ1loRmZ5NG1PM1dTMmhGd3d0
%0A
bVNhaG9vMVRBbitzQWRrZVBubWZxNGt6ajIyUHJpdEVxUGhHM0tKCmMzWmExU2FXWDJ0VjA0S0NE
%0A
L3owNmNOUEUrMjRwcExDR0VqSVViU2RyVDU2a0RmS0dqOFhKYmNyYjljM0lqbE9IWm5Rajl5UmM1
%0A
dnMKTDlFc2xLaVJNRU5rUVJ6RnVaN1k0OVBPTklkeVpsYUJwaG5UTlpkTy9DcmJDcmhvTjRZUUM2
%0A
cjZiSU4xcTdieHlIOTBxNjYyT0RCNwpNZVhzQVcwbjI3eEtUTHVDWmxhQnBoblROWmRPL0NyYkNy
%0A
aG9Od0dTRVo1aGxrNlAzMHpYOFFIaTJRZEhjM2JBQy9ESC9iQXBidko1CnUybDluN3FOYkFaT1ZO
%0A
ajNaYnlHSjJrVmdBPT0KP2tleUlkPTE
%3D
%0A
app=android&key=
dTdlMmtGQjZIQk45NEN3dTIzamdVb2xYMVp5dDZnYmgrMGRlK2xjbkpwZjJpWGczb1FhYWxUVUx1
%250A
aVJ1d01ZeG5mZUpXZzUvT0M0WQpuYnF1NG0wdjZEd0NaSldPMkZGQ1loRmZ5NG1PM1dTMmhGd3d0
%250A
bVNhaG9vMVRBbitzQWRrZVBubWZxNGt6ajIyUHJpdEVxUGhHM0tKCmMzWmExU2FXWDJ0VjA0S0NE
%250A
L3owNmNOUEUrMjRwcExDR0VqSVViU2RyVDU2a0RmS0dqOFhKYmNyYjljM0lqbE9IWm5Rajl5UmM1
%250A
dnMKTDlFc2xLaVJNRU5rUVJ6RnVaN1k0OVBPTklkeVpsYUJwaG5UTlpkTy9DcmJDcmhvTjRZUUM2
%250A
cjZiSU4xcTdieHlIOTBxNjYyT0RCNwpNZVhzQVcwbjI3eEtUTHVDWmxhQnBoblROWmRPL0NyYkNy
%250A
aG9Od0dTRVo1aGxrNlAzMHpYOFFIaTJRZEhjM2JBQy9ESC9iQXBidko1CnUybDluN3FOYkFaT1ZO
%250A
ajNaYnlHSjJrVmdBPT0KP2tleUlkPTE
%253D
%250A
encrypt就是一个关键方法了[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private
EncryptManager() {
KEYS.put(
"1"
,
"F#C@5IOBULR9L415C~ZX*97C"
);
KEYS.put(
"5"
,
"DB&T78AQF&W7T#@~LGP9YC~T"
);
KEYS.put(Constants.VIA_REPORT_TYPE_SHARE_TO_QQ,
"3D4BT10H4#DUQLXHJ*WLLN&B"
);
}
public
String encrypt(Map map) {
StringBuffer stringBuffer =
new
StringBuffer();
String json =
new
Gson().toJson(map);
stringBuffer.append(DESedeCoder.encode(json, KEYS.get(
"1"
)));
stringBuffer.append(
"?keyId="
);
stringBuffer.append(
"1"
);
String stringBuffer2 = stringBuffer.toString();
try
{
String encode = URLEncoder.encode(encryptBASE64(stringBuffer2.getBytes(
"UTF-8"
)),
"UTF-8"
);
Timber.v(
"encrypt %s->%s->%s"
, json, stringBuffer2, encode);
return
encode;
}
catch
(UnsupportedEncodingException e) {
e.printStackTrace();
return
null
;
}
}
既然进行了url编码,那打印出来的 %0A 就需要还原成换行符了,%3D 解码成 = 经过3DES加密之后的结果就是[Asm] 纯文本查看 复制代码
1
2
3
4
5
6
7
8
dTdlMmtGQjZIQk45NEN3dTIzamdVb2xYMVp5dDZnYmgrMGRlK2xjbkpwZjJpWGczb1FhYWxUVUx1
aVJ1d01ZeG5mZUpXZzUvT0M0WQpuYnF1NG0wdjZEd0NaSldPMkZGQ1loRmZ5NG1PM1dTMmhGd3d0
bVNhaG9vMVRBbitzQWRrZVBubWZxNGt6ajIyUHJpdEVxUGhHM0tKCmMzWmExU2FXWDJ0VjA0S0NE
L3owNmNOUEUrMjRwcExDR0VqSVViU2RyVDU2a0RmS0dqOFhKYmNyYjljM0lqbE9IWm5Rajl5UmM1
dnMKTDlFc2xLaVJNRU5rUVJ6RnVaN1k0OVBPTklkeVpsYUJwaG5UTlpkTy9DcmJDcmhvTjRZUUM2
cjZiSU4xcTdieHlIOTBxNjYyT0RCNwpNZVhzQVcwbjI3eEtUTHVDWmxhQnBoblROWmRPL0NyYkNy
aG9Od0dTRVo1aGxrNlAzMHpYOFFIaTJRZEhjM2JBQy9ESC9iQXBidko1CnUybDluN3FOYkFaT1ZO
ajNaYnlHSjJrVmdBPT0KP2tleUlkPTE=
ECB的填充模式,没有iv值,先将传入的字符串转换成byte的形式,再将传入的keyID=1作为密钥,看起来这个是一个标准的DESede/ECB加密
将原数据base64解码,去除掉拼接的 ?keyId=1, 得到密文,尝试DESede解码 [Asm] 纯文本查看 复制代码
1
2
3
4
5
6
7
u7e2kFB6HBN94Cwu23jgUolX1Zyt6gbh+0de+lcnJpf2iXg3oQaalTULuiRuwMYxnfeJWg5/OC4Y
nbqu4m0v6DwCZJWO2FFCYhFfy4mO3WS2hFwwtmSahoo1TAn+sAdkePnmfq4kzj22PritEqPhG3KJ
c3Za1SaWX2tV04KCD/z06cNPE+24ppLCGEjIUbSdrT56kDfKGj8XJbcrb9c3IjlOHZnQj9yRc5vs
L9EslKiRMENkQRzFuZ7Y49PONIdyZlaBphnTNZdO/CrbCrhoN4YQC6r6bIN1q7bxyH90q662ODB7
MeXsAW0n27xKTLuCZlaBphnTNZdO/CrbCrhoNwGSEZ5hlk6P30zX8QHi2QdHc3bAC/
DH
/bApbvJ5
u2l9n7qNbAZOVNj3ZbyGJ2kVgA==
?keyId=1
解密脚本 [JavaScript] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
const CryptoJS = require(
'crypto-js'
);
key =
"F#C@5IOBULR9L415C~ZX*97C"
data =
"u7e2kFB6HBN94Cwu23jgUolX1Zyt6gbh+0de+lcnJpf2iXg3oQaalTULuiRuwMYxnfeJWg5/OC4Ynbqu4m0v6DwCZJWO2FFCYhFfy4mO3WS2hFwwtmSahoo1TAn+sAdkePnmfq4kzj22PritEqPhG3KJc3Za1SaWX2tV04KCD/z06cNPE+24ppLCGEjIUbSdrT56kDfKGj8XJbcrb9c3IjlOHZnQj9yRc5vsL9EslKiRMENkQRzFuZ7Y49PONIdyZlaBphnTNZdO/CrbCrhoN4YQC6r6bIN1q7bxyH90q662ODB7MeXsAW0n27xKTLuCZlaBphnTNZdO/CrbCrhoNwGSEZ5hlk6P30zX8QHi2QdHc3bAC/DH/bApbvJ5u2l9n7qNbAZOVNj3ZbyGJ2kVgA=="
function
decodeDESede(data, key) {
var
_key = CryptoJS.enc.Utf8.parse(key);
var
decrypted = CryptoJS.TripleDES.decrypt(data, _key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}).toString(CryptoJS.enc.Utf8);
return
decrypted;
}
console.log(decodeDESede(data, key))
解密后的数据 [Asm] 纯文本查看 复制代码
1
2
3
4
5
6
7
解密后的数据:
{
"password"
:
"12345678"
,
"common"
:{
"uniqueCode"
:
"dd67d0c0-01e5-492d-8b3b-c35c5fa6ba48"
,
"appId"
:
"com.zcool.community"
,
"channel"
:
"zcool"
,
"mobileType"
:
"android"
,
"versionCode"
:4638},
"appLogin"
:
"https://www.zcool.com.cn/tologin.do"
,
"service"
:
"https://www.zcool.com.cn"
,
"appId"
:
"1006"
,
"username"
:
"13112345678"
}
再回头看put数据的方法,这里put了五项,但是map数值不是new的,说明 common字段以及提前构建好了 [Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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
public
Single<TrustedResponse<TrustedSignInInfo>> signIn(String str, String str2, String str3,
int
i) {
Map createBaseKeyParams = createBaseKeyParams();
createBaseKeyParams.put(
"appId"
, BaseApi.APP_ID);
createBaseKeyParams.put(NotificationCompat.CATEGORY_SERVICE,
"https://www.zcool.com.cn"
);
createBaseKeyParams.put(
"appLogin"
,
"https://www.zcool.com.cn/tologin.do"
);
createBaseKeyParams.put(
"username"
, str);
createBaseKeyParams.put(WifiEnterpriseConfig.PASSWORD_KEY, str2);
if
(!TextUtils.isEmpty(str3)) {
createBaseKeyParams.put(
"thirdId"
, str3);
createBaseKeyParams.put(
"siteId"
, Integer.valueOf(i));
}
Map<String, String> createBaseParams = createBaseParams();
buildAndSetKeyParams(createBaseParams, createBaseKeyParams);
return
this
.mApiInterface.signIn(createBaseParams, createBaseHeaders()).map(
new
Function<NetSignInInfo, TrustedResponse<TrustedSignInInfo>>() {
@Override
public
TrustedResponse<TrustedSignInInfo> apply(
@io
.reactivex.annotations.NonNull NetSignInInfo netSignInInfo)
throws
Exception {
com.zcool.community.data.api.entity.net.NetResponse netResponse =
new
com.zcool.community.data.api.entity.net.NetResponse();
netResponse.data = netSignInInfo.toTrustedSignInInfo();
if
(((TrustedSignInInfo) netResponse.data).result) {
netResponse.code =
0
;
}
else
{
netResponse.code = -
1
;
}
netResponse.msg = ((TrustedSignInInfo) netResponse.data).msg;
return
netResponse.toTrustedResponse(
new
Converter<TrustedSignInInfo, TrustedSignInInfo>() {
@Override
public
TrustedSignInInfo convert(TrustedSignInInfo trustedSignInInfo) {
return
trustedSignInInfo;
}
});
}
}).map(
new
Function<TrustedResponse<TrustedSignInInfo>, TrustedResponse<TrustedSignInInfo>>() {
@Override
public
TrustedResponse<TrustedSignInInfo> apply(
@io
.reactivex.annotations.NonNull TrustedResponse<TrustedSignInInfo> trustedResponse)
throws
Exception {
if
(trustedResponse.code ==
0
&& trustedResponse.data.userId >
0
) {
if
(!TextUtils.isEmpty(trustedResponse.data.SERVER_COOKIE_V1)) {
CookiesHelper.addPassportServerCookieV1(trustedResponse.data.SERVER_COOKIE_V1);
}
else
{
Timber.e(
"sign in success, but cookie not found"
,
new
Object[
0
]);
new
IllegalAccessError(
"SERVER_COOKIE_V1 not found"
).printStackTrace();
}
}
return
trustedResponse;
}
});
}
看common的构建,除了uniqueCode都是固定的,uniqueCode跟自己手机有关,这个字段不用管了
这样一来数据包传输的东西只有password和username是不固定的,其他的基本固定,可以直接拿数值构建,再来看请求头的构造 [Asm] 纯文本查看 复制代码
1
2
3
4
5
return
this
.mApiInterface.signIn(createBaseParams, createBaseHeaders()).map(new Function<NetSignInInfo, TrustedResponse<TrustedSignInInfo>>() {
}
// createBaseParams 是加密后的参数,createBaseHeaders()函数用于构造请求头
先获取令牌,由于是首次登录不存在令牌,在请求头添加 common 和 BaseInfo,内容相同
请求头的其他部分都是 OkHttp 默认添加的,也不存在时间戳等时间,开始简单仿造一个请求请求返回的数据经过gzip压缩,之前的请求头上以及表示可以接收gzip压缩了,可以写个判断,这里直接使用了,没判断[JavaScript] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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
const https = require(
'https'
);
const zlib = require(
'zlib'
);
const url =
'https://passport.zcool.com.cn/login_jsonp_active.do'
;
const headers = {
'Content-Type'
:
'application/x-www-form-urlencoded'
,
'Host'
:
'passport.zcool.com.cn'
,
'Connection'
:
'Keep-Alive'
,
'Accept-Encoding'
:
'gzip'
,
'Cookie'
:
'HWWAFSESID=a3b2973ef5454da521; HWWAFSESTIME=1741081260467'
,
'User-Agent'
:
'okhttp/3.12.0'
,
'common'
: {
"uniqueCode"
:
"dd67d0c0-01e5-492d-8b3b-c35c5fa6ba48"
,
"appId"
:
"com.zcool.community"
,
"channel"
:
"zcool"
,
"mobileType"
:
"android"
,
"versionCode"
:4638},
'BaseInfo'
: {
"uniqueCode"
:
"dd67d0c0-01e5-492d-8b3b-c35c5fa6ba48"
,
"appId"
:
"com.zcool.community"
,
"channel"
:
"zcool"
,
"mobileType"
:
"android"
,
"versionCode"
:4638}
};
const body = `app=android&key=dTdlMmtGQjZIQk45NEN3dTIzamdVb2xYMVp5dDZnYmgrMGRlK2xjbkpwZjJpWGczb1FhYWxUVUx1%250AaVJ1d01ZeG5mZUpXZzUvT0M0WQpuYnF1NG0wdjZEd0NaSldPMkZGQ1loRmZ5NG1PM1dTMmhGd3d0%250AbVNhaG9vMVRBbitzQWRrZVBubWZxNGt6ajIyUHJpdEVxUGhHM0tKCmMzWmExU2FXWDJ0VjA0S0NE%250AL3owNmNOUEUrMjRwcExDR0VqSVViU2RyVDU2a0RmS0dqOFhKYmNyYjljM0lqbE9IWm5Rajl5UmM1%250AdnMKTDlFc2xLaVJNRU5rUVJ6RnVaN1k0OVBPTklkeVpsYUJwaG5UTlpkTy9DcmJDcmhvTjRZUUM2%250AcjZiSU4xcTdieHlIOTBxNjYyT0RCNwpNZVhzQVcwbjI3eEtUTHVDWmxhQnBoblROWmRPL0NyYkNy%250AaG9Od0dTRVo1aGxrNlAzMHpYOFFIaTJRZEhjM2JBQy9ESC9iQXBidko1CnUybDluN3FOYkFaT1ZO%250AajNaYnlHSjJrVmdBPT0KP2tleUlkPTE%253D%250A`;
const options = {
hostname:
'passport.zcool.com.cn'
,
port: 443,
path:
'/login_jsonp_active.do'
,
method:
'POST'
,
headers: headers
};
const req = https.request(options, (res) => {
let
responseData =
''
;
const gunzip = zlib.createGunzip();
res.pipe(gunzip);
gunzip.on(
'data'
, (chunk) => {
responseData += chunk;
});
gunzip.on(
'end'
, () => {
console.log(
'响应数据:'
, responseData);
});
});
req.on(
'error'
, (error) => {
console.error(
'请求错误:'
, error);
});
req.write(body);
req.end();
和手机上结果相同
返回的数据是没有进行加密的,明文传输回来,就不需要写DESede解密方法了,写一个加密方法,完善这个请求 [JavaScript] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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
var
http = require(
'https'
);
const zlib = require(
'zlib'
);
const CryptoJS = require(
'crypto-js'
);
let
username =
"13112345678"
let
password =
"12345678"
let
date = `{
"password"
: ${password},
"common"
:{
"uniqueCode"
:
"dd67d0c0-01e5-492d-8b3b-c35c5fa6ba48"
,
"appId"
:
"com.zcool.community"
,
"channel"
:
"zcool"
,
"mobileType"
:
"android"
,
"versionCode"
:4638},
"appLogin"
:
"https://www.zcool.com.cn/tologin.do"
,
"service"
:
"https://www.zcool.com.cn"
,
"appId"
:
"1006"
,
"username"
: ${username}}`
function
decodeDESede(data, key) {
var
_key = CryptoJS.enc.Utf8.parse(key);
var
decrypted = CryptoJS.TripleDES.decrypt(data, _key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}).toString(CryptoJS.enc.Utf8);
return
decrypted;
}
function
encodeDESede(data, key) {
var
_key = CryptoJS.enc.Utf8.parse(key);
var
decrypted = CryptoJS.TripleDES.encrypt(data, _key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}).toString();
return
decrypted;
}
function
handleBody(body) {
var
result = body +
'\n'
+
'?keyId=1'
;
result = encodeURIComponent(btoa(result));
return
`app=android&key=${result}`;
}
console.log(handleBody(encodeDESede(date, key)))
function
post(result) {
const headers = {
'Content-Type'
:
'application/x-www-form-urlencoded'
,
'Host'
:
'passport.zcool.com.cn'
,
'Connection'
:
'Keep-Alive'
,
'Accept-Encoding'
:
'gzip'
,
'User-Agent'
:
'okhttp/3.12.1'
,
'common'
: {
"uniqueCode"
:
"dd67d0c0-01e5-492d-8b3b-c35c5fa6ba48"
,
"appId"
:
"com.zcool.community"
,
"channel"
:
"zcool"
,
"mobileType"
:
"android"
,
"versionCode"
:4638},
'BaseInfo'
: {
"uniqueCode"
:
"dd67d0c0-01e5-492d-8b3b-c35c5fa6ba48"
,
"appId"
:
"com.zcool.community"
,
"channel"
:
"zcool"
,
"mobileType"
:
"android"
,
"versionCode"
:4638}
}
var
options = {
hostname:
'passport.zcool.com.cn'
,
port: 443,
path:
'/login_jsonp_active.do'
,
method:
'POST'
,
headers: headers
}
var
req = http.request(options,
function
(res) {
var
body =
''
;
const gunzip = zlib.createGunzip();
res.pipe(gunzip);
gunzip.on(
'data'
,
function
(chunk) {
body += chunk;
});
gunzip.on(
'end'
,
function
() {
console.log(
'响应数据:'
, body)
});
});
req.on(
'error'
,
function
(e) {
console.log(
'problem with request:'
+ e.message);
})
req.write(result);
req.end();
}
post(handleBody(encodeDESede(date, key)))
这次没怎么hook(汗),hook代码 [JavaScript] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Java.perform(
function
() {
function
showStacks() {
console.log(
Java.use(
"android.util.Log"
)
.getStackTraceString(
Java.use(
"java.lang.Throwable"
).$
new
()
)
);
}
var
HashMap = Java.use(
'java.util.HashMap'
);
HashMap.put.implementation =
function
(a, b) {
if
(a.equals(
"username"
)) {
showStacks();
console.log(
"hashMap.put :"
, a, b);
}
console.log(
"put: "
, a, b);
return
this
.put(a, b);
}
var
EncryptManager = Java.use(
"com.zcool.community.data.api.encrypt.EncryptManager"
);
EncryptManager[
"encrypt"
].implementation =
function
(map) {
console.log(`EncryptManager.encrypt is called: map=${map}`);
let
result =
this
[
"encrypt"
](map);
console.log(`EncryptManager.encrypt result=${result}`);
return
result;
};
});
这个软件的登录挺简单的,标准的DESede加密,以及方法和密钥在明文里,没有so层的逆向,请求也比较简单,没有时间戳部分。
免费评分
参与人数 2 威望 +2
吾爱币 +101
热心值 +2
收起
理由
qinmou
+ 1
+ 1
我很赞同!
正己
+ 2
+ 100
+ 1
感谢发布原创作品,吾爱破解论坛因你更精彩!
查看全部评分