环境准备
夜神模拟器
frida-server + frida
andriod studio (可选)
东西准备的不算多,adb用夜神模拟器自带的就可以,别的就不多赘述了,别的博主那里已经写的非常清楚了,唯一要关注一点的是在新版夜神模拟器默认的端口是62025,adb只有连这个端口才能上的去,和网上说的有些差异。
对安卓的基本认识 文件层面
Class 和我们平常理解的字节码运行机制不一样,在Android平台上,.class
文件不能直接在Android设备上运行,因为Android使用了自己的运行时环境(最初是Dalvik虚拟机,后来转为ART),而不是标准的Java虚拟机(JVM)。在Android开发过程中,Java源代码会被编译成.class
文件,然后再通过工具将这些.class
文件转换成Dalvik可执行的.dex
(Dalvik Executable)文件。.dex
文件包含了Dalvik虚拟机可以理解和执行的指令集。
Dex dex文件是Android平台上的可执行文件。在编译Java代码之后,通过Android平台上的工具可以将Java字节码转换成Dex字节码。
Apk apk文件是Android上的安装文件。一个Android安装包包含了与某个Android应用程序相关的所有文件。apk文件将AndroidManifest.xml文件、应用程序代码(.dex文件)、资源文件和其他文件打成一个压缩包。
三者关系大致如以下表格所示。
文件类型
描述
APK
Android 应用程序包(Android Package),包含了应用的所有组件和资源文件,是安装和分发应用的主要格式,APK的组成如下所示。
核心的字节码文件(Dex 文件):存储了应用的字节码指令,以供 Android 设备的 Dalvik 或 ART 虚拟机执行。
资源文件:包括应用所需的图片、布局、字符串等资源文件。
清单文件(Manifest 文件):描述了应用的组件、权限要求等信息。
签名文件:APK 文件经过数字签名,用于验证应用的来源和完整性。
Class
Java 字节码文件,生成自 Java 源代码编译后的结果。Class 文件包含了应用程序的类、方法、字段等信息。在 Android 开发中,这些 Class 文件会被转换成 Dex 文件后才能在 Dalvik 或 ART 虚拟机上执行。
Dex
Dex(Dalvik Executable)文件是 Android 平台上特定的字节码文件格式,用于在 Dalvik 或 ART 虚拟机上执行。Dex 文件是为了优化在 Android 设备上的内存和执行效率而设计的。它将来自多个 Class 文件的字节码合并到一个文件中,并采用一些优化技术,例如压缩和跨方法优化。应用的所有 Class 文件经过转换,最终会生成一个 Dex 文件,该文件包含了应用的所有字节码指令。
抽象层次介绍
Java层 : 由Android 应用程序的主要开发层,包含了应用程序的各种功能和组件的实现逻辑。以下是 Java 层中常见的组件
Activity层 : 负责管理应用程序中的一个界面或一个屏幕,处理用户的输入、事件响应和生命周期管理等。【这个用的可多了】
Service层 : 在后台执行长时间运行的操作,没有用户界面,用于处理一些独立于界面的任务,如播放音乐、下载文件等。
BroadcastReceiver层:用于接收系统或其他应用程序发送的广播消息,可以在应用程序之间传递信息。
ContentProvider层:用于管理应用程序的数据共享,允许其他应用程序访问数据,如联系人、媒体文件等。
Fragment(片段):可以看作是 Activity 的一部分,用于构建灵活的用户界面,可以独立管理自己的生命周期和事件。
Intent(意图):用于在不同组件之间进行通信和传递数据,如启动另一个 Activity 或发送广播等。
Native层: 也被称为 C/C++ 层,用于编写性能敏感的组件。在 Native 层,开发人员可以使用 C/C+和相关的库来实现一些底层功能。常见的 Native 层库包括 OpenGL ES(图形渲染)、OpenSL ES(音频处理)等。Native 层的代码通常会编译成库文件,以.so (共享对象)文件的形式存在 ( Native层和Java层的交互依赖so来实现 )
Linux 内核层: Linux 内核层是 Android 操作系统的基础,Android 是基于 Linux 内核开发的。Linux 内核层负责管理硬件设备、提供系统服务和驱动等功能。它包括了设备驱动程序、文件系统、内存管理、进程管理、网络协议栈等。Linux 内核层提供了 Android 平台上的基本功能,允许应用程序通过系统服务和底层接口与硬件进行交互。
Frida使用 基本命令 模拟器端 (启动frida-server)
1 2 3 4 5 1. adb connect 127.0.0.1:62025 2. adb root 3. adb shell 4. cd /data/local/tmp 5. ./fs
模拟器端(查看进程包名)
frida-client
1 2 1.frida -U -f 包名 -l 要执行的js脚本 //未启动的app 2.frida -U 包名 - l 要执行的js脚本 //已启动的app
Hook模板
目标函数替换1 2 3 4 5 6 Java.perform(function() { var TargetClass = Java.use('目标className' ); TargetClass.目标methodName.implementation = function() { console.log('targetMethod is hooked' ); }; });
实战案例 分享一道简单题吧,2015年的一道猜拳的CTF题,这里通过jadx反编译得到以下源码。
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 package com.example.seccon2015.rock_paper_scissors;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.view.View;import android.widget.Button;import android.widget.TextView;import java.util.Random;public class MainActivity extends Activity implements View .OnClickListener { Button P; Button S; int flag; int m; int n; Button r; int cnt = 0 ; private final Handler handler = new Handler (); private final Runnable showMessageTask = new Runnable () { public void run () { TextView tv3 = (TextView) MainActivity.this .findViewById(R.id.textView3); if (MainActivity.this .n - MainActivity.this .m == 1 ) { MainActivity.this .cnt++; tv3.setText("WIN! +" + String.valueOf(MainActivity.this .cnt)); } else if (MainActivity.this .m - MainActivity.this .n == 1 ) { MainActivity.this .cnt = 0 ; tv3.setText("LOSE +0" ); } else if (MainActivity.this .m == MainActivity.this .n) { tv3.setText("DRAW +" + String.valueOf(MainActivity.this .cnt)); } else if (MainActivity.this .m < MainActivity.this .n) { MainActivity.this .cnt = 0 ; tv3.setText("LOSE +0" ); } else { MainActivity.this .cnt++; tv3.setText("WIN! +" + String.valueOf(MainActivity.this .cnt)); } if (1000 == MainActivity.this .cnt) { tv3.setText("SECCON{" + String.valueOf((MainActivity.this .cnt + MainActivity.this .calc()) * 107 ) + "}" ); } MainActivity.this .flag = 0 ; } }; public native int calc () ; static { System.loadLibrary("calc" ); } @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); this .P = (Button) findViewById(R.id.button); this .S = (Button) findViewById(R.id.button3); this .r = (Button) findViewById(R.id.buttonR); this .P.setOnClickListener(this ); this .r.setOnClickListener(this ); this .S.setOnClickListener(this ); this .flag = 0 ; } @Override public void onClick (View v) { if (this .flag != 1 ) { this .flag = 1 ; TextView tv3 = (TextView) findViewById(R.id.textView3); tv3.setText("" ); TextView tv = (TextView) findViewById(R.id.textView); TextView tv2 = (TextView) findViewById(R.id.textView2); this .m = 0 ; Random rm = new Random (); this .n = rm.nextInt(3 ); String[] ss = {"CPU: Paper" , "CPU: Rock" , "CPU: Scissors" }; tv2.setText(ss[this .n]); if (v == this .P) { tv.setText("YOU: Paper" ); this .m = 0 ; } if (v == this .r) { tv.setText("YOU: Rock" ); this .m = 1 ; } if (v == this .S) { tv.setText("YOU: Scissors" ); this .m = 2 ; } this .handler.postDelayed(this .showMessageTask, 1000L ); } } }
虽然从来没学过安卓,但是因为咱们学过英语,所以大体还是能够猜出来代码的意思的,需要我们猜赢1000次才能拿到flag,中间连平局都不能有。因为这里的关键逻辑在onClick这里,我们直接hook它就行,但在hook之前还需要我们查出来包名,虽然这里用jadx直接能看到,但还是演示下正常的做法。
在shell下输完以上命令后,再到模拟器里面打开要测试的app就会自己包名就会自己出来哩
考虑到实际情况下,直接代审查函数也挺麻烦的,这里演示一下如何通过frida来查找当前已经调用的函数,缩小代审范围,首先先准备好以下脚本当传家宝(要是会用objection也行,这里就当我没说就行)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Java.perform(function() { var packageName = "com.example.seccon2015.rock_paper_scissors" ; Java.enumerateLoadedClasses({ onMatch: function(className) { if (className.startsWith(packageName)) { var targetClass = Java.use(className); var methods = targetClass.class.getDeclaredMethods(); console.log("Class: " + className); for (var i = 0 ; i < methods.length; i++) { console.log(methods[i].toString()); } console.log("-------------------------------------" ); } }, onComplete: function() {} }); });
不知道为什么,这个模拟器用spwan模式老是崩,这里就用attach模式来做演示吧,先用adb连上去查一下pid
1 2 adb shell ps -elf|grep "com.example.seccon2015.rock_paper_scissors"
这里查出来是8590,接着直接运行frida,得到以下结果,可以看到已经通过内存漫游,找到了并打印了我们自定义的函数
然后再附上我们这里用来hook onClick()
的脚本
1 2 3 4 5 6 7 8 9 10 11 12 Java .perform (function ( ) { var MainActivity = Java .use ('com.example.seccon2015.rock_paper_scissors.MainActivity' ); var onClick = MainActivity .onClick ; onClick.implementation = function (v ) { send ('onClick' ); this .m .value = 0 ; this .n .value = 1 ; this .cnt .value = 999 ; console .log ('Done:' + JSON .stringify (this .cnt )); }; });
返回结果如下,手快,多按了几下 得到flag 参考链接https://www.anquanke.com/post/id/197657#h3-6 Frida Hook Android App 笔记 | clicker’s blogs (sleepydogyp.github.io)