吾爱破解安卓逆向入门教程学习
00x1 ELFELF 文件是Linux 和 Android 系统中最常见的二进制文件格式全称是“可执行与可链接格式”Executable and Linkable Format。它就像程序的“包装盒”里面装着代码、数据以及告诉操作系统如何运行程序的信息无论是直接运行的软件、动态库还是编译中间文件大多都采用这种格式ELF 文件是 Linux 和 Android 系统里最常用的程序文件格式用来存可执行程序、库文件和编译中间文件 。在 Android 应用运行时 , ELF 文件的大部分内容 , 会被 映射到内存中 ; 这就意味着 Android 应用内存中的很多数据 , 会遵循 ELF 文件格式的规范ELF 文件格式最常见的形式就是 .so 动态库 ;文件扩展名可能没有扩展名也可能是 .elf、.o、.so 等具体取决于文件用途 。ELF 文件的特点 :ELF 文件是以7F 45 4C 46开头 , 其中 7F 是一个二进制标志 , 45 4C 46 是 ELF 字符对应的 ASCII 码 ;ELF文件类型有可执行文件Executable File加载后可以直接运行的二进制机器直接执行可执行文件ELF格式被操作系统加载器加载进内存后CPU可以直接执行它里面的二进制机器码原生代码。可执行文件是包含程序代码的ELF文件经过编译和链接后可以直接执行。它通常包含程序的入口点操作系统加载它后便开始执行。这类文件可以通过命令行直接运行。例如执行一个ls命令的二进制文件就是一个可执行的ELF文件。文件的类型标识通常是ET_EXEC。目标文件Object File也就是翻译成机器码的c语言需要靠外部的库才能执行。.o翻译成机器码编译器自己就能搞定“依赖外部”发生在翻译完之后链接阶段问题出在你写的 C 代码几乎不可能只靠自己完成所有事。只要你调用了一个外部函数比如 printfc#include stdio.hvoid sayHello() {printf(Hello);}编译器把 sayHello 翻译成机器码后这个目标文件里会留下一个“坑”· “此处需要调用 printf 函数但 printf 的机器码不在我这里请后续帮我填上它的真实地址。”这个“填坑”的动作就叫链接Linking。它必须依赖外部的库如 libc.so因为 printf 的真实机器码在那里。目标文件是源代码编译后生成的中间文件它包含了程序的机器代码但还没有完全链接。目标文件通常不能单独运行需要与其他目标文件或者库文件一起链接生成可执行文件。文件的类型标识通常是ET_RELRelocatable。目标文件会包含符号表、重定位信息和调试信息供链接器使用。共享库文件Shared Library共享库环境空间也就是他就是单纯的依赖然后存储在一个文件共享库文件.so就是1. 单纯的依赖它本身只是一堆被编译好的函数如 printf、strcpy静静地等着别的程序来调用自己。2. 存储在一个文件中这些函数被打包在一个独立的二进制文件里方便管理和共享。它自己不做任何“汇聚”的动作只是一个被动的代码仓库。真正把依赖关系“连接”起来让程序能调用到它的是动态链接器Linker就像你之前理解的“引用的包”最终由 ART 虚拟机去加载和解析一样。4.核心转储文件Core Dump程序崩溃时生成的内存快照用于调试。共享库文件是一种可以被多个程序同时使用的动态链接库.so文件。它包含了供多个程序共享的函数和代码通常用于提供一些常见功能的共享实现。当一个程序需要某些功能时它会在运行时加载共享库而不是在编译时将这些功能静态链接进程序。共享库有助于节省内存和磁盘空间。文件的类型标识通常是ET_DYNDynamic。静态库文件Static Archive File目标文件打包是吧.a这是一个归档文件包含多个目标文件.o文件用于静态链接。链接器会将静态库中的代码复制到最终的可执行文件中。参考【Android 逆向】ELF 文件格式 ( ELF 文件头 | ELF 文件头标志 | ELF 文件位数 | ELF 文件大小端格式 )-腾讯云开发者社区-腾讯云【Linux篇】ELF文件及其加载与动态链接机制-腾讯云开发者社区-腾讯云elf文件详解_.elf-CSDN博客elf文件详解_.elf-CSDN博客00x2 NDK开发NDK开发是Android应用开发中的一项重要技术它允许开发者使用C和C语言进行高性能、安全、可移植的代码编写。本文将带您了解NDK开发的基本概念、应用场景、优势以及实践方法帮助您更好地掌握NDK开发技术。在Android平台上虽然Java是主要的开发语言但对于一些需要高性能的应用逻辑C和C无疑是更好的选择。这时候我们就需要用到Android NDKNative Development Kit这个工具了。NDK是Android平台提供的一套原生开发工具集它允许开发者使用C和C编写应用中的一部分代码并将其编译为动态链接库.so文件。这些库可以在Android应用中通过Java Native InterfaceJNI进行调用实现Java与C/C的交互。可以与java代码连接参考深入浅出NDK开发全解析【NDK开发】Android NDK 原生构建ndk-build 与 CMake-CSDN博客安卓开发1src该目录是放置所有Java代码的地方在这里的含义和 普通Java项目下的src目录是完全一样的在src目录中 可以创建多个包每个包中可以存放不同的文件或者 Activity。2gen该目录是自动生成的主要有一个R.java文件在项目中添加的任何资源文件都会在其中生成一个相应的资源Id,这个文件一定不要手动修改当res资源文件修改时 R.java文件都会重新编译。3Android 8.0.0该目录中存放的是当前工程使用的Android SDK,从图中可以看出当前应用程序引用的是Android SDK 8.0.0不同版本的SDK文件的名称也不同。4assets该目录用于存放一些随程序打包的文件通常放置一些项目中用到的多媒体资源。当Android程序打包时它会原封不动地一起打包安装时会直接解压到对应的assets 目录中。5bin该目录不需要过多的关心它主要包含了一些在编译时自动产生的文件其中会有一个当前项目编译好的安装包展开bin目录会看到HelloWorld程序的安装包HelloWorld.apk,把这个文件复制到手机上就可以直接安装了。但是不能作为发布版本使用。6libs如果项目中用到了第三方的Jar包就需要把这些Jar包都放在libs目录下放 在这个目录下的Jar包都会被添加到构建路径中去。7res该目录中放置的是Android要用到的各种程序资源如图片、布局、字符串等。可以分为以下几层8res/drawable存放png、jpg 等图标文件。其中drawable目录分为不同的文件夹drawable-hdpi、drawable-ldpi、drawable-mdpi、 drawable-xhdpi、drawable-xxhdpi,这些文件夹中存放的图片分别对应不同的手机屏幕大小以便做屏幕适配。9res/layout存放xml 界面文件xml 界面文件和HTML 文件一样主要用于显示用户操作界面。10res/values存放应用使用到的各种类型数据。不同类型的数据存放在不同的文件中其中strings.xml 定义字符串和数值colors.xml 定义颜色和颜色字串数值dimens.xml 定义尺寸数据styles.xml 定义样式。11res/anim存放自定义动画的XML 文件。12res/raw该目录用于存放应用使用到的原始文件如音效文件等。编译软件时这些数据不会被编译它们被直接加入到程序安装包里。13res/xmlXML资源文件。14AndroidManifest.xml该文件是整个项目的配置文件在程序中定义的四大组件都需要在这个文件里注册另外还可以在这个文件中给应用程序添加权限声明也可以重新 指定创建项目时程序最低兼容的版本和最高版本。清单文件配置的信息会配置到Android系统中当程序运行时系统会先找到清单文件中配置的信息然后根据设置的信息打开相应的组件。15proguard-project.txt该文件是Android提供的混淆代码工具Proguard的配置文件通过该文件可以混淆应用程序中的代码防止应用程序被反编译出源码。16project.properties该文件记录了 Android项目运打时的环境并通过一行代码指定了编译程序时所使用的SDK版本这个版本可以手动更改但必须是已下载的版本app/src/main/java/com/example/ndkdamo/MainActivity.java:31在这个java文件里面所见到的Activity就是在第一章认识的Android四大组件之一。这个java文件主要配置了Activity获得页面主要加载的界面文件activity_main.xml。MainActivity.java是 Android 应用程序中主活动Main Activity的 Java 源代码文件。它是大多数 Android 应用的入口点负责管理用户界面的显示和交互逻辑。package com.example.ndkdamo; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; import com.example.ndkdamo.databinding.ActivityMainBinding; public class MainActivity extends AppCompatActivity { // Used to load the ndkdamo library on application startup. //加载一次ndkdamo //· Android 上会查找 libndkdamo.so。 //· Linux 上查找 libndkdamo.so。 // · Windows 上查找 ndkdamo.dll。 static { System.loadLibrary(ndkdamo); } private ActivityMainBinding binding; //布局绑定 Override protected void onCreate(Bundle savedInstanceState) { //必须执行表示一个窗口正在生成 super.onCreate(savedInstanceState); //也就是这个activity main就是自动绑定activity_main.xml然后创建一个对象是吧 binding ActivityMainBinding.inflate(getLayoutInflater()); //获取外层视图并显示 setContentView(binding.getRoot()); // Example of a call to a native method //获取示例文本 TextView tv binding.sampleText; //文本控件赋值 tv.setText(stringFromJNI()); } /** * A native method that is implemented by the ndkdamo native library, * which is packaged with this application. */ //nativeso层实现方法如下 public native String stringFromJNI();?xml version1.0 encodingutf-8? androidx.constraintlayout.widget.ConstraintLayout xmlns:androidhttp://schemas.android.com/apk/res/android xmlns:apphttp://schemas.android.com/apk/res-auto xmlns:toolshttp://schemas.android.com/tools android:layout_widthmatch_parent android:layout_heightmatch_parent tools:context.MainActivity TextView android:idid/sample_text android:layout_widthwrap_content android:layout_heightwrap_content android:textHello World! app:layout_constraintBottom_toBottomOfparent app:layout_constraintEnd_toEndOfparent app:layout_constraintStart_toStartOfparent app:layout_constraintTop_toTopOfparent / /androidx.constraintlayout.widget.ConstraintLayoutCtrl shift aapp/src/main/cpp/CMakeLists.txt:20# For more information about using CMake with Android Studio, read the # documentation: https://d.android.com/studio/projects/add-native-code.html. # For more examples on how to use CMake, see https://github.com/android/ndk-samples. # Sets the minimum CMake version required for this project. cmake_minimum_required(VERSION 3.22.1) # Declares the project name. The project name can be accessed via ${ PROJECT_NAME}, # Since this is the top level CMakeLists.txt, the project name is also accessible # with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level # build script scope). project(ndkdamo) # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. # Gradle automatically packages shared libraries with your APK. # # In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define # the target library name; in the sub-modules CMakeLists.txt, ${PROJECT_NAME} # is preferred for the same purpose. # # In order to load a library into your app from Java/Kotlin, you must call # System.loadLibrary() and pass the name of the library defined here; # for GameActivity/NativeActivity derived applications, the same library name must be # used in the AndroidManifest.xml file. add_library(${CMAKE_PROJECT_NAME} SHARED # List C/C source files with relative paths to this CMakeLists.txt. native-lib.cpp) # Specifies libraries CMake should link to your target library. You # can link libraries from various origins, such as libraries defined in this # build script, prebuilt third-party libraries, or Android system libraries. target_link_libraries(${CMAKE_PROJECT_NAME} # List libraries link to the target library android log)函数名规则Java_com_example_ndkdamo_MainActivity_stringFromJNI· 格式固定Java_ 包名点换成下划线 _ 类名 _ 方法名。· 这里表示包名 com.example.ndkdamo类名 MainActivity方法名 stringFromJNI。· Java 层必须对应声明一个 public native String stringFromJNI();。std 是什么· std 是 C 标准库命名空间standard namespace的缩写。· C 标准库中的所有组件如 string、vector、cout、sort 等都定义在 std 命名空间中以避免与用户自定义的标识符冲突。· 整个函数包括声明和函数体都是 C 代码并不是“外面是 Java 可解析的东西里面是 C”这种分割。· 但是函数名Java_com_example_ndkdamo_MainActivity_stringFromJNI和修饰符extern C、JNIEXPORT、JNICALL是为了遵循 JNI 的命名与链接规范让 Java 虚拟机在加载动态库后能够根据这个固定名称找到函数的入口地址。· 一旦 Java 虚拟机找到了并调用这个函数函数内部的代码比如 std::string hello ...就完全是 C 的执行逻辑。外面的声明就相当于一个翻译的意思一个连接的桥梁。也就是说找到是一码事显示又是一码事。1. 外面的桥梁只管“找到函数”· extern C保证 C 函数名不被修饰让 JVM 能按名字找到这个函数。· JNIEXPORT保证函数符号被导出到动态库中让 JVM 能加载到。· 特殊函数名告诉 JVM 这个函数对应 Java 的哪个 native 方法。这些只解决了“定位函数入口”的问题不涉及参数和返回值如何在两种语言之间转换。---2. 数据类型转换必须手动完成Java 和 C/C 对数据的内存表示完全不同· Java 的 String是一个对象UTF-16 内部表示有垃圾回收。· C 的 std::string是一个类通常存储字节数组不兼容 Java。· C 的 char*只是一个指针指向字节。JVM 无法自动知道你想把 C 的 std::string 转成 Java 的 String所以你必须显式调用 JNI 提供的转换函数比如· NewStringUTF(const char*)把 C 字符串转成 Java String。· GetStringUTFChars(jstring, ...)反过来把 Java String 转成 C 字符串。· 外部声明extern C 等→ 让 JVM 找到你。· JNI 转换函数NewStringUTF 等→ 让你手动把 C 数据变成 Java 能接受的形式。这里#include jni.h//导入jni头文件提供jni函数及数据类型定义 #include string//导入c标准库 //声明jni函数会被java代码调用 //extern C c语音的方式编译该函数可以被java虚拟机按名字找到 //也就是兼容的作用让Java的虚拟机可以找到原生函数方便链接 //JNIEXPORT 宏一般是另外一个的地址C地库地址方便Java寻找。 //jstring 返回类型是宏 extern C JNIEXPORT jstring JNICALL //包名 com.example.ndkdamo类名 MainActivity方法名 stringFromJNI Java_com_example_ndkdamo_MainActivity_stringFromJNI( //JNIEnv Java Native Interface Environment 的缩写即“Java 本地接口环境”。它是一个结构体或说接口表包含了大量函数指针用于在原生代码中操作 Java 对象、类、方法、字段、字符串、数组等。 //表示“指向 JNIEnv 结构体的指针 JNIEnv* env, //调用该原生方法的 Java 对象实例实例方法 jobject /* this */) { //string字符串 std::string hello Hello from C; //hello.c_str()转化c //NewStringUTF 是 JNIJava Native Interface中用于将 Modified UTF-8 编码的 C/C 字符串转换为 Java java.lang.String 对象的函数返回 jstring由 JVM 自动管理内存。 //返回 return env-NewStringUTF(hello.c_str()); }参考HelloWorld我的第一趟旅程出发点-腾讯云开发者社区-腾讯云gradle/gradle-daemon-jvm.properties:13网络超时C:\Users\jc\AndroidStudioProjects\ndkdamogradlew.bat assembleDebug Downloading https://services.gradle.org/distributions/gradle-9.4.1-bin.zip Exception in thread main java.io.IOException: Downloading from https://services.gradle.org/distributions/gradle-9.4.1-bin.zip failed: timeout (10000ms) at org.gradle.wrapper.Install.forceFetch(SourceFile:4) at org.gradle.wrapper.Install$1.call(SourceFile:8) at org.gradle.wrapper.GradleWrapperMain.main(SourceFile:67) Caused by: java.net.SocketTimeoutException: Read timed out at java.base/sun.nio.ch.NioSocketImpl.timedRead(NioSocketImpl.java:288) at java.base/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:314) at java.base/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:355) at java.base/sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:808) at java.base/java.net.Socket$SocketInputStream.read(Socket.java:966) at java.base/sun.security.ssl.SSLSocketInputRecord.read(SSLSocketInputRecord.java:484) at java.base/sun.security.ssl.SSLSocketInputRecord.readHeader(SSLSocketInputRecord.java:478) at java.base/sun.security.ssl.SSLSocketInputRecord.bytesInCompletePacket(SSLSocketInputRecord.java:70) at java.base/sun.security.ssl.SSLSocketImpl.readApplicationRecord(SSLSocketImpl.java:1465) at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:1069) at java.base/java.io.BufferedInputStream.fill(BufferedInputStream.java:244) at java.base/java.io.BufferedInputStream.read1(BufferedInputStream.java:284) at java.base/java.io.BufferedInputStream.read(BufferedInputStream.java:343) at java.base/sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:826) at java.base/sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:761) at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1740) at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1641) at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:224) at org.gradle.wrapper.Install.forceFetch(SourceFile:2) ... 2 moregradle/gradle-daemon-jvm.properties:13· 文件头的注释 #This file is generated by updateDaemonJvm 说明它是由 Gradle 的 Daemon JVM 自动更新机制 生成的。· 当你项目里声明了需要某个特定版本的 JDK例如 JDK 21但本地没有时Gradle 就会生成或读取这样一份“工具链配置”告诉它去哪里下载对应平台的 JDK。#This file is generated by updateDaemonJvm toolchainUrl.FREE_BSD.AARCH64https\://api.foojay.io/disco/v3.0/ids/ec7520a1e057cd116f9544c42142a16b/redirect toolchainUrl.FREE_BSD.X86_64https\://api.foojay.io/disco/v3.0/ids/4c4f879899012ff0a8b2e2117df03b0e/redirect toolchainUrl.LINUX.AARCH64https\://api.foojay.io/disco/v3.0/ids/ec7520a1e057cd116f9544c42142a16b/redirect toolchainUrl.LINUX.X86_64https\://api.foojay.io/disco/v3.0/ids/4c4f879899012ff0a8b2e2117df03b0e/redirect toolchainUrl.MAC_OS.AARCH64https\://api.foojay.io/disco/v3.0/ids/73bcfb608d1fde9fb62e462f834a3299/redirect toolchainUrl.MAC_OS.X86_64https\://api.foojay.io/disco/v3.0/ids/846ee0d876d26a26f37aa1ce8de73224/redirect toolchainUrl.UNIX.AARCH64https\://api.foojay.io/disco/v3.0/ids/ec7520a1e057cd116f9544c42142a16b/redirect toolchainUrl.UNIX.X86_64https\://api.foojay.io/disco/v3.0/ids/4c4f879899012ff0a8b2e2117df03b0e/redirect toolchainUrl.WINDOWS.AARCH64https\://api.foojay.io/disco/v3.0/ids/9482ddec596298c84656d31d16652665/redirect toolchainUrl.WINDOWS.X86_64https\://api.foojay.io/disco/v3.0/ids/39701d92e1756bb2f141eb67cd4c660e/redirect toolchainVersion21解决方法国内链接gradle/wrapper/gradle-wrapper.properties:9#Sat Jun 06 01:00:57 CST 2026 distributionBaseGRADLE_USER_HOME distributionPathwrapper/dists distributionUrlhttps\://mirrors.cloud.tencent.com/gradle/gradle-9.5.1-bin.zip networkTimeout10000 validateDistributionUrltrue zipStoreBaseGRADLE_USER_HOME zipStorePathwrapper/dists00x3 jniJNI是Java Native Interface的缩写通过使用 Java本地接口书写程序可以确保代码在不同的平台上方便移植。这种机制使得Java程序能够调用本地应用程序或库中的函数这些本地方法通常用于执行与硬件直接交互、性能要求较高的任务如图形处理、音频处理等。同时也允许本地代码调用Java层的函数从而增强了Java程序的灵活性和扩展性。刚刚上面的Java就是jni注册。这个也是// 定义方法数组 JNINativeMethod methods[] { {nativeGetStringFromJNI, (Ljava/lang/String;)V, (void*)Java_com_example_ndkdemo_MainActivity_nativeGetStringFromJNI} }; // 获取类 jclass clazz env-FindClass(com/example/ndkdemo/MainActivity); // 手动注册 env-RegisterNatives(clazz, methods, 1);参考Android JNI机制概述-腾讯云开发者社区-腾讯云00x4 idaliblib目录下的子目录存放的是一些与手机CPU架构对应的C/C代码编译生成的so文件一般用于JNI开发。原生把一些注册代码以及渲染放在so文件下面So里面就是注册具体实现的东西里面可能有Java注册的逻辑。__int64 __fastcall Java_com_example_ndkdamo_MainActivity_stringFromJNI(_JNIEnv *a1) { const char *v1; // rsi __int64 v3; // [rsp18h] [rbp-48h] char v4[24]; // [rsp40h] [rbp-20h] BYREF unsigned __int64 v5; // [rsp58h] [rbp-8h] v5 __readfsqword(0x28u); std::__ndk1::basic_stringchar,std::__ndk1::char_traitschar,std::__ndk1::allocatorchar::basic_string[abi:ne190000]0( v4, Hello from C); v1 (const char *)sub_200B0(v4); v3 _JNIEnv::NewStringUTF(a1, v1); std::__ndk1::basic_stringchar,std::__ndk1::char_traitschar,std::__ndk1::allocatorchar::~basic_string(v4); if ( __readfsqword(0x28u) ! v5 ) JUMPOUT(0x20001LL); return v3; }public Java_com_example_ndkdamo_MainActivity_stringFromJNI Java_com_example_ndkdamo_MainActivity_stringFromJNI proc near var_60 qword ptr -60h var_58 qword ptr -58h var_50 qword ptr -50h var_48 qword ptr -48h var_3C dword ptr -3Ch var_38 qword ptr -38h var_30 qword ptr -30h var_28 qword ptr -28h var_20 byte ptr -20h var_8 qword ptr -8 ; __unwind { // __gxx_personality_v0 push rbp mov rbp, rsp sub rsp, 60h mov rax, fs:28h mov [rbpvar_8], rax mov [rbpvar_28], rdi mov [rbpvar_30], rsi lea rsi, aHelloFromC ; Hello from C lea rdi, [rbpvar_20] mov [rbpvar_58], rdi call __ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEC2B8ne190000ILi0EEEPKc ; std::__ndk1::basic_stringchar,std::__ndk1::char_traitschar,std::__ndk1::allocatorchar::basic_string0(char const*) mov rdi, [rbpvar_58] mov rax, [rbpvar_28] mov [rbpvar_50], rax call sub_200B0 mov rdi, [rbpvar_50] ; this mov rsi, rax ; char * ; try { call __ZN7_JNIEnv12NewStringUTFEPKc ; _JNIEnv::NewStringUTF(char const*) ; } // starts at 1FF89 mov [rbpvar_48], rax jmp $5他后面讲的改我以后再改的试试吧具体就是要么是汇编修改要么是二进制修改然后把注册的内容覆盖实现修改。