0.静态分析找到题目关键点
用jeb打开apk,先看程序入口点:
根据包名找到验证方法!
public void onClick(View view) {
if(password.getText().toString().equals(MainActivity.this.Encrypt())) {
Toast.makeText(MainActivity.this, "登录成功", 1).show();
return;
}
Toast.makeText(MainActivity.this, "登录失败", 0).show();
}
分析发现主要是将flag和Encrypt()函数的返回值进行对比!
继续看发现Encrypt()函数是native层的方法~
static {
System.loadLibrary("native-lib");
}
public native String Encrypt() {
}
导出so文件,拖入ida开始分析Encrypt()函数!
int __cdecl Java_com_example_ndktest2_MainActivity_Encrypt(_JNIEnv *a1)
{
char *v1; // eax
char *v2; // eax
char *v4; // [esp+24h] [ebp-B8h]
char v5[13]; // [esp+2Dh] [ebp-AFh] BYREF
char dest[6]; // [esp+3Ah] [ebp-A2h] BYREF
char v7[128]; // [esp+40h] [ebp-9Ch] BYREF
char v8[28]; // [esp+C0h] [ebp-1Ch] BYREF
strcpy(dest, "flag{");
strcpy(&v5[7], "wllm");
v5[12] = 0;
strcpy(v8, "welcome");
strcpy(v5, "newbee");
base64_encode(v5, v7);
v4 = strcat(dest, &v5[7]);
v1 = strcat(v8, v5);
v2 = strcat(v4, v1);
return _JNIEnv::NewStringUTF(a1, v2);
}
觉得很简单,flag中一部分通过base64加密后和flag{wllmwelcome拼接起来,最后试着提交密码,发现是错误的!非常的不理解QAQ
准备动调调试一下看看是不是base64或者哪里被修改了!在这个方法下个断点
Java_com_example_ndktest2_MainActivity_Encrypt(_JNIEnv *a1)
然后用ida+jeb+adb+雷神模拟器-》进行动态调试,实现过程在文章末尾…
1.根据静态分析开始动态调试
发现下了个断点后根本Encrypt()函数未被断下来非常不理解QAQ
总结一下过去写native层的apk的时候都涉及到一个函数_JNIEnv::NewStringUTF(a1, v2)
将传入的字符串进行格式统一,所以这个函数是一定会被用到的,那我直接在_JNIEnv::NewStringUTF(a1, v2)
这个函数下个断点来试试!
.text:A4FD1090 _ZN7_JNIEnv12NewStringUTFEPKc proc near ; CODE XREF: _JNIEnv::NewStringUTF(char const*)↑j
.text:A4FD1090 ; DATA XREF: .got.plt:off_A4FFEDF0↓o
.text:A4FD1090
.text:A4FD1090 this = dword ptr 8
.text:A4FD1090 arg_4 = dword ptr 0Ch
.text:A4FD1090
.text:A4FD1090 ; __unwind { // A4FC7000
.text:A4FD1090 push ebp
.text:A4FD1091 mov ebp, esp
.text:A4FD1093 push ebx
.text:A4FD1094 push edi
断点下在:A4FD1093,因为push ebp;mov ebp, esp;要保存栈帧
发现真的断了下来,说明没有反调试而是Encrypt()被混淆了或者被hook了
根据此时的函数调用栈找到了正真的函数test()
调用路径:test()->call art_quick_generic_jni_trampoline->call NewStringUTF
找到目标函数test才是正真的Encrypt()函数!!
去test函数下个断点,看看程序的返回值!
找到flag 了就是他"YouaretheB3ST"
2.解决Encrypt()函数是如何被混淆成test()的
但是依旧不理解这个调用的是Encrypt()却调用的test()
疯狂找wp:
- 第十届SWPUCTFwriteup-安全客 - 安全资讯平台 (anquanke.com)
讲解到了与JNI_Onload函数有关但不理解QAQ
.data:A4FFF008 off_A4FFF008 dd offset aEncrypt ; DATA XREF: sub_A4FD1570+21↑o
.data:A4FFF008 ; "Encrypt"
.data:A4FFF00C dd offset aLjavaLangStrin ; "()Ljava/lang/String;"
.data:A4FFF010 dd offset test
继续查资料:
知道了如何注册native方法的原理:
- java层执行:System.loadLibrary(“NativeLib”); //NativeLib 为native模块名称
- 触发函数JNI_Onload来注册方法
static JNINativeMethod methods[] = {
{"getNativeString", "()Ljava/lang/String;", reinterpret_cast<void*>(getString)}
};
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
JNIEnv* env;
if (JNI_OK != vm->GetEnv(reinterpret_cast<void**> (&env),JNI_VERSION_1_4)) {
LOGW("JNI_OnLoad could not get JNI env");
return JNI_ERR;
}
g_jvm = vm; //用于后面获取JNIEnv
jclass clazz = env->FindClass("com/example/myndkproj/NativeLib"); //获取Java NativeLib类
//注册Native方法
if (env->RegisterNatives(clazz, methods, sizeof(methods)/sizeof((methods)[0])) < 0) {
LOGW("RegisterNatives error");
return JNI_ERR;
}
return JNI_VERSION_1_4;
}
知道了注册原理和ida的伪代码很像!但是依旧不理解QAQ为啥!!
继续问chatgpt!
问:
1.我在调试apk时,apk注册了一个native方法Encrypt(),这个函数是写在.so文件里的,所以我在so文件下个断点但发现并未断下,反而so文件中的test()函数被调用了,apk调用的明明是Encrypt()方法为啥调用了test()函数
2.我可以确定函数名无错,Android有什么方法可以修改调用的函数
3._JNIEnv::RegisterNatives(a1, Class, a3, a4)
4.JNINativeMethod*数组如何写的
终于问到了关键点!!!>>>>>
JNINativeMethod
结构体数组用于在JNI中注册本地方法。
每个JNINativeMethod
结构体包含两个字段:
`char* name` 本地方法
`char* signature` 签名
`void* fnPtr` 实际的本地函数
发现不就是ida里面看到的!
.data:A4FFF008 off_A4FFF008 dd offset aEncrypt ; DATA XREF: sub_A4FD1570+21↑o
.data:A4FFF008 ; "Encrypt"
.data:A4FFF00C dd offset aLjavaLangStrin ; "()Ljava/lang/String;"
.data:A4FFF010 dd offset test
发现off_A4FFF008其实就是JNINativeMethod!!!
定义JNINativeMethod数组, 声明需要注册的方法
static JNINativeMethod methods[] = {
{"getNativeString", "()Ljava/lang/String;", reinterpret_cast<void*>(getString)}
};
其实那篇原理的文章就讲了但是我没理解QAQ
其中getNativeString
为Java类中定义的Native方法名。
()Ljava/lang/String;
为方法的签名, ()
表示该方法无参数, Ljava/lang/String;
表示返回值为Java中的String类型。具体签名规则请参考《JNI学习笔记》 中的内容。
reinterpret_cast<void*>(getString)
为Native实现的方法名。这里强制转换成了函数指针。
作者:JellyJoe_943
链接:https://www.jianshu.com/p/216a41352fd8
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
IDA调试Android的so文件
借鉴;[原创]新手关于ida动态调试so的一些坑总结-Android安全-看雪-安全社区|安全招聘|kanxue.com
启动模拟器后看看手机设备有几个:
C:\Users\Administrator>adb devices
List of devices attached
emulator-5554 device
高版本的雷电9.0模拟器有些坑,会自动启动两个安卓端口,所以连接的时候需要加IP
C:\Users\Administrator>adb -s 127.0.0.1:5555 push E:\ReverseTools\IDA8.3\dbgsrv\android_server64 /data/local/tmp/
E:\ReverseTools\IDA8.3\dbgsrv\android_server64: 1 file pushed, 0 skipped. 100.3 MB/s (1282848 bytes in 0.012s)
下载雷电模拟器5.0版,安卓后启动!
开始启动shell看看手机型号:
知识来源:adb查看手机设备型号、品牌、机型等信息_abd 获取手机型号-CSDN博客
C:\Users\Administrator>adb shell
root@aosp:/ # getprop ro.product.cpu.abi
x86
很显然是x86的那么接下来就是使用ida动态调试了,先把server push进模拟器~
C:\Users\Administrator>
adb push E:\ReverseTools\IDA8.3\dbgsrv\android_x86_server /data/local/tmp
E:\ReverseTools\IDA8.3\dbgsrv\android_x86_server: 1 file pushed, 0 skipped. 281.0 MB/s (1183352 bytes in 0.004s)
#给server文件权限
C:\Users\Administrator>adb shell
root@aosp:/ # cd /data/local/tmp/
root@aosp:/ # chmod 777 android_x86_server
#运行,成功
root@aosp:/data/local/tmp # ./android_x86_server
IDA Android x86 32-bit remote debug server(ST) v8.3.28. Hex-Rays (c) 2004-2023
2024-05-07 18:53:35 Listening on 0.0.0.0:23946...
在将手机的端口转发到Windows就可以了!
C:\Users\Administrator>adb forward tcp:23946 tcp:23946
23946
成功完成模拟器和ida的通信搭建!
接下来就算用jeb开始调试了!
先看看manifest有没有调试权限!
并且找到包名:<activity android:name="com.example.ndktest2.MainActivity">
有那么就可以开启程序的调试模式了用adb:
在cmd启动命令:
C:\Users\Administrator>adb shell am start -D -n com.example.ndktest2/.MainActivity
Starting: Intent { cmp=com.example.ndktest2/.MainActivity }
成功
打开jeb导出x86文件!
再将so文件导入ida!
在启动jeb的调试模式!
双击进程成功附加!!再去看看模拟器发现进入界面了!
下面就打开ida下个断点了,在Encrypt函数下个断点!
再修改一下:
如果不是默认调试端口就修改
由于Windows无法启动Android程序所以只能采取attach的模式!
选择目标
附加后会弹出:
下断点就可以调试了!
成功获得flag!