XCTF攻防世界 easy-dex 解题过程分析
本帖最后由 neilwu 于 2020-2-15 09:53 编辑0x00 前言
easy-dex.APK 是xctf moblie 新手区第11题,获取Flag的过程比较有学习价值,固记录一次Crack过程。
0x01easy-dex app 运行界面
运行界面没有任何组件显示,只是变换界面颜色。
0x02 Java层逻辑分析
apk中没有dex,找到dex应该就可以找到flag
android.app.NativeActivity Android的ndk开发,dex应该是藏在了so中。
Android NativeActivity 原理简单介绍一下,后面的分析需要用到;
1、 NativeActivity是Android进行纯NDK开发使用的,和普通的Activity没有区别;
2、 NativeActivity通过native_app_glu 启动c++线程, 然后创建线程在应用程序的.so动态链接库中寻找函数 __ANativeActivity_onCreate(ANativeActivity, void* size_t),并调用 android_main(android_app* pApplication)函数;
3、C++的入口函数就是android_main
0x03 Native层分析
ANativeActivity_onCreate
int __fastcall ANativeActivity_onCreate(_JNIEnv *a1, int a2, size_t a3)
{
_JNIEnv *v3; // r8
int v4; // r10
size_t v5; // r9
JNINativeInterface *v6; // r0
__int64 v7; // r2
char *v8; // r5
void *v9; // r0
int *v10; // r0
char *v11; // r0
pthread_attr_t attr; //
v3 = a1;
v4 = a2;
v5 = a3;
v6 = (JNINativeInterface *)a1->functions;
v6->DefineClass = (jclass (*)(JNIEnv *, const char *, jobject, const jbyte *, jsize))sub_3000;
v6->reserved0 = sub_305A;
v6->reserved1 = sub_3062;
v6->reserved2 = sub_306A;
v6->reserved3 = sub_30BE;
HIDWORD(v7) = sub_30EE;
v6->GetVersion = (jint (*)(JNIEnv *))sub_30C6;
LODWORD(v7) = sub_30DE;
v6->ThrowNew = (jint (*)(JNIEnv *, jclass, const char *))sub_30CE;
v6->ExceptionOccurred = (jthrowable (*)(JNIEnv *))sub_30D6;
*(_QWORD *)&v6->FindClass = v7;
v6->GetSuperclass = (jclass (*)(JNIEnv *, jclass))sub_30F4;
v6->IsAssignableFrom = (jboolean (*)(JNIEnv *, jclass, jclass))sub_30FC;
v6->ToReflectedField = (jobject (*)(JNIEnv *, jclass, jfieldID, jboolean))sub_3102;
v8 = (char *)malloc(0x94u);
_aeabi_memclr4();
*((_DWORD *)v8 + 3) = v3;
pthread_mutex_init((pthread_mutex_t *)(v8 + 64), 0);
pthread_cond_init((pthread_cond_t *)(v8 + 68), 0);
if ( v4 )
{
v9 = malloc(v5);
*((_DWORD *)v8 + 5) = v9;
*((_DWORD *)v8 + 6) = v5;
_aeabi_memcpy(v9, v4, v5);
}
if ( pipe(&attr.__align + 6) )
{
v10 = (int *)_errno();
v11 = strerror(*v10);
_android_log_print(6, "threaded_app", "could not create pipe: %s", v11);
v8 = 0;
}
else
{
*((_QWORD *)v8 + 9) = *((_QWORD *)&attr.__align + 3);
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, 1);
pthread_create((pthread_t *)v8 + 20, &attr, (void *(*)(void *))sub_325C, v8);
pthread_mutex_lock((pthread_mutex_t *)(v8 + 64));
while ( !*((_DWORD *)v8 + 27) )
pthread_cond_wait((pthread_cond_t *)(v8 + 68), (pthread_mutex_t *)(v8 + 64));
pthread_mutex_unlock((pthread_mutex_t *)(v8 + 64));
}
v3.functions = (const JNINativeInterface *)v8;
return _stack_chk_guard - *(&attr.__align + 8);
}
其中
pthread_create((pthread_t *)v8 + 20, &attr, (void *(*)(void *))sub_325C, v8);
java_main代码如下
int __fastcall android_main(int a1)
{
int v1; // r11
void *v2; // r5
Bytef *v3; // r10
signed int v4; // r2
signed int v5; // r1
time_t v6; // r8
int v7; // r0
int v8; // r3
int v9; // r6
int v10; // r4
float v11; // s0
int v12; // r0
int v13; // r5
int v14; // r5
int v15; // r3
void *v16; // r0
int v17; // r2
int v18; // r1
Bytef *v19; // r3
time_t v20; // r5
int v21; // r8
Bytef *dest; //
void *outData; //
int outEvents; //
int v26; //
ASensorManager *manager; //
const ASensor *v28; //
ASensorEventQueue *queue; //
int v30; //
float v31; //
int v32; //
int v33; //
uLongf destLen; //
char v35; //
int v36; //
float v37; //
int name; //
int v39; //
int v40; //
int v41; //
int v42; //
int v43; //
int v44; //
int v45; //
int v46; //
int v47; //
int v48; //
__int16 v49; //
char v50; //
int filename; //
int v52; //
int v53; //
int v54; //
int v55; //
int v56; //
int v57; //
int v58; //
int v59; //
int v60; //
int v61; //
int v62; //
int v63; //
char v64; //
int v65; //
v1 = a1;
destLen = 0x100000;
dest = (Bytef *)malloc(0x100000u);
v2 = off_43A18;
v3 = (Bytef *)malloc((size_t)off_43A18);
_aeabi_memcpy(v3, &unk_7004, v2);
filename = -1651995194;
v52 = -2003974520;
v53 = -1966700387;
v54 = -2000190330;
v55 = -2071422265;
v56 = -947092071;
v57 = -1920499569;
v58 = -1936879484;
v59 = -2138061167;
v60 = -962950011;
v61 = -1702328950;
v62 = -946172774;
v63 = -376337267;
v64 = 0;
name = -1651995194;
v39 = -2003974520;
v40 = -1966700387;
v41 = -2000190330;
v42 = -2071422265;
v43 = -947092071;
v44 = -1920499569;
v45 = -1936879484;
v46 = -2138061167;
v47 = -962950011;
v48 = -1853059706;
v50 = 0;
v4 = 1;
v49 = -5690;
LOBYTE(filename) = 47;
do
*((_BYTE *)&filename + v4++) ^= 0xE9u;
while ( v4 != 53 );
v5 = 1;
LOBYTE(name) = 47;
do
*((_BYTE *)&name + v5++) ^= 0xE9u;
while ( v5 != 47 );
j_app_dummy();
_aeabi_memclr8(&v26, 52);
*(_DWORD *)v1 = &v26;
*(_DWORD *)(v1 + 4) = sub_29B8;
*(_DWORD *)(v1 + 8) = &sub_2B90;
v26 = v1;
manager = ASensorManager_getInstance();
v28 = ASensorManager_getDefaultSensor(manager, 1);
v6 = 0;
queue = ASensorManager_createEventQueue(manager, *(ALooper **)(v1 + 28), 3, 0, 0);
v7 = *(_DWORD *)(v1 + 20);
if ( v7 )
{
v8 = *(_DWORD *)(v7 + 4);
v9 = *(_DWORD *)(v7 + 8);
v31 = *(float *)v7;
v32 = v8;
v33 = v9;
}
_android_log_print(4, "FindMyDex", "Can you shake your phone 100 times in 10 seconds?");
v10 = 0;
do
{
while ( 1 )
{
v12 = 0;
if ( !v30 )
v12 = -1;
v13 = ALooper_pollAll(v12, 0, &outEvents, &outData);
if ( v13 >= 0 )
break;
if ( v30 )
{
v11 = v31 + 0.01;
if ( (float)(v31 + 0.01) > 1.0 )
v11 = 0.0;
v31 = v11;
sub_2C14(&v26);
}
}
if ( outData )
(*((void (__fastcall **)(int))outData + 2))(v1);
if ( v13 == 3 && v28 )
{
while ( 1 )
{
do
{
if ( ASensorEventQueue_getEvents(queue, (ASensorEvent *)&v35, 1u) < 1 )
goto LABEL_51;
}
while ( v36 != 1 );
if ( v10 & 1 )
{
if ( v37 >= -15.0 )
{
LABEL_30:
v14 = v10;
goto LABEL_31;
}
if ( v10 == 1 )
v6 = time(0);
v14 = v10 + 1;
}
else
{
if ( v37 <= 15.0 )
goto LABEL_30;
v14 = v10 + 1;
if ( v10 >= 0 )
_android_log_print(4, "FindMyDex", "Oh yeah~ You Got it~ %d times to go~", 99 - v10);
}
LABEL_31:
v10 = v14;
if ( (unsigned int)(v14 - 1) <= 88 )
{
v10 = v14;
v15 = v14 / 10;
if ( v14 % 10 == 9 )
{
v16 = off_43A18;
v17 = (signed int)off_43A18 / 10;
v18 = (v15 + 1) * ((signed int)off_43A18 / 10);
if ( (signed int)off_43A18 / 10 * v15 < v18 )
{
v19 = &v3;
do
{
--v17;
*v19++ ^= v14;
}
while ( v17 );
}
if ( v14 == 89 )
{
while ( v18 < (signed int)v16 )
v3 ^= 0x59u;
}
v10 = v14 + 1;
}
}
if ( v14 == 100 )
{
if ( time(0) - v6 > 9 )
{
_android_log_print(4, "FindMyDex", "OH~ You are too slow. Please try again");
_aeabi_memcpy(v3, &unk_7004, off_43A18);
v10 = 0;
}
else
{
v20 = v6;
if ( uncompress(dest, &destLen, v3, (uLong)off_43A18) )
_android_log_print(5, "FindMyDex", "Dangerous operation detected.");
v21 = open((const char *)&filename, 577, 511);
if ( !v21 )
_android_log_print(5, "FindMyDex", "Something wrong with the permission.");
write(v21, dest, destLen);
close(v21);
free(dest);
free(v3);
if ( access((const char *)&name, 0) && mkdir((const char *)&name, 0x1FFu) )
_android_log_print(5, "FindMyDex", "Something wrong with the permission..");
sub_2368(v1);
remove((const char *)&filename);
_android_log_print(4, "FindMyDex", "Congratulations!! You made it!");
sub_2250(v1);
v10 = 2147483648;
v6 = v20;
}
}
}
}
LABEL_51:
;
}
while ( !*(_DWORD *)(v1 + 60) );
sub_2BDA(&v26);
return _stack_chk_guard - v65;
}
代码整体逆向下来,思路如下
1、读取字节流,通过解密后进行解压缩获得dex;
2、手机在1s中摇100次,会在/data/data/com.a.sample.findmydex/files 目录下生成 class.dex和odex,然后remove掉;
加密的dex以字节流的方式写在.data中,起始位置是 0x7004长度是 0x3ca10
用ida py脚本进行dump
import idaapi
start_address=0x7004
data_length=0x3ca10
data=idaapi.get_bytes(start_address, data_length)
fp = open('dump', 'wb')
fp.write(data)
fp.close()
解密算法
解密->解压缩->还原dex->删除
用Java实现一下解密dex
try {
FileInputStream fis =new FileInputStream("xx://dump");
int length = 0x3ca10;
byte[] b = new byte;
int tempchar;
int index = 0;
while((tempchar= fis.read()) != -1){
b = (byte) tempchar;
index++;
}
int v15,v16,v17,v18,v19;
for(int v14=0;v14<90;v14++){
v15 = v14 / 10;
if(v14 % 10 == 9){
v16 = length;
v17 = length / 10;
v18 = (v15 + 1) * (length / 10);
if(length / 10 * v15 < v18){
v19 = v17 * v15;
do{
--v17;
b ^= v14;
}
while (v17>0);
}
if(v14 == 89){
while (v18 < v16){
b ^= 89;
}
}
}
}
fis.close();
FileOutputStream fos = new FileOutputStream("xx://class.dex");
for(byte ch : unjzlib(b)){
fos.write(ch);
}
fos.flush();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static byte[] unjzlib(byte[] object) {
byte[] data = null;
try {
ByteArrayInputStream in = new ByteArrayInputStream(object);
ZInputStream zIn = new ZInputStream(in);
byte[] buf = new byte;
int num = -1;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((num = zIn.read(buf, 0, buf.length)) != -1) {
baos.write(buf, 0, num);
}
data = baos.toByteArray();
baos.flush();
baos.close();
zIn.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
return data;
}
获得的dex
拿到dex就可以开始搞一下flag了
由此可见是 明文+key = 密文
由此可以看到 应该是使用twofish算法
对m进行一下转义
new String(Base64.getEncoder().encode(m)
然后找个工具验证一下
到此获得成获得flag
0x04 总结
这道题主要是考察了Anddroid NativeActivity的理解,记录一下过程,大佬勿喷。
我在看雪发过一个类似的题目解题过程,题目几乎是一样的
https://bbs.pediy.com/thread-257289.htm
格式太乱了,编辑下吧。 为啥图全挂了?我重新编辑一下吧
Hmily 发表于 2020-2-14 22:53
格式太乱了,编辑下吧。
版主 我重新编辑了一下 我看是正常了 你看看 请问解密dex的Java代码中ZipInputStream是什么类 5ud0 发表于 2020-8-19 16:52
请问解密dex的Java代码中ZipInputStream是什么类
jzlib-1.1.3 还是不是很懂T^T 请教下问题 do
{
if ( ASensorEventQueue_getEvents(v26, v28, 1) < 1 )
goto LABEL_51;
}
while ( v29 != 1 );
这个代码的v29什么时候变1,也就是上面代码的v36
好帖子,感谢分享
页:
[1]