├── README.md
└── 抖音算法分析.md
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## 微信
3 | ### [iPad 协议](wechat/docs/ipad.md)
4 |
5 | ### [Android 设备HOOK](wechat/docs/android.md)
6 |
7 | ### [iOS 设备HOOK](wechat/docs/ios.md)
8 |
9 | ## 抖音
10 | ### [(协议版)新版 as、mas版本](aweme/docs/server-proto.md)
11 | ### [iOS版](aweme/docs/ios.md)
12 | #### [3.4.0](aweme/docs/ios-3.4.0.md)
13 | ### Android版本
14 | #### [3.4.0版](aweme/docs/android-3.4.0.md)
15 | ### 核心关键点:
16 | #### 静态特征:
17 | * 注册号段黑名单
18 | * 注册代理IP黑名单问题
19 | * 设备信息可信度
20 | * 验证码(深度学习识别验证码+人工打码)
21 |
22 | #### 动态特征:
23 |
24 |
25 | 防御策略 |
26 | 对抗策略 |
27 |
28 |
29 | 频率限制 |
30 | 限定频率 |
31 |
32 |
33 | 行为权重 |
34 | 随机行为 |
35 |
36 |
37 | 内容排重 |
38 | AI自动排重 |
39 |
40 |
41 |
42 | ## 合作
43 | ### 联系方式
44 | Telegram: @bitkey
45 | Email: bitkey007@hotmail.com
46 |
--------------------------------------------------------------------------------
/抖音算法分析.md:
--------------------------------------------------------------------------------
1 | ### 参考
2 | http://www.jintiankansha.me/t/cWVQOCYoxx
3 |
4 | ### 参考项目
5 | https://github.com/CrawlData/douyin-sign
6 | https://github.com/HackAppSign/douyin-sign/blob/master/analysis.md
7 |
8 |
9 |
10 | ### android移动加固
11 | ollvm
12 | https://github.com/obfuscator-llvm/obfuscator/tree/llvm-4.0
13 |
14 |
15 | ### 算法逆向
16 | https://bbs.ichunqiu.com/forum.php?mod=collection&action=view&ctid=116
17 |
18 |
19 | ### android APK分析
20 | libcms.so
21 | 利用IDA静态分析加上inline hook技术,我们找到了getUserInfo的native实现函数sub_26750(0x26750是函数的相对so文件起始位置的偏移)
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | 抖音协议分析
30 | ==========
31 |
32 | > 由于只使用 as, cp的 libuserinfo.so老版本算法逻辑都能从网上查到, 这里就不做分析了
33 |
34 | ## 版本信息
35 |
36 | [v3.1.0](https://share.weiyun.com/5nuuGx4), 算法为新版 as, cp, mas算法
37 |
38 | ## 逆向过程
39 |
40 | ### JAVA层面
41 |
42 | 众所周知, 抖音v2版本的算法签名为 as, cp, mas, 使用AK/Jadx打开目标apk, 找到签名逻辑, 在 `com.ss.android.ugc.aweme.app.api.b` 这个类中, 其中签名的逻辑
43 | 为
44 |
45 | ```java
46 | /**
47 | * 更多详细信息参见: http://hacksign.liebian.me
48 | */
49 | public static HttpUrl a(HttpUrl httpUrl, List list, int i) {
50 | if (PatchProxy.isSupport(new Object[]{httpUrl, list, new Integer(i)}, null, a, true, 4541, new Class[]{HttpUrl.class, List.class, Integer.TYPE}, HttpUrl.class)) {
51 | return (HttpUrl) PatchProxy.accessDispatch(new Object[]{httpUrl, list, new Integer(i)}, null, a, true, 4541, new Class[]{HttpUrl.class, List.class, Integer.TYPE}, HttpUrl.class);
52 | }
53 | String decode = URLDecoder.decode(httpUrl.toString());
54 | String c = d.c();
55 | String str = "";
56 | if (decode.contains("&device_id=") || decode.contains("?device_id=")) {
57 | if (TextUtils.isEmpty(c)) {
58 | c = (String) c(decode).get(x.u);
59 | }
60 | str = UserInfo.getUserInfo(i, (String[]) list.toArray(new String[list.size()]), null, c);
61 | } else {
62 | str = UserInfo.getUserInfo(i, (String[]) list.toArray(new String[list.size()]), null, "");
63 | }
64 | Builder newBuilder = httpUrl.newBuilder();
65 | int length = str.length();
66 | if (TextUtils.isEmpty(str)) {
67 | f.a(decode, (List) list, str, c, (long) i);
68 | newBuilder.addQueryParameter(AdvanceSetting.ADVANCE_SETTING, "a1iosdfgh").addQueryParameter("cp", "androide1");
69 | } else if (length % 2 == 0) {
70 | decode = str.substring(0, length >> 1);
71 | String substring = str.substring(length >> 1, length);
72 | str = "";
73 | a a = com.ss.sys.ces.f.b.a(GlobalContext.getContext(), (long) g.B().m());
74 | a.a(j.a());
75 | newBuilder.addQueryParameter(AdvanceSetting.ADVANCE_SETTING, decode).addQueryParameter("cp", substring).addQueryParameter("mas", k.a(a.a(decode.getBytes())));
76 | } else {
77 | f.a(decode, (List) list, str, c, (long) i);
78 | newBuilder.addQueryParameter(AdvanceSetting.ADVANCE_SETTING, "a1qwert123").addQueryParameter("cp", "cbfhckdckkde1");
79 | }
80 | return newBuilder.build();
81 | }
82 | ```
83 |
84 | 通过这段代码我们可以了解到如下内容
85 |
86 | > 调用 `com.ss.android.common.applog.UserInfo` 里的native 方法 `public static native String getUserInfo(int timestamp, String[] paramList, String[] strArr2, String deviceId)` 生成44位字符串, 前 22位给as, 后22位给cp
87 |
88 | 然后mas是调用 `com.ss.android.common.applog.k.a` 静态方法得来的, 参数为一个byte数组
89 |
90 | ```java
91 | package com.ss.android.common.applog;
92 | /**
93 | * 更多详细信息参见: http://hacksign.liebian.me
94 | */
95 | public class k {
96 | public static String a(byte[] bArr) {
97 | if (PatchProxy.isSupport(new Object[]{bArr}, null, a, true, 282, new Class[]{byte[].class}, String.class)) {
98 | return (String) PatchProxy.accessDispatch(new Object[]{bArr}, null, a, true, 282, new Class[]{byte[].class}, String.class);
99 | } else if (bArr == null) {
100 | return null;
101 | } else {
102 | char[] toCharArray = "0123456789abcdef".toCharArray();
103 | char[] cArr = new char[(bArr.length * 2)];
104 | for (int i = 0; i < bArr.length; i++) {
105 | int i2 = bArr[i] & 255;
106 | cArr[i * 2] = toCharArray[i2 >>> 4];
107 | cArr[(i * 2) + 1] = toCharArray[i2 & 15];
108 | }
109 | return new String(cArr);
110 | }
111 | }
112 | }
113 | ```
114 |
115 | 那么 `k.a(byte[] bArr)`的byte数组参数是哪里来的呢? 进一步发现是调用
116 |
117 | `com.ss.sys.ces.f.a` 这个接口的 `byte[] a(byte[] bArr)` 方法计算的, 该接口的实现类为 `com.ss.sys.ces.b`, 进而发现实际调用的是 `com.ss.sys.ces.a`类中的navtie方法 `public static native byte[] e(byte[] bArr)`, 实现逻辑在 `libcms.so` 中
118 |
119 |
120 | ### Native层面
121 |
122 | 辅助工具, inline hook:
123 | https://github.com/ele7enxxh/Android-Inline-Hook
124 |
--------------------------------------------------------------------------------