[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}