自定义rom grand 2026-01-18 2026-01-18 自定义rom 参考 android 15 源码分析【环境搭建: asfp 调试framework-java 与 native】_android asfp-CSDN博客
使用 ASfP 搭建 Android Framwork 开发调试阅读环境 | Client Infra
https://source.android.com/docs/core?hl=zh-cn
feicong/android-rom-book: 安卓系统定制:从入门到实践 开源图书🔥
AOSP 打包之如何新增预装应用 - Greg_LYU - 博客园
[原创]告别RegisterNatives获取JNI函数绑定的地址,迎接最底层的方式获取!(三个案例)-Android安全-看雪安全社区|专业技术交流与安全研究论坛
https://aosp.org.cn/docs/core/architecture/partitions/generic-boot
配置 ubuntu22.04
16g内存
32swap
清华源
500g硬盘
配置aosp源码的ide源码查看工具 下载安装aosp 正常启动
1 2 mmm development/tools/idegen sudo development/tools/idegen/idegen.sh
asfpd打开iml即可
自定义添加预装应用
这里就是我们要放置app的位置 我这里选择预装sukisu作为测试
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 pixel@pixel-virtual-machine:~/android13$ cd packages/apps/ pixel@pixel-virtual-machine:~/android13/packages/apps$ ls BasicSmsReceiver KeyChain SecureElement Browser2 Launcher3 Settings Calendar LegacyCamera SettingsIntelligence Camera2 ManagedProvisioning SpareParts Car Messaging Stk CarrierConfig Music StorageManager CellBroadcastReceiver MusicFX SystemUIGo CertInstaller Nfc Tag Contacts OnDeviceAppPrediction Test DeskClock OneTimeInitializer ThemePicker DevCamera PhoneCommon TimeZoneData Dialer Protips TimeZoneUpdater DocumentsUI Provision Traceur EmergencyInfo QuickAccessWallet TV Gallery QuickSearchBox TvSettings Gallery2 RemoteProvisioner UniversalMediaPlayer HTMLViewer SafetyRegulatoryInfo WallpaperPicker ImsServiceEntitlement SampleLocationAttribution WallpaperPicker2 pixel@pixel-virtual-machine:~/android13/packages/apps$ mkdir sukisu pixel@pixel-virtual-machine:~/android13/packages/apps$ ls BasicSmsReceiver Launcher3 SettingsIntelligence Browser2 LegacyCamera SpareParts Calendar ManagedProvisioning Stk Camera2 Messaging StorageManager Car Music sukisu CarrierConfig MusicFX SystemUIGo CellBroadcastReceiver Nfc Tag CertInstaller OnDeviceAppPrediction Test Contacts OneTimeInitializer ThemePicker DeskClock PhoneCommon TimeZoneData DevCamera Protips TimeZoneUpdater Dialer Provision Traceur DocumentsUI QuickAccessWallet TV EmergencyInfo QuickSearchBox TvSettings Gallery RemoteProvisioner UniversalMediaPlayer Gallery2 SafetyRegulatoryInfo WallpaperPicker HTMLViewer SampleLocationAttribution WallpaperPicker2 ImsServiceEntitlement SecureElement KeyChain Settings pixel@pixel-virtual-machine:~/android13/packages/apps$
新建目录 创建Android.mk文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := sukisu LOCAL_SRC_FILES := sukisu.apk LOCAL_MODULE_CLASS := APPS LOCAL_MODULE_TAGS := optional LOCAL_DEX_PREOPT := false LOCAL_PRODUCT_MODULE := true LOCAL_PRIVILEGED_MODULE := true LOCAL_CERTIFICATE := platform # 强制关闭依赖库检查 LOCAL_ENFORCE_USES_LIBRARIES := false include $(BUILD_PREBUILT)
修改device/google/raviole/device-oriole.mk 结尾添加
1 2 3 # My Custom Apps PRODUCT_PACKAGES += \ sukisu
完成 重新编译 这次只需要单独编译system.img即可 当然要是先前编译过一次 也可以整体编译 速度不会差很多
1 2 3 4 5 6 7 . build/envsetup.sh lunch aosp_oriole-userdebug m sukisu make snod
测试刷入 安全下机
aosp无法连接wifi&&同步时间 1 2 3 4 5 adb shell settings delete global captive_portal_https_url adb shell settings delete global captive_portal_http_url adb shell settings put global captive_portal_http_url http://connect.rom.miui.com/generate_204 adb shell settings put global captive_portal_https_url https://connect.rom.miui.com/generate_204 adb shell "settings put global ntp_server pool.ntp.org"
监控Native方法注册 这样就省去我们使用frida等动态插桩工具去获取注册地址等信息 简单认识两种注册手段
静态注册 当使用Android Studio创建一个Native C++的项目,其中默认使用的就是静态注册,Java函数与C++函数的绑定是通过Java和C++函数名的约定来实现的。具体地说,在Java代码中声明的native方法的命名规则为:Java_+全限定类名+_+方法名,将所有的点分隔符替换为下划线。例如,在这个例子中,Java类的全限定名为cn.rom.nativecppdemo.MainActivity,方法名为stringFromJNI,因此对应的C++函数名为Java_cn_rom_nativecppdemo_MainActivity_stringFromJNI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // java文件 public class MainActivity extends AppCompatActivity { static { System.loadLibrary("nativecppdemo"); } ... public native String stringFromJNI(); } // c++文件 extern "C" JNIEXPORT jstring JNICALL Java_cn_rom_nativecppdemo_MainActivity_stringFromJNI( JNIEnv* env, jobject /* this */) { std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); }
静态注册函数必须使用JNIEXPORT和JNICALL修饰符,这两个修饰符是JNI中的预处理器宏。其中,JNIEXPORT会将函数名称保存到动态符号表,在注册时通过dlsym函数找到该函数。
JNICALL宏主要用于消除不同编译器和操作系统之间的调用规则差异。在不同平台上,本地方法的参数传递、调用约定和名称修饰等方面可能存在差异。这些差异可能导致在一个平台上编译的共享库无法在另一个平台上运行。为了解决这个问题,JNI规范定义了一种标准的本地方法命名方式,即”Java包名 类名_方法名”的格式。使用JNICALL宏可以让编译器根据规范自动生成符合要求的本地方法名,从而确保能够正确调用本地方法。
需要注意的是,尽管JNICALL可以帮助我们消除平台差异,在某些情况下仍然需要手动指定本地方法名称。例如当我们需要使用JNI反射机制来动态调用本地方法时。此时,我们需要显式指定注册本地方法时所需绑定到Java代码中相应方法上去。
对于静态注册而言,并未看到直接使用RegisterNative进行注册操作, 但实际内部已经进行了隐式注册。具体来说,当Java类被加载时,会调用LoadMethod将方法加载到虚拟机中,并随后通过LinkCode将Native函数与Java函数进行链接。下面是相关代码片段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 void ClassLinker::LoadClass(Thread* self, const DexFile& dex_file, const dex::ClassDef& dex_class_def, Handle<mirror::Class> klass) { ... // 遍历一个 Java 类的所有字段和方法,并对它们进行操作 accessor.VisitFieldsAndMethods([&]( const ClassAccessor::Field& field) REQUIRES_SHARED(Locks::mutator_lock_) { ... // 所有字段 LoadMethod(dex_file, method, klass, art_method); LinkCode(this, art_method, oat_class_ptr, class_def_method_index); ... }, [&](const ClassAccessor::Method& method) REQUIRES_SHARED(Locks::mutator_lock_) { // 所有方法 ArtMethod* art_method = klass->GetVirtualMethodUnchecked( class_def_method_index - accessor.NumDirectMethods(), image_pointer_size_); LoadMethod(dex_file, method, klass, art_method); LinkCode(this, art_method, oat_class_ptr, class_def_method_index); ++class_def_method_index; }); ... }
下面继续看看LinkCode的实现,如果已经被编译就会有Oat文件,就可以获取到quick_code,直接从二进制中调用来快速执行,否则走解释执行。
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 static void LinkCode(ClassLinker* class_linker, ArtMethod* method, const OatFile::OatClass* oat_class, uint32_t class_def_method_index) REQUIRES_SHARED(Locks::mutator_lock_) { ... const void* quick_code = nullptr; if (oat_class != nullptr) { const OatFile::OatMethod oat_method = oat_class->GetOatMethod(class_def_method_index); quick_code = oat_method.GetQuickCode(); } // 是否使用解释执行 bool enter_interpreter = class_linker->ShouldUseInterpreterEntrypoint(method, quick_code); // 为指定的java函数设置二进制的快速执行入口 if (quick_code == nullptr) { method->SetEntryPointFromQuickCompiledCode( method->IsNative() ? GetQuickGenericJniStub() : GetQuickToInterpreterBridge()); } else if (enter_interpreter) { method->SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge()); } else if (NeedsClinitCheckBeforeCall(method)) { method->SetEntryPointFromQuickCompiledCode(GetQuickResolutionStub()); } else { method->SetEntryPointFromQuickCompiledCode(quick_code); } if (method->IsNative()) { // 为指定的java函数设置JNI入口点,IsCriticalNative表示java中带有@CriticalNative标记的native函数。一般的普通函数会调用后面的GetJniDlsymLookupStub method->SetEntryPointFromJni( method->IsCriticalNative() ? GetJniDlsymLookupCriticalStub() : GetJniDlsymLookupStub()); if (enter_interpreter || quick_code == nullptr) { DCHECK(class_linker->IsQuickGenericJniStub(method->GetEntryPointFromQuickCompiledCode())); } } }
上面可以看到JNI设置入口点有两种情况,Critical Native方法通常用于需要高性能、低延迟和可预测行为的场景,例如音频处理、图像处理、网络协议栈等。一般情况开发者使用的都是普通Native函数,所以会调用后者GetJniDlsymLookupStub,接着继续看看实现代码。
1 2 3 static inline const void* GetJniDlsymLookupStub() { return reinterpret_cast<const void*>(art_jni_dlsym_lookup_stub); }
这里看到就是将一个函数指针转换后返回,这个函数指针对应的是一段汇编代码,下面看看汇编代码实现。
1 2 3 4 5 6 7 8 9 10 11 12 ENTRY art_jni_dlsym_lookup_stub // spill regs. ... bl artFindNativeMethod b .Llookup_stub_continue .Llookup_stub_fast_or_critical_native: bl artFindNativeMethodRunnable ... 1: ret // restore regs and return to caller to handle exception. END art_jni_dlsym_lookup_stub
能看到里面调用了artFindNativeMethod和artFindNativeMethodRunnable继续查看相关函数。
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 extern "C" const void* artFindNativeMethod(Thread* self) { DCHECK_EQ(self, Thread::Current()); Locks::mutator_lock_->AssertNotHeld(self); // We come here as Native. ScopedObjectAccess soa(self); return artFindNativeMethodRunnable(self); } extern "C" const void* artFindNativeMethodRunnable(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) { Locks::mutator_lock_->AssertSharedHeld(self); // We come here as Runnable. uint32_t dex_pc; ArtMethod* method = self->GetCurrentMethod(&dex_pc); DCHECK(method != nullptr); ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); // 非静态函数的处理 if (!method->IsNative()) { ... } // 如果注册过了,这里就会直接获取到,返回对应的地址 const void* native_code = class_linker->GetRegisteredNative(self, method); if (native_code != nullptr) { return native_code; } // 查找对应的函数地址 JavaVMExt* vm = down_cast<JNIEnvExt*>(self->GetJniEnv())->GetVm(); native_code = vm->FindCodeForNativeMethod(method); if (native_code == nullptr) { self->AssertPendingException(); return nullptr; } // 最后通过Linker进行注册 return class_linker->RegisterNative(self, method, native_code); }
FindCodeForNativeMethod执行到内部最后是通过dlsym查找符号,并且成功在这里看到了前文所说的隐式调用的RegisterNative。
动态注册 动态注册一般是写代码手动注册,将指定的符号名与对应的函数地址进行关联,在AOSP源码中Native函数大部分都是使用动态注册方式的,动态注册例子如下。
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 // java文件 public class MainActivity extends AppCompatActivity { static { System.loadLibrary("native-lib"); } public native String stringFromJNI2(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView tv = findViewById(R.id.sample_text); tv.setText(stringFromJNI()); } } //c++文件 jstring stringFromJNI2(JNIEnv* env, jobject /* this */) { return env->NewStringUTF("Hello from C++"); } // 在 JNI_OnLoad 中进行动态注册 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { return -1; } // 手动注册 stringFromJNI 方法 jclass clazz = env->FindClass("com/example/myapplication/MainActivity"); JNINativeMethod methods[] = { {"stringFromJNI2", "()Ljava/lang/String;", reinterpret_cast<void *>(stringFromJNI2)} }; env->RegisterNatives(clazz, methods, sizeof(methods)/sizeof(methods[0])); return JNI_VERSION_1_6; }
动态注册中是直接调用JniEnv的RegisterNatives进行注册的,找到对应的实现代码如下。
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 static jint RegisterNatives(JNIEnv* env, jclass java_class, const JNINativeMethod* methods, jint method_count) { ... // 遍历所有需要注册的函数 for (jint i = 0; i < method_count; ++i) { // 取出函数名,函数签名,函数地址 const char* name = methods[i].name; const char* sig = methods[i].signature; const void* fnPtr = methods[i].fnPtr; ... // 遍历Java对象的继承层次结构,也就是所有父类,来获取函数 for (ObjPtr<mirror::Class> current_class = c.Get(); current_class != nullptr; current_class = current_class->GetSuperClass()) { m = FindMethod<true>(current_class, name, sig); if (m != nullptr) { break; } m = FindMethod<false>(current_class, name, sig); if (m != nullptr) { break; } ... } if (m == nullptr) { ... return JNI_ERR; } else if (!m->IsNative()) { ... return JNI_ERR; } ... // 内部也是调用了Linker的RegisterNative const void* final_function_ptr = class_linker->RegisterNative(soa.Self(), m, fnPtr); UNUSED(final_function_ptr); } return JNI_OK; }
在动态注册中,同样看到内部是调用了Linker的RegisterNative进行注册的,最后我们看看Linker中的实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const void* ClassLinker::RegisterNative( Thread* self, ArtMethod* method, const void* native_method) { CHECK(method->IsNative()) << method->PrettyMethod(); CHECK(native_method != nullptr) << method->PrettyMethod(); void* new_native_method = nullptr; Runtime* runtime = Runtime::Current(); runtime->GetRuntimeCallbacks()->RegisterNativeMethod(method, native_method, /*out*/&new_native_method); if (method->IsCriticalNative()) { ... } else { // 给指定的java函数设置对应的Native函数的入口地址。 method->SetEntryPointFromJni(new_native_method); } return new_native_method; }
分析到这里,就已经明白两个目标需求如何实现了:ClassLinker::RegisterNative是静态注册和动态注册执行流程中的共同点,该函数的返回值就是Native函数的入口地址。接下来可以开始进行插桩输出了。
RegisterNative实现插桩 前文简单介绍ROM插桩其实就是输出日志,找到了合适的时机,以及要输出的内容,最后就是输出日志即可。在函数ClassLinker::RegisterNative调用结束时插入日志输出如下
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 const void* ClassLinker::RegisterNative( Thread* self, ArtMethod* method, const void* native_method) { // 1. 基础检查 CHECK(method->IsNative()) << method->PrettyMethod(); CHECK(native_method != nullptr) << method->PrettyMethod(); // 2. 准备变量 void* new_native_method = nullptr; Runtime* runtime = Runtime::Current(); // 3. 调用回调 (这一步可能会改变 native_method 的值,比如被 Hook 框架拦截) runtime->GetRuntimeCallbacks()->RegisterNativeMethod(method, native_method, /*out*/&new_native_method); // 4. 根据是否是 CriticalNative 进行不同的注册处理 if (method->IsCriticalNative()) { MutexLock lock(self, critical_native_code_with_clinit_check_lock_); // Remove old registered method if any. auto it = critical_native_code_with_clinit_check_.find(method); if (it != critical_native_code_with_clinit_check_.end()) { critical_native_code_with_clinit_check_.erase(it); } // To ensure correct memory visibility, we need the class to be visibly // initialized before we can set the JNI entrypoint. if (method->GetDeclaringClass()->IsVisiblyInitialized()) { method->SetEntryPointFromJni(new_native_method); } else { critical_native_code_with_clinit_check_.emplace(method, new_native_method); } } else { // 普通 Native 方法直接设置入口点 method->SetEntryPointFromJni(new_native_method); } // ========================================== // [grand-ROM] 自定义日志插桩代码 START // ========================================== // 定义用于存储基址信息的结构体 Dl_info info; const void* base_addr = nullptr; const char* so_name = "unknown_so"; // 使用 dladdr 函数查询 new_native_method 所在的 SO 信息 // new_native_method 是实际注册的函数地址 if (dladdr(new_native_method, &info) != 0) { base_addr = info.dli_fbase; // 获取 SO 基址 if (info.dli_fname != nullptr) { so_name = info.dli_fname; // 获取 SO 文件路径 } } // 打印详细日志,包含基址 LOG(INFO) << "[grand-ROM] ClassLinker::RegisterNative " << method->PrettyMethod().c_str() << " native_ptr:" << new_native_method << " method_idx:" << method->GetMethodIndex() << " baseAddr:" << base_addr << " so_name:" << so_name; // ========================================== // [grand-ROM] 自定义日志插桩代码 END // ========================================== return new_native_method; }
开始编译 完成刷入
1 2 3 4 5 6 7 8 9 2026-01-17 02:17:54.679 1494-1537 system_server system_process I [grand-ROM] ClassLinker::RegisterNative java.io.FileDescriptor android.net.NetworkUtils.resNetworkSend(long, byte[], int, int) native_ptr:0x7884faaaf0 method_idx:30 baseAddr:0x7884fa8000 so_name:/apex/com.android.tethering/lib64/libframework-connectivity-jni.so 2026-01-17 02:17:54.679 1494-1537 system_server system_process I [grand-ROM] ClassLinker::RegisterNative java.io.FileDescriptor android.net.NetworkUtils.resNetworkQuery(long, java.lang.String, int, int, int) native_ptr:0x7884faac90 method_idx:27 baseAddr:0x7884fa8000 so_name:/apex/com.android.tethering/lib64/libframework-connectivity-jni.so 2026-01-17 02:17:54.679 1494-1537 system_server system_process I [grand-ROM] ClassLinker::RegisterNative android.net.DnsResolver$DnsResponse android.net.NetworkUtils.resNetworkResult(java.io.FileDescriptor) native_ptr:0x7884faae60 method_idx:28 baseAddr:0x7884fa8000 so_name:/apex/com.android.tethering/lib64/libframework-connectivity-jni.so 2026-01-17 02:17:54.679 1494-1537 system_server system_process I [grand-ROM] ClassLinker::RegisterNative void android.net.NetworkUtils.resNetworkCancel(java.io.FileDescriptor) native_ptr:0x7884fab140 method_idx:25 baseAddr:0x7884fa8000 so_name:/apex/com.android.tethering/lib64/libframework-connectivity-jni.so 2026-01-17 02:17:54.679 1494-1537 system_server system_process I [grand-ROM] ClassLinker::RegisterNative android.net.Network android.net.NetworkUtils.getDnsNetwork() native_ptr:0x7884fab210 method_idx:12 baseAddr:0x7884fa8000 so_name:/apex/com.android.tethering/lib64/libframework-connectivity-jni.so 2026-01-17 02:17:54.874 2694-4885 rkstack.process com.android.networkstack I [grand-ROM] ClassLinker::RegisterNative void android.net.util.NetworkStackUtils.addArpEntry(byte[], byte[], java.lang.String, java.io.FileDescriptor) native_ptr:0x794c299670 method_idx:2 baseAddr:0x794c289000 so_name:/system/priv-app/NetworkStackNext/NetworkStackNext.apk!/lib/arm64-v8a/libnetworkstackutilsjni.so 2026-01-17 02:17:54.875 2694-4885 rkstack.process com.android.networkstack I [grand-ROM] ClassLinker::RegisterNative void android.net.util.NetworkStackUtils.attachDhcpFilter(java.io.FileDescriptor) native_ptr:0x794c2998c0 method_idx:4 baseAddr:0x794c289000 so_name:/system/priv-app/NetworkStackNext/NetworkStackNext.apk!/lib/arm64-v8a/libnetworkstackutilsjni.so 2026-01-17 02:17:54.875 2694-4885 rkstack.process com.android.networkstack I [grand-ROM] ClassLinker::RegisterNative void android.net.util.NetworkStackUtils.attachRaFilter(java.io.FileDescriptor, int) native_ptr:0x794c299930 method_idx:5 baseAddr:0x794c289000 so_name:/system/priv-app/NetworkStackNext/NetworkStackNext.apk!/lib/arm64-v8a/libnetworkstackutilsjni.so 2026-01-17 02:17:54.875 2694-4885 rkstack.process com.android.networkstack I [grand-ROM] ClassLinker::RegisterNative void android.net.util.NetworkStackUtils.attachControlPacketFilter(java.io.FileDescriptor, int) native_ptr:0x794c2999d0 method_idx:3 baseAddr:0x794c289000 so_name:/system/priv-app/NetworkStackNext/NetworkStackNext.apk!/lib/arm64-v8a/libnetworkstackutilsjni.so
改进一下
1 2 3 4 5 6 7 8 9 10 [grand-ROM] >>> JNI Register Detected <<< Process : system_server (PID: 1509) Java Method: byte[] com.google.android.icing.IcingSearchEngine.nativeOptimize(com.google.android.icing.IcingSearchEngine) Signature : (Lcom/google/android/icing/IcingSearchEngine;)[B Native Info: -> SO Path : /apex/com.android.appsearch/lib64/libicing.so -> Symbol : Java_com_google_android_icing_IcingSearchEngine_nativeOptimize -> Abs Addr: 0x78a728f4d0 -> Base : 0x78a723f000 -> OFFSET : 0x504d0
feicong/android-rom-book: 安卓系统定制:从入门到实践 开源图书🔥 中也有优化的代码 功能确实更全面 但是在性能等方面确是有很大的问题 每一次加载都要走一次遍历无疑是巨大的性能消耗 以及多的一批的日志 但是我是测试机我就不管了
后面做出点新东西再更新