Android-2

安卓新人😋

Hello World

  1. Android Studio 创建一个项目,等待gradle
  2. 想要run起来,创建一个device。在Android Studio顶栏,device manager -> create.
  • 什么都先选第一个,缺什么东西使用IDE下载

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 接口。

常见组件

  1. onclick:点击事件
  2. button:按钮
  3. EditText:
    编辑文本控件
    编辑框(EditText)是TextView 的子类,在TextView 的基础上增加了文本编辑功能,用于处理用户输入,例如登录框等,是非常常用的组件。

APK

android 创建一个空项目,打开hello xxx。先不在意细节,直接编译,可以直接运行(创建一个模拟器)

打包成APK文件:

  1. Build -> Build Bundles/Apks -> Build APKs
  2. Build -> Make Project 然后在执行1
  3. 路径 app/build/outputs/apk

如果是debug版本,需要更改 build variants 为 release

然后就可以在jadx中反编译看看。

Native

  1. Android Studio选择创建一个native C++ 项目,等待gradle
  2. 出现一个 cpp 的文件夹,这就是我们需要写的库。同时存在java文件夹
  • device创建或者使用已经存在的
  1. Java层导入库
1
static { System.loadLibrary("xxx"); }
  1. 在Java代码中出现如下的方法
1
public native static void demo();

jni.h

在android ndk 下可以找到,直接采使用 find 命令找。

  1. 某些类型
1
2
3
4
typedef void*           jobject;
typedef jobject         jclass;
typedef jobject         jstring;
typedef jobject         jarray;
  1. JNIEnv 和 JavaVM声明,在C和C++ 用法不太相同。

JNI 定义了两个关键数据结构,即JavaVMJNIEnv。两者本质上都是指向函数表的二级指针。(在 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
  1. 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 /*__cplusplus*/
};

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*);
};
  1. JNIEnv
1
2
3
struct JNINativeInterface {
// xxx 比较多
}
  1. native method,签名有个具体的表
1
2
3
4
5
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;

静态注册

  1. 静态注册的函数名一般为 Java_包名_类名_函数名 将包名中的 . 替换为 _ 就是native层函数的名称
  2. 函数:从jni.h 可以看出,第一个是JNIEnv,第二个为jclass,然后就是Java层代码的参数
  3. 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 /* this */) {
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 {

// Used to load the 'native_lib' library on application startup.
static {
System.loadLibrary("native_lib");
}

private ActivityMainBinding binding;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

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 'native_lib' native library,
* which is packaged with this application.
* */
public native String stringFromJNI();
}

模拟器运行 + jadx反编译 + ida 打开so文件

动态注册

  1. 函数名不用这么长,但是一般会与Java层的名称相同,方便开发
  2. 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;
//1. 从JavaVM获取JNIEnv,这里使用1.4的版本
if(vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
// 2. 获取映射的java类
myClass = env->FindClass(className);
if(myClass == NULL)
{
printf("cannot get class:%s\n", className);
return -1;
}
// 2. 通过RegisterNatives方法动态注册
if(env->RegisterNatives(myClass, gMethods, sizeof(gMethods)/sizeof(gMethods[0])) < 0)
{
printf("register native method failed!\n");
return JNI_FALSE;
}
// 4. 返回版本,否则加载会失败。
return JNI_VERSION_1_4;
}
  1. 动态注册,主要靠JNIEnv的RegisterNatives函数
1
2
jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,
jint nMethods)
  1. 我们需要自定义 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
# 新版默认不会暂停,使用--pause 暂停 。
frida-ps -Uai // 获得名称
frida -U <name> -l hook.js

某些方法

1
2
3
4
5
6
7
// 获取环境变量,就是获取 JNIEnv ,并且我们可以调用JNIEnv的方法
Java.vm.getEnv()

// 类型转化
hexdump()
readCString()
toInt32()

native hook

  1. 获得so基址
  2. 获得函数基址,进行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);

// 一般so层函数第一个参数都是JniEnv,第二个参数是jclass
let func_addr = libc_base.add(func_offset);
Interceptor.attach(func_addr, {
onEnter: function (args) {
console.log("start function");
// console.log("args[0] = \n" + hexdump(args[0]));
// console.log("args[1] = \n" + hexdump(args[1]));
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

卡死的几率比较高

  1. 获得指令的地址
  2. 打印上下文 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

  1. 手机启动 IDA Pro dbgsrv 中的 android_server(x86_64 或者 arm 根据机型选择)
  2. 端口转发
  3. IDA Pro 顶栏 Debugger -> Attach -> Remote Android debugger

需要端口转发才能attach,将android端口转发一下

1
adb forward tcp:23946 tcp:23946

参考