介绍
这是关于尝试修改 2010 年大众高尔夫车电子动力转向(EPS)ECU 上运行的固件的博客系列的第三部分。目标是修改固件,以消除车道保持操作的 6 分钟锁定,并允许在低速时使用车道保持。
在上一部分中,我从更新文件中提取了固件的应用部分,并将其加载到 Ghidra 中。我对内存映射的布局进行了一些合理的猜测。在这一部分中,我将描述应用程序的逆向工程,并确定我需要进行的更改,以禁用 6 分钟计时器和最低速度。
当逆向工程类似 ELF 文件的东西时,您将从一些导出符号和文本/数据区域分离出来的东西开始。然而,当逆向工程嵌入式固件时,您必须从头开始。您甚至不知道字节代表数据、字符串还是代码,这并不总是明显的。首先,我将描述一些固件的部分,这些部分可以更容易地识别,并将作为寻找我们修改兴趣区域的起点。
在大多数情况下,我们感兴趣的是修改代码的某些部分,这些部分涉及到轮速或期望的车道保持辅助(LKAS)扭矩或模式。这些值通常存储在全局变量中的某个地方,并在包含相关数据的新 CAN 消息到达时进行更新。因此,可以通过查看所有引用这些速度或 LKAS 全局变量的代码来找到代码中感兴趣的区域。
反过来,可以通过查看解析与这些量相关的 CAN 消息的代码来找到这些全局变量的位置。可以通过查找 CAN 地址来找到这些 CAN 处理程序,猜测解析代码的样子,或者找到更多与 CAN 驱动程序寄存器交互的低级例程。
CAN寄存器
此时,我手头有一个包含数百个未知功能的二进制文件,我们正在寻找一个起点。就像拼接拼图一样,我们从角落开始,逐渐添加边缘,然后整个图片慢慢开始拼凑在一起。
一个很好的开始地方是查找一些重要的硬件寄存器,比如 CAN,在数据表中,并在 Ghidra 中标记它们。由于 Ghidra 会找到地址的所有交叉引用,因此很容易查看这些地址被使用的所有位置,并逐层向上跨越抽象层。
在标记 CAN 寄存器时,以及查看与其交互的代码时,最有趣的部分是接收地址的设置方式,以及数据的读取和复制位置。这使您能够跟踪属于特定 CAN 消息的数据,并找到解析各个字段的代码。
CAN 地址的示例寄存器布局。请注意,每个缓冲区的寄存器每 0x20 字节重复一次。
CAN 硬件通常具有多个缓冲区(有时称为邮箱)用于接收数据。每个缓冲区可以根据地址进行过滤,以确保特定消息最终进入正确的缓冲区。然而,有时消息会被发送到随机缓冲区,然后由中断处理程序复制到中间存储器。
很遗憾,我没有这个 EPS 中微控制器的数据表。但由于外围区域似乎相当小,也许我可以根据代码使用寄存器来进行一些有根据的猜测,找到相关的寄存器。
在浏览了一些外围区域后,我注意到在 01ffc100
周围有一个有趣的访问模式。在这里,一组 10 个字节作为一个单独的块复制进出这些地址,同时禁用中断,有时带有大小为 N*0x20
的偏移量,计算到基地址 01ffc100
上。这些很可能是与 CAN 相关的寄存器,这 10 个字节包括 8 个数据字节和有关数据包大小和地址的一些信息。当进行逆向工程时,引用这些地址的函数将是我开始寻找的第一个地方之一。
我尝试查阅一些现有的瑞萨 V850 数据表,看看我找到的任何 CAN 寄存器是否匹配,但没有找到任何结果。让我知道我错过了什么,你是否认识特定类型的 V850!
CAN解析
下一步是找到实际解析 CAN 消息的代码,并找到存储有用值(如汽车速度)的全局变量。此时,参考特定汽车的 DBC 文件并查看消息的布局是很有用的。当仅查看起始位和停止位时,我经常感到困惑,通常会将驱动程序加载到卡巴纳中,以颜色显示值如何放置在消息中。
在大多数情况下,处理 CAN 解析的功能是从包含地址(或邮箱号码)、数据的中间副本以及实际解析数据的回调函数的表中调用的。这个表通常由与底层 CAN 寄存器交互的代码引用。如果你幸运的话,它还包含消息的实际 CAN 地址,你可以通过搜索常量来找到它。
很遗憾,这次寻找常量并没有成功,因为表中的地址已经发生了偏移。我根据之前找到的 CAN 基础寄存器( 0x01ffc100
)找到了它们。Ghidra 并没有自动找到所有引用,因为其中一些是间接计算的。在这种情况下,您可以使用搜索→内存来查找常量。另一个很有效的技巧是使用反编译器的输出。使用文件→导出程序→C/C++,您可以创建一个包含所有反编译函数的单个 c 文件,然后可以使用您喜欢的文本编辑器进行搜索。
两种方法都在 0x0001728c
处产生了一个有趣的功能,该功能使用 CAN 基址寄存器来计算偏移量并将其存储在一个中间全局变量中。然后调用 0x000171b4
引用一些有趣的表。它查找长度,将数据从 CAN 寄存器复制到表中的地址。它还检查回调函数是否存在,并在回调函数不为空时调用。非常有前途!
在这三个表格之上,我们找到另一个包含地址的表格。虽然不是必需的,但现在我也知道哪个内存地址/回调函数属于哪个地址,这样事情就变得简单多了。这些地址并不是立即明显的,因为它们被向右移动了两位。你可以推断出这一点,因为来自 CAN 寄存器的值在与 0x1ffc
进行与操作之后再与表格中的值进行比较,因为底部两位可能被用作某种状态标志。
现在使用地址表的索引,我们可以找到中间缓冲区的地址,该地址由解析代码引用。如果您无法立即找到这些表,另一种方法是在反编译代码中寻找移位和 AND 操作的模式,这将需要根据 DBC 解析出特定值。幸运的话,消息解析代码中包含一些通用函数来验证计数器和校验和,这将引导您到其他解析函数。找到(一个)解析函数的地址后,您可以开始寻找包含该地址的表 1 。
使用这些表格和解析函数,我确定了 HCA1/HCAStatus ( 0x03ff32b2
)和 HCA1/LMOffset ( 0x03ff3238
)的全局地址,稍后我将使用它们来识别我想要进行的修改。
诊断处理程序(UDS/KWP2000)
固件中另一个我想要识别的有用部分是诊断处理程序。在我看过的所有 ECU 上,这通常包括某种表格或大函数,其中包含对每个单独服务的函数调用(例如 ReadDataByIdentifier,SecurityAccess 等)。在出现错误的情况下,这些函数通常会返回特定的 UDS/KWP 错误代码,这使得它们易于找到。
在这种情况下,我将查看 return0x35
(无效密钥)的反编译代码。这在函数 0x0000f820
中有一个命中。跟踪调用堆栈会导致 0x00026ac8
,其中包含一长串 if 语句,调用处理程序函数。无效密钥错误是从安全访问处理程序(0x27)返回的,这是合理的。
我不会详细介绍诊断处理程序。我会指出,闪烁功能(requestUpload/requestDownload)的地址确实受到限制,最多可达 1024,正如我们之前注意到的。此外,需要擦除闪存的例行控制并不存在。要刷写 ECU,我需要利用一个存在于地址范围 0x0-0xa000
的引导加载程序。在逆向工程 KWP/UDS 处理程序时,为命令和错误值定义枚举非常有用,这样可以更轻松地了解各个处理程序函数的控制流和错误处理情况。
LKAS 检查
有关所需 LKAS 扭矩和所需 LKAS 模式的全局变量已确定,我们可以检查它们在固件中的引用位置。HCA1/LMOffset(所需扭矩)仅在两个地方使用,一个函数似乎实际控制方向盘(如果正确的 LKAS 模式处于活动状态),另一个函数有一堆有趣的检查( 0x00030c46
)。
最低速度
在这个有趣的功能中,一个检查之一是将从 Bremse_3 消息中的车轮速度导出的数字与两个常数 50 和 255 进行比较。如果速度低于 50 公里/小时,则设置一个标志,将 HCA 模式强制进入禁用状态。这些常数放置在特定车辆校准地图中,该地图还包含特定车辆的扭矩曲线。
六分钟计时器
类似于速度检查,此功能将两个全局变量与常量 36000 进行比较,并在值超过时设置标志。如果这些变量是以 100 赫兹的速率递增的计时器,那么这将是 360 秒,也就是我们的 6 分钟计时器!
很遗憾,定时器和最大值都是 16 位,检查是一个小于或等于的比较。如果我将最大定时器值扩展到 0xffff
,它仍然会在大约 11 分钟后触发。需要另一种解决方案来绕过计数器。
重要的是,在达到六分钟超时后,您需要停止发送扭矩,并在计时器重置之前至少停留一秒钟进入待机模式。让我们找到这个重置逻辑和 1 秒超时在代码中,看看我们是否可以对此做些什么。
唯一与 HCA_TIMER_01/02
相关的其他功能是 0x00030e0e
。以下是其中的一部分:
此功能完全实现了观察到的逻辑。进入待机模式(模式 3)时, HCA_TIMER_03
被重置为零。在待机模式中, HCA_TIMER_01
被设置为零, HCA_TIMER_03
被递增。只有当 HCA_TIMER_03
达到 100(1 秒)时, HCA_TIMER_02
被重置为零。
幸运的是,这 MIN_HCA_TIMER_03
也在校准区域内。如果我将该值更改为零,则在仅 0.01 秒的待机操作后可能恢复正常运行。有趣的是,这与新款 VW MQB 车辆的车道保持行为相匹配。这让我想知道它是否运行相似的代码,只是配置不同。
期望的补丁
在逆向工程固件的所有工作之后,我已经确定了需要进行的相对较小的更改。MIN_SPEED
( 0x0005e283
) 需要从 50 公里/小时 ( 0x32
) 更改为 0 公里/小时,这是存储在单字节中的。另外 MIN_HCA_TIMER_03
( 0x0005e221
) 需要从 1 秒 ( 0x64
) 更改为零,这也是一个单字节。
这个 EPS 的固件在多辆汽车之间被重复使用,刷入特定汽车的转向地图后加载。对于 2501 固件,该地图存储在闪存中的 0x5e000
和 0x5efff
之间。有趣的是,这两个值都存储在这个特定车辆区域内,所以大概大众汽车可能有意向将不同配置运送到不同的车型或车型年份。
由于这个相对较小的区域可以独立闪存,因此应用补丁应该非常快速。我还检查了这些地址在此函数中仅被引用一次,并且更改它们不会产生意外的副作用。如果您在实际汽车上尝试这样做,请仔细检查拆卸并自行验证!
重新计算校验和
在我逆向工程的大多数 ECU 固件中,数据块通常会附带校验和以检查数据损坏。这些校验和会在启动时或定期进行检查。如果检测到校验和不匹配,ECU 将进入减少操作模式(即“瘸行模式”)。它仍会启动到常规应用程序,输出 CAN 消息,并响应诊断消息。但是,动力转向将被禁用,并且仪表盘上会显示警告灯。
在更改数据块时,您必须确保所有相关的校验和也已更新。这个校验和通常位于数据块的开头或结尾,并且可能是某种形式的 CRC。可以通过查看对数据块开头/结尾值的引用或查找校验和函数并查看其操作范围来找到校验和。
由于大多数校验和使用某种形式的 XOR,您可以搜索反编译输出以查找 ^
,通常可以很快判断函数是否正在计算校验和。或者,校验和函数可能会使用指令集中的 CRC 指令,这些指令也可以通过在 Ghidra 中使用指令查找器来找到。
在这种情况下,校验和函数可以在 0x0003def8
找到。用于计算从 0x5e000
到 0x5effb
(包括在内)的校验和。校验和本身存储在 0x5effc
。从 CRC_TABLE
我们可以看到多项式是 0x1021
,初始值似乎为零,使其成为 CRC16-XMODEM。这可以通过计算转储数据的 CRC 并确保结果与固件中的校验和匹配来验证。
结论
在这篇文章中,我对固件的应用部分进行了逆向工程。我找到了 CAN 解析逻辑,并使用它来识别与车道保持操作相关的全局变量。通过交叉引用这些全局变量,我能够识别实现我想要修补的检查的函数。这两个检查都可以通过在校准区域仅进行数据更改来绕过。仅使用数据补丁就能做到这一点比使用代码补丁要好得多。搞砸代码补丁通常会导致 ECU 处于砖砌状态,或者可能会产生不明显的意外后果(例如溢出或符号翻转)。在第 4 部分中,我将描述我是如何逆向工程闪存过程的,并将实际应用讨论过的补丁。
免责声明
由于传播、利用本公众号渗透安全团队所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号渗透安全团队及作者不为此承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,我们会立即删除并致歉。谢谢!