[QCTF2018]asong

这题 wp 看得我挺郁闷的,位运算那里花的时间最长,但 wp 都是一句话带过,在我看来或运算不可逆,因为答案不唯一,但是很奇怪官方 wp 还有网上搜到的一些 wp 逆出来了,也不知道进行的操作是什么原理,几个对这一步进行爆破的 wp 也不知道为什么 38 个数据的数组算着算着变成了 39 个,我不太熟悉 python 也看不懂脚本写的是什么......所以还是用习惯的 c++ 写了个,也在这里写一下 wp

首先看到 main 函数

整体就是 sub_400C02 去了一下 QCTF{} 的皮,sub_400AAA 是一个词频统计,sub_400E54 进行了最后的几步操作,接下来逐个看一下

首先是 sub_400C02,规定为 "QCTF{",并且在函数结束时输入内容扔掉了包裹

sub_400AAA 如下

查看关键函数 sub_400936

可见是对于每一位字符进行一些运算,然后返回运算后的值,并对于这个值进行一个计数

接下来是重头戏 sub_400E54

为了方便倒着分析一下吧

首先是输出的 out 文件,winhex 打开可以看到十六进制数据,一共 38 个

0xEC, 0x29, 0xE3, 0x41, 0xE1, 0xF7, 0xAA, 0x1D, 0x29, 0xED,
0x29, 0x99, 0x39, 0xF3, 0xB7, 0xA9, 0xE7, 0xAC, 0x2B, 0xB7,
0xAB, 0x40, 0x9F, 0xA9, 0x31, 0x35, 0x2C, 0x29, 0xEF, 0xA8,
0x3D, 0x4B, 0xB0, 0xE9, 0xE1, 0x68, 0x7B, 0x41

往上是那个头大的位运算

进行的运算很简单

  • a[0]~a[36] 进行的操作为:a[i] = (a[i] << 3) | (a[i+1] >> 5)
  • a[37] = (a[37] << 3) | (a[0] >> 5)

可见 38 个数据围成了一个环,每个数据的值分别取决于它本身以及它后面那个数(最后一个的后面即为第一个)

所以直接爆破,首先是先爆破第一个与第二个,可以唯一确定第二个数的值为 133,但是第一个有多解,过程中使用的脚本如下

#include <bits/stdc++.h>
using namespace std;
int a[] = {
  0xEC, 0x29, 0xE3, 0x41, 0xE1, 0xF7, 0xAA, 0x1D, 0x29, 0xED,
  0x29, 0x99, 0x39, 0xF3, 0xB7, 0xA9, 0xE7, 0xAC, 0x2B, 0xB7,
  0xAB, 0x40, 0x9F, 0xA9, 0x31, 0x35, 0x2C, 0x29, 0xEF, 0xA8,
  0x3D, 0x4B, 0xB0, 0xE9, 0xE1, 0x68, 0x7B, 0x41
};
int b[40];
set<int> st;
int main() {
  for (int i = 0; i < 256; i++) {
    for (int j = 0; j < 256; j++) {
      int num = (i << 3) | (j >> 5);
      num %= 256;
      if (num == a[0]) {
        st.insert(j);
	//cout << i << " " << j << endl;
      }
    }
  }
  cout << endl;
  for (set<int>::iterator it = st.begin(); it != st.end(); it++) {
    for (int j = 0; j < 256; j++) {
      int num = (*it << 3) | (j >> 5);
      num %= 256;
      if (num == a[1]) {
        cout << *it << endl;
      }
    }
  }
  return 0;
}

上述脚本输出均为 133,确定了第二位为 133,然后据此可以得到第一位的取值范围为 [29 61 93 125 157 189 221 253]

不慌,因为接下来的每次运算都可以唯一确定这一位的取值以及得到下一位可能的取值。我们这里用一个叫 st 的 set 保存每一位可能取的值,用一个名为 ans 的 set 保存每一位确定的取值(ans[0]并不是唯一确定的,当时为了方便就用ans存储了),然后一直爆破下去,最后一位可能的取值的集合与第一位可能的取值的集合相运算便可以唯一确定两位的值,脚本如下

#include <bits/stdc++.h>
using namespace std;
int a[] = {
  0xEC, 0x29, 0xE3, 0x41, 0xE1, 0xF7, 0xAA, 0x1D, 0x29, 0xED,
  0x29, 0x99, 0x39, 0xF3, 0xB7, 0xA9, 0xE7, 0xAC, 0x2B, 0xB7,
  0xAB, 0x40, 0x9F, 0xA9, 0x31, 0x35, 0x2C, 0x29, 0xEF, 0xA8,
  0x3D, 0x4B, 0xB0, 0xE9, 0xE1, 0x68, 0x7B, 0x41
};
int b[40];
set<int> st[40];
set<int> ans[40];
int main() {
  int now = 0;
  for (int i = 0; i < 256; i++) {
    for (int j = 0; j < 256; j++) {
      if (j != 133) continue;
      int num = (i << 3) | (j >> 5);
      num %= 256;
      if (num == a[0]) {
        //cout << i << " " << j << endl;
	st[now].insert(i);
	ans[now].insert(i);
      }
    }
  }
  now++; 
  st[now].insert(133); 
  ans[now].insert(133);
  for (set<int>::iterator it = st[now].begin(); it != st[now].end(); it++) {
    for (int j = 0; j < 256; j++) {
      int num = (*it << 3) | (j >> 5);
      num %= 256;
      if (num == a[now]) {
        ans[now].insert(*it);
	st[now + 1].insert(j);
	//cout << *it << " " << j << endl;
      }
    }
  }
  while (now < 36) {
    now++;
    //cout << endl;
    for (set<int>::iterator it = st[now].begin(); it != st[now].end(); it++) {
      for (int j = 0; j < 256; j++) {
        int num = (*it << 3) | (j >> 5);
	num %= 256;
	if (num == a[now]) {
	  ans[now].insert(*it);
	  st[now + 1].insert(j);
	  //cout << *it << " " << j << endl;
	}
      }
    }
  }
  for (set<int>::iterator it = st[37].begin(); it != st[37].end(); it++) {
    for (set<int>::iterator itt = ans[0].begin(); itt != ans[0].end(); itt++) {
      int num = (*it << 3) | (*itt >> 5);
      num %= 256;
      if (num == a[37]) {
        b[0] = *itt;
	b[37] = *it;
      }
    }
  }
  for (int i = 1; i < 37; i++) {
    for (set<int>::iterator it = ans[i].begin(); it != ans[i].end(); it++) {
      b[i] = *it;
    }
  }
  for (int i = 0; i < 38; i++) cout << b[i] << ", ";
  return 0;
}

结果为

61, 133, 60, 104, 60, 62, 245, 67, 165, 61, 
165, 51, 39, 62, 118, 245, 60, 245, 133, 118, 
245, 104, 19, 245, 38, 38, 165, 133, 61, 245, 
7, 169, 118, 29, 60, 45, 15, 104

这样位运算这步就结束辽,往上看到顺序变换的那里

根据表的顺序变换就可以,进行的操作为

int now = 0;
while (dword_6020a0[now]) {
  a1[now] = a1[dword_6020a0[now]];
  now = dword_6020a0[now]
}

提取出 dword_6020a0 表

0x16, 0x00, 0x06, 0x02, 0x1E, 0x18, 0x09, 0x01, 0x15, 0x07,
0x12, 0x0A, 0x08, 0x0C, 0x11, 0x17, 0x0D, 0x04, 0x03, 0x0E, 
0x13, 0x0B, 0x14, 0x10, 0x0F, 0x05, 0x19, 0x24, 0x1B, 0x1C,
0x1D, 0x25, 0x1F, 0x20, 0x21, 0x1A, 0x22, 0x23

打印一下操作的对象就是下面这样

虽然看不太懂 while 循环外到返回前进行的那两步操作是啥,不过看看左边这列少个 1,右边这列少个 0,大致也能猜到进行的操作就是 a1[1] = a1[0],所以就可以完善脚本了

#include <bits/stdc++.h>
using namespace std;
int a[] = {
  0xEC, 0x29, 0xE3, 0x41, 0xE1, 0xF7, 0xAA, 0x1D, 0x29, 0xED,
  0x29, 0x99, 0x39, 0xF3, 0xB7, 0xA9, 0xE7, 0xAC, 0x2B, 0xB7,
  0xAB, 0x40, 0x9F, 0xA9, 0x31, 0x35, 0x2C, 0x29, 0xEF, 0xA8,
  0x3D, 0x4B, 0xB0, 0xE9, 0xE1, 0x68, 0x7B, 0x41
};
int b[40], c[40];
/*int b[] = {
  61, 133, 60, 104, 60, 62, 245, 67, 165, 61, 
  165, 51, 39, 62, 118, 245, 60, 245, 133, 118, 
  245, 104, 19, 245, 38, 38, 165, 133, 61, 245, 
  7, 169, 118, 29, 60, 45, 15, 104
};*/
int table[] = {
  0x16, 0x00, 0x06, 0x02, 0x1E, 0x18, 0x09, 0x01, 0x15, 0x07,
  0x12, 0x0A, 0x08, 0x0C, 0x11, 0x17, 0x0D, 0x04, 0x03, 0x0E, 
  0x13, 0x0B, 0x14, 0x10, 0x0F, 0x05, 0x19, 0x24, 0x1B, 0x1C,
  0x1D, 0x25, 0x1F, 0x20, 0x21, 0x1A, 0x22, 0x23
};
set<int> st[40];
set<int> ans[40];
int main() {
  ...
  ...省略了之前进行的操作
  now = 0;
  c[0] = b[1];
  while (table[now]) {
    c[table[now]] = b[now];
    now = table[now];
  }
  for (int i = 0; i < 38; i++) {
    cout << c[i] << ",";
  }
  return 0;
}

得到变换前的数组为

133, 67, 104, 133, 245, 38, 60, 61, 39, 245, 
51, 104, 62, 60, 118, 38, 245, 118, 165, 245, 
19, 165, 61, 245, 62, 165, 45, 61, 245, 7, 
60, 118, 29, 60, 15, 104, 133, 169

然后就到了最后的词频统计那里,也就是刚刚得到的数据所对应的字母,这里把词频统计的函数复制过来算一下,然后用一个 map 映射一下就好了,总的脚本即为

#include <bits/stdc++.h>
using namespace std;
int a[] = {
  0xEC, 0x29, 0xE3, 0x41, 0xE1, 0xF7, 0xAA, 0x1D, 0x29, 0xED,
  0x29, 0x99, 0x39, 0xF3, 0xB7, 0xA9, 0xE7, 0xAC, 0x2B, 0xB7,
  0xAB, 0x40, 0x9F, 0xA9, 0x31, 0x35, 0x2C, 0x29, 0xEF, 0xA8,
  0x3D, 0x4B, 0xB0, 0xE9, 0xE1, 0x68, 0x7B, 0x41
};
int b[40], c[40];
/*int b[] = {
  61, 133, 60, 104, 60, 62, 245, 67, 165, 61, 
  165, 51, 39, 62, 118, 245, 60, 245, 133, 118, 
  245, 104, 19, 245, 38, 38, 165, 133, 61, 245, 
  7, 169, 118, 29, 60, 45, 15, 104
};*/
/*int c[] = {
  133, 67, 104, 133, 245, 38, 60, 61, 39, 245, 
  51, 104, 62, 60, 118, 38, 245, 118, 165, 245, 
  19, 165, 61, 245, 62, 165, 45, 61, 245, 7, 
  60, 118, 29, 60, 15, 104, 133, 169
};*/
int table[] = {
  0x16, 0x00, 0x06, 0x02, 0x1E, 0x18, 0x09, 0x01, 0x15, 0x07,
  0x12, 0x0A, 0x08, 0x0C, 0x11, 0x17, 0x0D, 0x04, 0x03, 0x0E, 
  0x13, 0x0B, 0x14, 0x10, 0x0F, 0x05, 0x19, 0x24, 0x1B, 0x1C,
  0x1D, 0x25, 0x1F, 0x20, 0x21, 0x1A, 0x22, 0x23
};
set<int> st[40];
set<int> ans[40];
struct node{
  char alp;
  int cnt;
} e[100];
map<int, char> mp;
int clac(char a1) {
  int result = (unsigned int)(a1 - 10);
  switch (a1) {
    case 10: {
      result = (unsigned int)(a1 + 35);
      break;
    }
    case 32:
    case 33:
    case 34: {
      result = (unsigned int)(a1 + 10);
      break;
    }  
    case 39: {
      result = (unsigned int)(a1 + 2);
      break;
    }
    case 44: {
      result = (unsigned int)(a1 - 4);
      break;
    }       
    case 46: {
      result = (unsigned int)(a1 - 7);
      break;
    }
    case 58:
    case 59: {
      result = (unsigned int)(a1 - 21);
      break;
    }
    case 63: {
      result = (unsigned int)(a1 - 27);
      break;
    }
    case 95: {
      result = (unsigned int)(a1 - 49);
      break;
    }
    default: {
      if (a1 <= 47 || a1 > 57) {
        if (a1 <= 64 || a1 > 90) {
          if ( a1 > 96 && a1 <= 122 )
            result = (unsigned int)(a1 - 87);
        } else {
          result = (unsigned int)(a1 - 55);
        }
      } else {
        result = (unsigned int)(a1 - 48);
      }
      break;
    }
  }
  return result;
}
int main() {
  int now = 0;
  for (int i = 0; i < 256; i++) {
    for (int j = 0; j < 256; j++) {
      if (j != 133) continue;
      int num = (i << 3) | (j >> 5);
      num %= 256;
      if (num == a[0]) {
        //cout << i << " " << j << endl;
	st[now].insert(i);
	ans[now].insert(i);
     }
    }
  }
  now++; 
  st[now].insert(133); 
  ans[now].insert(133);
  for (set<int>::iterator it = st[now].begin(); it != st[now].end(); it++) {
    for (int j = 0; j < 256; j++) {
      int num = (*it << 3) | (j >> 5);
      num %= 256;
      if (num == a[now]) {
	ans[now].insert(*it);
	st[now + 1].insert(j);
	//cout << *it << " " << j << endl;
      }
    }
  }
  while (now < 36) {
    now++;
    //cout << endl;
    for (set<int>::iterator it = st[now].begin(); it != st[now].end(); it++) {
      for (int j = 0; j < 256; j++) {
	int num = (*it << 3) | (j >> 5);
	num %= 256;
	if (num == a[now]) {
	  ans[now].insert(*it);
	  st[now + 1].insert(j);
	  //cout << *it << " " << j << endl;
	} 
      }
    }
  }
  for (set<int>::iterator it = st[37].begin(); it != st[37].end(); it++) {
    for (set<int>::iterator itt = ans[0].begin(); itt != ans[0].end(); itt++) {
      int num = (*it << 3) | (*itt >> 5);
      num %= 256;
      if (num == a[37]) {
	b[0] = *itt;
	b[37] = *it;
      }
    }
  }
  for (int i = 1; i < 37; i++) {
    for (set<int>::iterator it = ans[i].begin(); it != ans[i].end(); it++) {
      b[i] = *it;
    }
  }
  now = 0;
  c[0] = b[1];
  while (table[now]) {
    c[table[now]] = b[now];
    now = table[now];
  }
  /*for (int i = 0; i < 38; i++) {
  	cout << c[i] << ",";
  }*/
  freopen("that_girl.txt", "r", stdin);
  string s;
  while (cin >> s) {
    if (s == "ENDENDEND") break;  //我自己加的
    int len = s.length();
    for (int i = 0; i < len; i++) {
      int num = clac(s[i]);
      e[num].alp = tolower(s[i]); //词频分析那里不管大小写,我就默认比较多的小写了
      e[num].cnt++;
    }
  }
  for (int i = 0; i < 100; i++)
    mp[e[i].cnt] = e[i].alp;
  for (int i = 0; i < 38; i++)
    cout << mp[c[i]];
  return 0;
}

得到 flag

flag{that_girl_saying_no_for_your_vindicate}

posted @ 2022-07-25 18:05  Moominn  阅读(155)  评论(0编辑  收藏  举报