Skip to content

Latest commit

 

History

History
174 lines (140 loc) · 6.9 KB

File metadata and controls

174 lines (140 loc) · 6.9 KB

Flag 计算机

此题目基于 https://github.com/skeeto/dosdefender-ld31 修改而成。

由于我太菜了,所以引入了一个 BUG 导致题目下线修改后上线。给大家带来了不便,十分抱歉。

这道题主要想展现的效果是在 DOS 和 Windows 上都能运行的程序。在 Windows 上运行将弹出一个对话框。

这和我们在 DOS 上运行 Windows PE 程序在 DOS 中输出"This program cannot be run in DOS mode"相照应。如果我们把程序载入 010editor就会发现一些猫腻。

我们会发现原来的 DOS stub 充满了代码。实际上我是先编译了一个 COM 可执行文件,然后手动为 COM 文件加上 DOS 头。所以在此过程中 DOS 中的一些值是手工进行设置的。 然后我将能够运行的 DOS 可执行文件设置为 visual studio 2019 中的 exe 的 DOS stub。最终编译成了你们现在看到的样子。

逆向分析

我们将它拖入ida

我们选择让 IDA 分析 MS-DOS executeble。 然后我们观察 start 函数。

我们发现它调用了一个函数,然后执行了 DOS exit 中断,IDA 已经将其标注了出来。

这里用了一个 CPU 指令前缀的编译方法,使得我们的 80386 能够在实模式使用 32 位操作数。0x66
:它属于 Prefix group 3,Operand-size override prefix。

有了这样一个 opcode 会有怎么样的行为呢?

可以看到 operand size 变为了 32bit。 我们分析 102f 这个函数。

可以看到使用了大量使用这个技巧的痕迹,这就是为什么我们能在实模式使用 32bit 寄存器的原因。

asm (".code16gcc\n"
     "call  dosmain\n"
     "mov   $0x4C,%ah\n"
     "int   $0x21\n");

最先展开的 include 项目中有我们的 init.h 。 这个文件中的一个伪指令 ".code16cc" 代表着让gcc生成 16bit realmode 模式的汇编指令。所以这是为什么大量指令前都有 0x66 的原因。当我们的操作数是类似于 uint32 声明时,0x66 就会起作用。 逆向的核心过程就是明白程序干了哪些事情,橙色的操作数代表着地址,我们将鼠标悬停在上面,然后右键把它类型转换一下。

IDA 就能识别字符串了。 ps: 作为一个CTF玩家,我最喜欢的事情就是按 F5 ,hackergame 是为了让人能学到东西,而不是千篇一律的难题,不是为了难而难。所以我程序在 realmode 下,IDA 失效了,这样大家都能从 “0” 接触汇编。 显而易见的是,参数是字符串的一般就是我们的类似的 printf 函数。只不过这里是 vga 的输出而已。

我们看整个控制流,发现程序并不复杂,大多数都是控制显示的逻辑。 大家可以对着源代码再看一遍,这个函数就是对应的 main 函数。

下面讲下程序的核心逻辑。

static uint32_t gen_random_a()
{
    a = get_time() % 58379;
    // a = 26141;
    x0 = 1103515245;
}

static uint32_t myrand(void)
{
    x0 = x0 * a + 12345678;
    return x0;
}
gen_random_a();
uint32_t rand_gen[15];
for(uint32_t i=0;i<15;i++)
{
    rand_gen[i] = myrand();
}
for(uint32_t i=0;i<15;i++)
{
    for(uint32_t j=0;j<15;j++)
    {
        get_matrix[i] += (my_matrix[i*15+j] * rand_gen[j]) & (0xffff);
        get_matrix[i] = get_matrix[i] & 0xffff;
    }
}
char out[40];
out[30] = '$';
out[31] = '$';
out[32] = '$';
out[33] = '$';
unsigned short aim[30]={221, 49078,12436, 39423, 44156, 25529, 22179,10906, 15839, 27165, 45705, 55062,58013, 7081, 14308, 136, 49064,12481, 39404, 44086, 25520, 22263,10929, 15818, 27144, 45774, 55045,58097, 7156, 14313};
for(short i=0; i<30;i++)
{
    out[i] = (char)(aim[i] ^ get_matrix[i%15]);
}

删除掉我们的不重要的 delay 逻辑和输出逻辑就剩下这些了。很明显初始化的时候会取一个 0~58379 之间的数字作为 a ,我在生成flag的时候使用的是 26141。然后使用线性同余发生器生成15个伪随机数。 下图是生成15个伪随机数的逻辑代码:

下图是生成目标异或数组的核心逻辑:

下图是 xor 常量的核心逻辑,就是这段在使用这个库的时候出了 bug 。

最后 dump 出常量,小于 2**16 的爆破量就可以将 flag 爆破出来。

下面给出的代码是生成 get_matrix[] 数组的代码

#include <iostream>
#include <cstdint>
using namespace std;
uint32_t a,x0,b;
static uint32_t gen_random_a()
{
    // a = get_time() % 58379;
    a = 26141;
    x0 = 1103515245;
}

static uint32_t myrand(void)
{
    x0 = x0 * a + 12345678;
    return x0;
}

uint32_t get_matrix[15];
uint32_t my_matrix[226]={20597,19141,29258,17804,29076,28746,24890,28979,26196,31833,26624,24774,18916,29028,24033,22913,23436,25750,26539,21652,31296,22446,16506,21949,22761,30221,29477,29617,16497,23022,23179,30781,23877,29171,31665,26534,32159,22583,27525,28708,31216,17158,31988,32190,23747,21272,21278,24727,29984,25303,23445,23119,23155,26346,26389,30747,28948,31418,21323,31758,30911,18790,21312,25099,22348,25409,29357,22180,23588,28794,18133,25624,21972,23401,24821,31369,25187,31517,19840,28836,20794,20239,24523,30814,24016,17954,21227,16691,30290,23391,20482,24822,31968,30651,27908,22690,30875,31003,31747,19978,25482,18563,30143,27788,26658,26295,23244,27086,26456,24251,28647,22783,27460,19187,23252,24078,19203,26251,18113,19542,24533,16666,24038,32744,28670,30438,26379,18591,30109,26509,20947,27696,22945,27542,32128,25416,21675,19389,27085,29380,20163,21102,30936,30862,18230,21904,16938,16579,20641,27551,22740,24666,16836,23306,27661,26506,28623,29816,20166,29405,23982,30046,19365,24926,19029,32448,17567,17156,18678,28594,19769,28631,25769,31309,24457,30625,21825,29811,17112,31370,25345,24333,24005,31606,30942,21441,30599,22894,18015,19994,27901,26868,21948,27614,23449,21289,19588,19955,28133,16696,31509,26219,19946,27895,28760,28547,28315,16614,26006,17129,24769,24608,17714,17682,18532,17597,29247,28789,27011,29841,32640,17508,27662,23548,29514};
int main() {
    gen_random_a();
    uint32_t rand_gen[15];
    for(uint32_t i=0;i<15;i++)
    {
        rand_gen[i] = myrand();
    }
    for(uint32_t i=0;i<15;i++)
    {
        for(uint32_t j=0;j<15;j++)
        {
            get_matrix[i] += (my_matrix[i*15+j] * rand_gen[j]) & (0xffff);
            get_matrix[i] = get_matrix[i] & 0xffff;
        }
    }
    for(int i = 0; i <15;i++)
    {
        cout << get_matrix[i] << " ";
    }
    return 0;
}

每次改变a的值然后和目标数组 xor

unsigned short aim[30]={221, 49078, 12436, 39423, 44156, 25529, 22179, 10906, 15839, 27165, 45705, 55062, 58013, 7081, 14308, 136, 49064, 12481, 39404, 44086, 25520, 22263, 10929, 15818, 27144, 45774, 55045, 58097, 7156, 14313};
for(short i=0; i<30;i++)
{
    out[i] = (char)(aim[i] ^ get_matrix[i%15]);
}

然后都是可见字符并且开头以"flag{"的就是flag。