175 lines
6.9 KiB
Markdown
175 lines
6.9 KiB
Markdown
# Flag 计算机
|
||
此题目基于 [https://github.com/skeeto/dosdefender-ld31](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。
|