题目描述
打开附件是一个.apk文件,我们直接用手机下载该软件,然后随便输入,看它输出什么,输出了input error.Keep pumping! 如图
然后就用jadx打开.apk,左上角导航->文本搜索->搜Keep pumping,来到它的主逻辑处 ,如图
Java代码,我还不是很熟悉,还没看完Java,大多都问AI,看第55行的函数Jformat,被类onClick调用,当函数返回true时,才会提示”OH YES, ONE STEP AWAY FROM SUCCESS!”,也就是我们希望的结果,所以我们先只用看Jformat函数的返回值。
str.substring ()是 Java 中的一个方法,用于从字符串中提取子字符串。str.substring(int beginIndex, int endIndex)即是从指定的 beginIndex 开始,提取字符串直到 endIndex – 1。
所以代码意思是看你输入的字符串的第1个到第5个字符是否是 ISCC{ 。和最后一个字符是不是 },然后再看a.a接收ISCC{}包着的字符串,就是我们的flag。
我们点进去看,应该是是一个类
看class a里面的a函数
public static boolean a(String str) {
String substring = str.substring(0, str.length() - 15);
//提取字符串的第1到字符串长度-15个字符,但是我们不知道传入的字符串长度是多少。
String str2 = Myjni.getstr();
//这里点进去是System.loadLibrary("whathappened"),我们暂时不知道是啥,应该是加载一个lib
String str3 = substring + str2;
//拼接前面两个字符
String str4 = substring + str.substring(str.length() - 15, str.length());
//这里的str4就是就是我们的flag
return b(substring) && getSHA256(str4).equals("437414687cecdd3526281d4bc6492f3931574036943597fddd40adfbe07a9afa") && str4.equals(str3);
//这里第一b函数接收我们的substring,第二对我们的flag进行sha256加密处理,并与一串值对比,第三判断我们的flag是不是等于str3,那str3其实也是我们的flag,Myjni.getstr()函数是个关键函数。
}
那么我们就有思路,flag应该是被分成了两部分,第一部分应该由b函数可以得到,第而部分由函数Myjni.getstr()得到,先看b函数。
public static boolean b(String str) {
try {
if (c(str)) {
//这里又有个c函数,加在上面了
int parseInt = Integer.parseInt(str);
//将数字字符串转化为整数
if (get1(parseInt) && d(parseInt)) {
//这里又有两个函数,加在下面了,两个函数都是对数字处理,我们猜测数字的前8位是纯数字
int i = parseInt + 11;
if (!get1(i)) {
//get1函数的要求是第一位数字是4,才会返回true,上面数字加了11到了这里的if判断,第一位是4的话就不会往后执行了,所以我一开始猜测数字范围是49999989到49999999。
if (!d(i)) {
return true;
}
}
}
}
return false;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public static boolean c(String str) {
return str.length() == 8;
//要求flag.length() - 15 = 8,那我们求的flag长度就是23了,而传入b函数的长度只有8
}
public static boolean get1(int i) {
String num = Integer.toString(i);
for (int i2 = 0; i2 < num.length() - 1; i2++) {
i /= 10;
}
return i == 4;
//这个函数的要求就是数字的第一位要是4
}
public static boolean d(int i) {
int i2 = 2;
while (i2 < i && i % i2 != 0) {
i2++;
}
return i == i2;
}
因为要分析d和get1这两个函数直接得到结果有点不现实,我就直接爆破,一开始爆破的结果是49999991,但是最后发现不对,原因是因为我忽略了如果八位数字的第一位为0,第二位为4,也可以满足get1函数的要求,那就有很多种可能了,爆破的范围就没那么小了,最后爆破出很多结果,只有04999999是正确的。爆破脚本如下
public class Main {
public static boolean d(int i) {
int i2 = 2;
while (i2 < i && i % i2 != 0) {
i2++;
}
return i == i2;
}
public static boolean get1(int i) {
String num = Integer.toString(i);
for (int i2 = 0; i2 < num.length() - 1; i2++) {
i /= 10;
}
return i == 4;
}
public static boolean c(String str) {
return str.length() == 8;
}
public static boolean h(int parseInt) {
if (get1(parseInt) && d(parseInt)) {
int i = parseInt + 11;
if (!get1(i)) {
if (!d(i)) {
return true;
}
}
}
return false;
}
public static void main(String[] args) {
for(int j=0;j<50000000;j++){
//范围太大爆破很慢,自己操作可以灵活一点
if(h(j)){
System.out.println(j);
}
}
}
}
我们就得到了前八位纯数字,还差15位,再看函数Myjni.getstr(),点进去代码如下
package com.example.whathappened.MyJNI;
/* loaded from: classes.dex */
public class Myjni {
public static native String getstr();
static {
System.loadLibrary("whathappened");
}
}
AI问了一下System.loadLibrary是Java中用于加载本地库(如动态链接库DLL)的方法。这里应该是有个文件叫作whathappened,但是没有在附件里面,我就问了同级的大佬,告诉我将.apk后缀改成zip,在lib/下有四个文件夹,里面是不同CPU架构下的libwhathappened.so文件,这里代码加载的应该就是这个文件。然后提取x86_64文件夹里面的,用IDA进行分析,找到了我们要的函数
这里面不知道为啥还传入了一个a1参数,但是在jadx里面并没有传入参数到getstr()函数里面,还返回了应该像是一个函数,对a1和getend()函数返回的v1进行了处理,头要爆炸了。后来问了大佬,跟我解释了一堆巴拉巴拉,跟Java jni有关,但是我没听懂,暑假要开始学安卓逆向了。直接看getend(),点进去看是一坨代码,也不是什么加密,分析了一下,把它运行一下,应该就能得到它的result了,不用进行什么逆向。代码如下
#include<Windows.h>
#include<stdio.h>
#include<math.h>
_BYTE* getend(void)
{
_BYTE* result; // rax
__int64 v1; // rcx
int v2; // edi
int v3; // r8d
char v4; // r10
int v5; // r9d
int v6; // r11d
int v7; // r11d
int v8; // r11d
char aAbcdefghijklmn[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
result = malloc(0x10uLL);
result[15] = 0;
v1 = 0LL;
v2 = 0;
do
{
v3 = v2
+ 5 * v1
- 62
* (((v2 + 5 * v1 + ((-2078209981LL * (v2 + 5 * v1 + 7)) >> 32) + 7) >> 31)
+ ((v2 + 5 * v1 + ((-2078209981LL * (v2 + 5 * v1 + 7)) >> 32) + 7) >> 5))
+ 7;
v4 = aAbcdefghijklmn[v3];
v5 = v2 + 10;
v6 = 100;
do
{
v3 = (v5 + v3) % 62;
v4 = aAbcdefghijklmn[(v3 + v2 + v4) % 62];
--v6;
} while (v6);
v7 = 100;
do
{
v3 = (v5 + v3) % 62;
v4 = aAbcdefghijklmn[(v3 + v2 + v4) % 62];
--v7;
} while (v7);
v8 = 100;
do
{
v3 = (v5 + v3) % 62;
v4 = aAbcdefghijklmn[(v3 + v2 + v4) % 62];
--v8;
} while (v8);
result[v1] = v4;
if (v2-- == 0)
v2 = 15;
++v1;
} while (v1 != 15);
return result;
}
int main()
{
_BYTE* ji = getend();
printf("%s", ji);
}
//输出gwC9nOCNUhsHqZm,长度刚好为15位
注意在Visual Studio中运行以上代码,要在开头放入IDA的defs.h里面的宏定义。
所以得到的flag就是04999999gwC9nOCNUhsHqZm
Sha256加密后,和要对比的Sha256值相同,所以到这就对了,再输入手机里面,提示我们”OH YES, ONE STEP AWAY FROM SUCCESS!”,到这还有一步在哪我也卡住了,又去问了大佬,说安卓有Broadcast和Receiver机制,没学过,没办法。来到最后一步处
这里在与那串Sha256加密后的值对比之后,对其进行了加密,我们当然直接猜测这里的PART1就是纯数字,PART2是后面IDA分析后得出来的结果。经过分析,直接复制粘贴代码执行一次就可以得到flag了,要删除一些无关紧要的代码,脚本如下
package firstlearn;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Random;
public class testtwo {
private static String combineStrings(String str, String str2) {
return str + str2;
}
private static byte[] generateSalt(int i) {
byte[] bArr = new byte[i];
new Random(7906L).nextBytes(bArr);
return bArr;
}
private static byte[] customEncrypt(byte[] bArr, byte[] bArr2) {
byte[] bArr3 = new byte[bArr.length];
for (int i = 0; i < bArr.length; i++) {
bArr3[i] = (byte) (bArr[i] ^ bArr2[i % bArr2.length]);
}
return bArr3;
}
public static String encrypt(String str, String str2) {
byte[] generateSalt = generateSalt(16);
byte[] customEncrypt = customEncrypt(combineStrings(str, str2).getBytes(StandardCharsets.UTF_8), generateSalt);
byte[] bArr = new byte[generateSalt.length + customEncrypt.length];
System.arraycopy(generateSalt, 0, bArr, 0, generateSalt.length);
System.arraycopy(customEncrypt, 0, bArr, generateSalt.length, customEncrypt.length);
return Base64.getEncoder().encodeToString(bArr);
}
public final class ByteCompanionObject {
public static final byte MAX_VALUE = Byte.MAX_VALUE;
public static final byte MIN_VALUE = Byte.MIN_VALUE;
public static final int SIZE_BITS = 8;
public static final int SIZE_BYTES = 1;
private ByteCompanionObject() {
}
}
public static String encrypt2(String str) {
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) ((bytes[i] + ByteCompanionObject.MAX_VALUE) % 256);
}
byte[] bArr = new byte[bytes.length];
for (int i2 = 0; i2 < bytes.length; i2++) {
bArr[i2] = (byte) (i2 % 2 == 0 ? bytes[i2] ^ 123 : bytes[i2] ^ 234);
}
return Base64.getEncoder().encodeToString(bArr);
}
public static void main(String[] args) {
String a1="04999999";
String a2="gwC9nOCNUhsHqZm";
String s=encrypt2(encrypt(a1,a2)).substring(0, 32);
System.out.println(s);
}
}
//输出uR+TPrILryzIJY9SiyiZWK8GtAzNKYom
所以flag就是ISCC{uR+TPrILryzIJY9SiyiZWK8GtAzNKYom}
做完这题,更想学安卓逆向了。