关于比赛

学生组第2名,498分,离第1就差了4分,略有遗憾。不知道奖金是不是每个组都有,如果每个组都有的话还能喜提2k奖金,保底有个奖杯🏆还是不错的。

听学长说是misc,crypto,re混合赛才参加的,结果今年基本纯 misc + 1道 web 渗透 + 1道re,有一半的实操题要渗透打进去了才能拿到附件,但众所周知我除了 web 什么都会做一点,所以有一半的实操题都没拿到附件,所幸赛后有好心选手分享,我也拿到了此次比赛唯一一道逆向题的附件。

其他题简单描述一下,首先misc大部分是写爬虫,或者用其他手段爬取数据,尽量学会python写爬虫;剩余的则是常见的misc/数据处理;web其实就是一道弱口令,密码是项目的名字,我把我所知的渗透方法都尝试了也没想到是弱口令啊,遗憾离场😭;模型安全需要自己反推一个sigmoid的公式,推出来就很简单了,我应该就是靠的这道题才爬到第2的。

就简单写一下逆向的wp了

题目分析

题目描述

校园网管理系统的下载管理模块中,password.zip中的csv文件被可疑程序加密(加密的时间是2025年8月18
日上午8-12点),导致部分数据无法正常访问。请下载encrypy.zip,分析该加密程序,恢复被加密的数据。
在恢复出的数据中定位用户名为“user2030”的账户记录,提交其password字段的值作为答案。

csv表头为:id,username,password,hash

程序分析

ida直接打开程序发现应该是加壳了image-20250828150304025

查壳

image-20250828150346750

没有魔改的upx壳,直接脱壳

脱完壳看main函数,程序比较简单,根据我的理解为其修改了变量名并添加了注释

/*关键代码如下:*/
srand(key ^ 0x9E3779B9); // 设种子
do
*step1_ptr++ ^= rand(); // 每个字节与rand()结果异或
while ( step1_ptr != end_ptr );
srand(key ^ 0x85EBCA77); // 再设seed
do
{
ls_bit = rand() & 7; // 取低3bit
if ( ls_bit )
*step2_ptr = __ROL1__(*step2_ptr, ls_bit);// 左移bit位
++step2_ptr;
}
while ( step2_ptr != end_ptr );
if ( file_length != 1 )
{
key_1 = key;
table_idx = 0;
srand(key_1 ^ 0xC2B2AE3D); // 再设seed
step3_table = malloc(8 * file_length); // 置换表 (64bit * n)
table = step3_table;
if ( !step3_table )
sub_1260("perm alloc");
do
{
step3_table[table_idx] = table_idx; // 初始化置换表 [0,1,2,...,n-1]
length = table_idx++;
}
while ( file_length != table_idx );
step3_idx = length;
do
{
idx = rand();
v22 = table[step3_idx];
v23 = &table[idx % (step3_idx + 1)];
table[step3_idx] = *v23;
*v23 = v22;
--step3_idx; // 置换表生成
}
while ( step3_idx );
ptr_4 = malloc(table_idx);
if ( !ptr_4 )
sub_1260("perm buf alloc");
do
{
ptr_4[step3_idx] = file_content[table[step3_idx]];// 根据置换表打乱字节
v25 = step3_idx++;
}
while ( length != v25 );
__memcpy_chk(file_content, ptr_4, table_idx, ptr);
free(ptr_4);
free(table);
}

该程序是一个 加密器,它的加密步骤是:

  1. 随机异或byte ^= rand() (种子 = time ^ 0x9E3779B9
  2. 随机循环左移byte = ROL(byte, rand() & 7) (种子 = time ^ 0x85EBCA77
  3. 随机置换:按置换表打乱字节顺序 (种子 = time ^ 0xC2B2AE3D

解题思路

  1. 恢复置换
    • 生成相同的置换表(用相同种子 time ^ 0xC2B2AE3D)。
    • 置换是可逆的,用表恢复原顺序。
  2. 逆向循环左移
    • 重新生成相同的随机数序列(用种子 time ^ 0x85EBCA77)。
    • 逐字节执行 循环右移(与加密时左移相反)。
  3. 逆向异或
    • 重新生成相同的随机数序列(用种子 time ^ 0x9E3779B9)。
    • 再次执行 byte ^= rand()(异或是自逆运算)。

解题脚本

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

unsigned char ROR1(unsigned char val, int shift) {
return (val >> shift) | (val << (8 - shift));
}

int decrypt_file(const char *in_filename, const char *out_filename, int timestamp) {
FILE *in = fopen(in_filename, "rb");
if (!in) {
perror("open input");
return 1;
}

fseek(in, 0, SEEK_END);
long n = ftell(in);
rewind(in);

if (n <= 0) {
fclose(in);
printf("Empty file\n");
return 0;
}

unsigned char *buf = malloc(n);
if (!buf) {
perror("malloc");
fclose(in);
return 1;
}
if (fread(buf, 1, n, in) != (size_t)n) {
perror("fread");
free(buf);
fclose(in);
return 1;
}
fclose(in);

if (n > 1) {
srand(timestamp ^ 0xC2B2AE3D);
size_t *perm = malloc(sizeof(size_t) * n);
if (!perm) {
perror("perm alloc");
free(buf);
return 1;
}

for (size_t i = 0; i < (size_t)n; i++) {
perm[i] = i;
}
for (size_t i = n - 1; i > 0; i--) {
size_t j = rand() % (i + 1);
size_t tmp = perm[i];
perm[i] = perm[j];
perm[j] = tmp;
}

unsigned char *tmpbuf = malloc(n);
if (!tmpbuf) {
perror("perm buf alloc");
free(buf);
free(perm);
return 1;
}
for (size_t i = 0; i < (size_t)n; i++) {
tmpbuf[perm[i]] = buf[i];
}
memcpy(buf, tmpbuf, n);
free(tmpbuf);
free(perm);
}

srand(timestamp ^ 0x85EBCA77);
for (long i = 0; i < n; i++) {
int shift = rand() & 7;
if (shift) {
buf[i] = ROR1(buf[i], shift);
}
}

srand(timestamp ^ 0x9E3779B9);
for (long i = 0; i < n; i++) {
buf[i] ^= rand();
}
FILE *out = fopen(out_filename, "wb");
if (!out) {
perror("open output");
free(buf);
return 1;
}
if (fwrite(buf, 1, n, out) != (size_t)n) {
perror("fwrite");
fclose(out);
free(buf);
return 1;
}

fclose(out);
free(buf);

printf("Decrypted: %s -> %s\n", in_filename, out_filename);
return 0;
}

int main(int argc, char *argv[]) {
if (argc != 4) {
printf("Usage: %s <input> <output> <timestamp>\n", argv[0]);
return 1;
}
const char *in_file = argv[1];
const char *out_file = argv[2];
int timestamp = atoi(argv[3]); // 加密时的 time(0)
return decrypt_file(in_file, out_file, timestamp);
}

想要正确解密需要爆破时间戳,根据加密时间确定爆破范围image-20250828152521491

image-20250828152635392