菜鸡啥都学点,啥都不精。

image-20250113134711544

工欲善其、事必先利其器

安卓APK逆向的工具

雷电模拟器运行APK

雷电模拟器,作为安卓模拟器的佼佼者,一直以来备受用户青睐。它不仅可以让你在PC上畅快运行安卓应用,还能提供与手机端接近的使用体验,让你在开发、调试乃至游戏娱乐中都能游刃有余。安装雷电模拟器其实并不复杂,但要确保顺利完成,还是有一些细节需要关注。

没用过,不清楚

https://www.ldmnq.com/

ADB的使用

ADB(Android Debug Bridge),简直是安卓开发者和逆向工程师的“瑞士军刀”。无论是调试、安装应用,还是进行日志分析,ADB都是不可或缺的工具。你可能会认为ADB只是一个命令行工具,然而它的强大远超你的想象。

没有用过,只使用GDB。

使用方法:ADB安装及使用详解(非常详细)从零基础入门到精通,看完这一篇就够了-CSDN博客

下载地址:ADB下载地址

这里笔者也就配置了一下环境变量,然后看了看版本啥的

image-20250504131043362

具体的命令,没尝试过

JADX反编译APK

JADX是一款非常流行且功能强大的APK反编译工具,它能够将APK中的DEX文件(即Dalvik Executable文件)反编译成可读的Java源代码。JADX的优势不仅仅在于它的易用性,还在于它的反编译效果非常优秀,能够清晰地显示反编译后的Java代码,帮助开发者和安全人员深入理解应用的内部逻辑。

使用方法:APK反编译工具jadx - chuyaoxin - 博客园 (cnblogs.com)

下载地址:JADX下载地址

做过安卓题了解一点,看看加密逻辑,然解包啥的

GAD进行APK反汇编

GAD(Google Android Disassembler)是一个专注于APK底层字节码分析的工具。与JADX不同,GAD更多侧重于字节码级别的反汇编,它能够帮助安全研究人员和开发者深入到应用的最底层,查看其具体的机器码和执行逻辑。GAD特别适用于那些对字节码和汇编语言感兴趣的逆向工程师,它可以帮助我们获得应用中深层次的行为信息。

使用方法:[原创]GDA使用手册-Android安全-看雪-安全社区|安全招聘|kanxue.com

下载地址:GAD下载地址

没用过

image-20250504132816228

JEB进行APK反汇编

JEB的魅力在于其高精度的反汇编能力。它不仅能解析传统的DEX文件,还能处理各类复杂的文件格式,包括加固过的APK、经过混淆处理的代码,甚至是一些非标准的Android文件结构。它像一把锐利的刀刃,切开了应用的“外壳”,揭示其最核心的部分。

使用方法:第36讲: 使用Jeb工具反编译安卓APK_jeb反编译工具-CSDN博客

下载地址:JEB下载地址

没用过,简单安装一下java环境,就完了

IDA进行反汇编

Frida脱壳工具的使用

Frida 是一个强大的动态分析工具,广泛应用于反向工程和安全测试中,尤其是在对 Android 应用进行脱壳(解除保护)时,它能够帮助研究人员通过动态注入脚本来分析应用程序的行为。以下是使用 Frida 进行脱壳的环境配置和基本步骤。

Frida相关工具的安卓

1
2
3
4
>pip install frida         # 安装 Frida 主库
>#pip install frida-tools -i https://pypi.tuna.tsinghua.edu.cn/simple
>pip install frida-tools # 安装 Frida 的工具集,提供命令行工具
>pip install frida-dexdump # 安装 frida-dexdump,用于分析 APK 文件的 dex 内容

入门教程:[Frida入门教程] 一文搞定Frida环境搭建,基于逍遥模拟器(虚拟设备)和ADB(Android Debug Bridge)-CSDN博客

image-20250504141941152

APK解析基础

简单讲一下APK。

APK文件是Android操作系统中的应用程序包,它包含了应用的所有资源、代码和必要的配置文件。可以把APK看成一个容器,其中集成了Android应用所有的组成部分。

APK的基本结构

APK文件是以ZIP格式进行压缩打包的,因此,我们可以像操作普通的ZIP文件应用,使用解压工具对其进行解压,通过解压查看APK文件的目录结构,我们能够清晰了解每个组成部分的作用。

image-20250504151632487

我们简单拆一个包看一下其里边结构的目录文件。(题目:Palar靶场的Android)

image-20250504151950996

res

res/目录包含了Android应用所需的所有资源文件。与assets目录不同,res目录中的资源文件是经过编译的,按照不同类型的资源进行组织。

drawable:存放图片资源

layout:存放XML格式的布局文件、定义界面的结构

values:存放各种配置文件,定义应用的常量、颜色、字符串等资源。例如:

string.xml:存储应用的文本字符串

colors.xml:存储应用使用的颜色资源

style.xml:存储样式资源

META-INF

**META-INF/**目录与Java的JAR文件类似,用于存放APK文件的元数据,如签名文件、校验信息等。

MANIFEST.MF:存放APK的清单文件,包含关于APK文件本身的信息

CERT.RSA:包含APK文件的数字签名

CERT.SF:存放APK文件的签名摘要

lib

lib/目录包含了本地库文件,通常是通过JNI(Java Native Interface)与C/C++编写的本地代码。这些库文件可以针对不同的硬件架构(如arm、x86等)进行编译,因此lib/目录下通常会为每个架构创建相应的子目录。这个目录中存放的本地库可以通过Java代码调用JNI接口实现与系统底层的交互。

image-20250506193306201

resources.arsc

resources.arsc文件包含了应用程序的所有编译后的资源映射信息。这个文件并不存储实际的资源内容(如图片或字符串),而是存储资源与资源ID的映射关系。例如,它会保存应用中的字符串、颜色、尺寸、样式等信息以及这些资源的ID。通过这个文件,Android系统能够在应用运行时快速访问和加载所需的资源。

classes.dex

classes.dex文件包含了应用程序的可执行代码。它是应用的Dalvik字节码文件,也是Android应用在运行时通过Dalvik虚拟机ART解释执行的核心文件。每个Android应用中,所有的Java源代码都经过编译后形成一个或多个DEX文件,这些文件包含了应用的业务逻辑和代码实现。

在Android5.0之后,Google引入了ART代替传统的Dakvik虚拟机,ART的执行方式比Dalvik更高效,支持Ahead-of-time编译和即时编译策略。

拓展阅读一下,相关文档:

JAVA虚拟机、Dalvik虚拟机和ART虚拟机简要对比

安卓逆向学习—-smali,dex,java等文件之间转换关系

assets

assets目录包含了应用程序的原始资源文件,这些资源不经过编译,直接以原始形式存储。通常,开发者可以在该目录中存放字体文件、音频文件、HTML文件等,应用在运行时通过API来读取这些资源。例如,游戏可能会将所有的地图文件或纹理图像存放在此目录中。通过AssetManagerAPI,应用可以访问这些文件。

AndroidMainifest.xml(核心关键)

AndroidMainifest.xml是每个Android应用不可或缺的配置文件,他包含了应用的关键信息。我们可以把它看作是应用的“蓝图”或“说明书”,它向系统声明了应用的基本属性、组件以及权限等。

AndroidMainifest.xml包括以下重要的部分:

应用的包名:每个Android应用都有一个唯一的包名,通过包名来区分不同的应用。

应用的组件:声明应用包含哪些组件,以及这些组件的属性和功能。

权限声明:列出应用所需的权限,如访问网络、读取存储、使用相机等。

应用主题和图标:定义应用的UI样式和图标等。

最小SDK版本和目标SDK版本:确定能在什么版本的Android系统上运行。

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
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0"
android:compileSdkVersion="29"
android:compileSdkVersionCodename="10"
package="com.example.myapplication"
platformBuildVersionCode="29" platformBuildVersionName="10">
<uses-sdk
android:minSdkVersion="16"
android:targetSdkVersion="29"/>
<application
android:theme="@style/Theme.MyApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:allowBackup="true"
android:supportsRtl="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:appComponentFactory="androidx.core.app.CoreComponentFactory">
<activity android:name="com.example.myapplication.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>

解析一下关键字段:

  • mainifest:包含整个应用的包信息及权限定义
    • package:定义了应用的包名,通常为反向域名格式,如com.example.app。
    • android:versionCode:定义应用的版本号
    • android:versionName:定义应用版本名称
  • application:包含应用的核心配置,如主题、图标等
    • android:icon:定义应用的图标
    • android:label:定义应用的名称
    • android:theme:应用的UI主题
  • activity:声明应用的各个界面,以及这些Activity的属性和行为
    • android:name:Activity的类名
    • android:label:Activity的标签
    • android:theme:Activity特有的UI主题
  • uses-permission:声明应用所需要的权限,如访问网络、发送短信等
  • intent-filter:定义组件的功能和响应的事件,如Activity的启动方式或Broadcast Receiver接收的广播类型

Android逆向分析中的基础

Android逆向分析是一个深入挖掘应用内部工作原理的过程,通常用于漏洞挖掘、恶意软件分析或应用的安全性研究。

APK反编译与结构分析:如何找出程序的入口点

在进行Android逆向时,首先需要对APK文件进行反编译和结构分析。理解APK的基本结构至关重要,因为它帮助我们定位关键的组件和入口点。一个典型的APK文件包含多个元素,如AndroidMainifest.xml、DEX文件、资源文件和库文件等。

例题:BUUCTF-简单注册器

这里可以看一下笔者的BUUCTF-做题笔记

虽然只给了脚本

基本的逆向APP的流程

image-20250506200127981

  • 将APK拖入jadx后寻找AndroidMainifest.xml文件

image-20250506200449570

简单解释一下

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
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1" <!-- 应用版本代码用于区分不同版本的更新 -->
android:versionName="1.0" <!-- 应用版本名称,通常为可见版本号 -->
package="com.example.flag"> <!-- 应用包名,唯一标识应用 -->
<uses-sdk
android:minSdkVersion="8" <!-- 设置应用的最低 SDK 版本 -->
android:targetSdkVersion="19"/> <!-- 设置应用的最低 SDK 版本 -->
<application
android:theme="@style/AppTheme" <!-- 设置应用的主题 -->
android:label="@string/app_name" <!-- 设置应用的名称 -->
android:icon="@drawable/ic_launcher" <!-- 设置应用的图标 -->
android:debuggable="true" <!-- 设置应用是否可调试,调试模式下可以进行调试 -->
android:allowBackup="true"> <!-- 设置是否允许应用数据备份 -->
<activity
android:label="@string/app_name" <!-- 设置 Activity 的名称 -->
android:name="com.example.flag.MainActivity"> <!-- 定义该 Activity 的完整类名 -->
<intent-filter>

<action android:name="android.intent.action.MAIN"/> <!-- 指定这是应用的主入口 -->
<category android:name="android.intent.category.LAUNCHER"/><!--表明该Activity是启动器中的入口-->


</intent-filter>
</activity>
</application>
</manifest>

我们最主要看的就是应用的入口点和注意activity出现在应用的启动器中(即桌面)。然后找到程序入口点

也就是我们的 android:name="com.example.flag.MainActivity">

  • 我们开始分析activity的生命执行流程

一个Activity的生命周期是:onCreate()–>onStart()–>onResume()–>onPause()–>onStop()–>onDestory()

所以我们先锁定onCreate函数

image-20250506203101039

  • 分析按钮的逻辑代码,解析出需要输入的内容

image-20250506203439735

以我做CTF的思路就是直接找加密逻辑就完事了

1
2
flag = (xx.length() == 32 && xx.charAt(31) == 'a' && xx.charAt(1) == 'b' && (xx.charAt(0) + xx.charAt(2)) + (-48) == 56) ? 0 : 0;
//其实你仔细看就会发现,这可能是混淆的

image-20250506211555786

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
String= "dd2940c04462b4dd7c450528835cca15"
x=list(String)
#a=[]
x[2] = (chr(ord(x[2]) + ord(x[3]) - 50))
x[4] = (chr(ord(x[2]) + ord(x[5]) - 48))
x[30] = (chr(ord(x[31]) + ord(x[9]) - 48))
x[14] = (chr(ord(x[27]) + ord(x[28]) - 97))


for i in range(16):
a = x[31 - i]
x[31 - i] = x[i]
x[i] = a


for i in x:
print(i,end="")
#59acc538825054c7de4b26440c0999dd

然后我们就可以得到了flag,这里意在让我们分析如何找到程序的入口点

壳分析与绕过:简单分析梆梆免费加固

image-20250507115929726

在Android安全分析中,许多应用都使用加固技术来防止反编译和分析。梆梆是一种常见的加固,主要通过修改APK的结构;注入防护代码来提高应用的安全性。

例题:网鼎杯-2020-青龙组-bang

附件:链接:https://pan.baidu.com/s/1gZggDrrdrTe2b3r7X_MeLw?pwd=nksg 提取码:nksg

WP:[CTF逆向-网鼎杯 2020 青龙组]bang-安卓脱壳逆向:frida-dexdump导出得到源码-CSDN博客

梳理加壳APP的逆向流程

image-20250507121654989

  • 将APK拖入JADX后寻找AndroidMainifest.xml文件
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
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0"
android:compileSdkVersion="29"
android:compileSdkVersionCodename="10"
platformBuildVersionName="10"
platformBuildVersionCode="29"
package="com.example.how_debug">
<uses-sdk android:minSdkVersion="15"
android:targetSdkVersion="29"/>
<application android:theme="@style/AppTheme"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:name="com.SecShell.SecShell.ApplicationWrapper"
android:allowBackup="true" android:supportsRtl="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:appComponentFactory="androidx.core.app.CoreComponentFactory">
<activity android:name="com.example.how_debug.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>

我们虽然在xml文件中找到了Activity的MainActivity的方法,但是并无此com.example.how_debug.MainActivity的实现,首先判定的是该app实现了加壳操作!

然后我们继续看我们的xml中配置了Application信息来判定,该app实现了一个自定义操作将Application类进行了自定义修改成了com.SecShell.SecShell.ApplicationWrapper来实现自己的加密逻辑。

拓展

梆梆加固原理:
根据APK文件是否在AndroidManifest.xml配置Applicaiton信息,梆梆加固会做不同的处理:
通过上传Applicaiton不同配置的APK文件:

1.当APK配置有Applicaition信息时,梆梆加固重写Application类

2.当APK未配置Application信息时,梆梆加固新建类,并在AndroidManifest.xml中配置自己Application类

也可以直接看代码逻辑

image-20250507123903194

该静态代码块是应用启动时的第一个执行逻辑,属于加壳和初始化的关键部分。

System.loadLibrary("Shell")这行时代码加载的核心部分。他可能会执行一些关键的安全操作,如检查当前环境是否为调试状态、是否检测到杯反编译等。

  • 开始使用Frida-dexdump工具进行简单的脱壳

这里讲一下笔者遇到的踩坑点

这里笔者的环境当开始全安装VMware里边的win11里边。

BUT!!!!

我们在起雷电模拟器的时候,我们会发现有报错说有虚拟服务未关闭

image-20250507192204199

SO,这里笔者上网找了好久都没有解决问题,最终笔者准备安装在主机上,然后解决了问题

image-20250507192401151

SO?多一事不如少一事吧

言归正传,我们安装完之后,起完这个APK之后(直接将APK拖入雷电模拟器即可),如果起不起来可能就是安卓版本的问题,我们把它换成安卓7.1即可

image-20250507192945502

然后我们起来之后,

  • 首先可以下载一个MT管理器,然后拖入雷电模拟器里边,然后设置一下共享文件夹

image-20250507193735251

然后设置一下

image-20250507193807565

  • 然后上github上找一下自己对应版本的frida版本的server,下载解压后,给我们复制到我们电脑的共享文件下,然后在我们的雷电模拟器里打开安卓的共享路径既可以看我们下载的server文件

image-20250507194205547

  • 然后在我们的左边回到根目录下依次点击/data/local/tmp,然后来到我们的路径下,把右边的文件复制到左边来(鼠标长按即可看见复制),如上图。
  • 然后我们cmd打开命令管理器输入
1
2
3
adb shell 
cd /data/local/tmp
./frida-server-16.4.8-android-x86_64

image-20250507194638765

也就是对应我们的这一步

image-20250507200033304

就跟我们使用ida进行远程调试ELF文件差不多,在这里,笔者还是想夸一下,IDA Pro是真的牛逼

然后贴几个常用的命令和入门文章、视频

ADB常用命令及其用法大全_adb命令-CSDN博客

《安卓逆向这档事》

吾爱破解安卓逆向入门教程《安卓逆向这档事》

  • 然后我们就可以正常frida进行正常调试了

image-20250507195342045

  • 然后我们开始脱壳,寻找两个dex文件
1
2
3
4
# 使用frida-dexdump(frida-dexdump -U -f 包名 -o 保存地址)
# frida-dexdump -U -p port -o 保存地址 //通过端口
# frida-dexdump -U -n n1book1 -o 保存地址 //通过名字
frida-dexdump -U -n how_debug -o ./

image-20250507200742817

文件脱壳文件在当前的路径下 -o ./

image-20250507201030165

  • 直接拖入JADX开始分析工作,脱壳成功后找到了逻辑的主要代码

这里我们直接拖入,我们看见会发现会报错

jadx-gui添加dex文件失败,提示Bad checksum_jeb报错invalid dex checksum-CSDN博客

然后照着改一下即可

flag直接就看见了

image-20250507205334795

反编译有点问题,直接tab(查看smali代码)一下也行

image-20250507205409995

怎么说呢。这里问了一下bananaship师傅,师傅还是建议看一下基础课,就比如吾爱的正己就挺不错的。

资源与布局文件分析

A、如何寻找一个Activity页面的xml页面布局文件的位置?

例题:攻防世界-基础Android

目标:掌握如何定位和分析APK中的资源文件

首先用JADX打开APK找到xml文件,然后分析一下

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
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0"
package="com.example.test.ctf02"
platformBuildVersionCode="24"
platformBuildVersionName="7"> //版本是Android7
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="24"/>
<application
android:theme="@style/AppTheme"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:debuggable="true" //允许abd或者其他工具进行调试
android:allowBackup="true"
android:supportsRtl="true">
<activity android:name="com.example.test.ctf02.MainActivity">
<intent-filter> //程序入口
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<receiver
android:name="com.example.test.ctf02.GetAndChange"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.is.very.fun"/>
</intent-filter>
</receiver>
<activity android:name="com.example.test.ctf02.NextContent"/>
<activity android:name="com.example.test.ctf02.MainActivity2"/>
</application>
</manifest>

更具xml找到函数的入口

根据Activity的生命周期onCreate()-->onStart()-->onResume()-->onPause()-->onStop()-->onDestory()在MianActivity分析一下

image-20250508184931495

1
2
//寻找资源与布局文件的话,就可以锁定下面的函数
setContentView(R.layout.acticity_main_1); //双击进去可以看见资源文件的id= 0x7f04001a

setContentView()是Android中的一个方法,用于设置当前Activity的界面识图(UI)。这个方法通常在Activity的onCreate()方法中调用,用来加载布局文件,并将其显示在当前的界面上

0x7f04001a是一个整数值,代表一个资源的ID。这个资源ID对应的是一个xml布局文件(通常位于res/layout/目录下),在编译过程中由Android构建工具自动生成。

image-20250508185040799

所以我们可以手动寻找布局文件通过Jadx的搜索功能寻找目标资源的名称。

image-20250510171630042

获得资源的id和名称,接下来就是在布局文件寻找,在res目录下的layout目录下找到了activity的页面布局文件:

找到文件之后我们可以对比运行后的app界面:

image-20250511171637214

解题:

misc解法
解包在assets/路径下是一个压缩包,010打开

image-20250511181632390

FF D8这是jpg的文件开头

换zip为jpg打开

image-20250511181714863

得到flag{08067-wlecome}

Android解法

找到函数的入口点为

1
<activity android:name="com.example.test.ctf02.MainActivity">

并且观察到下面是注册了一个广播

1
2
3
4
5
6
7
8
<receiver
android:name="com.example.test.ctf02.GetAndChange"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.is.very.fun"/>
</intent-filter>
</receiver>

直接看MainActivity

image-20250511184136267

点进去看一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Check {
public boolean checkPassword(String str) {
char[] pass = str.toCharArray(); //把输入的字符串转为字符串组
if (pass.length != 12) { //规定密码的长度为12
return false;
}
for (int len = 0; len < pass.length; len++) {
pass[len] = (char) (((255 - len) - 100) - pass[len]); //核心加密校验逻辑
if (pass[len] != '0' || len >= 12) { //每个字符处理后必须等于0(0的ASCll码是48)
return false;
}
}
return true; //所以字符处处理后都为"0",验证通过
}
}

直接逆向即可呗

1
2
3
4
str=list("000000000000")
for i in range(12):
print(char(255-i-100-48),end='')
#kjihgfedcba`

然后我们就可以进入判断

image-20250511191158289

我们看见mainifest.xml知道触发该广播的内容是“android.is.very.fun”,而GetAndChange是广播接收器,我们继续跟进GetAndChange之后启动NextContent之后就是吧zip文件中转换jpg的逻辑过程

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
public class NextContent extends AppCompatActivity {
ImageView imageView;

@Override // android.support.v7.app.AppCompatActivity, android.support.v4.app.FragmentActivity, android.support.v4.app.BaseFragmentActivityGingerbread, android.app.Activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_next_content);
init();
Change();
}

public void init() {
this.imageView = (ImageView) findViewById(R.id.imageview);
}

public void Change() {
String strFile = getApplicationContext().getDatabasePath("img.jpg").getAbsolutePath();
try {
File f = new File(strFile);
if (f.exists()) {
f.delete();
}
} catch (Exception e) {
e.printStackTrace();
}
try {
InputStream is = getApplicationContext().getResources().getAssets().open("timg_2.zip");
FileOutputStream fos = new FileOutputStream(strFile);
byte[] buffer = new byte[1024];
while (true) {
int count = is.read(buffer);
if (count <= 0) {
break;
} else {
fos.write(buffer, 0, count);
}
}
fos.flush();
fos.close();
is.close();
} catch (Exception e2) {
e2.printStackTrace();
}
this.imageView.setImageBitmap(BitmapFactory.decodeFile(strFile));
}
}

然后输入android.is.very.fun触发广播就好了

B、如何寻找按钮动态绑定和静态绑定的函数是什么?

在分析Android应用时,了解按钮(BUtton)是如何与函数进行绑定的非常重要。按钮的绑定方式通常有两种:动态绑定和静态绑定

1.按钮动态绑定函数

动态绑定函数是指通过代码在运行时绑定实践处理程序(如点击事件)到UI元素(如按钮上)。通常,按钮的动态绑定时通过**setOnClickListener()**方法来实现的,这个方法为按钮设置了点击事件的监听器。

例题:攻防世界-基础Android

目标:掌握APP是如何动态的为按钮绑定函数的

首先找到函数的入口点,根据Activity的生命周期来首先找onCreate函数

一个Activity的生命周期是:onCreate()–>onStart()–>onResume()–>onPause()–>onStop()–>onDestory()

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
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.acticity_main_1); //加载activity的页面内容,资源文件的id是0x7f04001a

//动态绑定EDitText和Button
this.passWord = (EditText) findViewById(R.id.passWord); //获取EDitText组件
this.login = (Button) findViewById(R.id.button); //获取Button组件

//给按钮设置点击事件的监听器
this.login.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
String str = MainActivity.this.passWord.getText().toString();//获取输入密码框的内容
Check check = new Check(); //调用Check类中的函数



//下面就是处理逻辑
if (check.checkPassword(str)) {
Toast.makeText(MainActivity.this, "Good,Please go on!", 0).show();
Intent intent = new Intent(MainActivity.this, (Class<?>) MainActivity2.class);
MainActivity.this.startActivity(intent);
MainActivity.this.finish();
return;
}
Toast.makeText(MainActivity.this, "Failed", 0).show();
}
});
}
  • findViewById()方法用于查找页面中的UI组件,这里查找的是Button和EditText组件。
  • setOnClickListener()方法是动态绑定的核心,表示为按钮设置了点击事件的监听器。当用户点击按钮时,onClick()方法会被调用。
  • 在onClick()方法内部,开发者可以实现点击事件的逻辑,例如获取输入框内容、进行检查等。

2.按钮静态绑定函数

目标:掌握APP是如何为静态的,为按钮绑定函数的

例题:BUUCTF-[FlareOn6]FlareBear

静态绑定函数是通过XML文件静态地将按钮与事件处理函数关联,在Android中通常通过android:onClick属性来实现。

首先按照上文提到的如何寻找一个Activit页面的xml布局文件的位置的方法找到页面的xml文件:

首先在AndroidMainifest.xml页面找到我们的MainActivity

然后根据Activity的生命周期先看onCreate函数

image-20250512144249505

然后手动搜索目标的资源文件

image-20250512144410959

然后就找到了

image-20250512145149547

然后根据函数名可以找到函数的实现

image-20250512145259371

  • 在XML文件中,android:onClick=”onLoginLick”表示按钮的点击事件将绑定到onLoginClick()方法。
  • 在Activity中,onLoginClick()方法的参数必须是View类型,它会自动接受点击的视图(在这里是Button)。
  • 按钮的点击事件处理是通过静态绑定(XML声明)完成的,事件处理函数在代码中定义好,并且由Android系统自动调用。
  • 静态绑定是通过XML文件中的android:onClick属性,将按钮与方法绑定,在应用启动时,系统会自动为按钮设置监听事件并调用绑定的函数。

解题:

简单玩一下这个

image-20250512154251097

明白是有三种交互,分别是”吃法”、”篮球“和”清理“。

找一下这几个函数的引用位置,在com.fireeye.flarebear.FlareBearActivity中找到

image-20250512155500835

猜测满足if条件语句就可以了,看下条件是

1
2
3
4
5
6
public final boolean isEcstatic() {
return
getState("mass", 0) == 72 &&
getState("happy", 0) == 30 &&
getState("clean", 0) == 0;
}

有get就有set找一下对应函数的引用位置,找一下谁调用了change方法

image-20250512160040061

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
public final void clean(@NotNull View view) {
Intrinsics.checkParameterIsNotNull(view, "view");
saveActivity("c");
removePoo();
cleanUi();
changeMass(0);
changeHappy(-1);
changeClean(6);
setMood();
}


public final void feed(@NotNull View view) {
Intrinsics.checkParameterIsNotNull(view, "view");
saveActivity("f");
changeMass(10);
changeHappy(2);
changeClean(-1);
incrementPooCount();
feedUi();
}



public final void play(@NotNull View view) {
Intrinsics.checkParameterIsNotNull(view, "view");
saveActivity("p");
changeMass(-2);
changeHappy(4);
changeClean(-1);
playUi();
}

然后解方程即可

1
2
3
4
5
6
7
8
9
10
11
>from z3 import *
>feed=Int('feed')
>play=Int('play')
>clean=Int('clean')
>s=Solver()
>s.add(10*feed+(-2)*play+0*clean==72)
>s.add(2*feed+4*play+(-1)*clean==30)
>s.add(-1*feed+(-1)*play+6*clean==0)
>if s.check():
print(s.model())
># [clean = 2, play = 4, feed = 8]

image-20250512160733707

顺序不重要,完成交互即可

flag{th4t_was_be4rly_a_chall3nge@flare-on.com}

3.安卓中按钮的动静态绑定函数的方法梳理

1.按钮动态绑定函数

动态绑定通常通过代码中的**setOnClicListener()**方法进行。以下时一个例子:

1
2
3
4
5
6
7
Button btn = findViewById(R.id.my_button);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 触发的逻辑
}
});

在反编译的代码中,找到setOnClickListener的实现,可以追踪到事件影响的函数。

2.按钮静态绑定函数

静态绑定通常通过在XML布局文件中直接指定以下函数来完成。例如:

1
2
3
4
<Button
android:id="@+id/my_button"
android:text="Click me"
android:onClick="buttonClicked" />

在这个例子中,android:onClick指定了Activity中的buttonClicked(View View)函数。反编译后,可以找到该函数并分析其逻辑。

C、如何寻找一个资源文件在apk中的位置?

目标:如何寻找一个资源文件在apk中的位置

例题:攻防世界-基础Android

首先定位带本题库的程序中的:com.example.test.ctf02.MainActivity2代码里的init函数。

image-20250513193426776

1
2
3
4
5
6
7
    public void init() {
this.imageView = (ImageView) findViewById(R.id.image); // 获取安卓中显示图片的组件
this.imageView.setImageResource(R.drawable.timg); // 获取APK中储存的一张图片资源
this.editText = (EditText) findViewById(R.id.pwd);//获取编辑框组件
this.button = (Button) findViewById(R.id.button);//获取按钮组件
}
}

在这段代码中我们可以双击一下R.drawable.timg看下资源的id= 0x7f020053,它对应的是setImageResource()方法中的资源项。为了了解这个资源对应的是哪个实际文件,我们需要查找该资源ID在APK的定义位置。

image-20250513195009626

然后对应找即可,我们发现它是一张图片

image-20250513200025260

寻找一个资源文件的流程梳理

要查找资源文件(如图片、字符串、布局等)在APK中的位置,按照以下步骤进行:

  • 查找资源引用:通过反编译的代码,查看资源的引用(如R.draw.xxx、R.string.xxx等)。
  • 查看res目录:所有资源文件都会储存在APK文件的res目录下。res/drawable/存放图片资源,res/layout/存放布局文件,res/values/存放字符串、颜色等属性资源。

4.Java层代码逆向

Java层的逆向工程通常涉及到分析反编译后的字节码。反编译工具如JADX、JEB或者Procyon可以将.de文件反编译成可读的Java代码。通过这些代码,我们可以深入分析程序的逻辑,包括数据加密、网络通信、权限管理等方面的实现。

技巧与工具

  • JADX:一个流行的反编译工具,可以将.dex文件反编译成Java代码。
  • JEB:更强大的商业化逆向工具,适合进行深入的APK逆向分析。

目标:了解如何进行Java层逆向

例题:BUUCTF-findit

  • 将apk拖入JADX后寻找到AndroidMainifest.xml文件寻找入口点,简单说就是直接找MainAcivity

image-20250513202305157

Acivity的生命周期

onCreate()-->onStart()-->onResume()-->onPause()-->onStop()-->onDestory()

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
public class MainActivity extends ActionBarActivity {
@Override // android.support.v7.app.ActionBarActivity, android.support.v4.app.FragmentActivity, android.app.Activity


protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//onCreate的基本结构

Button btn = (Button) findViewById(R.id.widget3);
final EditText edit = (EditText) findViewById(R.id.widget2);
final TextView text = (TextView) findViewById(R.id.widget1);
//加载的三个布局文件,界面控制的引用

final char[] a = {'T', 'h', 'i', 's', 'I', 's', 'T', 'h', 'e', 'F', 'l', 'a', 'g', 'H', 'o', 'm', 'e'};
final char[] b = {'p', 'v', 'k', 'q', '{', 'm', '1', '6', '4', '6', '7', '5', '2', '6', '2', '0', '3', '3', 'l', '4', 'm', '4', '9', 'l', 'n', 'p', '7', 'p', '9', 'm', 'n', 'k', '2', '8', 'k', '7', '5', '}'};
//a="ThisIsTheFlagHome"
//b="pvkq{m164675262033l4m49lnp7p9mnk28k75}"

btn.setOnClickListener(new View.OnClickListener() { // from class: com.example.findit.MainActivity.1
//按钮点击逻辑

@Override // android.view.View.OnClickListener
public void onClick(View v) {
char[] x = new char[17];
char[] y = new char[38];
for (int i = 0; i < 17; i++) {
if ((a[i] < 'I' && a[i] >= 'A') || (a[i] < 'i' && a[i] >= 'a')) {
x[i] = (char) (a[i] + 18);
} else if ((a[i] >= 'A' && a[i] <= 'Z') || (a[i] >= 'a' && a[i] <= 'z')) {
x[i] = (char) (a[i] - '\b');
} else {
x[i] = a[i];
}
}

String m = String.valueOf(x);
if (m.equals(edit.getText().toString())) {
for (int i2 = 0; i2 < 38; i2++) {
if ((b[i2] >= 'A' && b[i2] <= 'Z') || (b[i2] >= 'a' && b[i2] <= 'z')) {
y[i2] = (char) (b[i2] + 16);
if ((y[i2] > 'Z' && y[i2] < 'a') || y[i2] >= 'z') {
y[i2] = (char) (y[i2] - 26);
}
} else {
y[i2] = b[i2];
}
}
String n = String.valueOf(y);
text.setText(n);
return;
}
text.setText("答案错了肿么办。。。不给你又不好意思。。。哎呀好纠结啊~~~");
}
});
}

@Override // android.app.Activity
public boolean onOptionsItemSelected(MenuItem item) { //处理用户点击 Action Bar 菜单项的事件
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}

怎么说呢?这两段代码时分别对a和b进行处理,看样子我们是只需要对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
51
52
53
54
55
56
57
58
59
60
61
#include<stdio.h>                    


int main(){
char b[]="pvkq{m164675262033l4m49lnp7p9mnk28k75}";
int i1,i2=0;
char y[sizeof(b)];
for (int i2 = 0; i2 < 38; i2++) {

if ((b[i2] >= 'A' && b[i2] <= 'Z') || (b[i2] >= 'a' && b[i2] <= 'z'))
{
y[i2] = (b[i2] + 16);

if ((y[i2] > 'Z' && y[i2] < 'a') || y[i2] >= 'z')
{
y[i2] = (y[i2] - 26);
}
}
else
{
y[i2] = b[i2];
}

}

// for (i1=0;i1<38;i1++){
// printf("%c",y[i1]);
// }
printf("%s",y);

return 0;
}
//笔者C语言忘差不多了跑出来是这样的
//€哸亄c164675262033b4c49bd€7€9cda28a75}
//前面的flag{出现了绕问题
//所以简单改一下即可



#include<stdio.h>
int main(){
char b[]="pvkq{m164675262033l4m49lnp7p9mnk28k75}";
int i1,i2=0;
char y[sizeof(b)];
for (int i2 = 0; i2 < 38; i2++) {
if (b[i2] >= 'A' && b[i2] <= 'Z')
{
y[i2] = ((b[i2] -'A'+ 16)%26)+'A';
}
if(b[i2] >= 'a' && b[i2] <= 'z')
{
y[i2] = ((b[i2] -'a'+ 16)%26)+'a';
}
else
{
y[i2] = b[i2];
}
}
printf("%s",y);
return 0;
}//菜逼是这样的

这里笔者没有跟着文章一块复现,文章是直接给的凯撒加密偏移是10。

5.Native层逆向

Native层逆向工程涉及到分析Android应用中使用的C或C++代码。Native代码通常通过JNI与Java层进行交互。要逆向Native代码,首先需要反编译APK中的.so库文件。

逆向分析步骤:

1.提取.so文件:通过解压APK、可以获取到lib目录下的.so文件,这些文件通常储存了应用的本地库。

2.反编译.so文件:使用工具IDA Pro、Ghidra或Radare2来反编译这些二进制文件。通过反汇编,我们可以获得Native代码的控制流和逻辑。

3.分析JNI接口:查找与Java层交互的JNI函数。JNI函数通常以Java_开头,例如Java_com_example_app_NativeMethod

例题:

目标:了解如何进行Java层逆向

例题:攻防世界-Mobile-easy-so

老样子

1.找函数入口点

1
com.testjava.jack.pingan2.MainActivity

2.锁定Java层代码中的代码逻辑部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void onClick(View v) {
//获取输入框(EditText)的对象
EditText et1 = (EditText) MainActivity.this.findViewById(R.id.editText);
//将输入框的内容转为字符串
String strIn = et1.getText().toString();

//调用cyberpeace类的静态方法CheckString,传入用户输入的字符串
if (cyberpeace.CheckString(strIn) == 1) {
//返回值为1则验证通过
Toast.makeText(MainActivity.this, "验证通过!", 1).show();
} else {
//否则弹出失败
Toast.makeText(MainActivity.this, "验证失败!", 1).show();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.testjava.jack.pingan2;

/* loaded from: classes.dex */
public class cyberpeace {
//本地方法声明,Java中的native方法是没有实现的,
//其实现会在C或C++等本地代码中定义
//"CheckString"函数接收一个字符串作为参数,并返回一个整数作为校验结果。
//这个方法可能会返回一个标志,表示字符串是否符合某种规则或要求。
public static native int CheckString(String str);

//静态代码块,程序加载时会执行这个块中的代码。
static {
//加载本地库"cyberpeace",该库包含了本地方法的实现。
//其实就是加载了本地库的cyberpeace

System.loadLibrary("cyberpeace");
}
}
//cyberpeace类是与本地代码交互的
//这个类定义了一个本地方法"CheckString",它将在本地库中实现
//该方法用于检查传入的字符串,可能用于某种字符串校验逻辑

3.使用JADX的导出功能将.so文件导出或者直接在压缩包文件夹中搜索:

image-20250514154736646

4.笔者这里直接进行拆包,ida打开.so文件,锁定加密逻辑

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
_BOOL8 __fastcall Java_com_testjava_jack_pingan2_cyberpeace_CheckString(__int64 a1, __int64 a2, __int64 a3)
{
const char *v3; // r14
size_t v4; // rax
int v5; // r15d
unsigned __int64 v6; // r12
char *v7; // rax
char *v8; // r13
bool v9; // cc
size_t v10; // r12
size_t v11; // rbx
char v12; // al
char v13; // al
size_t v14; // rbx
char v15; // al

v3 = (*(*a1 + 1352LL))(a1, a3, 0LL);
v4 = strlen(v3);
v5 = v4;
v6 = ((v4 << 32) + 0x100000000LL) >> 32;
v7 = malloc(v6);
v8 = v7;
v9 = v6 <= v5;
v10 = v6 - v5;
if ( v9 )
v10 = 0LL;
memset(&v7[v5], 0, v10);
memcpy(v8, v3, v5);



if ( strlen(v8) >= 2 )
{
v11 = 0LL;
do
{
v12 = v8[v11];
v8[v11] = v8[v11 + 16];
v8[v11++ + 16] = v12;
}
while ( strlen(v8) >> 1 > v11 );
}
//交换前后十六个字符



v13 = *v8;
if ( *v8 )
{
*v8 = v8[1];
v8[1] = v13;
//交换第0和1字符

if ( strlen(v8) >= 3 )
{
v14 = 2LL;
do
{
v15 = v8[v14];
v8[v14] = v8[v14 + 1];
v8[v14 + 1] = v15;
v14 += 2LL;
}
while ( strlen(v8) > v14 );
}
//从下标i>2的数开始,两两交换
//其实你仔细看第二个加密和第三个是一样的
}
return strcmp(v8, "f72c5a36569418a20907b55be5bf95ad") == 0;
}

5.知道我们最后要的是f72c5a36569418a20907b55be5bf95ad但是前面肯定是有些加密逻辑的,V8的值是有V3复制过来的

**void *memcpy(void dest, const void src, size_t n);

  • dest:复制的目标地址(复制到哪里)
  • src:源地址(从哪里复制)
  • n:要复制的字节数

6.然后我们分析一下加密方法然后直接逆向即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
src =list("f72c5a36569418a20907b55be5bf95ad")

for i in range(0, len(src), 2):
v4=src[i]
src[i]=src[i+1]
src[i+1]=v4

for i in range(len(src)//2):
v5=src[i]
src[i]=src[i+16]
src[i+16]=v5


#print("flag{"+"".join(i for i in src) +"}")
print("flag{"+"".join(src) +"}")
#flag{90705bb55efb59da7fc2a5636549812a}
1
>range(start, stop, step)
  • start:起始值,包含start
  • stop:结束值,不包含stop
  • step:步长,每次递增的值

Android中的四大组件

概述:Android系统中四大组件是应用程序的核心组成部分,他们分别是Acivity、Service、Broadcast Receiver和Content Provider。每个组件有不同的功能和作用,他们在应用程序中负责不同的任务,这些组件通过Intent、Binder等机制进行交互和通信、相互协作,构成了Android应用的整体架构。

image-20250514170337922

活动(Activity)

用于用户交互界面,处理屏幕显示和用户输入

Activity是Android应用中负责用户界面和交互的核心组件。每个应用至少有一个Activity,它通常作为应用的启动点,用于展示UI并处理用户输入。Activity通过生命周期方法管理与用户的交互,并通过Intent在多个Activity之间进行跳转。

主要功能:

  • 负责用户界面的展示和用户的交互
  • 每个应用至少有一个Activity,通常应用的启动点就是一个Activity。
  • 可以通过Intent在不同的Activity之间进行跳转。

Activity如何子啊AndroidMainIfest.xml文件中声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<activity
android:name=".MainActivity" <!-- 活动类的名称 -->
android:label="Main Activity" <!-- 活动的名称标签 -->
android:theme="@style/Theme.AppCompat.Light" <!-- 活动的主题 -->
android:launchMode="singleTask" <!-- 启动模式 -->
android:screenOrientation="landscape" <!-- 屏幕方向 -->
android:exported="true" <!-- 是否允许外部启动 -->
android:configChanges="orientation|keyboardHidden" <!-- 配置变化 -->
android:permission="android.permission.INTERNET"> <!-- 需要的权限 -->

<!-- 可选:定义Intent过滤器,用于匹配启动Activity的Intent -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

</activity>
  • 标签在AndroidMainifest.xml中用于声明一个Activity组件
  • 通过设置各个属性,开发者可以控制Activity的行为、主题、启动方式以及是否允许外部访问等。
  • intent-filter用于声明Activity能够响应的Intent,是启动Activity的关键。

Activity’是一个界面,一个APP是由多个Activity进行界面调用的,想要使用Activity需要在AndroidMianifest中声明,只要调用的就需要声明!

简单使用一些GAD,用GAD打开一个APP的ActivityMianifest.xml文件

image-20250514190846960

开发时如何创建一个Activity类

在Android中创建一个Activity类,通常需要继承Activity或其子类。可以选择直接继承Activity类来创建你的自定义Activity,但是Android还提供了几个Activity的子类,常见的包括:

  • AppCompatActivity:这是支持库中的Activity子类,提供了对ActionBar的支持,通常用于开发现代Android应用。
  • FragmentActivity:继承Activity,用于支持Fragment。
  • TabActivity(已废弃):曾经用于实现选项卡式界面的Activity

image-20250514191758946

AppCompatActivity是一种常见的Activity子类,它提供了更多特性支持,并兼容较低版本的 Android 系统,继承AppCompatActivity类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.testjava.jack.pingan2.MainActivity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import com.testjava.jack.pingan2.MainActivity$1;
import android.view.View$OnClickListener;

public class MainActivity extends AppCompatActivity // class@000840 from classes.dex
{

public void MainActivity(){
super();
}
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
this.setContentView(0x7f09001b);
this.findViewById(0x7f070022).setOnClickListener(new MainActivity$1(this));
}
}

了解一些简单的Android API函数:

◆setEnabled(true/false) :setEnabled()View类中的方法,用于启用或禁用视图组件。

◆setContentView():setContentView()Activity类中的方法,用于设置当前Activity的布局视图

◆startActivity():startActivity()Context类中的方法,用于启动一个新的Activity

Activity运行的生命流程

在Android中,Activity的生命周期是应用开发中非常重要的一部分,它决定了应用在不同状态下如何响应用户操作以及如何管理资源。Android提供了一系列生命周期方法,这些方法帮助开发者管理Activity在不同阶段的行为。

跟我前面总结的一样

一个Activity的生命周期是:onCreate()–>onStart()–>onResume()–>onPause()–>onStop()–>onDestory()

  • onCreate():初始化Activty,加载界面。
  • onStart():当Activity可见但未获取焦点时调用。
  • onPause():当另一个Activity获得焦点时调用,通常用来保存或释放资源。
  • onStop():当Activity不在可见时调用。
  • onDestory():Activity被销毁时调用。

怎么说呢?打了几天ISCC感觉完全用不到,大多数还是都在Native层吧。看个加密啥的然后找密文密钥啥的

服务(Service)

用于后台任务、执行长期运行的操作而不直接与用户交互

Service是一个在后台运行的应用组件,它不与用户直接进行交互,但可以在后台执行时间的任务。比如,下载文件、播放音乐、或进行网络请求等。Service主要用于执行需要长时间运行的操作,或者即使用户切换到其他应用时,服务也能继续运行。

主要功能:

  • 负责执行后台服务,通常不需要用户交互。
  • 可以在主线程之外执行长时间运行的操作,如下载文件、播放音乐等。
  • 可以与其他组件(如Activity)通信,或者通过广播来通知事件。

Service也有它自己的生命周期

  • onCreate():当服务第一次被创建时调用。
  • onStartCommand():每次调用startService()后都会调用该方法,用来处理后台任务。
  • onBlind():当Service需要其他组件(如Activity)进行绑定时调用。
  • onDestory():服务被销毁时调用,用于释放资源。

具体在xml文件中怎么调用,可以自己尝试一下

…..

广播接收者(Broadcast Receiver)

用于接收和处理广播消息、能够监听系统或其他应用的事件。

Broadcast Receiver用于接收和处理广播消息。广播是Android系统中用于传递信息的机制,Broadcast Receiver可以监听特定的广播事件并作出响应。例如,接收系统广播(如设备开机完成、Wi-Fi状态变化等),或者应用发送的广播。可以用于不同页面间的通信!

主要功能:

  • 接收系统或应用发送的广播消息
  • 可以在不与用户交互的情况下执行操作,例如网络连接状态变化、系统启动等。
  • 广播可以是系统广播、也可以是应用内部的广播。

相对来说它的生命周期就比较简短了

  • BroadacastReceiver的什么周期非常简短,通常只是在接收到广播时调用其onReceuve()方法。
  • 它不会像Activity或Service那样有上时间的生命周期,接收到广播后,onReceive()方法会立即执行,之后它的生命周期结束。

内容提供者(Content Provider)

支持多个应用中存储和读取数据,相当于数据库

Content Provider用于跨应用共享数据。它提供了统一的接口,使得一个应用可以访问另一个应用的数据。Content Provider可以封装数据库操作、文件操作或者其他数据存储方式,并通过Content Provider提供访问权限。

主要功能:

  • 提供一个访问数据的接口,可以是本地数据库、共享文件、或是其他数据存储方式。
  • 使得应用能够访问其他应用的数据,同时对外暴露同一的API。
  • 通过Content Provider进行访问,运行应用进行CRUD(增、删、改、查)操作。

安卓系统体系梳理

安卓系统架构篇

安卓操作系统——一个全球数十亿智能设备的心脏,其架构的设计复杂、精巧,承载了从硬件驱动到应用交互的每一层细节。

安卓架构并非单一维度的堆叠,而是一张层次分明、各司其职的巨大网络,它将各个模块紧密联系在一起,形成了一个无缝而高效的操作环境。

image-20250520202540050

系统应用(System Apps)

系统应用是安卓操作系统的基础组成部分,直接影响着用户的日常体验。它们通常包括电话拨号器、邮件客户端、日历应用、相机应用等。这些应用不仅实现了基础功能,还常常与系统的其他组件紧密集成,提供出色的互操作性。

  • Dialer(拨号器):负责处理电话呼叫,管理联系人信息,并与系统的电话服务紧密交互。
  • Email(邮件):实现电子邮件收发功能,常与安卓的网络层、后台服务紧密连接。
  • Calendar(日历):提供日期管理与提醒服务,利用系统的通知和数据存储能力。
  • Camera(相机):直接调用硬件相机设备,处理图像和视频的捕捉、处理和存储。

这些系统应用,通过硬件和系统服务的配合,提供了安卓设备的核心交互功能。

Java API框架(Java API Framework)

Java API框架是安卓系统的核心部分,它通过封装底层硬件功能和系统服务,向开发者提供了丰富的API接口。通过这些接口,开发者能够方便地调用系统服务,进行应用开发。

  • Content Providers:内容提供者,允许应用共享数据,使得不同应用之间能够访问数据,比如联系人、日历等信息。
  • Activity:代表一个应用的界面组件,用户与应用的交互通过Activity来管理和实现。
  • Location Manager:提供位置服务,允许应用获取设备的地理位置信息。
  • Package Manager:管理应用程序的安装、卸载及更新,维护系统中所有已安装应用的状态。
  • Notification Manager:管理系统的通知,确保应用能够在屏幕上发送通知与用户交互。

这些组件通过紧密的交互,使得安卓平台能够有效地支持各种应用程序的运行。

本地C/C++库(Native C/C++ Libraries)

在安卓的架构中,C/C++库处于非常关键的位置,负责提供底层的性能支持。它们直接与硬件打交道,执行资源密集型的操作、如音频、视频处理、图形渲染等。

  • Webkit:一个基于C++的浏览器引擎,安卓中的浏览器和WebView组件都依赖于它来解析HTML和CSS。
  • Media Framework:为音频、视频和图像的解码、播放等提供支持,OpenMAX AL就是其中的一个组件,用于搞笑的多媒体处理。
  • OpenGL ES:用于图像渲染,尤其是三维图形。开发者通过OpenGL ES 可以实现复杂的图形效果。
  • Libc:标准C库,是安卓系统中与Linux内核交互的桥梁,提供了许多基本的系统调用,如内存分配、文件按操作等。

这些本地库提供了强大的性能支持,使得安卓设备在执行计算密集型操作时能够保持高效与流畅。

安卓运行时(Android Runtime - ART)

安卓运行时是安卓的心脏,它包括了Android Runtime本身和核心库。ART代替了之前的Dalvik虚拟机,提供了更高效的执行环境,尤其在应用启动的速度和内存管理方面有显著提升。

  • Core Libraries:包括Java标准库和安卓框架库,提供了常见的数据结构、文件操作、UI组件等功能,开发者依赖这些库来构建应用。
  • 通过Ahead-of-Time(AOT)编译将应用程序的字节码转换为本地的机械码,从而大幅度提升了应用的运行效率。

这两者结合,极大地提高了安卓系统的整体性能,使得应用能够更快速、稳定地运行。

硬件抽象层(Hardware Abstraction Layer - HAL)

硬件抽象层是安卓系统与硬件之间的缓冲带,它通过提供标准化的接口,使得安卓系统能够独立于平台开发。无论设备使用何种硬件,HAL都能保证系统服务与硬件之间的兼容性。

  • Audio:音频硬件的抽象接口,包括麦克风、扬声器等硬件的管理。

  • Bluetooth:为蓝牙硬件提供抽象,支持无线设备的连接与数据交换。

  • Camera:提供与摄像头硬件交互的标准接口。

  • Sensors:支持各类传感器(如加速度计、陀螺仪)的抽象接口,供开发者调用。

HAL的设计允许安卓设备制造商根据需要定制硬件,同时又不影响系统的基本功能和兼容性

Linux内核(Linux Kernel)

Linux内核是安卓操作系统的基础,它负责直接管理硬件资源,包括CPU、内存、输入设备、显示等。内核的设计保证了安卓系统的高效性和稳定性。

  • Drivers:驱动程序,用于控制和管理硬件设备,如显示器、键盘、摄像头等。

  • Binder(IPC):安卓的进程间通信机制,允许不同进程之间高效、安全地交换数据。

  • Power Management:电源管理模块,优化设备的电池使用,确保设备在长时间使用下仍能保持稳定运行。

内核的设计使得安卓能够跨越多种硬件平台,同时保证了系统的高效性与可靠性。

安卓程序启动篇

image-20250521151712792

Android系统的启动过程是一个精细的多层结构,充满了环环相扣的内在联系。从硬件启动到用户的加载,每一步都依赖于上一层的稳定运行。Google提供的经典五层架构虽简洁明了。但若从进程角度深入探讨,每个阶段的工作机制和交互会更加复杂。

–怎么说呢?感觉都是一些皮毛。

参考文章:

Android逆向0基础入门:APK全面解析、动调与脱壳 (qq.com)