magic

这个题调得有点久23333

但是收获很大0v0

先看看

直接运行有警告(黑客做的东西一个都不能在实体机上打开.jpg

命令行运行试试

img1.png

看这时间就很有意思啊orz

看来还得获取正确的时间

工具

x64dbg & IDA

漫长的调试之路

img2.png

入口看起来就有点迷

步进以后是各种系统函数的开头ntdll.xxxxxxx开头的各种调用,开始慌了,但是这些应该都是系统调用,主要看的应该是magic.xxxxx这样的才对

然后就每个magic开头的调用都用ida找到对应的地址,分析一波函数,耗费无数时间和头发以后,终于来到一个奇妙的函数sub_402563,用ida看这个函数是这样子的:

img3.png

这个看起来就很棒啊,然而看着一地的头发,我不禁陷入沉思:为什么不一开始就搜索字符串。。。

然后看这判断,一看就可以写脚本跑啊,然后

img4.png

呵呵,摔(╯‵□′)╯︵┻━┻

对不起我这一地头发!!!

算了继续。

然后就接着单步,在wp的提醒下(羞愧,捂脸),我学会了一边看控制台一边单步,来找到关键函数。。。(我还是太菜了

img5.png

通过单步发现这个call输出了结果,最终结果是magic.402218输出的,但是ida分析后发现这个函数没有判断,然后就往上找可以找到magic.402268,然后ida打开,这就找到了判断时间的关键函数

img6.png

很明显,byte_405020是密钥,通过与正确的时间作为种子所生成的随机数异或,然后调用另外一个函数sub_4027ED

img7.png

仔细看可以看出来,Dst是个结构体,大小为12byte,是个有256个结构体的结构体数组,结构体大概长这样

1
2
3
4
5
struct Dst{
BYTE ch;
DWORD mem1;
DWORD mem2;
}; //由于对齐的原因,ch后补了3个空字节,DWORD占4个字节,所以总共是12个字节

对于每个结构体,ch被赋值为相应的异或后的结果,mem1赋值为0x7FFFFFFF,mem2赋值为0,然后就进入下个函数

还可以通过查看栈的位置,发现v2和v1其实分别是Dst[255]的mem1和mem2的值

sub_4026D0就是个对结构体的处理函数,于是就可以总结一哈:

  • 时间在 0x5AFFE78F~0x5B028A8F 之间,并作为随机数种子
  • 通过与一个字符数组的序列进行异或,生成一个结构体数组
  • 结构体数组进行操作,可以得到v1、v2的值
  • v2的值实际上是Dst[255].mem1,并且应该等于0x700

于是,有了这些,就可以愉快的写脚本爆破了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <iomanip>
using namespace std;

typedef unsigned char sngl;

struct Ele{
char byte; //由于最开始写的时候把byte的类型设置为unsigned char
int mem1; //硬是跑出包含正确种子在内的21个种子。。。
int mem2; //无法接受.jpg
};

sngl aim_table[]={
0x58,0x71,0x8F,0x32,0x05,0x06,0x51,0xC7,0xA7,0xF8,0x3A,
0xE1,0x06,0x48,0x82,0x09,0xA1,0x12,0x9F,0x7C,0xB8,0x2A,
0x6F,0x95,0xFD,0xD0,0x67,0xC8,0xE3,0xCE,0xAB,0x12,0x1F,
0x98,0x6B,0x14,0xEA,0x89,0x90,0x21,0x2D,0xFD,0x9A,0xBB,
0x47,0xCC,0xEA,0x9C,0xD7,0x50,0x27,0xAF,0xB9,0x77,0xDF,
0xC5,0xE9,0xE1,0x50,0xD3,0x38,0x89,0xEF,0x2D,0x72,0xC2,
0xDF,0xF3,0x7D,0x7D,0x65,0x95,0xED,0x13,0x00,0x1C,0xA3,
0x3C,0xE3,0x57,0xE3,0xF7,0xF7,0x2C,0x73,0x88,0x34,0xB1,
0x62,0xD3,0x37,0x19,0x26,0xBE,0xB2,0x33,0x20,0x3F,0x60,
0x39,0x87,0xA6,0x65,0xAD,0x73,0x1A,0x6D,0x49,0x33,0x49,
0xC0,0x56,0x00,0xBE,0x0A,0xCF,0x28,0x7E,0x8E,0x69,0x87,
0xE1,0x05,0x88,0xDA,0x54,0x3E,0x3C,0x0E,0xA9,0xFA,0xD7,
0x7F,0x4E,0x44,0xC6,0x9A,0x0A,0xD2,0x98,0x6A,0xA4,0x19,
0x6D,0x8C,0xE1,0xF9,0x30,0xE5,0xFF,0x33,0x4A,0xA9,0x52,
0x3A,0x0D,0x67,0x20,0x1D,0xBF,0x36,0x3E,0xE8,0x56,0xBF,
0x5A,0x88,0xA8,0x69,0xD6,0xAB,0x52,0xF1,0x14,0xF2,0xD7,
0xEF,0x92,0xF7,0xA0,0x70,0xA1,0xEF,0xE3,0x1F,0x66,0x2B,
0x97,0xF6,0x2B,0x30,0x0F,0xB0,0xB4,0xC0,0xFE,0xA6,0x62,
0xFD,0xE6,0x4C,0x39,0xCF,0x20,0xB3,0x10,0x60,0x9F,0x34,
0xBE,0xB2,0x1C,0x3B,0x6B,0x1D,0xDF,0x53,0x72,0xF2,0xFA,
0xB1,0x51,0x82,0x04,0x30,0x56,0x1F,0x37,0x72,0x7A,0x97,
0x50,0x29,0x86,0x4A,0x09,0x3C,0x59,0xC4,0x41,0x71,0xF8,
0x1A,0xD2,0x30,0x88,0x63,0xFF,0x85,0xDE,0x24,0x8C,0xC3,
0x37,0x14,0xC7};

Ele *check(Ele *_struct,int index)
{
Ele *result = NULL;
if(index >= 0 && index <= 255)
result = &_struct[index];
else
memset(result, 0 ,sizeof(result));
return result;
}

void work(Ele *dst,int idx)
{
Ele *result = NULL;
Ele *v3 = NULL;
int v4;
Ele *v5 = NULL;
Ele *v6 = NULL;
Ele *v7 = NULL;
Ele *v8 = NULL;
unsigned int index;

v8 = dst;
index = idx;
result = check(v8,idx);
v7 = result;

if(result)
{
if(index & 0xF)
v3 = check(v8,index-0x1);
else
v3 = 0x0;
v6 = v3;
if(index + 0xF <= 0x1E)
result = 0x0;
else
result = check(v8,index - 0x10);
v5 = result;
if(v6 || result)
{
if(v6)
{
v7->mem1 = v7->byte + v6->mem1;
result = v7;
v7->mem2 = 2 * v6->mem2;
}
if(v5)
{
v4 = v5->mem1 + v7->byte;
if(v4 < v7->mem1)
{
v7->mem1 = v4;
result = v7;
v7->mem2 = 2 * v5->mem2 | 1;
}
}
}
else
{
result = v7;
v7->mem1 = v7->byte;
}
}
return;
}

bool calculate(sngl str[])
{
Ele *Dst;
Dst = (Ele *)malloc(sizeof(Ele)*0x100);
memset(Dst,0,0xC00);
for(int i = 0;i <= 255 ; i++)
{
Dst[i].byte = str[i];
Dst[i].mem1 = 0x7FFFFFFF;
Dst[i].mem2 = 0;
work(Dst,i);
}
if(Dst[255].mem1 == 0x700)
return true;
else
return false;
}

int main()
{
unsigned int time;
int cas = 0;

for(time = 0x5AFFE790 ; time < 0x5B028A8F ; time ++)
{
srand(time);
sngl table[256];
memcpy(table,aim_table,256);
for(int i = 0 ; i <= 255 ; i++)
{
table[i] ^= rand();
}
if(calculate(table))
{
printf("0x%x",time);
break;
}
}
return 0;
} //可以得到时间是0x5B00E398

然后就在magic._time64调用完后修改返回值

然后就单步,会找到一个需要输入的函数sub_4023B1,这里用到了回调的机制,真是打开了新世界的大门

依旧ida打开

img8.png

可以看到输入是26个字符,然后对输入进行加密(我修改过函数名)然后一个判断,通过了以后再对加密串进行加密(可以猜测加密是异或之类的可以两次加密复原的那种),然后分析encode,发现确实是通过异或加密的。

然后再看sub_4029C7这个判断函数(只截了一部分)

img9.png

看起来贼复杂(其实做起来也很复杂。。。啊我的头发

这里用到了setjmp和longjmp,大致上算是goto的升级版

其实相当于汇编机器码一样的,ins_table里存的就相当于指令,根据指令一个个进行操作,简化以后是这样

ex_img1.png

1
2
3
4
5
unsigned char arg = 0x66;
ch += 0xCC;
ch &= 0xFF;
ch ^= arg;
arg = ~ arg;

由于密文也已经知道(key_word)就可以进行爆破正确的加密后的密文了(正确的输入没必要跑,因为可以通过程序自动第二次加密得到:P

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
using namespace std;

int keyboard[]={
0x89 ,0xC1 ,0xEC ,0x50 ,0x97 ,0x3A ,0x57 ,0x59 ,0xE4 ,0xE6 ,0xE4 ,0x42 ,0xCB,
0xD9 ,0x08 ,0x22 ,0xAE ,0x9D ,0x7C ,0x07 ,0x80 ,0x8F ,0x1B ,0x45 ,0x04 ,0xE8};

int main()
{
char ans[26];
int arg[2] = {0x66,0x99};
for(int i = 0 ;i < 26 ; i++)
{
int j;
int ch;
for(j = 0x00;j < 0xFF ; j++)
{
ch = j;
ch += 0xCC;
ch &= 0xFF;
ch ^= arg[i%2];
if(ch == keyboard[i])
{
printf("%02x",j);
break;
}
}
}
return 0; //238cbefd25d765f4b6b3b60fe174a2effc384ed21a4ab11096a5
}

然后就在第一次加密后,将对应的内存修改为得到的字串

img10.png

然后这时候就有神秘的情况出现,这是错误输入没有的

img11.png

通过对比和分析机器码可以猜测,这个操作不重要,于是可以把出现除0的地方改成除1,由于除数也是机器码里的,所以直接通过修改内存就可以

然后继续步过,当再次通过加密函数的时候,可以看见内存里之前修改的部分已经变成了一半的flag。。。

img12.png

然后注意控制台黑框框,继续步过

然后就能得到完整的输出

img13.png

可以得到完整的flag是rctf{h@ck_For_fun_02508iO2_2iOR}

前面一部分可以通过眯眼看出来23333,像我就不一样,我取下眼镜就能直接看出来2333333

要不比划一下?

img14.png

收获

这次的题目,我学到了挺多东西的,或许说是更熟练了(白学家gunna

  • 对x64dbg这个工具用的更熟练了,毕竟调了快20个小时233333发现了之前没有用到过的操作
  • 碰到了回调函数,知道了这种机制,以后碰到就有了方向
  • 感受到了windows下逆向的便捷,x86dbg和ida一起用起来如有神助(然而这和我是个菜鸡有什么关系呢.jpg
  • 学到了setjmp()和longjmp()的操作
  • 看到了结构体在内存中的存储方式
  • 又一次分析了模拟计算机机器码的操作,下次碰到争取不慌XD