首页
社区
课程
招聘
[原创] 第三题 寻踪觅源 WP
2020-4-18 09:03 3325

[原创] 第三题 寻踪觅源 WP

2020-4-18 09:03
3325

注意:本篇 wp 不涉及对题目校验算法的完整分析,请关注我正在抱着的大腿以获得最佳题解。

 

题目用 quickjs 实现,本人由于过菜,并不会逆其虚拟机字节码。好在作者并没有丧心病狂到将符号去除,通过把在 main 函数内被注册了的 js 函数中看着嫌疑比较大的都稍微下断点调一调跑一跑跟一跟,我们可以发现校验算法与 quickjs 的大整数运算相关。这样一来我将关注重点放在了 BigInt(Float) 的相关函数上,例如 bf_mul / bf_cmp 等等,关注程序在哪些地方、以何种顺序调用了这些函数。

 

有了源码之后我便失去了在 IDA 中打标记的动力,因为可以直接在 quickjs 库中找到 bf_mul / bf_cmp 的声明:

int bf_cmpu(const bf_t *a, const bf_t *b){...}
int bf_mul(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec,
           bf_flags_t flags){...}

可以发现这些函数操作对象是 bf_t 数据类型,它的结构如下:

typedef struct {
    struct bf_context_t *ctx;
    int sign;
    slimb_t expn;
    limb_t len;
    limb_t *tab;
} bf_t;

它是以 hexadecimal floating number(这瞎编的名字是什么?)的形式存储的,limb_t 在我的机器上其实就是一个 DWORD,存的是十六进制的小数有效值部分,len 是 tab 数组的长度。既然打算去跟踪这些函数调用,自然会想到在每一次调用的时候输出调用的参数(比方说打印出 mul 、cmp 的两个操作数),因为已经清楚 bf_t 的结构,我们已经可以直接打印出这个 bf_t。

 

因为懒惰,写代码之前我在源码中报着期望的目光一瞧瞧二看看,果然在 libbf.c 里我发现了:

/* for debugging */
void bf_print_str(const char *str, const bf_t *a)
{
    ...

于是乎写了个调试器,hook 了 bf_mul / bf_cmp ,顺便把 quickjs 的源码拉进来用,在被 hook 函数 hit 的时候输出相应的调用参数:

#include <Windows.h>
#include <iostream>
#include "libbf.h"

void Bp(LPVOID BpAddr);
void bf_print_str_(const bf_t *a);
bf_t *get_bf_t_from_remote_memory(PVOID addr);
void destroy_bf_t(bf_t *n);

#define ProcessNameToDebug L"F:\\OrzRiatreWhileEatingPizza\\lelfei_fix.exe"

STARTUPINFO si = {0};
PROCESS_INFORMATION pi = {0};
BYTE orgs[100] = {0};

void ProcessDebugEvent(DEBUG_EVENT *dbg_event, DWORD *dbg_status) {
    switch (dbg_event->dwDebugEventCode) {
        case EXIT_THREAD_DEBUG_EVENT: {
            printf("The thread %d exited with code: %d\n", dbg_event->dwThreadId,
                   dbg_event->u.ExitThread.dwExitCode);
            *dbg_status = DBG_CONTINUE;
            break;
        }
        case CREATE_THREAD_DEBUG_EVENT: {
            printf("Thread 0x%x (Id: %d) created at: 0x%x\n",
                   dbg_event->u.CreateThread.hThread, dbg_event->dwThreadId,
                   dbg_event->u.CreateThread.lpStartAddress);
            *dbg_status = DBG_CONTINUE;
            break;
        }
        case EXCEPTION_DEBUG_EVENT: {
            EXCEPTION_DEBUG_INFO &exception = dbg_event->u.Exception;
            PVOID &addr = exception.ExceptionRecord.ExceptionAddress;
            switch (exception.ExceptionRecord.ExceptionCode) {
                case STATUS_BREAKPOINT:  // Same value as EXCEPTION_BREAKPOINT
                    CONTEXT lcContext;
                    lcContext.ContextFlags = CONTEXT_ALL;
                    GetThreadContext(pi.hThread, &lcContext);

                    if (addr == (PVOID)(0x0042A3D0 - 5)) {
                        lcContext.Eip += 6;
                        SetThreadContext(pi.hThread, &lcContext);

                        PVOID a = (PVOID)(lcContext.Ecx);
                        PVOID b = (PVOID)(lcContext.Edx);
                        bf_t *n1 = get_bf_t_from_remote_memory(a);
                        bf_t *n2 = get_bf_t_from_remote_memory(b);
                        bf_print_str_(n1);
                        printf(" * ");
                        bf_print_str_(n2);
                        printf(";\n");
                        destroy_bf_t(n1);
                        destroy_bf_t(n2);
                    }
                    if (addr == (PVOID)(0x00434210 - 5)) {
                        lcContext.Eip += 6;
                        SetThreadContext(pi.hThread, &lcContext);

                        PVOID a = (PVOID)(lcContext.Eax);
                        PVOID b = (PVOID)(lcContext.Edx);
                        bf_t *n1 = get_bf_t_from_remote_memory(a);
                        bf_t *n2 = get_bf_t_from_remote_memory(b);
                        printf("cmp ");
                        bf_print_str_(n1);
                        printf(" == ");
                        bf_print_str_(n2);
                        printf("\n");
                        destroy_bf_t(n1);
                        destroy_bf_t(n2);
                    }

                    *dbg_status = DBG_CONTINUE;
                    break;
                default:
                    printf("Unhandled exception at %p.\n", addr);
                    *dbg_status = DBG_EXCEPTION_NOT_HANDLED;
            }
        }
        default:
            *dbg_status = DBG_CONTINUE;
    }
    return;
}

bf_t *get_bf_t_from_remote_memory(PVOID addr) {
    bf_t *tmp = new bf_t;
    ReadProcessMemory(pi.hProcess, addr, tmp, sizeof(bf_t), NULL);
    limb_t *tmp_limb_t = new limb_t(tmp->len);
    ReadProcessMemory(pi.hProcess, tmp->tab, tmp_limb_t,
                      tmp->len * sizeof(limb_t), NULL);
    tmp->tab = tmp_limb_t;
    return tmp;
}

void destroy_bf_t(bf_t *n) {
    // 这里写的有 bug(程序会崩溃),但既然这只是个 dirty script,
    // 并且我也懒得找是哪儿写崩了,那么我们就直接砍掉这个函数吧
    return;

    delete n->tab;
    n->tab = nullptr;
    delete n;
    n = nullptr;
}

void bf_print_str_(const bf_t *a) {
    slimb_t i;

    if (a->expn == BF_EXP_NAN) {
        printf("NaN");
    } else {
        if (a->sign)
            putchar('-');
        if (a->expn == BF_EXP_ZERO) {
            putchar('0');
        } else if (a->expn == BF_EXP_INF) {
            printf("Inf");
        } else {
            printf("0x0.");
            for (i = a->len - 1; i >= 0; i--)
                printf("%08x", a->tab[i]);
            printf("p%d", a->expn);
        }
    }
}

void Bp(LPVOID BpAddr) {
    BYTE INT3 = 0xcc;
    BYTE trampo[7] = {0, 0, 0xcc, 0x90, 0x90, 0x90, 0x90};
    BYTE jmp[2] = {0xeb, 0xf7};

    ReadProcessMemory(pi.hProcess, BpAddr, trampo, 2, NULL);  // move
    WriteProcessMemory(pi.hProcess, BpAddr, jmp, sizeof(jmp), NULL);
    WriteProcessMemory(pi.hProcess, (LPVOID)((int)BpAddr - 7), trampo,
                       sizeof(trampo), NULL);
    FlushInstructionCache(pi.hProcess, (LPVOID)((int)BpAddr - 7), 9);
}

void SetBreakPoints() {
    Bp((LPVOID)0x00434210);
    Bp((LPVOID)0x0042A3D0);
}

void CreateDebuggee() {
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));
    CreateProcess(ProcessNameToDebug, NULL, NULL, NULL, FALSE,
                  DEBUG_ONLY_THIS_PROCESS, NULL, NULL, &si, &pi);
}

int main() {
    CreateDebuggee();
    SetBreakPoints();
    DEBUG_EVENT debug_event = {0};
    for (;;) {
        if (!WaitForDebugEvent(&debug_event, INFINITE))
            return 0;
        DWORD dbg_status;
        ProcessDebugEvent(&debug_event, &dbg_status);
        ContinueDebugEvent(debug_event.dwProcessId, debug_event.dwThreadId,
                           dbg_status);
    }
}

值得夸奖的是 binary 里面这些函数的头部都留有类似 lea edi, [edi+0] 这样又长又没用的废话指令方便我们进行 hook。

 

把给的 UserName / SN 拿来跑跑:

0x0.ac000000p6 * 0;
0x0.ac000000p6 * 0x0.d4000000p6;
0x0.ac000000p6 * 0x0.92800000p12;
0x0.ac000000p6 * 0x0.c4fe0000p17;
0x0.ac000000p6 * 0x0.845b2a00p23;
0x0.ac000000p6 * 0x0.b1da84a0p28;
0x0.ac000000p6 * 0x0.eefda25780000000p33;
0x0.ac000000p6 * 0x0.a092691354000000p39;
0x0.ac000000p6 * 0x0.d7c4bd31fd000000p44;
0x0.ac000000p6 * 0x0.90f82f1d960d8000p50;
0x0.ac000000p6 * 0x0.c2cd7f4fc1a28c00p55;
0x0.ac000000p6 * 0x0.82e2118996193820p61;
0x0.ac000000p6 * 0x0.afdfc790e1b1e378p66;
0x0.ac000000p6 * 0x0.ec54b42aaf4709a9c2000000p71;
0x0.ac000000p6 * 0x0.9ec8e90cadc3ba7e10180000p77;
0x0.ac000000p6 * 0x0.d55df929097f029965b08000p82;
0x0.fe000000p7 * 0x0.907c1b9ed001586ff0330000p81;
0x0.c8000000p7 * 0;
0x0.c8000000p7 * 0x0.80000000p1;
0x0.c8000000p7 * 0x0.ad000000p8;
0x0.c8000000p7 * 0x0.87640000p15;
0x0.c8000000p7 * 0x0.d38e5800p21;
0x0.c8000000p7 * 0x0.a54739c0p28;
0x0.c8000000p7 * 0x0.811fa52620000000p35;
0x0.c8000000p7 * 0x0.c9c1720ba6000000p41;
0x0.c8000000p7 * 0x0.9d9f211919ba0000p48;
0x0.c8000000p7 * 0x0.f648a3b738330000p54;
0x0.c8000000p7 * 0x0.c068bfe723e7d8e8p61;
0x0.c8000000p7 * 0x0.9651d5ec940d2177f0000000p68;
0x0.c8000000p7 * 0x0.eadfde41a754844b78800000p74;
0x0.c8000000p7 * 0x0.b77ee5a34aba075af62c8000p81;
cmp 0x0.8f5b2367926155bf1052ca00p88 == 0x0.8f5b2367926155bf1052ca00p88

经过几轮观察,猜测 UserName 的作用除了计算出一个 BigInt1 之外还对某种形式的表进行了搅乱,这个表用来将 SN 的每 2 个 bytes 置换为一个 100 内的十进制数,然后这些十进制数会被不断拼接成一个 BigInt2,进而与 BigInt1 进行比较。于是我在 UserName 固定时将验证算法简单地抽象成了 assert(f(UserName)==g(SN)),其中 f、g 互不相干,固定 UserName 为 KCTF 后可以得到 BigInt1 为:

0x0.c95140ea9f2e45d83f013800p88

现在需要将这个数转换为十进制,Python 直接处理这种数据会丢失精度,因才疏学浅不了解其它更好的方法,我手糊了一个脚本用来生成算式:

def gen_exp(h):
    # h: 0x0.xxxxxxxxxpxxx
    h = h[4:]
    significand, exp = h.split('p')
    exp = int(exp)
    s_sum = '('
    for i in range(len(significand)):
        s_sum += str(int(significand[i], 16)) + f'/({16**(i+1)})+'
    s_sum = s_sum[:-1] + f')*({2**exp})'
    print(s_sum)

gen_exp('0x0.c95140ea9f2e45d83f013800p88')

然后把输出丢给科学家们用的 Mathematica 求解:

In[0] = (12/(16) + 9/(256) + 5/(4096) + 1/(65536) + 4/(1048576) + 
   0/(16777216) + 14/(268435456) + 10/(4294967296) + 
   9/(68719476736) + 15/(1099511627776) + 2/(17592186044416) + 
   14/(281474976710656) + 4/(4503599627370496) + 
   5/(72057594037927936) + 13/(1152921504606846976) + 
   8/(18446744073709551616) + 3/(295147905179352825856) + 
   15/(4722366482869645213696) + 0/(75557863725914323419136) + 
   1/(1208925819614629174706176) + 3/(19342813113834066795298816) + 
   8/(309485009821345068724781056) + 
   0/(4951760157141521099596496896) + 
   0/(79228162514264337593543950336))*(309485009821345068724781056)

Out[0] = 243377798925556026477314360

简单地划分一下这个结果得到:02 43 37 77 98 92 55 56 02 64 77 31 43 60

 

接下来便是跑表的过程了,同样因为没有什么好办法,我开始了手动跑表,跑了一会儿发现在每个段(用 --- 隔开的部分)内 in-out 的映射关系是有规律的:

in out
------
d0 92
d1 93
d2 90
d3 91
d4 96
d5 97
d6 94
d7 95
d8 error
d9 error
da 98
db 99
---
70 32
71 33
72 30
73 31
74 36
75 37
76 34
77 35
78 error
79 error
7a 38
7b 39

于是后续阶段里每个段只需要跑一个结果即可推出剩余的部分。

 

得到表之后,把 02 43 37 77 98 92 55 56 02 64 77 31 43 60 映射回来,即为本题的 flag: 40017535dad01714402635730122


 

PS. 我在手动跑表的过程中得知附件被换掉了,内心顿时受到 1w+ 的暴击伤害并直接回到了泉水(指床)开始了为期一晚的挂机疗养,后经美味 pizza 与强壮大腿的滋润才得以康复。


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

最后于 2020-4-18 23:46 被yypEx编辑 ,原因: pizzatql
收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回