基础 1、配置 ubutnu 模拟器环境
AS+genymotion 配置(396kyzdwPwp6b7A)
2、手机/模拟器连接
1、USB线连接
2、wifi线,需要桥接,同一个网段,真机需要 root,打开开发者模式和USB调试
手残,在物理机开雷电模拟器,ubuntu的AS连接(要装ARM吗)
1 2 3 4 5 在ubuntu上,用AS的adb执行 adb connect 192.168.3.175:5555 adb disconnect ip:5555 adb -s 设备名称 //多个设备连接
基础语法自己看去教程
四大组件:活动(activity)、意图(Intent)、广播、服务(Service)
活动(activity) -> 意图(Intent) ->活动(activity)
服务(Service):安卓保活
网络通信——HttpURLConnectio_okhttp
JNI
让 Java 调用 C 语言或者 C++代码,让 C 调用 Java 代码
NDK
Android 的开发包,用于开发 C / C++的动态库,编译成 /so文件供 Java 调用,支持打包成 apk。NDK(Native Development Kit)是一套用于开发Android应用程序的工具集,它允许您在C/C++中编写性能关键的部分代码,并将这些代码与Java代码进行连接。
实战
参考资料:安卓逆向这档事
https://www.52pojie.cn/thread-1787667-1-1.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class MainActivity extends AppCompatActivity { static { System.loadLibrary("native" ); } @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d("topday" ,setMySonName("topday" )); sayHello("topday" ); Log.d("topday" ,"加法" +add(1 ,2 )); } private native String setMySonName (String name) ; public native void sayHello (String speak) ; public native int add (int a,int b) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 extern "C" JNIEXPORT jstring JNICALL Java_com_example_lesson16_MainActivity_setMySonName (JNIEnv *env, jobject thiz, jstring name) { LOGE ("我是来自Java层的 ->%s" ,env->GetStringUTFChars (name, JNI_FALSE)); std::string ss= "topdayforjni" ; jstring jstring1 = env->NewStringUTF (ss.c_str ()); return jstring1; } void sayHelloToJNI (JNIEnv *env, jobject thiz,jstring speak) { LOGE ("我是来自Java层说话的的函数 ->%s" ,env->GetStringUTFChars (speak, JNI_FALSE)); } jint addToJNI (JNIEnv *env, jobject thiz,jint a,jint b) { return a+b; } static const JNINativeMethod nativeMethod[] = { {"sayHello" ,"(Ljava/lang/String;)V" ,(void *)sayHelloToJNI}, {"add" ,"(II)I" ,(void *)addToJNI}, }; jint JNI_OnLoad (JavaVM *vm,void * reserved) { JNIEnv *env = NULL ; if (vm->GetEnv ((void **)&env,JNI_VERSION_1_4)!=JNI_OK){ return JNI_FALSE; } jclass clazz = env->FindClass ("com/example/test/MainActivity" ); if (clazz == NULL ){ return JNI_FALSE; } int result = env->RegisterNatives (clazz,nativeMethod,sizeof (nativeMethod) / sizeof (nativeMethod[0 ])); if (result<0 ){ LOGE ("方法映射失败" ); } return JNI_VERSION_1_4; }
逆向环境 & 安卓基础 真机还没到,先用模拟器+面具+lsp
抓包 Charles
导入证书,手动导入 / chls.pro/ssl
证书移动到系统目录,安装 Move_Certificates 模块,自动把证书移动到系统目录(雷电的 ROOT 怎么不稳定)
数据加密原理
安装 Postern
HTTPS= HTTP + SSL ,此处加密在 OSI 7层模型 的表示层
VPN 协议大多是作用在 OSI 的第二层和第三层之间。能抓到更多的流量
利用 Postern 转发本地 SOCKS5 流量到 Charles
手机请求 -> Postern(手机代理) ->charles -> 主机代理(charles 本地) -> 服务器
dex 文件就是字节码
编译后的安卓程序代码文件
dex 反汇编后是 Smail 代码
反汇编工具
Jeb,带自动跳转,./jeb/jeb_linux.sh
基础语法
调试(Jeb 工具)
setprop ro.debuggable 1
Jeb 工具,CTRL + B 下断点,Tab 跳转到反编译源码(Smail -> Java)
点击 start 附加在运行的程序上
模拟器自带的 Dex 编辑器++能够直接重新打包
LSPosed xposed -> edxposed -> lsposed
编写插件/组件、便于 Hook 的框架
1、正常创建项目,导入 api-82 包
2、修改 compileOnly files(‘libs/api-82.jar’) //只在编译时有效,不会参与打包
3、AndroidManifest.xml 新增模块
4、新建 assets 文件夹,指定 xposed_init 初始化模块
5、编写代码
参考:https://www.52pojie.cn/thread-1740944-1-1.html
原理:https://www.52pojie.cn/thread-1694093-1-1.html
某手利用代理的方式还是抓不到评论
1 2 3 4 语法: 1、正常类直接看案例 2、jadx 可直接右键复制 xposed 代码,修改 classLoader,指定外部类 3、匿名类,查看 Smail 语法,仿写 com.example.Hookdemo.Person$1
常用语法整理:
1 2 3 4 public void handleLoadPackage (XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { if (!loadPackageParam.packageName.equals("com.example.hookdemo" )){ return ; }
Hook 类的构造方法 1 2 3 4 5 6 7 8 9 10 11 12 13 XposedHelpers.findAndHookConstructor("com.example.hookdemo.Person" , loadPackageParam.classLoader, String.class, int .class, String.class, new XC_MethodHook () { @Override protected void beforeHookedMethod (MethodHookParam param) throws Throwable { super .beforeHookedMethod(param); } @Override protected void afterHookedMethod (MethodHookParam param) throws Throwable { super .afterHookedMethod(param); } });
Hook 类的普通方法 1 2 3 4 5 6 7 8 9 10 ClassLoader classLoader = loadPackageParam.classLoader;Class<?> clazz = classLoader.loadClass("com.example.hookdemo.Person" ); XposedHelpers.findAndHookMethod(clazz, "getName" , new XC_MethodHook () { @Override protected void afterHookedMethod (MethodHookParam param) throws Throwable { super .afterHookedMethod(param); } });
Hook 类的内部类的构造方法 1 2 3 4 5 6 7 8 9 10 11 ClassLoader classLoader = loadPackageParam.classLoader;Class<?> Person_clazz = classLoader.loadClass("com.example.hookdemo.Person" ); XposedHelpers.findAndHookConstructor("com.example.hookdemo.Person$People" , Person_clazz.getClassLoader(), Person_clazz,String.class, new XC_MethodHook () { @Override protected void beforeHookedMethod (MethodHookParam param) throws Throwable { super .beforeHookedMethod(param); param.args[1 ] = "xxx" ; } });
Hook 类的内部匿名类 1 2 3 4 5 6 7 8 XposedHelpers.findAndHookMethod("com.example.hookdemo.Person$1" , loadPackageParam.classLoader, "eatFunc" , String.class, new XC_MethodHook () { @Override protected void beforeHookedMethod (MethodHookParam param) throws Throwable { super .beforeHookedMethod(param); } });
Hook 类的普通成员变量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 XposedHelpers.findAndHookMethod("com.example.hookdemo.Person" , loadPackageParam.classLoader, "print" , new XC_MethodHook () { @Override protected void beforeHookedMethod (MethodHookParam param) throws Throwable { super .beforeHookedMethod(param); ClassLoader classLoader = loadPackageParam.classLoader; Class<?> Person_clazz = classLoader.loadClass("com.example.hookdemo.Person" ); Object obj_person = XposedHelpers.newInstance(Person_clazz); String name = (String) XposedHelpers.getObjectField(obj_person, "name" );
Hook 类的静态成员变量(java反射) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int xp_age = XposedHelpers.getStaticIntField(Person_clazz, "age" );Log.d("topday" ,"获取普通类的 private_age ->" +xp_age); XposedHelpers.setStaticIntField(param.thisObject.getClass(),"age" ,100 ); String xp_address = (String) XposedHelpers.getStaticObjectField(param.thisObject.getClass(), "address" );Log.d("topday" ,"获取普通类的 public static String ->" +xp_address); XposedHelpers.setStaticObjectField(param.thisObject.getClass(),"address" ,"M78星云" ); Field age_id = param.thisObject.getClass().getDeclaredField("age" );age_id.setAccessible(true ); int age_int = (int )age_id.get(null );Log.d("xxx" ,"获取普通类的 private_age ->" +age_int); age_id.set(null ,100 );
Hook 类的类参数的普通成员变量 1 2 3 4 5 6 7 8 Class<?> PeopleClass = Person_clazz.getClassLoader().loadClass("com.example.hookdemo.Person$People" ); Object peoPleobject = XposedHelpers.newInstance(PeopleClass, obj_person);String name1 = (String) XposedHelpers.getObjectField(peoPleobject, "name" );Log.d("xxx" ,"获取内部类的普通属性 name ->" +name1);
Hook 类的匿名类的普通成员变量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Class<?> niminmgclass = XposedHelpers.findClass("com.example.hookdemo.Person$1" , Person_clazz.getClassLoader()); Object nimingduixiang = XposedHelpers.newInstance(niminmgclass, obj_person);int ceshi = XposedHelpers.getIntField(nimingduixiang, "ceshi" );Log.d("xxx" ,"获取匿名类的普通属性 ceshi ->" +ceshi); int xp_anonymoutInt = XposedHelpers.getStaticIntField(niminmgclass, "anonymoutInt" );Log.d("xxx" ,"获取匿名类的静态属性 xp_anonymoutInt ->" +xp_anonymoutInt); XposedHelpers.setStaticIntField(niminmgclass,"anonymoutInt" ,666 );
Hook 内主动调用方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 XposedHelpers.findAndHookMethod("com.example.hookdemo.Person" , loadPackageParam.classLoader, "print" , new XC_MethodHook () { @Override protected void beforeHookedMethod (MethodHookParam param) throws Throwable { super .beforeHookedMethod(param); Class<?> Person_clazz = XposedHelpers.findClass("com.example.hookdemo.Person" ,loadPackageParam.classLoader); XposedHelpers.callMethod(XposedHelpers.newInstance(Person_clazz),"print" ,"1" ,100 ,"2" ); XposedHelpers.callStaticMethod(Person_clazz,"print" ,"李四" ,99 ); Class<?> PeopleClass = Person_clazz.getClassLoader().loadClass("com.example.hookdemo.Person$People" ); Object peoPleobject = XposedHelpers.newInstance(PeopleClass, XposedHelpers.newInstance(Person_clazz)); XposedHelpers.callMethod(peoPleobject,"print" ,"1内部" ,100 ,"2" ); } @Override protected void afterHookedMethod (MethodHookParam param) throws Throwable { super .afterHookedMethod(param); } });
hookAllMethods & hookMethod 1 2 3 4 5 6 7 8 9 10 11 12 13 hookAllMethods hookMethod Class<?> Person_clazz = XposedHelpers.findClass("com.example.hookdemo.Person" , loadPackageParam.classLoader); Method print_id = Person_clazz.getDeclaredMethod("print" , String.class, int .class);XposedBridge.hookMethod(print_id, new XC_MethodReplacement () { @Override protected Object replaceHookedMethod (MethodHookParam methodHookParam) throws Throwable { Log.d("topday" ,"两个参数 此函数已经被我替换了" ); return null ; } });
加壳 / 多dex导致找不到类 1 2 3 4 5 6 7 测试加固: 1、app 签名 2、上传到 360 加固 3、MT 管理器实现签名 hook attach attach:https://blog.csdn.net/caoshen2014/article/details/103341690
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 XposedHelpers.findAndHookMethod(Application.class, "attach" , Context.class, new XC_MethodHook () { @Override protected void afterHookedMethod (MethodHookParam param) throws Throwable { ClassLoader cl= ((Context)param.args[0 ]).getClassLoader(); Class<?> hookclass=null ; try { hookclass=cl.loadClass("com.example.hookdemo.Person$People" ); }catch (Exception e){ Log.d("xxx" ,"未找到类" ,e); return ; } XposedHelpers.findAndHookMethod(hookclass, "print" , new XC_MethodHook () { @Override protected void afterHookedMethod (MethodHookParam param) throws Throwable { Log.d("xxx" ,"hook打印" ); } }); } });
Tips:Xposed hook 在 java 层,SO 在 native 层,关键性能代码常在 native 层
Hook native(Dobby ) 参考:https://blog.csdn.net/weixin_56039202/article/details/128591978
xposed 写一个 so 注入器
新建 native 项目,编写 Dobby 代码,编译出自己的 so 文件(提取 apk 的 so 文件,指定绝对地址)
把 so 放到指定目录
运行注入器
类内有特殊参数 https://bbs.kanxue.com/thread-215039.htm
1 2 3 4 5 6 7 用Xposed自身提供的XposedHelpers的findClass方法加载每一个类 XposedHelpers.findClass // 作为参数传入/实例化 XposedHelpers.findAndHookMethod final Class<?> ArrayList= XposedHelpers.findClass("java.util.ArrayList", loadPackageParam.classLoader); final Class<?> Map= XposedHelpers.findClass("java.util.Map", loadPackageParam.classLoader);
lspatch+shizuku 实现 Hook
https://duzhaokun123.github.io/2022/05/06/simple-lspatch-guide.html
1、电脑运行命令激活 Shizuku
2、LSPatch
显示 Shizuku
服务可用
3、本地和打包运行(本机运行和单机运行)
XPosed调试流程 1、AS 编写 XPosed 代码,手机 ROOT,安装 LSP,通过 LSP 去指定模块、指定程序
2、免 ROOT 流程,激活 Shizuku,LSPatch 中 Shizuku 服务可用,在 LSPatch 中指定程序,运行后 LSPosed 脚本后,可在指定运行的模块。
Hook 代码交互 https://skyhand.blog.csdn.net/article/details/117674003
https://jasper1024.com/jasper/20220317024954/
frida 基础
firda->client 用 python安装即可,安装 frida-tools 自动安装 frida
firda 的版本随着 python,server 和 client 版本要对上
client ->写代码端、server ->接受代码执行
打开编辑器 code .
14.2.18
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 frida-ps的具体使用方法如下: https://frida.re/docs/frida-trace/ #连接到USB设备查看进程列表 frida-ps -U #连接到USB设备查看正在运行的应用 frida-ps -U -a #连接到USB设备查看所有安装的应用 frida-ps -U -a -i #指定查看某个设备 frida-ps -D a666666 -a frida-trace -UF -j '*!*tesucanshu*' // 设备相关 -D 连接到指定的设备,多个设备时使用。示例:frida-trace -D 555315d66cac2d5849408f53da9eea514a90547e -F -U 连接到USB设备,只有一个设备时使用。示例fria-trace -U -F // 应用程序相关 -f 目标应用包名。spawn模式。示例:frida-trace -U -f com.apple.www -F 当前正在运行的程序。attach模式示例。示例:frida-trace -U -F或frida-trae -UF -n 正在运行的程序的名字。attach模式。示例:frida-trace -U -n QQ -N 正在运行的程序的包名。attach模式。示例:frida-trace -U -N com.apple.www -p 正在运行的程序的pid。attach模式。示例:frida-trace -U -p 2302 // 方法相关,以下参数在一条跟踪命令中可重复使用 -I 包含模块。示例:frida-trace -UF -I "libcommonCrypto*" -X 不包含模块。示例:frida-trace -UF -X "libcommonCrypto*" -i 包含c函数。示例:frida-trace -UF -i "strtsr" -x 不包名c函数。示例:frida-trace -UF -i "*MD5" -x "CC_MD5" -a 包含模块+偏移跟踪。示例:frida-trace -UF -a 模块名\!0x7B7D48 -j 包含某个Java方法 实列frida-trace -UF -j '*!*tesucanshu*/isu' !用来分隔MODULE和OFFSET,例如"gdi32full.dll!ExtTextOutW" *代表匹配任意内容 -J 包含某个Java方法 实列frida-trace -UF -J '*!*tesucanshu*/isu' // 日志相关 -o 日志输出到文件。示例:frida-trace -UF -j '*!*tesucanshu*/isu' -o run.log
LSPosed 和 Frida hook 流程 1 2 3 4 5 6 7 8 LSPosed: 1、安装面具(管理root权限和安装模块)、安装LSPosed(执行XPosed代码、指定程序、刷新需重启程序) 2、之前案例是编写 java 代码,常用 findAndHookMethod,重写 beforeHookedMethod 和 afterHookedMethod Frida: 1、服务端在 app 上启动,自动注入 hook 的启动程序 2、客户端在主机启动,执行命令连接,而后编写 js 脚本进行 hook,动态刷新 3、可注入当前运行程序 / 自选包名(Attach/Spwan)
查看修改参数/返回值
文档有许多的例子:因为frida 出来很久了所以百度会有许多他的案例
https://blog.csdn.net/freeking101/article/details/112634649
https://kevinspider.github.io/frida/frida-hook-java/
https://www.anquanke.com/post/id/195869
https://frida.re/docs/examples/android/
https://www.cnblogs.com/angelyan/p/16941444.html 常用frida hook - Maple_feng - 博客园
1 2 3 4 5 6 frida -U -f com.example.hookdemo -l lesson2.js --no-pause frida -UF -l lesson3.js Tip: 1、HOOK默认是把对象hook没了,要主动返回或是继续操作(this/return) 2、默认已连接adb的设备会自动连接frida服务端,只有一台的情况下 3、雷电模拟器要下载x86_64版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 function main ( ){ Java .perform (function ( ){ if (Java .available ){ let Person = Java .use ("com.example.hookdemo.Person" ); Person .setAddress .implementation = function (address2 ) { console .log (`Person.setAddress is called: address2=${address2} ` ); let address = "riben" ; this ["setAddress" ](address); }; Person ["getName" ].implementation = function ( ) { console .log (`Person.getName is called` ); let result = this ["getName" ](); console .log (`Person.getName result=${result} ` ); return "lisi" ; }; Person ["print" ].overload ('java.lang.String' , 'int' , 'java.lang.String' ).implementation = function (a1,a2,a3 ) { console .log (`Person.print is called` ); this ["print" ](a1,a2,a3); }; Person ["print" ].overload ('java.lang.String' , 'int' ).implementation = function (a1,a2 ) { console .log (`Person.print is called` ,a1,a2); this ["print" ](a1,a2); }; } }) } setImmediate (main);
主动调用静态、普通函数、内部类、匿名类 正式交付用 XPosed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 Java .perform (function ( ){ let AnonymousClass1 = Java .use ("com.example.hookdemo.Person$1" ); var AnonymousClass1 _ins = AnonymousClass1 .$new(perins); AnonymousClass1 _ins.eatFunc ("dayu" ); })
特殊参数打印 记住几个,用到的时候去查
访问静态属性 1 2 3 4 5 6 7 8 9 10 11 12 13 Java .perform (function ( ){ var person_class = Java .use ("com.example.hookdemo.Person" ); Java .choose ("com.example.hookdemo.Person" ,{ onMatch :function (instance ){ console .log (instance) instance.name .value = "xiugainame" ; console .log (instance.name .value ); },onComplete :function ( ){ console .log ("end" ); } }) })
遍历指定类的所有方法 https://www.jianshu.com/p/c9e6776516e8
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 Java .perform (function ( ) { Java .enumerateLoadedClasses ({ onMatch :function (name,handle ){ if (name.indexOf ("com.example.hookdemo.Person" )!=-1 ){ var TargetClass = Java .use (name); var methods = TargetClass .class .getDeclaredMethods (); for (var i=0 ;i<methods.length ;i++){ console .log (methods[i].getName ()) } } },onComplete :function ( ){ console .log ("serach end" ); } }) var classList = Java .enumerateLoadedClassesSync (); var parsedMethods = []; for (var i = 0 ; i < classList.length ; i++) { var targetClass = classList[i]; if (targetClass == "com.example.hookdemo.Person" ) { var TargetClass = Java .use (targetClass); var methods = TargetClass .class .getDeclaredMethods (); for (var j = 0 ; j < methods.length ; j++) { var methodName = methods[j].getName (); parsedMethods.push (methodName); } parsedMethods.forEach (function (targetMethod ) { console .log (TargetClass , targetMethod); var overloadCount = TargetClass [targetMethod].overloads .length ; for (var x = 0 ; x < overloadCount; x++) { TargetClass [targetMethod].overloads [x].implementation = function ( ) { console .log (1 , targetMethod); for (var a = 0 ; a < arguments .length ; a++) { console .log ('arguments' , a, arguments [a]); } return this [targetMethod].apply (this , arguments ); } } }); } } });
frida 集成工具 —— objection https://www.anquanke.com/post/id/197657
1、集成各种常用 API,列出某个包的类/方法等 2、启动前 frida 必须正常连接
运行步骤:
1、查找包名 / 应用ID / PID 1 2 3 4 // 根据应用名查找包名 frida-ps -U -a // 根据进程名查找包名 frida-ps -U
2、指定包名 / 应用ID / PID 1 2 3 4 5 6 7 objection --help 可以查看命令 objection -g xxx/PID explore 1.如果使用PID/应用名称 那就是attach模式 2.如果使用包名,那就是spwan模式,并且会帮助我们自动启动app -s "命令" -c "路径"
3、常用命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 1.列出所有已加载的类: android hooking list classes 2.搜索关键字类 android hooking search classes MainActivity 3.列出类的所有方法 android hooking list class_methods com.example.hookdemo.MainActivity 4.hook方法 可以带上参数 返回值 调用栈,监控 android hooking watch class_method com.example.hookdemo.MainActivity.tesucanshu2 --dump-args --dump-return 5.hook类的所有方法 android hooking watch class com.example.hookdemo.MainActivity 6.查看与取消hook jobs list jobs kill 049953 7.搜索实列: android heap search instances com.example.hookdemo.Person 8.通过实例执行方法: android heap execute 48164808 print 9.进入编辑器环境 android heap evaluate 48164808 写代码:clazz.print() esc 键退出环境,enter回车执行 这个功能可以即时编写、出结果、即时调试自己的代码,不用再编写→注入→操作→看结果→再调整,而是直接出结果。 10.启动activity或service 查看可用的:android hooking list activities 启动:android intent launch_activity 也可以先使用android hooking list services查看可供开启的服务,然后使用android intent launch_service com.android.settings.bluetooth.BluetoothPairingService命令来开启服务。 11、枚举内存中的所有模块 memory list modules 12、枚举模块中所有导出函数 memory list exports muyangdemo --json 路径(结果太多时用)
Wallbreak 1、可集成在 objection 使用
2、用于快速分析 Java 类/对象结构
https://bbs.kanxue.com/thread-260110.htm
https://github.com/hluwa/Wallbreaker
attach模式 :Frida会附加app
Spawn模式:重新启动app
1、加载插件
2、执行命令
1 2 3 4 plugin wallbreaker classdump '类名' plugin wallbreaker classsearch '类名' plugin wallbreaker objectdump '对象名' plugin wallbreaker objectsearch '对象名'
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 function printStacks (name ){ console .log ("====== printStacks start ====== " + name + "==============================" ) var throwable = Java .use ("android.util.Log" ).getStackTraceString (Java .use ("java.lang.Throwable" ).$new()); console .log (throwable); console .log ("====== printStacks end ======== " + name + "==============================" ) } var jclazz = null ;var jobj = null ;function getObjClassName (obj ) { if (!jclazz) { var jclazz = Java .use ("java.lang.Class" ); } if (!jobj) { var jobj = Java .use ("java.lang.Object" ); } return jclazz.getName .call (jobj.getClass .call (obj)); } function watch (obj, mtdName ) { var listener_name = getObjClassName (obj); var target = Java .use (listener_name); if (!target || !mtdName in target) { return ; } target[mtdName].overloads .forEach (function (overload ) { overload.implementation = function ( ) { printStacks ("WatchEvent" +mtdName) console .log ("[WatchEvent] " + mtdName + ": " + getObjClassName (this )) return this [mtdName].apply (this , arguments ); }; }) } function OnClickListener ( ) { Java .perform (function ( ) { Java .use ("android.view.View" ).setOnClickListener .implementation = function (listener ) { if (listener != null ) { watch (listener, 'onClick' ); } return this .setOnClickListener (listener); }; Java .choose ("android.view.View$ListenerInfo" , { onMatch : function (instance ) { instance = instance.mOnClickListener .value ; if (instance) { console .log ("mOnClickListener name is :" + getObjClassName (instance)); watch (instance, 'onClick' ); } }, onComplete : function ( ) { } }) }) } setImmediate (OnClickListener );
实战 1 https://www.52pojie.cn/thread-1446044-1-1.html
手动思路:
1、反编译源码,通过apk按钮定位到触发的类,以及多个疑似触发方法
2、通过 wallbreak dump出触发时的内存变量,定位到具体的触发方法
3、审视代码,定位密码变量,直接在内存中看
遗留问题:
1、frida 的 hook setOnClickListener 脚本无法手动注入
2、扒第一关源码算法后,打印的值疑似加密,但代码逻辑无加密
坑点:
1、hook equals 到死
2、要用 –no-pause 以免程序暂停
3、编写算法时,类可能会在 onstart 等地方初始化,或是依托特有代码进行处理(用 sendEmptyMessage 发送。handleMessage中处理)
1 2 3 4 5 6 7 8 Java .perform (function ( ){ var Str = Java .use ("java.lang.String" ); Str .equals .implementation = function (a1 ){ console .log (a1); var re = this .equals (a1); return re; } })
Hook 系统SO & 静态注册SO函数 https://www.anquanke.com/post/id/195215#h2-1
Process / Module / Memory / Interceptor.attach
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var module = Process .findModuleByName ("libhookdemo.so" );var pattern = "55 41 57 41 56 41 55 41 54 53 48 83 EC 18 49 89 FE 48 BD EF FF FF FF FF FF FF 3F" ;console .log (module .base )Memory .scan (module .base ,module .size ,pattern,{ onMatch :function (address,size ){ console .log ('搜索到 ' +pattern +" 地址是:" + address.toString ()); }, onError :function (resaon ){ console .log ("搜索失败" ,resaon); }, onComplete :function ( ){ console .log ("搜索完成" ); } });
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 var pointer = Process .findModuleByName ("libc.so" ).base ; const r = Memory .alloc (0x10 ); r.writePointer (pointer); var buffer = Memory .readByteArray (r, 0x10 );var arr = [0x74 ,0x65 ,0x73 ,0x74 ];const r = Memory .alloc (arr.length );Memory .writeByteArray (r,arr);console .log ("readCString():" +r.readCString ());const newPtrstr = r.writeUtf8String ("haha" );console .log ("readCString():" +newPtrstr.readCString ());var arr = [0x74 ,0x65 ,0x73 ,0x74 ];var funaddress = Module .findExportByName (null ,"strstr" );Interceptor .attach (funaddress,{ onEnter :function (args ){ console .log ('Context information:' ); console .log ('Context : ' + JSON .stringify (this .context )); console .log ('Return : ' + this .returnAddress ); console .log ('ThreadId : ' + this .threadId ); console .log ('Depth : ' + this .depth ); console .log ('Errornr : ' + this .err ); }, onLeave : function (retval ) { retval.replace (0 ); } }) var funaddress1 = Module .findExportByName ("libhookdemo.so" ,"Java_com_example_hookdemo_MainActivity_signatureTest" );Interceptor .attach (funaddress1,{ onEnter :function (args ){ var env = Java .vm .getEnv (); if (env!=null ){ var canshu = env.getStringUtfChars (args[2 ],false ); console .log (canshu.readCString ()); } }, onLeave : function (retval ) { var env = Java .vm .getEnv (); if (env!=null ){ var canshu = env.getStringUtfChars (retval,false ); console .log (canshu.readCString ()); } } }) var hookdemo_add = Module .findBaseAddress ("libhookdemo.so" );var add_0D64 = hookdemo_add.add (0x667A0 ); var newaddString_func = new NativeFunction (add_0D64,"pointer" ,['pointer' ,'pointer' ]);var env = Java .vm .getEnv ();var canshu1 = env.newStringUtf ("test" );var canshu2 = env.newStringUtf ("nihao" );canshu1 = env.getStringUtfChars (canshu1,false ); canshu2 = env.getStringUtfChars (canshu2,false ); var ret = newaddString_func (canshu1,canshu2);console .log (ret.readCString ());if (hookdemo_add){ var add_0D64 = hookdemo_add.add (0x667A0 ); console .log ("add_0D64 =>" ,add_0D64); Interceptor .attach (add_0D64,{ onEnter :function (args ){ console .log ("args[0] =>" ,args[0 ].readCString ()); console .log ("args[1] =>" ,args[1 ].readCString ()); }, onLeave :function (retval ){ console .log (hexdump (retval)) } }) } 找到某方法的执行首地址 1 、直接在IDA 里的导出方法搜索,双击进入到具体的处理逻辑,在开头按Tab 跳转汇编查看地址2 、找到某个已注册方法,并调用了目标方法,后续操作如上3 、IDA 的导出函数可以(静态so方法,及非动态的方法实现)
1、根据内存中的地址 hook 函数 2、不确定 so 名,hook 其中的导出函数 3、hook 自实现的静态 so 函数 4、根据内存地址去主动调用函数
Hook 动态注册SO函数: https://github.com/lasting-yang/frida_hook_libart
1 2 3 4 5 6 7 8 9 10 原生打印出来的 symbols 需要转换 https://demangler.com/ hook sayhello,不知道包名求出这个注册函数的地址 RegisterNatives 函数专门用于动态注册,hook这个就行 动态注册在启动时就注入,用 attach 模式 Process.findModuleByName("libhookdemo.so") // 得到当前进程内so的地址 1、hook所有的so函数 2、hook动态注册函数
RPC 开放调用Java层和SO层函数 应用场景:https://bbs.kanxue.com/thread-259862.htm
修改默认端口(27042)
1 2 ./frida-server -l 0.0.0.0:port frida -H ip:port -F
用 web 服务(flask)映射手机内的接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 function javafunc (name ){ var perins = null ; Java .perform (function ( ){ var perclass = Java .use ("com.example.hookdemo.Person" ); perins = perclass.$new(name,20 ,"zhongguo" ); }) send (perins.getName ()) var recy = recv ('poke' ,function (value ){ console .log (value.payload ) }) recy.wait () return perins.getName (); } function sofunc (a1,a2 ){ var hookdemo_add = Module .findBaseAddress ("libhookdemo.so" ); var add_0D64 = hookdemo_add.add (0x60D54 ); var newaddString_func = new NativeFunction (add_0D64,"pointer" ,['pointer' ,'pointer' ]) var env = Java .vm .getEnv (); var canshu1 = env.newStringUtf (a1); var canshu2 = env.newStringUtf (a2); canshu1 = env.getStringUtfChars (canshu1,false ); canshu2 = env.getStringUtfChars (canshu2,false ); var ret = newaddString_func (canshu1,canshu2); return ret.readCString (); } rpc.exports = { javafun :javafunc, sofunc :sofunc, }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 import fridaimport timeimport flask,requestsfrom flask import Flask, requestapp = Flask(__name__) def on_message (message, data ): if message['type' ] == 'send' : print (message['payload' ]) script.post({"type" :"poke" ,"payload" :"woshipython" }) elif message['type' ] == 'error' : print (message['stack' ]) session = frida.get_usb_device(5000 ).attach('com.example.hookdemo' ) with open ("./function.js" )as f: source = f.read() script = session.create_script(source) script.on('message' , on_message) script.load() @app.route("/" ) def index (): s1 = request.args.get('a1' ) s2 = request.args.get('a2' ) return script.exports.sofunc(s1,s2) if __name__ == "__main__" : app.run(host='0.0.0.0' , port=8888 , debug=True )
1、js 编写 frida 脚本,把函数导出 2、python 脚本调用 js 导出函数,开启 flask 服务供第三方调用 3、frida Python与js脚本交互
https://blog.csdn.net/Qwertyuiop2016/article/details/114286877
Hook interface接口 搜 implement 关键词
加密算法 URLTOHEX 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <stdio.h> #include <string.h> void encodeToHex (const unsigned char * data,int dataSize,char * encodeData) { const char * hexChars = "0123456789ABCDEF" ; int i,j; for (i=0 ,j=0 ; i<dataSize; ++i){ encodeData[j++] = hexChars[(data[i] >> 4 ) & 0x0f ]; encodeData[j++] = hexChars[data[i] & 0x0f ]; } encodeData[j]='\0' ; } int main () { const char * inputData = "muxss" ; int inputSize = strlen (inputData); char encodeOutPut[strlen (inputData)]; encodeToHex(inputData,inputSize,encodeOutPut); printf ("Encode data: %s\n" ,encodeOutPut); return 0 ; }
字符串转换为十六进制的原理是逐个字符读取字符串中的每个字符,获取其对应的 ASCII 值,然后将该整数值转换为十六进制表示。
每个字符占一个字节,两个十六进制
Base64 ASCII 编码用 256(2的8次方)个字符对二进制数据进行编码
Base64 用 64(2的6次方)个字符对二进制数据进行编码,二进制数据到字符的过程
原理:
用 64(2的6次方)个特定的 ASCII 字符来表示 256(2的8次方)个 ASCII 字符,也就是说三个 ASCII 字符经过 Base64 编码后变为四个 ASCII 字符显示(公约数为24),编码后数据长度比原来增加 1/3,不足 3n用”=”补齐。
小写字母a-z、大写字母A-Z、数字0-9、符号”+”、”/“(再加上作为垫字的”=”,实际上是65个字符)有时候+/会变成 - , _
第一步:将每三个字节作为一组,一个是24个二进制位
第二步,将这24个二进制位分为四组,每个组有6个二进制位
第三步,在每组前面加两个00,扩展成32个二进制位,即四个字节
第四步,根据下表,得到扩展后的每个字节的对应符号,就是Base64的编码值
Base64编码表自查
111 => MTEx,每个字符占一个字节,每个字节有8位,总共有24位,而base64编码每个字符用6位表示,所以3个字节需要用4个可打印字符来表示。
1 2 3 4 5 6 F T D 70 84 68 01000110 01010100 01000100 (00)010001 (00)100101 (00)010001 (00)000100 17 37 17 4 R I R E
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 #include <stdio.h> #include <stdlib.h> #include <string.h> static const char base64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" ;char * base64_encode (const unsigned char * input, int length) { int output_length = 4 * ((length + 2 ) / 3 ); char * encoded_data = (char *)malloc (output_length + 1 ); if (encoded_data == NULL ) { return NULL ; } int i, j; for (i = 0 , j = 0 ; i < length; i += 3 , j += 4 ) { unsigned char byte1 = input[i]; unsigned char byte2 = (i + 1 < length) ? input[i + 1 ] : 0 ; unsigned char byte3 = (i + 2 < length) ? input[i + 2 ] : 0 ; encoded_data[j] = base64_table[byte1 >> 2 ]; encoded_data[j + 1 ] = base64_table[((byte1 & 0x03 ) << 4 ) | (byte2 >> 4 )]; encoded_data[j + 2 ] = base64_table[((byte2 & 0x0F ) << 2 ) | (byte3 >> 6 )]; encoded_data[j + 3 ] = base64_table[byte3 & 0x3F ]; } int padding = length % 3 ; if (padding == 1 ) { encoded_data[j - 2 ] = '=' ; encoded_data[j - 1 ] = '=' ; } else if (padding == 2 ) { encoded_data[j - 1 ] = '=' ; } encoded_data[output_length] = '\0' ; return encoded_data; } unsigned char * base64_decode (const char * input, int * length) { int input_length = strlen (input); if (input_length % 4 != 0 ) { return NULL ; } int output_length = input_length / 4 * 3 ; if (input[input_length - 1 ] == '=' ) { output_length--; if (input[input_length - 2 ] == '=' ) { output_length--; } } unsigned char * decoded_data = (unsigned char *)malloc (output_length); if (decoded_data == NULL ) { return NULL ; } int i, j; for (i = 0 , j = 0 ; i < input_length; i += 4 , j += 3 ) { unsigned char ch1 = strchr (base64_table, input[i]) - base64_table; unsigned char ch2 = strchr (base64_table, input[i + 1 ]) - base64_table; unsigned char ch3 = strchr (base64_table, input[i + 2 ]) - base64_table; unsigned char ch4 = strchr (base64_table, input[i + 3 ]) - base64_table; decoded_data[j] = (ch1 << 2 ) | (ch2 >> 4 ); if (j + 1 < output_length) { decoded_data[j + 1 ] = (ch2 << 4 ) | (ch3 >> 2 ); } if (j + 2 < output_length) { decoded_data[j + 2 ] = (ch3 << 6 ) | ch4; } } *length = output_length; return decoded_data; } int main () { const unsigned char * original_data = "Hello" ; int original_length = strlen (original_data); char * encoded_data = base64_encode(original_data, original_length); printf ("Base64 encoded data: %s\n" , encoded_data); int decoded_length; unsigned char * decoded_data = base64_decode(encoded_data, &decoded_length); printf ("Base64 decoded data: " ); for (int i = 0 ; i < decoded_length; i++) { printf ("%c" , decoded_data[i]); } printf ("\n" ); free (encoded_data); free (decoded_data); return 0 ; }
消息摘要算法 消息摘要算法?
算法助手同理
md5
WT-JS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 const config = { showStacks : false , showDivider : true , } Java .perform (function ( ) { function showStacks (name = '' ) { if (config.showStacks ) { console .log (Java .use ('android.util.Log' ).getStackTraceString (Java .use ('java.lang.Throwable' ).$new(name))) } } function showDivider (name = '' ) { if (config.showDivider ) { console .log (`==============================${name} ==============================` ) } } function showArguments ( ) { console .log ('arguments: ' , ...arguments ) } const ByteString = Java .use ('com.android.okhttp.okio.ByteString' ) const Encode = { toBase64 (tag, data ) { console .log (tag + ' Base64: ' , ByteString .of (data).base64 ()) }, toHex (tag, data ) { console .log (tag + ' Hex: ' , ByteString .of (data).hex ()) }, toUtf8 (tag, data ) { console .log (tag + ' Utf8: ' , ByteString .of (data).utf8 ()) }, toAll (tag, data ) { Encode .toUtf8 (tag, data) Encode .toHex (tag, data) Encode .toBase64 (tag, data) }, toResult (tag, data ) { Encode .toHex (tag, data) Encode .toBase64 (tag, data) }, } const MessageDigest = Java .use ('java.security.MessageDigest' ) { let overloads_update = MessageDigest .update .overloads for (const overload of overloads_update) { overload.implementation = function ( ) { const algorithm = this .getAlgorithm () showDivider (algorithm) showStacks (algorithm) Encode .toAll (`${algorithm} update data` , arguments [0 ]) return this .update (...arguments ) } } let overloads_digest = MessageDigest .digest .overloads for (const overload of overloads_digest) { overload.implementation = function ( ) { const algorithm = this .getAlgorithm () showDivider (algorithm) showStacks (algorithm) const result = this .digest (...arguments ) if (arguments .length === 1 ) { Encode .toAll (`${algorithm} update data` , arguments [0 ]) } else if (arguments .length === 3 ) { Encode .toAll (`${algorithm} update data` , arguments [0 ]) } Encode .toResult (`${algorithm} digest result` , result) return result } } } const Mac = Java .use ('javax.crypto.Mac' ) { Mac .init .overload ('java.security.Key' , 'java.security.spec.AlgorithmParameterSpec' ).implementation = function (key, AlgorithmParameterSpec ) { return this .init (key, AlgorithmParameterSpec ) } Mac .init .overload ('java.security.Key' ).implementation = function (key ) { const algorithm = this .getAlgorithm () showDivider (algorithm) showStacks (algorithm) const keyBytes = key.getEncoded () Encode .toAll (`${algorithm} init Key` , keyBytes) return this .init (...arguments ) } let overloads_doFinal = Mac .doFinal .overloads for (const overload of overloads_doFinal) { overload.implementation = function ( ) { const algorithm = this .getAlgorithm () showDivider (algorithm) showStacks (algorithm) const result = this .doFinal (...arguments ) if (arguments .length === 1 ) { Encode .toAll (`${algorithm} update data` , arguments [0 ]) } else if (arguments .length === 3 ) { Encode .toAll (`${algorithm} update data` , arguments [0 ]) } Encode .toResult (`${algorithm} doFinal result` , result) return result } } } const Cipher = Java .use ('javax.crypto.Cipher' ) { let overloads_init = Cipher .init .overloads for (const overload of overloads_init) { overload.implementation = function ( ) { const algorithm = this .getAlgorithm () showDivider (algorithm) showStacks (algorithm) if (arguments [0 ]) { const mode = arguments [0 ] console .log (`${algorithm} init mode` , mode) } if (arguments [1 ]) { const className = JSON .stringify (arguments [1 ]) if (className.includes ('OpenSSLRSAPrivateKey' )) { } else { const keyBytes = arguments [1 ].getEncoded () Encode .toAll (`${algorithm} init key` , keyBytes) } } if (arguments [2 ]) { const className = JSON .stringify (arguments [2 ]) if (className.includes ('javax.crypto.spec.IvParameterSpec' )) { const iv = Java .cast (arguments [2 ], Java .use ('javax.crypto.spec.IvParameterSpec' )) const ivBytes = iv.getIV () Encode .toAll (`${algorithm} init iv` , ivBytes) } else if (className.includes ('java.security.SecureRandom' )) { } } return this .init (...arguments ) } } let overloads_doFinal = Cipher .doFinal .overloads for (const overload of overloads_doFinal) { overload.implementation = function ( ) { const algorithm = this .getAlgorithm () showDivider (algorithm) showStacks (algorithm) const result = this .doFinal (...arguments ) if (arguments .length === 1 ) { Encode .toAll (`${algorithm} update data` , arguments [0 ]) } else if (arguments .length === 3 ) { Encode .toAll (`${algorithm} update data` , arguments [0 ]) } Encode .toResult (`${algorithm} doFinal result` , result) return result } } } const Signature = Java .use ('java.security.Signature' ) { let overloads_update = Signature .update .overloads for (const overload of overloads_update) { overload.implementation = function ( ) { const algorithm = this .getAlgorithm () showDivider (algorithm) showStacks (algorithm) Encode .toAll (`${algorithm} update data` , arguments [0 ]) return this .update (...arguments ) } } let overloads_sign = Signature .sign .overloads for (const overload of overloads_sign) { overload.implementation = function ( ) { const algorithm = this .getAlgorithm () showDivider (algorithm) showStacks (algorithm) const result = this .sign () Encode .toResult (`${algorithm} sign result` , result) return this .sign (...arguments ) } } } })
对称加密算法 & 非对称加密算法 DES/CBC/PKCS5Padding 原理自查
代码如上
其他乱七八糟的 图解 HTTP
okhttp框架(学习hook绕过证书单向校验、证书双向校验)
https://github.com/WooyunDota/DroidSSLUnpinning?tab=readme-ov-file
https://codeshare.frida.re/@akabe1/frida-multiple-unpinning/
https://ch3nye.top/Android-HTTPS认证的N种方式和对抗方法总结
下载导入 httpstest,apk源码
SSL PINNING 更新公钥和证书:
1、修改百度公钥,from hex,tobase64
2、修改bing.com的证书校验,导出baidu的证书,校验的网站也改成百度
对抗VPN和代理
ProxyDroid
iptables——在app限制代理和vpn抓包下的另类 解决办法
r0capture
flutter (app开发框架) 抓包
IDA 搜索 ssl_client,选择所在函数头的特征码,operation-General-Number of opcode bytes (4)
用 frida hook app 内部 SSL 验证,把校验关了就能抓到包了。用在挂了代理/VPN抓不到包的场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 function hook_ssl_verify_result (address ) { Interceptor .attach (address, { onEnter : function (args ) { console .log ("Disabling SSL validation" ) }, onLeave : function (retval ) { console .log ("Retval: " + retval); retval.replace (0x1 ); } }); } function hookFlutter ( ) { var m = Process .findModuleByName ("libflutter.so" ); var pattern = "FF 03 05 D1 FD 7B 0F A9 9A E3 05 94 08 0A 80 52 48 00 00 39 16 54 40 F9 56 07 00 B4 C8 02 40 F9 08 07 00 B4" ; var res = Memory .scan (m.base , m.size , pattern, { onMatch : function (address, size ){ console .log ('[+] ssl_verify_result found at: ' + address.toString ()); hook_ssl_verify_result (address); }, onError : function (reason ){ console .log ('[!] There was an error scanning memory' ); }, onComplete : function ( ) { console .log ("All done" ) } }); }
动态加载 dex,dex文件是apk的二进制包?
三代加壳技术
frida_dexdump,用objection加载插件,python启动
grep -ril “xxx” * //在dex目录搜关键词
一代壳直接dump,内存即完整
二代壳动态加载,youpk
第一次启动 frida 时的 venv 环境切换
抓取海外app的包 Charles_Proxy_External Proxy Settings_打勾前两个,用于设置 charles 的上游出口
app ->VPN -> Charles -> 本地proxy ->服务器
用在线工具把请求头直接转换成py代码、右键copy cURL Request
https://curlconverter.com
分析 x-xxx-devices
1、apk直接拖进 jadx
2、搜索 / 自吐脚本或者算法助手 / 手动定位
3、jadx 选中字段按 x,查找调用
4、默认手机里安装的apk在,/data/data/包名/shared_prefs
5、清除应用信息,缓存的文件就没了
6、有个a方法
https://boyyongxin.github.io/2021/06/10/%E5%A6%82%E4%BD%95%E6%8A%93%E5%8F%96%E5%9B%BD%E5%A4%96%E7%BD%91%E7%AB%99%E3%80%81app%E7%9A%84%E5%8C%85
1、搜索字段 x-xxx-signature 2、密码学基础:(这个key就是加密值) 2.1、mac.update(key) 2.2、doFinal(key)
(根据digest往上找,根据空字符串去做SHA256加密->FROM HEX->to base64)
HmacSHA256 update data Utf8: digest:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
host:openapi.xxx.com
key-id:gf665hj0kpsdkpllgek11zrz
target:get /xxxapps/v3/bespoke/member/search-with-ads/mmx?keywords=iphone case mockup¤cy=CNY&buyer_features[app_foreground_time]=1730735563258&buyer_features[app_initial_start_time]=1730304497092&buyer_features[device_model]=OnePlus GM1910&buyer_features[network_type]=WWAN&with_static_filters=true&include_additional_listing_images=true&ship_to=CN&instant_download=null&category_prolist=1&sort_on=score&sort_order=down&include_featured_categories=false&with_deep_facets=1
timestamp:1730736403
version:1
x-xxx-device:1k5BCmYBShylRczcYkN5HQ
HmacSHA256 update data Hex: 6469676573743a3437444....
HmacSHA256 update data Base64: ZGlnZXN0OjQ3REVRcGo4S....
HmacSHA256 doFinal result Hex: a1dd6501b449be5b....
HmacSHA256 doFinal result Base64: od1lAbRJvlvA794Un2/vXWnM6L0DWHriXmWEwENNiD4=
AES/CTR/NoPadding update data Utf8: oauth2_access_token
AES/CTR/NoPadding update data Hex: 6f61757468325f6163636573735f746f6b656e
AES/CTR/NoPadding update data Base64: b2F1dGgyX2FjY2Vzc190b2tlbg==
AES/CTR/NoPadding doFinal result Hex: 2def734bbf607b03703dbf58ca6ded8ebf578d
AES/CTR/NoPadding doFinal result Base64: Le9zS79gewNwPb9Yym3tjr9XjQ==
3、算法还原(cyberchef / java代码) hmac (key、SHA256) FROM HEX TO BASE64
1 2 3 4 5 6 7 device_id.py UUID randomUUID = UUID.randomUUID(); ByteBuffer wrap = ByteBuffer.wrap(new byte[16]); wrap.putLong(randomUUID.getMostSignificantBits()); wrap.putLong(randomUUID.getLeastSignificantBits()); return Base64.encodeToString(wrap.array(), 11);
xBox 1、frida-dump 脱壳多个dex,利用其他 frida 脚本 youpk / aprt / FART脱壳
1 2 3 4 5 6 7 8 frida脚本脱壳 1、dex写入目录需要权限 2、mkdir box_fart / mv *.dex box_fart/ 3、adb pull xxx/box_fart grep -ril "xxxMainActivity" //当前目录的dex搜索 Youpk logcat
https://bbs.kanxue.com/thread-259854.htm
脱下来也是多个dex
疑问:为什么这里要用Youpk脱壳呢,用frida-dump不行嘛
2、过 root 检测
1 2 3 4 5 6 7 8 9 10 11 12 1、jadx会重命名,要hook原来的包名 2、js脚本 function xxx(){ Java.perform(function(){ xxx }) } function main() { xxx() } setInterval(main, 1000); // 有壳 frida 脚本要延迟加载
绕过后没数据,有VPN检测
利用 AS 的 logcat 可以查看当前手机运行的 app 打印的日志
用 AS 查看方便点,直接 adb logcat 也行
3、flutter框架的抓包方式
https://bbs.kanxue.com/thread-261941.htm
https://juejin.cn/post/7106300111927377956
1 2 3 4 5 6 7 8 9 10 11 12 13 直接按压缩包方式解压找到 libflutter.so,分析 SSL 入口点 1、IDA 打开 libflutter.so 2、Shift+F12 搜索 ssl_client 3、找引用 4、反汇编 5、找开头 sub_xxx shift+f12:可以打开string窗口,一键找出所有的字符串,右击setup,还能对窗口的属性进行设置 x:对着某个函数、变量按该快捷键,可以查看它的交叉引用 g:直接跳转到某个地址 f5:一键反汇编
HttpCanary
wToken
4、RPC远程调用
PANDA(8.33.0) md5可能是16位也可能是32位
1、算法助手和自吐脚本都是hook某个操作过程的算法操作,可用于分析加密
2、自吐脚本是通用的嘛
https://www.secpulse.com/archives/177572.html
request_pkcs12
Charles 抓包小tips & 问题汇总:
1、开启 Enabke SSL Proxy就是为了抓https的包,证书有问题的情况下,开启后无法访问
2、模拟器放到 /storage/emulated/0/Pictures/ 目录下
3、模拟器和主机重启,不行就再重启(postren走的是socket5)
4、真机每次切换证书可以试试重启,抓不到包换个浏览器,真机有时候只能用默认浏览器抓包,可能是证书安装有问题,其他浏览器不信任系统证书。但APP是可以抓包的