下载下来,是一个apk文件,就是安卓手机软件的安装包,先把它安装看一下:

只有一个界面,上面有个200000在倒计时,应该是等时间到了,就会显示flag。
接下来我们反编译,可以自己逐步解压缩,用apktools、dex2jar转化为jar包,也可以直接用集成工具打开
刚开始用JAD打开jar包,查看到的.class文件的代码是这样子的:

public class MainActivity
extends AppCompatActivity
{
int beg = (int)(System.currentTimeMillis() / 1000L) + 200000;//之前的当前时间+200000
int k = 0;
int now;
long t = 0L;
static
{
System.loadLibrary("lhm");
}
public static boolean is2(int paramInt)
{
boolean bool1 = true;
boolean bool2;
if (paramInt <= 3) {
if (paramInt > 1) {
bool2 = bool1;
}
}
for (;;)
{
return bool2;
bool2 = false;
continue;
if ((paramInt % 2 != 0) && (paramInt % 3 != 0)) {
break;
}
bool2 = false;
}
for (int i = 5;; i += 6)
{
bool2 = bool1;
if (i * i > paramInt) {
break;
}
if ((paramInt % i == 0) || (paramInt % (i + 2) == 0))
{
bool2 = false;
break;
}
}
}
protected void onCreate(final Bundle paramBundle)
{
super.onCreate(paramBundle);
setContentView(2130968600);
final TextView localTextView1 = (TextView)findViewById(2131492944);
final TextView localTextView2 = (TextView)findViewById(2131492945);
paramBundle = new Handler();
paramBundle.postDelayed(new Runnable()
{
public void run()
{
MainActivity.this.t = System.currentTimeMillis();
MainActivity.this.now = ((int)(MainActivity.this.t / 1000L));//当前时间的秒数(毫秒/1000)
MainActivity.this.t = (1500L - MainActivity.this.t % 1000L);
localTextView2.setText("AliCTF");
if (MainActivity.this.beg - MainActivity.this.now <= 0)
{
localTextView1.setText("The flag is:");
localTextView2.setText("alictf{" + MainActivity.this.stringFromJNI2(MainActivity.this.k) + "}");
}
MainActivity localMainActivity;
if (MainActivity.is2(MainActivity.this.beg - MainActivity.this.now)) {
localMainActivity = MainActivity.this;
}
for (localMainActivity.k += 100;; localMainActivity.k -= 1)
{
localTextView1.setText("Time Remaining(s):" + (MainActivity.this.beg - MainActivity.this.now));//剩下的时间是:
paramBundle.postDelayed(this, MainActivity.this.t);
return;
localMainActivity = MainActivity.this;
}
}
}, 0L);
}
public boolean onCreateOptionsMenu(Menu paramMenu)
{
getMenuInflater().inflate(2131558400, paramMenu);
return true;
}
public boolean onOptionsItemSelected(MenuItem paramMenuItem)
{
if (paramMenuItem.getItemId() == 2131492959) {}
for (boolean bool = true;; bool = super.onOptionsItemSelected(paramMenuItem)) {
return bool;
}
}
public native String stringFromJNI2(int paramInt);
}
主要的函数的逻辑就是时间从200000秒开始逐秒减少计时,每减少一秒,就判断一下之前的时间beg减当前的时间now是否小于等于0,这里的beg=之前的当前时间+200000,所以判断条件也就是从之前到现在是否过了200000秒,过了200000就输出flag。
flag是由
localTextView2.setText("alictf{" + MainActivity.this.stringFromJNI2(MainActivity.this.k) + "}");
这行代码输出的,其中stringFromJNI2函数是Native层的函数,所以我们主要就是先算出它的实参k,这是下面和k有关的代码:
if (MainActivity.is2(MainActivity.this.beg - MainActivity.this.now)) {
localMainActivity = MainActivity.this;
}
for (localMainActivity.k += 100;; localMainActivity.k -= 1)
{
localTextView1.setText("Time Remaining(s):" + (MainActivity.this.beg - MainActivity.this.now));//剩下的时间是:
paramBundle.postDelayed(this, MainActivity.this.t);
return;
localMainActivity = MainActivity.this;
}
但它的实参k我开始看了半天都不知该怎么算出来,然后把k转换成flag。后来我换了两个打开class文件的软件:

发现打开的.class文件反编译出来的代码竟然不一样:

主要就是对k的值的计算的代码不一样了:
if(MainActivity.is2(beg - now))
{
MainActivity mainactivity = MainActivity.this;
mainactivity.k = mainactivity.k + 100;
} else
{
MainActivity mainactivity1 = MainActivity.this;
mainactivity1.k = mainactivity1.k - 1;
}
所以现在程序的逻辑就是时间从200000到0,每一秒钟判断当前的时间过去了多久,然后判断MainActivity.is2(beg - now),之后对k进行一次计算。最后时间到0的时候,k也计算出了一个值,用k作实参输入stringFromJNI2(k)函数计算出flag。
我们要得到flag就要先算出时间到0时k的值,直接按照反编译出来的代码计算k的值:
Timer.java
/**
* 文件注释:
*
* @auther: Legends
* @Date: 2018/9/21 09:19
* @Description:
*/
public class Timer {
public static void main(String[] args) {
int k = 0 ;
for(int i = 200000 ; i > 0 ; i-- ){
if (is2(i)) {
k += 100;
} else {
--k;
}
}
System.out.println(k);
}
public static boolean is2(int var0) {
if (var0 <= 3) {
if (var0 <= 1) {
return false;
} else {
return true;
}
} else if (var0 % 2 != 0 && var0 % 3 != 0) {
for(int var1 = 5; var1 * var1 <= var0; var1 += 6) {
if (var0 % var1 == 0 || var0 % (var1 + 2) == 0) {
return false;
}
}
return true;
} else {
return false;
}
}
}
运行结果:

得到的k的值为:1616384。接下来的flag就是把k=1616384作为实参输入stringFromJNI2(k)函数即可计算出flag。
这时候有两种思路:
1、一种是复现stringFromJNI2(k)函数,再按部就班的计算出flag。
2、另一种是修改apk中k的值,把k的值修改为1616384,让软件自己计算出flag显示出来。
方法1先用IDA打开反编译出来的lib文件夹下面armeabi文件夹的liblhm.so:armeabi是指Android 设备的CPU类型,文件夹下用来存放.so库,主要针对不同的设备兼容,也可以说是专门针对不同Android手机下CPU架构的兼容。不同版本的so文件反编译的结果可能不同。
• mips / mips64: 极少用于手机可以忽略
• x86 / x86_64: x86 架构的手机都会包含由 Intel 提供的称为 Houdini 的指令集动态转码工具,实现 对 arm .so 的兼容,再考虑 x86 1% 以下的市场占有率,x86 相关的两个 .so 也是可以忽略的
• armeabi: ARM v5 这是相当老旧的一个版本,缺少对浮点数计算的硬件支持,在需要大量计算时有性能瓶颈
• armeabi-v7a: ARM v7 目前主流版本
• arm64-v8a: 64位支持
再IDA找到Java_net_bluelotus_tomorrow_easyandroid_MainActivity_stringFromJNI2函数:

发现打开的stringFromJNI2函数十分复杂,调用的函数也很多,所以这种方法并不是一种好的解题思路,于是我们采用方法2。
首先是要先通过逻辑判断MainActivity.this.beg - MainActivity.this.now <= 0,才能输出flag,这里就可以把beg的值改小200000、把now的值改大200000、把<=0改成>=0或者把整个判断去掉都行。我们这里就直接把beg加上的200000改成0了:

0x30d40=200000,上面就是int beg = (int)(System.currentTimeMillis() / 1000L) + 200000;的smili代码;我们把0x30d40改为0就可以让判断MainActivity.this.beg - MainActivity.this.now <= 0一直成立了。

之后再找到
localTextView2.setText("alictf{" + MainActivity.this.stringFromJNI2(MainActivity.this.k) + "}");
这一行的smali代码:

在iget v3, v3, Lnet/bluelotus/tomorrow/easyandroid/MainActivity;->k:I(读取int型k到v3(第一个v3),v3(第二个v3)寄存器中是 Lnet/bluelotus/tomorrow/easyandroid/MainActivity实例的引用,I表示int型),再下一行加上const v3,0x18aa00把k的值一直赋值为1616384 = 0x18aa00,smali代码的int型变量默认都是16进制,就算改成1616384,它编译之后也会自己改成0x18aa00。之后每次就能计算显示正确的flag:

之后保存全部,编译生成apk,安装,运行:

flag果然显示出来了:alictf{Y0vAr3TimerMa3te7}。上面的Time Remaining(s):-3是因为我们把beg从当前时间+200000改成了当前时间,所以之后每过一秒就会-1。

通过对一款计时器APP的反编译及代码分析,揭示了其内部工作原理,并成功解析出隐藏的flag,详细介绍了如何计算关键参数k及获取最终答案。

859

被折叠的 条评论
为什么被折叠?



