介绍
这是本系列中的第二篇文章,试图修改2010年大众高尔夫的电子动力转向(EPS)ECU上运行的固件。在前一部分中,我获得了该模块的副本,并做了一些初步的研究。在这一部分中,我将介绍如何获取ECU上运行的应用程序固件。
首先,我试图通过CAN总线提取固件,但没有成功。然而,这导致了一些关于如何强制认证和进入密码保护的诊断模式的知识。
其次,我将描述我如何找到一个VW升级文件并对其进行解密。最后,我将把二进制文件加载到Ghidra中,并对这个未知微控制器的架构和内存映射进行一些初步猜测。
KWP 2000- ReadMemoryByAddress
由于我已经可以通过KWP 2000诊断协议与ECU对话,下一步将尝试使用RequestUpload或ReadMemoryByAddress服务。不幸的是,RequestUpload服务不可用。尝试ReadMemoryByAddress导致访问被拒绝错误。但是,这是一个好消息,因为这意味着端点确实存在,但只能从与默认模式(0x89)不同的诊断模式访问。
并非所有的诊断模式都可以立即进入,有些需要通过登录程序。在KWP 2000和UDS中,这是通过请求“种子”,执行某种计算将“种子”转换为“密钥”并将其发送回ECU来完成的。根据我的经验,这可以是1到8字节之间的任何地方,计算从单个xor到一些更复杂的密码学。
幸运的是,我的obd 11软件有一个登录功能,并非常有帮助地建议一个默认密码,允许访问一些(但不是所有)诊断模式。通过观察OBD 11执行一些种子密钥过程,我可以推断出种子/密钥的长度为4字节。此外,要将种子转换为密钥,只需添加密码。不幸的是,默认密码仍然不允许进入“工程”模式(0x 86),这将有望访问ReadMemoryByAddress。
因此,工程模式所需的密码需要被暴力破解。OBD 11应用程序接受高达65536的密码,所以看起来空间只有两个字节大,应该可以在合理的时间内通过每秒几次猜测来验证。通常,ECU有一些保护措施来防止暴力强制种子/密钥算法。这通常分为两部分。首先,在启动ECU之后,在可以请求种子之前存在超时。第二,在一次(有时三次)尝试后,您将被锁定,需要重新启动ECU。这个EPS似乎允许在boot后立即请求种子,但它将错误尝试的次数存储在非易失性存储器中,这些错误尝试的次数仅在运行时间约10分钟后才被清除。
幸运的是,有一个错误,无论使用哪个密码登录,错误尝试的次数都会被清除。由于已知有效密码(具有较少的权限),因此您可以在蛮力猜测和已知的正确密码之间交替,并且永远不会触发锁定。
我在之前编写的KWP 2000库的基础上编写了一个小脚本来执行这种暴力攻击。此脚本可以在本博客系列的GitHub存储库中找到:bruteforcepasword. py。经过几个小时的暴力破解,这给了两个新密码,其中一个允许进入工程模式。
https://github.com/I-CAN-hack/pq-flasher/blob/master/extras/bruteforcepasword.py
我对ReadMemoryByAddress可以从其他诊断模式访问的猜测是正确的,我能够从ECU请求一些数据。但是,我只能请求地址0x400,返回的数据包含有效的VIN。我们可以假设这个端点可能用于读取非易失性内存的内容,以进行调试 2 ,并且不会让我更接近固件转储。
更新文件
找到ECU固件的另一种方法是尝试从制造商那里找到更新文件。有时发布更新是为了修复一个bug,或者在不同的汽车中使用相同的硬件,需要运行不同的软件。如果你幸运的话,制造商工具包含一个更新文件。然后,您可以尝试从制造商使用的任何压缩或加密容器格式中提取原始固件,或者闪存实际ECU并嗅探CAN总线的固件。后者并不总是可能的,因为解密可能发生在ECU端,除非你已经知道解密过程是如何工作的。
我发现这个不错的网站,我可以输入部件号( 1K0909144E
),它会告诉我固件文件寻找:lh0022901.sgo
.你可以在大众的闪存盘中搜索这个文件名。文件是存在的,但不幸的是太小,包含任何有趣的东西。然而,更多的谷歌指向我 lh0012501.sgo
和 lh0013501.sgo
。2501固件有点难找到,所以你也可以使用3501,如果你跟着沿着。所以在这种情况下,我很幸运,(部分)固件可以在线下载。
https://vag-flashinfo.de/
现在我们有了这个未知的.sgo格式的固件,是时候弄清楚如何提取它了。快速搜索显示它是一些自定义格式,所以我们主要靠自己。第一步是在文件上运行 strings
。通常ECU固件至少包含几个字符串,包括版本号和部件号。除了头文件 SGMLObjectFile
之外,文件中没有任何字符串。下一步是运行 binwalk
,看看它是否识别出任何已知的魔术签名:
$ binwalk lh0012501.sgo
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
结果也是空的。所以没有使用标准的压缩格式。Binwalk还可以绘制熵图,这可能会告诉我们更多关于这个文件的信息。
这是个好消息大部分文件的熵大约是0.8,这意味着我们可能正在查看代码!在文件的后面,熵有点嘈杂,一直到零。这可能是一些更结构化的数据与查找表或只是零。熵不接近1的事实意味着它没有被压缩或加密。然而,字符串没有返回任何东西,所以我们可能在这里处理一个简单的xor密码。
在查看了文件的十六进制转储后,我注意到有很大的区域只包含 0xff
。这些是原始文件中的零区域吗?VW用 0xff
xor了所有的东西吗?让我们来看看!一个简单的python一行程序将写出文件的副本:python-c"open('lh0012501.sgo.out', 'wb').write(bytes([c ^ 0xff for c in open('lh0012501.sgo', 'rb').read()])")
。在生成的输出上运行 strings
之后,我们现在得到了一些有用的字符串,包括构建日期。成功了!
$ strings lh0012501.sgo.out
<snip>
EPS_ZFLS Kl.
STG_Lenkhilfe
<snip>
EPS_ZFLS Kl.
370300D0601
370300D0601_T00
gx0630
2008/08/01 08:59:24.22
7805079363
370300B0201
?W =
B1B3B9D1GKIDKOHCM1HYPLLWPDDUDDFTPRSEERDMOF
PREDRIVE
DRIVEUP
DRIVEDOWN
FACTORYTEST
POSTRUN
SERVICE
ERROR
DIAGMODE
<snip>
除了字符串之外,我们还可以使用cpurec检查文件是否包含有效的指令,cpurec可以识别一段V850代码。
$ ./cpu_rec.py lh0012501.sgo.out
full(0x562a2) V850 chunk(0x37000;110) V850
因此,在确认更新文件包含实际代码后,如果能对它有更多的了解,那就太好了。在做了更多的研究之后,我发现了一个名为VAGHelperX的工具(现已停产),它可以提供有关文件内容的更多信息,还可以将.sgo的各个部分提取为二进制或(损坏的)Intel HEX。也许我会在以后的博客文章中为.sgo文件编写一个python解析器/打包器,因为它不需要依赖专有软件,并且在修改后还能够创建新的.sgo文件。这也可以变成一个Ghidra加载器直接加载.sgo文件。
https://forum.skodapilot.com/viewtopic.php?t=34
VAGHelper 显示,.sgo 文件由四个部分组成,其中三个部分相当小。实际固件可能包含在范围从 0xa000
到 0xa5ffff
内。这也意味着此更新跨越了大部分闪存,只有从 0x0
到 0x9fff
的部分不包含在此更新中。固件的较低部分可能包含用于实际刷新更新的引导加载程序,但更多内容将在以后的博客文章中介绍。VAGHelper 显示闪存中的偏移量是很好的,因为这将有助于在 Ghidra 中以正确的偏移量加载生成的二进制文件。
校准协议(CCP)
在这个过程中,我并不需要使用这种方法,因为我已经从更新文件中获得了足够的固件来开始进行逆向工程。但是你可能没有这么幸运,也没有可用的更新文件。
作为 KWP2000 或 UDS 的替代方案,您可以使用一些更晦涩的协议通过 CAN 提取固件:CCP 和 XCP。CAN 标定协议(CCP)于 1999 年推出,随后又推出了通用测量和校准协议(XCP),除了常规 CAN 外,还可以在 CAN-FD 或以太网上使用。
CCP/XCP 被 ECU 开发人员用来读取数值,或在汽车中测试 ECU 时进行调整。对于我们汽车黑客来说,最有趣的功能是能够读取和写入内存。许多中间值存储在全局变量中,用于调试目的,可以使用 CCP 实时读取和绘制。全局变量的地址存储在 A2L 文件中,可以通过类似 CANape 的工具加载,类似于用于 CAN 数据包的 DBC 文件。它还可以用于在运行 ECU 时更新查找表或调整数值。
如果您能找到这个 CCP 接口,可能有助于提取固件或在开发补丁时将其用作调试工具。可以通过暴力破解找到 CCP/XCP 地址,但如果使用非常高的 CAN 地址或站点 ID,您可能永远找不到它们。CCP/XCP 还具有与 KWP2000/UDS 相似的登录过程,具有种子/密钥算法。因此,即使找到它们,您可能会被锁定。
CCP CONNECT 命令是 0x01
,后跟一个计数器(在本例中为 0x00
),然后是一个 2 字节的站点 ID。扫描 CCP 是通过向每个 CAN 地址和每个可能的站点 ID 发送 CONNECT 命令,并检查响应来完成的。搜索范围相当大,因此我建议从地址 0x600
和 0x800
之间以及站点 ID 低于 64 开始。
XCP CONNECT 命令更简单,只是 0xff
后跟连接模式( 0x00
为正常连接模式)。无需强制使用站点 ID。
在以后的博客文章中,我将重新访问 CCP 以提取引导加载程序,因为我没有从固件更新文件中获取到它。
Ghidra 中加载固件
通过获取部分固件更新,我可以在 Ghidra 中加载它。我已经知道文件需要在偏移 0xa000
处加载,CPU 架构是 V850。不幸的是,Ghidra 中有一个小错误,所以我建议应用一个小补丁作为解决方法。在第一次打开文件时,当被要求分析时,请按“否”。加载文件后,我还创建了一个从 0x0
到 0x9fff
的空部分,作为引导加载程序的占位符。
加载文件后,按下第一条指令( d
)上的 0xa000
,这应该是跳转到固件实际入口点的指令。Ghidra 已经根据此标记了一些函数为代码,我们将在自动化分析中使用这些函数。但在运行之前,我们需要确保 tp
(文本指针)和 gp
(全局指针)被正确设置。tp
通常设置为固件或数据部分的末尾,并用于引用静态数据。gp
指向 RAM 中的某个位置,并用作全局变量的偏移量。tp
和 gp
在入口点中设置。有关更多信息,请参阅 V850 参考手册。
tp=0x60000-0x2000+0x4000+0x4000=0x66000
, gp=0x3ff1000+0x4000+0x4000=0x3ff9000
。通过选择闪存区域,单击右键并选择“设置寄存器值”来修复这些寄存器值。现在我们可以运行自动分析,但请确保禁用“创建地址表”,因为这会在代码区域内生成误报,从而干扰发现代码区域。
固件被布置为引导加载程序应用程序固件数据。运行自动分析后,我们可以看到这种结构出现在侧边栏 3 中,但某些数据将被错误地标记为代码。因此,我们现在可以进入并清除所有找到的代码后 0x50000
(右键单击,清除选项,选择代码和函数)。
还有一些功能之间的区域尚未标记为代码。这段代码可能不是通过跳转引用的,但可能是根据某个数据表调用的。通过手动标记这些代码(基本上是在 0xd000
和 0x4eaea
之间的所有内容)。一些形式为 0xe00x07
的指令未正确解析(可能是 Ghidra 中 V850 SLEIGH 定义中缺少的指令),因此在跳过该指令后,您可能需要手动重新开始反汇编。之后,您的概览侧边栏应该如下所示(左侧为代码/数据,右侧为熵),并且 gp
和 tp
寄存器已设置。
滚动查看,您会看到许多不属于内存映射的地址引用。这些引用 RAM( 0x3ffXXXX
)和硬件外设( 0xfffffXXX
和 0x01ffcXXX
)。将这些缺失的范围添加到内存映射中。对于 RAM,我选择了 0x3ff0000
到 0x3ffffff
,对于外设选择 0xfffff000
到 0xffffffff
和 0x01ffc000
到 0x01ffcfff
。这些范围可能比实际微控制器上的范围大,但这并不重要。确保将外设范围标记为易失性。更新内存映射后,再次运行自动分析。
结论
在这部分中,我尝试使用 KWP2000 通过 CAN 提取固件,但只成功提取了 1024 个配置闪存。然后我设法找到并提取了一个更新文件,并将其加载到 Ghidra 中。通过一些合理的猜测,我重建了固件、外围设备和内存映射的布局。我现在准备开始实际的逆向工程过程!
在第 3 部分中,我将描述一些一般技术,如何在从头开始逆向工程固件时找到一些起点。我将寻找诊断处理程序,CAN 解析,并找到负责 6 分钟锁定和最小速度检查的实际功能。
免责声明
由于传播、利用本公众号渗透安全团队所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号渗透安全团队及作者不为此承担任何责任,一旦造成后果请自行承担!
原文始发于微信公众号(车联网攻防日记):【车联网】大众高尔夫动力转向ECU破解(2)