├── 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 | 39 | 40 |
防御策略对抗策略
频率限制限定频率
行为权重随机行为
内容排重AI自动排重
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 | --------------------------------------------------------------------------------