[DubheCTF 2024]Destination
本文最后更新于19 天前,其中的信息可能已经过时,如有错误可以直接在文章下留言

题目描述

//人死后会去向何方?

大一时候的一道题,当时学长发了天堂之门的相关文章给我,没有复现,回来再复现这道题,发现可以学到蛮多知识的,关于异常处理和天堂之门,最近也是遇到了其他异常处理的题目,发现自己也的确不是很熟悉。

DubheCTF-Destination&Moon | Clovershrub

DubheCTF 2024 Reverse – lrhtony 的小站

DUBHEctf_Destination | fishjump’s blog

题目描述和题目本身其实已经提示了天堂之门的考点

主函数直接下断点调试会直接跑飞,但是主函数里面翻了一下没有反调试,我们就在主函数执行之前的下断点,发现程序运行到0x418279的j__initterm函数时会退出,那这个就是反调试了,搜索相关资料可以发现该函数接受两个指针,指向一个函数指针数组的起始和结束位置,然后依次执行数组中每个非空函数指针指向的函数。

从0x41E000到0x41E318,数组很大,但是大部分是NULL,先出现?pre_cpp_initialization@@YAXXZ,后续再出现三个反调试的相关函数。

出题人给了源码,反调试写的挺复杂的,绕过方法很简单,把j__initterm函数nop或者地址区间的值全部改成0就行。

然后进入主函数,主函数反编译代码很简单

  for ( i = 0; i < 45; ++i )
    *((_BYTE *)&dword_4234A8 + i) = getchar();
  __debugbreak();
  for ( j = 0; j < 48; ++j )
  {
    if ( *((unsigned __int8 *)&dword_4234A8 + j) == (unsigned __int8)byte_423038[j] )
      ++v6;
  }
  if ( v6 == 48 )
    sub_411122("Good answer! You will be taken to HEAVEN!\n", v4);
  else
    sub_411122("Bad answer! You will be taken to HELL!\n", v4);
  system("PAUSE");
  return 0;

只有输入和判断部分,但是运行会发现,最后判断时输入会被加密,并且这里布过 __debugbreak()函数会触发一个软件断点异常。

那么就需要看汇编界面了,这里发现了一个int 3指令触发了异常,并且注册了异常处理的函数

这里的sub_4113D4其实就是调用sub_4140D7函数,所以两者等同,这里触发异常后我们在sub_4140D7打个断点进入sub_4140D7函数,然后就发现函数有很多花指令。

参考别人文章,也利用IDA的Instruction tracing功能跟踪一下程序

该函数有很多循环,,高亮大部分执行的指令后,发现花指令主要包含两种,并且有些分支会连在一起

第一种是call和retn组成的无用花指令,第二种就是永真跳转,手动nop的话有点麻烦,我们可以看看出题人给的idc去花脚本

#include <idc.idc>
static main()
{
    auto last_eip;
    auto eip = 0x4140D7;
    auto offset;
    auto sum = 1;
    auto just_jump = 0;
    while(Byte(eip) != 0xC3) //遇到retn指令停止
    {
        if(Byte(eip) == 0xE8 && Byte(eip+1) == 0x00 && Byte(eip+1) == 0x00 && Byte(eip+1) == 0x00 && Byte(eip+1) == 0x00)
        {   //当遇到无效call花指令
            PatchByte(eip + 0, 0x90);
            PatchByte(eip + 1, 0x90);
            PatchByte(eip + 2, 0x90);
            PatchByte(eip + 3, 0x90);
            PatchByte(eip + 4, 0x90);
            PatchByte(eip + 5, 0x90);
            PatchByte(eip + 6, 0x90);
            PatchByte(eip + 7, 0x90);
            PatchByte(eip + 8, 0x90);
            PatchByte(eip + 9, 0x90);
            //去除call和retn组成的花指令
            eip = eip + 10;
            continue;
        }
        else if(Byte(eip) == 0xE9 && just_jump == 0)
        {   //当遇到长跳转jmp指令
            offset = Byte(eip+1);
            offset = Byte(eip+2) * 256 + offset;
            offset = Byte(eip+3) * 256 * 256 + offset;
            offset = Byte(eip+4) * 256 * 256 * 256 + offset;
            eip = eip + offset;
            eip = eip + 5;
            eip = eip & 0xffffffff;
            print(eip);
            //根据opcode计算跳转地址
            sum = sum + 1;
            just_jump = 1;
            continue;
        }
        else if(Byte(eip) == 0x0F && Byte(eip+1) == 0x84 && just_jump == 0)
        {   //当遇到永恒跳转花指令
            last_eip = eip;
            offset = Byte(eip+2);
            offset = Byte(eip+3) * 256 + offset;
            offset = Byte(eip+4) * 256 * 256 + offset;
            offset = Byte(eip+5) * 256 * 256 * 256 + offset;
            eip = eip + offset;
            eip = eip + 6;
            eip = eip & 0xffffffff;
            //根据opcode计算跳转地址
            print(eip);
            sum = sum + 1;
            just_jump = 1;
            PatchByte(last_eip, 0x90);
            PatchByte(last_eip+1, 0xE9);
            //将永恒跳转花指令替换为jmp
            continue;
        }
        else if(Byte(eip) == 0xEB && just_jump == 0)
        {   //当遇到短跳转jmp指令
            offset = Byte(eip+1);
            eip = eip + offset;
            eip = eip + 2;
            // //根据opcode计算跳转地址
            print(eip);
            sum = sum + 1;
            just_jump = 1;
            continue;
        }
        else if(Byte(eip) == 0x74 && just_jump == 0)
        {   //这里应该同理,但是翻了一下没找到这个对应花指令,去的差不多就行了
            last_eip = eip;
            offset = Byte(eip+1);
            eip = eip + offset;
            eip = eip + 2;
            print(eip);
            sum = sum + 1;
            just_jump = 1;
            PatchByte(last_eip, 0xEB);
            continue;
        }
        eip = eip + 1;
        just_jump = 0;
    }
    print(sum);
    return 0;
}

去玩花指令之后我就卡在这里了,应该不知道什么原因,没有恢复好函数,IDA里面无法反编译生成伪代码了,一开始以为是大量的垃圾数据的原因,因为出题人除了设置了以上提到的两种花指令之外,在每个代码块直接还夹杂了大量没用的数据,如图

把这些数据nop掉要花很多时间,所以已经怀疑这些数据并不用处理了,最后nop完之后还是反编译不出伪代码。

后面和学长讨论研究了一下,发现指令处有一些这样的情况,如图

这里的jmp指令没有被识别在函数的指令块当中,我们需要对着jmp指令按E,就可以让IDA把他包括在指令块当中。还有一些指令块实际被执行,但是却没有被识别属于sub_4140D7函数,一样对着按E就可以自动分析归属到sub_4140D7函数,可以反编译之后可能会有红色的JUMPOUT错误,这个错误会指示哪里还没修好。

修好后就可以得到伪代码了。

上面这种是我通过这道题学习到的常规方法,去掉比较明显的花指令,IDA再进行trace,将多余的指令和执行的指令分隔,然后一步步把函数恢复好,IDA反编译。

当然还有硬搓汇编的方法,因为对于这种运算特征比较鲜明的加密算法,直接拿x32dbg进行trace,得到执行过的汇编指令之后也不难看出是执行两次XXTEA加密,而且我不止在这道题看到过这样的方法,其他题目也见过,而且这种方法目前我见得还更多。顺便一提,x64dbg的trace似乎会比IDA的trace功能更准确些。

sub_4140D7函数代码如下

void sub_4140D7()
{
  unsigned int v1; // [esp-10Ch] [ebp-110h]
  unsigned int v2; // [esp-10Ch] [ebp-110h]
  unsigned int sum; // [esp-44h] [ebp-48h]
  unsigned int v4; // [esp-38h] [ebp-3Ch]
  int e; // [esp-20h] [ebp-24h]
  unsigned int i; // [esp-14h] [ebp-18h]
  int rounds; // [esp-8h] [ebp-Ch]

  rounds = 50;
  sum = 0;
  v4 = flag[11];
  do
  {
    sum -= 0x5B4B9F9E;
    e = (sum >> 2) & 3;
    for ( i = 0; i < 11; ++i )
    {
      v2 = (((v4 ^ key[e ^ i & 3]) + (flag_1[i] ^ sum)) ^ (((16 * v4) ^ (flag_1[i] >> 3)) + ((4 * flag_1[i]) ^ (v4 >> 5))))
         + flag[i];
      flag[i] = v2;
      v4 = v2;
    }
    v1 = (((v4 ^ key[e ^ i & 3]) + (flag[0] ^ sum)) ^ (((16 * v4) ^ (flag[0] >> 3)) + ((4 * flag[0]) ^ (v4 >> 5))))
       + flag[11];
    flag[11] = v1;
    v4 = v1;
  }
  while ( --rounds );
}

可以看出这是个XXTEA加密算法,然后这里动态调试会发现,这个函数会执行两次,出题人给了这个链接,说是异常处理的机制,但是反正我是没看懂,埋个坑,不过这个可以通过动调发现,所以不知道似乎也不影响。

Windows-SEH学习笔记(2) – 云之君’s Blog

这里发现我们输入经过两次XXTEA加密之后,执行后面的jmp far loc_4142A7指令后,数据又被加密

但是这里0x4142A7地址处的数据转成汇编指令并不是很对。

得知这里运用了天堂之门的的技术,通过修改CS寄存器实现32位到64位代码的切换,用于反调试

天堂之门(WoW64技术)总结及CTF中的分析-CTF对抗-看雪-安全社区|安全招聘|kanxue.com

[原创]天堂之门 (Heaven’s Gate) C语言实现-软件逆向-看雪-安全社区|安全招聘|kanxue.com

我们可以用x32dbg再打开程序

可以看到实际的指令其实是jmp far 0x33:413F77,这里将cs寄存器的修改为0x33从而让32位程序执行64位代码,0x413F77处的代码如下

void sub_413F60()
{
  int *v1; // ebp
  int v2; // ecx
  int v3; // eax
  unsigned int v4; // esi
  unsigned int v5; // edi
  int v6; // ecx
  unsigned int v7; // esi
  int savedregs; // [esp+CCh] [ebp+0h] BYREF

  v1 = &savedregs;
  v2 = 2;
  v3 = -858993461;
  while ( 1 )
  {
    v1 = (int *)((char *)v1 - 1);
    v4 = 0;
    do
    {
      v5 = v4 >> 31;
      v6 = v2 - 3;
      if ( v4 >> 31 == 1 )
        v7 = (2 * v4) ^ 0x84A6972F;
      else
        v7 = 2 * v4;
      v4 = v7 + 1;
      v2 = v6 - 2;
    }
    while ( v4 != 32 );
    *(_DWORD *)&flag[4 * v5] = 32;
    v3 -= 2;
    if ( v5 == 11 )
      __asm { retf }
  }
}

这里逻辑32位的IDA会识别有误,从而成功通过天堂之门的技术影响了IDA分析,比如汇编指令这里明明出现了两次flag变量,但是伪代码却只出现了一次。

我们这里把opcode dump出来然后再放到IDA64位当中,得到以下结果

void sub_0()
{
  __int64 flag; // rdi
  unsigned __int64 v1; // rsi
  __int64 i; // r14

  flag = 0i64;
  while ( 1 )
  {
    v1 = *(unsigned int *)(4 * flag + 0x4234A8);
    for ( i = 0i64; i != 32; ++i )
    {
      if ( v1 >> 31 == 1 )
        v1 = (2 * (_DWORD)v1) ^ 0x84A6972F;
      else
        v1 = (unsigned int)(2 * v1);
    }
    *(_DWORD *)(4 * flag++ + 0x4234A8) = v1;
    if ( flag == 12 )
      __asm { retfq }
  }
}

这里是判断数据符号位来决定是否进行异或,并且汇编指令最后

在跳转回0x4179F3的时候又将cs寄存器转为了0x23,此后继续执行32位代码。

那么解题脚本参考上面三位师傅

#include <iostream>
#include <cstring>
using namespace std;
#define DELTA 0xa4b46062
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))
int main()
{
    // unsigned char flag[100] = {67, 149, 84, 158, 72, 179, 124, 94, 47, 74, 168, 217, 222, 153, 235, 133, 132, 88, 130, 182, 161, 78, 247, 196, 138, 130, 177, 34, 150, 114, 13, 41, 115, 228, 142, 25, 41, 181, 85, 150, 106, 25, 172, 56, 54, 98, 43, 25};
    unsigned char flag[100] = { 214, 250, 144, 167, 119, 162, 200, 232, 250, 132, 3, 207, 215, 127, 108, 46, 139, 150, 51, 109, 39, 194, 87, 91, 94, 166, 60, 101, 252, 241, 198, 133, 119, 37, 243, 225, 118, 174, 215, 212, 196, 109, 175, 63, 140, 157, 89, 13 };
    unsigned int f;
    int i, j;
    for (i = 0; i < 12; i++)
    {
        f = *((unsigned int*)flag + i);
        for (j = 0; j < 32; j++)
        {
            if (f & 1)
            {
                f ^= 0x84a6972f;
                f >>= 1;
                f |= 0x80000000;
            }
            else
            {
                f >>= 1;
                f &= 0x7fffffff;
            }
        }
        *((unsigned int*)flag + i) = f;
    }
    unsigned int rounds, sum, e, p, y, z;
    unsigned int key[4] = { 0x6b0e7a6b, 0xd13011ee, 0xa7e12c6d, 0xc199aca6 };

    rounds = 0x32;
    sum = rounds * DELTA;
    y = ((unsigned int*)flag)[0];
    do
    {
        e = (sum >> 2) & 3;
        for (p = 11; p > 0; p--)
        {
            z = ((unsigned int*)flag)[p - 1];
            y = ((unsigned int*)flag)[p] -= MX;
        }
        z = ((unsigned int*)flag)[11];
        y = ((unsigned int*)flag)[0] -= MX;
        sum -= DELTA;
    } while (--rounds);

    rounds = 0x32;
    sum = rounds * DELTA;
    y = ((unsigned int*)flag)[0];
    do
    {
        e = (sum >> 2) & 3;
        for (p = 11; p > 0; p--)
        {
            z = ((unsigned int*)flag)[p - 1];
            y = ((unsigned int*)flag)[p] -= MX;
        }
        z = ((unsigned int*)flag)[11];
        y = ((unsigned int*)flag)[0] -= MX;
        sum -= DELTA;
    } while (--rounds);

    for (i = 0; i < 48; i++)
    {
        printf("%c", flag[i]);
    }
    return 0;
}
//输出DubheCTF{82e1e3f8-85fe469f-8499dd48-466a9d60}

所有flag就是DubheCTF{82e1e3f8-85fe469f-8499dd48-466a9d60}

这道题学习到很多知识,而且值得更加深入的去研究一下,比如这里的异常处理机制,还有天堂之门技术,我目前只是处于一个初步了解的状态。

文末附加内容

评论

  1. Bianca4180
    Windows Chrome
    17 小时前
    2025-4-25 23:55:08
  2. Nora1907
    Windows Chrome
    17 小时前
    2025-4-25 23:56:09

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇