用jadx打开,发现java层只有简单的输入输出,检查逻辑在native层
解包apk,ida打开libhello-jni.so看native层,代码很乱,考虑动态调试
先尝试用ida在真机上调试,体验很不好(最主要的问题是,0x5000处的汇编指令ida识别为"BL LR, #0xBA ",但是单步调试时无法进入这条指令内部,不知道原因)
考虑到这个so文件接口不复杂,于是写了一个简单的loader在linux上直接加载(按相对偏移mmap所有的load segment;自己定义一些dummy函数填入got表和JNIEnv.functions表),然后gdb本地调试,体验非常好(方便随时重启;配合插件能看多级指针)(另:0x5000处的汇编在gdb里识别为"ldr lr, [sp], #4",和ida里不一样,而且能单步调试)(但是有一个坑,不能下数据断点(rwatch/watch),让后续分析变得麻烦)
loader的代码:
编译:arm-linux-gnueabi-gcc loader.c -g -mthumb -o a.out
运行:qemu-arm -L /usr/arm-linux-gnueabi/ -g 1234 ./a.out
调试:gdb-multiarch -ex "file ./a.out" -ex "target remote localhost:1234"
(在Ubuntu上可以 apt-get install gcc-arm-linux-gnueabi libc6-armel-cross gdb-multiarch 安装依赖)
题目是类似vmp的虚拟机。
虚拟机指令的结构:以0x5d04处为例:
从thumb指令开始(0x5d04),通过一个B跳转跳过1或2个dword(0x5d06和0x5d08)到后面的arm指令(0x5d10),先是 BX PC ,然后是 STR PC, [SP,#var_FC] 把PC放入栈,最后B跳转到一个外部函数(0x7270),外部函数返回到下一条指令。
外部函数大部分以push所有寄存器开始,以pop所有寄存器结束,通过栈上保存的PC向前找参数(0x5d06和0x5d08),返回到下一条指令(0x5d1c)。
几个关键的位置:
所有的内存读取:0x72fc、0x7304、0x730c
所有的内存写入:0x71f4、0x71fc、0x7204
比较(cmp):0x75ac
加法:0x7644
在这8个位置下断点,基本上就能看出程序的完整流程,不需要分析虚拟机指令:
分配并初始化若干个缓冲区
对name做一些运算
对serial做hexdecode(逐字符调用程序里的 sub_DDA 函数,这个函数没有混淆)
初始化RC4的sbox(0x6054附近,循环)
计算RC4(0x638c的附近,循环)
前期调试分析过程很漫长,但最终找出serial很简单:
先在 0x638c 下断点,运行到这里后在 0x71f4 下断点,可以发现每计算出一个加密值,就会存入另一个缓冲区中,且这个值与serial做hexdecode之后的值是相等的。所以,只需要把name初始化为"KCTF",就可以在这里提取出正确的serial。
name:KCTF
serial:17726331DA0FE737149C8202
hexdecode没有区分大小写,因此serial的字母大小写可以替换,造成多解。
struct
str
{
const char
*
s;
int
len
;
};
/
/
struct
str
global_name
=
{.s
=
"ed8b9244350d3644"
, .
len
=
16
};
/
/
struct
str
global_serial
=
{.s
=
"7C9815255BFE832D3F93140B"
, .
len
=
24
};
struct
str
global_name
=
{.s
=
"KCTF"
, .
len
=
4
};
struct
str
global_serial
=
{.s
=
"17726331DA0FE737149C8202"
, .
len
=
24
};
/
/
struct
str
global_serial
=
{.s
=
"17726331da0fe737149c8202"
, .
len
=
24
};
struct JNINativeInterface_ {
unsigned
int
f[
0x1000
/
4
];
};
typedef struct JNIEnv_ {
struct JNINativeInterface_
*
functions;
} JNIEnv;
void JNICALL_FindClass(JNIEnv
*
env, const char
*
name) {
printf(
"JNICALL_FindClass %s\n"
, name);
}
void JNICALL_NewStringUTF(JNIEnv
*
env, const char
*
utf) {
printf(
"%s %s\n"
, __func__, utf);
}
int
JNICALL_GetMethodID(JNIEnv
*
env, void
*
clazz, const char
*
name, const char
*
sig) {
printf(
"%s %p %s %s\n"
, __func__, clazz, name, sig);
return
0x55555501
;
}
void
*
JNICALL_CallObjectMethod(JNIEnv
*
env, void
*
obj,
int
methodID) {
assert
(methodID
=
=
0x55555501
);
printf(
"%s %p %x\n"
, __func__, obj, methodID);
return
obj;
}
int
JNICALL_GetArrayLength(JNIEnv
*
env, struct
str
*
array) {
printf(
"%s %p\n"
, __func__, array);
return
array
-
>
len
;
}
unsigned char
*
JNICALL_GetByteArrayElements(JNIEnv
*
env, struct
str
*
array,
int
isCopy) {
printf(
"%s %p %d\n"
, __func__, array, isCopy);
return
array
-
>s;
}
void JNICALL_ReleaseByteArrayElements(JNIEnv
*
env, void
*
array, void
*
elems,
int
mode) {
printf(
"%s %p %p %d\n"
, __func__, array, elems, mode);
}
void
*
got_malloc(
int
size) {
void
*
r
=
malloc(size);
printf(
"%s %d %p\n"
, __func__, size, r);
return
r;
}
void got_free(void
*
p) {
printf(
"%s\n"
, __func__);
free(p);
}
void got_memset(char
*
p,
int
n,
int
count) {
printf(
"%s %p %d %d\n"
, __func__, p, n, count);
memset(p, n, count);
}
void bp(void) {
;
}
void stack_chk_guard(void) {
printf(
"%s\n"
, __func__);
}
void imp___gnu_Unwind_Find_exidx(void) {
printf(
"%s\n"
, __func__);
}
void cxa_call_unexpected(void) {
printf(
"%s\n"
, __func__);
}
int
main(void) {
int
fd
=
open
(
"libhello-jni.so"
, O_RDONLY);
unsigned char
*
fmem
=
mmap(NULL,
0x7000
, PROT_READ, MAP_PRIVATE, fd,
0
);
unsigned char
*
mem
=
mmap(
0xdead0000
,
0x8000
, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS,
-
1
,
0
);
memcpy(mem, fmem,
0x2b13
);
memcpy(mem
+
0x3e8c
, fmem
+
0x2e8c
,
0x1a8
);
memcpy(mem
+
0x5000
, fmem
+
0x4000
,
0x26bc
);
munmap(fmem,
0x7000
);
close(fd);
*
(unsigned
int
*
)(mem
+
0x3f98
)
=
mem
+
0x1034
+
1
;
/
/
xxxxxxxxxx2
+
1
*
(unsigned
int
*
)(mem
+
0x3f9c
)
=
stack_chk_guard;
*
(unsigned
int
*
)(mem
+
0x3fb8
)
=
imp___gnu_Unwind_Find_exidx;
*
(unsigned
int
*
)(mem
+
0x3fc4
)
=
cxa_call_unexpected;
*
(unsigned
int
*
)(mem
+
0x3fc8
)
=
mem
+
0x3f98
;
/
/
_GLOBAL_OFFSET_TABLE_@got
*
(unsigned
int
*
)(mem
+
0x3fa0
)
=
mem
+
0x10e4
+
1
;
/
/
Java_com_example_hellojni_HelloJni_stringFromJNI_ptr
+
1
*
(unsigned
int
*
)(mem
+
0x3fa4
)
=
mem
+
0x4004
;
/
/
f_data_key_dllink
*
(unsigned
int
*
)(mem
+
0x3fa8
)
=
mem
+
0x401c
;
/
/
f_sucess
*
(unsigned
int
*
)(mem
+
0x3fdc
)
=
got_malloc;
/
/
malloc@got
*
(unsigned
int
*
)(mem
+
0x3fe0
)
=
got_memset;
/
/
memset@got
*
(unsigned
int
*
)(mem
+
0x3fe4
)
=
got_free;
/
/
free@got
for
(
int
i
=
0x3f98
; i <
0x4000
; i
+
=
4
) {
if
(
*
(unsigned
int
*
)(mem
+
i)
=
=
0
) {
/
/
*
(unsigned
int
*
)(
0x77770000
+
i)
=
i;
}
}
struct JNINativeInterface_ jnii
=
{.f
=
{
0
}};
for
(
int
i
=
0
; i <
0x100
; i
+
+
) {
jnii.f[i]
=
0x11110000
+
i
*
4
;
}
jnii.f[
0x18
/
4
]
=
JNICALL_FindClass;
jnii.f[
0x29c
/
4
]
=
JNICALL_NewStringUTF;
jnii.f[
0x84
/
4
]
=
JNICALL_GetMethodID;
jnii.f[
0x88
/
4
]
=
JNICALL_CallObjectMethod;
jnii.f[
0x2ac
/
4
]
=
JNICALL_GetArrayLength;
jnii.f[
0x2e0
/
4
]
=
JNICALL_GetByteArrayElements;
jnii.f[
0x300
/
4
]
=
JNICALL_ReleaseByteArrayElements;
JNIEnv env;
env.functions
=
&jnii;
printf(
"&global_name: %p, &global_serial: %p\n"
, &global_name, &global_serial);
bp();
((void (
*
)(
int
,
int
,
int
,
int
,
int
))(mem
+
0x10e4
+
1
))(&env,
0xaaaa
, &global_name, &global_serial,
0xdddd
);
return
0
;
}
struct
str
{
const char
*
s;
int
len
;
};
/
/
struct
str
global_name
=
{.s
=
"ed8b9244350d3644"
, .
len
=
16
};
/
/
struct
str
global_serial
=
{.s
=
"7C9815255BFE832D3F93140B"
, .
len
=
24
};
struct
str
global_name
=
{.s
=
"KCTF"
, .
len
=
4
};
struct
str
global_serial
=
{.s
=
"17726331DA0FE737149C8202"
, .
len
=
24
};
/
/
struct
str
global_serial
=
{.s
=
"17726331da0fe737149c8202"
, .
len
=
24
};
struct JNINativeInterface_ {
unsigned
int
f[
0x1000
/
4
];
};
typedef struct JNIEnv_ {
struct JNINativeInterface_
*
functions;
} JNIEnv;
void JNICALL_FindClass(JNIEnv
*
env, const char
*
name) {
printf(
"JNICALL_FindClass %s\n"
, name);
}
void JNICALL_NewStringUTF(JNIEnv
*
env, const char
*
utf) {
printf(
"%s %s\n"
, __func__, utf);
}
int
JNICALL_GetMethodID(JNIEnv
*
env, void
*
clazz, const char
*
name, const char
*
sig) {
printf(
"%s %p %s %s\n"
, __func__, clazz, name, sig);
return
0x55555501
;
}
void
*
JNICALL_CallObjectMethod(JNIEnv
*
env, void
*
obj,
int
methodID) {
assert
(methodID
=
=
0x55555501
);
printf(
"%s %p %x\n"
, __func__, obj, methodID);
return
obj;
}
int
JNICALL_GetArrayLength(JNIEnv
*
env, struct
str
*
array) {
printf(
"%s %p\n"
, __func__, array);
return
array
-
>
len
;
}
unsigned char
*
JNICALL_GetByteArrayElements(JNIEnv
*
env, struct
str
*
array,
int
isCopy) {
printf(
"%s %p %d\n"
, __func__, array, isCopy);
return
array
-
>s;
}
void JNICALL_ReleaseByteArrayElements(JNIEnv
*
env, void
*
array, void
*
elems,
int
mode) {
printf(
"%s %p %p %d\n"
, __func__, array, elems, mode);
}
void
*
got_malloc(
int
size) {
void
*
r
=
malloc(size);
printf(
"%s %d %p\n"
, __func__, size, r);
return
r;
}
void got_free(void
*
p) {
printf(
"%s\n"
, __func__);
free(p);
}
void got_memset(char
*
p,
int
n,
int
count) {
printf(
"%s %p %d %d\n"
, __func__, p, n, count);
memset(p, n, count);
}
void bp(void) {
;
}
void stack_chk_guard(void) {
printf(
"%s\n"
, __func__);
}
void imp___gnu_Unwind_Find_exidx(void) {
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2021-5-16 03:48
被mb_mgodlfyn编辑
,原因: