可以看到关键在于cyberpeace.CheckString()函数
双击跟进之后可以发现是native层函数
程序逻辑:
1. 将字符串保存到新的空间buffer中
2. 第一个判断是将buffer的前16个字符和后16个字符进行交换
3. 第二个判断是将buffer的2个相邻的字符互换位置
_BOOL8 __fastcall Java_com_testjava_jack_pingan2_cyberpeace_CheckString(__int64 a1, __int64 a2, __int64 a3)
{
const char *str; // r14
size_t len; // rax
int len2; // r15d
unsigned __int64 v6; // r12
char *newMem; // rax
char *buffer; // r13
bool v9; // cc
size_t v10; // r12
size_t i; // rbx
char tmp; // al
char tmp1; // al
size_t j; // rbx
char tmp2; // al
str = (*(*a1 + 1352LL))(a1, a3, 0LL);
len = strlen(str);
len2 = len;
v6 = ((len << 32) + 0x100000000LL) >> 32;
newMem = malloc(v6);
buffer = newMem;
v9 = v6 <= len2;
v10 = v6 - len2;
if ( v9 )
v10 = 0LL;
memset(&newMem[len2], 0, v10);
memcpy(buffer, str, len2);
if ( strlen(buffer) >= 2 )
{
i = 0LL;
do
{
tmp = buffer[i];
buffer[i] = buffer[i + 16];
buffer[i++ + 16] = tmp;
}
while ( strlen(buffer) >> 1 > i );
//32>>1 = 16 也就是说i<16时执行循环
}
tmp1 = *buffer;
if ( *buffer )
{
*buffer = buffer[1];
buffer[1] = tmp1;
if ( strlen(buffer) >= 3 )
{
j = 2LL;
do
{
tmp2 = buffer[j];
buffer[j] = buffer[j + 1];
buffer[j + 1] = tmp2;
j += 2LL;
}
while ( strlen(buffer) > j );
}
}
return strcmp(buffer, "f72c5a36569418a20907b55be5bf95ad") == 0;
}
注意,不同的so文件得到的反汇编结果不一样
一开始我使用arm64-v8a文件夹中的so文件得到的是这个结果,显然是错误的,这个循环最后会丢失一些数据
后来打开x86_64文件夹的so文件就正常了
#include
int main()
{
char flag[33] = "f72c5a36569418a20907b55be5bf95ad";
char tmp;
for (int i = 0; i < 32; i += 2) // 两两交换
{
tmp = flag[i];
flag[i] = flag[i + 1];
flag[i + 1] = tmp;
}
int i = 0;
do
{
tmp = flag[i];
flag[i] = flag[i + 16];
flag[i++ + 16] = tmp;
} while (i<16);
printf("flag{%s}",flag);
//flag{90705bb55efb59da7fc2a5636549812a}
return 0;
}
可以看到也是获取字符串然后加密
1. 获取字符串
2. 进行base64加密(base表有更换)
3. 调用ncheck这个native函数判断结果
base64encode函数,换表base64加密:
ncheck函数,这里应该和上一题easyso是一样的操作,只是由于反编译错误
1. 先将前16和后16个字符进行交换
2. 然后从头遍历,每两个字符一组互换位置
先输出ncheck处理前的base串
#include
int main()
{
char flag[33] = "MbT3sQgX039i3g==AQOoMQFPskB1Bsc7";
char tmp;
for (int i = 0; i < 32; i += 2) // 两两交换
{
tmp = flag[i];
flag[i] = flag[i + 1];
flag[i + 1] = tmp;
}
int i = 0;
do
{
tmp = flag[i];
flag[i] = flag[i + 16];
flag[i++ + 16] = tmp;
} while (i<16);
printf("%s",flag);
//QAoOQMPFks1BsB7cbM3TQsXg30i9g3==
return 0;
}
先输出base64表
public class test1{
private static final char[] table = {'i', '5', 'j', 'L', 'W', '7', 'S', '0', 'G', 'X', '6', 'u', 'f', '1', 'c', 'v', '3', 'n', 'y', '4', 'q', '8', 'e', 's', '2', 'Q', '+', 'b', 'd', 'k', 'Y', 'g', 'K', 'O', 'I', 'T', '/', 't', 'A', 'x', 'U', 'r', 'F', 'l', 'V', 'P', 'z', 'h', 'm', 'o', 'w', '9', 'B', 'H', 'C', 'M', 'D', 'p', 'E', 'a', 'J', 'R', 'Z', 'N'};
public static void main(String[] args) {
System.out.println("hello");
String s="hellk";
System.out.println(s);
for (int i=0;i<table.length;i++)
{
System.out.print(table[i]);
//i5jLW7S0GX6uf1cv3ny4q8es2Q+bdkYgKOIT/tAxUrFlVPzhmow9BHCMDpEaJRZN
}
}
}
然后用CyberChef嗦一下
这题基本和安卓没什么关系,主要是分析java代码逻辑
主函数这里调用了一个check方法判断输入的flag
再看看check方法
先判断头尾flag{}格式,然后获取花括号内的字符
创建两个类用于后续的一些处理
然后使用stringBuilder和func函数创建一个新的字符串
注意func函数每次传进去的是单字符的字符串,所以每次append添加的是单个字符
func函数
用了两个函数来套娃操作
先看看index函数(注意这里的函数名和变量名都是根据分析手动修改的)
这里的函数逻辑已经分析出来了,首先判断字符是否为字母,然后获取该字符在str中的下标
再用下标在array中查找对应位置,然后将array中的下标返回
最后使用change函数改变表(change是个循环左移一位的操作)
这个逻辑就比较简单了,也是表中查值然后记录index,根据index在table表中查找字符
list和上一个函数有类似的操作
judgeCount,由于字符根本没有25那么长,这个函数纯纯干扰让人多分析一下,没什么实际作用
import java.util.ArrayList;
public class glass0{
static String str = "abcdefghijklmnopqrstuvwxyz";
static ArrayList<Integer> array = new ArrayList<>();
static void change() {
int value = array.get(0).intValue(); // 首个元素的int值
array.remove(0); // 移除
array.add(Integer.valueOf(value)); // 又加回去?
str += "" + str.charAt(0);
str = str.substring(1, 27); // 切割字符串并调整,左移一位的效果
}
public static void main(String[] args) {
char[] flag="wigwrkaugala".toCharArray();
String table = "abcdefghijklmnopqrstuvwxyz";
int[] Table={7, 14, 16, 21, 4, 24, 25, 20, 5, 15, 9, 17, 6, 13, 3, 18, 12, 10, 19, 0, 22, 2, 11, 23, 1, 8};
int[] intergerArr = {8, 25, 17, 23, 7, 22, 1, 16, 6, 9, 21, 0, 15, 5, 10, 18, 2, 24, 4, 11, 3, 14, 19, 12, 20, 13};
ArrayList<Integer> list = new ArrayList<>();
//初始化list
for(int i=3;i<Table.length;i++){
list.add(Table[i]);
}
for(int i=0;i<3;i++){
list.add(Table[i]);
}
//初始化array
for (int i = 2; i < intergerArr.length; i++) {
array.add(intergerArr[i]); // 截取num~length后半段字符串
}
for (int j = 0; j < 2; j++) {
array.add(intergerArr[j]); // 截取0~num-1前半段字符串
}
//开始解密
for(int i=0;i<flag.length;i++){
//获取下标值
int index=table.indexOf(flag[i]);
//获取num值
int num=list.get(index);
//这个num是getIndex函数的返回值
int value=array.get(num);
char chr=str.charAt(value);
flag[i]=chr;
change();
}
System.out.println(flag);//venividivkcr
}
}
flag{venividivkcr}
对用户名和输入的SN码进行检测,用户名这里已经给出,所以查看checkSN即可
checkSN
可以看到sn码有22位,然后是对userName进行md5处理,md5之后的十六进制字符串每隔2位取一个,然后用flag{}包裹住就是需要的sn码
import hashlib
text = "Tenshine"
hash_obj = hashlib.md5()
hash_obj.update(text.encode())
encrypted_text = hash_obj.hexdigest()
for i in range(0,len(encrypted_text),2):
print(encrypted_text[i],end='')
#bc72f242a6af3857
flag:bc72f242a6af3857
使用jeb动态调试
下断点后运行程序输入22个字符即可成功断下并看到字符串的值
jadx也可以调试,但是感觉没有jeb那么丝滑
而且这红色的是变量值,第一眼看过去还以为是报错…
主要是调用了JNI.getResult这个native函数
Init的功能是将str分为三段,分别存储在三个数组中,然后就是一些异或和比较操作了
解题脚本(注意循环是0-3,不包括第五个字符)
#include
int main()
{
unsigned char one[5] = "LN^dl";
unsigned char two[5] = " 5-0a";
two[3] = 0x16;
unsigned char three[5] = "AFBo}";
unsigned char flag[16] = { 0 };
for (int i = 0; i < 4; i++)
{
three[i] ^= two[i];
two[i] ^= one[i];
one[i] = (one[i] ^ 0x80) / 2;
flag[i * 3] = one[i];
flag[i * 3 + 1] = two[i];
flag[i * 3 + 2] = three[i];
}
flag[12] = one[4];
flag[13] = two[4];
flag[14] = three[4];
printf("%s",flag);
return 0;
}
flag{sosorryla}
jar文件,用jadx打开分析,
主函数先加载一个checker对象,然后调用该对象中的checkPassword方法检测输入的密码是否正确
下方给出了loadCheckerObject方法的实现,可以看到是对ClassEnc文件进行AES解密,解密之后将该类作为loader方法的值返回
注意Java Cipher库默认的AES解密为ECB模式,默认填充方式为PKCS5Padding,被GPT说是CBC模式坑了
解密脚本
import os
from Crypto.Cipher import AES
key=bytes().fromhex("bb27630cf264f8567d185008c10c3f96")
with open("ClassEnc","rb") as input:
data=input.read()
cipher=AES.new(key,AES.MODE_ECB)
decode= cipher.decrypt(data)
with open("ClassDec","wb") as out:
out.write(decode)
input.close()
out.close()
用jadx打开解密后的ClassDec文件
可以发现CheckPass类实现了CheckInterface接口的checkPassword方法
一个简单的md5,在线解密得到monkey99
所以flag{monkey99}
很多文章都是直接给出jar包中自带的这个CheckPass方法,但是根据程序逻辑来看并没有调用这里,可能是出题人忘了删这一段代码
参考文章:
.ab文件,chatgpt是这么解释:
通常情况下,.ab后缀的文件是Android应用程序备份文件,也称为“应用程序包文件”(.apk文件)的备份文件。
当您使用Android设备上的备份和重置功能时,系统将应用程序的数据和设置打包成一个.ab文件,以便稍后可以还原到设备上。
这些备份文件可以保存在本地存储设备或云存储中,以防意外数据丢失或设备更换。
.ab有加密和未加密两种,这里未加密就显示none
下载解包工具:android-backup-extractor
注意:该工具使用需要java11及以上环境
使用命令
java -jar abe.jar unpack 399649a0e89b46309dd5ae78ff96917a.ab app3.tar
然后就会输出app3.tar压缩包,解压即可在文件夹里面找到apk文件
jadx打开分析逻辑