米米的博客

做了一点微小的工作

近期,笔者接到了维修一台屏幕完全碎裂的 iPad 的请求。根据机主描述,这台 iPad 在多年前由于从高处跌落,外屏完全损坏,无法开机,因此长期没有使用。但是里面有一些重要的照片,希望能够取出来。

经过检查,这台 iPad 型号为 iPad Mini 1(A1432 版本)。机器状况确实非常不理想,不仅屏幕碎裂,后壳也有明显的变形。只能祈祷主板没有受损,不然就超出笔者的维修能力了。

变形的机身和内屏,外屏由于完全碎裂爆出了很多玻璃渣,已经丢弃

这一代 iPad 的外屏是通过胶水粘合的,正常的拆卸方法是加热后将屏幕揭开,但由于外屏已经完全脱落,这一步骤甚至可以省略。进一步拆卸可以参考 iFixit 发布的指南,并不麻烦,主要就是拧螺丝和拆排线,谨慎操作即可,避免损坏一些脆弱的部件。经过拆卸,这台机器的主板外观还比较完好,变形也比较轻微,看上去还能抢救一下。但是,除了主板外,其它很多硬件都存在不同程度的受损。

拆卸出的主板很完整,但电池已经变形了

相比于单独购买屏幕、电池等配件进行替换,更方便的方法是直接买一台好的 iPad,来一手借尸还魂,把坏机器的主板挪过去。在闲鱼上,同款的 iPad Mini 1 已经是白菜价格,外观成色完美的也就大几十块钱,因此这个方案是经济可行的。

硬件维修的部分还算顺利,经过拆机和更换硬件,这台 iPad Mini 1 终于能够正常开机了。

拆机过程

点亮设备

接下来才是真正有趣的部分:这台 iPad 设置了锁屏密码,但是由于多年没有开机,机主已经忘记了密码。如果要将其中的数据备份出来,就得想办法访问到 iPad 的文件系统了。如果设备在越狱后开启了 SSH 服务,我们可以通过 libimobiledevice 提供的 usbmuxd 转发端口,使用 SSH 连接来访问文件系统。但遗憾的是,经过简单的实验,这台 iPad 似乎并没有越狱,SSH 服务也没有启动,这条路行不通。

由于 iPad Mini 1 是非常古早的设备,想必是有很多现成的 n-day 漏洞可以利用。其中,最著名的就是 2019 年由 iOS 安全研究员 axi0mX 披露的 checkm8 漏洞(这个缩写类似 DotA2 中的 str8,8=eight 是读音的缩写,原意即为 checkmate)。checkm8 漏洞存在于 iOS 设备的 ROM 中,通过 USB 发送恶意的 Payload,可以实现任意代码执行,绕过设备的安全启动限制。这个漏洞影响了搭载 A5 到 A11 芯片的苹果设备,并且苹果无法通过软件更新来修复这个漏洞,非常适合我们的场景。不过,由于 checkm8 漏洞利用需要通过 USB 进行,目前针对 A5 芯片最成熟的方案是使用 Arduino USB Host Shield,这需要我们准备特殊的硬件。

阅读全文 »

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 了。

0%