公开的用户名/密码:
Username: 5D39642871C061E7
key: 9FF4D2D192B6081530E2C792605EBC939763C5681D4E2AF9366880D4A15B0553
题目答案:
Username: KCTF
Key: 901AB07A40D87A7B96ED4B0420EA2B552C9E12C71F9E967E5BEA37A83BC79E1F

题目名称:智慧中心登录器.
题目背景:大概是我高中的时候经常偷玩电脑,被智慧中心抓了好几次.今天我搞到了他们的登录程序,看看能不能登录学校官网后台.
我混淆器的思路是,准备一个context栈,每次调用指令的时候进入虚拟机,把栈迁移至虚拟空间.调用指令后,退出虚拟机,退出前,将下一条指令的key压入栈,在dispatcher函数中解密下一条指令的opcode,跳往执行下一条指令.进入函数第一时间保存所有寄存器,依照x64调用约定拷贝参数,全部寄存器入context栈,退出时恢复到上一个函数的context.对于jmp使用直接修改opcode,call则不进入虚拟机,执行完毕第一时间保存返回值并返回到dispatcher,handler象征性地加了一条花指令,每个handler在程序中都分配一块128字节的空间,handler指令的开始位置在空间里是随机的,随机后剩余空间填满一些假的指令bytes.就这么简单.明年上大学我会加入一些表达式混淆和api混淆.(这版本像个sb一样)
不算难解.之前比赛看到一题类似思路的,他也是一个tea算法,但是他没有经过dispatcher分发指令.
我给出一种可能解法:

这是保存寄存器至context.

这是计算函数ctx的地址并压入第一条指令的密钥.

拷贝参数,如果这是第一次进入vm,则初始化虚拟栈和ctx栈.

进入dispatcher.
dispatcher代码(汇编和c++混编):


解析版:

进入vm:

退出vm.

恢复符号位,执行原指令,备份符号位,push密钥,再跳往退出虚拟机.
一条一条指令跑下来,就可以恢复原程序(除了jmp和call),unicorn啥的都行.(菜鸡没搞指令替换,上大学再搞).恢复源程序后,分块解密32轮tea,每块解密两次即可(tea的key来源于sum,参数也有变,不过大同小异),解密脚本见附件.也可以硬看,不难.知道看雪巨佬连真正的vmp都能手撕,我这个小虾米又算啥..
keygen脚本:
const char* zx = "welcome_to_fzbz,my_name_is_sbzx!";
const char* sb = "0123456789ABCDEF";
void gen(BYTE* N,BYTE* O)
{
BYTE usn[17] = { 0 };
for (int i = 0; i < 16; i++)
{
usn[i] = N[i] ^ zx[i];
}
BYTE out[0x100] = { 0 };
for (int s = 0; s < 32; s += 8)
{
DWORD sum = *(DWORD*)(usn + 8);
DWORD delta = *(DWORD*)(usn + 12);
DWORD v0, v1;
v0 = *(DWORD*)(zx+s);
v1 = *(DWORD*)(zx + s+4);
for (int i = 0; i < 31; i++)
{
sum += delta;
}
v0 ^= sum;
v1 ^= sum;
for (int i = 0; i < 31; i++)
{
v1 -= ((v0 << 4) + getpos(sum, 2)) ^ (v0 + sum) ^ ((v0 >> 5) + getpos(sum, 1));
sum -= delta;
v0 -= ((v1 << 4) + getpos(sum, 3)) ^ (v1 + sum) ^ ((v1 >> 5) + getpos(sum, 0));
}
sum = *(DWORD*)usn;
delta = *(DWORD*)(usn + 4);
for (int i = 0; i < 31; i++)
{
sum += delta;
}
v0 ^= sum;
v1 ^= sum;
for (int i = 0; i < 31; i++)
{
v1 -= ((v0 << 6) + getpos(sum, 3)) ^ (v0 + sum) ^ ((v0 >> 3) + getpos(sum, 2));
sum -= delta;
v0 -= ((v1 << 6) + getpos(sum, 0)) ^ (v1 + sum) ^ ((v1 >> 3) + getpos(sum, 1));
}
*(DWORD*)(out + s) = v0;
*(DWORD*)(out + s + 4) = v1;
}
for (int i = 0; i < 32; i++)
{
BYTE M = out[i];
O[i * 2] = sb[M >> 4];
O[i * 2 + 1] = sb[M & 0xf];
}
}
//keygen
int main()
{
char out[0x100] = { 0 };
const char* username = "KCTF";
char h[17] = { 0 };
strcpy((char*)h, username);
gen((BYTE*)h, (BYTE*)out);
std::cout << out;
}
const char* zx = "welcome_to_fzbz,my_name_is_sbzx!";
const char* sb = "0123456789ABCDEF";
void gen(BYTE* N,BYTE* O)
{
BYTE usn[17] = { 0 };
for (int i = 0; i < 16; i++)
{
usn[i] = N[i] ^ zx[i];
}
BYTE out[0x100] = { 0 };
for (int s = 0; s < 32; s += 8)
{
DWORD sum = *(DWORD*)(usn + 8);
DWORD delta = *(DWORD*)(usn + 12);
DWORD v0, v1;
v0 = *(DWORD*)(zx+s);
v1 = *(DWORD*)(zx + s+4);
for (int i = 0; i < 31; i++)
{
sum += delta;
}
v0 ^= sum;
v1 ^= sum;
for (int i = 0; i < 31; i++)
{
v1 -= ((v0 << 4) + getpos(sum, 2)) ^ (v0 + sum) ^ ((v0 >> 5) + getpos(sum, 1));
[招生]科锐逆向工程师培训(2026年7月3日实地,远程教学同时开班, 第56期)!
最后于 2024-8-28 13:33
被kanxue编辑
,原因: