问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
改机过风控实现无限制使用某加速器软件
移动安全
本文站在风控的角度,找到风控的弱点,通过改机,让 App 不认识所使用的这台设备,从而实现无限制“薅羊毛”。
改机过风控实现无限制使用某加速器 ---------------- ### 风控是什么? 风控,其实就是字面意思,把这两个字拆开看就可以得到正确的解释,就是 风险 和 控制。 黑产们制造风险,从而获得相应的利润。 厂家们为了减少风险或尽量控制住风险,就有了风控这个概念的诞生。 ### 某加速器的软件的风控点在哪? 这是一款加速器 App。 当安装这款加速器 App 后,有两种方式可以使用这款 App。 第一种方式是:用户注册一个账户来使用,然后每天赠送一个小时的使用时间。 ![1.png](https://shs3.b.qianxin.com/attack_forum/2023/05/attach-82459daca932ab58dcc100430b99a60da41b1437.png) 第二种方式是:当安装这款 App 后,它会自动分配一个体验用户,并且也会赠送每天一个小时的使用时间。 ![2.png](https://shs3.b.qianxin.com/attack_forum/2023/05/attach-691cfeadb26eed187e1f188210fe280b4a850524.png) 从上面的分析得知,只要安装并且在不手动注册的情况下,就会分配一个体验用户,体验一个小时。 那么是不是只要卸载再重新安装,就会再体验一个小时呢? 依次类推,只要到期就卸载,然后一直用 App 赠送的体验时间,就可以达到永久免费使用此款加速器了呢? 经过卸载在安装后多次的尝试,发现并不能!而且我的信息,也就是我的用户ID,也不会随着卸载和重装而改变。 所以经过推理得知,用户ID 可能是随机生成的,但用户ID 一定是和使用的手机是绑定的关系。 所以最终得出的结果就是,这款 App 的风控点就在于识别本机设备。 ### 过某加速器App风控的思路 从上面的分析得知,这款 App 的风控点就在于识别本机设备。 也就是说,App 有一种方式或者几种方式可以识别出本机设备。 在人类社会中,如何识别出一个人呢?方式大概有以下几种:面部识别,声音识别,身份证识别,家庭地址识别,手机号码识别等等。 此时假设有一个场景:自然大灾难来临,一个人可以领一个馒头。那怎么样才能领两个馒头呢? 他只要具备以下几点:首先整容,整的面目全非让别人认不出自己;然后声音变一下发音方式;在做一个假的身份证件;最后谎报家庭地址和手机号码即可。但是他还是他,只是人们认识的“他”变了,他本身没有改变,吃两个馒头的也还是他。 而在手机世界中,想识别出一个手机的方式非常常用的方式有以下几种:硬件序列号,android\_id,IMEI,SIM 序列号,IMSI,WifiMAC。 过这款加速器的风控和大灾难来临,人如何领两个馒头是一样的。手机想办法改变 App 认识它的所有方式,但最终手机还是那个手机,只不过 App 已经不认识它了,这款加速器 App 会为它分配新的体验用户,也就是再次吃了本应该吃一次的“馒头”。 下面分别介绍如果更改手机身份信息,先只介绍思路,项目放在后面。 #### 更改身份之 - 改硬件序列号 硬件序列号写在了 android.os.Build.SERIAL 中,它是 Android 系统中定义的一个静态字符串常量,用于表示设备的硬件序列号。 该字符串是由设备制造商生成的,是唯一的,可以用来识别设备。 该值存储在 android.os.Build 类中,可以通过该类访问该值,安卓系统源码如下 ![3.JPG](https://shs3.b.qianxin.com/attack_forum/2023/05/attach-6850ae9bf4fe6b26fdb6c310856b8c0c11a60a23.jpg) 由于 SERIAL 是静态变量,Xposed 直接调用 setStaticObjectField 即可修改掉变量的值。 但是细心的话可以看到,SERIAL 的值是通过 getString("no such thing") 方法执行 跟踪方法,getString(String property) 方法内部调用了 SystemProperties.get() 方法。 ![4.JPG](https://shs3.b.qianxin.com/attack_forum/2023/05/attach-e933fe165bdbeecffb314d02504339357bd1389a.jpg) 再继续跟踪方法,发现 SystemProperties.get() 方法,调用了 native\_get() 方法,而 native\_get() 方法是一个 native 方法,已没有跟踪的必要。 ![5.JPG](https://shs3.b.qianxin.com/attack_forum/2023/05/attach-c4066e054ea7926b8941d0e9d6ad51c6975fba16.jpg) 所以即使修改了 android.os.Build.SERIAL 的值,App 也可以调用 SystemProperties.get() 方法重新获得一次值,所以要在修改 android.os.Build.SERIAL 的值的基础上,hook SystemProperties.get() 方法并修改其返回值。 #### 更改身份之 - 改 android\_id 用户首次设置设备时随机生成的 64 位数字(表示为十六进制字符串),对于应用签名密钥、用户和设备的每个组合都是唯一的。 android\_id 的值由签名密钥和用户限定。如果在设备上执行恢复出厂设置或 APK 签名密钥更改,则该值可能会更改。 简而言之,android\_id 是 Android 设备里不依赖于硬件的一种半永久标识符,在系统生命周期内不会改变,但系统重置或刷机后会发生变化。而实在没必要因为要过风控去重置或刷机,那就太麻烦。 并且根据以往逆向经验,核验用户身份时,现在 App 也常常使用 android\_id。所以更改 android\_id 是非常必要的。 android.provider.Settings 类的内部类 Secure 的 getString 方法可以获得 android\_id。 ```java String id= Settings.Secure.getString(this.getContentResolver(),Settings.Secure.ANDROID_ID); ``` 所以只需要 hook 住这个方法,修改掉返回值,即可改 android\_id。 #### 更改身份之 - 改 IMEI IMEI(International Mobile Equipment Identity,国际移动身份识别码),俗称“手机串号”存储在手机的EEPROM(俗称码片)里,每一个移动设备都对应唯一的 IMEI。用于在移动电话网络中识别每一部独立的手机,相当于移动电话的身份证。 序列号共有 15~17 位数字,前8位(TAC)是型号核准号码(早期为6位),是区分手机品牌和型号的编码。接着2位(FAC)是最后装配号(仅在早期机型中存在),代表最终装配地代码。后6位(SNR)是串号,代表生产顺序号。国际移动设备识别码一般贴于机身背面与外包装上,同时也存在于手机存储器中,通过在手机拨号键盘中输入 \*#06# 即可查询。 ![6.png](https://shs3.b.qianxin.com/attack_forum/2023/05/attach-d699b8c397f4845f3cd3caa1cef7b5b0a962d521.png) 获得 IMEI 的方法很简单,直接调用 android.telephony.TelephonyManager 的 getDeviceId 方法即可。 ```java TelephonyManager telephonyManager = (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE); String IMEI = telephonyManager.getDeviceId(); ``` 所以只需要 hook 住这个方法,修改掉返回值,即可改 IMEI。 #### 更改身份之 - 改 SIM 序列号 上面介绍的:硬件序列号、android\_id、IMEI 都是系统或手机自带的。 SIM 序列号并不是自带的,但是又有哪个人买手机不安装 SIM 卡呢,如果一个手机没有 SIM 卡也会显得很怪异。 SIM 序列号有唯一性,每个 SIM 卡序列号必须是唯一的,不能与其他 SIM 卡重复,所以只要 SIM 卡不更换,完全可以证明还是那部手机,也就是反向证明了手机设备的唯一性。 获得 SIM 序列号的方法也很简单,直接调用 android.telephony.TelephonyManager 的 getDeviceId 方法即可。 ```java TelephonyManager telephonyManager = (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE); String IMEI = telephonyManager.getSimSerialNumber(); ``` 所以也只需要 hook 住这个方法,修改掉返回值,即可改 SIM 序列号。 #### 更改身份之 - 改 IMSI 国际移动用户识别码(IMSI:International Mobile Subscriber Identification Number)是区别移动用户的标志,储存在 SIM 卡中,可用于区别移动用户的有效信息,所以更改 IMSI 是非常必要的。 IMSI 其总长度不超过 15 位,同样使用 0~9 的数字。其中 MCC 是移动用户所属国家代号,占 3 位数字,中国的 MCC 规定为 460 ;MNC 是移动网号码,由两位或者三位数字组成,中国移动的移动网络编码(MNC)为 00;用于识别移动用户所归属的移动通信网;MSIN 是移动用户识别码,用以识别某一移动通信网中的移动用户。 获得 IMSI 的方法也很简单,直接调用 android.telephony.TelephonyManager 的 getSubscriberId 方法即可。 ```java TelephonyManager telephonyManager = (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE); String IMEI = telephonyManager.getSubscriberId(); ``` 所以也只需要 hook 住这个方法,修改掉返回值,即可改 IMSI。 #### 更改身份之 - 改 WifiMAC MAC 地址是一种硬件标识符,可帮助在本地网络上找到设备,也就是 MAC 地址和手机是绑定的,修改 WifiMAC 地址肯定是必要的。 修改 WifiMAC 地址,就没有修改 IMEI 、SIM序列号、IMSI 这么简单了,不是一个 API 就能解决的。 因为不同版本查看 WifiMAC 地址的方式是不同的。 1.Android 6.0 之前通过 WifiInfo 获取。 ```java WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); WifiInfo wifiInfo = wifiManager.getConnectionInfo(); return wifiInfo.getMacAddress(); ``` hook 住 WifiInfo 的 getMacAddress() 方法,并修改掉返回值,即可改掉 WifiMAC。 2.Android 7.0 以后通过网络接口驱动 wlan0 获取。 7.0 以后就要麻烦很多了。首先获取网络接口,然后遍历网络接口,如果网络接口的名字是 wlan0 就获取它的值。 ```java try { //获取本机器所有的网络接口 Enumeration enumeration = NetworkInterface.getNetworkInterfaces(); while (enumeration.hasMoreElements()) { NetworkInterface networkInterface = (NetworkInterface) enumeration.nextElement(); //获取硬件地址,一般是MAC byte[] arrayOfByte = networkInterface.getHardwareAddress(); if (arrayOfByte == null || arrayOfByte.length == 0) { continue; } StringBuilder stringBuilder = new StringBuilder(); for (byte b : arrayOfByte) { //格式化为:两位十六进制加冒号的格式,若是不足两位,补0 stringBuilder.append(String.format("%02X:", b)); } if (stringBuilder.length() > 0) { //删除后面多余的冒号 stringBuilder.deleteCharAt(stringBuilder.length() - 1); } String str = stringBuilder.toString(); // wlan0:无线网卡 eth0:以太网卡 if (networkInterface.getName().equals("wlan0")) { return str; } } } catch (SocketException socketException) { return null; } ``` hook 它的话也比较麻烦,hook java.net.NetworkInterface 的 getHardwareAddress 方法,然后获取对象,进行主动调用 getName 普通方法,如果 getName 的值是 wlan0 ,就直接构建一个 byte 数组修改 getHardwareAddress 方法的返回值。 ### 改机完整项目 通过上面的分析,已经知道了每一个身份识别长什么样子,也知道如何修改它。 项目的结构:一共有两个主要文件,HookTest.java 里面是 hook 的主要逻辑。Utis.java 里面是根据规则,随机生成身份识别等字符串。 Utis.java 代码 ```java package com.bmstd.xposed1; import java.util.Random; public class Utis { // 生成 IMEI public static String getIMEI() { int r1 = 1000000 + new java.util.Random().nextInt(9000000); int r2 = 1000000 + new java.util.Random().nextInt(9000000); String input = r1 + "" + r2; char[] ch = input.toCharArray(); int a = 0, b = 0; for (int i = 0; i < ch.length; i++) { int tt = Integer.parseInt(ch[i] + ""); if (i % 2 == 0) { a = a + tt; } else { int temp = tt * 2; b = b + temp / 10 + temp % 10; } } int last = (a + b) % 10; if (last == 0) { last = 0; } else { last = 10 - last; } return input + last; } // 生成 IMSI public static String getImsi() { String title = "4600"; int second = 0; do { second = new java.util.Random().nextInt(8); } while (second == 4); int r1 = 10000 + new java.util.Random().nextInt(90000); int r2 = 10000 + new java.util.Random().nextInt(90000); return title + "" + second + "" + r1 + "" + r2; } // 生成 硬件序列号 public static String getRandomSERIAL() { String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; Random random = new Random(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < 12; i++) { int number = random.nextInt(36); sb.append(str.charAt(number)); } return sb.toString(); } // 生成 android_id public static String getRandomAndroid_id() { String str = "abcdefghijklmnopqrstuvwxyz0123456789"; Random random = new Random(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < 16; i++) { int number = random.nextInt(36); sb.append(str.charAt(number)); } return sb.toString(); } // 生成 SIM 序列号 public static String getRandomSIM_SERIAL() { String str = "0123456789"; Random random = new Random(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < 14; i++) { int number = random.nextInt(10); sb.append(str.charAt(number)); } return "898600" + sb.toString(); } // 生成 WifiMAC 地址 public static byte[] getWifiMAC() { byte[] WifiMAC = new byte[6]; for (int i = 0; i < 6; i++) { Random random = new Random(); WifiMAC[i] = (byte) random.nextInt(128); } return WifiMAC; } } ``` HookTest.java 代码 ```java package com.bmstd.xposed1; import android.content.ContentResolver; import android.provider.Settings; import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XC_MethodReplacement; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LoadPackage; public class HookTest implements IXposedHookLoadPackage { @Override public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { String SERIAL = Utis.getRandomSERIAL(); String android_id = Utis.getRandomAndroid_id(); String IMEI = Utis.getIMEI(); String SIM_SERIAL = Utis.getRandomSIM_SERIAL(); String IMSI = Utis.getImsi(); byte[] WifiMAC = Utis.getWifiMAC(); // 修改 硬件序列号 Class<?> classBuild = XposedHelpers.findClass("android.os.Build", loadPackageParam.classLoader); XposedHelpers.setStaticObjectField(classBuild, "SERIAL", SERIAL); Class<?> classSysProp = XposedHelpers.findClass("android.os.SystemProperties", loadPackageParam.classLoader); XposedHelpers.findAndHookMethod(classSysProp, "get", String.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { super.afterHookedMethod(param); String serialno = (String) param.args[0]; if (serialno.equals("gsm.version.baseband") || serialno.equals("no message")) { param.setResult(SERIAL); } } }); XposedHelpers.findAndHookMethod(classSysProp, "get", String.class, String.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { super.afterHookedMethod(param); String serialno = (String) param.args[0]; if (serialno.equals("gsm.version.baseband") || serialno.equals("no message") ) { param.setResult(SERIAL); } } }); // 修改 android_id XposedHelpers.findAndHookMethod("android.provider.Settings.Secure", loadPackageParam.classLoader, "getString", ContentResolver.class, String.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { if (param.args[1].equals(Settings.Secure.ANDROID_ID)) { param.setResult(android_id); } } }); // 修改 IMEI XposedHelpers.findAndHookMethod("android.telephony.TelephonyManager", loadPackageParam.classLoader, "getDeviceId", XC_MethodReplacement.returnConstant(IMEI)); // 修改 SIM 序列号 XposedHelpers.findAndHookMethod("android.telephony.TelephonyManager", loadPackageParam.classLoader, "getSimSerialNumber", XC_MethodReplacement.returnConstant(SIM_SERIAL)); // 修改 IMSI XposedHelpers.findAndHookMethod("android.telephony.TelephonyManager", loadPackageParam.classLoader, "getSubscriberId", XC_MethodReplacement.returnConstant(IMSI)); // 修改 Android 6.0 之前 WifiMAC XposedHelpers.findAndHookMethod("android.net.wifi.WifiInfo", loadPackageParam.classLoader, "getMacAddress", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { super.afterHookedMethod(param); param.setResult(WifiMAC); } }); // 修改 Android 7.0 之后 WifiMAC XposedHelpers.findAndHookMethod("java.net.NetworkInterface", loadPackageParam.classLoader, "getHardwareAddress", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { String name = (String) XposedHelpers.callMethod(param.thisObject, "getName"); if (name.equals("wlan0")) { param.setResult(WifiMAC); } } }); } } ``` ### 过加速器软件风控,实现无限制 使用 通过设备信息获取 App ,获取到没有改机之前的设备信息如下图: ![7.png](https://shs3.b.qianxin.com/attack_forum/2023/05/attach-17af0a43d05c8ecf096c5be87bab11a967e4280e.png) 改机之后的 App 信息如下图所示: ![8.png](https://shs3.b.qianxin.com/attack_forum/2023/05/attach-f6d6274346a3e9e8b40c427430dac011a9f125f5.png) 通过上面的两张图可以看到,“大变设备”,设备已经变的面目全非了。 没改机之前,App 的试用时间已经到期。 ![9.png](https://shs3.b.qianxin.com/attack_forum/2023/05/attach-ac263c1c78c7c1a236cedc55098c03c07d65028a.png) 而改机之后,卸载重新安装,App 会认为又是一个新的设备,会为这个设备分配新的临时用户,也就是又可以继续试用,从而达到了改机过风控实现无限制使用的目的。 ![10.png](https://shs3.b.qianxin.com/attack_forum/2023/05/attach-cbe316e3cfe140c5108cd3aad99344daf023be48.png) ### 总结语 本文通过对风控的分析,找到风控点,从而通过编写 Hook 代码绕过风控,实现无限制“薅羊毛”。 真正的“斗争”技术只有一小部分,更多的是哲学层面的攻击防御,和人性思考的斗争。通过对本文的阅读,可以发现从始至终,都没有对 App 进行过静态或动态的逆向分析,而是站在人思维的斗争面去思考问题。 最后还要再次申明,“黑夜给了我黑色眼睛,我却用它去寻找光明”,了解风控的弱点,并不是去绕过它,因为那只会给企业带来无止境的灾难,了解风控的弱点,是应该更好的控制风险!
发表于 2023-06-05 09:00:02
阅读 ( 7020 )
分类:
漏洞分析
3 推荐
收藏
0 条评论
请先
登录
后评论
bmstd
13 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!