Skip to content

Instantly share code, notes, and snippets.

@bczhc
Created January 27, 2026 14:34
Show Gist options
  • Select an option

  • Save bczhc/982607bae3e72297938b0326cbf3e1ae to your computer and use it in GitHub Desktop.

Select an option

Save bczhc/982607bae3e72297938b0326cbf3e1ae to your computer and use it in GitHub Desktop.
逆向寻找zsh history中被不小心以文本打开又保存的非ASCII文本 #zsh #bruteforce

代码由ChatGPT编写。

使用的汉字集为常用字1500:

的一是了不在有个人这上中大为来我到出要以时和地们得可下对生也子就过能他会多发说而于自之用年行家方后作成开面事好小心前所道法如进着同经分定都然与本还其当起动已两点从问里主实天高去现长此三将无国全文理明日些看只公等十意正外想间把情者没重相那向知因样学应又手但信关使种见力名二处门并口么先位头回话很再由身入内第平被给次别几月真立新通少机打水果最部何安接报声才体今合性西你放表目加常做己老四件解路更走比总金管光工结提任东原便美及教难世至气神山数利书代直色场变记张必受交非服化求风度太万各算边王什快许连五活思该步海指物则女或完马强言条特命感清带认保望转传儿制干计民白住字它义车像反象题却流且即深近形取往系量论告息让决未花收满每华业南觉电空眼听远师元请容她军士百办语期北林识半夫客战院城候单音台死视领失司亲始极双令改功程爱德复切随李员离轻观青足落叫根怎持精送众影八首包准兴红达早尽故房引火站似找备调断设格消拉照布友整术石展紧据终周式举飞片虽易运笑云建谈界务写钱商乐推注越千微若约英集示呢待坐议乎留称品志黑存六造低江念产刻节尔吃势依图共曾响底装具喜严九况跟罗须显热病证刚治绝群市阳确究久除闻答段官政类黄武七支费父统府查般斯倒突号树拿克初广奇愿欢希母香破谁致线急古既句京甚仍晚争游龙余护另器细木权星哪苦孩试朝阿队居害独讲错局男差参社换选止际假汉够诉资密案史较环投静宝专修室区料帮衣竟模脸善兵考规联团冷玉施派纪采历顾春责夜画惊银负续吗简章左块索酒值态按陈河巴冲阵境助角户乱呼灵脚继楼景怕停铁异谢否伤兰置医良承福科属围需退基右速适药怀击买素背岁土忙充排价质遇端列印贵疑露哥杀标招血礼弟亮齐穿脑委州某顺省讨尚维板散项状追笔副层沙养读习永草胡济执察归富座雨堂威忽苏船罪敢妇村著食导免温莫掌激慢托胜险寻守波雷沉秀职验靠楚略族藏丽渐刘仅肯担扬盘唐钟级毛营坚松皮供店饭范哈赶吧雪斗效临农味恶烟园烈配杂短卫跳孙曲封抓移顿律卖艺旧朋救防脱翻划迎痛校窗宣乡杨叶警限湖软掉财词压挥超屋秋跑忘馆暗班党宗坏技困登姐预耳席梦朱组旁份禁套亚益探康增诗戏伯晓含劳恩顶君庄谓付田毕纸研虚怪宁替犯灯优您姓例丝盖误架幸隐股毒娘占智佛床米凡介征彩演射祖欲束获舞圣伙梅普借私源镇睡缓升纳织歌宫概野醒夏互积街牌休摇洋败监骨批兄刀网率庭熟创访硬仁菜丁绿牛避阴拍雄秘缺卷姑尼油恐玩释遍握球降虑荣策肉妈迷检伸欧攻练育危厅啊睛摆茶勇判材抱亦妻吸喝趣嘴逐操午吉浪轮默毫冰珠鼓阶孔徐固偏陆诸遗爷述帝闭补编巨透弄尤鲁拥录吴墙货弱敌挑宽迹抽忍折输稳皇桌献蒙纷麻洗评挂童尊舍唯博剧乃混弹附迟敬杯鱼控塞剑厚佳测训牙洞淡盛县芳雅革款横累择乘刺载猛逃构赵杜庆途奔虎巧抗针徒圆闪谷绍聚额健诚鲜泪闲均序震仿缘戴婚篇亡奶忠烦赛闹协杰残懂丹柳妹映桥叹愈旅授享暴偷蓝氏寒宜弃丰延辈抢颜赞典冒眉烧厂唱径库川辞伴怒型纯贝票隔穷拜审伦悲柔启减页纵扫伟迫振瑞丈梁洲枪央触予孤缩洛损促番罢宋奋销幕犹锁珍抬陪妙摸峰劲镜沈夺昨哭讯貌谋泰侧贫扶阻贴申岸彼赏版抵泽插迅凭伊潮咱仙符宇肩尝递燕洁拒郎凝净遭仪薄卡末勒乌森诺呀壮忧沿惯丢季企壁惜婆袋朗零辛忆努舒枝凤灭韩胆灰旦孟陷俗绕疾瞧洪甲帐糊泛皆碰吹码奉箱倾胸堆狂仲圈冬餐厉腿尖括佩鬼欣垂跃港骗融撞塔紫荡敏郑赖滑允鸟课暂瓦祥染滚浮粗刑辆狗扑稍秦扎魂岛腾臣琴悉络摩措域冠竹殊豪呆萨旋喊寄悄倍祝剩督旗返召彻宾甘吐乔腰拔幅违详臂尺饮颗涉逼竞培惠亏叔伏唤鸡邻池怨奥侯骑漫拖俊尾恨贯凌兼询碎晨罚铺浓伍宿泉井繁粉绪筑恢匹尘辉魔仰董描距盗渡勤劝莲坦搭挺踪幽截荒恰慧邦颇焦醉废掩签丧灾鼻侵陶肃裁俱磨析奖匆瓶泥拾凉麦钢涌潜隆津搞蛋奈扰耐傅锦播墨偶捕惑飘屈鸣挤毁斜啦污赤慰饰锋覆汤寿跨羊

这个工具仅靠暴力搜索是行不通的,因为路径真的太多。合适的方法是,结合汉字的语义(比如要是有意义的词语),由于CLI可以指定已知前缀,所以自己手动把可能的前缀给传上去,再跑,这样重复做,最终或许能找到。

use std::char;
use std::env;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
fn make_polluted(text: &str) -> String {
// 与你示例里相同的转换逻辑
let bytes = zsh_metafied::metafy(text.as_bytes());
let s = String::from_utf8_lossy(&bytes).to_string();
s.replace(char::REPLACEMENT_CHARACTER, "?")
}
fn main() {
// 加载目标 polluted(从文件读取,不写 literal)
let target_polluted = include_str!("../../target-polluted.txt").trim().to_string();
// 加载字表(请确认你本地的 charset 文件路径是否为 ../../charset.txt)
// 如果你的字表与 target-polluted 在同一文件,请按需修改此处路径。
let han_charset = include_str!("../../charset-chinese-1500.txt").trim();
let mut chars = han_charset.chars().collect::<Vec<_>>();
// 扩充一些常用符号
chars.extend(['。', ',', '!', '?', ';', ':', ' ', '“', '”'].iter());
// 可通过命令行参数传入已知的起始前缀,例如 `cargo run -- 从`
let start_prefix = env::args().nth(1).unwrap_or_default();
// 限制:为了避免无限增长,设定原文长度上限为 target_polluted.len() * 2(可调整)
let max_original_len = (target_polluted.len().max(1)) * 2;
println!("target_polluted (len={}):\n{}\n", target_polluted.len(), target_polluted);
println!("starting prefix: {:?}", start_prefix);
println!("charset size: {}", chars.len());
println!("max original len: {}", max_original_len);
// 计数器(可选,用于观察进度)
let trials = Arc::new(AtomicUsize::new(0));
let mut solutions: Vec<String> = Vec::new();
// DFS 回溯闭包(递归)
fn dfs(
cur_text: String,
chars: &Vec<char>,
target: &str,
max_len: usize,
trials: Arc<AtomicUsize>,
solutions: &mut Vec<String>,
) {
// safety: stop if current text too long
if cur_text.len() > max_len {
return;
}
// 每次尝试前缀对应的 polluted
let polluted = make_polluted(&cur_text);
trials.fetch_add(1, Ordering::Relaxed);
// 如果 polluted 不是 target 的前缀,则这个分支可以剪枝
if !target.starts_with(&polluted) {
return;
}
println!("{}", cur_text);
// 如果恰好相等,记录为解(注意可能有多个)
if polluted == target {
solutions.push(cur_text.clone());
// 继续搜索可能存在更长的原文也映射到相同 polluted(取决情况),可以选择返回或继续;
// 这里继续探索以寻找所有可能。
}
// 继续扩展下一个字(回溯)
if cur_text.len() < max_len {
for &ch in chars.iter() {
let mut next = cur_text.clone();
next.push(ch);
// 进一步递归
dfs(next, chars, target, max_len, Arc::clone(&trials), solutions);
}
}
}
// 启动 DFS
dfs(
start_prefix.clone(),
&chars,
&target_polluted,
max_original_len,
Arc::clone(&trials),
&mut solutions,
);
// 输出结果
println!("\ntrials tried: {}", trials.load(Ordering::Relaxed));
if solutions.is_empty() {
println!("No solutions found within the max length ({}).", max_original_len);
} else {
println!("Found {} solution(s):", solutions.len());
for (i, s) in solutions.iter().enumerate() {
println!("{}: {:?}", i + 1, s);
// 也打印对应的 polluted 以供核对
println!(" polluted: {}", make_polluted(s));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment