文章目录


一、easy-so

题目链接:​​https://adworld.xctf.org.cn/task/task_list?type=mobile&number=6&grade=0​

二、答题步骤

1.运行app

点击check提示

【愚公系列】2021年12月 攻防世界-简单题-MOBILE-001(easy-so)_安全

2.jadx反编译apk文件

搜索​​验证失败​​字符串

【愚公系列】2021年12月 攻防世界-简单题-MOBILE-001(easy-so)_安全架构_02

找到源码

【愚公系列】2021年12月 攻防世界-简单题-MOBILE-001(easy-so)_安全_03

发现核查字符串函数:​​CheckString​

3.IDA修改apk逻辑实现破解

上IDA,搜索刚才的方法名check,只有一个结果,双击跳过去,F5转伪代码,大概看一下,就是TestDec输出的字符串和​​f72c5a36569418a20907b55be5bf95ad​​进行对比,如果相等则返回1,也就是验证通过。

_BOOL8 __fastcall Java_com_testjava_jack_pingan2_cyberpeace_CheckString(__int64 a1, __int64 a2, __int64 a3)
{
const char *v3; // r14
size_t v4; // rax
int v5; // er15
unsigned __int64 v6; // r12
char *v7; // rax
char *v8; // r13
bool v9; // cc
size_t v10; // r12
size_t v11; // rbx
char v12; // al
char v13; // al
size_t v14; // rbx
char v15; // al

v3 = (const char *)(*(__int64 (__fastcall **)(__int64, __int64, _QWORD))(*(_QWORD *)a1 + 1352LL))(a1, a3, 0LL);
v4 = strlen(v3);
v5 = v4;
v6 = (__int64)((v4 << 32) + 0x100000000LL) >> 32;
v7 = (char *)malloc(v6);
v8 = v7;
v9 = v6 <= v5;
v10 = v6 - v5;
if ( v9 )
v10 = 0LL;
memset(&v7[v5], 0, v10);
memcpy(v8, v3, v5);
if ( strlen(v8) >= 2 )
{
v11 = 0LL;
do
{
v12 = v8[v11];
v8[v11] = v8[v11 + 16];
v8[v11++ + 16] = v12;
}
while ( strlen(v8) >> 1 > v11 );
}
v13 = *v8;
if ( *v8 )
{
*v8 = v8[1];
v8[1] = v13;
if ( strlen(v8) >= 3 )
{
v14 = 2LL;
do
{
v15 = v8[v14];
v8[v14] = v8[v14 + 1];
v8[v14 + 1] = v15;
v14 += 2LL;
}
while ( strlen(v8) > v14 );
}
}
return strcmp(v8, "f72c5a36569418a20907b55be5bf95ad") == 0;
}

【愚公系列】2021年12月 攻防世界-简单题-MOBILE-001(easy-so)_系统安全_04

const char * v3是传入的字符串,接下来逐个分析代码逻辑:

第一步:两两交换

v4 = strlen(v3);//取变量v4=v3的字符串长度,假设v3="abcd",v4=4
v5 = v4;
v6 = (__int64)((v4 << 32) + 0x100000000LL) >> 32;
v7 = (char *)malloc(v6);//为字符指针v7请求一块长度为v6的内存空间
v8 = v7;
v9 = v6 <= v5;
v10 = v6 - v5;
if ( v9 )
v10 = 0LL;
memset(&v7[v5], 0, v10);//将v7扩增一倍并后面扩增的部分初始化为0,此行代码结束,v7=----0000
memcpy(v8, v3, v5);//将v3的内容复制到v8中
if ( strlen(v8) >= 2 )//若v8的长度大于等于2则执行花括号内的内容
{
v11 = 0LL;//初始化v11=0
do//执行循环
{
v12 = v8[v11];//从第0个开始读取v8的每个字符
v8[v11] = v8[v11 + 16];//逐个将v8的第v11个字符与第v11+16个字符交换位置
v8[v11++ + 16] = v12;//v6自增1
}
while ( strlen(v8) >> 1 > v11 );
}

假设传入字符串为abcd,则上述代码执行完之后的v8为cdab

继续分析接下来的代码:

第二步:头尾互换

v13 = *v8;
if ( *v8 )
{
*v8 = v8[1];
v8[1] = v13;
if ( strlen(v8) >= 3 )
{
v14 = 2LL;
do
{
v15 = v8[v14];
v8[v14] = v8[v14 + 1];
v8[v14 + 1] = v15;
v14 += 2LL;
}
while ( strlen(v8) > v14 );
}
}

根据上述我们直接手动得到flag的code:

1.将f7 2c 5a 36 56 94 18 a2 09 07 b5 5b e5 bf 95 ad​​两两交换​​得到7f c2 a5 63 65 49 81 2a 90 70 5b b5 5e fb 59 da

2.将7fc2a5636549812a 90705bb55efb59da从中间砍断,头尾互换得到90705bb55efb59da 7fc2a5636549812a

得到flag:​​flag{90705bb55efb59da7fc2a5636549812a}​

4.脚本解题和Java源码

python脚本

data = list('f72c5a36569418a20907b55be5bf95ad') //把字符串传入,并转为列表
for i in range(len(data)//2): //第一个循环体
a = data[i]
data[i] = data[i+16]
data[i+16] = a
for i in range(0,len(data),2): //第二个循环体
a2 = data[i]
data[i]=data[i+1]
data[i+1]=a2
key=''.join(data) //拼接字符串
print("flag{"+key+"}") //把题目中的flag{}和最终字符串拼接并输出

java源码解析

@Test
public void demo2() {
String str = "f72c5a36569418a20907b55be5bf95ad";
char[] data = str.toCharArray();

//伪代码中的第二次循环
int index2 = 0;
int result;
int v9;
do {
char v8 = data[index2 + 2];
data[index2 + 2] = data[index2 + 3];
data[index2 + 3] = v8;
result = data.length;
v9 = index2 + 4;
index2 += 2;
}
while (v9 < result);


//循环后进行的简单的互换
result = data[0];
data[0] = data[1];
data[1] = (char) result;
result = data.length;


//伪代码中第一次循环
int index = 0;
do
{
char v4 = data[index];
data[index] = data[index + 16];
data[index+16] = v4;
++index;
}
while ( index < data.length >> 1 );

System.out.println(data);
//输出结果 -> 90705bb55efb59da7fc2a5636549812a
}

总结


  • APK
  • JADX
  • IDA