N1CTF2021 - Easyre

很多汇编码 IDA 反编译不了,查询后得知 v 开头的一系列指令来自 AVX,遂给 IDA 安装 Micro AVX,然而无济于事。考虑直接逆汇编码。

步骤一:Extend

大体观察一下可以发现,整个流程就是对一个 int128 进行加密,但是储存加密结果的寄存器可能会改变,所以首先考虑写一个程序去追踪它被储存到了哪个寄存器,形如 aesenclast xmm4,xmm0,xmmX 这样,也就是操作数 1 与操作数 2 和操作数 3 都不相同的指令就意味着加密结果从 xmm0 转移到了 xmm4,把它拆分成两个指令,即 aesenclast xmm0,xmm0,xmmX 和 movdqa xmm4,xmm0。而另一种指令形如 aesenclast xmm0,xmm0,xmmX 则不意味着主寄存器的改变,于是我们把所有主寄存器改变的指令集中到了 vmovdqa 这一种上。

#include <cstdio>
#include <iostream>
#include <string>
#include <deque>
using namespace std;

string str;

deque<string> dq;

struct opt{
	string opt0, opta, optb;
	opt(){}
	opt(const string &str){
		opt0 = str.substr(str.find_last_of('	') + 1, str.find(',') - str.find_last_of('	') - 1);
		opta = str.substr(str.find(',') + 1, str.find_last_of(',') - str.find(',') - 1);
		optb = str.substr(str.find_last_of(',') + 1);
		if(optb.back() == 10) optb.pop_back();
	}
	opt(const string &str, bool dual){
		opt0 = str.substr(str.find_last_of('	') + 1, str.find(',') - str.find_last_of('	') - 1);
		opta = str.substr(str.find_last_of(',') + 1);
		if(opta.back() == 10) opta.pop_back();
	}
};

int line;
string reg = "xmm0";

int main(){
	freopen("nottobeworked.asm", "r", stdin);
	while(getline(cin, str)){
		if(str.find("vmovdqa") != string::npos){
			opt optnow(str, 1);
			if(optnow.opta == reg) reg = optnow.opt0;
			dq.push_back(str + "\n");
		}else if(str.find("aesimc") != string::npos){
			opt optnow(str, 1);
			if(optnow.opt0 != reg && optnow.opta == reg){
				cout << "ERROR";
				dq.push_back("	aesimc	" + reg + "," + reg + "\n");
				dq.push_back("	vmovdqa	" + optnow.opt0 + "," + reg + "\n");
				reg = optnow.opt0;
			}else dq.push_back(str + "\n");
		}else{
			opt optnow(str);
			if(optnow.opt0 != reg && (optnow.opta == reg || optnow.optb == reg)){
				str.replace(str.find(optnow.opt0), optnow.opt0.length(), reg);
				dq.push_back(str + "\n");
				dq.push_back("	vmovdqa	" + optnow.opt0 + "," + reg + "\n");
				reg = optnow.opt0;
			}else dq.push_back(str + "\n");
		}
		if(reg != "xmm0") cout << reg << endl;
	}
	cout << reg;
	freopen("extended.asm", "w", stdout);
	while(!dq.empty()){
		cout << dq.front();
		dq.pop_front();
	}
	return 0;
}

步骤二:Replace

然后还有形如 vpxor xmm1,xmm2,xmm15 这样的指令,它不代表任何主寄存器的改变,仅仅是求了一个异或的中间结果,但是这样做的问题是 xmm1 寄存器上原来的值会丢失,因此我的做法是,先进行一次假加密操作,但是这次操作中会把每个被覆盖的值先备份到栈中,也就是把这条指令扩展成 movdqa [rsp+xxx],xmm1 和 vpxor xmm1,xmm2,xmm15。最后在解密操作的时候把它的值复原。

由于求中间结果这一步对于解密来说毫无意义,因此我们先考虑直接将 vpxor xmm1,xmm2,xmm15 替换成 movdqa [rsp+xxx],xmm1。

#include <cstdio>
#include <iostream>
#include <string>
using namespace std;

int alloc = 16;
string str;

string int2str(int x){
	string res;
	while(x){
		int num = x % 16;
		string pre;
		pre.push_back(num < 10 ? '0' + num : 'A' + num - 10);
		res = pre + res;
		x /= 16;
	}
	if(isupper(res[0])) res = "0" + res;
	return res;
}

struct opt{
	string opt0, opta, optb;
	opt(){}
	opt(const string &str){
		opt0 = str.substr(str.find_last_of('	') + 1, str.find(',') - str.find_last_of('	') - 1);
		opta = str.substr(str.find(',') + 1, str.find_last_of(',') - str.find(',') - 1);
		optb = str.substr(str.find_last_of(',') + 1);
		if(optb.back() == 10) optb.pop_back();
	}
	opt(const string &str, bool dual){
		opt0 = str.substr(str.find_last_of('	') + 1, str.find(',') - str.find_last_of('	') - 1);
		opta = str.substr(str.find_last_of(',') + 1);
		if(opta.back() == 10) opta.pop_back();
	}
};

int main(){
	freopen("extended.asm", "r", stdin);
	freopen("replaced.asm", "w", stdout);
	while(getline(cin, str)){
		if(str.find("aesimc") != string::npos || str.find("vmovdqa") != string::npos){
			int offset = str.find("aesimc") != string::npos ? 8 : 9;
			string reg = str.substr(offset, str.find(',') - offset);
			string output = "	vmovdqa	";
			output += "[rsp+" + int2str(alloc) + "h]," + reg;
			alloc += 16;
			cout << output << endl;
			if(str.find("vmovdqa") != string::npos) cout << str << endl;
		}else if(str.find("vpxor") != string::npos){
			string opt0 = str.substr(7, str.find(',') - 7);
			string opta = str.substr(str.find(',') + 1, str.find_last_of(',') - str.find(',') - 1);
			string optb = str.substr(str.find_last_of(',') + 1);
			if(opt0 != opta && opt0 != optb){
				string reg = opt0;
				string output = "	vmovdqa	";
				output += "[rsp+" + int2str(alloc) + "h]," + reg;
				alloc += 16;
				cout << output << endl;
			}else cout << str << endl;
		}else if(str.find("vpaddq") != string::npos || str.find("vpsubq") != string::npos){
			string opt0 = str.substr(8, str.find(',') - 8);
			string opta = str.substr(str.find(',') + 1, str.find_last_of(',') - str.find(',') - 1);
			string optb = str.substr(str.find_last_of(',') + 1);
			if(opt0 != opta && opt0 != optb){
				string reg = opt0;
				string output = "	vmovdqa	";
				output += "[rsp+" + int2str(alloc) + "h]," + reg;
				alloc += 16;
				cout << output << endl;
			}else cout << str << endl;
		}else if(str.find("vpshufd") != string::npos){
			string opt0 = str.substr(9, str.find(',') - 9);
			string opta = str.substr(str.find(',') + 1, str.find_last_of(',') - str.find(',') - 1);
			if(opt0 != opta){
				string reg = opt0;
				string output = "	vmovdqa	";
				output += "[rsp+" + int2str(alloc) + "h]," + reg;
				alloc += 16;
				cout << output << endl;
			}else cout << str << endl;
		}else cout << str << endl;
	}
}

步骤三:Add

这一步执行的就是上一步第一段提到的内容。需要注意不能从栈直接赋值到栈。

#include <cstdio>
#include <iostream>
#include <string>
using namespace std;

int alloc = 16;
string str, thereg;

string int2str(int x){
	string res;
	while(x){
		int num = x % 16;
		string pre;
		pre.push_back(num < 10 ? '0' + num : 'A' + num - 10);
		res = pre + res;
		x /= 16;
	}
	if(isupper(res[0])) res = "0" + res;
	return res;
}

int main(){
	freopen("extended.asm", "r", stdin);
	freopen("added.asm", "w", stdout);
	while(getline(cin, str)){
		thereg = "xmm15";
		if(str.find("aesimc") != string::npos || str.find("vmovdqa") != string::npos){
			int offset = str.find("aesimc") != string::npos ? 8 : 9;
			string reg = str.substr(offset, str.find(',') - offset);
			if(reg == thereg) thereg = "xmm14";
			cout
			<< "	vmovdqa	[rsp]," + thereg + "\n"
			<< "	vmovdqa	" + thereg + "," << reg << '\n'
			<< "	vmovdqa	[rsp+" << int2str(alloc) << "h]," + thereg + "\n"
			<< "	vmovdqa " + thereg + ",[rsp]\n"
			;
			alloc += 16;
		}else if(str.find("vpxor") != string::npos){
			string opt0 = str.substr(7, str.find(',') - 7);
			string opta = str.substr(str.find(',') + 1, str.find_last_of(',') - str.find(',') - 1);
			string optb = str.substr(str.find_last_of(',') + 1);
			if(opt0 != opta && opt0 != optb){
				string reg = opt0;
				if(reg == thereg) thereg = "xmm14";
				cout
				<< "	vmovdqa	[rsp]," + thereg + "\n"
				<< "	vmovdqa	" + thereg + "," << reg << '\n'
				<< "	vmovdqa	[rsp+" << int2str(alloc) << "h]," + thereg + "\n"
				<< "	vmovdqa " + thereg + ",[rsp]\n"
				;
				alloc += 16;
			}
		}else if(str.find("vpaddq") != string::npos || str.find("vpsubq") != string::npos){
			string opt0 = str.substr(8, str.find(',') - 8);
			string opta = str.substr(str.find(',') + 1, str.find_last_of(',') - str.find(',') - 1);
			string optb = str.substr(str.find_last_of(',') + 1);
			if(opt0 != opta && opt0 != optb){
				string reg = opt0;
				if(reg == thereg) thereg = "xmm14";
				cout
				<< "	vmovdqa	[rsp]," + thereg + "\n"
				<< "	vmovdqa	" + thereg + "," << reg << '\n'
				<< "	vmovdqa	[rsp+" << int2str(alloc) << "h]," + thereg + "\n"
				<< "	vmovdqa " + thereg + ",[rsp]\n"
				;
				alloc += 16;
			}
		}else if(str.find("vpshufd") != string::npos){
			string opt0 = str.substr(9, str.find(',') - 9);
			string opta = str.substr(str.find(',') + 1, str.find_last_of(',') - str.find(',') - 1);
			if(opt0 != opta){
				string reg = opt0;
				if(reg == thereg) thereg = "xmm14";
				cout
				<< "	vmovdqa	[rsp]," + thereg + "\n"
				<< "	vmovdqa	" + thereg + "," << reg << '\n'
				<< "	vmovdqa	[rsp+" << int2str(alloc) << "h]," + thereg + "\n"
				<< "	vmovdqa " + thereg + ",[rsp]\n"
				;
				alloc += 16;
			}
		}
		cout << str << endl;
	}
	return 0;
}

步骤四:Reverse

这一步需要做的是将每一条指令解释成对应的逆向指令,并用一个栈来倒指令。比较重要的是 AES 相关的指令,这些指令不能找到对应的逆向指令,但是可以用多个指令排列组合达到同样的效果。更具体一点,把每个指令分解成 AES 的四个经典步骤,然后排列组合。

#include <string>
#include <iostream>
#include <stack>
#include <map>
#include <vector>
#include <utility>
using namespace std;

vector<pair<string, string>> pr;

unsigned char cand[4] = {0, 1, 2, 3};
bool used[4];
unsigned char id[4];

char half2hex(unsigned char x){
	return x < 10 ? x + '0' : x + 'A' - 10;
}

string char2hex(unsigned char x){
	string ret;
	ret.push_back(half2hex(x >> 4));
	ret.push_back(half2hex(x & 0xF));
	return ret;
}

void dfs(int i, unsigned char key){
	if(i < 0){
		unsigned char res = 0;
		for(int i = 0; i < 4; i++)
			res |= id[i] << (i << 1);
		string keystr = char2hex(key);
		string resstr = char2hex(res);
		if(isupper(keystr[0])) keystr = "0" + keystr;
		if(isupper(resstr[0])) resstr = "0" + resstr;
		pr.push_back(make_pair(keystr, resstr));
		return;
	}
	for(int j = 0; j < 4; j++) if(!used[j]){
		used[j] = true;
		id[cand[j]] = i;
		dfs(i - 1, key | (cand[j] << (i << 1)));
		used[j] = false;
	}
}

void rev_shuf(){
	dfs(3, 0);
}

string keep, reg, str, thereg;
stack<string> s;

int line;

int main(){
	rev_shuf();
	freopen("replaced.asm", "r", stdin);
	while(getline(cin, str)){
		thereg = "xmm15";
		line++;
		if(str.find("vmovdqa") != string::npos){
			string opta = str.substr(9, str.find(',') - 9);
			string optb = str.substr(str.find(',') + 1);
			swap(opta, optb);
			if(thereg == opta || thereg == optb) thereg = "xmm14";
			if(thereg == opta || thereg == optb) thereg = "xmm13";
			s.push(
			"	vmovdqa	[rsp]," + thereg + "\n"
			+ "	vmovdqa	" + thereg + "," + optb + "\n"
			+ "	vmovdqa	" + opta + "," + thereg + "\n"
			+ "	vmovdqa	" + thereg + ",[rsp]\n"
			);
		}else if(str.find("vpxor") != string::npos){
			s.push(str + "\n");
		}else if(str.find("vaesdeclast") != string::npos){
			string opta = str.substr(str.find(',') + 1, str.find_last_of(',') - str.find(',') - 1);
			string optb = str.substr(str.find_last_of(',') + 1);
			s.push(
			"	pxor	" + opta + "," + optb + "\n"
			+ "	aesenclast	" + opta + "," + optb + "\n"
			+ "	pxor	" + opta + "," + optb + "\n"
			);
		}else if(str.find("vaesdec") != string::npos){
			string opta = str.substr(str.find(',') + 1, str.find_last_of(',') - str.find(',') - 1);
			string optb = str.substr(str.find_last_of(',') + 1);
			s.push(
			"	pxor	" + opta + "," + optb + "\n"
			+ "	aesdeclast	" + opta + "," + optb + "\n"
			+ "	pxor	" + opta + "," + optb + "\n"
			+ "	aesenc	" + opta + "," + optb + "\n"
			+ "	pxor	" + opta + "," + optb + "\n"
			+ "	aesenclast	" + opta + "," + optb + "\n"
			+ "	pxor	" + opta + "," + optb + "\n"
			);
		}else if(str.find("vaesenclast") != string::npos){
			string opta = str.substr(str.find(',') + 1, str.find_last_of(',') - str.find(',') - 1);
			string optb = str.substr(str.find_last_of(',') + 1);
			s.push(
			"	pxor	" + opta + "," + optb + "\n"
			+ "	aesdeclast	" + opta + "," + optb + "\n"
			+ "	pxor	" + opta + "," + optb + "\n"
			);
		}else if(str.find("vaesenc") != string::npos){
			string opta = str.substr(str.find(',') + 1, str.find_last_of(',') - str.find(',') - 1);
			string optb = str.substr(str.find_last_of(',') + 1);
			s.push(
			"	pxor	" + opta + "," + optb + "\n"
			+ "	aesimc	" + opta + "," + opta + "\n"
			+ "	aesdeclast	" + opta + "," + optb + "\n"
			+ "	pxor	" + opta + "," + optb + "\n"
			);
		}else if(str.find("vpshufd") != string::npos){
			for(auto x : pr) if(str.find(x.first) != string::npos){
				str.replace(str.find(x.first), x.first.length(), x.second);
				break;
			}
			s.push(str + "\n");
		}else if(str.find("vpaddq") != string::npos){
			str.replace(str.find("add"), 3, "sub");
			string opt0 = str.substr(8, str.find(',') - 8);
			string opta = str.substr(str.find(',') + 1, str.find_last_of(',') - str.find(',') - 1);
			string optb = str.substr(str.find_last_of(',') + 1);
			if(opt0 != opta){
				str.replace(str.find(',') + 1, str.find_last_of(',') - str.find(',') - 1, optb);
				str.replace(str.find_last_of(',') + 1, str.length() - str.find_last_of(',') - 1, opta);
			}
			s.push(str + "\n");
		}else if(str.find("vpsubq") != string::npos){
			str.replace(str.find("sub"), 3, "add");
			s.push(str + "\n");
		}else cout << "ERROR " << line << endl;
	}
	freopen("reversework.asm", "w", stdout);
	while(!s.empty()){
		cout << s.top();
		s.pop();
	}
	return 0;
}

步骤五

将 Add 的结果和 Reverse 的结果拼接起来,即可获得一套完整的加解密回路。

posted @ 2021-11-26 17:14  SpaceJellyfish  阅读(209)  评论(0)    收藏  举报