又是一个周五晚上,就在刚才搜集到这道题的关键“证据”,到这里这道题算是完全”破解“成功(开心)。所以就写一篇文章,总结下我的心路历程吧,包括但不限于:
总之,完全是新手向的文章,遇到的坑,一步步怎么做,我都会说清楚,即使你是新手也没关系。
然后,这也是我第一次真真切切深入安卓逆向,之前只是静态反编译解决,这次学习了frida,CE(cheat engine),安卓模拟器等工具使用,写篇文章,也算是自己的一个阶段性总结。
看完这篇文章你讲学到:
题目非常简单,输入n1ctf{flag}, 点击check检查,很正规的安卓题
放入jadx-gui查看一下主逻辑:
可以看到主要逻辑在enc中,enc 属于native 函数,通过JNI调用,enc位于通过System.loadLibrary()的两个so中
ida 打开libnative.so反编译会发现有大量的类似指针数组的调用,其实这是JNI调用
关于JNI调用可以看:[https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html](https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html)
native code 想要访问java VM的特性就需要调用JNI函数,调用JNI函数需要JNI interface pointer
并且JNI interface pointer是native函数的第一个参数,如下:
这里 double 会经过名称混淆变为Java_pkg_Cls_f_ILjava_lang_String_2
JNI interface pointer是一个pointer to pointer,具体来说就是一个指针数组,这个数组保存着JNI函数的地址,包括:
但是ida中并没有JNIEnv等等结构体,一个个倒入自动识别太麻烦,手动计算又太蠢
该怎么办呢?
其实真正常用的JNI 函数就那几个,可以看到enc中传入了字符串,所以native函数想要获取这个字符串,会调用关于String的JNI调用 一般为GetStringUTFChars
关于环境:我的pc是mac m1,手头也没有安卓设备,最后选择mu mu pro模拟器(啥都好,就是要花钱)
在mu mu pro模拟器中安装好 frida server后,运行frida server后就可以hook了
运行结果
这里就知道sub_1b148是enc了
接下来,定位enc调用了哪些函数,还是hook
运行结果
这就获取了调用顺序,在ida里看一下,一眼丁真,分别是EOR,rc4,base64
可以看到EOR和rc4的密钥都是rand()获取的,libnative2.so中的.init.array中有个init函数,初始化了随机种子
真正解密会发现解密失败,实际上这里rand被修改了,如法炮制,在libnative1.so的.init.array中有三个函数
这里可以很明显的是一个rand的替换操作,rand替换为了sub_1B140,这个函数恒定返回233,就是真正的密钥了。
如果这个修改rand got表的操作不在.init.got表中,如何找到他呢?
tips:
要看so 在哪被修改了,CE 扫描的时机很重要,要在native2加载的时候扫描一次,然后native1加载后或者再往后的一个时机扫描改变的字节
所以要hook System.loadLibrary()
这里真是大坑了,查看github issues 才知道System.loadLibrary()是不可以hook的函数之一,因为你在Java.perfrom()里使用,但它会修改classloadrer,导致报错
所以最根本的方法就是hook dlopen 或者 android_dlopen_ext
这里我选择 hook android_dlopen_ext,在 native1加载的时候暂停一会,方便CE 扫描
hook mprotect的调用,关注地址在so地址范围的地址
运行结果
计算偏移 正好是0x43f8 也就是 rand_ptr的位置
CE 查看修改后的内容
正好是 native1 中 sub_1B140
整体 难度不大 但是很有趣 这个过程中探索了各种工具的使用 各种环境的搭建 还是学到了很多,感谢你看到了这里 祝你玩的开心。
public
class
MainActivity
extends
AppCompatActivity {
private
ActivityMainBinding binding;
public
native
String enc(String str);
public
native
String stringFromJNI();
void
m157lambda$onCreate$
0
$comn1ctf2024ezapkMainActivity(View view) {
String obj =
this
.binding.flagText.getText().toString();
if
(obj.startsWith(
"n1ctf{"
) && obj.endsWith(
"}"
)) {
if
(enc(obj.substring(
6
, obj.length() -
1
)).equals(
"iRrL63tve+H72wjr/HHiwlVu5RZU9XDcI7A="
)) {
Toast.makeText(
this
,
"Congratulations!"
,
1
).show();
return
;
}
else
{
Toast.makeText(
this
,
"Try again."
,
0
).show();
return
;
}
}
Toast.makeText(
this
,
"Try again."
,
0
).show();
}
static
{
System.loadLibrary(
"native2"
);
System.loadLibrary(
"native1"
);
}
}
public
class
MainActivity
extends
AppCompatActivity {
private
ActivityMainBinding binding;
public
native
String enc(String str);
public
native
String stringFromJNI();
void
m157lambda$onCreate$
0
$comn1ctf2024ezapkMainActivity(View view) {
String obj =
this
.binding.flagText.getText().toString();
if
(obj.startsWith(
"n1ctf{"
) && obj.endsWith(
"}"
)) {
if
(enc(obj.substring(
6
, obj.length() -
1
)).equals(
"iRrL63tve+H72wjr/HHiwlVu5RZU9XDcI7A="
)) {
Toast.makeText(
this
,
"Congratulations!"
,
1
).show();
return
;
}
else
{
Toast.makeText(
this
,
"Try again."
,
0
).show();
return
;
}
}
Toast.makeText(
this
,
"Try again."
,
0
).show();
}
static
{
System.loadLibrary(
"native2"
);
System.loadLibrary(
"native1"
);
}
}
package
pkg;
class
Cls {
native
double
f(
int
i, String s);
...
}
package
pkg;
class
Cls {
native
double
f(
int
i, String s);
...
}
jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (
JNIEnv *env,
jobject obj,
jint i,
jstring s)
{
const
char
*str = (*env)->GetStringUTFChars(env, s, 0);
...
(*env)->ReleaseStringUTFChars(env, s, str);
return
...
}
jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (
JNIEnv *env,
jobject obj,
jint i,
jstring s)
{
const
char
*str = (*env)->GetStringUTFChars(env, s, 0);
...
(*env)->ReleaseStringUTFChars(env, s, str);
return
...
}
const
struct
JNINativeInterface ... = {
NULL,
NULL,
NULL,
NULL,
GetVersion,
DefineClass,
GetJavaVM,
GetStringRegion,
GetStringUTFRegion,
GetObjectRefType
};
const
struct
JNINativeInterface ... = {
NULL,
NULL,
NULL,
NULL,
GetVersion,
DefineClass,
GetJavaVM,
GetStringRegion,
GetStringUTFRegion,
GetObjectRefType
};
_BYTE *__fastcall iusp9aVAyoMI(
__int64
a1,
size_t
a2)
{
size_t
i;
_BYTE *v4;
v4 =
malloc
(a2);
__memcpy_chk(v4, a1, a2, -1LL);
for
( i = 0LL; i < a2; ++i )
v4[i] ^=
rand
();
return
v4;
}
_BYTE *__fastcall SZ3pMtlDTA7Q(
__int64
a1,
int
a2)
{
v20[2] = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
v16 =
malloc
(a2);
__memcpy_chk(v16, a1, a2, -1LL);
v20[1] = 0LL;
v20[0] = 0LL;
for
( i = 0; i < 16; ++i )
*((_BYTE *)v20 + i) =
rand
();
}
_BYTE *__fastcall iusp9aVAyoMI(
__int64
a1,
size_t
a2)
{
size_t
i;
_BYTE *v4;
v4 =
malloc
(a2);
__memcpy_chk(v4, a1, a2, -1LL);
for
( i = 0LL; i < a2; ++i )
v4[i] ^=
rand
();
return
v4;
}
_BYTE *__fastcall SZ3pMtlDTA7Q(
__int64
a1,
int
a2)
{
v20[2] = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
v16 =
malloc
(a2);
__memcpy_chk(v16, a1, a2, -1LL);