#include <Windows.h>
#include <cstdio>
#define Hardware_hookaddr 0x1400015C5
#define Software_hookaddr 0x14000165B
#define RIP_OFFSET 46 // the true address offset -3
char key[] = "ILoveTim1lh!";
char cipher[] = "Y25zc3tUaGlzX2lzX2FfZmFrZV9mbGFnLlRyeV9IYXJkZXIufQ==";
char alpha[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
char flag[62];
// cnss{ae67c65e-dd78-44fa-9bd9-23fb6700c902-Br4kPo1nt}
size_t NTAPI _scanf(EXCEPTION_POINTERS *ExceptionInfo) {
    if ((size_t)ExceptionInfo->ExceptionRecord->ExceptionAddress ==
        Hardware_hookaddr) {
        key[5] = 'S';
        key[6] = 'h';
        key[7] = '1';
        key[8] = 'N';
        key[9] = '0';
        key[10] = '?';
        ExceptionInfo->ContextRecord->Rip += RIP_OFFSET;
        return EXCEPTION_CONTINUE_EXECUTION;
    } else if ((size_t)ExceptionInfo->ExceptionRecord->ExceptionAddress ==
               Software_hookaddr) {
        char secrets[] = {33,  41, 23,  14,  21,  57, 6,  12,  114, 88,  2,
                          31,  39, 106, 0,   25,  89, 96, 78,  14,  113, 93,
                          85,  7,  123, 37,  0,   68, 67, 106, 80,  92,  39,
                          13,  3,  26,  114, 36,  93, 77, 92,  117, 33,  72,
                          113, 80, 100, 69,  115, 41, 16, 0};
        for (int i = 0; i < 52; ++i)
            cipher[i] = secrets[i] ^ 11;
        ExceptionInfo->ContextRecord->Rip += 1;
        return EXCEPTION_CONTINUE_EXECUTION;
    } else {
        return EXCEPTION_CONTINUE_SEARCH;
    }
}
class magic {
  public:
    magic(void) {
        AddVectoredExceptionHandler(1, (PVECTORED_EXCEPTION_HANDLER)_scanf);
        CONTEXT ctx;
        ctx.ContextFlags = CONTEXT_ALL;
        GetThreadContext(GetCurrentThread(), &ctx);
        ctx.Dr0 = Hardware_hookaddr;
        ctx.Dr7 = 0x405;
        SetThreadContext(GetCurrentThread(), &ctx);
    }
};
magic a;
void base64(char *flag) {
    for (int i = 0; i < 52; ++i) {
        flag[i] = alpha[flag[i]];
        flag[i] ^= key[i % 12];
    }
    __asm__ __volatile__("int $3");
}
int main() {
    puts("Input your flag:");
    scanf("%52s", flag);
    if (strlen(flag) != 52) {
        puts("Wrong flag. Try again.");
        return 0;
    }
    base64(flag);
    if (!memcmp(flag, cipher, 52)) {
        puts("Correct flag. Congratulations!");
    } else {
        puts("Wrong flag. Try again.");
    }
}

下面是旧的 WP 要把那个 Alpha 去掉

直接复制粘贴反汇编程序

#include <iostream>
#include <cstring>
#include <cassert>
 
char flag[62] = {0};
 
char cipher[95] = "Y25zc3tUaGlzX2lzX2FfZmFrZV9mbGFnLlRyeV9IYXJkZXIufQ==";
 
char key[32] = "ILoveTim1lh!";
 
char alpha[127] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 
char fuckAlpha(char x);
 
int main() {
    char v2[55] = {0x2A, 0x22, 0x1C, 0x05, 0x1E, 0x32, 0x0D, 0x07, 0x79, 0x53, 0x09, 0x14, 0x2C, 0x61, 0x0B, 0x12,
                   0x52, 0x6B, 0x45, 0x05, 0x7A, 0x56, 0x5E, 0x0C, 0x70, 0x2E, 0x0B, 0x4F, 0x48, 0x61, 0x5B, 0x57,
                   0x2C, 0x06, 0x08, 0x11, 0x79, 0x2F, 0x56, 0x46, 0x57, 0x7E, 0x2A, 0x43, 0x7A, 0x5B, 0x6F, 0x4E,
                   0x78, 0x22, 0x1B, 0x0B};
    int i; // [rsp+3Ch] [rbp-4h]
 
 
    for (i = 0; i <= 51; ++i)
        cipher[i] = v2[i];
 
    key[5] = 83;
    key[6] = 104;
    key[7] = 49;
    key[8] = 78;
    key[9] = 48;
    key[10] = 63;
 
    for (i = 51; i >= 0; --i) {
        cipher[i] = (unsigned __int8) cipher[i] ^ key[i % 12];
        cipher[i] = fuckAlpha(cipher[i]);
    }
 
    std::cout << cipher;
    return 0;
}
 
char fuckAlpha(char x) {
    for (char i = 0; i < 127; ++i) {
        if (alpha[i] == x)return i;
    }
    return 0;//assert(false);
}
 

大概把流程逆过来实现了,但还是不对。。

容我分析一下程序先:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  _main();
  puts("Input your flag:");
  scanf("%52s", flag);
  if ( strlen(flag) == 52 )
  {
    base64(flag);
    if ( !memcmp(flag, cipher, 52ui64) )
      puts("Correct flag. Congratulations!");
    else
      puts("Wrong flag. Try again.");
    return 0;
  }
  else
  {
    puts("Wrong flag. Try again.");
    return 0;
  }
}

程序入口

_main() 函数初始化全局变量,包括 magic a;

这个 magic a 是一个伪装的包装异常处理函数的结构体

magic *__fastcall magic::magic(magic *this)
{
  HANDLE CurrentThread; // rax
  HANDLE v2; // rax
  _CONTEXT Context; // [rsp+20h] [rbp-60h] BYREF
 
  AddVectoredExceptionHandler(1u, (PVECTORED_EXCEPTION_HANDLER)_scanf);
  Context.ContextFlags = 1048607;
  CurrentThread = GetCurrentThread();
  GetThreadContext(CurrentThread, &Context);
  Context.Dr0 = 4200238i64;
  Context.Dr7 = 1029i64;
  v2 = GetCurrentThread();
  return (magic *)SetThreadContext(v2, &Context);
}

这是 magic 的构造函数吧

可以看到程序是用 VEH 处理异常,并注册了 _scanf() 这个处理异常的函数

__int64 __fastcall _scanf(struct _EXCEPTION_POINTERS *ExceptionInfo)
{
  char v2[12]; // [rsp+0h] [rbp-40h] BYREF
  char v3[8]; // [rsp+Ch] [rbp-34h] BYREF
  char v4[40]; // [rsp+14h] [rbp-2Ch] BYREF
  int i; // [rsp+3Ch] [rbp-4h]
 
  if ( ExceptionInfo->ExceptionRecord->ExceptionAddress == &loc_40172E )
  {
    key[5] = 83;
    key[6] = 104;
    key[7] = 49;
    key[8] = 78;
    key[9] = 48;
    key[10] = 63;
    ExceptionInfo->ContextRecord->Rip += 49i64;
    return -1i64;
  }
  else if ( ExceptionInfo->ExceptionRecord->ExceptionAddress == &loc_4017C2 )
  {
    qmemcpy(v2, "!)", 2);
    v2[2] = 23;
    v2[3] = 14;
    v2[4] = 21;
    v2[5] = 57;
    v2[6] = 6;
    v2[7] = 12;
    v2[8] = 114;
    v2[9] = 88;
    v2[10] = 2;
    v2[11] = 31;
    strcpy(v3, "'j");
    v3[3] = 25;
    v3[4] = 89;
    v3[5] = 96;
    v3[6] = 78;
    v3[7] = 14;
    strcpy(v4, "q]U\a{%");
    v4[7] = 68;
    v4[8] = 67;
    v4[9] = 106;
    v4[10] = 80;
    v4[11] = 92;
    v4[12] = 39;
    v4[13] = 13;
    v4[14] = 3;
    v4[15] = 26;
    v4[16] = 114;
    v4[17] = 36;
    v4[18] = 93;
    v4[19] = 77;
    v4[20] = 92;
    v4[21] = 117;
    v4[22] = 33;
    v4[23] = 72;
    v4[24] = 113;
    v4[25] = 80;
    v4[26] = 100;
    v4[27] = 69;
    v4[28] = 115;
    v4[29] = 41;
    v4[30] = 16;
    v4[31] = 0;
    for ( i = 0; i <= 51; ++i )
      cipher[i] = v2[i] ^ 0xB;
    ++ExceptionInfo->ContextRecord->Rip;
    return -1i64;
  }
  else
  {
    ExceptionInfo->ContextRecord->Dr0 = 0x40172Ei64;
    ExceptionInfo->ContextRecord->Dr7 = 0x405i64;
    return 0i64;
  }
}

这是 _scanf() 函数,它在系统抛出错误之后当调试器不处理之后他就来处理错误,可以看到程序判断了发生错误的位置和相应修改内存的代码

第一个位置 loc_40172E

loc_40172E:                             ; CODE XREF: base64(char *)+A6↓j
.text:000000000040172E                 mov     eax, [rbp+var_4]
.text:0000000000401731                 movsxd  rdx, eax        ; Move with Sign-Extend Doubleword
.text:0000000000401734                 mov     rax, [rbp+arg_0]
.text:0000000000401738                 lea     rcx, [rdx+rax]  ; Load Effective 
 
......
 
 
.text:00000000004017AE                 xor     eax, r9d        ; Logical Exclusive OR
.text:00000000004017B1                 mov     [r8], al
.text:00000000004017B4                 add     [rbp+var_4], 1  ; Add
.text:00000000004017B8
.text:00000000004017B8 
 
loc_4017B8:                             ; CODE XREF: base64(char *)+13↑j
.text:00000000004017B8                 cmp     [rbp+var_4], 33h ; '3' ; Compare Two Operands
.text:00000000004017BC                 jle     loc_40172E      ; Jump if Less or Equal (ZF=1 | SF!=OF)
.tex

反汇编后在这里:

for ( i = 0; i <= 51; ++i )
  {//<<-------------------------------进入循环内部的地方
    input[i] = alpha[input[i]];
    now = (unsigned __int8)input[i];
    key_index = i % 12;
    result = now ^ (unsigned int)(unsigned __int8)key[key_index];
    input[i] = now ^ key[key_index];
  }

可知在进入循环的时候可能会报错,但是报错的原因还不知道

好了现在知道了,这里有一个物理断点存在,一开始就在 magic 里设置了

AddVectoredExceptionHandler(1u, (PVECTORED_EXCEPTION_HANDLER)_scanf);
Context.ContextFlags = 1048607;
CurrentThread = GetCurrentThread();
GetThreadContext(CurrentThread, &Context);
Context.Dr0 = 0x40172Ei64;//中断地址
Context.Dr7 = 0x405i64;//其他信息
v2 = GetCurrentThread();
return (magic *)SetThreadContext(v2, &Context);

初始化线程的寄存器,DR0-DR3 为设置断点的地址,DR7 保存了断点是否启用、断点类型和长度等信息。

看看 DR7 存 405 意味着什么

img

0x405 == 0b0100 0000 0101

也就是 L0 = 1 L1 = 1 其他为 0,也就是开启了 DR0 和 1 的硬件断点,可是明明前面只有开了一个 Dr0, Dr1 有没有指定地址呢?

好吧,问题不在这,没设置就是没设置

问题在于

if ( ExceptionInfo->ExceptionRecord->ExceptionAddress == &loc_40172E )
  {
    key[5] = 83;
    key[6] = 104;
    key[7] = 49;
    key[8] = 78;
    key[9] = 48;
    key[10] = 63;
    ExceptionInfo->ContextRecord->Rip += 49i64;
    return -1i64;
  }

Rip += 49i64 通过让 IP 移动来达到异常产生后跳过一部分代码的功能,通过对 DX 跟踪找到最终要跳转的地址 40175F

也就是这里:

for ( i = 0; i <= 51; ++i ){
    input[i] = alpha[input[i]];
    now = (unsigned __int8)input[i];
    key_index = i % 12;
    result = now ^ (unsigned int)(unsigned __int8)key[key_index];
    input[i] = now ^ key[key_index];//<<-------------------------------跳到这里了
}

所以说循环变成了这样:

for ( i = 0; i <= 51; ++i ){
    key[5] = 83;
    key[6] = 104;
    key[7] = 49;
    key[8] = 78;
    key[9] = 48;
    key[10] = 63;
    input[i] = now ^ key[key_index];
}

第二个位置 loc_4017C2

loc_4017C2:                             ; Trap to Debugger
.text:00000000004017C2 018 CC                                              int     3
.text:00000000004017C3 018 48 83 C4 10                                     add     rsp, 10h                        ; Add
.text:00000000004017C7 008 5D                                              pop     rbp
.text:00000000004017C8 000 C3                                              retn                                    ; Return Near from Procedure
.text:00000000004017C8                                                     _Z6base64Pc endp

可以很清楚的知道这里用 int 3 打了一个软中断,会把这个识别成断点。

因为调试器的原理就是运用异常的处理机制来运作的,所以这个 int 3 会引发异常也是肯定的,如果不是在调试状态下的话,程序一定会进入 _snanf() 里面,并进行对 cipher 的修改

总之 主函数的其他函数基本都没有什么问题,就是这个 base64 () 内有玄机,运用中断的异常和赋值操作的异常来调用一些一般不会起作用的后门函数来起到迷惑的作用

所以基于这种逻辑,可以通过逆向来把异常修复的函数显式的调用之后再把整个过程从 cipher 逆过来反推到 flag