题目链接:app3
参考文章:
- XCTF攻防世界 Mobile新手训练区 Writeup
- 浅谈安卓系统备份文件ab格式解析
- android-backup-extractor 下载
- 攻防世界mobile-app3
- SQLiteStudio 操作教程
思路:
- 分析.ab文件,得到 tar文件;
- tar文件中包含有一个apk文件,对源码分析后,编写程序得到解密字符串;
- 使用解密后的字符串,打开.db文件,可以得到flag,但此时是base64加密的;
- 对base64进行解密得到本题最终的flag
本文的目标读者是初学者,因为自己也是初学者,所以加了很多的细节,因而显得有些错乱。 请根据自己的能力进行适当的阅读取舍。
具体的操作:
分析.ab 文件,得到tar文件
下载后的题目是 .ab文件。一开始直接修改后缀名为apk后,在虚拟机中进行安装,提示安装失败。网上查阅后发现 .ab 是Android的备份文件,需要先对文件进行分析。
根据 浅谈安卓系统备份文件ab格式解析,.ab文件分为加密和不加密的两种。需要使用二进制编辑器进行查看,这里使用 010 editext 进行编辑。

可以判断出本题的ab文件未进行加密,下载工具 android-backup-extractor 解压后为免安装版本,直接进入到目录下
android-backup-tookit\android-backup-extractor\android-backup-extractor-20180521-bin
可以查看到这样的内容

将本题的ab文件拷贝到该目录下,然后打开dos窗口,使用命令进行ab文件的转换。对于未加密的ab文件,命令格式为:
abe.jar unpack backup.ab backup.tar
在相应目录下的dos窗口中使用命令
abe.jar unpack app3.ab app3.tar

此时我们得到tar文件,完成了第一步。
分析源码
对tar文件进行解压后,进入到子目录中,可以看到有一个Encrypto.db文件,作为一个数据库文件,内部是否存有flag? 使用SQLiteStudio 尝试打开 (SQLiteStudio 下载 ),但是在数据类型选择为SQLite3 时,无法正常打开,加上这个这个文件的命名是加密的意思,换言之,这个文件里藏有秘密,但是需要我们先获取解密的密码。 我们只能 查看其他文件夹。发现在子目录a下有一个base.apk文件, 使用雷电模拟器打开文件,效果如下:

之后使用 Jadx 打开。通过查看AndrodiMainifest 文件可以确认程序的入口点是MainActivity。在MainActivity中可以看到

鉴于监听器的代码比较简单,先分析button 被点击后的处理:

传递了一些数据并跳转到 AnotherActivity

但是会发现这个类中,接收了数据后,甚至没有进行任何数据的比对,直接Toast输出一段话,感觉没有获取到任何有价值的信息。回到MainActivity 对函数 m20a进行分析:
/* renamed from: a */
private void m20a() {
SQLiteDatabase.loadLibs(this);
this.f33b = new DatabaseManager(this, "Demo.db", (SQLiteDatabase.CursorFactory) null, 1);
ContentValues contentValues = new ContentValues();
contentValues.put("name", "Stranger");
contentValues.put("password", 123456);
Cipher aVar = new Cipher();
String a = aVar.mo6317a(contentValues.getAsString("name"), contentValues.getAsString("password"));
this.f32a = this.f33b.getWritableDatabase(aVar.mo6316a(a + aVar.mo6318b(a, contentValues.getAsString("password"))).substring(0, 7));
this.f32a.insert("TencentMicrMsg", (String) null, contentValues);
}
首先我没看懂代码的含义,那么只能寻找一些突破点。一句句来看吧。加载了数据库,然后创建了一个DataManager的对象,于是进行对这个类的查看

应当是进行了表的创建,并且这里有一个”F_l_a_g“ 字段,证明我们需要到数据库中进行查询,加上上文的分析,我们需要获取数据库的密码。
回到m20函数的分析,在DataManager后进行了数据的键值对的存储,然后又新建了一个Cipher的对象,对Cipher进行查看。

结合Cipher 内部定义的函数和 m20a函数剩下的代码,
Cipher aVar = new Cipher();
String a = aVar.mo6317a(contentValues.getAsString("name"), contentValues.getAsString("password"));
this.f32a = this.f33b.getWritableDatabase(aVar.mo6316a(a + aVar.mo6318b(a, contentValues.getAsString("password"))).substring(0, 7));
this.f32a.insert("TencentMicrMsg", (String) null, contentValues);
我们一句一句进行分析,首先是
String a = aVar.mo6317a(contentValues.getAsString("name"), contentValues.getAsString("password"));
也就是 ⬇
String a = aVar.mo6317a("Stranger", "123456");
也就是 ⬇
String a = "Stra1234";
this.f32a = this.f33b.getWritableDatabase(aVar.mo6316a(a + aVar.mo6318b(a, contentValues.getAsString("password"))).substring(0, 7));
也就是 ⬇
this.f32a = this.f33b.getWritableDatabase(aVar.mo6316a("Stra1234" + aVar.mo6318b("Stra1234", "123456")).substring(0, 7));
也就是 ⬇
this.f32a = this.f33b.getWritableDatabase(aVar.mo6316a("Stra1234" + SHA1Manager.m24a("Stra1234")).substring(0, 7));
也就是 ⬇
this.f32a = this.f33b.getWritableDatabase(SHA1Manager.m25b("Stra1234" + SHA1Manager.m24a("Stra1234") + "yaphetshan").substring(0, 7));
对于 this.f33b.getWritableDatabase 内部调用了诸多父类的方法,暂时先不处理,我们先处理
SHA1Manager.m25b("Stra1234" + SHA1Manager.m24a("Stra1234") + "yaphetshan").substring(0, 7)
先看内部的m24a

对字符进行了MD5的算法处理。

对字符进行了SHA-1的算法处理。
我们新建一个java程序,看上述的算法可以得到什么样的结果。
import java.security.MessageDigest;
class SHA1Manager {
public static final String m24a(String str) {
char[] cArr = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
try {
byte[] bytes = str.getBytes();
MessageDigest instance = MessageDigest.getInstance("MD5");
instance.update(bytes);
// 这里的r4是自己添加的,为了程序正常运行
int r4 = instance.getDigestLength();
char[] cArr2 = new char[(r4 * 2)];
int i = 0;
for (byte b : instance.digest()) {
int i2 = i + 1;
cArr2[i] = cArr[(b >>> 4) & 15];
i = i2 + 1;
cArr2[i2] = cArr[b & 15];
}
return new String(cArr2);
} catch (Exception e) {
return null;
}
}
/* renamed from: b */
public static final String m25b(String str) {
char[] cArr = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
try {
byte[] bytes = str.getBytes();
MessageDigest instance = MessageDigest.getInstance("SHA-1");
instance.update(bytes);
// 这里的r4是自己添加的,为了程序正常运行
int r4 = instance.getDigestLength();
char[] cArr2 = new char[(r4 * 2)];
int i = 0;
for (byte b : instance.digest()) {
int i2 = i + 1;
cArr2[i] = cArr[(b >>> 4) & 15];
i = i2 + 1;
cArr2[i2] = cArr[b & 15];
}
return new String(cArr2);
} catch (Exception e) {
return null;
}
}
public static void main(String[] args){
String s = SHA1Manager.m25b("Stra1234" + SHA1Manager.m24a("Stra1234") + "yaphetshan").substring(0, 7);
System.out.println(s);
}
}
输出的结果为: ae56f99,于是最开始的 m20a函数中的
this.f32a = this.f33b.getWritableDatabase(aVar.mo6316a(a + aVar.mo6318b(a, contentValues.getAsString("password"))).substring(0, 7));
也就是 ⬇
this.f32a = this.f33b.getWritableDatabase(" ae56f99");
查看getWritableDatabase(String str)

由图可知,我们需要分析下面的函数,但是Jadx没能逆向出代码。此时,我们使用Jeb 打开这个apk文件,查看这个函数:

将String 转换为字符数组后,传入 getWritableDatabase(char[] str)

如果数据库不存在,就用传进来的参数,作为密码创建一个。因此使用上面我们求出来的字符串 "ae56f99", 用 SQLiteStudio 打开Encryto.db,选择 SQLCiper模式,
如下图连接成功

进而得到flag, 但注意到字符串最后结尾是=, 猜测是base64加密后的,即 VGN0ZntIM2xsMF9Eb19ZMHVfTG92M19UZW5jM250IX0=

对base64进行解密得到本题最终的flag
使用在线的base64解密工具: base64解码 得到 Tctf{H3ll0_Do_Y0u_Lov3_Tenc3nt!}

题目并不难,对于第2步的代码分析耗时又费力,其实当用Jadx 逆向出代码后,可以直接建立类 Ciphe在乎的是结果,在明确破解的思路后,可以直接使用Java进行输出。样例代码 提取码:ipb8