[原创]看雪 2021 KCTF 春季赛 第五题 华山论剑
source link: https://bbs.pediy.com/thread-267627.htm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
用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的代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <assert.h>
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
;
}
编译: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)。
LOAD:
00005D04
;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
LOAD:
00005D04
CODE16
LOAD:
00005D04
B sub_5D10 ; Branch
LOAD:
00005D04
;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
LOAD:
00005D06
CODE32
LOAD:
00005D06
DCW
0xBF00
LOAD:
00005D08
DCD
8
,
0x30D00
LOAD:
00005D10
CODE16
LOAD:
00005D10
LOAD:
00005D10
;
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
S U B R O U T I N E
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
LOAD:
00005D10
LOAD:
00005D10
; Attributes: thunk
LOAD:
00005D10
LOAD:
00005D10
sub_5D10 ; CODE XREF: LOAD:
00005D04
↑j
LOAD:
00005D10
BX PC ; Branch to
/
from
Thumb mode
LOAD:
00005D10
;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
LOAD:
00005D12
DCB
1
LOAD:
00005D13
DCB
0
LOAD:
00005D13
; End of function sub_5D10
LOAD:
00005D13
LOAD:
00005D14
CODE32
LOAD:
00005D14
LOAD:
00005D14
;
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
S U B R O U T I N E
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
LOAD:
00005D14
LOAD:
00005D14
LOAD:
00005D14
sub_5D14 ; CODE XREF: sub_5D10↑j
LOAD:
00005D14
LOAD:
00005D14
var_FC
=
-
0xFC
LOAD:
00005D14
LOAD:
00005D14
STR
PC, [SP,
#var_FC] ; Store to Memory
LOAD:
00005D18
B sub_7270 ; Branch
LOAD:
00005D18
; End of function sub_5D14
LOAD:
00005D1C
;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
LOAD:
00005D1C
CODE16
LOAD:
00005D1C
B sub_5D28 ; Branch
LOAD:
00005D1C
;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
LOAD:
00005D1E
CODE32
几个关键的位置:
所有的内存读取: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的字母大小写可以替换,造成多解。
[看雪官方培训] Unicorn Trace还原Ollvm算法!《安卓高级研修班》2021年6月班火热招生!!
BL是 IDA 配置的问题,刚开始我也疑惑了很久,arm的标准一直在演进(例如 v5、v7),这个 so 在声明时使用的是 v5(使用 readelf -A),但这个机器码疑似是 v7 才有的,导致 IDA 识别错误(也可能是 IDA 本身默认就是这个错误的配置)。
正确的 IDA 配置方式如图(此时就可以下断点和调试):
出现疑问可以使用 pwntools 来验证猜想
In [3]: disasm(bytes.fromhex('5df804eb'), arch='thumb')
Out[3]: ' 0: f85d eb04 ldr.w lr, [sp], #4'
In [4]: disasm(bytes.fromhex('5df8'), arch='thumb')
Out[4]: ' 0: Address 0x0000000000000000 is out of bounds.'
断点不成功,因为 BL 是 2byte 的,运行时LDR是 4byte 的,下断点必然会抽风。
师傅请教一下为什么您的IDA反编译出来的代码存在0x5d04,而我反编译的出现这样的,是怎么得到师傅的那种情况的哇
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK