米米的博客

做了一点微小的工作

CVE-2023-21537 是一个 Windows 消息队列(MSMQ)驱动程序 mqac.sys 中的漏洞。该漏洞于 2023 年 1 月披露,并已被微软修复。漏洞并没有公开的 PoC 程序,漏洞发现者只通过文章 Racing bugs in Windows kernel 透露了部分信息。笔者在其基础上深入分析了相关代码,成功地复现了此漏洞。本文就是对漏洞研究的总结。

漏洞复现环境搭建

由于此漏洞已经在较新的系统中被修复,复现漏洞需要在旧版本的系统中进行。笔者在 Hyper-V 虚拟机中安装了 Windows 10 21H1 版本,内部版本号为 19043.928。此外,由于消息队列是 Windows 的可选功能,需要在控制面板中手动启用。方法是:打开控制面板,选择「程序」,点击「启用或关闭 Windows 功能」,并开启「Microsoft 消息队列(MSMQ)服务器」。如下图所示。

启用消息队列

之后,打开计算机管理,在侧栏中选择「服务和应用程序」,点开「消息队列」下面的「专用队列」,并右键新建专用队列。队列名称需要记下来,之后的 PoC 程序里面需要用到。完成后,情况如下图所示。

新建专用队列

为了方便调试 PoC 程序,在虚拟机中还可以开启内核调试,这样就可以通过 Host 上的 WinDbg 给虚拟机的 Windows 内核下断点进行调试了。方法是开启管理员权限的 Powershell,执行如下命令

1
2
bcdedit /debug on
bcdedit /dbgsettings net hostip:192.168.1.2 port:50001 key:1.2.3.4

hostip 换成 Host 的 ip 地址,portkey 可以修改。完成后,重启虚拟机,之后在 WinDbg 里面用这些参数就可以对虚拟机的内核进行 debug 了。

漏洞分析

根据漏洞发现者的文章,该漏洞的成因是 mqac.sys 中的 ACSendMessage 函数会两次读取一个来自用户的输入参数,第一次该参数用于控制数组长度,第二次则是在释放堆内存时,根据该长度进行释放。然而,这段逻辑并未考虑参数会被用户修改的可能,因而构成一个 Double fetch 漏洞,可能导致错误的内存被释放。

mqac.sys 提供了 IoControl 调用的处理函数,名称为 ACDeviceControl,该函数将会解析用户传入的参数,并调用不同的派发函数。通过逆向分析 ACDeviceControl 函数,发现当 IoControl 调用号为 0x19658107 且输出缓冲区的总长度为 0x2C0 时,它会进一步调用 ACSendMessage 这一派发函数。IDA 反编译得到的关键代码如下(省略了部分上下文):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__int64 __fastcall ACDeviceControl(struct _DEVICE_OBJECT *DeviceObject, struct _IRP *Irp)
{
struct _IO_STACK_LOCATION *CurrentStackLocation Irp->Tail.Overlay.CurrentStackLocation;
unsigned int LowPart = CurrentStackLocation->Parameters.Read.ByteOffset.LowPart;
__int64 Length = CurrentStackLocation->Parameters.Read.Length;
const struct CQueueBase *FsContext = CurrentStackLocation->FileObject->FsContext;
NTSTATUS Information;
// ...
switch ( LowPart )
{
case 0x19658107u:
if ( (_DWORD)Length == 704 )
{
Information = ACSendMessage(DeviceObject, Irp, Options, FsContext, (struct CACSendParameters *)UserBuffer);
goto LABEL_246;
}
// ...
}
// ...
}

ACSendMessage 函数首先将用户态缓冲区复制到内核态栈上的缓冲区,之后,将执行核心的业务逻辑,调用 CQueue::PutNewPacket 来发送用户请求的数据,完成后再调用 ACFreeDeepCopyQueueFormat 进行堆内存的释放。此处便存在漏洞:进行内存释放操作传入的第二个参数直接读取自用户态缓冲区中。下面是 IDA 反编译得到的关键结果,参数 UserBuffer 就是指向用户缓冲区的指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
__int64 __fastcall ACSendMessage(
struct _DEVICE_OBJECT *DeviceObject,
struct _IRP *Irp,
unsigned int Options,
const struct CQueueBase *FsContext,
struct CACSendParameters *UserBuffer)
{
const struct BOID *v15;
PVOID Contents[36];
memset(Contents, 0, 0x118ui64);
_DWORD *DeviceExtension = DeviceObject->DeviceExtension;
int v9 = CQueueBase::Validate(FsContext);
if ( v9 >= 0 )
{
if ( DeviceExtension[246] )
{
ACDeepProbeSendParams(DeviceObject, UserBuffer, (struct ACSendParametersPointerContents *)Contents);
v15 = (const struct BOID *)*((_QWORD *)UserBuffer + 45);
}
// ...
if ( Contents[23] )
ACFreeDeepCopyQueueFormat((char *)Contents[23], *((_DWORD *)UserBuffer + 148));
// ...
}
// ...
}

这样问题看上去就很明确了:我们首先获得指向 MQAC 虚拟设备的句柄,然后向其发送号码为 0x19658107 的 IoControl 调用,并通过调整参数使 ACFreeDeepCopyQueueFormat 函数能够执行。这又要满足 Contents[23] 不为零的约束条件。观察之后发现 Contents 先被初始化为了全 0,然后传入了 ACDeepProbeSendParams 这个函数里面,所以还要设法让这个函数帮我们修改 Contents[23],才能触发漏洞。

阅读全文 »

内网穿透是一种反向代理技术,允许你从任何位置访问一台处于复杂网络环境中的机器。它的应用场景有很多,例如,你在家中使用树莓派作为 Home Assistant 家庭中枢,但家庭网络的网关没有公网 IP,那么在离开家时就无法控制家中的智能设备。而在配置内网穿透后,就可以随时随地通过代理服务器的公网 IP 连接到树莓派,从而访问 Home Assistant 的服务。

内网穿透的原理是,利用一个具有固定公网 IP 的主机做跳板,让两个客户端建立 P2P 连接;如果不成功,就通过跳板主机中转,将所有流量代理到内网中的主机上。为不同需求所设计的内网穿透解决方案有很多,例如 frp 和 Cloudflare Tunnel。frp 允许你将内网中主机的端口映射到公网的主机上,因此使用起来非常直接。而 frp 的缺点是主机和端口都需要单独配置,如果要用多台设备组网,会有些繁琐;此外,这些端口都会直接暴露在公网上,安全性较差。另一个方案 Cloudflare Tunnel,可以直接用 Cloudflare 的节点进行中转,是非常不错的选择,但缺点和 frp 类似,在多台设备需要互相访问时很麻烦。

经过实验,如果需要将不同网络环境中的多台设备都配置内网穿透,并能够互相访问,那么使用 ZeroTier 搭建虚拟局域网是一个不错的选择。下面笔者将会介绍具体的配置方法。

准备

首先,打开 ZeroTier 官网,注册账号,然后根据使用的设备系统下载客户端并安装。ZeroTier 安装完成后,将会创建一个虚拟网卡。

在 ZeroTier 客户端中,输入 Network ID 并选择加入,你的设备就将分配到一个 ZeroTier 虚拟网络的 IP 地址。将需要内网穿透进行联网的设备都安装好 ZeroTier 客户端,并加入同一个 Network ID 的网络,就可以通过分配的 IP 地址互相访问了。就是这么简单!

阅读全文 »

在文章 DIY 辉光管时钟中,笔者介绍了自己设计辉光管时钟数字逻辑部分的一些思路。除此之外,升压电路也是在驱动辉光管时必不可少的组件,但由于当时缺乏设计经验,所以没有研究这个问题,而是买了一个成品的基于 MC34063 的升压电路板。这个方案其实是有一些问题的,比如说需要 12V 输入电压才能工作,并且转换效率有待商榷。所以笔者决定自己设计一个升压电路,这样就可以完全掌握整个电路的设计。

当然,我还没有能力完全从零开始设计这样一个电路,得先参考一下现有的解决方案。目前市面上已有的方案,除了 MC34063,还有 MAX1771 和 LM3481 等。经过搜索,发现 OMNIXIE 出品的 NCH8200HV 非常满足我的需求。它的体积非常小巧,集成起来很方便,并且只需要 5V 输入就可以驱动。但是,OMNIXIE 为了保护版权,没有公开 NCH8200HV 的原理图,售卖的成品中两个 IC 和一个二极管上的丝印也被打磨掉了。通过肉眼识别,可以发现电路板上有一个型号为 TTRN-060S-054 的变压器,和阻值分别为 0.01 欧、499 千欧、69.8 千欧和 10 兆欧的贴片电阻。

OMNIXIE NCH8200HV

结合这些信息,可以搜索得到一个开源的升压电路原理图:辉光管 5V-170V 升压测试模块原理图 PCB 工程文件。这个方案基于 MAX668 芯片和型号为 TPH1R403NL 的 MOS 管。其中,69.8 千欧和 10 兆欧的电阻是为了分压进行电压反馈,而 0.01 欧的电阻是电流采样电阻;499 千欧的电阻则是控制 MAX668 输出的 PWM 频率。通过观察 NCH8200HV 上一些关键元件的连接方式,可以判断出这个开源的原理图使用的是与之相同的拓扑:耦合电感式的 Boost 电路。

阅读全文 »

网盘是一种存储、备份、分享文件的便捷方式,在近十年得到了长足发展。许多公司都在自家产品的生态圈里加入了云存储的功能,例如苹果的 iCloud 和微软的 OneDrive,它们的侧重点是本地文件的备份和同步。而百度网盘和已经凉掉的 360 网盘,则注重于文件分享和离线下载。只可惜很多网盘服务商和迅雷这样的下载工具都有会员制度,并对非会员进行了下载限速,这对免费用户很不友好。那么,有没有办法集合这些网盘服务的优点呢?答案是存在的,那就是自行部署私有云服务。无论是一台树莓派,还是一个 VPS 主机,都可以轻松地搭建支持离线下载、方便进行同步和分享的私有云。

目前几种主流的私有云服务包括:SeafilePydioownCloudNextcloud。这些服务都各有特色。SeaFile 面向的是企业网盘,基于 Python 开发。Pydio 新推出的 Cells 由 Go 语言重写,提供面向团队的文件共享服务,但是在社区支持上有所欠缺。而 ownCloud 和 Nextcloud 则是基于 PHP 的开源项目,Nextcloud 是由原来的 ownCloud 团队成员独立出来开发的,它们有很多相似之处,但 Nextcloud 在许多方面都更胜一筹(例如移动端的支持)。
经过笔者自己的测试,不管是搭建个人使用还是团队协作用的私有云服务,Nextcloud 的功能基本上都可以击败其他的竞争对手。这篇文章也将主要介绍 Nextcloud,其它服务的搭建方式可以在它们各自的官网上找到。

安装 Nextcloud

笔者几年前安装过一次 Nextcloud,当时还需要手工配置 PHP 和 MySQL 的环境,步骤有些繁琐。现在,Nextcloud 已经提供了一键安装的 Docker 镜像,可以让我们在几分钟内搭建起一个 Nextcloud 服务。使用方法是,创建一个 docker-compose.yml 文件,并填入以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
version: '2'

volumes:
nextcloud:
db:

services:
db:
image: mariadb:10.6
restart: always
command: --transaction-isolation=READ-COMMITTED --log-bin=binlog --binlog-format=ROW
volumes:
- db:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=
- MYSQL_PASSWORD=
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=nextcloud

app:
image: nextcloud
restart: always
ports:
- 8080:80
links:
- db
volumes:
- nextcloud:/var/www/html
environment:
- MYSQL_PASSWORD=
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=nextcloud
- MYSQL_HOST=db

其中,需要设置 MYSQL_ROOT_PASSWORD 和两个相同的 MYSQL_PASSWORD,使 Nextcloud 可以连接数据库。然后,运行以下命令:

1
docker-compose up -d

完成后,访问 http://localhost:8080 就可以看到 Nextcloud 的页面了。在初次登录时,需要设置管理员账号和密码,然后就可以开始使用了。

配置 Cloudflare Tunnel

如果服务器有公网 IP,那么配置好防火墙就可以直接访问 Nextcloud 了。但是,如果服务器没有公网 IP,那么就需要进行内网穿透。下文将介绍使用 Cloudflare 的 Tunnel 服务来实现内网穿透的方法。

首先,需要注册 Cloudflare 账号,并将域名托管在 Cloudflare 上。随后,在 Cloudflare 的控制面板中点击 Zero Trust,并在侧栏目录中选择 Access 下的 Tunnels。在这个页面中,就可以创建一个新的 Tunnel。在 Tunnel 的设置页面中,可以根据你的操作系统选择安装的命令。以 Debian 系为例,命令类似

1
2
3
curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared.deb
sudo cloudflared service install ...

完成后,就可以在 Public hostnames 面板中设置公网访问的域名了。将域名设置为你的一个子域名,并将添加的 Service 设置为 http://localhost:8080 即可。这样,我们就可以在外网访问 Nextcloud 了。

根据微软的文档 Options for using Windows 11 with Mac® computers with Apple® M1® and M2™ chips,现在可以通过 Parallels® Desktop 18 在 M1 Mac 上安装 ARM 架构的 Windows 虚拟机。本文将介绍具体的步骤。

首先是安装 Parallels® Desktop,具体步骤可以参考 ParallelsDesktopCrack

然后,需要准备一个 ARM 架构的 Windows ISO 镜像。微软并没有直接提供镜像的下载,但所需的文件其实都可以通过 Windows Update 的服务器获得。通过网站 UUP dump,可以方便的获取到生成 ISO 镜像的脚本文件。

打开 UUP dump 网站后,在搜索框中输入关键词 “Windows 11 arm64”,并将搜索结果按照时间排序。结果中可能包括稳定版本和 Insider Preview 版本。笔者测试时发现,Insider Preview 版本的资源可能下载失败,因此可以选择稳定版本,例如 Windows 11, version 22H2 (22621.1485) arm64。之后,按照提示选取语言和系统版本,即可进入到下载页面。

UUP dump下载页面

点击 Create download package 按钮,即可下载到一个包含脚本文件的压缩包。

压缩包中存在一个名为 readme.unix.md 的文档,里面给出了继续生成 ISO 镜像所需要的软件依赖。不过,笔者测试时,发现其中的依赖 [email protected] 在 M1 Mac 上无法正常安装。要解决这个问题,安装方法应改为:

1
2
brew tap minacle/chntpw
brew install cabextract wimlib cdrtools minacle/chntpw/chntpw

在安装完成后,即可执行

1
bash uup_download_macos.sh

如果一切顺利的话,此目录下将出现一个名称类似 22621.1_MULTI_ARM64_ZH-CN.ISO 的镜像文件。最后,在 Parallels Desktop 中创建一个新的虚拟机,选择 ISO 镜像文件安装系统即可。

Parallels Desktop安装Windows 11


参考文章:How to Download and Install Windows 11 ARM With ISO

0%