题目来源: Timer(阿里CTF) - Bugku CTF
解压题目附件, 是一个APK. 安装到开发机上, 发现有一个倒计时功能. 倒计时结束(大约两天半左右)后即可获取到flag
alictf{Y0vAr3TimerMa3te7}
完
解法2:
静态分析
使用jadx加载apk进行反编译. 由于该apk并未混淆, 可以非常好的还原其源代码.
jadx得到的关键代码如下所示:
handler.postDelayed(new Runnable() {
/* class net.bluelotus.tomorrow.easyandroid.MainActivity.AnonymousClass1 */
public void run() {
MainActivity.this.t = System.currentTimeMillis();
MainActivity.this.now = (int) (MainActivity.this.t / 1000);
MainActivity.this.t = 1500 - (MainActivity.this.t % 1000);
tv2.setText("AliCTF");
if (MainActivity.this.beg - MainActivity.this.now <= 0) {
tv1.setText("The flag is:");
tv2.setText("alictf{" + MainActivity.this.stringFromJNI2(MainActivity.this.k) + "}"); // 关键代码在这里, 需要解出k
}
if (MainActivity.is2(MainActivity.this.beg - MainActivity.this.now)) {
MainActivity.this.k += 100;
} else {
MainActivity mainActivity = MainActivity.this;
mainActivity.k--;
}
tv1.setText("Time Remaining(s):" + (MainActivity.this.beg - MainActivity.this.now));
handler.postDelayed(this, MainActivity.this.t);
}
}, 0);
可以看到, 在第11行处调用了JNI, 该调用利用传入的k
值计算出flag的内容并返回. 因此, 求解本题的flag就需要两个关键操作:
- 求解
k
值 - 将
k
值传入函数stringFromJNI2
, 解出flag.
求解flag
分两步走, 首先:
求解变量k
变量k的运算过程其实很简单, 就是一个循环过程而已. 使用循环进行重写即可.
//let date = new Date();
//let beg = (Number.parseInt(date.getTime() / 1000)) + 200000;// 因为只是计算时间差值,
let beg = 200000; // 因此并不需要实际的时间值
let t = 0;
let k = 0;
function is2(n) {
if (n <= 3) {
return n > 1;
}
if (n % 2 == 0 || n % 3 == 0) {
return false;
}
for (let i = 5; i * i <= n; i += 6) {
if (n % i == 0 || n % (i + 2) == 0) {
return false;
}
}
return true;
}
//i = date.getTime();
i = 0; // i是毫秒级
while (1) {
t = i;
now = Number.parseInt(t / 1000); // now是"秒"级
t = 1500 - (t % 1000); // t是"毫秒"级
if (beg - now <= 0) { // beg和now都是"秒"级
console.log("k is ", k);
break;
}
if (is2(beg - now)) {
k += 100;
} else {
k--;
}
i+=t;
}
最终求解出的k
值为1616384
传入函数stringFromJNI2, 求解flag
吐槽
起初我想的是使用Ghidra逆向so库, 将函数stringFromJNI2
逆向出来执行. 但是奈何对arm的汇编不熟, 捉急了老半天没能把算法逆向出来. 遂决定放弃, 使用动态调试的方式拿flag.
首先尝试使用jadx导出gradle工程, 使用Android Studio导入工程, 连接adb对该应用debug, 从而实现传入k
到stringFromJNI2
, 得到flag. 但是未果, Android Studio编译时出现各种各样奇奇怪怪的Gradle兼容性问题.
正文:
在安卓手机中使用MT管理器打开该app, 直接修改dalvik虚拟机字节码. 之后签名重装, 成功拿到flag.
在MT管理器中打开apk文件, 打开classes.dex, 点击MainActivity$1
类.
定位到run
方法, 找到取k
值的地方, 将k
改为定值1616384
;
取k 值 |
将k 改为定值 |
---|---|
图中v3 寄存器即为变量k |
定位到变量beg
和now
大小判断的逻辑, 将差v0
直接修改为定值0
.
beg 和now 大小判断的逻辑代码 |
将v0 修改为定值0 |
---|---|
最终改动的字节码如下所示:
get v1, v1, Lnet/bluelotus/tomorrow/easyandroid/MainActivity;->now:I
const v0, 0x0
if-gtz v0, :cond_64
.line 53
iget-object v0, p0, Lnet/bluelotus/tomorrow/easyandroid/MainActivity$1;->val$tv1:Landroid/widget/TextView;
const-string v1, "The flag is:"
invoke-virtual {v0, v1}, Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;)V
.line 54
iget-object v0, p0, Lnet/bluelotus/tomorrow/easyandroid/MainActivity$1;->val$tv2:Landroid/widget/TextView;
new-instance v1, Ljava/lang/StringBuilder;
invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V
const-string v2, "alictf{"
invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v1
iget-object v2, p0, Lnet/bluelotus/tomorrow/easyandroid/MainActivity$1;->this$0:Lnet/bluelotus/tomorrow/easyandroid/MainActivity;
iget-object v3, p0, Lnet/bluelotus/tomorrow/easyandroid/MainActivity$1;->this$0:Lnet/bluelotus/tomorrow/easyandroid/MainActivity;
const v3, 0x18aa00
invoke-virtual {v2, v3}, Lnet/bluelotus/tomorrow/easyandroid/MainActivity;->stringFromJNI2(I)Ljava/lang/String;
最后再把drwable里面那阴间配色的背景图给换掉, 不然很影响看flag.
最终用MT管理器回签名, 安装新apk, 就可以看到flag了.
关于JNI的逆向可以看一看安卓逆向之自动化 JNI 静态分析 (seebug.org), Ghidra安装插件jni_helper时, Ghidra内置变量$GHIDRA_HOME
的目录是位于Ghidra的安装目录下的Ghidra文件夹. 另外, 在Script Manager里右击鼠标可以选择Script Directories菜单项, 在这里可以添加脚本目录.