APK解包后是没有dex文件的,运行代码使用了NativeActivity的方法封装到了libnative.so 这个文件中。APK实际包含了2个APP,这2个APP的包名一致。一个存放在libnative.so文件中,另一个是解压后的dex文件。APK安装后,的功能是摇晃收集达到一定次数(10秒100次,肯定做不到)然后解压一个dex文件到特定的目录下。flag在通过分析释放出来dex文件得出。
这里得到flag涉及了几个步骤:
提示:以下是本篇文章正文内容,下面案例可供参考
题目链接:https://adworld.xctf.org.cn/task/task_list?type=mobile&number=6&grade=0&page=1
dex文件结构
文件头固定:64 65 78 0A 30 33 35 00
odex是dex的优化,结构如下:
文件头固定:64 65 79 0A 30 33 36 00
可以看一下AndroidManifest
可以看到这个是个纯C/C++写的,没有Java代码,是个NativeActivity。
但是后面又有个MainActivity,所以可能是运行so之后就会放出来一个dex。
NativeActivity的启动函数是android_main,进入so中查看android_main
源码分析
int __fastcall android_main(_DWORD *a1)
{
void *v2; // r5
char *v3; // r10
int v4; // r2
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
char *v19; // r3
time_t v20; // r5
int v21; // r8
Bytef *dest; // [sp+8h] [bp-158h]
int v24; // [sp+10h] [bp-150h] BYREF
char v25[4]; // [sp+14h] [bp-14Ch] BYREF
int v26[13]; // [sp+18h] [bp-148h] BYREF
uLongf destLen; // [sp+4Ch] [bp-114h] BYREF
char v28[8]; // [sp+50h] [bp-110h] BYREF
int v29; // [sp+58h] [bp-108h]
float v30; // [sp+70h] [bp-F0h]
char name[4]; // [sp+B8h] [bp-A8h] BYREF
int v32; // [sp+BCh] [bp-A4h]
int v33; // [sp+C0h] [bp-A0h]
int v34; // [sp+C4h] [bp-9Ch]
int v35; // [sp+C8h] [bp-98h]
int v36; // [sp+CCh] [bp-94h]
int v37; // [sp+D0h] [bp-90h]
int v38; // [sp+D4h] [bp-8Ch]
int v39; // [sp+D8h] [bp-88h]
int v40; // [sp+DCh] [bp-84h]
int v41; // [sp+E0h] [bp-80h]
__int16 v42; // [sp+E4h] [bp-7Ch]
char v43; // [sp+E6h] [bp-7Ah]
char filename[4]; // [sp+E8h] [bp-78h] BYREF
int v45; // [sp+ECh] [bp-74h]
int v46; // [sp+F0h] [bp-70h]
int v47; // [sp+F4h] [bp-6Ch]
int v48; // [sp+F8h] [bp-68h]
int v49; // [sp+FCh] [bp-64h]
int v50; // [sp+100h] [bp-60h]
int v51; // [sp+104h] [bp-5Ch]
int v52; // [sp+108h] [bp-58h]
int v53; // [sp+10Ch] [bp-54h]
int v54; // [sp+110h] [bp-50h]
int v55; // [sp+114h] [bp-4Ch]
int v56; // [sp+118h] [bp-48h]
char v57; // [sp+11Ch] [bp-44h]
int v58; // [sp+124h] [bp-3Ch]
destLen = 0x100000;
dest = (Bytef *)malloc(0x100000u);
v2 = off_43A18;
v3 = (char *)malloc((size_t)off_43A18);
qmemcpy(v3, &unk_7004, (size_t)v2);
*(_DWORD *)filename = -1651995345;
v45 = -2003974520;
v46 = -1966700387;
v47 = -2000190330;
v48 = -2071422265;
v49 = -947092071;
v50 = -1920499569;
v51 = -1936879484;
v52 = -2138061167;
v53 = -962950011;
v54 = -1702328950;
v55 = -946172774;
v56 = -376337267;
v57 = 0;
*(_DWORD *)name = -1651995194;
v32 = -2003974520;
v33 = -1966700387;
v34 = -2000190330;
v35 = -2071422265;
v36 = -947092071;
v37 = -1920499569;
v38 = -1936879484;
v39 = -2138061167;
v40 = -962950011;
v41 = -1853059706;
v43 = 0;
v4 = 1;
v42 = -5690;
do
filename[v4++] ^= 0xE9u;
while ( v4 != 53 );
v5 = 1;
name[0] = 47;
do
name[v5++] ^= 0xE9u;
while ( v5 != 47 );
j_app_dummy();
memset(v26, 0, sizeof(v26));
*a1 = v26;
a1[1] = sub_29B8;
a1[2] = sub_2B90;
v26[0] = (int)a1;
v26[1] = ASensorManager_getInstance();
v26[2] = ASensorManager_getDefaultSensor(v26[1], 1);
v6 = 0;
v26[3] = ASensorManager_createEventQueue(v26[1], a1[7], 3, 0, 0);
v7 = (int *)a1[5];
if ( v7 )
{
v8 = v7[1];
v9 = v7[2];
v26[10] = *v7;
v26[11] = v8;
v26[12] = v9;
}
_android_log_print(4, "FindMyDex", "Can you shake your phone 100 times in 10 seconds?");
v10 = 0;
do
{
while ( 1 )
{
v12 = 0;
if ( !v26[4] )
v12 = -1;
v13 = ALooper_pollAll(v12, 0, v25, &v24);
if ( v13 >= 0 )
break;
if ( v26[4] )
{
v11 = *(float *)&v26[10] + 0.01;
if ( (float)(*(float *)&v26[10] + 0.01) > 1.0 )
v11 = 0.0;
*(float *)&v26[10] = v11;
sub_2C14(v26);
}
}
if ( v24 )
(*(void (__fastcall **)(_DWORD *))(v24 + 8))(a1);
if ( v13 == 3 && v26[2] )
{
while ( 1 )
{
do
{
if ( ASensorEventQueue_getEvents(v26[3], v28, 1) < 1 )
goto LABEL_51;
}
while ( v29 != 1 );
if ( (v10 & 1) != 0 )
{
if ( v30 >= -15.0 )
{
LABEL_30:
v14 = v10;
goto LABEL_31;
}
if ( v10 == 1 )
v6 = time(0);
v14 = v10 + 1;
}
else
{
if ( v30 <= 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) <= 0x58 )
{
v10 = v14;
v15 = v14 / 10;
if ( v14 % 10 == 9 )
{
v16 = off_43A18;
v17 = (int)off_43A18 / 10;
v18 = (v15 + 1) * ((int)off_43A18 / 10);
if ( (int)off_43A18 / 10 * v15 < v18 )
{
v19 = &v3[v17 * v15];
do
{
--v17;
*v19++ ^= v14;
}
while ( v17 );
}
if ( v14 == 89 )
{
while ( v18 < (int)v16 )
v3[v18++] ^= 0x59u;
}
v10 = v14 + 1;
}
}
if ( v14 == 100 )
{
if ( (int)(time(0) - v6) > 9 )
{
_android_log_print(4, "FindMyDex", "OH~ You are too slow. Please try again");
qmemcpy(v3, &unk_7004, (size_t)off_43A18);
v10 = 0;
}
else
{
v20 = v6;
if ( uncompress(dest, &destLen, (const Bytef *)v3, (uLong)off_43A18) )
_android_log_print(5, "FindMyDex", "Dangerous operation detected.");
v21 = open(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(name, 0) && mkdir(name, 0x1FFu) )
_android_log_print(5, "FindMyDex", "Something wrong with the permission..");
sub_2368(a1);
remove(filename);
_android_log_print(4, "FindMyDex", "Congratulations!! You made it!");
sub_2250(a1);
v10 = 0x80000000;
v6 = v20;
}
}
}
}
LABEL_51:
;
}
while ( !a1[15] );
sub_2BDA(v26);
return _stack_chk_guard - v58;
}
要求10秒内晃动100次,并逐渐解密dex文件,解密算法使用异或操作。加密的dex文件存放位置:
qmemcpy(v3, &unk_7004, (size_t)v2);//v2为3CA10
地址:0x7004 大小:3CA10
加密算法是将这部分内容分为10分,并且分别和9、19、29、39、49、59、69、79、89 进行异或。如下:
数据范围 | 异或值 |
---|---|
(0, 24833) | 9 |
(24833, 49666) | 19 |
(49666, 74499) | 29 |
(74499, 99332) | 39 |
(99332, 124165) | 49 |
(124165, 148998) | 59 |
(148998, 173831) | 69 |
(173831, 198664) | 79 |
(198664, 223497) | 89 |
(223497, 248336) | 89 |
IDA pro中直接运行python脚本得到dex文件,代码如下:
dump地址文件
import idaapi
addr = 0x7004
size = 0x3CA10
with open('C:/Users/Happy/Desktop/题目/dump', 'wb') as f:
f.write(idaapi.get_bytes(addr,size))
print('dump finished')
python解密
import zlib
with open('dump', 'rb') as f:
data1 = f.read()
data = list(data1)
v14 = 0
while True:
if (v14 - 1) <= 0x58:
v15 = v14 // 10
if v14 % 10 == 9:
v16 = 0x3CA10
v17 = 0x3CA10 // 10
v18 = (v15 + 1) * (0x3CA10 // 10)
if 0x3CA10 // 10 * v15 < v18:
v19 = v17 * v15
v17 -= 1
data[v19] ^= v14
v19 += 1
while v17:
v17 -= 1
data[v19] ^= v14
v19 += 1
if v14 == 89:
while v18 < v16:
data[v18] ^= 0x59
v18 += 1
else:
break
v14 += 1
filebytes = bytes(data)
with open('easy-dex.dex', 'wb') as w:
w.write(zlib.decompress(filebytes))
print('decrypt finished')
运行脚本文件得到easy-dex.dex文件
package com.a.sample.findmydex;
import android.os.Bundle;
import android.support.v7.a.u;
import android.widget.Button;
import android.widget.EditText;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
/* loaded from: C:\Users\Happy\Desktop\题目\easy-dex.dex */
public class MainActivity extends u {
private static byte[] m = {-120, 77, -14, -38, 17, 5, -42, 44, -32, 109, 85, 31, 24, -91, -112, -83, 64, -83, Byte.MIN_VALUE, 84, 5, -94, -98, -30, 18, 70, -26, 71, 5, -99, -62, -58, 117, 29, -44, 6, 112, -4, 81, 84, 9, 22, -51, 95, -34, 12, 47, 77};
/* JADX INFO: Access modifiers changed from: private */
public static byte[] b(String str, String str2) {
try {
BufferedInputStream bufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(str.getBytes()));
ArrayList arrayList = new ArrayList();
Object a = b.a(str2.getBytes());
for (byte[] bArr = new byte[16]; bufferedInputStream.read(bArr, 0, 16) != -1; bArr = new byte[16]) {
arrayList.add(b.a(bArr, 0, a));
}
ByteBuffer allocate = ByteBuffer.allocate(arrayList.size() * 16);
for (Object obj : arrayList.toArray()) {
allocate.put((byte[]) obj);
}
return allocate.array();
} catch (Exception e) {
return new byte[1];
}
}
/* JADX INFO: Access modifiers changed from: protected */
@Override // android.support.v7.a.u, android.support.v4.a.v, android.support.v4.a.p, android.app.Activity
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(2130968602);
((Button) findViewById(2131427413)).setOnClickListener(new a(this, (EditText) findViewById(2131427412), this));
}
}
在资源文件中找到:twofish
python还原加密字符串
import base64
m = [-120, 77, -14, -38, 17, 5, -42, 44, -32, 109, 85, 31, 24, -91, -112, -83,
64, -83, -128, 84, 5, -94, -98, -30, 18, 70, -26, 71, 5, -99, -62, -58, 117, 29,
-44, 6, 112, -4, 81, 84, 9, 22, -51, 95, -34, 12, 47, 77]
data = []
for i in m:
data.append(i&0xFF)
print(base64.b64encode(bytes(data)))
加密字符串:iE3y2hEF1izgbVUfGKWQrUCtgFQFop7iEkbmRwWdwsZ1HdQGcPxRVAkWzV/eDC9N
加密密钥:I have a male fish and a female fish.
在线解密网址:http://tool.chacuo.net/crypttwofish
得到flag:qwb{TH3y_Io
总结
ida
jadx
异或
base64
dex