string_analyze/rules/core/
flag.rs1use std::sync::LazyLock;
2
3use crate::rules::lazy_rule;
4use crate::tool::{has_chars, has_keyword};
5use base64::Engine;
6use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
7use hex;
8
9const FLAG_PREFIXES: &[&str] = &[
10 "flag",
11 "ctf",
12 "bugku",
13 "htb",
14 "thm",
15 "picoctf",
16 "nssctf",
17 "dasctf",
18 "ciscn",
19 "cyberpeace",
20 "qwb",
21 "xctf",
22 "iscc",
23 "seccon",
24 "pctf",
25 "actf",
26 "mrctf",
27 "vnctf",
28 "litctf",
29 "adworld",
30 "roarctf",
31 "flare",
32];
33
34static FLAG_PREFIXES_B64: LazyLock<Vec<String>> = LazyLock::new(|| {
35 FLAG_PREFIXES
36 .iter()
37 .map(|s| {
38 let encoded = BASE64_STANDARD.encode(format!("{s}{{"));
39 let trimmed = encoded.trim_end_matches('=');
40 let invariant_len = ((s.len() + 1) * 8) / 6;
41 trimmed[..invariant_len.min(trimmed.len())].to_string()
42 })
43 .collect::<Vec<_>>()
44});
45
46static FLAG_PREFIXES_HEX: LazyLock<Vec<String>> = LazyLock::new(|| {
47 let mut hex_patterns = Vec::new();
48
49 for s in FLAG_PREFIXES {
50 let bytes = format!("{s}{{").into_bytes();
51
52 hex_patterns.push(hex::encode(&bytes));
53
54 let escaped: String = bytes.iter().map(|b| format!("\\x{:02x}", b)).collect();
55 hex_patterns.push(escaped);
56
57 let ox_formatted: String = bytes
58 .iter()
59 .map(|b| format!("0x{:02x}", b))
60 .collect::<Vec<_>>()
61 .join(","); hex_patterns.push(ox_formatted);
63
64 let space_separated: String = bytes
65 .iter()
66 .map(|b| format!("{:02x}", b))
67 .collect::<Vec<_>>()
68 .join(" ");
69 hex_patterns.push(space_separated);
70 }
71
72 hex_patterns
73});
74
75lazy_rule!(
76 RE_FLAG = r#"(?i)\b[a-z0-9_.-]{0,20}(?:flag|ctf)[a-z0-9\s-]{0,4}\{[^\{\}\n=\t()]{4,256}\}"#,
77 "发现标准 flag",
78 100
79);
80
81lazy_rule!(
82 RE_PLATFORM_FLAG = r#"(?i)\b(?:bugku|htb|thm|picoctf|nssctf|dasctf|ciscn|cyberpeace|qwb|xctf|iscc|seccon|pctf|actf|mrctf|vnctf|litctf|adworld|roarctf|flare)[a-z0-9_-]{0,4}\{[^\{\}\n=\t()]{4,256}\}"#,
83 "发现 特定平台/赛事的 flag",
84 100
85);
86
87lazy_rule!(
88 FLAG1 = |state| (state.input.len() < 256)
89 && FLAG_PREFIXES
90 .iter()
91 .any(|&prefix| has_keyword(&mut *state, prefix, true)),
92 "存在已知flag前缀以及 '{','}'",
93 70,
94 |_, input| has_chars(input, &['{', '}'], false, false)
95 && !has_chars(
96 input,
97 &['(', ')', '[', ']', '=', ' ', '`', '"', '\'', ';', ':'],
98 true,
99 false
100 )
101);
102
103lazy_rule!(
104 FLAG2 = |state| (state.input.len() < 256)
105 && FLAG_PREFIXES.iter().any(|&prefix| {
106 let chars_vec: Vec<char> = prefix.chars().collect();
107 has_chars(&mut *state, &chars_vec, false, true)
108 }),
109 "存在被打乱的flag前缀字符并且含有 '{','}'",
110 45,
111 |_, input| has_chars(input, &['{', '}'], false, false)
112 && !has_chars(
113 input,
114 &['(', ')', '[', ']', '=', ' ', '`', '"', '\'', ';', ':'],
115 true,
116 false
117 )
118);
119
120lazy_rule!(
121 FLAG_B64 = |state| (state.input.len() >= 16)
122 && FLAG_PREFIXES_B64
123 .iter()
124 .any(|b64_prefix| has_keyword(&mut *state, b64_prefix, false)),
125 "疑似 Base64 编码的 flag前缀",
126 65
127);
128
129lazy_rule!(
130 FLAG_HEX = |state| (state.input.len() >= 16)
131 && FLAG_PREFIXES_HEX
132 .iter()
133 .any(|hex_prefix| has_keyword(&mut *state, hex_prefix, true)),
134 "疑似 Hex 编码的 flag前缀",
135 65
136);
137
138#[cfg(test)]
139mod tests {
140 use crate::analyze_with;
141 use crate::rules::get_rules;
142
143 #[test]
144 fn test() {
145 let input = r#"
146flag{5tgb8uik,0ol}
147buuctf{aaac38f5-046b-43e8-a089-03813226d280}
148fgaag_!l{_oun}amb_ob # railfence_group 3
149hex_flag = 666c61677b68656c6c6f5f63746665727d
150b64_flag = ZmxhZ3s1dGdiOHVpaywwb2x9
151b64_ctf = Y3Rme3Rlc3RfMTIzNH0=
15266 6c 61 67 7b 68 65 6c 6c 6f 5f 63 74 66 65 72 7d
15366%6c%61%67%7b%68%65%6c%6c%6f%5f%63%74%66%65%72%7d
1540x66,0x6c,0x61,0x67,0x7b,0x68,0x65,0x6c,0x6c,0x6f,0x5f,0x63,0x74,0x66,0x65,0x72,0x7d
1550x660x6c0x610x670x7b0x680x650x6c0x6c0x6f0x5f0x630x740x660x650x720x7d
156\x66\x6c\x61\x67\x7b\x68\x65\x6c\x6c\x6f\x5f\x63\x74\x66\x65\x72\x7d
15766,6c,61,67,7b,68,65,6c,6c,6f,5f,63,74,66,65,72,7d
158"#;
159 for line in input.lines() {
160 if line.trim().is_empty() {
161 continue;
162 }
163 println!(
164 "{}",
165 analyze_with(
166 line,
167 &get_rules(|m, _| module_path!().split("::tests").any(|s| s == m)),
168 )
169 )
170 }
171 }
172}