风控基础概念(写给小白的入门指南)
为了更通俗地理解“风控”,我们可以把它想象成一家银行或超市的安保系统。它的核心目的就是区分正常顾客(真实用户)和捣乱分子(爬虫、黑产、羊毛党)。
技术风控通常从三个维度来“审视”一个用户:
1. 你是谁?(环境风控)
这是安保的第一道关卡,检查你的“身份证”和“交通工具”。
- IP地址(住址)
- 原理:IP就像你的家庭住址。
- 例子:如果安保发现一秒钟内有1000个人都从同一个“住址”(IP)挤进超市,那肯定有问题(可能是数据中心IP或代理IP)。
- 设备指纹(生物特征)
- 原理:就像人的指纹或DNA。即使你换了衣服(清除浏览器Cookie),你的身高、体重、虹膜(显卡型号、屏幕分辨率、电池电量等硬件信息)是很难改变的。
- 例子:网站发现一个账号虽然换了名字,但“指纹”和之前被封号的坏蛋一模一样,就会立刻警觉。
- 运行环境(伪装检测)
- 原理:检查你是不是在“穿玩偶服”或者是“机器人”。
- 例子:手机上的模拟器、电脑上的无头浏览器(Headless Chrome,一种没有界面的浏览器),就像是穿着人类衣服的机器人,很容易被识别出来。
2. 你在做什么?(行为风控)
这是安保在监控摄像头里看你的表现。
- 访问频率(手速)
- 原理:正常人看网页需要时间阅读。
- 例子:如果你一秒钟打开了50个商品页面,或者24小时不睡觉一直在刷新,这显然是机器人(爬虫)的手速。
- 操作轨迹(动作)
- 原理:人类操作鼠标和触摸屏是有惯性和抖动的。
- 例子:机器人的鼠标移动往往是笔直的直线,点击位置非常精确;而人类的手会抖动,轨迹是弯曲的。
- 访问逻辑(行为逻辑)
- 原理:正常人逛淘宝是:首页 -> 搜索 -> 详情页 -> 评价。
- 例子:爬虫往往为了省事,直接“瞬移”到详情页(直接请求API接口),跳过了前面的步骤,这就不符合人类逻辑。
3. 你在说什么?(代码与协议风控)
这是安保和你对“暗号”。
- 代码混淆(鬼画符)
- 原理:网站把给浏览器看的代码写得乱七八糟,变量名用
a,b,c或者乱码代替。 - 例子:本来代码写的是“点击按钮领取红包”,混淆后变成了“当X触发Y时执行Z函数”,让人类程序员很难读懂,增加破解难度。
- 原理:网站把给浏览器看的代码写得乱七八糟,变量名用
- 加密参数(对暗号)
- 原理:在发送请求时,必须带上一个经过特定算法计算的“签名”(Sign)或“令牌”(Token)。
- 例子:你想查快递信息,服务器问:“天王盖地虎?”如果你答不上来“宝塔镇河妖”(正确的加密结果),服务器就拒绝给你数据。
Web风控
Web风控技术介绍
- 浏览器指纹识别: 通过收集浏览器特征(Canvas、WebGL、字体列表、插件信息等)生成唯一标识
- TLS指纹识别: 分析TLS/SSL握手过程中的特征,识别自动化工具
- HTTP/2指纹: 检测HTTP/2协议的实现差异,区分真实浏览器和模拟请求
- JavaScript挑战: 通过复杂的JavaScript代码执行环境检测
- 鼠标轨迹分析: 分析鼠标移动、点击的时间和路径特征
- 键盘输入检测: 监测键盘输入的速度和节奏
- 页面行为分析: 追踪页面滚动、停留时间、元素交互等行为
如何阻止Web爬虫
部署WAF(Web应用防火墙)使用云WAF服务如Cloudflare、AWS WAF等:
- 配置IP黑白名单
- 设置访问频率限制
- 启用Bot管理功能
实施多层验证机制
- 首次访问时进行JavaScript环境检测
- 关键操作触发滑块验证或行为验证
- 异常流量触发图片验证码
使用反调试技术
jsx// 检测开发者工具 (function() { const devtools = /./; devtools.toString = function() { this.opened = true; } console.log('%c', devtools); if(devtools.opened) { window.location.href = 'about:blank'; } })(); // 禁用右键和F12 document.oncontextmenu = () => false; document.onkeydown = (e) => { if(e.key === 'F12') return false; };动态加密通信
jsx// 动态生成加密密钥 function encryptRequest(data) { const key = generateDynamicKey(); const encrypted = CryptoJS.AES.encrypt( JSON.stringify(data), key ).toString(); return { payload: encrypted, timestamp: Date.now(), sign: generateSignature(encrypted) }; }
如何突破Web风控
使用真实浏览器环境
- Selenium + undetected-chromedriver
- Puppeteer Stealth插件
- Playwright的反检测模式
pythonfrom undetected_chromedriver import Chrome from selenium.webdriver.common.by import By options = Chrome.ChromeOptions() options.add_argument('--disable-blink-features=AutomationControlled') driver = Chrome(options=options) # 修改webdriver属性 driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', { 'source': ''' Object.defineProperty(navigator, 'webdriver', { get: () => undefined }) ''' })模拟真实用户行为
pythonimport random import time def human_like_mouse_move(driver, element): # 模拟人类鼠标移动轨迹 action = ActionChains(driver) # 添加随机停顿和曲线移动 for _ in range(random.randint(3, 8)): action.move_by_offset( random.randint(-5, 5), random.randint(-5, 5) ) action.pause(random.uniform(0.01, 0.05)) action.move_to_element(element) action.click() action.perform() def human_like_typing(element, text): for char in text: element.send_keys(char) time.sleep(random.uniform(0.1, 0.3))使用代理池和浏览器指纹伪造
pythonfrom selenium_profiles.webdriver import Chrome from selenium_profiles.profiles import profiles # 使用随机浏览器配置文件 profile = profiles.Windows() driver = Chrome(profile=profile) # 配置代理 profile.proxy = { 'http': 'http://proxy:port', 'https': 'https://proxy:port' }逆向分析加密算法
- 使用Chrome DevTools分析网络请求
- 利用jadx、IDA Pro等工具逆向JavaScript
- hook关键函数获取加密参数
jsx// 使用Tampermonkey hook加密函数 (function() { const originalEncrypt = window.encryptFunction; window.encryptFunction = function(...args) { console.log('加密参数:', args); const result = originalEncrypt.apply(this, args); console.log('加密结果:', result); return result; }; })();
Android风控
Android风控技术介绍
- 设备指纹采集: 收集IMEI、Android ID、MAC地址、设备型号等硬件信息
- Root检测: 检测设备是否已Root,防止Hook和篡改
- 模拟器检测: 识别Genymotion、夜神、雷电等Android模拟器
- VPN/代理检测: 检测网络是否使用了VPN或代理
- 多开检测: 识别应用分身、多开软件
- Hook框架检测: 检测Xposed、Frida、VirtualXposed等Hook工具
- 内存检查: 检测内存中是否存在可疑进程或注入
- 签名校验: 验证APK签名,防止应用被二次打包
- 代码混淆: 使用ProGuard、R8等工具混淆代码
- SO加固: 对Native层代码进行加密和保护
如何阻止Android端攻击
实施Root检测
javapublic class RootDetection { public static boolean isDeviceRooted() { return checkRootMethod1() || checkRootMethod2() || checkRootMethod3(); } private static boolean checkRootMethod1() { String buildTags = android.os.Build.TAGS; return buildTags != null && buildTags.contains("test-keys"); } private static boolean checkRootMethod2() { String[] paths = { "/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su" }; for (String path : paths) { if (new File(path).exists()) return true; } return false; } private static boolean checkRootMethod3() { Process process = null; try { process = Runtime.getRuntime().exec(new String[]{"which", "su"}); BufferedReader in = new BufferedReader( new InputStreamReader(process.getInputStream()) ); return in.readLine() != null; } catch (Throwable t) { return false; } finally { if (process != null) process.destroy(); } } }模拟器检测
javapublic class EmulatorDetection { public static boolean isEmulator() { return (Build.FINGERPRINT.startsWith("generic") || Build.FINGERPRINT.startsWith("unknown") || Build.MODEL.contains("google_sdk") || Build.MODEL.contains("Emulator") || Build.MODEL.contains("Android SDK built for x86") || Build.MANUFACTURER.contains("Genymotion") || (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")) || "google_sdk".equals(Build.PRODUCT)); } private static boolean checkEmulatorFiles() { String[] known_files = { "/dev/socket/qemud", "/dev/qemu_pipe", "/system/lib/libc_malloc_debug_qemu.so", "/sys/qemu_trace" }; for (String file : known_files) { if (new File(file).exists()) { return true; } } return false; } }检测Hook框架
javapublic class HookDetection { public static boolean isXposedActive() { try { throw new Exception(); } catch (Exception e) { for (StackTraceElement stackTraceElement : e.getStackTrace()) { if (stackTraceElement.getClassName().contains("de.robv.android.xposed")) return true; } } // 检查已安装应用 List<ApplicationInfo> apps = context.getPackageManager() .getInstalledApplications(PackageManager.GET_META_DATA); for (ApplicationInfo app : apps) { if (app.packageName.equals("de.robv.android.xposed.installer") || app.packageName.equals("com.saurik.substrate")) { return true; } } return false; } public static boolean isFridaRunning() { String[] frida_servers = { "frida-server", "frida-agent", "gadget" }; try { Process process = Runtime.getRuntime().exec("ps"); BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream()) ); String line; while ((line = reader.readLine()) != null) { for (String server : frida_servers) { if (line.contains(server)) { return true; } } } } catch (Exception e) { e.printStackTrace(); } return false; } }使用加固服务
- 360加固保
- 腾讯乐固
- 梆梆加固
- 爱加密
SSL Pinning
java// 使用OkHttp实现证书固定 CertificatePinner certificatePinner = new CertificatePinner.Builder() .add("yourapi.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") .build(); OkHttpClient client = new OkHttpClient.Builder() .certificatePinner(certificatePinner) .build();
如何突破Android风控
使用Frida进行动态Hook
jsx// Frida脚本绕过Root检测 Java.perform(function() { var RootDetection = Java.use('com.example.RootDetection'); RootDetection.isDeviceRooted.implementation = function() { console.log('Root检测被调用,返回false'); return false; }; // Hook文件存在性检查 var File = Java.use('java.io.File'); File.exists.implementation = function() { var path = this.getAbsolutePath(); if (path.indexOf('su') >= 0 || path.indexOf('Superuser') >= 0) { console.log('拦截su文件检查: ' + path); return false; } return this.exists(); }; });使用VirtualXposed或太极
- 无需Root即可使用Xposed模块
- 在虚拟环境中运行目标应用
- 使用JustTrustMe等模块绕过SSL Pinning
模拟器伪装
python# 修改模拟器Build属性 adb shell su mount -o rw,remount /system vi /system/build.prop # 修改以下参数 ro.build.fingerprint=真实设备指纹 ro.product.manufacturer=Xiaomi ro.product.model=MI 10 ro.build.tags=release-keys绕过SSL Pinning
jsx// 使用Frida绕过OkHttp的证书固定 Java.perform(function() { var CertificatePinner = Java.use('okhttp3.CertificatePinner'); CertificatePinner.check.overload('java.lang.String', 'java.util.List') .implementation = function(hostname, peerCertificates) { console.log('绕过证书固定检查: ' + hostname); return; }; // 信任所有证书 var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager'); var SSLContext = Java.use('javax.net.ssl.SSLContext'); var TrustManager = Java.registerClass({ name: 'com.example.TrustManager', implements: [X509TrustManager], methods: { checkClientTrusted: function(chain, authType) {}, checkServerTrusted: function(chain, authType) {}, getAcceptedIssuers: function() { return []; } } }); });使用反编译和重打包
bash# 使用apktool反编译 apktool d app.apk -o app_decompiled # 修改smali代码,去除检测逻辑 # 编辑 app_decompiled/smali/com/example/RootDetection.smali # 重新打包 apktool b app_decompiled -o app_modified.apk # 签名 jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 \ -keystore my-release-key.keystore app_modified.apk alias_name
iOS风控
iOS风控技术介绍
- 越狱检测: 检测设备是否已越狱,防止运行时篡改
- 设备指纹: 收集IDFV、IDFA、设备型号等信息
- 代码签名验证: 验证应用签名,防止被修改
- 反调试: 检测是否被LLDB等调试器附加
- 反注入: 检测动态库注入(如Substrate、Frida)
- 完整性校验: 检查可执行文件和资源文件的完整性
- SSL Pinning: 证书固定防止中间人攻击
- 代码混淆: 使用LLVM混淆或商业混淆工具
- 字符串加密: 加密敏感字符串和配置信息
如何阻止iOS端攻击
越狱检测
swiftimport UIKit class JailbreakDetection { static func isJailbroken() -> Bool { return checkSuspiciousFiles() || checkSuspiciousApps() || checkWritePermission() || checkCydia() } private static func checkSuspiciousFiles() -> Bool { let paths = [ "/Applications/Cydia.app", "/Library/MobileSubstrate/MobileSubstrate.dylib", "/bin/bash", "/usr/sbin/sshd", "/etc/apt", "/private/var/lib/apt/" ] for path in paths { if FileManager.default.fileExists(atPath: path) { return true } } return false } private static func checkSuspiciousApps() -> Bool { guard let url = URL(string: "cydia://package/com.example.package") else { return false } return UIApplication.shared.canOpenURL(url) } private static func checkWritePermission() -> Bool { let testPath = "/private/jailbreak_test.txt" do { try "test".write(toFile: testPath, atomically: true, encoding: .utf8) try FileManager.default.removeItem(atPath: testPath) return true } catch { return false } } private static func checkCydia() -> Bool { let cydiaPath = "/Applications/Cydia.app" return FileManager.default.fileExists(atPath: cydiaPath) } }反调试保护
objectivec#import <sys/sysctl.h> #import <unistd.h> // 检测调试器 BOOL isDebuggerAttached() { struct kinfo_proc info; size_t info_size = sizeof(info); int name[4]; name[0] = CTL_KERN; name[1] = KERN_PROC; name[2] = KERN_PROC_PID; name[3] = getpid(); if (sysctl(name, 4, &info, &info_size, NULL, 0) == -1) { return NO; } return (info.kp_proc.p_flag & P_TRACED) != 0; } // 防止被ptrace附加 void disablePtrace() { #import <dlfcn.h> #import <sys/types.h> typedef int (*ptrace_ptr_t)(int _request, pid_t _pid, caddr_t _addr, int _data); void *handle = dlopen(NULL, RTLD_GLOBAL | RTLD_NOW); ptrace_ptr_t ptrace_ptr = dlsym(handle, "ptrace"); ptrace_ptr(31, 0, 0, 0); // PT_DENY_ATTACH = 31 dlclose(handle); }检测注入和Hook
swiftimport MachO class InjectionDetection { static func detectInjection() -> Bool { let suspiciousLibraries = [ "MobileSubstrate", "Substrate", "Frida", "FridaGadget", "libcycript" ] var count: UInt32 = 0 guard let images = _dyld_image_count() as UInt32? else { return false } for i in 0..<images { guard let imageName = _dyld_get_image_name(i) else { continue } let name = String(cString: imageName) for library in suspiciousLibraries { if name.contains(library) { return true } } } return false } }SSL Pinning实现
swiftimport Security class SSLPinning: NSObject, URLSessionDelegate { let certificates: [SecCertificate] init(certificates: [SecCertificate]) { self.certificates = certificates } func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { guard let serverTrust = challenge.protectionSpace.serverTrust else { completionHandler(.cancelAuthenticationChallenge, nil) return } let serverCertificates = (0..<SecTrustGetCertificateCount(serverTrust)).compactMap { SecTrustGetCertificateAtIndex(serverTrust, $0) } for serverCert in serverCertificates { for localCert in certificates { if serverCert == localCert { completionHandler(.useCredential, URLCredential(trust: serverTrust)) return } } } completionHandler(.cancelAuthenticationChallenge, nil) } }代码混淆和加密
- 使用Hikari LLVM混淆器
- 使用商业加固方案(如爱加密、梆梆)
- 字符串加密和函数名混淆
如何突破iOS风控
使用Frida绕过越狱检测
jsx// Frida脚本绕过越狱检测 if (ObjC.available) { // Hook文件存在性检查 var NSFileManager = ObjC.classes.NSFileManager; Interceptor.attach(NSFileManager['- fileExistsAtPath:'].implementation, { onEnter: function(args) { var path = ObjC.Object(args[2]).toString(); if (path.indexOf('Cydia') >= 0 || path.indexOf('MobileSubstrate') >= 0 || path.indexOf('/bin/bash') >= 0) { console.log('拦截文件检查: ' + path); args[2] = ObjC.classes.NSString.stringWithString_('/tmp'); } } }); // Hook canOpenURL var UIApplication = ObjC.classes.UIApplication; Interceptor.attach(UIApplication['- canOpenURL:'].implementation, { onEnter: function(args) { var url = ObjC.Object(args[2]).toString(); if (url.indexOf('cydia') >= 0) { console.log('拦截URL检查: ' + url); this.hooked = true; } }, onLeave: function(retval) { if (this.hooked) { retval.replace(0); } } }); // Hook sysctl检测调试器 var sysctl = Module.findExportByName(null, 'sysctl'); Interceptor.attach(sysctl, { onLeave: function(retval) { var info = Memory.readPointer(this.context.x2); var p_flag = Memory.readU32(info.add(8)); if ((p_flag & 0x800) !== 0) { // P_TRACED Memory.writeU32(info.add(8), p_flag & ~0x800); } } }); }使用Objection自动化绕过
bash# 安装Objection pip3 install objection # 注入应用 objection --gadget "com.example.app" explore # 绕过越狱检测 ios jailbreak disable # 绕过SSL Pinning ios sslpinning disable # Hook方法 ios hooking watch method "-[ClassName methodName:]"重签名和注入
bash# 使用insert_dylib注入动态库 git clone https://github.com/Tyilo/insert_dylib.git cd insert_dylib make # 解密应用(如果需要) # 使用frida-ios-dump或Clutch # 注入FridaGadget ./insert_dylib @executable_path/FridaGadget.dylib \ Payload/App.app/App \ --output Payload/App.app/App_patched # 复制FridaGadget到应用目录 cp FridaGadget.dylib Payload/App.app/ # 重签名 codesign -f -s "iPhone Developer: Your Name" Payload/App.app # 打包 zip -r app_modified.ipa Payload绕过SSL Pinning
jsx// 使用Frida绕过SSL Pinning if (ObjC.available) { // Method 1: Hook NSURLSession var NSURLSession = ObjC.classes.NSURLSession; Interceptor.attach( NSURLSession['- URLSession:didReceiveChallenge:completionHandler:'].implementation, { onEnter: function(args) { var completionHandler = new ObjC.Block(args[4]); var credential = ObjC.classes.NSURLCredential.credentialForTrust_(args[3]); completionHandler.implementation(0, credential); // NSURLSessionAuthChallengeUseCredential } } ); // Method 2: Hook SecTrustEvaluate var SecTrustEvaluate = Module.findExportByName('Security', 'SecTrustEvaluate'); Interceptor.replace(SecTrustEvaluate, new NativeCallback(function(trust, result) { Memory.writeU8(result, 1); // kSecTrustResultProceed return 0; // errSecSuccess }, 'int', ['pointer', 'pointer'])); }使用越狱检测绕过工具
- A-Bypass: 通用越狱检测绕过工具
- Liberty Lite: Cydia插件,绕过越狱检测
- Shadow: 隐藏越狱状态
- FlyJB X: 绕过越狱检测
风控对抗总结
攻防对抗的持续演进
- 防御方: 需要持续更新检测策略,采用多层防御,结合设备指纹、行为分析、机器学习等技术
- 攻击方: 需要深入理解系统机制,掌握逆向分析、动态调试、代码注入等技术
- 平衡点: 风控策略需要在安全性和用户体验之间找到平衡,过度的防护会影响正常用户
最佳实践建议
- 采用纵深防御策略,不要依赖单一防护手段
- 重要数据和逻辑放在服务端处理
- 定期更新和升级防护机制
- 建立监控和告警体系,及时发现异常
- 对敏感操作进行二次验证
- 使用专业的安全加固服务
- 遵守相关法律法规,合理使用风控技术