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

推荐订阅源

D
Docker
爱范儿
爱范儿
人人都是产品经理
人人都是产品经理
博客园 - 司徒正美
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
量子位
罗磊的独立博客
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
小众软件
小众软件
C
Cybersecurity and Infrastructure Security Agency CISA
Cyberwarzone
Cyberwarzone
大猫的无限游戏
大猫的无限游戏
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
雷峰网
雷峰网
Simon Willison's Weblog
Simon Willison's Weblog
The Cloudflare Blog
博客园 - 三生石上(FineUI控件)
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
Cyber Attacks, Cyber Crime and Cyber Security
博客园_首页
博客园 - 叶小钗
V
Vulnerabilities – Threatpost
T
The Exploit Database - CXSecurity.com
T
Tailwind CSS Blog
IT之家
IT之家
博客园 - 聂微东
Spread Privacy
Spread Privacy
V2EX - 技术
V2EX - 技术
S
Security Affairs
宝玉的分享
宝玉的分享
V
V2EX
C
Cisco Blogs
博客园 - Franky
美团技术团队
酷 壳 – CoolShell
酷 壳 – CoolShell
月光博客
月光博客
S
Securelist
J
Java Code Geeks
Webroot Blog
Webroot Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
P
Proofpoint News Feed
Last Week in AI
Last Week in AI
L
LINUX DO - 热门话题
NISL@THU
NISL@THU
WordPress大学
WordPress大学
W
WeLiveSecurity
T
Threatpost
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
腾讯CDC
阮一峰的网络日志
阮一峰的网络日志

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 [微机实验/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 「さくら、もゆ。」的空白字体列表——一次逆向问题定位过程实录 | 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
X-NUCA 2020 - hellowasm 题解 | Yesterday17's Blog
Yesterday17 · 2020-11-01 · via Yesterday17's Blog

前言

在比赛结束后一小时,得出了最终结果——

怎么说呢,虽然有点遗憾,但也是做出来了。至少没白费这几小时的时间吧(笑)

比赛期间没做出来不叫 WriteUp((

开始

开始游戏。首先尝试用 JEB 读一下:

可以看到,逻辑还是非常清楚的(部分函数名为手动替换)。但美中不足的是对最关键的 f8f9 不能查看伪代码,好像是 br_table 没做好,因此只能暂时放弃(

在等待了一段时间后,群里出现了 wasm2c 编译得到的 hello.o 文件。于是决定到 Windows 下用 F5 一战——

f9(base64)

f9 这个函数有超级明显的特征,就是这个字符串:

这东西过于 base64 了,甚至最后的 +/ 都完全一致。其得到结果的长度在动态调试下也和 base64 的一致,这就很有趣了。我们借助 IDAF5 来看。首先是没进入循环的初始化部分:

注意到 72 行有一个 strlen 函数。这个函数并不是 IDA 读出来的,而是在动态调试中试出来的。这里也有一个经验谈吧,就是以字符串为输入的 WASM 程序中基本都会有一个用来计算字符串长度的函数。

接下来的 74-83 行是和 3 有关的数学运算。这里就是 base64 的长度计算了:当整除 3 时即为 /3*4,当不整除时要多补 4 位。

然后是分配内存,并且将内存空间的最后一位置 \0,最后则是将两个数据置了 0。这里又是一个经验谈,在这种地方初始化成 0 的一般都是循环里面用到的变量。所以在这里简单将其命名为 ij

看完了初始化的部分,我们来看下面的循环:

首先是跳出循环的条件,姑且看懂就好。由于我们猜测这个函数和 base64 有较大关联,因此主要要做的就是代码,并与 base64 的源码进行比对。最先完成的是 addr[i] 的部分。

然后如法炮制,可以得到 addr[i+1]addr[i+2]addr[i+3] 的部分。

循环最后是对循环变量的处理。到此,接下来的部分基本不需要看了。我们整理一下上面的逻辑,可以写出 C 的对应伪代码:

char* encode(char* str) {

char* addr;

int len;

int i = 0, j = 0;

while (1) {

if (i >= addr + len - 2) {

break;

}

addr[i] = table[str[j] >> 2] ^ 0xa;

addr[i + 1] = table[((str[j] & 3) << 4) | (str[j+1] >> 4)] ^ 0xb;

addr[i + 2] = table[((str[j+1] & 0xf) << 2) | (str[j+2] >> 6)] ^ 0xc;

addr[i + 3] = table[str[j+2] & 0x3f] ^ 0xd;

i += 4;

j += 3;

}

return addr;

}

对比 base64C 语言实现[1]:

unsigned int b64_encode(const unsigned char* in, unsigned int in_len, unsigned char* out) {

unsigned int i=0, j=0, k=0, s[3];

for (i=0;i<in_len;i++) {

s[j++]=*(in+i);

if (j==3) {

out[k+0] = b64_chr[ (s[0]&255)>>2 ];

out[k+1] = b64_chr[ ((s[0]&0x03)<<4)+((s[1]&0xF0)>>4) ];

out[k+2] = b64_chr[ ((s[1]&0x0F)<<2)+((s[2]&0xC0)>>6) ];

out[k+3] = b64_chr[ s[2]&0x3F ];

j=0; k+=4;

}

}

if (j) {

if (j==1)

s[1] = 0;

out[k+0] = b64_chr[ (s[0]&255)>>2 ];

out[k+1] = b64_chr[ ((s[0]&0x03)<<4)+((s[1]&0xF0)>>4) ];

if (j==2)

out[k+2] = b64_chr[ ((s[1]&0x0F)<<2) ];

else

out[k+2] = '=';

out[k+3] = '=';

k+=4;

}

out[k] = '\0';

return k;

}

可以看到,其和标准 base64 实现的唯一区别就是对每一位都进行了一次异或。到此,f9 解析完成。

f8(主逻辑)

f8 的逻辑就复杂多了,不过也不是无迹可寻。看着复杂的代码分解到每个小部分却又各自非常简单,这就是 f8 的状况。

循环之前

我们首先来看进入循环之前的代码。

这里涉及到了大量对 u_addr 内存的操作。这里可以偷个懒,在初始化完成后的地方打上断点,然后把对应内存的内容复制出来:

未整理的内存内容
未整理的内存内容

简单整理一下对应 offset 的实际内容:

可以看到 21282160 分别被存储在了 offset = 4offset = 24,而经过 base64 的数据则是存储在了 offset = 32

循环过程:初探

面对这样一个循环谁都头大。由于循环内是一个巨大的 switch,因此我简单跟踪了一下每次 switch 的取值:

中间发现了一个循环,于是猜测了一下:

循环过程:函数对应

观察每个 switch 分支,可以看到他们都是 call_indirect 调用函数:

这部分可能在 IDA 里观察不大明显,但动态调试跟进去就很直观了。这个技巧在当时逆向 SC 的时候也用到了。

经过简单对比,我们发现:

于是省下了 114514 秒的动态调试时间(

循环过程:逻辑分析

这个过程就是单纯的分析那一连串函数的时间了,总体耗时约 40 分钟。最终得到这样一张图:

其中 _ 开头的表示调用函数,括号内是这个函数执行的顺序。

整理出这样的逻辑后,仔细观察。可以发现,整个比对是基于异或的。在 offset = 20 赋值的比对中,第一次比对应该是实际的数值比对。这次比对比较的是 base64 的值和 offset = 12 处的值,而这处的值经过了两次异或。这也就是最基本的逻辑了。

再整理一下,这张图可以修订成这样:

可以看到,offset = 12 处的值在最初被赋为 0,然后经过两次异或。第一次异或是 base64 对应偏移的内容,而第二次则是 0xee

循环过程:逻辑整理

令输入数组为 b64,加密后数组为 data,当前位下标为 i,我们可以得到如下结论:

\mathtt{b64}_0\oplus\mathtt{0xee}=\mathtt{data}_0 \\

\mathtt{b64}_0\oplus\mathtt{0xee}\oplus\mathtt{b64}_1\oplus\mathtt{0xee}=\mathtt{data}_1

假设输入正确,则可以得到:

\mathtt{data}_{i-1}\oplus\mathtt{b64}_i\oplus\mathtt{0xee}=\mathtt{data}_i

即:

\mathtt{b64}_0=\mathtt{data}_0\oplus\mathtt{0xee} \\

\mathtt{b64}_i=\mathtt{data}_i\oplus\mathtt{data}_{i-1}\oplus\mathtt{0xee}

解密

最终的脚本如下:

data = `0xbe

6

0xac

'

0x99

O

0xde

D

0xee

_

0xda

0x0b

0xb5

0x17

0xb8

h

0xc2

N

0x9c

J

0xe1

C

0xf0

0x22

0x8a

;

0x88

[

0xe5

T

0xff

h

0xd5

g

0xd4

0x06

0xad

0x0b

0xd8

P

0xf9

X

0xe0

o

0xc5

J

0xfd

/

0x84

6

0x85

R

0xfb

s

0xd7

0x0d

0xe3`

.split("\n")

.map(t => (t.length === 1 ? t.charCodeAt() : Number(t)));

b64 = data

.map((d, i) => (i == 0 ? d : d ^ data[i - 1]))

.map(t => t ^ 0xee)

.map((t, i) => {

switch (i % 4) {

case 0:

return t ^ 0xa;

case 1:

return t ^ 0xb;

case 2:

return t ^ 0xc;

case 3:

return t ^ 0xd;

}

})

.map(t => String.fromCharCode(t))

.join("");

atob(b64);

好耶,是十点半做出来的(

后记

到目前为止,我做过的 WASM 题都做出来了,这是不是算 AK 了我人生中所有的 WASM(逃

从这题开始我开始尝试用 IDA 分析 WASM。看过我之前博客(包括加密的两篇 SC)的读者应该知道,我之前都是直接分析 wat 的。但在 wat 行数不断增加的当下,我们也需要快速分析的手段。

JEB 分析很好用,但对于有些代码没办法成伪代码;IDA 配合 F5 也可一用,但混杂了大量无用的代码需要手动去除;Chrome 的动态调试是神,虽然还有不大好用的地方,但能大幅简化分析的难度;WAT 的阅读是基本功,只要会读 WAT,那么逆向 WASM 只是时间问题。

嘛,就是这样(逃

引用

  1. https://github.com/joedf/base64.c/blob/master/base64.c