文章总结: 本文以国外某app为例,详细介绍了安卓设备环境检测的绕过方法。作者使用红米note11pro手机,通过root工具和内核模块隐藏技术,成功规避了多种检测软件的检查。重点演示了使用Frida公开脚本绕过开发者USB检测的具体实现过程,并强调安全分析应优先于环境检测的实用建议。 综合评分: 78 文章分类: 移动安全,逆向分析,安全工具,红队,渗透测试
,今天来分析一篇,以国外某app为例
安卓手机环境准备
首先我的手机是红米note11 pro,系统是安卓14,hyperos v2.0.5
)

比如有的检测lsp和zygisk,那你不可能说你不用了吧
咱是搞安全逆向分析的,不是搞环境检测的,侧重点分清楚
调试
好了,进入正题,先手动打开目标app,没有问题,表示环境那一关是过了
过掉开发者usb检测
打开投屏软件,卧槽一片黑
这个界面,调试分析发现是检测了开发者和usb,首先用frida公开的usb%20bypass脚本测试
/* %20 %20frida%20--codeshare%20meerkati/universal-android-debugging-bypass%20-f%20YOUR_BINARY %20 %20Universal%20Android%20Debugging%20Bypass%20frida%20script%20v0.1
%20 %20Useful%20when%20bypassing%20USB%20debugging%20detection%20on%20Android! %20 %20If%20it%20doesn't%20work,%20remove%20the%20conditional%20statement
*/
setTimeout(function()%20{ %20 %20Java.perform(function()%20{ %20 %20 %20 var androidSettings%20=%20['adb_enabled']; %20 %20 %20 var sdkVersion%20=%20Java.use('android.os.Build$VERSION'); %20 %20 %20 %20console.log("SDK%20Version%20:%20" +%20sdkVersion.SDK_INT.value);
%20 %20 %20 /*%20API%2016%20or%20lower%20Settings.Global%20Hook%20*/ %20 %20 %20 if (sdkVersion.SDK_INT.value%20<= 16)%20{ %20 %20 %20 %20 %20 var settingSecure%20=%20Java.use('android.provider.Settings$Secure');
%20 %20 %20 %20 %20 %20settingSecure.getInt.overload('android.content.ContentResolver', 'java.lang.String').implementation%20=%20function(cr,%20name)%20{ %20 %20 %20 %20 %20 %20 %20 //console.log("[*]settingSecure.getInt(cr,name)%20:%20"%20+%20name); %20 %20 %20 %20 %20 %20 %20 if (name%20==%20androidSettings[0])%20{ %20 %20 %20 %20 %20 %20 %20 %20 %20 %20console.log('[+]Secure.getInt(cr,%20name)%20Bypassed'); %20 %20 %20 %20 %20 %20 %20 %20 %20 return 0; %20 %20 %20 %20 %20 %20 %20 %20} %20 %20 %20 %20 %20 %20 %20 var ret%20= this.getInt(cr,%20name); %20 %20 %20 %20 %20 %20 %20 return ret; %20 %20 %20 %20 %20 %20}
%20 %20 %20 %20 %20 %20settingSecure.getInt.overload('android.content.ContentResolver', 'java.lang.String', 'int').implementation%20=%20function(cr,%20name,%20def)%20{ %20 %20 %20 %20 %20 %20 %20 //console.log("[*]settingSecure.getInt(cr,name,def)%20:%20"%20+%20name); %20 %20 %20 %20 %20 %20 %20 if (name%20==%20(androidSettings[0]))%20{ %20 %20 %20 %20 %20 %20 %20 %20 %20 %20console.log('[+]Secure.getInt(cr,%20name,%20def)%20Bypassed'); %20 %20 %20 %20 %20 %20 %20 %20 %20 return 0; %20 %20 %20 %20 %20 %20 %20 %20} %20 %20 %20 %20 %20 %20 %20 var ret%20= this.getInt(cr,%20name,%20def); %20 %20 %20 %20 %20 %20 %20 return ret; %20 %20 %20 %20 %20 %20}
%20 %20 %20 %20 %20 %20settingSecure.getFloat.overload('android.content.ContentResolver', 'java.lang.String').implementation%20=%20function(cr,%20name)%20{ %20 %20 %20 %20 %20 %20 %20 //console.log("[*]settingSecure.getFloat(cr,name)%20:%20"%20+%20name); %20 %20 %20 %20 %20 %20 %20 if (name%20==%20androidSettings[0])%20{ %20 %20 %20 %20 %20 %20 %20 %20 %20 %20console.log('[+]Secure.getFloat(cr,%20name)%20Bypassed'); %20 %20 %20 %20 %20 %20 %20 %20 %20 return 0; %20 %20 %20 %20 %20 %20 %20 %20} %20 %20 %20 %20 %20 %20 %20 var ret%20= this.getFloat(cr,%20name) %20 %20 %20 %20 %20 %20 %20 return ret; %20 %20 %20 %20 %20 %20}
%20 %20 %20 %20 %20 %20settingSecure.getFloat.overload('android.content.ContentResolver', 'java.lang.String', 'float').implementation%20=%20function(cr,%20name,%20def)%20{ %20 %20 %20 %20 %20 %20 %20 //console.log("[*]settingSecure.getFloat(cr,name,def)%20:%20"%20+%20name); %20 %20 %20 %20 %20 %20 %20 if (name%20==%20androidSettings[0])%20{ %20 %20 %20 %20 %20 %20 %20 %20 %20 %20console.log('[+]Secure.getFloat(cr,%20name,%20def)%20Bypassed'); %20 %20 %20 %20 %20 %20 %20 %20 %20 return 0; %20 %20 %20 %20 %20 %20 %20 %20} %20 %20 %20 %20 %20 %20 %20 var ret%20= this.getFloat(cr,%20name,%20def); %20 %20 %20 %20 %20 %20 %20 return ret; %20 %20 %20 %20 %20 %20}
%20 %20 %20 %20 %20 %20settingSecure.getLong.overload('android.content.ContentResolver', 'java.lang.String').implementation%20=%20function(cr,%20name)%20{ %20 %20 %20 %20 %20 %20 %20 //console.log("[*]settingSecure.getLong(cr,name)%20:%20"%20+%20name); %20 %20 %20 %20 %20 %20 %20 if (name%20==%20androidSettings[0])%20{ %20 %20 %20 %20 %20 %20 %20 %20 %20 %20console.log('[+]Secure.getLong(cr,%20name)%20Bypassed'); %20 %20 %20 %20 %20 %20 %20 %20 %20 return 0; %20 %20 %20 %20 %20 %20 %20 %20} %20 %20 %20 %20 %20 %20 %20 var ret%20= this.getLong(cr,%20name) %20 %20 %20 %20 %20 %20 %20 return ret; %20 %20 %20 %20 %20 %20}
%20 %20 %20 %20 %20 %20settingSecure.getLong.overload('android.content.ContentResolver', 'java.lang.String', 'long').implementation%20=%20function(cr,%20name,%20def)%20{ %20 %20 %20 %20 %20 %20 %20 //console.log("[*]settingSecure.getLong(cr,name,def)%20:%20"%20+%20name); %20 %20 %20 %20 %20 %20 %20 if (name%20==%20androidSettings[0])%20{ %20 %20 %20 %20 %20 %20 %20 %20 %20 %20console.log('[+]Secure.getLong(cr,%20name,%20def)%20Bypassed'); %20 %20 %20 %20 %20 %20 %20 %20 %20 return 0; %20 %20 %20 %20 %20 %20 %20 %20} %20 %20 %20 %20 %20 %20 %20 var ret%20= this.getLong(cr,%20name,%20def); %20 %20 %20 %20 %20 %20 %20 return ret; %20 %20 %20 %20 %20 %20}
%20 %20 %20 %20 %20 %20settingSecure.getString.overload('android.content.ContentResolver', 'java.lang.String').implementation%20=%20function(cr,%20name)%20{ %20 %20 %20 %20 %20 %20 %20 //console.log("[*]settingSecure.getString(cr,name)%20:%20"%20+%20name); %20 %20 %20 %20 %20 %20 %20 if (name%20==%20androidSettings[0])%20{ %20 %20 %20 %20 %20 %20 %20 %20 %20 var stringClass%20=%20Java.use("java.lang.String"); %20 %20 %20 %20 %20 %20 %20 %20 %20 var stringInstance%20=%20stringClass.$new("0");
%20 %20 %20 %20 %20 %20 %20 %20 %20 %20console.log('[+]Secure.getString(cr,%20name)%20Bypassed'); %20 %20 %20 %20 %20 %20 %20 %20 %20 return stringInstance; %20 %20 %20 %20 %20 %20 %20 %20} %20 %20 %20 %20 %20 %20 %20 var ret%20= this.getString(cr,%20name); %20 %20 %20 %20 %20 %20 %20 return ret; %20 %20 %20 %20 %20 %20} %20 %20 %20 %20}
%20 %20 %20 /*%20API%2017%20or%20higher%20Settings.Global%20Hook%20*/ %20 %20 %20 if (sdkVersion.SDK_INT.value%20>= 17)%20{ %20 %20 %20 %20 %20 var settingGlobal%20=%20Java.use('android.provider.Settings$Global');
%20 %20 %20 %20 %20 %20settingGlobal.getInt.overload('android.content.ContentResolver', 'java.lang.String').implementation%20=%20function(cr,%20name)%20{ %20 %20 %20 %20 %20 %20 %20 //console.log("[*]settingGlobal.getInt(cr,name)%20:%20"%20+%20name); %20 %20 %20 %20 %20 %20 %20 if (name%20==%20androidSettings[0])%20{ %20 %20 %20 %20 %20 %20 %20 %20 %20 %20console.log('[+]Global.getInt(cr,%20name)%20Bypassed'); %20 %20 %20 %20 %20 %20 %20 %20 %20 return 0; %20 %20 %20 %20 %20 %20 %20 %20} %20 %20 %20 %20 %20 %20 %20 var ret%20= this.getInt(cr,%20name); %20 %20 %20 %20 %20 %20 %20 return ret; %20 %20 %20 %20 %20 %20}
%20 %20 %20 %20 %20 %20settingGlobal.getInt.overload('android.content.ContentResolver', 'java.lang.String', 'int').implementation%20=%20function(cr,%20name,%20def)%20{ %20 %20 %20 %20 %20 %20 %20 //console.log("[*]settingGlobal.getInt(cr,name,def)%20:%20"%20+%20name); %20 %20 %20 %20 %20 %20 %20 if (name%20==%20(androidSettings[0]))%20{ %20 %20 %20 %20 %20 %20 %20 %20 %20 %20console.log('[+]Global.getInt(cr,%20name,%20def)%20Bypassed'); %20 %20 %20 %20 %20 %20 %20 %20 %20 return 0; %20 %20 %20 %20 %20 %20 %20 %20} %20 %20 %20 %20 %20 %20 %20 var ret%20= this.getInt(cr,%20name,%20def); %20 %20 %20 %20 %20 %20 %20 return ret; %20 %20 %20 %20 %20 %20}
%20 %20 %20 %20 %20 %20settingGlobal.getFloat.overload('android.content.ContentResolver', 'java.lang.String').implementation%20=%20function(cr,%20name)%20{ %20 %20 %20 %20 %20 %20 %20 //console.log("[*]settingGlobal.getFloat(cr,name)%20:%20"%20+%20name); %20 %20 %20 %20 %20 %20 %20 if (name%20==%20androidSettings[0])%20{ %20 %20 %20 %20 %20 %20 %20 %20 %20 %20console.log('[+]Global.getFloat(cr,%20name)%20Bypassed'); %20 %20 %20 %20 %20 %20 %20 %20 %20 return 0; %20 %20 %20 %20 %20 %20 %20 %20} %20 %20 %20 %20 %20 %20 %20 var ret%20= this.getFloat(cr,%20name); %20 %20 %20 %20 %20 %20 %20 return ret; %20 %20 %20 %20 %20 %20}
%20 %20 %20 %20 %20 %20settingGlobal.getFloat.overload('android.content.ContentResolver', 'java.lang.String', 'float').implementation%20=%20function(cr,%20name,%20def)%20{ %20 %20 %20 %20 %20 %20 %20 //console.log("[*]settingGlobal.getFloat(cr,name,def)%20:%20"%20+%20name); %20 %20 %20 %20 %20 %20 %20 if (name%20==%20androidSettings[0])%20{ %20 %20 %20 %20 %20 %20 %20 %20 %20 %20console.log('[+]Global.getFloat(cr,%20name,%20def)%20Bypassed'); %20 %20 %20 %20 %20 %20 %20 %20 %20 return 0; %20 %20 %20 %20 %20 %20 %20 %20} %20 %20 %20 %20 %20 %20 %20 var ret%20= this.getFloat(cr,%20name,%20def); %20 %20 %20 %20 %20 %20 %20 return ret; %20 %20 %20 %20 %20 %20}
%20 %20 %20 %20 %20 %20settingGlobal.getLong.overload('android.content.ContentResolver', 'java.lang.String').implementation%20=%20function(cr,%20name)%20{ %20 %20 %20 %20 %20 %20 %20 //console.log("[*]settingGlobal.getLong(cr,name)%20:%20"%20+%20name); %20 %20 %20 %20 %20 %20 %20 if (name%20==%20androidSettings[0])%20{ %20 %20 %20 %20 %20 %20 %20 %20 %20 %20console.log('[+]Global.getLong(cr,%20name)%20Bypassed'); %20 %20 %20 %20 %20 %20 %20 %20 %20 return 0; %20 %20 %20 %20 %20 %20 %20 %20} %20 %20 %20 %20 %20 %20 %20 var ret%20= this.getLong(cr,%20name); %20 %20 %20 %20 %20 %20 %20 return ret; %20 %20 %20 %20 %20 %20}
%20 %20 %20 %20 %20 %20settingGlobal.getLong.overload('android.content.ContentResolver', 'java.lang.String', 'long').implementation%20=%20function(cr,%20name,%20def)%20{ %20 %20 %20 %20 %20 %20 %20 //console.log("[*]settingGlobal.getLong(cr,name,def)%20:%20"%20+%20name); %20 %20 %20 %20 %20 %20 %20 if (name%20==%20androidSettings[0])%20{ %20 %20 %20 %20 %20 %20 %20 %20 %20 %20console.log('[+]Global.getLong(cr,%20name,%20def)%20Bypassed'); %20 %20 %20 %20 %20 %20 %20 %20 %20 return 0; %20 %20 %20 %20 %20 %20 %20 %20} %20 %20 %20 %20 %20 %20 %20 var ret%20= this.getLong(cr,%20name,%20def); %20 %20 %20 %20 %20 %20 %20 return ret; %20 %20 %20 %20 %20 %20}
%20 %20 %20 %20 %20 %20settingGlobal.getString.overload('android.content.ContentResolver', 'java.lang.String').implementation%20=%20function(cr,%20name)%20{ %20 %20 %20 %20 %20 %20 %20 //console.log("[*]settingGlobal.getString(cr,name)%20:%20"%20+%20name); %20 %20 %20 %20 %20 %20 %20 if (name%20==%20androidSettings[0])%20{ %20 %20 %20 %20 %20 %20 %20 %20 %20 var stringClass%20=%20Java.use("java.lang.String"); %20 %20 %20 %20 %20 %20 %20 %20 %20 var stringInstance%20=%20stringClass.$new("0");
%20 %20 %20 %20 %20 %20 %20 %20 %20 %20console.log('[+]Global.getString(cr,%20name)%20Bypassed'); %20 %20 %20 %20 %20 %20 %20 %20 %20 return stringInstance; %20 %20 %20 %20 %20 %20 %20 %20} %20 %20 %20 %20 %20 %20 %20 var ret%20= this.getString(cr,%20name); %20 %20 %20 %20 %20 %20 %20 return ret; %20 %20 %20 %20 %20 %20} %20 %20 %20 %20} %20 %20});}, 0);
试了之后发现过不去,哈哈
没事,仔细看那个界面的提示,虽然是外文看不懂,但是最后有个【V0030】
这个就很简单,拿到V0003直接源码里搜
直接定位到这里,看变量名
不需要太多的时间,就定位这段逻辑:
这里就直接hook,把n82.a的值改一下,他就不会往下走通过intent跳转到那个V003的界面了
这个Intent,相信搞过那种登录注册界面绕过app的朋友来说,这里应该不陌生

还是上面的办法,找到检测位置,但是这个他提示的就直接没法搜到了

以下是找到的主要不同

调试完了之后
代码逻辑如下,仅展示部分,太长了就不贴全了

另外就是这个app很二逼,每次启动要重新下载资源再跳转主页,所以调试起来非常蛋疼
后面有空再搞这个针对性的flutter处理
今天只是展示一下常规的检测绕过
完整的代码后续发星球
结语
工作避坑&内推(仅成都)、技术交流、群互动
扫码或者搜ID:geekbyte
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:编角料 编角料 编角料《移动安全之某app设备环境常规检测绕过》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论