ChallengeMobile
解题思路
获取到输入的字符串保存到s,调用Jformat方法对s进行验证,返回true则代表输入字符串正确反之错误。
Jformat方法分析:
首先看到使用了LoadData加载了”ming“给了a方法,a方法的返回值赋值给了arr_b。
接着判断SDK_INT是否小于29:
意思就是判断Android版本是否小于10,如果不是则
使用InMemoryDexClassLoader从内存中动态加载DEX,加载方法在"challengemobile"文件中,这个是SO文件。
根据inMemoryDexClassLoader0的结果动态加载com.example.challengemobile下的Checker类。猜测是加载后的DEX文件。
method0是class0通过getMethod获取到Checker类下的"isflag"方法
如果以上操作都完成了则进入到if判断这里
很明显,取s字符串前五个判断是否是:
flag{,去最后一个判断是否是:}
如果都满足则继续进入if里:
首先获取s除去前五个字符的字符串赋值给了s1
通过反射机制调用了method0类的isflag方法,把s1字符串除去最后一个字符作为参数传递给了isflag方法。其实就是把flga{}括号中的字符串作为参数给了isflag方法。
根据isflag方法的返回值走return代码
如果返回了null则直接返回false,否则返回boolean0的布尔值。
其实说白了就是:
输入字符串--->isflag方法执行--->返回null可能代表isfhlag方法执行异常--->放回false则代码输入字符串不满足isflag方法的判断,true则证明输入的字符串满足了判断。同时就是正确的flag了
a方法分析
native方法,在"challengemobile"so文件中
解包APK在lib目录下使用IDA分析so文件
分析不难发现使用”ming“文件的数据去异或genrand_int32()函数的返回值,赋值给v10,v10的赋值的v8,那么可以说明这个循环执行完毕之后v8的数据就是动态加载的DEX文件了。这里有两个方法dump到v8的值:
1:动调SO文件,执行完循环查看V8的值;
2:使用Frida直接HOOK a方法的返回值;
我使用Frida去拿到DEX,因为比较快。
let MainActivity = Java.use("com.example.challengemobile.MainActivity");
MainActivity["a"].implementation = function (bArr) {
// console.log(`MainActivity.a is called: bArr=${bArr}`);
let result = this["a"](bArr);
// console.log(`MainActivity.a result = ${result}`);
// 将 result 转换为十六进制字符串
let hexResult = Array.from(result).map(num => {
// 对每个数值进行十六进制转换,并确保其为两个字符
// (例如,0xA 会变成 0x0A)
return ('0' + (num & 0xFF).toString(16)).slice(-2);
}).join(' '); // 以空格分隔每个十六进制数
console.log(`MainActivity.a result (hex): ${hexResult}`);
return result;
};
HOOK脚本可以直接在jadx右键复制,我这里改了输出结果为十六进制。
保存十六进制数据到txt中,使用010打开导入十六进制数据另存为dex文件
使用JADX打开dex文件分析:
可以看到就是Chekcer类
isflag方法,str就是{}中的数据,调用encryptToBase64String方法传入str和getKey方法的返回值。
getKey方法
也是个native方法,调用了example so文件
通过getKey方法定位到这里发现是使用了Java_example_Checker_getKey方法把v4复制给了v5,接着调用AES_ECB_decrypt方法对v5进行解密,其实就是v4经过AES_ECB_decrypt解密后就是key了,这个so文件是通过另一个so文件调用的,可能IDA无法调试。
还是使用Ffrida来获取getKey的返回值
Java.perform(function () {
Java.enumerateClassLoaders({
onMatch: function (loader) {
try {
if (loader.findClass("com.example.challengemobile.Checker")) {
Java.classFactory.loader = loader;
// console.log(loader);
}
} catch (error) {
}
}, onComplete: function () {
}
});
let Checker = Java.use("com.example.challengemobile.Checker");
console.log("Key---> " + Checker["getKey"]())
}
使用enumerateClassLoaders遍历所有类,找到Checker类,在使用use来获取到Checker类下的所有对象,通过Checker来主动调用getKey方法获取到返回值。
``Key---> oM51I504n137gp2~
获取到key后继续分析
encryptToBase64String方法
调用encrypt方法,str与str2作为参数传递过去
encrypt方法
调用另一个encrypt方法并把str与str2的字节数组作为参数传递过去
encrypt方法(第二个)
如果bArr长度为0则直接返回bArr的值,否则调用
toByteArray(encrypt(toIntArray(bArr, true), toIntArray(fixKey(bArr2), $assertionsDisabled)), $assertionsDisabled);
这一串方法调用,按顺序分析
encrypt方法(第三个)
很经典的一个XXTEA加密。
iArr为输入字符串的整数数组。
iArr2为key的整数数组。
那么不难理解:
toIntArray方法就是把字节数组转为对应的整数数组。
toByteArray方法把整数数组转为字节数组。
所有加密执行完之后回到encryptToBase64String方法
也就是说把XXTEA加密后的结果进行了Base64加密。所以
Ckh/PFCSS/i4kMVw1lswyghOZbIg+W5SymREHNcRg721Tm9w
就是密文了
分析到这里就可以去解密了,没有魔改的XXTEA加密,有了Key和密文,直接解密后在进行Base64解密就是flag了。
使用java实现
package CTF.ISCC.ChallengeMobile;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
public class Exp {
private static int MX(int i, int i2, int i3, int i4, int i5, int[] iArr) {
return (((i3 >>> 5) ^ (i2 << 2)) + ((i2 >>> 3) ^ (i3 << 4))) ^ ((i ^ i2) + (iArr[(i4 & 3) ^ i5] ^ i3));
}
private static int[] toIntArray(byte[] bArr, boolean z) {
int[] iArr;
int length = (bArr.length & 3) == 0 ? bArr.length >>> 2 : (bArr.length >>> 2) + 1;
if (z) {
iArr = new int[length + 1];
iArr[length] = bArr.length;
} else {
iArr = new int[length];
}
int length2 = bArr.length;
for (int i = 0; i < length2; i++) {
int i2 = i >>> 2;
iArr[i2] = iArr[i2] | ((bArr[i] & 255) << ((i & 3) << 3));
}
return iArr;
}
private static byte[] fixKey(byte[] bArr) {
if (bArr.length != 16) {
byte[] bArr2 = new byte[16];
if (bArr.length < 16) {
System.arraycopy(bArr, 0, bArr2, 0, bArr.length);
} else {
System.arraycopy(bArr, 0, bArr2, 0, 16);
}
return bArr2;
}
return bArr;
}
private static byte[] toByteArray(int[] iArr, boolean z) {
int i;
int length = iArr.length << 2;
if (z) {
i = iArr[iArr.length - 1];
int i2 = length - 4;
if (i < i2 - 3 || i > i2) {
return null;
}
} else {
i = length;
}
byte[] bArr = new byte[i];
for (int i3 = 0; i3 < i; i3++) {
bArr[i3] = (byte) (iArr[i3 >>> 2] >>> ((i3 & 3) << 3));
}
return bArr;
}
private static int[] encrypt(int[] iArr, int[] iArr2) {
int length = iArr.length - 1;
if (length >= 1) {
int i = (52 / (length + 1)) + 6;
int i2 = iArr[length];
int i3 = 0;
while (true) {
int i4 = i - 1;
if (i <= 0) {
break;
}
i3 -= 1640531527;
int i5 = (i3 >>> 2) & 3;
int i6 = 0;
while (i6 < length) {
i2 = iArr[i6] + MX(i3, iArr[i6 + 1], i2, i6, i5, iArr2);
iArr[i6] = i2;
i6++;
}
i2 = iArr[length] + MX(i3, iArr[0], i2, i6, i5, iArr2);
iArr[length] = i2;
i = i4;
}
}
return iArr;
}
/**
*
* @param iArr
* @param iArr2
* @return
*/
private static int[] decrypt(int[] iArr, int[] iArr2) {
int length = iArr.length - 1;
if (length >= 1) {
int i = (52 / (length + 1)) + 6;
int i3 = 0;
for (int j = 0; j < i; ++j)
i3 -= 0x61c88647;
for (int j = 0; j < i; ++j) {
int i6 = length;
int i2 = iArr[length - 1];
int i5 = (i3 >>> 2) & 3;
iArr[length] = iArr[length] - MX(i3, iArr[0], i2, i6, i5, iArr2);
while (i6 > 0) {
--i6;
i2 = iArr[i6 - 1 < 0 ? length : i6 - 1];
iArr[i6] = iArr[i6] - MX(i3, iArr[i6 + 1], i2, i6, i5, iArr2);
}
i3 += 0x61c88647;
}
}
return iArr;
}
public static void main(String[] args) {
int[] decBase64 = toIntArray(Base64.getDecoder().decode("Ckh/PFCSS/i4kMVw1lswyghOZbIg+W5SymREHNcRg721Tm9w"),false);
// System.out.println(Arrays.toString(decBase64));
int[] decKey = toIntArray(fixKey("oM51I504n137gp2~".getBytes()),true);
// System.out.println(Arrays.toString(decKey));
byte[] flag = toByteArray(decrypt(decBase64, decKey), true);
// System.out.println(Arrays.toString(flag));
assert flag != null;
String str = new String(flag,StandardCharsets.UTF_8);
System.out.println("flag{" + str + "}");
}
}
flag:
flag{ZVDK$8m|/;&6L6#zYJa3?Ming%a[Qt->}
随机推荐
- SSH Exporter:基于Prometheus的远程系统性能监控神器
SSH Exporter English | 中文 介绍 SSH Exporter 是一个基于 Prometheus 规范的监控工具,通过 SSH 协议远程收集目标服务器的系统性能数据,如 CPU 使 ...
- 【Spring-Cloud】Nacos2.0.3 单系统集群部署问题汇总
1.强制要求JDK8版本 且 64位 C:\Users\Administrator\Desktop\Nacos-Server\Nacos-2.0.3-8848\bin>java -version ...
- 新手入门深度学习:在不使用Google的情况下如何在国内获得免费的算力 —— 算力共享,驱动人工智能创新的新引擎
分享链接地址: 算力获新生 | 算力共享,驱动人工智能创新的新引擎
- 如何使用Python环境下的2D经典游戏仿真器(openai推出的)retro库运行游戏"刺猬索尼克" (SonicTheHedgehog-Genesis)
很多资料上都有使用游戏仿真器(openai推出的)retro库运行游戏"刺猬索尼克" (SonicTheHedgehog-Genesis),但是均没有给出详细的安装该款游戏的步骤 ...
- 再探 游戏 《 2048 》 —— AI方法—— 缘起、缘灭(6) —— Python版本实现的《2048》游戏环境运行性能对比
<2048>游戏在线试玩地址: https://play2048.co/ 如何解决<2048>游戏源于外网的一个讨论帖子,而这个帖子则是讨论如何解决该游戏的最早开始,可谓是&q ...
- 如何对MIL-STD-1553B进行选型
MIL-STD-1553B产品选型是一个复杂而细致的过程,需要综合考虑多个因素以确保所选产品能够满足特定应用场景的需求. 一.引言 MIL-STD-1553B作为一种广泛应用于航空航天领域的数据总 ...
- DolphinScheduler日志乱码、worker日志太多磁盘报警、版本更新导致不兼容怎么办?
作者 | 刘宇星 本文作者总结了在使用Apache DolphinScheduler过程中遇见过的常见问题及其解决方案,包括日志出现乱码.worker日志太多磁盘报警.版本更新导致不兼容问题等,快来看 ...
- kafka查看未被消费的消息
$ kubectl exec -it gitee-kafka-0 -n gitee bash unset JMX_PORT $ kafka-consumer-groups.sh --bootstrap ...
- 【Mac】之安装VM虚拟机并安装centos7系统
参考文章:<Mac 安装VMware Fusion虚拟机> 一.安装VMware Fusion 首先下载Mac版VMware虚拟机: 链接:https://pan.baidu.com/s/ ...
- 5个必知的高级SQL函数
5个必知的高级SQL函数 SQL是关系数据库管理的标准语言,用于与数据库通信.它广泛用于存储.检索和操作数据库中存储的数据.SQL不区分大小写.用户可以访问存储在关系数据库管理系统中的数据.SQL允许 ...