逆向工程师的硬件调试第 1 部分:SWD、OpenOCD 和 Xbox One 控制器
背景
我正在我的公寓里寻找下一篇文章的潜在目标,并惊喜地发现以下 XBox One 控制器仍在包装中:
我不太喜欢玩我的 XBox,所以我认为拆解这个控制器并看看我们可以从中提取什么样的信息可能会很有趣。
目标
在评估嵌入式平台时,您可以做或尝试完成许多事情,在这篇文章中,我想演示/测试以下内容:
- 是否可以从目标中提取固件?
- 是否可以以这样一种方式调试或检测目标,以便我们更多地了解其内部操作?
- 可以通过软件利用或硬件修改来修改或更改固件吗?
回答其中一些问题的第一步将是硬件拆解。
硬件拆解
打开外壳,可以看到以下PCB:
请注意,这里真的没有太多可看的,因为主芯片被环氧树脂覆盖。幸运的是,很多测试板都贴有标签,但贴有标签的似乎是各种按钮按下的测试点,所以没有什么令人兴奋的。
在电路板的底部有一个标有AK4961的IC,但这是一个音频编解码器芯片。数据表可以在这里找到。该芯片是一款低功耗 24 位立体声编解码器,带有麦克风、耳机和扬声器放大器。
然而,如果我们看一下它的右边,有一小群带有一些丝网印刷标签的垫子:
所以我们看到,,,在丝网印刷中贴上了标签。这值得一看,如果您已经阅读了我之前关于路由器拆解和发现 UART 的帖子,您可能已经对如何在这里进行有一些想法。我们首先用万用表测量每个引脚的电压:3V3
A13
A14
RES
针 | 价值 |
---|---|
0/无 | 0 (GND) |
RES | 3.3V |
答14 | 0.1V |
答13 | 3.3V |
3V3 | 3.3V |
RES、A14 或 A13 上没有波动或调制,所以这些一定是别的东西,但是什么?鉴于其中一个标签是(可能代表系统重置),则很有可能存在JTAG或SWD标头。RES
我们可以通过用 10k 电阻器将引脚拉低来测试引脚是否真的复位了目标(请记住,我们在这里颠倒了事情,不想意外短路!如果您不熟悉这些类型的接头或系统复位引脚的典型工作方式,它们通常处于低电平有效状态,这意味着它们在高值处空闲,必须拉低才能激活。因此,如果我们监控该线路的输出并使用 10k 电阻将该线路设置为低电平,我们会看到什么?RES
dmesg -w
[ 2108.588884] usb 1-6.4: new full-speed USB device number 10 using xhci_hcd
[ 2108.691108] usb 1-6.4: New USB device found, idVendor=0e6f, idProduct=02a2, bcdDevice= 1.0f
[ 2108.691113] usb 1-6.4: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 2108.691116] usb 1-6.4: Product: PDP Wired Controller for Xbox One - Crimson Red
[ 2108.691119] usb 1-6.4: Manufacturer: Performance Designed Products
[ 2108.691122] usb 1-6.4: SerialNumber: 0000AE38D7650465
[ 2108.698675] input: Generic X-Box pad as /devices/pci0000:00/0000:00:14.0/usb1/1-6/1-6.4/1-6.4:1.0/input/input25
[ 2131.403862] usb 1-6.4: USB disconnect, device number 10
[ 2133.420350] usb 1-6.4: new full-speed USB device number 11 using xhci_hcd
[ 2133.522469] usb 1-6.4: New USB device found, idVendor=0e6f, idProduct=02a2, bcdDevice= 1.0f
[ 2133.522474] usb 1-6.4: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 2133.522478] usb 1-6.4: Product: PDP Wired Controller for Xbox One - Crimson Red
[ 2133.522480] usb 1-6.4: Manufacturer: Performance Designed Products
[ 2133.522483] usb 1-6.4: SerialNumber: 0000AE38D7650465
[ 2133.530103] input: Generic X-Box pad as /devices/pci0000:00/0000:00:14.0/usb1/1-6/1-6.4/1-6.4:1.0/input/input26
啊太好了,这样做导致控制器重置,这是一个引脚,还有 2 个。
当看到这样的调试头时,一个常见的假设是它用于JTAG或其他形式的硬件级调试。但是,JTAG规范要求至少有4个引脚,,和。我们的目标上只有两个,所以这很有可能是一个单线调试 (SWD) 端口。TDO
TDI
TMS
TCK
了解社署
SWD 是用于 ARM Cortex 目标的常见调试接口。顾名思义,SWD只需要一条数据线和一条时钟线,但是我们如何确定哪一条是哪一条呢?在我们走这条路之前,我们应该更多地了解 SWD 是如何运作的,以及可以使用哪些工具来与它进行交互。
首先 - SWD 与称为“调试访问端口”(DAP)的东西连接。DAP代理访问各种“接入端口”(AP),这些端口提供的功能包括典型的硬件调试、传统JTAG内核和其他高性能内存总线。下图从本文档中提取,直观地展示了 DAP 和 AP 的架构方式。
每个 AP 由 64 个 32 位寄存器组成,其中一个寄存器用于标识 AP 类型。AP 的功能和特性决定了如何访问和利用这些寄存器。您可以在此处找到有关某些标准 AP 的这些事务的所有信息。默认情况下,ARM接口规范定义了两个AP,分别是JTAG-AP和MEM-AP。MEM-AP 还包括连接到它的组件的发现机制。
SWD协议
正如我们之前提到的 - SWD 是作为 JTAG 的伪替代品而开发的。使用SWD,引脚数量从4个减少到2个,它提供了许多与JTAG相同的功能。然而,SWD的一个缺点是设备不能以菊花链方式连接在一起,而JTAG允许这样做。SWD 中使用的两个引脚如下:
针 | 目的 |
---|---|
SWCLK | 向 CPU 发送时钟信号,确定何时采样和发送数据SWDIO |
SWDIO | 双向数据引脚,用于将数据传输到目标 CPU 或从目标 CPU 传输数据 |
SWD 利用基于数据包的协议来读取和写入 DAP/AP 中的寄存器,它们包括以下阶段:
- 主机到目标数据包请求
- 巴士周转
- 目标到主机确认响应
- 数据传输阶段
数据包结构如下图所示,我也分解了表中的各个字段。
田 | 用法 |
---|---|
开始 | 起始位,值 1 |
APnDP的 | 此位指示是使用调试端口访问寄存器还是访问端口访问寄存器。 |
RnW(英语:RnW) | 读/写位 |
答[2:3] | AP 或 DP 地址的地址字段 |
平价 | 所有数据包请求的奇偶校验位 |
停 | 停止位(始终为 0) |
公园 | 在周转期间将推高的线路 |
ACK[0:2] | 从目标到主机的 Ack 位 |
数据[0:32] | 实际数据帧,根据 RnW 位从目标读取或写入目标 |
平价 | 数据帧的最终奇偶校验位 |
在停放位(从主机到目标)之后有一个周转期,这基本上意味着目标现在将在同一条线上做出响应。
从极高的级别来看,SWD 端口使用这些数据包与 DAP 连接,这反过来又允许访问 MEM-AP,从而提供对调试和内存读/写功能的访问。出于本文的目的,我们将使用一个名为 OpenOCD 的工具来执行这些事务。接下来我们将回顾如何构建和使用 OpenOCD。
OpenOCD的
安装依赖项:
sudo apt-get install build-essential libusb-1.0-0-dev automake libtool gdb-multiarch
克隆存储库、配置和构建!
wrongbaud@115201:~/blog$ git clone https://git.code.sf.net/p/openocd/code openocd-code
cd openocd-code
./bootstrap
./configure
make -j$(nproc)
构建 OpenOCD 后,我们可以尝试通过 SWD 调试此控制器。为了做到这一点,我们至少需要告诉OpenOCD两件事:
- 我们用什么来调试(我们使用哪个调试适配器)
- 我们调试的目标是什么
为了进行调试,我们将使用我们在上一篇文章中使用的FT2232H来转储 SPI 闪存。有了这个接口,我们可以使用 OpenOCD 通过 SWD 查询有关目标的信息,这很重要,因为在反转过程的这个阶段,我们甚至不知道目标 CPU 是什么!
下表用于确定FT2232H上的哪些引脚需要连接到 SWD 目标:
FT2232H Pin | 社署目标 |
---|---|
AD1 | 社署 |
AD0 | SCLK公司 |
AD4 | SRST(系统重置) |
最后,为了将FT2232H用作 SWD 适配器,您必须在FT2232H之间/上面放置一个 470 欧姆电阻器。AD1
AD2
一旦我们将FT2232H上的引脚连接到目标,我们就可以使用以下脚本来查询 DAP 控制器上的寄存器:DPIDR
# We are using an FT2232H so specify this here
interface ftdi
# Provide the VID/PID of the FT2232H
ftdi_vid_pid 0x0403 0x6010
# There are two channels, this is the default
ftdi_channel 0
# To the best of my knowledge, this is used to properly set and confiture the state of the lines we are using
ftdi_layout_init 0x0018 0x05fb
# Enable SWD for the lines that we are using, and the port
ftdi_layout_signal SWD_EN -data 0
# This is used to specify the sRST pin, in our case we're using
ftdi_layout_signal nSRST -data 0x0010
# Here we are selecting SWD as opposed to another transport layer such as JTAG
transport select swd
# Set the speed of the adapter, this will vary based on what your hardware supports
adapter_khz 100
# Create a new dap, (TAP for JTAG terms) with name chip and role CPU, -enable let's OpenOCD to know to add it to the scan
swd newdap chip cpu -enable
# Create the DAP instance, this must be explicitly created according to the OpenOCD docs
dap create chip.dap -chain-position chip.cpu
我们可以用 openocd 运行这个脚本,如图所示,输出如下(注意,第一次运行时,没有输出,交换 / 行后打印出以下输出)。有关从FT2232到控制器的连接,请参见下表SWD
SCLK
FT2232H Pin | 控制器 |
---|---|
AD1 | 社署署 (A13 ) |
AD0 | SCLK (A14 ) |
AD4 | SRST (RES ) |
wrongbaud@wubuntu:~/blog/stm32-xbox$ sudo openocd -f openocd.cfg
Open On-Chip Debugger 0.10.0+dev-01040-ge7e681ac (2020-01-27-18:55)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : FTDI SWD mode enabled
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : clock speed 100 kHz
Info : SWD DPIDR 0x2ba01477
Warn : gdb services need one or more targets defined
非常好!我们发现了一个芯片 ID,如果我们用谷歌搜索这个 ID,我们会看到各种 Cortex M / STM32 设备的大量点击——这是有道理的,因为这个处理器系列支持 SWD。现在我们可以与 DAP 进行通信,我们应该看看是否可以确定正在使用的确切处理器 - 如果这是一个为它编写配置文件的处理器,我们将能够转储闪存组并从目标处理器获取其他辅助信息。有了这些额外的信息,我们可以告诉 OpenOCD 创建一个目标,使用具有 Cortex M 定义的芯片,这将有望让我们更多地利用 DAP 并访问一些更通用的功能,同时我们试图准确地找出我们的目标 CPU:0x2ba01477
# Set up the GDB target for the CPU, cortex_m is the CPU type,
target create chip.cpu cortex_m -dap chip.dap
# init reads out all of the necessary information from the DAP, kicks off the debugging session, etc
init
# Read out the information from the DAP, including the ROM table
dap info
当我们使用此配置文件运行 openocd 时,我们会看到以下结果:
wrongbaud@wubuntu:~/blog/stm32-xbox$ sudo openocd -f openocd.cfg
Open On-Chip Debugger 0.10.0+dev-01040-ge7e681ac (2020-01-27-18:55)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : FTDI SWD mode enabled
Info : clock speed 100 kHz
Info : SWD DPIDR 0x2ba01477
Info : chip.cpu: hardware has 6 breakpoints, 4 watchpoints
Info : chip.cpu: external reset detected
Info : Listening on port 3333 for gdb connections
AP ID register 0x24770011
Type is MEM-AP AHB3
MEM-AP BASE 0xe00ff003
Valid ROM table present
Component base address 0xe00ff000
Peripheral ID 0x00000a0411
Designer is 0x0a0, STMicroelectronics
Part is 0x411, Unrecognized
Component class is 0x1, ROM table
MEMTYPE system memory present on bus
ROMTABLE[0x0] = 0xfff0f003
Component base address 0xe000e000
Peripheral ID 0x04000bb00c
Designer is 0x4bb, ARM Ltd.
Part is 0xc, Cortex-M4 SCS (System Control Space)
Component class is 0xe, Generic IP component
ROMTABLE[0x4] = 0xfff02003
Component base address 0xe0001000
Peripheral ID 0x04003bb002
Designer is 0x4bb, ARM Ltd.
Part is 0x2, Cortex-M3 DWT (Data Watchpoint and Trace)
Component class is 0xe, Generic IP component
ROMTABLE[0x8] = 0xfff03003
Component base address 0xe0002000
Peripheral ID 0x04002bb003
Designer is 0x4bb, ARM Ltd.
Part is 0x3, Cortex-M3 FPB (Flash Patch and Breakpoint)
Component class is 0xe, Generic IP component
ROMTABLE[0xc] = 0xfff01003
Component base address 0xe0000000
Peripheral ID 0x04003bb001
Designer is 0x4bb, ARM Ltd.
Part is 0x1, Cortex-M3 ITM (Instrumentation Trace Module)
Component class is 0xe, Generic IP component
ROMTABLE[0x10] = 0xfff41003
Component base address 0xe0040000
Peripheral ID 0x04000bb9a1
Designer is 0x4bb, ARM Ltd.
Part is 0x9a1, Cortex-M4 TPIU (Trace Port Interface Unit)
Component class is 0x9, CoreSight component
Type is 0x11, Trace Sink, Port
ROMTABLE[0x14] = 0xfff42003
Component base address 0xe0041000
Peripheral ID 0x04000bb925
Designer is 0x4bb, ARM Ltd.
Part is 0x925, Cortex-M4 ETM (Embedded Trace)
Component class is 0x9, CoreSight component
Type is 0x13, Trace Source, Processor
ROMTABLE[0x18] = 0x0
End of ROM table
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
通过这些新的变化,我们不仅可以与 DAP 和 MEM-AP 进行交互,还可以通过 GDB 调试目标。我们还可以确定目标 CPU 是 STM32F2X 系列,因为 MEM-AP 条目中的0x411部件号:
MEM-AP BASE 0xe00ff003
Valid ROM table present
Component base address 0xe00ff000
Peripheral ID 0x00000a0411
Designer is 0x0a0, STMicroelectronics
Part is 0x411, Unrecognized
Component class is 0x1, ROM table
然而 - 只是为了好玩,如果我们无法访问 DAP 并想通过内存读取和写入来弄清楚我们的目标是什么怎么办?为了弄清楚这一点,STM32 CPU中有一些常见的内存区域,用于存储ID和闪存信息。有了这些信息,我们可以修改 OpenOCD 脚本来读取这些区域并查找相关的 ID 信息!下表包含 ID 信息的必要偏移量:
STM系列 | 抵消 |
---|---|
通用设备 ID 注册 | 0xE0042000 |
STM32F0/STM32F3 | 0x1FFFF7AC |
STM32F1 | 0x1FFFF7E8 |
STM32F2/STM32F4 | 0x1FFF7A10 |
STM32F7 | 0x1FF0F420 |
STM32L0 | 0x1FF80050 |
STM32L0/ L1 Cat.1,Cat.2 | 0x1FF80050 |
L1 Cat.3,Cat.4,Cat.5,Cat.6 | 0x1FF800D0 |
mdw 0x1FFFF7AC 3
mdw 0x1FFFF7E8 3
mdw 0x1FFF7A10 3
mdw 0x1FF0F420 3
mdw 0x1FF80050 3
mdw 0x1FF800D0 3
当我们运行更新的 OpenOCd 脚本和上述命令时,我们会看到以下结果:
> mdw 0x1FFFF7AC 3
0x1ffff7ac: ffffffff ffffffff ffffffff
> mdw 0x1FFFF7E8 3
0x1ffff7e8: ffffffff ffffffff ffffffff
> mdw 0x1FFF7A10 3
0x1fff7a10: 006c0028 31385114 30373639
> mdw 0x1FF0F420 3
SWD DPIDR 0x2ba01477
Failed to read memory at 0x1ff0f424
> mdw 0x1FF80050 3
SWD DPIDR 0x2ba01477
Failed to read memory at 0x1ff80054
> mdw 0x1FF800D0 3
SWD DPIDR 0x2ba01477
Failed to read memory at 0x1ff800d4
>
我们可以使用以下命令获取闪存大小,使用该芯片数据表或上面链接的存储库中的闪存地址:
> mdh 0x1FFF7A22
0x1fff7a22: 0100
现在我们知道了确切的目标,我们可以从配置文件中删除目标和行,并将它们替换为从命令行调用。这将正确枚举目标 CPU。我们现在也知道,这款STM32F2系列芯片0x100 1kb 页的闪存。swd
dap
target
-f /usr/local/share/openocd/scripts/target/stm32f2x.cfg
wrongbaud@wubuntu:~/blog/stm32-xbox$ sudo openocd -f openocd.cfg -f /usr/local/share/openocd/scripts/target/stm32f2x.cfg
[sudo] password for wrongbaud:
Open On-Chip Debugger 0.10.0+dev-01040-ge7e681ac (2020-01-27-18:55)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : FTDI SWD mode enabled
adapter speed: 100 kHz
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : clock speed 1000 kHz
Info : SWD DPIDR 0x2ba01477
Info : stm32f2x.cpu: hardware has 6 breakpoints, 4 watchpoints
Info : Listening on port 3333 for gdb connections
现在这已经起作用了,我们可以使用以下命令转储内部闪存:
> flash list
{name stm32f2x base 0 size 0 bus_width 0 chip_width 0} {name stm32f2x base 536836096 size 0 bus_width 0 chip_width 0}
> flash read_bank 0 bank0.bin
device id = 0x00016423
flash size = 256 kbytes
wrote 262144 bytes to file bank0.bin from flash bank 0 at offset 0x00000000 in 3.690861s (69.361 KiB/s)
> flash read_bank 1 bank1.bin
flash size = 512 bytes
wrote 512 bytes to file bank1.bin from flash bank 1 at offset 0x00000000 in 0.007852s (63.678 KiB/s)
我们还可以使用以下命令使用 gdb 调试控制器:
wrongbaud@wubuntu:~/blog/stm32-xbox$ gdb-multiarch
GNU gdb (Ubuntu 8.1-0ubuntu3.2) 8.1.0.20180409-git
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb) set architecture arm
The target architecture is assumed to be arm
(gdb) target remote localhost:3333
Remote debugging using localhost:3333
warning: No executable has been specified and target does not support
determining executable automatically. Try using the "file" command.
0x0800307e in ?? ()
(gdb) x/10x 0x1FFF7A10
0x1fff7a10: 0x006c0028 0x31385114 0x30373639 0xc000fcc0
0x1fff7a20: 0x0100c000 0x67ff47d2 0x05dcf000 0x04a803b3
0x1fff7a30: 0x451744b1 0xffffffff
(gdb)
所以在这一点上,我们已经转储了闪存,我们可以调试并单步完成固件,但是......我们可以重新刷新 MCU 吗?
如果我们可以在固件映像中找到 USB 描述符字符串并修补它们,我们可以将其用作可见方法来确定我们是否可以修补固件。让我们在GHIDRA中加载固件,看看是否能找到它们,固件映像可以在地址加载。我们知道固件是根据数据表加载的,但是如果我们没有数据表,这可以通过发出命令和单步执行第一条指令从 OpenOCD 确定。幸运的是,这个固件映像相当小,Ghidra 可以快速处理它。可以在下面的屏幕截图中看到输出中看到的字符串:0x8000000
0x8000000
reset halt
dmesg
让我们对产品字符串做一个简单的补丁,将其更改为“测试固件补丁”。可以在 OpenOCD telnet 控制台中使用以下命令覆盖闪存:
wrongbaud@wubuntu:~/blog/stm32-xbox$ telnet localhost 4444
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Open On-Chip Debugger
> flash read 0 bank0-orig.bin
> flash read_bank 1 bank1-orig.bin
flash size = 512 bytes
wrote 512 bytes to file bank1-orig.bin from flash bank 1 at offset 0x00000000 in 0.007867s (63.557 KiB/s)
> stm32f2x unlock 0
Target not halted
stm32f2x failed to unlock device
> halt
target halted due to debug-request, current mode: Handler External Interrupt(67)
xPSR: 0x61000053 pc: 0x0800839c msp: 0x2000ff48
> stm32f2x unlock 0
stm32f2x unlocked.
INFO: a reset or power cycle is required for the new settings to take effect.
> reset halt
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x080002a4 msp: 0x20010000
> stm32f2x mass_erase 0
stm32x mass erase complete
> flash write_bank 0 bank0-patch.bin
wrote 262144 bytes from file bank0-patch.bin to flash bank 0 at offset 0x00000000 in 3.744948s (68.359 KiB/s)
> reset
>
这里有几个步骤可能没有意义,所以我想解释一下:
- 在尝试重新刷新之前,始终始终备份任何闪存图像。
- 见项目符号 1
- STM32闪存控制器有一个锁定位,可防止不必要的写入。这是在STM32的“选项字节”中设置的
- 幸运的是,我们能够解锁闪光灯,这有时不是一种选择!
- 对于 STM32 上的内部闪存,我们需要在写入之前执行擦除操作
- 我应该在这里补充一点,如果你的目标很昂贵或很重要,除非你 100% 确定你可以把它恢复到原来的状态,否则你永远不会想这样做
- 我们写入修补后的固件映像,然后重新启动 CPU,提示以下内容显示在
dmesg
[54691.886194] usb 1-6.4: new full-speed USB device number 14 using xhci_hcd
[54691.992411] usb 1-6.4: New USB device found, idVendor=0e6f, idProduct=02a2, bcdDevice= 1.0f
[54691.992417] usb 1-6.4: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[54691.992420] usb 1-6.4: Product: Testing Firmware Patches
[54691.992423] usb 1-6.4: Manufacturer: Performance Designed Products
[54691.992426] usb 1-6.4: SerialNumber: 0000AE38D7650465
[54691.998102] input: Generic X-Box pad as /devices/pci0000:00/0000:00:14.0/usb1/1-6/1-6.4/1-6.4:1.0/input/input28
太好了 - 所以我们现在已经完全提取了固件并加载到 ghidra 中,并且能够根据我们认为合适的方式对其进行修改......现在是时候编写一些宏了。但是已经很晚了,我差点错过了一月份的每月发布截止日期,所以我想我要在这里结束它!
结论
在对嵌入式系统执行评估时,通常需要枚举和浏览与目标交互的所有可能的接口和方法。无论您的最终目标是寻找错误、修改设备的正常操作,还是只是详细了解其工作原理,硬件调试都非常有用。通过利用硬件调试,我们能够从该目标中提取固件,设置实时调试器并修改固件。通过本练习,我们还介绍了单线调试的工作原理,以及如何使用硬件调试工具识别、枚举和调试未知 CPU。OpenOCD 还与基于FT2232H的接口一起使用,以提取固件映像并将新固件重新刷新到目标上。感谢您的阅读,如果您有任何问题或只是想更多地谈论此类内容,请随时在 twitter 上联系我