安卓新人😋
Hello World
Android Studio 创建一个项目,等待gradle
想要run起来,创建一个device。在Android Studio顶栏,device manager -> create.
Activity Activity代表了一个具有用户界面的单一屏幕
Android 初始化是通过 Activity 中的 onCreate() 回调的调用开始的。存在有一序列的回调方法来启动一个活动,同时有一序列的方法来关闭活动
onCreate()
这是第一个回调,在活动第一次创建时调用
onStart()
这个回调在活动为用户可见时被调用
onResume()
这个回调在应用程序与用户开始可交互的时候调用
onPause()
被暂停的活动无法接受用户输入,不能执行任何代码。当前活动将要被暂停,上一个活动将要被恢复时调用
onStop()
当活动不在可见时调用
onDestroy()
当活动被系统销毁之前调用
onRestart()
当活动被停止以后重新打开时调用
bundle Bundle主要用于传递数据 :它保存的数据,是以key-value(键值对)的形式存在的。
我们经常使用Bundle在Activity之间传递数据 ,传递的数据可以是boolean、byte、int、long、float、double、string等基本类型或它们对应的数组,也可以是对象或对象数组。当Bundle传递的是对象或对象数组时,实现Serializable 或 Parcelable 接口。
常见组件
onclick:点击事件
button:按钮
EditText: 编辑文本控件 编辑框(EditText)是TextView 的子类,在TextView 的基础上增加了文本编辑功能,用于处理用户输入,例如登录框等,是非常常用的组件。
APK android 创建一个空项目,打开hello xxx。先不在意细节,直接编译,可以直接运行(创建一个模拟器)
打包成APK文件:
Build -> Build Bundles/Apks -> Build APKs
Build -> Make Project 然后在执行1
路径 app/build/outputs/apk
如果是debug版本,需要更改 build variants 为 release
然后就可以在jadx中反编译看看。
Native
Android Studio选择创建一个native C++ 项目,等待gradle
出现一个 cpp
的文件夹,这就是我们需要写的库。同时存在java文件夹
Java层导入库
1 static { System.loadLibrary("xxx" ); }
在Java代码中出现如下的方法
1 public native static void demo () ;
jni.h
在android ndk 下可以找到,直接采使用 find 命令找。
某些类型
1 2 3 4 typedef void * jobject;typedef jobject jclass;typedef jobject jstring;typedef jobject jarray;
JNIEnv 和 JavaVM声明,在C和C++ 用法不太相同。
JNI 定义了两个关键数据结构,即JavaVM
和JNIEnv
。两者本质上都是指向函数表的二级指针。(在 C++ 版本中,它们是一些类,这些类具有指向函数表的指针,并具有每个通过该函数表间接调用的 JNI 函数的成员函数。)JavaVM 提供调用接口
函数,您可以利用此类来函数创建和销毁 JavaVM。理论上,每个进程可以有多个 JavaVM,但 Android 只允许有一个。
JNIEnv 提供了大部分 JNI 函数。原生函数都会收到 JNIEnv 作为第一个参数。
1 2 3 4 5 6 7 8 9 10 11 struct _JNIEnv ;struct _JavaVM ;typedef const struct JNINativeInterface * C_JNIEnv;#if defined(__cplusplus) typedef _JNIEnv JNIEnv;typedef _JavaVM JavaVM;#else typedef const struct JNINativeInterface * JNIEnv;typedef const struct JNIInvokeInterface * JavaVM;#endif
JavaVM 原型
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 struct _JavaVM { const struct JNIInvokeInterface * functions; #if defined(__cplusplus) jint DestroyJavaVM () { return functions->DestroyJavaVM (this ); } jint AttachCurrentThread (JNIEnv** p_env, void * thr_args) { return functions->AttachCurrentThread (this , p_env, thr_args); } jint DetachCurrentThread () { return functions->DetachCurrentThread (this ); } jint GetEnv (void ** env, jint version) { return functions->GetEnv (this , env, version); } jint AttachCurrentThreadAsDaemon (JNIEnv** p_env, void * thr_args) { return functions->AttachCurrentThreadAsDaemon (this , p_env, thr_args); }#endif }; struct JNIInvokeInterface { void * reserved0; void * reserved1; void * reserved2; jint (*DestroyJavaVM)(JavaVM*); jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void *); jint (*DetachCurrentThread)(JavaVM*); jint (*GetEnv)(JavaVM*, void **, jint); jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void *); };
JNIEnv
1 2 3 struct JNINativeInterface { }
native method,签名有个具体的表
1 2 3 4 5 typedef struct { const char * name; const char * signature; void * fnPtr; } JNINativeMethod;
静态注册
静态注册的函数名一般为 Java_包名_类名_函数名
将包名中的 .
替换为 _
就是native层函数的名称
函数:从jni.h
可以看出,第一个是JNIEnv,第二个为jclass,然后就是Java层代码的参数
android studio 创建一个native C++ 默认为静态注册
native-lib.cpp
1 2 3 4 5 6 7 8 9 10 11 #include <jni.h> #include <string> extern "C" JNIEXPORT jstring JNICALL Java_com_learn_native_1lib_MainActivity_stringFromJNI ( JNIEnv* env, jobject ) { std::string hello = "Hello from C++" ; return env->NewStringUTF (hello.c_str ()); }
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 25 26 27 28 29 30 31 32 33 34 35 36 package com.learn.native_lib; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; import com.learn.native_lib.databinding.ActivityMainBinding; public class MainActivity extends AppCompatActivity { static { System.loadLibrary("native_lib" ); } private ActivityMainBinding binding; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); binding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); TextView tv = binding.sampleText; tv.setText(stringFromJNI()); } public native String stringFromJNI () ; }
模拟器运行 + jadx反编译 + ida 打开so文件
动态注册
函数名不用这么长,但是一般会与Java层的名称相同,方便开发
JNI_Onload 函数,动态注册
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 #define classname "com/learn/native_demo/MainActivity" static jclass myClass;jint JNI_OnLoad (JavaVM* vm, void * reserved) { JNIEnv* env = NULL ; jint result = -1 ; if (vm->GetEnv ((void **) &env, JNI_VERSION_1_4) != JNI_OK) { return -1 ; } myClass = env->FindClass (className); if (myClass == NULL ) { printf ("cannot get class:%s\n" , className); return -1 ; } if (env->RegisterNatives (myClass, gMethods, sizeof (gMethods)/sizeof (gMethods[0 ])) < 0 ) { printf ("register native method failed!\n" ); return JNI_FALSE; } return JNI_VERSION_1_4; }
动态注册,主要靠JNIEnv的RegisterNatives函数
1 2 jint RegisterNatives (jclass clazz, const JNINativeMethod* methods, jint nMethods)
我们需要自定义 JNINativeMethod 数组
getNativeString
为Java类中定义的Native方法名。
()Ljava/lang/String;
为方法的签名, ()
表示该方法无参数
reinterpret_cast<void*>(getString)
为Native实现的方法名。这里强制转换成了函数指针。
这些函数都需要我们实现
1 2 3 static JNINativeMethod gMethods[] = { {"getNativeString" , "()Ljava/lang/String;" , reinterpret_cast <void *>(getString)} };
smali 语法 Dalvik 虚拟机:Dalvik 是 Google 专门为 Android 平台设计的虚拟机。虽然 Android 程序可以使用 Java 语言来进行开发,但 Dalvik VM 和 Java VM 是两款不同的虚拟机。Dalvik VM 基于寄存器,而 Java VM 基于栈 。Dalvik VM 有专门的文件执行格式 dex (Dalvik Executable),而 Java VM 则执行的是 Java 字节码。DVM 比 JVM 速度更快,占用的空间更少。
不必要死记硬背,使用时查表,用着就熟悉了
Smali - CTF Wiki (ctf-wiki.org)
Firda
只能说多看官方文档
命令使用
1 2 3 4 5 frida -U <package_name> -l hook.js --no-pause frida-ps -Uai // 获得名称 frida -U <name> -l hook.js
某些方法
1 2 3 4 5 6 7 Java .vm .getEnv ()hexdump ()readCString () toInt32 ()
native hook
获得so基址
获得函数基址,进行attach,两个方法,进入函数(onEnter)和退出函数(onLeave)的操作
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 Java .perform (() => { let lib_name = "xxx.so" let func_offset = 0x114514 ; let libc_base = Module .findBaseAddress (lib_name); console .log ("libc base: " + libc_base); let func_addr = libc_base.add (func_offset); Interceptor .attach (func_addr, { onEnter : function (args ) { console .log ("start function" ); console .log ("args[2] = \n" + hexdump (args[2 ])); console .log ("args[3] = \n" + hexdump (args[3 ])); }, onLeave : function (retval ) { console .log ("function return" ); console .log ("return => " + hexdump (retval)); } }); });
inline hook
卡死的几率比较高
获得指令的地址
打印上下文 context
1 2 3 4 5 6 7 8 9 10 11 12 Interceptor .attach (addr, { onEnter : function (args ) { console .log ("start function" ); console .log (JSON .stringify (this .context )) conslole.log (this .context .x0 ) }, onLeave : function ( ) { } });
IDA Attach
IDA yyds
手机启动 IDA Pro dbgsrv
中的 android_server(x86_64 或者 arm 根据机型选择)
端口转发
IDA Pro 顶栏 Debugger -> Attach -> Remote Android debugger
需要端口转发才能attach,将android端口转发一下
1 adb forward tcp:23946 tcp:23946
参考