APK

APK RE

摘自KANXUEANDROIDREVERSE

APK Structure

1.AndroidManifest.xml

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

  • <intent-filter> 用于在应用的组件声明中指定哪些 Intent 可以触发该组件。
  • <action> 用于指定该 Intent 过滤器能够处理的操作(Action)。它告诉 Android 系统,当有一个 Intent 的动作(action)匹配时,应该触发相应的组件(如 BroadcastReceiver)。

2.classes.dex 文件包含了应用程序的可执行代码。它是应用的Dalvik字节码文件,也是Android应用在运行时通过 Dalvik虚拟机ART(Android Runtime) 解释执行的核心文件。

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

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

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

6.res/ 目录包含了Android应用所需的所有资源文件。与 assets/ 目录不同,res/ 目录中的资源文件是经过编译的

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

Analysis process(basic)

一、用Jadx打开分析AndroidMainfest.xml。查找 <activity> 标签,它们通常定义了应用的各个 Activity(包括启动 Activity)。入口点通常如下表示:

image-20250401102707202

方法:

(1) 直接查看 AndroidManifest.xml

  1. 在 Jadx 中打开 APK,导航至 Resources → AndroidManifest.xml
  2. 搜索 action.MAINcategory.LAUNCHER,找到对应的 Activity。

(2) 搜索 onCreate() 方法

  • 入口 ActivityonCreate() 是应用启动后第一个执行的用户代码。
  • 在 Jadx 的 搜索栏Ctrl + F)输入:
    • onCreate( (注意括号)
    • 或直接搜索 MainActivity(根据 AndroidManifest.xml 的结果)。

(3) 查找 Application

  • 如果存在自定义 Application 类,其 onCreate() 会比 Activity 更早执行。
  • 在 Jadx 中搜索:
    • extends Application
    • 或查看 AndroidManifest.xml<application android:name="..."> 指定的类。

最终得出该app的程序入口点代码是在android:name="com.example.flag.MainActivity"

二、成功寻找到activity的代码入口处,开始分析activity的生命执行流程:
根据android:name="com.example.flag.MainActivity"字段成功找到Activity的功能实现代码位置,一个Activity的生命周期是:onCreate()->onStart()->onResume()->onPause()->onStop()->onDestroy(),所以可以先锁定onCreate函数,锁定app加载的主要逻辑。

三、分析Java层代码,程序逻辑

如果方法在Native层,用IDA Pro对对应的.so文件进行逆向。可能需要导入jni.h.

File–>Load File–>Prase C Header File–>选择jni.h

查找与 Java 层交互的 JNI 函数。JNI 函数通常以 Java_ 开头,例如 Java_com_example_app_NativeMethod

加壳app分析(以网鼎杯_2020_青龙组_bang为例)

一、先分析AndroidMainfest.xml

<?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>

入口应该在how_debug类,但进去发现没有MainActivity方法,只有资源文件。

image-20250401113351717

可以判断被加壳。

还可以通过观察APK文件是否在AndroidManifest.xml配置Applicaiton信息来判定,该app实现了一个自定义操作将Application 类进行了自定义修改成了com.SecShell.SecShell.ApplicationWrapper来实现自己的加壳逻辑。

二、脱壳

python查壳脚本:

python detect_shell_name.py file_name

使用frida-dexdump来脱壳。adb connect 127.0.0.1:7555连接到Mumu模拟器,然后在adb shell下启动frida-serverimage-20250401141526335

然后Mumu前台运行how_debug,frida-dexdump出dex:

frida-dexdump -U -n how_debug -o ./

image-20250401142030658

3.把dump出来的dex文件夹用jadx打开,就可以分析到com.example.how_debugMainActivity,找到onCreate()方法:

    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.activity_main);
        mContext = getApplicationContext();
        jinit();
        Log.i("tag", "protect MainActivity onCreate");
        ((TextView) findViewById(R.id.textView2)).setText(stringFromJNI1());
        final EditText editText = (EditText) findViewById(R.id.editText3);
        final EditText editText2 = (EditText) findViewById(R.id.editText2);
        ((Button) findViewById(R.id.button)).setOnClickListener(new View.OnClickListener() { // from class: com.example.how_debug.MainActivity.1
            @Override // android.view.View.OnClickListener
            public void onClick(View view) {
                String obj = editText.getText().toString();
                String obj2 = editText2.getText().toString();
                if (obj.equals(obj2)) {
                    MainActivity.showmsg("user is equal passwd");
                } else if (obj.equals("admin") && obj2.equals("pass71487")) {
                    MainActivity.showmsg("success");
                    MainActivity.showmsg("flag is flag{borring_things}");
                } else {
                    MainActivity.showmsg("wrong");
                }
            }
        });
    }

定位资源文件(以攻防世界-基础Android,buu-[FlareOn6]FlareBear为例)

每个 Activity 在启动时通常会加载一个或多个布局文件。要找到与 Activity 关联的 XML 布局文件,可以按照以下步骤进行:

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

x 是一个整数值(有时直接给出了文件名称),代表一个资源的 ID。这个资源 ID 对应的是一个 XML 布局文件(通常位于 res/layout/ 目录下),在编译过程中由 Android 构建工具自动生成。所以我们可以手动寻找到布局文件或通过jadx的搜索功能寻找到目标资源的名称。

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

image-20250401145936707

通过这个 ID(如 R.layout.activity_main),你可以定位到 res/layout 目录下的 XML 布局文件。

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

动态绑定

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

public class MainActivity extends AppCompatActivity {
    private Button login;
    private EditText passWord;
 
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(0x7f04001a); // 加载activity的页面内容,资源文件的id是0x7f04001a
         
        // 动态绑定 EditText 和 Button
        this.passWord = (EditText) findViewById(0x7f0b0055); // 获取 EditText 组件
        this.login = (Button) findViewById(0x7f0b0056); // 获取 Button 组件
         
        // 给按钮设置点击事件监听器
        this.login.setOnClickListener(new View.OnClickListener() {
            @Override // android.view.View.OnClickListener
            public void onClick(View v) {
                String str = MainActivity.this.passWord.getText().toString(); // 获取密码输入框的内容
                Check check = new Check(); // 调用 Check 类中的函数
                // 处理逻辑...
            }
        });
    }
}
  • findViewById() 方法用于查找页面中的 UI 组件,这里查找的是 ButtonEditText 组件。
  • setOnClickListener() 方法是动态绑定的核心,表示为按钮设置了点击事件监听器。当用户点击按钮时,onClick() 方法会被调用。
  • onClick() 方法内部,开发者可以实现点击事件的逻辑,例如获取输入框内容、进行检查等。

本题:过第一步Check()后来到第二步:

public class MainActivity2 extends AppCompatActivity {
    Button button;
    EditText editText;
    ImageView imageView;

    /* JADX INFO: Access modifiers changed from: protected */
    @Override // android.support.v7.app.AppCompatActivity, android.support.v4.app.FragmentActivity, android.support.v4.app.BaseFragmentActivityGingerbread, android.app.Activity
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_2);
        init();
        this.button.setOnClickListener(new View.OnClickListener() { // from class: com.example.test.ctf02.MainActivity2.1
            @Override // android.view.View.OnClickListener
            public void onClick(View v) {
                MainActivity2.this.sendBroadcast(new Intent(MainActivity2.this.editText.getText().toString()));
            }
        });
    }

    public void init() {
        this.imageView = (ImageView) findViewById(R.id.image);
        this.imageView.setImageResource(R.drawable.timg);
        this.editText = (EditText) findViewById(R.id.pwd);
        this.button = (Button) findViewById(R.id.button);
    }
}

与第一步类似分析可以得到按钮布局,分析可知就是将editText的内容广播出去。在AndroidMainfest中有一个receiver:

        <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"/>

接收到android.is.very.fun后,看com.example.test.ctf02.GetAndChange:

public class GetAndChange extends BroadcastReceiver {
    @Override // android.content.BroadcastReceiver
    public void onReceive(Context context, Intent intent) {
        context.startActivity(new Intent(context, NextContent.class));
    }
}

执行NextContent,拿到flag。

也可以直接构造广播发送:

adb shell am broadcast -a android.is.very.fun

静态绑定

flare bear通过同样方法可以定位到activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:background="@drawable/bgrepeat" android:layout_width="match_parent" android:layout_height="match_parent">
    <TextView android:textSize="46sp" android:typeface="normal" android:textStyle="bold" android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="@string/text_flare_bear" android:fontFamily="casual" app:layout_constraintLeft_toLeftOf="0" app:layout_constraintRight_toRightOf="0" app:layout_constraintTop_toTopOf="0"/>
    <Button android:enabled="false" android:textSize="24sp" android:id="@+id/buttonContinue" android:layout_width="250dp" android:layout_height="80dp" android:layout_marginBottom="24dp" android:text="@string/text_continue" android:onClick="continueGame" android:fontFamily="casual" app:layout_constraintBottom_toTopOf="@+id/buttonNew" app:layout_constraintEnd_toEndOf="0" app:layout_constraintHorizontal_bias="0.497" app:layout_constraintStart_toStartOf="0"/>
    <Button android:textSize="24sp" android:id="@+id/buttonNew" android:layout_width="250dp" android:layout_height="80dp" android:layout_marginTop="8dp" android:layout_marginBottom="4dp" android:text="@string/text_new_flare_bear" android:onClick="newGame" android:fontFamily="casual" app:layout_constraintBottom_toBottomOf="0" app:layout_constraintEnd_toEndOf="0" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="0" app:layout_constraintTop_toBottomOf="@+id/textView"/>
    <LinearLayout android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginStart="8dp" android:layout_marginEnd="8dp" app:layout_constraintBottom_toBottomOf="0" app:layout_constraintEnd_toEndOf="0" app:layout_constraintStart_toStartOf="0">
        <Button android:textSize="24sp" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="80dp" android:text="Help" android:onClick="showHelp" android:fontFamily="casual"/>
        <Button android:textSize="24sp" android:id="@+id/buttonCredits" android:layout_width="wrap_content" android:layout_height="80dp" android:layout_marginLeft="20dp" android:text="@string/text_credits" android:onClick="showCredits" android:fontFamily="casual"/>
    </LinearLayout>
</android.support.constraint.ConstraintLayout>
  • 在 XML 文件中,android:onClick="onLoginClick" 表示按钮的点击事件将绑定到 onLoginClick() 方法。
  • Activity 中,onLoginClick() 方法的参数必须是 View 类型,它会自动接收点击的视图(在这里是 Button)。
  • 按钮的点击事件处理是通过静态绑定(XML 声明)完成的,事件处理函数在代码中定义好,并且由 Android 系统自动调用。

四大组件

1.活动(activity) :用于表现功能

xml声明:

<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>

2.服务(service):后台运行服务,不提供界面呈现

声明:

<service
    android:name=".MyService"   <!-- 声明服务的类名,可以是全名或相对路径 -->
    android:enabled="true"      <!-- 指定服务是否启用,如果为 false,系统不会启动此服务 -->
    android:exported="false"    <!-- 控制该服务是否允许其他应用程序访问,true表示服务可以被外部应用调用,false表示只能在当前应用内调用 -->
    android:permission="android.permission.BIND_JOB_SERVICE">  <!-- 为服务定义一个权限,表示访问此服务的应用必须声明相应的权限 -->
 
    <!-- 可选:定义服务的最小 SDK 版本要求 -->
    <meta-data
        android:name="com.example.myapp.SERVICE_META"
        android:value="some_value" />
     
    <!-- 可选:声明该服务能够响应的 Intent -->
    <intent-filter>
        <action android:name="com.example.myapp.ACTION_START_SERVICE" />
    </intent-filter>
 
    <!-- 可选:指定服务所依赖的特定权限 -->
    <uses-permission android:name="android.permission.INTERNET" />
</service>

3.广播接收者(Broadcast reciver) :用于接受广播

声明:

<receiver
    android:name=".MyReceiver"              <!-- 广播接收器类的名称 -->
    android:enabled="true"                  <!-- 是否启用该广播接收器 -->
    android:exported="false"                <!-- 是否允许外部应用访问 -->
    android:permission="android.permission.RECEIVE_BOOT_COMPLETED"> <!-- 需要的权限 -->
     
    <!-- 可选:定义接收的广播事件 -->
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" /> <!-- 广播事件 -->
        <category android:name="android.intent.category.DEFAULT" />   <!-- 广播分类 -->
    </intent-filter>
 
    <!-- 可选:声明接收器需要的权限 -->
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
</receiver>

4.内容提供者(Content provider):支持多个应用中存储和读取数据,相当于数据库

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

<provider
    android:name=".MyProvider"                   <!-- ContentProvider 的类名 -->
    android:authorities="com.example.myapp.provider" <!-- ContentProvider 的 URI 标识符 -->
    android:exported="true"                      <!-- 是否允许外部应用访问 -->
    android:permission="android.permission.READ_CONTACTS"  <!-- 需要的权限 -->
    android:enabled="true"                       <!-- 是否启用 ContentProvider -->
    android:multiProcess="true"                  <!-- 是否支持多进程访问 -->
    android:readPermission="android.permission.READ_EXTERNAL_STORAGE"   <!-- 读取权限 -->
    android:writePermission="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- 写入权限 -->
posted @ 2025-04-01 17:10  brs7  阅读(78)  评论(0)    收藏  举报