Class: UnicodeStego
- Inherits:
-
Object
- Object
- UnicodeStego
- Defined in:
- lib/ccc/unicode_stego.rb
Constant Summary collapse
- CHANNELS =
通道定义:每个通道包含 4 个同形字符(2 bits)
{ apostrophe: { name: "撇号", bits: 2, chars: [ { code: 0x0027, char: "'", name: "标准 ASCII 撇号" }, # 00 { code: 0x2019, char: "\u2019", name: "右单引号" }, # 01 { code: 0x02BC, char: "\u02BC", name: "修饰字母撇号" }, # 10 { code: 0x02B9, char: "\u02B9", name: "修饰字母上标号" }, # 11 ], regex: /['\u2019\u02BC\u02B9]/, }, hyphen: { name: "短横线", bits: 2, chars: [ { code: 0x002D, char: "-", name: "标准连字符" }, # 00 { code: 0x2010, char: "\u2010", name: "连字符" }, # 01 { code: 0x2011, char: "\u2011", name: "非断连字符" }, # 10 { code: 0x2212, char: "\u2212", name: "减号" }, # 11 ], regex: /[-\u2010\u2011\u2212]/, }, space: { name: "空格", bits: 2, chars: [ { code: 0x0020, char: " ", name: "标准空格" }, # 00 { code: 0x00A0, char: "\u00A0", name: "不间断空格" }, # 01 { code: 0x2002, char: "\u2002", name: "En 空格" }, # 10 { code: 0x2003, char: "\u2003", name: "Em 空格" }, # 11 ], regex: /[ \u00A0\u2002\u2003]/, }, period: { name: "句号", bits: 2, chars: [ { code: 0x002E, char: ".", name: "标准句点" }, # 00 { code: 0x2024, char: "\u2024", name: "一点前导" }, # 01 { code: 0x06D4, char: "\u06D4", name: "阿拉伯句号" }, # 10 { code: 0x3002, char: "\u3002", name: "中文句号" }, # 11 ], regex: /[.\u2024\u06D4\u3002]/, }, colon: { name: "冒号", bits: 2, chars: [ { code: 0x003A, char: ":", name: "标准冒号" }, # 00 { code: 0x2236, char: "\u2236", name: "比例符号" }, # 01 { code: 0x02F8, char: "\u02F8", name: "修饰字母冒号" }, # 10 { code: 0xA789, char: "\uA789", name: "拉丁修饰冒号" }, # 11 ], regex: /[:\u2236\u02F8\uA789]/, }, }.freeze
- CHANNEL_ORDER =
通道优先级(从高到低)
[:apostrophe, :hyphen, :space, :period, :colon].freeze
Instance Attribute Summary collapse
-
#channel_order ⇒ Object
readonly
Returns the value of attribute channel_order.
-
#channels ⇒ Object
readonly
Returns the value of attribute channels.
Instance Method Summary collapse
-
#channel_values_to_data(values) ⇒ Integer
将通道序列解码为数据.
-
#count_available_slots(text) ⇒ Hash
统计文本中各通道可用字符数量.
-
#data_to_channel_values(data, bits) ⇒ Array<Integer>
将数据编码为通道序列.
-
#decode(stego_text, bits = nil) ⇒ Hash
解码:从文本中提取数据.
-
#detect(text) ⇒ Hash
检测文本是否包含隐写信息.
-
#encode(text, data, bits = nil) ⇒ Hash
编码:将数据嵌入文本.
-
#generate_fingerprint(env = {}) ⇒ Integer
生成环境指纹(模拟 Claude Code 的用法).
-
#get_capacity(text) ⇒ Integer
计算文本的最大编码容量(bits).
-
#get_channel_char(channel_name, value) ⇒ String?
获取通道的字符.
-
#get_channel_value(channel_name, char) ⇒ Integer
获取通道的编码值(0-3).
-
#initialize ⇒ UnicodeStego
constructor
A new instance of UnicodeStego.
-
#parse_fingerprint(fingerprint) ⇒ Hash
解析环境指纹.
-
#sanitize(text) ⇒ String
防御:清除文本中的隐写信息.
Constructor Details
#initialize ⇒ UnicodeStego
Returns a new instance of UnicodeStego.
77 78 79 80 |
# File 'lib/ccc/unicode_stego.rb', line 77 def initialize @channels = CHANNELS.dup @channel_order = CHANNEL_ORDER.dup end |
Instance Attribute Details
#channel_order ⇒ Object (readonly)
Returns the value of attribute channel_order.
75 76 77 |
# File 'lib/ccc/unicode_stego.rb', line 75 def channel_order @channel_order end |
#channels ⇒ Object (readonly)
Returns the value of attribute channels.
75 76 77 |
# File 'lib/ccc/unicode_stego.rb', line 75 def channels @channels end |
Instance Method Details
#channel_values_to_data(values) ⇒ Integer
将通道序列解码为数据
150 151 152 153 154 155 156 |
# File 'lib/ccc/unicode_stego.rb', line 150 def channel_values_to_data(values) data = 0 values.reverse_each do |v| data = (data << 2) | v end data end |
#count_available_slots(text) ⇒ Hash
统计文本中各通道可用字符数量
110 111 112 113 114 115 116 117 118 119 |
# File 'lib/ccc/unicode_stego.rb', line 110 def count_available_slots(text) counts = {} @channel_order.each do |name| channel = @channels[name] regex = channel[:regex] matches = text.scan(regex) counts[name] = matches.length end counts end |
#data_to_channel_values(data, bits) ⇒ Array<Integer>
将数据编码为通道序列
137 138 139 140 141 142 143 144 145 |
# File 'lib/ccc/unicode_stego.rb', line 137 def data_to_channel_values(data, bits) values = [] remaining = data (bits.to_f / 2).ceil.times do values << (remaining & 0b11) remaining >>= 2 end values end |
#decode(stego_text, bits = nil) ⇒ Hash
解码:从文本中提取数据
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/ccc/unicode_stego.rb', line 211 def decode(stego_text, bits = nil) values = [] used_channels = {} # 按优先级扫描通道 @channel_order.each do |channel_name| channel = @channels[channel_name] regex = channel[:regex] stego_text.scan(regex) do |match| char = match.is_a?(Array) ? match[0] : match value = get_channel_value(channel_name, char) if value != -1 values << value used_channels[channel_name] ||= { count: 0, values: [] } used_channels[channel_name][:count] += 1 used_channels[channel_name][:values] << value end end end bits ||= values.length * 2 # 只取需要的位数 needed_values = (bits.to_f / 2).ceil actual_values = values.take(needed_values) data = channel_values_to_data(actual_values) { data: data, used_channels: used_channels, bits: [bits, values.length * 2].min, all_values: values, } end |
#detect(text) ⇒ Hash
检测文本是否包含隐写信息
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 |
# File 'lib/ccc/unicode_stego.rb', line 252 def detect(text) suspicious = [] @channels.each do |name, channel| channel[:chars].each_with_index do |char_info, i| next if i == 0 # 跳过基准字符 regex = Regexp.new(Regexp.escape(char_info[:code].chr(Encoding::UTF_8))) matches = text.scan(regex) next if matches.empty? suspicious << { channel: name, char: char_info[:code].chr(Encoding::UTF_8), code: "U+#{char_info[:code].to_s(16).upcase.rjust(4, '0')}", name: char_info[:name], count: matches.length, } end end { has_stego: !suspicious.empty?, suspicious: suspicious, summary: suspicious.empty? ? "未发现可疑 Unicode 字符" : "发现 #{suspicious.length} 种可疑字符,可能包含隐写信息", } end |
#encode(text, data, bits = nil) ⇒ Hash
编码:将数据嵌入文本
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
# File 'lib/ccc/unicode_stego.rb', line 163 def encode(text, data, bits = nil) bits ||= (Math.log2(data).floor + 1) capacity = get_capacity(text) raise "数据需要 #{bits} bits,但文本容量只有 #{capacity} bits" if bits > capacity values = data_to_channel_values(data, bits) used_channels = {} value_index = 0 result = text.dup # 按优先级使用通道 @channel_order.each do |channel_name| break if value_index >= values.length channel = @channels[channel_name] regex = channel[:regex] match_count = 0 result.gsub!(regex) do |match| break match if value_index >= values.length value = values[value_index] new_char = get_channel_char(channel_name, value) used_channels[channel_name] ||= { count: 0, values: [] } used_channels[channel_name][:count] += 1 used_channels[channel_name][:values] << value value_index += 1 match_count += 1 new_char end end { stego_text: result, used_channels: used_channels, data: data, bits: bits, capacity: capacity, } end |
#generate_fingerprint(env = {}) ⇒ Integer
生成环境指纹(模拟 Claude Code 的用法)
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 |
# File 'lib/ccc/unicode_stego.rb', line 297 def generate_fingerprint(env = {}) fingerprint = 0 # bit 0: 是否中国时区 if env[:time_zone] == "Asia/Shanghai" || env[:time_zone] == "Asia/Urumqi" fingerprint |= 0b1 end # bit 1-2: 代理类型 proxy_type = 0 proxy_type |= 0b01 if env[:is_china_domain] proxy_type |= 0b10 if env[:is_ai_lab] fingerprint |= (proxy_type & 0b11) << 1 # bit 3: 是否使用代理 fingerprint |= 0b1 << 3 if env[:has_proxy] # bit 4-5: 操作系统类型 os_map = { "windows" => 1, "darwin" => 2, "linux" => 3 } os_type = os_map[env[:platform]&.downcase] || 0 fingerprint |= (os_type & 0b11) << 4 # bit 6-7: 语言区域 lang_map = { "zh-cn" => 1, "en-us" => 2 } lang_type = lang_map[env[:locale]&.downcase] || 0 fingerprint |= (lang_type & 0b11) << 6 # bit 8-9: 保留/随机 fingerprint |= (rand(4) & 0b11) << 8 fingerprint & 0x3FF # 确保 10 bits end |
#get_capacity(text) ⇒ Integer
计算文本的最大编码容量(bits)
124 125 126 127 128 129 130 131 |
# File 'lib/ccc/unicode_stego.rb', line 124 def get_capacity(text) counts = count_available_slots(text) total_bits = 0 @channel_order.each do |name| total_bits += counts[name] * @channels[name][:bits] end total_bits end |
#get_channel_char(channel_name, value) ⇒ String?
获取通道的字符
101 102 103 104 105 |
# File 'lib/ccc/unicode_stego.rb', line 101 def get_channel_char(channel_name, value) channel = @channels[channel_name] return nil unless channel && value >= 0 && value <= 3 channel[:chars][value][:code].chr(Encoding::UTF_8) end |
#get_channel_value(channel_name, char) ⇒ Integer
获取通道的编码值(0-3)
86 87 88 89 90 91 92 93 94 95 |
# File 'lib/ccc/unicode_stego.rb', line 86 def get_channel_value(channel_name, char) channel = @channels[channel_name] return -1 unless channel cp = char.ord channel[:chars].each_with_index do |c, i| return i if c[:code] == cp end -1 end |
#parse_fingerprint(fingerprint) ⇒ Hash
解析环境指纹
333 334 335 336 337 338 339 340 341 342 |
# File 'lib/ccc/unicode_stego.rb', line 333 def parse_fingerprint(fingerprint) { is_china_tz: (fingerprint & 0b1) != 0, proxy_type: (fingerprint >> 1) & 0b11, has_proxy: ((fingerprint >> 3) & 0b1) != 0, os_type: (fingerprint >> 4) & 0b11, locale_type: (fingerprint >> 6) & 0b11, reserved: (fingerprint >> 8) & 0b11, } end |
#sanitize(text) ⇒ String
防御:清除文本中的隐写信息
283 284 285 286 287 288 289 290 291 292 |
# File 'lib/ccc/unicode_stego.rb', line 283 def sanitize(text) result = text.dup @channels.each do |_name, channel| channel[:chars].each_with_index do |char_info, i| next if i == 0 # 跳过基准字符 result.gsub!(char_info[:code].chr(Encoding::UTF_8), channel[:chars][0][:code].chr(Encoding::UTF_8)) end end result end |