惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

A
About on SuperTechFans
Cyberwarzone
Cyberwarzone
博客园 - Franky
V
V2EX
人人都是产品经理
人人都是产品经理
爱范儿
爱范儿
Martin Fowler
Martin Fowler
The Cloudflare Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
Vercel News
Vercel News
Blog — PlanetScale
Blog — PlanetScale
Webroot Blog
Webroot Blog
Hacker News: Ask HN
Hacker News: Ask HN
Forbes - Security
Forbes - Security
D
Docker
C
CXSECURITY Database RSS Feed - CXSecurity.com
Project Zero
Project Zero
大猫的无限游戏
大猫的无限游戏
云风的 BLOG
云风的 BLOG
C
Cyber Attacks, Cyber Crime and Cyber Security
Recent Announcements
Recent Announcements
L
LINUX DO - 热门话题
小众软件
小众软件
Stack Overflow Blog
Stack Overflow Blog
G
Google Developers Blog
S
Security @ Cisco Blogs
T
Threat Research - Cisco Blogs
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
The Register - Security
The Register - Security
O
OpenAI News
雷峰网
雷峰网
P
Proofpoint News Feed
宝玉的分享
宝玉的分享
Scott Helme
Scott Helme
Google DeepMind News
Google DeepMind News
博客园 - 司徒正美
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Help Net Security
Help Net Security
F
Full Disclosure
Engineering at Meta
Engineering at Meta
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
T
Tailwind CSS Blog
Google DeepMind News
Google DeepMind News
博客园 - 三生石上(FineUI控件)
Apple Machine Learning Research
Apple Machine Learning Research
Attack and Defense Labs
Attack and Defense Labs
T
Tenable Blog
AI
AI
Spread Privacy
Spread Privacy

Yesterday17's Blog

2026 新年解密红包 / Melody Flag | Yesterday17's Blog 谈谈 Iori 的设计思路(二):如何实现一个 Showroom 录制工具? | Yesterday17's Blog 谈谈 Iori 的设计思路(一):从 Nico Timeshift 说起 | Yesterday17's Blog Iori Minyami 0.1.0 发布 | Yesterday17's Blog 2025 新年解密红包 / Melody Flag | Yesterday17's Blog 使用 Cloudflare Warp 解决罗森票务的海外登录问题 | Yesterday17's Blog How To Blog 04: The Astro v5 Era | Yesterday17's Blog 谈谈 tokio::select! 的公平性 | Yesterday17's Blog Learning Pingora 05 - Connect with TLS | Yesterday17's Blog Leaving Bytedance | Yesterday17's Blog 大橋彩香 AsiaTour「Reflection」上海公演 个人向记录 & Repo | Yesterday17's Blog Recoving from burnout - What happened? | Yesterday17 Yubikey 重建手册 | Yesterday17's Blog How To Blog 03: Heimus | Yesterday17's Blog 🪧 Blog Migration Accouncement | Yesterday17's Blog Learn Your IDE - VSCode 是如何仅重启插件的? | Yesterday17's Blog How To Blog 02: Astro❤️Password | Yesterday17's Blog How To Blog 01: Why, How, and the Future | Yesterday17's Blog Learning Pingora 04 - Establish L4 Connection | Yesterday17's Blog Learning Pingora 03 - Upstreams and Peers | Yesterday17's Blog Learning Pingora 02 - A Simple HTTP Server | Yesterday17's Blog Learning Pingora 01 - Getting Started | Yesterday17's Blog 2024 新年解密红包 / Melody Flag | Yesterday17's Blog 向新的一年飞驰——记录 2023 | Yesterday17's Blog 「サクラノ刻」对话选摘(2) | Yesterday17's Blog PGP Key Revocation 注销声明 | Yesterday17's Blog 「サクラノ刻」对话选摘(1) | Yesterday17's Blog 2023 新年解密红包 / Melody Flag | Yesterday17's Blog 『蒼の彼方のフォーリズム』通关感想 | Yesterday17's Blog 单显卡直通教程 | Yesterday17's Blog 对博客与笔记的思考 | Yesterday17's Blog Project Anni 之旅(3)自动化 Flutter 应用 CI/CD 上架流程 | Yesterday17's Blog AsobiStage 直接播放链接 | Yesterday17 如何在后分P时代进行投稿——sswa使用详解 | Yesterday17's Blog JSON RPC 与 LSP 协议基础 | Yesterday17's Blog Grajapa Shueisha / BookEnd 加密方式调查 | Yesterday17's Blog 【2022篇+WriteUp】如何再收一个新年红包? | Yesterday17's Blog 如何将良心云的良心功能清理干净 | Yesterday17's Blog 【油猴脚本】bilibili 投稿页面返回旧版+旧版页面强制允许分P上传 | Yesterday17's Blog Cloudr1v1 授权方式分析 | Yesterday17 Typora 1.0.2 逆向实录 | Yesterday17's Blog Project Anni 之旅(2)ValueAfterTable——toml-rs的实现与限制 | Yesterday17's Blog IPv4透明代理+IPv6 Passthrough——树莓派单臂软路由折腾记 | Yesterday17's Blog Chaos; Child 汉化补丁 神秘编码探索 | Yesterday17's Blog 镣铐与舞蹈——个性与共性之迷思 | Yesterday17's Blog Go 学习笔记 02 - 找准 io 之道 | Yesterday17's Blog NAT Slipstreaming v1 原理浅析 | Yesterday17's Blog 绕过「9-nine-」的 CDKEY 验证——KrkrPlugin 正(?)向实录 | Yesterday17's Blog 静流的青春纪念册——「サクラノ刻 -櫻の森の下を歩む-」体验版感言 | Yesterday17's Blog Project Anni 之旅 01 - 从 clap-builder 到 derive | Yesterday17's Blog [Google CTF 2021] CPP WriteUp | Yesterday17's Blog 获取 アソビステージ 的实际播放链接 | Yesterday17's Blog 90 行 Rust 代码实现 AsyncTeeReader | Yesterday17's Blog 或许还算有价值一读的文章列表 | Yesterday17's Blog 从零开始的 Seedbox 之旅 | Yesterday17's Blog [随笔]技术型博客行文迷思(1) | Yesterday17's Blog 浅谈 git fetch 的工作方式 | Yesterday17's Blog 『ソーサレス*アライヴ! ~the World's End Fallen Star~』通关感想" | Yesterday17's Blog Rust std::fmt 格式语法简述 | Yesterday17's Blog 日亚修改居住国的解决方案 | Yesterday17's Blog [Windows/Linux] GC553 的 Switch 完美采集之路 | Yesterday17's Blog 【翻译】Subtyping and Variance / 子类型与变型 | Yesterday17's Blog Berd's Red Envelope 2021 WriteUp | Yesterday17's Blog 【中英对照】ALSA 音频 API 使用教程/A Tutorial on Using the ALSA Audio API | Yesterday17's Blog 从 cue_scanner.l 看 CUE Sheet 的词法单元 | Yesterday17's Blog Postman 历史记录导出的解决方案 | Yesterday17's Blog 《恋爱绮谭 不存在的夏天》通关感想 | Yesterday17's Blog [微机实验/TD-PITE] 微机接口综合实验 | Yesterday17's Blog [微机实验/TD-PITE] 键盘扫描及数码管显示实验 | Yesterday17's Blog [微机实验/TD-PITE] 数码管显示实验 | Yesterday17's Blog Airsonic Advanced+Google Drive+Caddy 部署纪实 | Yesterday17's Blog X-NUCA 2020 - hellowasm 题解 | Yesterday17's Blog [微机实验/TD-PITE] 8251 串行接口实验 | Yesterday17's Blog Node.js child_process.fork 与 env 污染 RCE | Yesterday17's Blog EP.01 「夜の向日葵」 | Yesterday17's Blog [微机实验/TD-PITE] 8254 定时/计数器实验+选做实验 | Yesterday17's Blog [JLU CTF/2020] babywasm WriteUp | Yesterday17's Blog PHP 反序列化与经典利用 | Yesterday17's Blog WebAssembly 逆向简述 | Yesterday17's Blog 『彼女、お借りします』一期完结点评 | Yesterday17's Blog [微机实验/TD-PITE] D/A 转换实验+选做实验 | Yesterday17's Blog [微机实验/TD-PITE] A/D 转换实验+选做实验 | Yesterday17's Blog 开源项目申请 JetBrains Open Source License 简单流程 | Yesterday17's Blog 微软拼音与 JetBrains 搜索快捷键冲突的解决方案 | Yesterday17's Blog [微机实验/TD-PITE] 8259 中断优先级实验+选做实验 | Yesterday17's Blog IFTTT 测试(续) | Yesterday17 IFTTT 测试 | Yesterday17's Blog [微机实验/TD-PITE] 存储器扩展实验+选做实验 | Yesterday17's Blog 新版 GCC 针对 -fdump-translation-unit 的替代方案 | Yesterday17's Blog 一次 HSTS 策略配置的排错之旅 | Yesterday17's Blog YukiNative 踩坑记——Windows 的消息队列 | Yesterday17's Blog 我是我自己——论获取 HTTPS 证书时的验证步骤 | Yesterday17's Blog 【设计文档】对 PUG 的大规模设计修订(1.1) | Yesterday17's Blog GS65 折腾记(2)加装固态,分区,Grub2 引导 Manjaro LiveCD | Yesterday17's Blog GSuite 探索篇(1)使用 Service Account 向 Google Drive 传输文件 | Yesterday17's Blog 『サクラノ詩 -櫻の森の上を舞う-』通关感想 | Yesterday17's Blog 《ATRI -My Dear Moments-》通关感想 | Yesterday17's Blog [工具][VSCode 扩展] AegiKit——方便 Aegisub 使用的工具箱 | Yesterday17's Blog 贝塞尔曲线、字体矢量化与曲线运算 | Yesterday17's Blog NAT 类型初探 | Yesterday17's Blog
「さくら、もゆ。」的空白字体列表——一次逆向问题定位过程实录 | Yesterday17's Blog
Yesterday17 · 2020-08-09 · via Yesterday17's Blog

大家好,好久不见,我是某昨。

复习之余摸鱼「さくら、もゆ。」,结果遇到了这样的问题:

之前还没在意,打开一看果然如此:

字体列表空绝对很奇怪吧!但是咕咕噜上没有任何类似的问题反馈。于是借着这个机会,把 Notion 最小化,我开始摸鱼了(

ToC

  • 初期调查
    • 字符串
    • Imports
  • EnumFontFamiliesExA
  • EnumFontFamExProc(Proc)
    • a3 & 4
    • a1->lfFaceName[0] != 64
    • !strcmp(&a1[2].lfFaceName[8], (const char*)&unk_45E560)
    • 综上
    • 解决方案 A
  • 解决方案 B
    • 调试方式
    • 比较失败?
    • 解决
  • 解决方案 C
    • 调试方式
    • 分析
    • 复现
    • 解决
    • 思考
  • 写在最后

初期调查

字符串

既然是字体相关的问题,那首先想到的就是去找字体。我上来先是在 Strings Window 里搜索了 Font

其实这里最合我胃口的是 .?AVFontList@@,但用 X 找不到引用。又看了看其他的那几个,都没什么有价值的东西。

Imports

字符串无果之后,我又开始寻找新的切入点。下一个切入点是 Imports 列表,同样是在这里搜索 Font

可以看到有两个 GDI32 的函数,而最引人注意的就是第二个。Enum 看上去就很遍历,那是不是这个呢?

EnumFontFamiliesExA

首先是看这个函数的调用。通过 X,我们找到了 sub_443BC0,再用 Tab

在了解了传入参数之后,接下来就是查文档的时光了。在 MicroSoft Docs 上,我找到了这个函数的文档[1]

int EnumFontFamiliesExA(

HDC hdc,

LPLOGFONTA lpLogfont,

FONTENUMPROCA lpProc,

LPARAM lParam,

DWORD dwFlags

);

可以看到,第三个参数是 lpProc,对应的是 EnumFontFamExProc 类型。而去看 EnumFontFamExProc 的文档,我们发现这其实是一个回调,对应这个回调的就是上面第 13 行的 Proc

EnumFontFamExProc(Proc)

首先还是查文档[2]

int CALLBACK EnumFontFamExProc(

const LOGFONT *lpelfe,

const TEXTMETRIC *lpntme,

DWORD FontType,

LPARAM lParam

);

同时进入 Proc,我们来看伪代码:

先看这几个参数吧。a1LOGFONTA 类型的指针,对应的是字体的信息;a3 是字体类型。

接下来就是这个 if 的判断了。if 判断有三个条件,我们逐个来看。

a3 & 4

我们已经知道,a3 对应的是字体类型,而从文档中,我们找到了更加详细的内容:

从一般角度来考虑,DEVICE_FONTTYPE 应该是 1RASTER_FONTTYPE 应该是 2TRUETYPE_FONTTYPE 应该是 4,这样才方便通过 AND 进行比对。于是这个判断条件的含义就是:字体类型为 TrueType

a1->lfFaceName[0] != 64

这个就比较简单了。ASCII 码的 64 对应的是符号 @。其实看到这个符号的时候就已经能够明白了(笑),这是防止列表中加入竖排字体的选项。

!strcmp(&a1[2].lfFaceName[8], (const char*)&unk_45E560)

这是最后也是最难判断的条件了。首先我们发现,他使用的是 !strcmp,也就是期望两个字符串相等

第一个字符串是从下标 8 开始到结束,而第二个字符串看上去像是一串没有意义的数据:

鉴于这是字符串,我们先把它导出:

然后尝试用 VSCode 打开:

干脆利落地乱码了。这时候我们选择ShiftJIS 重新打开文件

破案了。

综上

综上,我们明白了这三个判断条件的意义。用一条注释来概括吧:

解决方案 A

在知道了问题的原因之后,解决起来就简单多了。这里用了最粗暴的方式,直接把这个 ! 删掉了。修改的部分在这里:

对应的 IDA View B 是这样的(把 jnz 替换成了 jz):

对应到伪代码就是少了个 !

这样就可以正常显示字体列表了:

解决方案 B

上面这种方案虽然直接运行没什么问题,但是还是略显粗暴了。而且还有一个关键的问题:会导致 Locale Emulator 无法正常工作,没法启动的同时在游戏目录下面给你塞一个 core dump。于是这次借着这个不完美的机会,我决定来探究一下这个问题的本质。

我们知道,这个问题源于不同语言的 Windows 对某些内容的处理不尽相同。对于 Windows 而言,编码显然虽然是大多数问题的根源,但却不是导致所有问题的罪魁祸首。导致问题的核心是语言本身的差别

当然了,在研究之前我们是不知道这一点的。我只是单纯地将问题怪罪于 ShiftJIS甚至这篇文章的原拟定标题中就有 ShiftJIS。虽然 ShiftJIS 确实不行太傻逼了,但幕后黑手另有其人。下面我们来看。

调试方式

在正式介绍之前,迫于 IDA 7.0 调试的严重 bug,我不得不在这里提一句解决方案。

IDA 7.0,直接 F9 是无法正常动态分析的,会出现 internal error 1491,进而导致 IDA 崩溃。

唯一的解决方案是通过远程调试,即通过 127.0.0.1 下的 Remote Debug 来曲线救国。进入 IDA 安装目录,打开 dbgsrv/win32_remote.exe 文件,然后再 IDA 里设置远程调试的 IP127.0.0.1

然后就可以正常调试了。

比较失败?

既然问题出在 strcmp 上,那也就自然而然地有了第二种解决问题的灵感:把 ShiftJIS 出问题的「日本語」替换掉。于是,我们希望知道和「日本語」(ShiftJIS 版)作了比较的原文究竟是什么。

要探究这个问题,我们就必须要回到汇编,而不能单纯依靠 HexRay 了。unk_45E560 附近的代码是这样的:

可以看到,在 loc_443B43 下第二行就是 cmp,比较的是存有 unk_45E560 地址的 ecx 的内容和 [eax]eax 是通过计算得出的,值相当于 arg_0+9Ch,对应的就是伪代码中 [2].lfFaceName[8] 的部分。

于是我们尝试在 cmp 这行打上断点,看看 [eax] 里究竟存了什么:

F9 运行:

切换到 Hex View

见鬼,这太简体中文了

解决

解决这个问题的方式是修改 unk_45E560 的值。鉴于新的值比旧的要短,我们只需要在多出来的地方补 \0 就可以了:

至此,不优雅的方案 A 就可以抛弃了。

解决方案 C

上面这种方案使用的思想从根本上解决问题,但对于 Locale Emulator 而言,仍然是不可用的。LE 本身存在兼容性问题不说,也不能完全解决所遇到的问题,导致对于各个不同游戏实现而言,存在着大量的兼容性问题。

解决方案 C 不是对解决方案 B 的修正,它只是另一种工作环境下的 Polyfill 罢了。可以说,方案 C 是方案 B 在 LocaleEmulator 环境下的兼容版本。那究竟该如何兼容呢?

调试方式

这里还是要插播一条调试新闻,不过这次不是 IDA 的问题了,而是 LE 的。

首先我们知道,我们需要通过 LE 启动游戏才能达到转区的效果,但这个“启动”的过程对于 IDA 而言就不是那么好办了。我们希望的是游戏启动时的状态是挂起,这样我们才能把调试器 Attach 上去。不过好在 LE 提供了这个功能:

通过这个功能我们终于可以附加到进程了。不过这还不够,无论是 IDA 还是 OD,都没办法正常调试,我们来看解决方案。

首先是 Attach 上去之后的效果:

F9 继续运行,等到 Threads 只有一个时(大约要等一到两分钟),我们会发现 Sakura.exe 进程不知道为什么又被挂起了。这时使用系统自带的资源管理器恢复进程:

就可以正常调试了。

分析

我们跟方案 B 时候一样,把断点打在 cmp dl, [ecx] 处:

这时候来看 [eax] 的内容:

我们发现,[eax] 的内容是 93FA3F,对应的东西不知所云。但其实,我们只需要通过 VSCode 就可以明白这串文本的来历了。

复现

打开 VSCode 新建文件,将编码更改为 GB2312,然后输入“日语”二字。

保存文件,然后将编码更换为 ShiftJIS

最后通过 hexdump 插件打开文件:

我想,我们已经找到答案了。

解决

这个解决方案也很简单,只需要将解决方案 B 中的 C8 D5 D3 EF 修改为 93 FA 3F 就可以了。

于是我们又多了一个 Sakura.exe

思考

不难想到,这里是 LE 把对应的 GB2312 转成了 ShiftJIS。但 ShiftJIS 里并没有简体中文的“语”,于是导致这串内容不仅无法通过匹配,还变成了只有一半内容正确的乱码。也正是由于这个原因,方案 B 才无法对 LE 起效。

写在最后

我明明只是想摸一会鱼,为什么会这样呢

嘛,总之问题是解决了,太好了(

文件存了一份在 GDrive,链接是这个w