首页
社区
课程
招聘
[原创]kctf2024题目提交-智慧中心登录器
发表于: 2024-6-13 11:59 1646

[原创]kctf2024题目提交-智慧中心登录器

2024-6-13 11:59
1646

公开的用户名/密码:
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脚本:

#include<iostream>
#include<windows.h>
#define getpos(X,N) (((X)>>((N)<<3))&0xFF)
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;
}
#include<iostream>
#include<windows.h>
#define getpos(X,N) (((X)>>((N)<<3))&0xFF)
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));

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2024-8-28 13:33 被kanxue编辑 ,原因:
上传的附件:
收藏
免费 0
支持
分享
最新回复 (2)
游客
登录 | 注册 方可回帖
返回
//