JNI, NDK, so, IDA
实验准备
在settings-System Settings-Android SDK-SDK Tools中安装NDK 和 CMake
在AndroidStudio中新建C++项目
选择语言为java
等待 Build,项目运行正常。
在Project Structure中配置ndk路径,然后可以在local.properties中查看配置
并且项目会生成cpp文件夹
查看 CMakeLists.txt 的内容
在构建脚本中指定“native-lib”作为共享库的名称,CMake 就会创建一个名为
libnative-lib.so 的文件
- add_library 用来设置编译生成的本地库的名字为 native-lib,SHARED 表示
编译生成的是动态链接库,native-lib.cpp 表示参与编译的文件。 - find_library 是用来添加一些我们在编译我们的本地库的时候需要依赖的一些
库,由于 cmake 已经知道系统库的路径,所以这里只是指定使用 log 库,然
后给 log 库起别名为 log-lib 便于后面引用,此处的 log 库是后面调试时需要
用来打 log 日志的库,是 NDK 提供的。 - target_link_libraries 是为了关联自己的库和一些第三方库或者系统库,这里把
自己的库 native-lib 库和 log 库关联起来。
实验过程
一、编写登录程序
在MainActivity里声明
public native boolean checkNameAndPwd(String name, String pwd);
并且alt+enter自动create在对应cpp中生成代码
编写实现方法
extern "C"
JNIEXPORT jboolean JNICALL
/**
* C 语言检查用户名密码
* @param name 用户名
* @param pwd 密码
* @return name == pwd
*/
Java_com_exampie_hellojni_MainActivity_checkNameAndPwd(JNIEnv *env, jobject thiz, jstring name,
jstring pwd) {
// TODO: implement checkNameAndPwd()
// 获取 nameC 和 pwdC
const char *nameC = (char *)(env->GetStringUTFChars(name, JNI_FALSE));
const char *pwdC = (char *)(env->GetStringUTFChars(pwd, JNI_FALSE));
env->ReleaseStringUTFChars(name, nameC);
env->ReleaseStringUTFChars(pwd, pwdC);
return (jboolean)(strncmp(nameC, pwdC, strlen(pwdC)+1) == 0);
}
在MainActivity里编写监听代码
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TextView tv = findViewById(R.id.checkResult);
EditText editTextName = findViewById(R.id.name);
EditText editTextPwd = findViewById(R.id.pwd);
String result = checkNameAndPwd(editTextName.getText().toString(), editTextPwd.getText().toString()) ? "Welcome" : "Sorry";
tv.setText(result);
}
});
}
layout页面
尝试运行,运行成功
二、学习so文件
在 src-main 文件夹中,新建一个文件夹 jnilibs,然后将 build 出来的 so 文件拷贝进去
接着,删除 cpp 文件夹里的 cpp 文件
事实上,此时 AS 也会提醒该文件被正引用着,所以很显然,删除完之后,还需要删除对应的引用。
这里是 CMakeLists.txt 文件:
注释 add_library 部分:
再注释 target_link_libraries 部分:
此时,MainActivity 之前声明的方法都报红,因为 cpp 文件已经被删除,它并不知
道其如何实现:
删除 build 文件夹,重新打包。这时会发现,build 不再有 cmake 文件夹。
重新安装程序,发现运行正常。
在 Android Studio 中,如果将 so 文件放在 libs 目录里,是不会被打包到 apk 中
的,只有 jniLibs 目录里的 so 文件会被打包到 apk 中。当然这个目录指定的文件夹
可以更改,但是默认就是 \src\main\jniLibs
更改目录的方法是,在 app 内的 gradle 文件里面的 android{…},添加一个:
sourceSets {
main {
jniLibs.srcDirs = ['src/main/jni']
}
}
此时,src/main/jni 下的 so 文件也会被打包,是的,之前的 jniLibs 仍然可用
流程类似于手动编译,但是安全性大大提高
so文件调试
打开 CMD,作为电脑端,在 IDA 的 dbgsrv 目录下,将 android_server 文件
上传到手机端(路径可以自定义):
adb push android_server /data/local/tmp/android_server
再打开一个 CMD,作为手机端,进入其终端:
adb shell
进入刚才上传的文件夹,可以看到文件上传成功:
赋予文件可执行权限,并执行:
- 赋予权限
chmod 777 android_server - 查看
ls -l android_server - 执行文件
./android_server
端口转发,电脑端 CMD 输入命令:
adb forward tcp:23946 tcp:23946
打开 IDA(注意无需打开 so 文件),选择 Debugger -> Attach-> Remote
ARMLinux/Android debugger,设置对应 IP 及端口,这里即默认的 23946
点击 OK,会弹框如下,选择对应的 apk 包名
然后再modules中选择对应的函数,双击查看代码
F2 下断点,然后 F9 ,此时 APP 可以输入用户名密码:
输入后点击提交,程序停在断点处:
接下来尝试静态调试
使用ak逆向,打开MainActivity查看汇编代码,但是看不到native-lib实现
但是多了lib文件夹,里面有so文件
使用ida打开so文件,找到MainActivity
F5查看伪代码
分析代码,v20和v19分别存储a3用户名和a4密码,即传入的参数,接着根据v8用户名的长度 malloc一个v7,将当前值赋为ascii63(为‘?’),总长度减一然后strncat一个字符串he110
成功