攻防世界--reverse

本文最后更新于:a year ago

打算往逆向方向发展,这对我以后从事的工作也有一定的好处,这里记录一下攻防世界里的题目。

open-source

给出题目给的代码,不算真正的逆向,考验一下读取代码的水平吧。

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
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
if (argc != 4) {
printf("what?\n");
exit(1);
}

unsigned int first = atoi(argv[1]);
if (first != 0xcafe) {
printf("you are wrong, sorry.\n");
exit(2);
}

unsigned int second = atoi(argv[2]);
if (second % 5 == 3 || second % 17 != 8) {
printf("ha, you won't get it!\n");
exit(3);
}

if (strcmp("h4cky0u", argv[3])) {
printf("so close, dude!\n");
exit(4);
}

printf("Brr wrrr grr\n");

unsigned int hash = first * 31337 + (second % 17) * 11 + strlen(argv[3]) - 1615810207;

printf("Get your key: ");
printf("%x\n", hash);
system("pause");
return 0;
}
1
2
3
4
if (argc != 4) {
printf("what?\n");
exit(1);
}

先分析第一步,判断argc数组中,当用户输入的参数个数不为四时,输出what?之后退出。
由此我们可以看出,我们需要输入四个字符串。

1
2
3
4
5
unsigned int first = atoi(argv[1]);
if (first != 0xcafe) {
printf("you are wrong, sorry.\n");
exit(2);
}

接着分析,atoi函数的作用是将argv所指向的字符串转换为一个整数(类型为 int 型)。如果 first!=0xcafe,
输出 you are wrong, sorry,退出。但是我们需要进行下一行,所以可知,argv[1]="51966" (十进制后的0xcafe)

1
2
3
4
5
unsigned int second = atoi(argv[2]);
if (second % 5 == 3 || second % 17 != 8) {
printf("ha, you won't get it!\n");
exit(3);
}

接着分析这一串代码,将用户输入赋值给second,判断如果 second%=5==3 或者,second%17!=0, 输出 ha, you won't get it! 我们需要进行下一行,所以要求不能判断为true,则需要 second%5!=3 或者 second%17==0;

1
2
3
4
if (strcmp("h4cky0u", argv[3])) {
printf("so close, dude!\n");
exit(4);
}

匹配”h4cky0u”与argv[3]中的字符串,如果大于或小于返回非零,输出 so close, dude! 所以我们需要令argv[3]为”h4cky0u”
给出代码:

1
2
3
4
5
   argc = 4;
argv[0] = "test"; //argv[1]可以随意
argv[1] = "51966";
argv[2] = "25";
argv[3] = "h4cky0u";
1

simple-unpack

附件给出文件,用Exeinfo分析文件

1

得到的信息是 1、64位文件;2、UPX加壳(具体加壳原理这里不解释,根据深度了解)
两种方法:
1、直接搜索字符串(简单的才会出现这种,本题可以)
直接拖进IDA中,用二进制,注意,这里需要IDA64位的,shift+F12,输入flag,查找字符串,最后能找到了flag。

1

2、UPX脱壳
1

脱壳之后,直接拖进IDA中,可以看到,脱壳之后的文件

1

查看main函数即可得到flag。

===============================
总结:
1、有时给的文件,分析文件后,可以直接拖进IDA/OD中,直接搜索flag,可能会有不一样的结果。
2、本题考了UPX加壳,原理不难,但要如果要学逆向的话,一定要了解。

logmein

和我们平常的逆向不同,CTF中的逆向,是涉及到算法的,所以对于代码水平也需要比较严格的要求。
这题就涉及到了简单的算法。

===============================
先将所给的附件拖进Exeinfo中分析:1、64位文件; 2、Linux下的ELF文件

1

拖进IDA,shift+F12,查看字符串

1

看到比较重要的几句话。

接着选中main函数,F5进行反编译,得到伪代码:

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
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
size_t v3; // rsi
int i; // [rsp+3Ch] [rbp-54h]
char s[36]; // [rsp+40h] [rbp-50h]
int v6; // [rsp+64h] [rbp-2Ch]
__int64 v7; // [rsp+68h] [rbp-28h]
char v8[8]; // [rsp+70h] [rbp-20h]
int v9; // [rsp+8Ch] [rbp-4h]

v9 = 0;
strcpy(v8, ":\"AL_RT^L*.?+6/46");
v7 = 28537194573619560LL;
v6 = 7;
printf("Welcome to the RC3 secure password guesser.\n", a2, a3);
printf("To continue, you must enter the correct password.\n");
printf("Enter your guess: ");
__isoc99_scanf("%32s", s);
v3 = strlen(s);
if ( v3 < strlen(v8) )
sub_4007C0(v8);
for ( i = 0; i < strlen(s); ++i )
{
if ( i >= strlen(v8) )
((void (*)(void))sub_4007C0)();
if ( s[i] != (char)(*((_BYTE *)&v7 + i % v6) ^ v8[i]) )
((void (*)(void))sub_4007C0)();
}
sub_4007F0();
}

追踪一下sub_4007C0这个函数,

1

发现是错误输入函数。
分析代码,发现 __isoc99_scanf("%32s", s); 这条代码,用户输入,即flag。\

1
2
3
4
5
6
7
for ( i = 0; i < strlen(s); ++i )
{
if ( i >= strlen(v8) )
((void (*)(void))sub_4007C0)();
if ( s[i] != (char)(*((_BYTE *)&v7 + i % v6) ^ v8[i]) )
((void (*)(void))sub_4007C0)();
}

两种方法:

方法一:

然后将用户输入的flag,这里的v7,我并不知道什么意思,v8是进行异或,看了wp后,这里解释:

LL是长长整型,v7要转换为16进制然后在转换为字符串,而且字符是小端序( 值得注意的是,x86系列的CPU都是以小端序储存数据的,即低位字节存入低地址,高位字节存入高地址,所以正确的字符串应该反过来 ),所以把得到的字符翻转然后和v8的每一位进行异或。

1
2
3
4
5
print(hex(28537194573619560))
# 0x65626d61726168

print([chr(int(i, 16)) for i in "0x65 0x62 0x6d 0x61 0x72 0x61 0x68".split()])
# 0x65 0x62 0x6d 0x61 0x72 0x61 0x68

给出python代码,这里先将v7转化成16进制,再将十六进制转化成ASCII码,得到字符串,

1

又因为CPU是小端存储(这个涉及到内存的存储格式,逆向必备知识),v7=harambe
贴出脚本:

1
2
3
4
5
6
7
8
v7 = 'harambe'
v8 = ':\"AL_RT^L*.?+6/46'
tmp = ''
for i in range(len(v8)):
c = ord(v7[i % 7]) ^ ord(v8[i])
tmp += chr(c)

print(tmp)

方法二:

这个方法不用理解那么多,就根据它的代码来,写代码,我比较偏向脚本二,这里直接贴上脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BYTE unsigned char

int main(int argc, char* argv[]) {
unsigned int i;
char v8[18] = ":\"AL_RT^L*.?+6/46";
__int64 v7 = 28537194573619560;
int v6 = 7;

char s[18] = "";
for (i = 0; i < strlen(v8); ++i) {
s[i] = (char)(*((BYTE*)&v7 + i % v6)^v8[i]);
}

printf("%s\n", s);

system("PAUSE");
return 0;
}

两种方法都可以得到flag值,但是下面的方法直接就根据代码写脚本,比较好理解.

=========================
总结:
1、CTF中,逆向考的不仅仅是你的逆向水平,还有审计代码的水平,一个好的reverse选手,需要两者兼备;
2、我们在根据算法写脚本时,根据代码来写脚本,会更加高效,省时。

insanity

这题真的。。。。太简单了,确实可以缓和一下。

1

首先放入Exeinfo分析,得到信息:
32位文件

接着拖入32位IDA中,用二进制打开,shift+F12,查看字符串

1

看到这个我就懵逼了,这么简单?然后输入flag值,发现正确。
不敢相信这么简单,搜了一下wp,发现真的就是这样。
没什么好总结,送分题而已。

getit

这题考的也是算法。

得到附件,拖入Exeinfo中

1

得到信息:64位文件。

知道64位后,拖入64位IDA中,用二进制打开,shift+F12,看看有没有什么有用的字符串

1

看到了SharifCTF{????????????????????????????????},这个肯定不是正确的flag,
再用IDA打开,F5反编译,查看伪代码。

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v3; // al
__int64 v5; // [rsp+0h] [rbp-40h]
int i; // [rsp+4h] [rbp-3Ch]
FILE *stream; // [rsp+8h] [rbp-38h]
char filename[8]; // [rsp+10h] [rbp-30h]
unsigned __int64 v9; // [rsp+28h] [rbp-18h]

v9 = __readfsqword(0x28u);
LODWORD(v5) = 0;
while ( (signed int)v5 < strlen(s) )
{
if ( v5 & 1 )
v3 = 1;
else
v3 = -1;
*(&t + (signed int)v5 + 10) = s[(signed int)v5] + v3;
LODWORD(v5) = v5 + 1;
}
strcpy(filename, "/tmp/flag.txt");
stream = fopen(filename, "w");
fprintf(stream, "%s\n", u, v5);
for ( i = 0; i < strlen(&t); ++i )
{
fseek(stream, p[i], 0);
fputc(*(&t + p[i]), stream);
fseek(stream, 0LL, 0);
fprintf(stream, "%s\n", u);
}
fclose(stream);
remove(filename);
return 0;
}

这里贴出伪代码,分析一下代码,发现存在指针 t 不知道指向哪里,数组 s 不知道值为多少,
追踪指针 t 和 数组 s

1 1

发现了 s 和 t 的值。
知道这些后,我们就可以分析代码了,

1
2
3
4
5
6
7
8
9
10
11
v9 = __readfsqword(0x28u);
LODWORD(v5) = 0;
while ( (signed int)v5 < strlen(s) )
{
if ( v5 & 1 )
v3 = 1;
else
v3 = -1;
*(&t + (signed int)v5 + 10) = s[(signed int)v5] + v3;
LODWORD(v5) = v5 + 1;
}

__readsqword(0x28u) 通常用于alarm函数,防止调试,
接着while循环,如果v5小于数组 s 的字节长度,则进行循环,v5和1相与,如果不为零,则将1赋值给v3,否则将-1赋值给v3,然后就是指针的移位赋值了,最后v5自增加一。但是出现了一点问题,当 t 指向harifCTF{????????????????????????????????}时,运行出来的是这样的,

1

再回到一开始我们查看字符串那里,尝试将指针 t 指向 SharifCTF{????????????????????????????????},成功。
(t的值应该是0x53+’harifCTF{????????????????????????????????}’)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(){

char a[] = "c61b68366edeb7bdce3c6820314b7498";
char b[] = "SharifCTF{????????????????????????????????}";
int v5 = 0;
int v3;
while(v5 < strlen(a)){
if( v5 & 1 )
v3 = 1;
else
v3 = -1;
*(b + v5 + 10) = a[v5] + v3;
v5++;
}
printf("%s", b);

return 0;
}

====================================
总结:
这题考得也是算法,对调试文件的操作不多,可以说没有,也是CTF的考法的特点之一。

python-trade

这题考的是对python的反编译

============================
拿到附件,发现是.pyc结尾的文件,于是想到python反编译,
这里提供反编译的网站:https://tool.lu/pyc/
反编译之后的代码贴出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import base64

def encode(message):
s = ''
for i in message:
x = ord(i) ^ 32
x = x + 16
s += chr(x)

return base64.b64encode(s)

correct = 'XlNkVmtUI1MgXWBZXCFeKY+AaXNt'
flag = ''
print 'Input flag:'
flag = raw_input()
if encode(flag) == correct:
print 'correct'
else:
print 'wrong'

这里分析代码,代码很简单,将flag进行encode函数的处理与变量 correct 中的字符串向比较,如果相等,即输出correct,应该就是flag了。

分析一下encode函数,总体来说,这个函数很简单,循环,逐个处理message中的字符,先通过ord()函数,以一个字符(长度为1的字符串)作为参数,返回对应的 ASCII 数值,接着ASCII码与32进行异或,然后x加上16,转换成字符串赋给s。
这个函数还是比较好理解的,直接给出EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import base64

def decode(message):
flag_message = base64.b64decode(message)
# print(base64.b64decode(message))
s = ''
for i in flag_message:
x = ord(i) - 16
x = x ^ 32
s += chr(x)
return s

correct = 'XlNkVmtUI1MgXWBZXCFeKY+AaXNt'

flag_value = decode(correct)
print(flag_value)

==============================
总结:
1、这题考的是python文件的反编译,这个知识点要知道。
2、这几题基本上都是考算法的,很少带有调试的部分,所以难度不大,逆向真正难的地方是进行文件调试。

re1

终于接触到反调试部分了,也是逆向中最难的一部分,本题的难度不大,但是意味着进入了reverse这个门了。

拿到附件,发现是.exe文件,拖入OllyDbg中查看,

1

在分析文件之前,我首先会使用智能搜索,这个插件是OD将识别出来的字符串显示出来,事实证明是有用的

1

查找到flag值,追踪一下,

1

很简单的一题,但也表示正式进入reverse了。。。

game

下载附件,.exe文件,先运行一下,发现叫我们玩一个游戏,成功就给出flag。想了想,作为reverse选手,怎么能通过玩游戏解除flag呢?

1

先拖进Exeinfo中分析,

1

得到的信息:
win32程序
再拖进IDA中分析,
本题三种解法

方法一

在IDA中,搜索main函数,F5进行反编译

1

没有太多信息,追踪main_0(),发现以下代码:

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
void __cdecl main_0()
{
signed int i; // [esp+DCh] [ebp-20h]
int v1; // [esp+F4h] [ebp-8h]

sub_45A7BE((int)&unk_50B110);
sub_45A7BE((int)&unk_50B158);
sub_45A7BE((int)&unk_50B1A0);
sub_45A7BE((int)&unk_50B1E8);
sub_45A7BE((int)&unk_50B230);
sub_45A7BE((int)&unk_50B278);
sub_45A7BE((int)&unk_50B2C0);
sub_45A7BE((int)&unk_50B308);
sub_45A7BE((int)"二 |\n");
sub_45A7BE((int)"| by 0x61 |\n");
sub_45A7BE((int)"| |\n");
sub_45A7BE((int)"|------------------------------------------------------|\n");
sub_45A7BE((int)"Play a game\n"
"The n is the serial number of the lamp,and m is the state of the lamp\n"
"If m of the Nth lamp is 1,it's on ,if not it's off\n"
"At first all the lights were closed\n");
sub_45A7BE((int)"Now you can input n to change its state\n");
sub_45A7BE((int)"But you should pay attention to one thing,if you change the state of the Nth lamp,the state of (N-1)th"
" and (N+1)th will be changed too\n");
sub_45A7BE((int)"When all lamps are on,flag will appear\n");
sub_45A7BE((int)"Now,input n \n");
while ( 1 )
{
while ( 1 )
{
sub_45A7BE((int)"input n,n(1-8)\n");
sub_459418();
sub_45A7BE((int)"n=");
sub_4596D4("%d", &v1);
sub_45A7BE((int)"\n");
if ( v1 >= 0 && v1 <= 8 )
break;
sub_45A7BE((int)"sorry,n error,try again\n");
}
if ( v1 )
{
sub_4576D6(v1 - 1);
}
else
{
for ( i = 0; i < 8; ++i )
{
if ( (unsigned int)i >= 9 )
j____report_rangecheckfailure();
byte_532E28[i] = 0;
}
}
j__system("CLS");
sub_458054();
if ( byte_532E28[0] == 1
&& byte_532E28[1] == 1
&& byte_532E28[2] == 1
&& byte_532E28[3] == 1
&& byte_532E28[4] == 1
&& byte_532E28[5] == 1
&& byte_532E28[6] == 1
&& byte_532E28[7] == 1 )
{
sub_457AB4();
}
}
}

主要分析这一行代码

1
2
3
4
5
6
7
8
9
10
11
if ( byte_532E28[0] == 1
&& byte_532E28[1] == 1
&& byte_532E28[2] == 1
&& byte_532E28[3] == 1
&& byte_532E28[4] == 1
&& byte_532E28[5] == 1
&& byte_532E28[6] == 1
&& byte_532E28[7] == 1 )
{
sub_457AB4();
}

如果数组中的值都为1,则执行sub_457AB4()函数,跟踪下去

1

继续跟踪

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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
int sub_45E940()
{
signed int i; // [esp+D0h] [ebp-94h]
char v2; // [esp+DCh] [ebp-88h]
char v3; // [esp+DDh] [ebp-87h]
char v4; // [esp+DEh] [ebp-86h]
char v5; // [esp+DFh] [ebp-85h]
char v6; // [esp+E0h] [ebp-84h]
char v7; // [esp+E1h] [ebp-83h]
char v8; // [esp+E2h] [ebp-82h]
char v9; // [esp+E3h] [ebp-81h]
char v10; // [esp+E4h] [ebp-80h]
char v11; // [esp+E5h] [ebp-7Fh]
char v12; // [esp+E6h] [ebp-7Eh]
char v13; // [esp+E7h] [ebp-7Dh]
char v14; // [esp+E8h] [ebp-7Ch]
char v15; // [esp+E9h] [ebp-7Bh]
char v16; // [esp+EAh] [ebp-7Ah]
char v17; // [esp+EBh] [ebp-79h]
char v18; // [esp+ECh] [ebp-78h]
char v19; // [esp+EDh] [ebp-77h]
char v20; // [esp+EEh] [ebp-76h]
char v21; // [esp+EFh] [ebp-75h]
char v22; // [esp+F0h] [ebp-74h]
char v23; // [esp+F1h] [ebp-73h]
char v24; // [esp+F2h] [ebp-72h]
char v25; // [esp+F3h] [ebp-71h]
char v26; // [esp+F4h] [ebp-70h]
char v27; // [esp+F5h] [ebp-6Fh]
char v28; // [esp+F6h] [ebp-6Eh]
char v29; // [esp+F7h] [ebp-6Dh]
char v30; // [esp+F8h] [ebp-6Ch]
char v31; // [esp+F9h] [ebp-6Bh]
char v32; // [esp+FAh] [ebp-6Ah]
char v33; // [esp+FBh] [ebp-69h]
char v34; // [esp+FCh] [ebp-68h]
char v35; // [esp+FDh] [ebp-67h]
char v36; // [esp+FEh] [ebp-66h]
char v37; // [esp+FFh] [ebp-65h]
char v38; // [esp+100h] [ebp-64h]
char v39; // [esp+101h] [ebp-63h]
char v40; // [esp+102h] [ebp-62h]
char v41; // [esp+103h] [ebp-61h]
char v42; // [esp+104h] [ebp-60h]
char v43; // [esp+105h] [ebp-5Fh]
char v44; // [esp+106h] [ebp-5Eh]
char v45; // [esp+107h] [ebp-5Dh]
char v46; // [esp+108h] [ebp-5Ch]
char v47; // [esp+109h] [ebp-5Bh]
char v48; // [esp+10Ah] [ebp-5Ah]
char v49; // [esp+10Bh] [ebp-59h]
char v50; // [esp+10Ch] [ebp-58h]
char v51; // [esp+10Dh] [ebp-57h]
char v52; // [esp+10Eh] [ebp-56h]
char v53; // [esp+10Fh] [ebp-55h]
char v54; // [esp+110h] [ebp-54h]
char v55; // [esp+111h] [ebp-53h]
char v56; // [esp+112h] [ebp-52h]
char v57; // [esp+113h] [ebp-51h]
char v58; // [esp+114h] [ebp-50h]
char v59; // [esp+120h] [ebp-44h]
char v60; // [esp+121h] [ebp-43h]
char v61; // [esp+122h] [ebp-42h]
char v62; // [esp+123h] [ebp-41h]
char v63; // [esp+124h] [ebp-40h]
char v64; // [esp+125h] [ebp-3Fh]
char v65; // [esp+126h] [ebp-3Eh]
char v66; // [esp+127h] [ebp-3Dh]
char v67; // [esp+128h] [ebp-3Ch]
char v68; // [esp+129h] [ebp-3Bh]
char v69; // [esp+12Ah] [ebp-3Ah]
char v70; // [esp+12Bh] [ebp-39h]
char v71; // [esp+12Ch] [ebp-38h]
char v72; // [esp+12Dh] [ebp-37h]
char v73; // [esp+12Eh] [ebp-36h]
char v74; // [esp+12Fh] [ebp-35h]
char v75; // [esp+130h] [ebp-34h]
char v76; // [esp+131h] [ebp-33h]
char v77; // [esp+132h] [ebp-32h]
char v78; // [esp+133h] [ebp-31h]
char v79; // [esp+134h] [ebp-30h]
char v80; // [esp+135h] [ebp-2Fh]
char v81; // [esp+136h] [ebp-2Eh]
char v82; // [esp+137h] [ebp-2Dh]
char v83; // [esp+138h] [ebp-2Ch]
char v84; // [esp+139h] [ebp-2Bh]
char v85; // [esp+13Ah] [ebp-2Ah]
char v86; // [esp+13Bh] [ebp-29h]
char v87; // [esp+13Ch] [ebp-28h]
char v88; // [esp+13Dh] [ebp-27h]
char v89; // [esp+13Eh] [ebp-26h]
char v90; // [esp+13Fh] [ebp-25h]
char v91; // [esp+140h] [ebp-24h]
char v92; // [esp+141h] [ebp-23h]
char v93; // [esp+142h] [ebp-22h]
char v94; // [esp+143h] [ebp-21h]
char v95; // [esp+144h] [ebp-20h]
char v96; // [esp+145h] [ebp-1Fh]
char v97; // [esp+146h] [ebp-1Eh]
char v98; // [esp+147h] [ebp-1Dh]
char v99; // [esp+148h] [ebp-1Ch]
char v100; // [esp+149h] [ebp-1Bh]
char v101; // [esp+14Ah] [ebp-1Ah]
char v102; // [esp+14Bh] [ebp-19h]
char v103; // [esp+14Ch] [ebp-18h]
char v104; // [esp+14Dh] [ebp-17h]
char v105; // [esp+14Eh] [ebp-16h]
char v106; // [esp+14Fh] [ebp-15h]
char v107; // [esp+150h] [ebp-14h]
char v108; // [esp+151h] [ebp-13h]
char v109; // [esp+152h] [ebp-12h]
char v110; // [esp+153h] [ebp-11h]
char v111; // [esp+154h] [ebp-10h]
char v112; // [esp+155h] [ebp-Fh]
char v113; // [esp+156h] [ebp-Eh]
char v114; // [esp+157h] [ebp-Dh]
char v115; // [esp+158h] [ebp-Ch]

sub_45A7BE((int)"done!!! the flag is ");
v59 = 18;
v60 = 64;
v61 = 98;
v62 = 5;
v63 = 2;
v64 = 4;
v65 = 6;
v66 = 3;
v67 = 6;
v68 = 48;
v69 = 49;
v70 = 65;
v71 = 32;
v72 = 12;
v73 = 48;
v74 = 65;
v75 = 31;
v76 = 78;
v77 = 62;
v78 = 32;
v79 = 49;
v80 = 32;
v81 = 1;
v82 = 57;
v83 = 96;
v84 = 3;
v85 = 21;
v86 = 9;
v87 = 4;
v88 = 62;
v89 = 3;
v90 = 5;
v91 = 4;
v92 = 1;
v93 = 2;
v94 = 3;
v95 = 44;
v96 = 65;
v97 = 78;
v98 = 32;
v99 = 16;
v100 = 97;
v101 = 54;
v102 = 16;
v103 = 44;
v104 = 52;
v105 = 32;
v106 = 64;
v107 = 89;
v108 = 45;
v109 = 32;
v110 = 65;
v111 = 15;
v112 = 34;
v113 = 18;
v114 = 16;
v115 = 0;
v2 = 123;
v3 = 32;
v4 = 18;
v5 = 98;
v6 = 119;
v7 = 108;
v8 = 65;
v9 = 41;
v10 = 124;
v11 = 80;
v12 = 125;
v13 = 38;
v14 = 124;
v15 = 111;
v16 = 74;
v17 = 49;
v18 = 83;
v19 = 108;
v20 = 94;
v21 = 108;
v22 = 84;
v23 = 6;
v24 = 96;
v25 = 83;
v26 = 44;
v27 = 121;
v28 = 104;
v29 = 110;
v30 = 32;
v31 = 95;
v32 = 117;
v33 = 101;
v34 = 99;
v35 = 123;
v36 = 127;
v37 = 119;
v38 = 96;
v39 = 48;
v40 = 107;
v41 = 71;
v42 = 92;
v43 = 29;
v44 = 81;
v45 = 107;
v46 = 90;
v47 = 85;
v48 = 64;
v49 = 12;
v50 = 43;
v51 = 76;
v52 = 86;
v53 = 13;
v54 = 114;
v55 = 1;
v56 = 117;
v57 = 126;
v58 = 0;
for ( i = 0; i < 56; ++i )
{
*(&v2 + i) ^= *(&v59 + i);
*(&v2 + i) ^= 0x13u;
}
return sub_45A7BE((int)"%s\n");
}

仔细分析会发现v2到v115在堆栈中的地址是连续的,此时就可以利用地址来取值,原理类似数组取值。
再根据for循环来写函数,sub_45A7BE()看样子可能是输出函数。尝试写出EXP:

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
v2 = [123, 32, 18, 98, 119, 108,
65, 41, 124, 80, 125, 38,
124, 111, 74, 49, 83, 108,
94, 108, 84, 6, 96, 83, 44,
121, 104, 110, 32, 95, 117,
101, 99, 123, 127, 119, 96,
48, 107, 71, 92, 29, 81, 107,
90, 85, 64, 12, 43, 76, 86,
13, 114, 1, 117, 126, 0]

v59 = [18, 64, 98, 5, 2, 4, 6, 3,
6, 48, 49, 65, 32, 12, 48,
65, 31, 78, 62, 32, 49, 32,
1, 57, 96, 3, 21, 9, 4, 62,
3, 5, 4, 1, 2, 3, 44, 65, 78,
32, 16, 97, 54, 16, 44, 52, 32,
64, 89, 45, 32, 65, 15, 34, 18, 16, 0]

flag = ''

for i in range(0, 56):
v2[i] ^= v59[i]
v2[i] ^= 0x13
flag += chr(v2[i])

print(flag)

得到flag

方法二

我发现很多wp都是关于算法的解法,但是我认为在真实测试环境中,调试分析才是最重要的部分,仅仅只是靠算法的解题的话,我觉的这样仅仅只能限制在CTF中。

这里我要说的第二种解法就是如何定位关键点,进行爆破的。

1

先将文件拖入OD中,先智能搜索,分析一下字符串,发现了 done!!! the flag is

1

觉得有戏,回车进入

1

二话不说,现在这个函数头部下断点(反正也没有什么思路),然而并没有什么用。。。(后面有用)
再次查看搜索出来的字符串,发现了一个很重要的信息

1

进入看看,

1
2
3
4
5
6
0136F502  |. /0F84 6E010000 |je a0f2af98.0136F676
0136F508 |. |68 7CB54101 |push a0f2af98.0141B57C ; input n,n(1-8)\n
0136F50D |. |E8 ACB2FFFF |call a0f2af98.0136A7BE
0136F512 |. |83C4 04 |add esp,0x4
0136F515 |. |E8 FE9EFFFF |call a0f2af98.01369418
0136F51A |. |68 90B54101 |push a0f2af98.0141B590 ; n=

再看看我们程序运行的结果

1

作用不用我多说了吧,话不多说,直接下断点,再重新让它跑起来,

1

在这个地方停了下来,目的让我们输入,我们随便输入一个2,接着调试,

1

可以发现这里存在清屏,继续单步,

1

发现了一个非常大的跳转,不做任何操作,接着往下单步我发现,就会进行清屏操作,所以问题一定是出在这里的,于是,我尝试将

1
0136F5DB     /0F85 90000000 jnz a0f2af98.0136F671

这一行代码nop掉,事实证明我的判断还是正确的,但是下面还有几行跳转,我都一一nop掉,(这里其实可以直接用jmp指令,直接跳转到这个call)
终于,问题就出现在了这个call中,

1

发现跳转到开始我们看的 done!!! the flag is 那个函数中,这里就是为什么我们需要那个时候下断点,不下的话,不会跳转。接下来就继续单步,走到这里时,我们看到这个,done!!! the flag is 显示在命令行中,

1

懂了,Ctrl+F8,自动步过,我们会发现,它会一直在这一段跑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
0136EB61  |> /8B85 6CFFFFFF /mov eax,[local.37]
0136EB67 |. |83C0 01 |add eax,0x1
0136EB6A |. |8985 6CFFFFFF |mov [local.37],eax
0136EB70 |> |83BD 6CFFFFFF> cmp [local.37],0x38
0136EB77 |. |7D 48 |jge short a0f2af98.0136EBC1
0136EB79 |. |8B85 6CFFFFFF |mov eax,[local.37]
0136EB7F |. |0FBE4C05 BC |movsx ecx,byte ptr ss:[ebp+eax-0x44]
0136EB84 |. |8B95 6CFFFFFF |mov edx,[local.37]
0136EB8A |. |0FBE8415 78FF>|movsx eax,byte ptr ss:[ebp+edx-0x88]
0136EB92 |. |33C1 |xor eax,ecx
0136EB94 |. |8B8D 6CFFFFFF |mov ecx,[local.37]
0136EB9A |. |88840D 78FFFF>|mov byte ptr ss:[ebp+ecx-0x88],al
0136EBA1 |. |8B85 6CFFFFFF |mov eax,[local.37]
0136EBA7 |. |0FBE8C05 78FF>|movsx ecx,byte ptr ss:[ebp+eax-0x88]
0136EBAF |. |83F1 13 |xor ecx,0x13
0136EBB2 |. |8B95 6CFFFFFF |mov edx,[local.37]
0136EBB8 |. |888C15 78FFFF>|mov byte ptr ss:[ebp+edx-0x88],cl
0136EBBF ^\EB A0 \jmp short a0f2af98.0136EB61

应该就是我们上述分析的获取 flag 的算法了,让它跑就完事了。
最后得到flag

1

保存到1.exe,这样,不论我们输入什么,都会输出flag值。

方法三

这个方法我就不说了,其实就是直接写个脚本,暴力破解这个游戏,答案12345678或者87654321,都是可以的,但是能够逆向,我为什么要暴力破解呢?

========================================
总结:
1、(CTF中)逆向不单单可以从调试找到关键点来破解,还可以从算法上入手,可以不单单拘泥于一种方法;
2、逆向在于找到关键点,进行破解,获取flag值,但是,总的来说,还是要多操作,多实践才可以。

maze

u1s1,这题有一定难度,这题我卡在了二维函数指针那里,所以这里记录一下。

0x01 查壳和详细信息

1

可以看到程序是ELF文件,64位。

0x02 IDA分析文件

shift+F12,查看有没有关键的信息。

1

可以看到有一些关键字,和一串很多的✳+空格,不知道是什么先不管。
接着我进行了反编译,遇事不决反编译。
贴出反编译后的伪代码:

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
signed __int64 v3; // rbx
signed int v4; // eax
bool v5; // bp
bool v6; // al
const char *v7; // rdi
__int64 v9; // [rsp+0h] [rbp-28h]

v9 = 0LL;
puts("Input flag:");
scanf("%s", &s1, 0LL);
if ( strlen(&s1) != 24 || strncmp(&s1, "nctf{", 5uLL) || *(&byte_6010BF + 24) != 125 )
{
LABEL_22:
puts("Wrong flag!");
exit(-1);
}
v3 = 5LL;
if ( strlen(&s1) - 1 > 5 )
{
while ( 1 )
{
v4 = *(&s1 + v3);
v5 = 0;
if ( v4 > 78 )
{
v4 = (unsigned __int8)v4;
if ( (unsigned __int8)v4 == 79 )
{
v6 = sub_400650((_DWORD *)&v9 + 1);
goto LABEL_14;
}
if ( v4 == 111 )
{
v6 = sub_400660((int *)&v9 + 1);
goto LABEL_14;
}
}
else
{
v4 = (unsigned __int8)v4;
if ( (unsigned __int8)v4 == 46 )
{
v6 = sub_400670(&v9);
goto LABEL_14;
}
if ( v4 == 48 )
{
v6 = sub_400680((int *)&v9);
LABEL_14:
v5 = v6;
goto LABEL_15;
}
}
LABEL_15:
if ( !(unsigned __int8)sub_400690(asc_601060, HIDWORD(v9), (unsigned int)v9) )
goto LABEL_22;
if ( ++v3 >= strlen(&s1) - 1 )
{
if ( v5 )
break;
LABEL_20:
v7 = "Wrong flag!";
goto LABEL_21;
}
}
}
if ( asc_601060[8 * (signed int)v9 + SHIDWORD(v9)] != 35 )
goto LABEL_20;
v7 = "Congratulations!";
LABEL_21:
puts(v7);
return 0LL;
}

先分析这一行代码

1
if ( strlen(&s1) != 24 || (v3 = "nctf{", strncmp(&s1, "nctf{", 5uLL)) || *(&byte_6010BF + 24) != 125 )

发现输入的字符串(即flag)需要是24个字符,前五个必须是”nctf{“,最后一个必须是”}”,这里一阵兴奋啊,没想到这么快就有头目了,,,但是我失策了。
直接贴出几行重点代码

1
2
3
4
v6 = sub_400650((_DWORD *)&v9 + 1);
v6 = sub_400660((int *)&v9 + 1);
v6 = sub_400670(&v9);
v6 = sub_400680((int *)&v9);

这四行代码是导致我没看懂的原因,给出这四个函数的伪代码:

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
//'O'-->左
sub_400650:
bool __fastcall sub_400650(_DWORD *a1)
{
int v1; // eax

v1 = (*a1)--;
return v1 > 0;
}

//'o'-->右
sub_400660:
bool __fastcall sub_400660(int *a1)
{
int v1; // eax

v1 = *a1 + 1;
*a1 = v1;
return v1 < 8;
}


//'.'-->上
sub_400670:
bool __fastcall sub_400670(_DWORD *a1)
{
int v1; // eax

v1 = (*a1)--;
return v1 > 0;
}

//'0'-->下
sub_400680:
bool __fastcall sub_400680(int *a1)
{
int v1; // eax

v1 = *a1 + 1;
*a1 = v1;
return v1 < 8;
}

这里说说为什么我花了很长时间都没有看懂这题。。。

开始是真的没看懂为什么传参sub_400650函数是传入v9+1是向左,sub_400660函数传入v9+1是向右;
而sub_400670传入v9是向上,sub_400680传入v9是向下。
看了wp,也没说清楚,看了很久,突然发现,二维数组的话,p+1表示的是在二维上操作,而p表示的在一维上操作,v9+1其实就是表示在行上操作,而v9就是表示在列上操作。

其他的不是很难,还有一个知识点。

1
2
if ( !(unsigned __int8)sub_400690(asc_601060, HIDWORD(v9), (unsigned int)v9) )
goto LABEL_22;

先看一下这行代码中,这个函数sub_400690

1
2
3
4
5
6
7
8
__int64 __fastcall sub_400690(__int64 a1, int a2, int a3)
{
__int64 result; // rax

result = *(unsigned __int8 *)(a1 + a2 + 8LL * a3);
LOBYTE(result) = (_DWORD)result == 32 || (_DWORD)result == 35;
return result;
}

(a1是迷宫地址,a2是v10下一单位,SHIDWORD就是这个意思,a3就是v10。这里说的就是SHIDWOED这个小知识点)
我们看一下asc_601060这个值,发现是这么一个东西,就是我们开始搜索字符串的东西

1
'  *******   *  **** * ****  * ***  *#  *** *** ***     *********'

再根据题目提示的迷宫,有可能会想到这是地图,不过还是比较抽象的,如果你想到了,再根据,判断上下左右的函数,中 return v1和0,8比较,可以看出是一个8x8的地图。从[0,0]开始走到”#”这个位置。

1
2
3
4
5
6
7
8
  ******
* * *
*** * **
** * **
* *# *
** *** *
** *
********

再根据,’O’–>左,’o’–>右,’.’–>上,’0’–>下,写出flag。有人可能会说说不只一种走法,所以不只一种flag,但是,前面我们提到过,strlen(&s1) != 24,不等于24,直接输出 Wrong flag!

========================================

总结一下:
1、多读代码,多看代码,多写代码,才能更好、更快理解代码。
2、脑洞放大一下,这需要通过长期练习,说多了,就是多练多写。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!