本文中所有内容仅供研究与学习使用,请勿用于任何商业用途和非法用途,否则后果自负!
app 版本:110.1(1827) 酷安下载
设备:Pixel 2XL Android 8.1
抓包工具:Charles + Postern VPX 抓包
反汇编工具:JADX 1.4.5、IDA Pro 8.3.0
hook:frida 12.8.0、frida-tools 5.3.0
静态分析
动态分析
网络流量分析
猜,你猜猜我猜猜

:method: GET
:path: /nc/api/v1/search/flow/comp?start=Kg%3D%3D&limit=20&q=NuS6uuWFseS6qzIwMjTmrKfmtLLmna%2Fph5HpnbQ%3D&deviceId=bvYT4UzsuBXNhfobtucjoWbhxxvqginXtZw7lCuhHuFXCnf2uGrW417TW%2FmVXqy1IIGNeE0nI41SFrBIaL1THA%3D%3D&version=newsclient.110.1.android&channel=c2VhcmNo&canal=UVFfbmV3c195dW55aW5nNA%3D%3D&dtype=0&tabname=zonghe&position=5pCc57Si5qGG6aKE572u6K%2BN&ts=1721110863&sign=W7Xphf%2BpqyOGN9JoXUHnuo0ikgGVNQynXl0Z9SFrPG148ErR02zJ6%2FKXOnxX046I&spever=FALSE
:authority: gw.m.163.com
:scheme: https
add-to-queue-millis: 1721110863718
data4-sent-millis: 1721110863719
cache-control: no-cache
user-agent: NewsApp/110.1 Android/8.1.0 (google/Pixel 2 XL)
x-nr-trace-id: 1721110863720_199869332_ODUwNjE0ZjAyOGJhZTNiYV9fZ29vZ2xlX1BpeGVsIDIgWEw%3D
x-nr-ise: 0
x-xr-original-host: gw.m.163.com
user-c: 5pCc57Si
user-rc: UjgzLZ+E4Lemnj+sMro9qwqQ3xlDp4PUECu18073DbE2Sp1cMm3KWoG4EVq0Iff0
user-d: bvYT4UzsuBXNhfobtucjoWbhxxvqginXtZw7lCuhHuFXCnf2uGrW417TW/mVXqy1IIGNeE0nI41SFrBIaL1THA==
user-vd: WfXzE4qBsQrzqoxcYqkdAh77TaqeHGwDc3c5MKUbPwL1PDEQtOAxYiJJj6XJo9TGePBK0dNsyevylzp8V9OOiA==
user-appid: TItcOwjV9bndQ91C5VadYg==
user-sid: jeYbWWG30X4+b4psq4KnvtJQ7bvpgJC2TvUpgWA0pfw=
user-lc: 67NqtW9W02z/qXjaEOOHag==
user-n: yEWDFuJGE3Gmj2a0IPdYcA==
user-id: rTboMPOe7X3a3PlAcfTomAyKsptKyhPdg7sH0emGPiqAQ4ozbxeRq4WEUnIhA/QejIuMU9rtRKIrUSa49DmGut4ZRL0MIJQQ8KOb5fiSUJuMVMtqqnzmaVeVeFXjZ10SePBK0dNsyevylzp8V9OOiA==
x-nr-ts: 1721110863728
x-nr-sign: 6db19e20ad7a9a890ca02e7a509ba00d
x-nr-net-lib: okhttp
accept-encoding: br,gzip
'start': 'Kg==',
'limit': '20',
'q': 'NuS6uuWFseS6qzIwMjTmrKfmtLLmna/ph5HpnbQ=',
'deviceId': 'bvYT4UzsuBXNhfobtucjoWbhxxvqginXtZw7lCuhHuFXCnf2uGrW417TW/mVXqy1IIGNeE0nI41SFrBIaL1THA==',
'version': 'newsclient.110.1.android',
'channel': 'c2VhcmNo',
'canal': 'UVFfbmV3c195dW55aW5nNA==',
'dtype': '0',
'tabname': 'zonghe',
'position': '5pCc57Si5qGG6aKE572u6K+N',
'ts': '1721110863',
'sign': 'W7Xphf+pqyOGN9JoXUHnuo0ikgGVNQynXl0Z9SFrPG148ErR02zJ6/KXOnxX046I',
'spever': 'FALSE',
多次抓包对比确定需要逆向的参数,这边参数实在太多了,将 app 拖入 jadx 等待反编译完成后逐个进行分析。
固定:'gw.m.163.com',
整数型 13 位时间戳:int(time.time() * 1000),
整数型 13 位时间戳:int(time.time() * 1000),
固定:'no-cache',
给了就好:'NewsApp/110.1 Android/8.1.0 (google/Pixel 2 XL)',
x-nr-trace-id: 1721110863720_199869332_ODUwNjE0ZjAyOGJhZTNiYV9fZ29vZ2xlX1BpeGVsIDIgWEw%3D
初步猜测:
整数型 13 位时间戳 + "" + "???" + "" + ODUwNjE0ZjAyOGJhZTNiYV9fZ29vZ2xlX1BpeGVsIDIgWEw%3D
在 Jadx 中查找,看看具体是如何生成的:

总计找到 37 处,着重查看 request 发包相关的:
定位到:GalaxyResponse a() 函数,代码有做删减,看到 X-NR-Trace-Id 是通过 this.f12927b 来的:
1
2
3
4
5
|
@Override / / com.netease.galaxy.net.IRequest
public GalaxyResponse a() throws Throwable {
OkHttpClient a2;
method.header( "X-NR-Trace-Id" , this.f12927b);
}
|
跟进 c(String str) 函数中:
1
2
3
|
private String c(String str ) {
return System.currentTimeMillis() + "_" + str + "_" + Galaxy.O(Galaxy.N());
}
|
从 c(String str) 函数中可以看出,和我们的初步猜测是一样的,str 为 String.valueOf(hashCode()) 也就是当前对象的哈希码(HashCode)的字符串形式,使用 frida 进行 hook:
1
2
3
4
5
6
7
8
9
|
Java.perform(function () {
var GalaxyRequest = Java.use( "com.netease.galaxy.net.GalaxyRequest" );
GalaxyRequest[ "c" ].implementation = function ( str ) {
console.log( 'c is called' + ', ' + 'str: ' + str );
var ret = this.c( str );
console.log( 'c ret value is ' + ret);
return ret;
};
})
|

据返回值可以发现:ODUwNjE0ZjAyOGJhZTNiYV9fZ29vZ2xlX1BpeGVsIDIgWEw%3D,字符一直是固定的,那么总结可得:
整数型 13 位时间戳 + "" + "hashCode()" + "" + ODUwNjE0ZjAyOGJhZTNiYV9fZ29vZ2xlX1BpeGVsIDIgWEw%3D
固定值:'0',
固定值:'gw.m.163.com',
在 Jadx 中查找 user-c,查看其具体是如何生成的,仅定位到两处,一眼出:

定位到:Request F1() 函数,代码有做删减,看到 User-C 是通过 URLEncoder.encode(StringUtil.e(o2, "UTF-8") 来的,User-U、User-D、User-N 三个参数都是通过Encrypt.getEncryptedParams(i2) 来的。
1、user-c
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
|
public static Request F1(String str , String str2) {
ArrayList arrayList = new ArrayList();
String d2 = Common.g().a().getData().d();
if (!TextUtils.isEmpty(d2)) {
arrayList.add(new Header( "User-U" , Encrypt.getEncryptedParams(d2)));
}
String s2 = SystemUtilsWithCache.s();
if (!TextUtils.isEmpty(s2)) {
arrayList.add(new Header( "User-D" , Encrypt.getEncryptedParams(s2)));
}
String i2 = NetUtil.i();
if (!TextUtils.isEmpty(i2)) {
try {
arrayList.add(new Header( "User-N" , Encrypt.getEncryptedParams(i2)));
} catch (Exception unused) {
}
}
String o2 = CommonGalaxy.o();
if (!TextUtils.isEmpty(o2)) {
try {
arrayList.add(new Header( "User-C" , URLEncoder.encode(StringUtil.e(o2, "UTF-8" ), "UTF-8" )));
} catch (UnsupportedEncodingException unused2) {
}
}
return BaseRequestGenerator.a(String. format (NGRequestUrls.PicSet.f23298a, str , str2), arrayList);
}
|
先分析 User-C 对 StringUtil.e(o2, "UTF-8") 返回的字符串进行 URL 编码,使用 UTF-8 字符集,写个 hook 代码,查看其编码的对象:
1
2
3
4
5
6
7
8
9
|
Java.perform(function () {
var StringUtil = Java.use( "com.netease.newsreader.support.utils.string.StringUtil" );
StringUtil[ "e" ].implementation = function ( str , str2) {
console.log( 'e is called' + ', ' + 'str: ' + str + ', ' + 'str2: ' + str2);
var ret = this.e( str , str2);
console.log( 'e ret value is ' + ret);
return ret;
};
})
|

反复进行抓包,其有两个值反复横跳:
user-c: 5pCc57S
user-c: 5aS05p2h
对其进行 base64 解码:
5pCc57S -> 搜索
5aS05p2h -> 头条
2、user-d、user-n、user-rc 等等
继续分析User-U、User-D、User-N ...... 等参数,跟进 getEncryptedParams() 方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public static String getEncryptedParams(String str , int i2) {
if (TextUtils.isEmpty( str )) {
return str ;
}
synchronized (sEncryptCache) {
Map map = sEncryptCache.get(i2);
if ( map ! = null && !TextUtils.isEmpty( map .get( str ))) {
return map .get( str );
}
String encryptedParamsInner = getEncryptedParamsInner( str , i2);
if ( map = = null) {
map = new HashMap<>( 2 );
sEncryptCache.put(i2, map );
}
map .put( str , encryptedParamsInner);
return encryptedParamsInner;
}
}
|
调用 getEncryptedParamsInner(str, i2) 方法,跟进查看:
1
2
3
|
private static String getEncryptedParamsInner(String str , int i2) {
return getBase64Str(callEncrypt(Core.context(), str , i2));
}
|
调用 callEncrypt 方法,跟进查看:
1
2
3
4
5
6
7
8
9
10
11
|
private static byte[] callEncrypt(Context context, String str , int i2) {
try {
return encrypt(context, str , i2);
} catch (Error e2) {
e2.printStackTrace();
return null;
} catch (Exception e3) {
e3.printStackTrace();
return null;
}
}
|
调用 encrypt 方法,跟进查看:
1 |
private static native synchronized byte[] encrypt(Context context, String str , int i2);
|
到 so 层了,定位其对应的 so 文件:
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2024-7-18 14:39
被行简编辑
,原因: