本文站在风控的角度,找到风控的弱点,通过改机,让 App 不认识所使用的这台设备,从而实现无限制“薅羊毛”。
改机过风控实现无限制使用某加速器
风控是什么?
风控,其实就是字面意思,把这两个字拆开看就可以得到正确的解释,就是 风险 和 控制。
黑产们制造风险,从而获得相应的利润。
厂家们为了减少风险或尽量控制住风险,就有了风控这个概念的诞生。
某加速器的软件的风控点在哪?
这是一款加速器 App。
当安装这款加速器 App 后,有两种方式可以使用这款 App。
第一种方式是:用户注册一个账户来使用,然后每天赠送一个小时的使用时间。
第二种方式是:当安装这款 App 后,它会自动分配一个体验用户,并且也会赠送每天一个小时的使用时间。
从上面的分析得知,只要安装并且在不手动注册的情况下,就会分配一个体验用户,体验一个小时。
那么是不是只要卸载再重新安装,就会再体验一个小时呢?
依次类推,只要到期就卸载,然后一直用 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 类中,可以通过该类访问该值,安卓系统源码如下
由于 SERIAL 是静态变量,Xposed 直接调用 setStaticObjectField 即可修改掉变量的值。
但是细心的话可以看到,SERIAL 的值是通过 getString("no such thing") 方法执行
跟踪方法,getString(String property) 方法内部调用了 SystemProperties.get() 方法。
再继续跟踪方法,发现 SystemProperties.get() 方法,调用了 native_get() 方法,而 native_get() 方法是一个 native 方法,已没有跟踪的必要。
所以即使修改了 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。
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# 即可查询。
获得 IMEI 的方法很简单,直接调用 android.telephony.TelephonyManager 的 getDeviceId 方法即可。
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 方法即可。
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 方法即可。
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 获取。
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 就获取它的值。
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 代码
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 代码
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 ,获取到没有改机之前的设备信息如下图:
改机之后的 App 信息如下图所示:
通过上面的两张图可以看到,“大变设备”,设备已经变的面目全非了。
没改机之前,App 的试用时间已经到期。
而改机之后,卸载重新安装,App 会认为又是一个新的设备,会为这个设备分配新的临时用户,也就是又可以继续试用,从而达到了改机过风控实现无限制使用的目的。
总结语
本文通过对风控的分析,找到风控点,从而通过编写 Hook 代码绕过风控,实现无限制“薅羊毛”。
真正的“斗争”技术只有一小部分,更多的是哲学层面的攻击防御,和人性思考的斗争。通过对本文的阅读,可以发现从始至终,都没有对 App 进行过静态或动态的逆向分析,而是站在人思维的斗争面去思考问题。
最后还要再次申明,“黑夜给了我黑色眼睛,我却用它去寻找光明”,了解风控的弱点,并不是去绕过它,因为那只会给企业带来无止境的灾难,了解风控的弱点,是应该更好的控制风险!
转载:https://forum.butian.net/share/2295
作者:bmstd
欢迎大家去关注作者
欢迎师傅加入安全交流群(qq群:611901335),或者后台回复加群
如果想和我一起讨论,欢迎加入我的知识星球!!!
扫描下图加入freebuf知识大陆
师傅们点赞、转发、在看就是最大的支持
后台回复知识星球或者知识大陆也可获取加入链接(两个加其一即可)
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……
还没有评论,来说两句吧...