Windows记事本编码反汇编分析

转载自:liam.page


网上有一个流传多年的段子。这个段子大致是说,若你在简体中文版本的 Windows 系统下,用系统自带的记事本程序,以默认的 ANSI 编码保存「联通」两个字,那么重新打开后「联通」二字就消失了。如果我没记错的话,还曾有好事者据此编排,认定 Windows 背后的微软和联通有仇,故意不让联通二字正常显示。

当然,这个说法肯定是假的。但是这一现象背后的原因究竟是什么呢?

Windows 记事本都做了什么

这里着重感谢 margen 对 Windows 记事本程序做的逆向工作。没有他的工作,本文不至于这样精彩。光荣属于前辈!

根据 margen 的逆向分析,在打开文件的过程中,记事本程序会调用 fDetermineFileType 来判断文件的编码类型。翻译成 C 语言代码,大致如下。Windows 记事本在以 ANSI 保存文件时,没有任何多余的动作,直接将 buffer 中的内容通过WriteFile 系统调用写入到 txt 文件当中。

我们以 010editor 打开保存了「联通」二字文件看看。
在这里插入图片描述
可以看到,在简体中文 Windows 下,以记事本保存「联通」两个字。那么保存得到的 txt 文件内,就仅有 0xC1AACDA8 这些内容。而 0xC1AA0xCDA8 正是「联通」两个字的 GBK 编码。

打开的过程

根据 margen 的逆向分析,在打开文件的过程中,记事本程序会调用 fDetermineFileType 来判断文件的编码类型。翻译成 C 语言代码,大致如下。

int __stdcall fDetermineFileType(LPVOID lpBuffer,int cb)
{
        int iType = 0;
        WORD wSign = 0;

        if( cb <= 1 ) return 0;

        wSign = *(PWORD)lpBuffer;
        switch( wSign )
        {
        case 0xBBEF:
                {
                        if( cb >= 3 && (PBYTE)lpBuffer[3] == 0xBF) iType = 3;
                }
                break;
        case 0xFEFF:
                {
                        iType = 1;
                }
                break;
        case 0xFFFE:
                {
                        iType = 2;
                }
                break;
        default:
                {
                        if( !IsInputTextUnicode( lpBuffer, cb ) )
                        {
                                if( IsTextUTF8( lpBuffer, cb ) ) iType = 3;
                        }
                        else iType = 1;
                }
        }
        return iType;
}

首先,代码从文件头部取出了前 2 个字节,然后走 switch 分支判断。

若前两个字节是 0xBBEF,且文件第三个字节是 0xBF,则组成 UTF-8 的 BOM(虽然 UTF-8 不需要)。那么据此判断文件编码是 UTF-8。

若前两个字节是 0xFEFF,那么这是小端序 UTF-16 的 BOM。据此判断文件编码是(Windows 所谓的)Unicode 编码。

若前两个字节是 0xFFFE,那么这是大端序的 UTF-16 的 BOM。据此判断文件编码是(Windows 所谓的)Unicode Big Endian 编码。

否则,则需要做更深层次的判断。注意到,iType 被初始化为 0,代表 ANSI 编码(简体中文下是 CP936,相当于是 GBK 编码)。若已走到了 default 分支,要函数返回 0,当且仅当 IsTextUTF8( lpBuffer, cb ) 为 false 才行。然而,这个函数的写法是这样的。

BOOL IsTextUTF8( LPSTR lpBuffer, int iBufSize )
{
        int iLeftBytes = 0;
        BOOL bUtf8 = FALSE;
        if( iBufSize <= 0 ) return FALSE;

        for( int i=0;i<iBufSize;i++)
        {
                char c = lpBuffer[i];
                if( c < 0 ) bUtf8 = TRUE;
                if( iLeftBytes == 0 )
                {
                        if( c >= 0 ) continue;
                        do
                        {
                                c <<= 1;
                                iLeftBytes++;
                        } while( c < 0 );
                        iLeftBytes--;
                        if( iLeftBytes == 0 ) return FALSE;
                }
                else
                {
                        c &= 0xC0;
                        if( c != (char)0x80 ) return FALSE;
                        else iLeftBytes--;
                }
        }
        if( iLeftBytes ) return FALSE;

        return bUtf8;
}

我们重点看 for循环内部的逻辑。首先,char c = lpBuffer[i]; 从 buffer 中取出一个字节,保存在 signed char 当中。而后判断if( c < 0 )。因为 c 是有符号的 char,所以 c < 0 意味着最高位是 1。这就意味着该字符肯定不是 ASCII 字符,可能是一个 UTF-8 字符。因此将bUtf8 置为 true。

而后,在 if( iLeftBytes == 0 ) 分支中,我们看到 c <<= 1; iLeftBytes++;的 do-while 循环。这是在判断 UTF-8 编码的首字符中,有多少个前缀的 1。根据 UTF-8 的编码规则,这个数值就是该 UTF-8 字符的编码长度,记录在iLeftBytes 当中。

接下来,根据 iLeftBytes 的大小,逐一检查后续的字节,是否以 10 开头。一旦发现有不满足条件的字节,就能判定当前文档不是 UTF-8 编码的。或是(在 for 循环结束之后)发现 iLeftBytes 尚未自减到 0 就已经到了文档末尾,则也可以判定当前文档不是 UTF-8 编码的。

也就是说,这个函数的逻辑,是根据 UTF-8 编码规则,全文扫描。若发现有一个字符不符合 UTF-8 的编码规则,则返回 false;否则若全文都符合 UTF-8 的编码规则,则返回 true

「联通」都经历了什么?

回过头,我们看到,联通二字以 ANSI(CP936)保存的 txt 文件里只有 0xC1AACDA8这些内容。因为无有 BOM,所以在fDetermineFileType函数中必然走到 default 分支,而后陷入IsTextUTF8 函数当中。

不巧的是,0xC1AA0xCDA8 都符合 UTF-8 编码的要求。因此该函数返回 true。于是,Windows 记事本打开这一文件时,认定这是一个无 BOM 的 UTF-8 编码的文件。于是按照 UTF-8 编码去解读0xC1AACDA8,那么就乱码了。

还有哪些字符

从前文的分析,我们可以得到结论:如果一个以 ANSI(CP936/GBK)保存的文档,内里包含的所有字符,都不幸满足了 UTF-8 的编码规则。那么这个文档将被 Windows 记事本当做是 UTF-8 编码的文件打开,就会乱码。

由于 GBK 是双字节的编码格式,只可能满足 UTF-8 中对 U+0080U+07FF 编码的格式:110xxxxx, 10xxxxxx。我们可以将这些字符全都扫描输出出来。以下是完成这一任务的 Python 代码。

pluses = map(lambda i:"+%s" % (hex(i)[2:].upper()), xrange(16))
headline = "%s%s" % (" " * 7, " ".join(pluses))
print headline

for i in xrange(192, 224):
    high = hex(i)[2:].upper()
    for j in xrange(4):
        low = hex(128 + j * 16)[2:].upper()
        chars = map(lambda k:(chr(i) + chr(128 + j * 16 + k)).decode("gbk"), xrange(16))
        line = u"0x%s%s %s" % (high, low, " ".join(chars))
        print line

得到的结果是:

       +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
0xC080 纮 纴 纻 纼 绖 绤 绬 绹 缊 缐 缞 缷 缹 缻 缼 缽
0xC090 缾 缿 罀 罁 罃 罆 罇 罈 罉 罊 罋 罌 罍 罎 罏 罒
0xC0A0 罓 馈 愧 溃 坤 昆 捆 困 括 扩 廓 阔 垃 拉 喇 蜡
0xC0B0 腊 辣 啦 莱 来 赖 蓝 婪 栏 拦 篮 阑 兰 澜 谰 揽
0xC180 羳 羴 羵 羶 羷 羺 羻 羾 翀 翂 翃 翄 翆 翇 翈 翉
0xC190 翋 翍 翏 翐 翑 習 翓 翖 翗 翙 翚 翛 翜 翝 翞 翢
0xC1A0 翣 痢 立 粒 沥 隶 力 璃 哩 俩 联 莲 连 镰 廉 怜
0xC1B0 涟 帘 敛 脸 链 恋 炼 练 粮 凉 梁 粱 良 两 辆 量
0xC280 聙 聛 聜 聝 聞 聟 聠 聡 聢 聣 聤 聥 聦 聧 聨 聫
0xC290 聬 聭 聮 聯 聰 聲 聳 聴 聵 聶 職 聸 聹 聺 聻 聼
0xC2A0 聽 隆 垄 拢 陇 楼 娄 搂 篓 漏 陋 芦 卢 颅 庐 炉
0xC2B0 掳 卤 虏 鲁 麓 碌 露 路 赂 鹿 潞 禄 录 陆 戮 驴
0xC380 脌 脕 脗 脙 脛 脜 脝 脟 脠 脡 脢 脣 脤 脥 脦 脧
0xC390 脨 脩 脪 脫 脭 脮 脰 脳 脴 脵 脷 脹 脺 脻 脼 脽
0xC3A0 脿 谩 芒 茫 盲 氓 忙 莽 猫 茅 锚 毛 矛 铆 卯 茂
0xC3B0 冒 帽 貌 贸 么 玫 枚 梅 酶 霉 煤 没 眉 媒 镁 每
0xC480 膧 膩 膫 膬 膭 膮 膯 膰 膱 膲 膴 膵 膶 膷 膸 膹
0xC490 膼 膽 膾 膿 臄 臅 臇 臈 臉 臋 臍 臎 臏 臐 臑 臒
0xC4A0 臓 摹 蘑 模 膜 磨 摩 魔 抹 末 莫 墨 默 沫 漠 寞
0xC4B0 陌 谋 牟 某 拇 牡 亩 姆 母 墓 暮 幕 募 慕 木 目
0xC580 艀 艁 艂 艃 艅 艆 艈 艊 艌 艍 艎 艐 艑 艒 艓 艔
0xC590 艕 艖 艗 艙 艛 艜 艝 艞 艠 艡 艢 艣 艤 艥 艦 艧
0xC5A0 艩 拧 泞 牛 扭 钮 纽 脓 浓 农 弄 奴 努 怒 女 暖
0xC5B0 虐 疟 挪 懦 糯 诺 哦 欧 鸥 殴 藕 呕 偶 沤 啪 趴
0xC680 苺 苼 苽 苾 苿 茀 茊 茋 茍 茐 茒 茓 茖 茘 茙 茝
0xC690 茞 茟 茠 茡 茢 茣 茤 茥 茦 茩 茪 茮 茰 茲 茷 茻
0xC6A0 茽 啤 脾 疲 皮 匹 痞 僻 屁 譬 篇 偏 片 骗 飘 漂
0xC6B0 瓢 票 撇 瞥 拼 频 贫 品 聘 乒 坪 苹 萍 平 凭 瓶
0xC780 莯 莵 莻 莾 莿 菂 菃 菄 菆 菈 菉 菋 菍 菎 菐 菑
0xC790 菒 菓 菕 菗 菙 菚 菛 菞 菢 菣 菤 菦 菧 菨 菫 菬
0xC7A0 菭 恰 洽 牵 扦 钎 铅 千 迁 签 仟 谦 乾 黔 钱 钳
0xC7B0 前 潜 遣 浅 谴 堑 嵌 欠 歉 枪 呛 腔 羌 墙 蔷 强
0xC880 葊 葋 葌 葍 葎 葏 葐 葒 葓 葔 葕 葖 葘 葝 葞 葟
0xC890 葠 葢 葤 葥 葦 葧 葨 葪 葮 葯 葰 葲 葴 葷 葹 葻
0xC8A0 葼 取 娶 龋 趣 去 圈 颧 权 醛 泉 全 痊 拳 犬 券
0xC8B0 劝 缺 炔 瘸 却 鹊 榷 确 雀 裙 群 然 燃 冉 染 瓤
0xC980 蓘 蓙 蓚 蓛 蓜 蓞 蓡 蓢 蓤 蓧 蓨 蓩 蓪 蓫 蓭 蓮
0xC990 蓯 蓱 蓲 蓳 蓴 蓵 蓶 蓷 蓸 蓹 蓺 蓻 蓽 蓾 蔀 蔁
0xC9A0 蔂 伞 散 桑 嗓 丧 搔 骚 扫 嫂 瑟 色 涩 森 僧 莎
0xC9B0 砂 杀 刹 沙 纱 傻 啥 煞 筛 晒 珊 苫 杉 山 删 煽
0xCA80 蕗 蕘 蕚 蕛 蕜 蕝 蕟 蕠 蕡 蕢 蕣 蕥 蕦 蕧 蕩 蕪
0xCA90 蕫 蕬 蕭 蕮 蕯 蕰 蕱 蕳 蕵 蕶 蕷 蕸 蕼 蕽 蕿 薀
0xCAA0 薁 省 盛 剩 胜 圣 师 失 狮 施 湿 诗 尸 虱 十 石
0xCAB0 拾 时 什 食 蚀 实 识 史 矢 使 屎 驶 始 式 示 士
0xCB80 藔 藖 藗 藘 藙 藚 藛 藝 藞 藟 藠 藡 藢 藣 藥 藦
0xCB90 藧 藨 藪 藫 藬 藭 藮 藯 藰 藱 藲 藳 藴 藵 藶 藷
0xCBA0 藸 恕 刷 耍 摔 衰 甩 帅 栓 拴 霜 双 爽 谁 水 睡
0xCBB0 税 吮 瞬 顺 舜 说 硕 朔 烁 斯 撕 嘶 思 私 司 丝
0xCC80 虁 虂 虃 虄 虅 虆 虇 虈 虉 虊 虋 虌 虒 虓 處 虖
0xCC90 虗 虘 虙 虛 虜 虝 號 虠 虡 虣 虤 虥 虦 虧 虨 虩
0xCCA0 虪 獭 挞 蹋 踏 胎 苔 抬 台 泰 酞 太 态 汰 坍 摊
0xCCB0 贪 瘫 滩 坛 檀 痰 潭 谭 谈 坦 毯 袒 碳 探 叹 炭
0xCD80 蛝 蛠 蛡 蛢 蛣 蛥 蛦 蛧 蛨 蛪 蛫 蛬 蛯 蛵 蛶 蛷
0xCD90 蛺 蛻 蛼 蛽 蛿 蜁 蜄 蜅 蜆 蜋 蜌 蜎 蜏 蜐 蜑 蜔
0xCDA0 蜖 汀 廷 停 亭 庭 挺 艇 通 桐 酮 瞳 同 铜 彤 童
0xCDB0 桶 捅 筒 统 痛 偷 投 头 透 凸 秃 突 图 徒 途 涂
0xCE80 蝷 蝸 蝹 蝺 蝿 螀 螁 螄 螆 螇 螉 螊 螌 螎 螏 螐
0xCE90 螑 螒 螔 螕 螖 螘 螙 螚 螛 螜 螝 螞 螠 螡 螢 螣
0xCEA0 螤 巍 微 危 韦 违 桅 围 唯 惟 为 潍 维 苇 萎 委
0xCEB0 伟 伪 尾 纬 未 蔚 味 畏 胃 喂 魏 位 渭 谓 尉 慰
0xCF80 蟺 蟻 蟼 蟽 蟿 蠀 蠁 蠂 蠄 蠅 蠆 蠇 蠈 蠉 蠋 蠌
0xCF90 蠍 蠎 蠏 蠐 蠑 蠒 蠔 蠗 蠘 蠙 蠚 蠜 蠝 蠞 蠟 蠠
0xCFA0 蠣 稀 息 希 悉 膝 夕 惜 熄 烯 溪 汐 犀 檄 袭 席
0xCFB0 习 媳 喜 铣 洗 系 隙 戏 细 瞎 虾 匣 霞 辖 暇 峡
0xD080 衻 衼 袀 袃 袆 袇 袉 袊 袌 袎 袏 袐 袑 袓 袔 袕
0xD090 袗 袘 袙 袚 袛 袝 袞 袟 袠 袡 袣 袥 袦 袧 袨 袩
0xD0A0 袪 小 孝 校 肖 啸 笑 效 楔 些 歇 蝎 鞋 协 挟 携
0xD0B0 邪 斜 胁 谐 写 械 卸 蟹 懈 泄 泻 谢 屑 薪 芯 锌
0xD180 褉 褋 褌 褍 褎 褏 褑 褔 褕 褖 褗 褘 褜 褝 褞 褟
0xD190 褠 褢 褣 褤 褦 褧 褨 褩 褬 褭 褮 褯 褱 褲 褳 褵
0xD1A0 褷 选 癣 眩 绚 靴 薛 学 穴 雪 血 勋 熏 循 旬 询
0xD1B0 寻 驯 巡 殉 汛 训 讯 逊 迅 压 押 鸦 鸭 呀 丫 芽
0xD280 襽 襾 覀 覂 覄 覅 覇 覈 覉 覊 見 覌 覍 覎 規 覐
0xD290 覑 覒 覓 覔 覕 視 覗 覘 覙 覚 覛 覜 覝 覞 覟 覠
0xD2A0 覡 摇 尧 遥 窑 谣 姚 咬 舀 药 要 耀 椰 噎 耶 爷
0xD2B0 野 冶 也 页 掖 业 叶 曳 腋 夜 液 一 壹 医 揖 铱
0xD380 觻 觼 觽 觾 觿 訁 訂 訃 訄 訅 訆 計 訉 訊 訋 訌
0xD390 訍 討 訏 訐 訑 訒 訓 訔 訕 訖 託 記 訙 訚 訛 訜
0xD3A0 訝 印 英 樱 婴 鹰 应 缨 莹 萤 营 荧 蝇 迎 赢 盈
0xD3B0 影 颖 硬 映 哟 拥 佣 臃 痈 庸 雍 踊 蛹 咏 泳 涌
0xD480 詟 詠 詡 詢 詣 詤 詥 試 詧 詨 詩 詪 詫 詬 詭 詮
0xD490 詯 詰 話 該 詳 詴 詵 詶 詷 詸 詺 詻 詼 詽 詾 詿
0xD4A0 誀 浴 寓 裕 预 豫 驭 鸳 渊 冤 元 垣 袁 原 援 辕
0xD6A0 譅 帧 症 郑 证 芝 枝 支 吱 蜘 知 肢 脂 汁 之 织
0xD6B0 职 直 植 殖 执 值 侄 址 指 止 趾 只 旨 纸 志 挚
0xD780 讇 讈 讉 變 讋 讌 讍 讎 讏 讐 讑 讒 讓 讔 讕 讖
0xD790 讗 讘 讙 讚 讛 讜 讝 讞 讟 讬 讱 讻 诇 诐 诪 谉
0xD7A0 谞 住 注 祝 驻 抓 爪 拽 专 砖 转 撰 赚 篆 桩 庄
0xD7B0 装 妆 撞 壮 状 椎 锥 追 赘 坠 缀 谆 准 捉 拙 卓
0xD880 貈 貋 貍 貎 貏 貐 貑 貒 貓 貕 貖 貗 貙 貚 貛 貜
0xD890 貝 貞 貟 負 財 貢 貣 貤 貥 貦 貧 貨 販 貪 貫 責
0xD8A0 貭 亍 丌 兀 丐 廿 卅 丕 亘 丞 鬲 孬 噩 丨 禺 丿
0xD8B0 匕 乇 夭 爻 卮 氐 囟 胤 馗 毓 睾 鼗 丶 亟 鼐 乜
0xD980 賭 賮 賯 賰 賱 賲 賳 賴 賵 賶 賷 賸 賹 賺 賻 購
0xD990 賽 賾 賿 贀 贁 贂 贃 贄 贅 贆 贇 贈 贉 贊 贋 贌
0xD9A0 贍 佟 佗 伲 伽 佶 佴 侑 侉 侃 侏 佾 佻 侪 佼 侬
0xD9B0 侔 俦 俨 俪 俅 俚 俣 俜 俑 俟 俸 倩 偌 俳 倬 倏
0xDA80 趢 趤 趥 趦 趧 趨 趩 趪 趫 趬 趭 趮 趯 趰 趲 趶
0xDA90 趷 趹 趻 趽 跀 跁 跂 跅 跇 跈 跉 跊 跍 跐 跒 跓
0xDAA0 跔 凇 冖 冢 冥 讠 讦 讧 讪 讴 讵 讷 诂 诃 诋 诏
0xDAB0 诎 诒 诓 诔 诖 诘 诙 诜 诟 诠 诤 诨 诩 诮 诰 诳
0xDB80 踿 蹃 蹅 蹆 蹌 蹍 蹎 蹏 蹐 蹓 蹔 蹕 蹖 蹗 蹘 蹚
0xDB90 蹛 蹜 蹝 蹞 蹟 蹠 蹡 蹢 蹣 蹤 蹥 蹧 蹨 蹪 蹫 蹮
0xDBA0 蹱 邸 邰 郏 郅 邾 郐 郄 郇 郓 郦 郢 郜 郗 郛 郫
0xDBB0 郯 郾 鄄 鄢 鄞 鄣 鄱 鄯 鄹 酃 酆 刍 奂 劢 劬 劭
0xDC80 軃 軄 軅 軆 軇 軈 軉 車 軋 軌 軍 軏 軐 軑 軒 軓
0xDC90 軔 軕 軖 軗 軘 軙 軚 軛 軜 軝 軞 軟 軠 軡 転 軣
0xDCA0 軤 堋 堍 埽 埭 堀 堞 堙 塄 堠 塥 塬 墁 墉 墚 墀
0xDCB0 馨 鼙 懿 艹 艽 艿 芏 芊 芨 芄 芎 芑 芗 芙 芫 芸
0xDD80 輤 輥 輦 輧 輨 輩 輪 輫 輬 輭 輮 輯 輰 輱 輲 輳
0xDD90 輴 輵 輶 輷 輸 輹 輺 輻 輼 輽 輾 輿 轀 轁 轂 轃
0xDDA0 轄 荨 茛 荩 荬 荪 荭 荮 莰 荸 莳 莴 莠 莪 莓 莜
0xDDB0 莅 荼 莶 莩 荽 莸 荻 莘 莞 莨 莺 莼 菁 萁 菥 菘
0xDE80 迉 迊 迋 迌 迍 迏 迒 迖 迗 迚 迠 迡 迣 迧 迬 迯
0xDE90 迱 迲 迴 迵 迶 迺 迻 迼 迾 迿 逇 逈 逌 逎 逓 逕
0xDEA0 逘 蕖 蔻 蓿 蓼 蕙 蕈 蕨 蕤 蕞 蕺 瞢 蕃 蕲 蕻 薤
0xDEB0 薨 薇 薏 蕹 薮 薜 薅 薹 薷 薰 藓 藁 藜 藿 蘧 蘅
0xDF80 還 邅 邆 邇 邉 邊 邌 邍 邎 邏 邐 邒 邔 邖 邘 邚
0xDF90 邜 邞 邟 邠 邤 邥 邧 邨 邩 邫 邭 邲 邷 邼 邽 邿
0xDFA0 郀 摺 撷 撸 撙 撺 擀 擐 擗 擤 擢 攉 攥 攮 弋 忒
0xDFB0 甙 弑 卟 叱 叽 叩 叨 叻 吒 吖 吆 呋 呒 呓 呔 呖

若一个文档里只包含这个表格中的汉字(可以再包含 ASCII 字符),在简体中文 Windows 下在记事本中以 ANSI 编码保存,则再次打开必然乱码。特别地,这个表格本身也会乱码。

据此,你可以构造出各种跟微软「有仇」的文档。比如「联通」,比如「小泉水」。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值