下载附件后是一个.ab文件。无其它提示信息。
ab文件
百度一下,.ab 后缀名的文件是 Android 系统的备份文件格式。
分为加密和未加密两种类型,.ab 文件的前 24 个字节是类似文件头的东西,如果是加密的,在前 24 个字节中会有 AES-256 的标志,如果未加密,则在前 24 个字节中会有 none 的标志。
用UE打卡这个样本,看到文件头中有none:
表明未加密。
可以通过abe.jar(android-backup-extractor)将其转换成.tar文件然后进行解压。
用法:java -jar abe.jar unpack xxx.ab xx.tar:
解压后目录如下:
目录a下有个base.apk:
题目提供ab文件就是这个apk的备份,我们下面重点分析它。
APK
拖到模拟器里运行一下:
随便输入用户名和密码,点击登录,弹出一条提示信息,让等几分钟:
不知道要等什么,我们就把它放在那里不管了,先反编译一下。
反编译
老步骤,apk重命名为zip,解压,使用dex2jar反编译解压后的dex:
用jd-gui打开得到的jar,找到MainActivity,先看构造函数:
构造函数主要工作:
- 调用setOnClickListener将自身设置为按钮点击事件响应函数,也就是点击按钮会调用MainActivity的onClick函数。
- 通过putString向数据存储区存入三个值
- 调用函数a
函数a的反编译代码为:
主要工作为:
- 打开数据库Demo.db
- 创建ContentValues对象并设置两个值
- 以不同参数调用类a的a、b、a函数计算数据库密码
- 以算出的密码打开数据库并插入数据
我们从tar解压后的内容中有两个db,一个Demo.db,一个Encrypto.db,并且这两个数据库都是有密码的,猜测这道题的重点就是获得密码,解密这两个数据库。
这次我们换一个思路,不去搞清计算密码的算法到底是啥,而是通过在Smali代码中插入LOG来将原程序计算好的密码输出到日志。
根据函数a的反编译代码,结合资料搜集,我们能知道getWritableDatabase函数是用来打开并得到数据库对象的函数,它的参数就是数据库的密码。在这里密码就是str1字符串的前7个字符。
我们只要在调用getWritableDatabase函数的代码之前插入Log函数,打印str1字符串就能得到密码。
修改Smali
首先使用apktool得到smali代码。Apktool能将apk反编译得到Smali代码。
Apktool会创建一个apk同名的目录,保存反编译的内容。为了不和之前zip解压的目录名冲突,这里我们将apk备份一个,重命名为base1.apk,之后对base1.apk进行操作。
使用apktool反编译base1.apk:
在apktool生成的目录中,我们找到MainActivity的Smali文件:
用文本编辑器打开MainActivity.smali,根据前面对jar的分析我们知道,数据库密码在MainActivity类的a函数中计算得到。在该smali文件中搜索getWritableDatabase,找到调用getWritableDatabase函数的代码:
.line 55
iget-object v2, p0, Lcom/example/yaphetshan/tencentwelcome/MainActivity;->b:Lcom/example/yaphetshan/tencentwelcome/a;
const/4 v3, 0x0
const/4 v4, 0x7
invoke-virtual {v1, v3, v4}, Ljava/lang/String;->substring(II)Ljava/lang/String;
move-result-object v1
invoke-virtual {v2, v1}, Lcom/example/yaphetshan/tencentwelcome/a;->getWritableDatabase(Ljava/lang/String;)Lnet/sqlcipher/database/SQLiteDatabase;
move-result-object v1
iput-object v1, p0, Lcom/example/yaphetshan/tencentwelcome/MainActivity;->a:Lnet/sqlcipher/database/SQLiteDatabase;
从中可以看出,第一个invoke-virtual调用substring来截取子串,这和jar代码一致。这里的第一个参数v1就是完整的字符串,也就是我们想获得的内容。
直接在这段代码前面插入smali代码,调用Log函数:
.line 55
invoke-static {v1, v1}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
.line 56
iget-object v2, p0, Lcom/example/yaphetshan/tencentwelcome/MainActivity;->b:Lcom/example/yaphetshan/tencentwelcome/a;
const/4 v3, 0x0
const/4 v4, 0x7
invoke-virtual {v1, v3, v4}, Ljava/lang/String;->substring(II)Ljava/lang/String;
move-result-object v1
invoke-virtual {v2, v1}, Lcom/example/yaphetshan/tencentwelcome/a;->getWritableDatabase(Ljava/lang/String;)Lnet/sqlcipher/database/SQLiteDatabase;
move-result-object v1
iput-object v1, p0, Lcom/example/yaphetshan/tencentwelcome/MainActivity;->a:Lnet/sqlcipher/database/SQLiteDatabase;
这里我们调用Log函数时,两个参数都设置为v1。
这里要注意,Log函数调用语句占用源代码中的一行,在这里就占据了.line 55。所以smali代码中后面的所有行号都要+1。
修改之后使用apktool打包成apk:
这里打包好的apk是没有签名的,无法安装。需要使用signapk进行签名:
到这里修改后的APK就准备好了。
查看LOG得到FLAG
打开Android Studio,在底部切换到Logcat窗口。左上角的下拉菜单是设备列表,这里选择我们的模拟器:
在模拟器中安装并运行修改后的apk,在Logcat窗口中就看到了密码:
用字符串的前7个字符解密Encryto.db,得到FLAG:
FLAG字符串以=结尾,怀疑是BASE64编码后的,对这个字符串进行BASE64解码,就得到了最终结果:
Tctf{H3ll0_Do_Y0u_Lov3_Tenc3nt!}
欢迎关注我的微博:大雄_RE。专注软件逆向,分享最新的好文章、好工具,追踪行业大佬的研究成果。