说实话,这几天被逆向的恐怖思维,深深的吓着了,真的要抱腿腿了,叫我失眠了好几晚,我觉得逆向分析是CTF中最难的,求腿腿指点迷津啊,我决定不闷头自己研究了。
第一题:app1
解题报告:在MUMU模拟器中安装运行
通过dex2jar和jd-gui,逆向出class文件,然后直接进Main函数
在BuildConfig.class文件中可以轻松的拿到解密的关键字段
通过简单的Python编程,成功拿到flag
第二题:app2
解题报告:在MUMU模拟器中安装运行
在JEB中打开,JEB真的没有排面啊
进入SecondActivity类中,寻找关键方法onCreate()
使用IDA打开Lib库中的.so文件,找到关键函数doRawData的实现
大概的意思就是使用AES对咱们的字符串进行加密,使用的密钥是thisisatestkey==,恰好使得结果等于VEIzd/V2UPYNdn/bxH3Xig==就可以了。
输入aimage/tencent结果发现还是腾讯等你哟 Waiting for you,被戏耍了,并不是正确的flag
于是通过dex2jar和jd-gui,逆向出class文件,发现第一个FileDataActivity类,里面也有一个奇怪的加密字符串
将上面的加密结果改成9YuQ2dk8CSaCe7DTAmaqAA==
,再次运行,成功拿到flag
第三题:app3
解题报告:
下载附件,发现是.ab文件,需要转换为.tar文件
解压.tar这个宝藏文件,得到了一个apk安装包
把apk拖进jeb,进入主程序,发现可疑处
第一步:分析这个类A
第二步:分析这个类B
解密得到关键密码:ae56f99
查看一下加密的版本3.4.0
去找一下3.4.0版本,然后编译安装一下sqlcipher,make不成功
https://github.com/sqlcipher/sqlcipher/releases
于是用windows版本的3.0.1编译好的可执行程序,只要大版本一样就行了
https://github.com/CovenantEyes/sqlcipher-windows/releases
sqlite> PRAGMA key = "ae56f99";
sqlite> ATTACH DATABASE "ailx10.db" AS plaintext KEY "";
sqlite> SELECT sqlcipher_export("plaintext");
sqlite> DETACH DATABASE plaintext;
最后,解密这个Base64编码就搞定了
第四题:easy-apk
解题报告:
安装运行,看不出来啥
扔进JEB里面,看上去逻辑很简单的亚子。
核心逻辑就是怎么逆向计算Base64New
发现这个JEB反编译出来或的操作很难读啊,只好转到dex2jar来试一试
反编译出来的结果让我心碎,一毛一样的亚,菜就是原罪,只好硬着皮头逆向运算了=。=
这个或运算太骚了吧,这玩意咋逆运算啊?失眠了。这题最难的地方就是异或怎么处理,最后来一个逐项暴力破解版,大约5秒,效果极佳。这是Base64的通用做法,记好哟~
最后一部分的=
号暴力破解:大约1秒,凑一起就完事了。
最后我花了一个草图,实际上这题就是Base64换了一个表,通过不停的或运算,实际上还是那一堆数字,0101并没有发生任何改变,因此直接写一个Base64的逆过程就完事了。
逆向核心代码:
package c4;
public class Base64New {
private static char[] Base64ByteToStr = null;
private static final int RANGE = 255;
private static byte[] StrToBase64Byte;
static {
Base64New.Base64ByteToStr = new char[]{'v', 'w', 'x', 'r', 's', 't', 'u', 'o', 'p', 'q', '3', '4', '5', '6', '7', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'y', 'z', '0', '1', '2', 'P', 'Q', 'R', 'S', 'T', 'K', 'L', 'M', 'N', 'O', 'Z', 'a', 'b', 'c', 'd', 'U', 'V', 'W', 'X', 'Y', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', '8', '9', '+', '/'};
Base64New.StrToBase64Byte = new byte[128];
}
public Base64New() {
super();
}
public byte getIndex(char x)
{
byte index = -1;
String talbe = new String(Base64New.Base64ByteToStr);
if(x != '=') {
index = (byte) talbe.indexOf(x);
}
else {
index = 0;
}
return index;
}
public void Base64Decode() {
String enflag = "5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs=";
String flag = "";
String flag_temp = "";
for(int i = 0;i < enflag.length();i+=4)
{
String enf = enflag.substring(i,i+4);
byte flag1 = (byte)(((getIndex(enf.charAt(0)) & 255) << 2 | ((getIndex(enf.charAt(1)) & 255) >>> 4)));
byte flag2 = (byte)(((getIndex(enf.charAt(1)) & 255) << 4 | ((getIndex(enf.charAt(2)) & 255) >>> 2)));
byte flag3 = (byte)(((getIndex(enf.charAt(2)) & 255) << 6 | ((getIndex(enf.charAt(3)) & 255))));
flag_temp = "" + (char)flag1 + (char)flag2 + (char)flag3;
flag += flag_temp;
}
System.out.println(flag);
return;
}
public String Base64Encode(byte[] arg9) {
int v7 = 3;
StringBuilder v3 = new StringBuilder();
int v1;
for(v1 = 0; v1 <= arg9.length - 1; v1 += 3) {
byte[] v0 = new byte[4];
byte v4 = 0;
int v2;
for(v2 = 0; v2 <= 2; ++v2) {
if(v1 + v2 <= arg9.length - 1) {
v0[v2] = (byte)((arg9[v1 + v2] & 255) >>> (v2 * 2 + 2) | v4);
v4 = ((byte)(((arg9[v1 + v2] & 255) << (2 - v2) * 2 + 2 & 255) >>> 2));
}
else {
v0[v2] = v4;
v4 = 64;
}
}
v0[v7] = v4;
for(v2 = 0; v2 <= v7; ++v2) {
if(v0[v2] <= 63) {
v3.append(Base64New.Base64ByteToStr[v0[v2]]);
}
else {
v3.append('=');
}
}
}
return v3.toString();
}
}
第五题:easy-java
解题报告:
拖进JEB中找到核心逻辑,这个a方法是关键
进入a方法里面,发现调用的是b方法,可以看到提交的字符格式了吧。然后就是重头戏了,有两个类a和b,经过一些列的计算,最后结果等于wigwrkaugala
就可以了。
flag{ }
整体思路:这是一个错误思路,读者可以直接忽略,为了记录我的愚蠢的。
- 首先用第一个参数(flag),调用B类的a方法,将结果传递给A类的a方法,写入v3中
- 然后用B类的b方法对25取余数,得到v6
- 如果v6大于等于1,并且大于v1,那么v1++,否则v0++
- 往复循环,处理完整个flag
B类的a方法:B类的b中包含flag的话,找到下标数字,然后在B类的a中到找到相同的数字,输出移动了几次。比如字母a,在B类的b中的下标是0,在B类的a中0的位置是10,那么输出10
A类的a方法:A类的a中找到这个参数的下标,然后输出A类的b中的该下标的字母。比如10在A类的a的下标是14,那么输出A类的b[14],也就是输出字母n
然后我以为我把所有的字母从明文到密文的转换,搞成一个转换字典,然后根据密文去反查明文,岂不是美滋滋,事实证明这个思路走不通。因为明文aa映射到密码并不是xx,而是xy,导致了这个思路破碎,只能硬着皮头反逻辑,这个没有经过老师点拨训练,真的很痛苦。
偷个懒,来一次暴力破解,
flag如果是4位,需要尝试45万次
flag如果是8位,需要尝试2000亿次
额,实际flag是12位,暴力美学,一点都不美丽了,弃坑,CTF没有暴力破解。
第二次解题思路:读者继续从这里开始。
先搞懂正向计算过程,再去分析逆向计算过程。
- 正向计算0层架构:调用B的a方法,再调用A的a方法
- B的a方法:参数在B的b中索引,在B的a中索引,再调用B的c方法
- B的c方法:B的a数组,B的b数组循环移动一位,123变成231
- A的a方法:参数在A的a中索引,再调用A的b中索引值
逆向过程如下:
- A的a方法:参数在A的b中索引,在A的a中索引值
- B的a方法:参数在B的a中索引值,在B的b中索引值,再调用B的c方法
- B的c方法:和正向操作一样,保持操作的表是一样的
那么现在思路清晰了,就很简单了:
其中A的a方法的逆运算如下:
B的a方法逆运算如下:
第六题:easy-jni
解题报告:
仍进JEB中,发现问题的关键是:
- new a() 这个A类的a()方法,对输入进行处理
- native中的ncheack()方法,检测是否相等
关键问题1:对类A对a方法进行分析
关键问题2:解压apk包,把lib下的.so文件拖进IDA32中,对库函数分析
正向思路整理:
- 对输入的flag进行Base64换表计算B64Encode
- 对输入的B64Encode进行变换得到加密的flag
逆向思路整理:
- 对加密的flag进行逆向变换得到B64Encode
- 对B64Encode进行Decode计算得到flag
编程就非常简单了:
第一步:交换操作
第二步:Base64Decode操作
第七题:easy-so
解题报告:
扔进JEB中,发现只要经过cyberpeace.CheckString
,计算结果等于1就拿到flag
而最关键的cyberpeace.CheckString
在lib库中,将apk解压,把里面的.so库文件拖到IDA中,发现逻辑非常简单,和上面一题的交换部分一样。
直接利用上一题的代码改改,就拿到flag,有点送分的意思。
第八题:Ph0en1x-100
解题报告:
仍进JEB中,需要注意3个地方,一个一个分析
第一个注意的地方:getSecret
发现等号两边都要经过这个消息摘要函数的计算,那么只要比较输入值相等就可以了。
第二个注意的地方:getFlag
第三个注意的地方:encrypt
最后,很容易的写出逆向代码,直接拿到flag
第九题:RememberOther
解题报告:
扔进JEB中看看里面的逻辑:
进入checkSN,拿到flag有2种选择,要么什么也不要输入,要么输入的用户名经过消息摘要算法等于注册码,所以我们什么也不输入就能拿到flag
在App里面点击注册,一串md5一闪而过,在配置文件里面,咱们可以找到它。
找一个在线MD5解密网站,扔进去拿到flag:
结合令人无语的脑洞,末尾加一个“ANDROID”字符串,拼成最终的flag
YOU_KNOW_ANDROID
至此,基础题目都做完了,撒花花~