强网拟态 2021 showyourflag Writeup
题目简介
题目给出程序 showyourflag
以及用该程序加密后的文件 yourflag
。
可以通过命令行执行:
showyourflag [infile] [outfile]
其中 infile
为输入文件,outfile
为输出文件。
加密分析
字节变换
程序首先对输入文件的内容进行逐字节变换,位于函数 sub_401FC0
。
v8 = ~__ROL1__(v8, 1);
子串压缩
随后实现了一个类似于压缩的算法,位于函数 sub_4023D0
。
函数先对子串开头的三个字节进行哈希,随后在字典中查找之前是否出现过相同哈希值的子串。
若存在则求出两个子串相同部分长度的最大值,随后将两个子串相差的距离 dis
以及匹配的长度 len
进行编码,并将编码结果存储到输出文件中。
若不存在则直接将子串内容存储到输出文件中。
while ( 1 )
{
tri_bytes = *now_ptr & 0xFFFFFF;
hash = (0x9E3779B9 * tri_bytes) >> 18;
last_pos = dict[hash];
dict[hash] = now_ptr - a1;
last_ptr = &a1[last_pos];
dis = now_ptr - last_ptr;
if ( (now_ptr - last_ptr) <= 0x1FFF )
{
if ( now_ptr >= tail_13 )
goto LABEL_21;
v10 = (now_ptr + 1);
if ( tri_bytes != (*last_ptr & 0xFFFFFF) )
goto LABEL_10;
if ( tail_13 <= v10 )
goto LABEL_21;
if ( v9 < now_ptr )
copy_raw(now_ptr - v9, v9, output_);
len_d2 = len_prefix(last_ptr + 3, (now_ptr + 3), tail_4);
dis_d1 = dis - 1;
len_d2_remain = len_d2;
for ( hi_dis_d1 = (dis - 1) >> 8; len_d2_remain > 0x106; *(output - 1) = dis - 1 )
{ // case len_d2 : 0x107 ~ 0x1FFF
// split for 0x106 bytes
*output = hi_dis_d1 - 0x20; // out[0] = high_byte(dis_d1) - 0x20
// 0xE0 ~ 0xFF
len_d2_remain -= 0x106; // len_d2 -= 0x106
output += 3;
*(output - 2) = 0xFD; // out[1] = 0x106 - 2 - 7
// 0xFD
} // out[2] = low_byte(dis_d1)
if ( len_d2_remain > 6 ) // 0x0 ~ 0xFF
{
output[2] = dis_d1; // case len_d2 : 0x7 ~ 0x106
output_ = output + 3;
*output = hi_dis_d1 - 0x20; // out[0] = high_byte(dis_d1) - 0x20
// 0xE0 ~ 0xFF
output[1] = len_d2_remain - 7; // out[1] = len_d2 - n * 0x106 - 7
// 0x0 ~ 0xFF
} // out[2] = low_byte(dis_d1)
else // 0x0 ~ 0xFF
{
output[1] = dis_d1; // case len_d2 : 0x1 ~ 0x6
output_ = output + 2;
*output = hi_dis_d1 + 0x20 * len_d2_remain;//
// out[0] = high_byte(dis_d1) + 0x20 * len_d2
// 0x20 ~ 0xDF
} // out[1] = low_byte(dis_d1)
// 0x0 ~ 0xFF
v22 = (v18 + len_d2);
v23 = *v22;
now_ptr = (v22 + 2);
dict[(0x9E3779B9 * (v23 & 0xFFFFFF)) >> 18] = v22 - a1;
v9 = v22 + 2;
dict[(0x9E3779B9 * (v23 >> 8)) >> 18] = v22 + 1 - a1;
if ( tail_13 <= v22 + 2 )
goto LABEL_21;
}
else
{
if ( now_ptr >= tail_13 )
goto LABEL_21;
v10 = (now_ptr + 1);
LABEL_10:
now_ptr = v10;
}
}
LABEL_21:
return result;
解密分析
提取子串相差的距离 dis
以及匹配的长度 len
后还原原串内容即可。
#include <cstdio>
#include <cstdlib>
int main(){
FILE *pFile, *pFile2;
long lSize;
unsigned char *buffer,*buffer2;
size_t result;
pFile = fopen ( "yourflag" , "rb" );
pFile2 = fopen ( "flag.png" , "wb+" );
fseek (pFile , 0 , SEEK_END);
lSize = ftell (pFile);
rewind (pFile);
buffer = (unsigned char*) malloc (sizeof(char)*lSize);
buffer2 = (unsigned char*) malloc (sizeof(char)*lSize);
result = fread (buffer,1,lSize,pFile);
int i=0,dis=0,len,cnt=0;
while(i<lSize-2){
printf("%02X ",buffer[i]);
if (0<=buffer[i]&&buffer[i]<0x20){
for (int j=1;j<=buffer[i]+1;j++){
buffer2[cnt++]=buffer[i+j];
}
i+=buffer[i]+2;
dis=0;
len=0;
}
else if (0x20<=buffer[i]&&buffer[i]<0xE0){
len=(buffer[i]&0xE0)/0x20+2;
dis=buffer[i+1]+((buffer[i]&0x1F)<<8)+1;
i+=2;
}
else if (0xE0<=buffer[i]&&buffer[i]<0x100){
len=buffer[i+1]+9;
dis=((buffer[i]-0xE0)<<8)+buffer[i+2]+1;
i+=3;
}
for (int j=0;j<len;j++){
buffer2[cnt]=buffer2[cnt-dis];
cnt++;
}
}
for (int i=0;i<lSize;i++){
int t=~buffer2[i];
buffer2[i]=((t&0xFE)>>1)+((t&0x01)<<7);
}
fwrite(buffer2,1,lSize,pFile2);
fclose (pFile);
fclose (pFile2);
free (buffer);
return 0;
}
运行后得到解密的 flag.png
,内容如下:
后记
结合 Tenet
插件来调这道题会轻松很多。