0%

2021CISCN初赛部分RE

glass

题目解压之后发现是apk文件,更改后缀为.zip,再次解压
在这里插入图片描述
在这里插入图片描述
看到有.dex文件。dex文件是Android系统的可执行文件,包含应用程序的全部操作指令以及运行时数据。我们使用dex2jar反编译该文件。
把class.dex复制到dex2jar.bat所在目录,即dex2jar目录下。打开cmd,进入dex2jar所在的目录然后运行dex2jar.bat classes.dex,生成classes-dex2jar.jar就是所要转化jar包。
在这里插入图片描述
在这里插入图片描述
使用jd-gui打开classes-dex2jar.jar

在这里插入图片描述
在这里插入图片描述

我们可以看到函数里面存在native定义,并且,在整个java代码里面没有checkFlag的定义,只有一个声明,这些都说明函数是定义在so文件之中的。
我们来寻找需要的so文件。
题目的lib文件里面只有一个so文件,正好也是寻找的native的so文件。在这里插入图片描述
使用ida对其进行反编译。
通过搜索checkFlag函数找到该函数
在这里插入图片描述
我们可以知道flag的长度应该是39位的
接下来看函数sub_FFCsub_1088
sub_FFC:
在这里插入图片描述
sub_1088:
在这里插入图片描述
可以很明显的可以看出来是RC4加密,不清楚的同学可以对照百度百科上面的代码比较,会发现非常相像。同时,RC4的加密脚本也就是它的解密脚本。
那么checkFlag中的12345678就是key。
我们再查看函数sub_10D4
可以看到对RC4处理完成的字符串再次进行处理,先进行异或,后面还有一些处理,看似复杂但是不用太在意,写脚本的时候直接倒回去就可以。
返回主checkFlag,我们发现最后得到的字符要和unk_497C进行比较。
unk_497C:
Shift+E提取一下
在这里插入图片描述
写脚本破解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include<stdio.h>

void rc4_init(unsigned char*s,unsigned char*key, unsigned long Len)
{
int i=0,j=0;
unsigned char k[256]={0};
unsigned char tmp=0;
for(i=0;i<256;i++)
{
s[i]=i;
k[i]=key[i%Len];
}
for(i=0;i<256;i++)
{
j=(j+s[i]+k[i])%256;
tmp=s[i];
s[i]=s[j];
s[j]=tmp;
}
}

void rc4_crypt(unsigned char*s,unsigned char*Data,unsigned long Len)
{
int i=0,j=0,t=0;
unsigned long k=0;
unsigned char tmp;
for(k=0;k<Len;k++)
{
i=(i+1)%256;
j=(j+s[i])%256;
tmp=s[i];
s[i]=s[j];
s[j]=tmp;
t=(s[i]+s[j])%256;
Data[k]^=s[t];
}
}

int main()
{
unsigned char s[256]={0};
int i,j,k;
char key[256] = "12345678";
unsigned char data[] =
{
0xA3, 0x1A, 0xE3, 0x69, 0x2F, 0xBB, 0x1A, 0x84, 0x65, 0xC2,
0xAD, 0xAD, 0x9E, 0x96, 0x05, 0x02, 0x1F, 0x8E, 0x36, 0x4F,
0xE1, 0xEB, 0xAF, 0xF0, 0xEA, 0xC4, 0xA8, 0x2D, 0x42, 0xC7,
0x6E, 0x3F, 0xB0, 0xD3, 0xCC, 0x78, 0xF9, 0x98, 0x3F
};
unsigned long data_len = 39;
unsigned long key_len = 8;
for ( j = 0; j < data_len; j += key_len )
{
for ( k = 0; (key_len & ~(key_len >> 31)) != k && j + k < data_len; ++k )
data[j + k] ^= key[k];
}
for(i = 0;i < data_len;i += 3)
{
data[i+1] = data[i] ^ data[i+1];
data[i+2] = data[i+1] ^ data[i+2];
data[i] = data[i+2] ^ data[i];
}
rc4_init(s,(unsigned char*)key,key_len);
rc4_crypt(s,(unsigned char*)data,data_len);
for(i=0;i<39;i++)
{
printf("%c",data[i]);
}
return 0;
}

flag是:CISCN{6654d84617f627c88846c172e0f4d46c}

baby.bc

查看文件类型
在这里插入图片描述
LLVM IR bitcode,二进制文件。需要转换成.s,然后再转成可执行文件。
在当前目录下打开终端,输入如下命令:

1
clang baby.bc -o baby

可以看到生成的文件已经是可执行文件了
在这里插入图片描述
将该生成的文件使用IDA打开
查看main函数
在这里插入图片描述
那么接下来就查看fill_numberdocheck两个函数了
fill_number():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
__int64 __fastcall fill_number(__int64 a1)
{
char v2; // [rsp+1h] [rbp-69h]
char v3; // [rsp+11h] [rbp-59h]
char v4; // [rsp+21h] [rbp-49h]
char v5; // [rsp+31h] [rbp-39h]
char v6; // [rsp+40h] [rbp-2Ah]
char v7; // [rsp+41h] [rbp-29h]
signed __int64 v8; // [rsp+4Ah] [rbp-20h]
__int64 v9; // [rsp+52h] [rbp-18h]
__int64 v10; // [rsp+5Ah] [rbp-10h]

v10 = 0LL;
do
{
v9 = v10;
v8 = 5 * v10;
v7 = *(_BYTE *)(a1 + 5 * v10);
if ( map[5 * v10] )
{
v6 = 0;
if ( v7 != 48 )
return v6 & 1;
}
else
{
map[5 * v10] = v7 - 48;
}
v5 = *(_BYTE *)(a1 + v8 + 1);
if ( map[5 * v10 + 1] )
{
v6 = 0;
if ( v5 != 48 )
return v6 & 1;
}
else
{
map[5 * v10 + 1] = v5 - 48;
}
v4 = *(_BYTE *)(a1 + v8 + 2);
if ( map[5 * v10 + 2] )
{
v6 = 0;
if ( v4 != 48 )
return v6 & 1;
}
else
{
map[5 * v10 + 2] = v4 - 48;
}
v3 = *(_BYTE *)(a1 + v8 + 3);
if ( map[5 * v10 + 3] )
{
v6 = 0;
if ( v3 != 48 )
return v6 & 1;
}
else
{
map[5 * v10 + 3] = v3 - 48;
}
v2 = *(_BYTE *)(a1 + v8 + 4);
if ( map[5 * v10 + 4] )
{
v6 = 0;
if ( v2 != 48 )
return v6 & 1;
}
else
{
map[5 * v10 + 4] = v2 - 48;
}
++v10;
v6 = 1;
}
while ( v9 + 1 < 5 );
return v6 & 1;
}

由这段代码可得,五个字符是一循环,分别判断其每一行的1~5位的数字。分析if/else,主要就是说map[]不为0时,当前的输入字符必须是’0’;如果map[]为0,当前的map字符为输入字符的ASCII码减去48。

map[]是一个二维数组,我们可以查看它的值。

1
2
3
4
5
6
7
8
unsigned char ida_chars[] =
{
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0, 0, 4, 0, 0,
0, 0, 0, 3, 0,
0, 0, 0, 0, 0,
};

fill_number函数的功能是:
当5*5的map二维矩阵中的值为0时,将该值转化为输入字符的ASCII码减去48;不为0时,对应的输入值为字符一定要为’0’。因为返回的是v6&1,这个值需要返回是真值才可以出flag

docheck():
第一部分就是说map每行和每一列没有重复的数字
循环取输入的值,然后将取到值的对应位置写1,然后在后面循环中,如果该位置已经是1了,说明之前有出现过相同的数字,就是这样判断的。
在这里插入图片描述
接下来的row和col:
我们可以通过分析循环的次数和判断条件来得出如下的结果,代码比较长,需要耐心分析才行
在这里插入图片描述
在这里插入图片描述
最后得到的应该是如上的数独图
数独大佬可以自己解,变成大佬可以写代码用Z3
14253

53142

35421

21534

42315

根据上面的要求改map[2][2]和map[3][3]为0,md5之后得到flag
在这里插入图片描述
flag:CISCN{8a04b4597ad08b83211d3adfa1f61431}