HowIHackedMyCar 2021款 现代IONIQ (三) Making Software

HowIHackedMyCar  2021款 现代IONIQ (一)
HowIHackedMyCar 2021款 现代IONIQ (二) Making a Backdoor

本合集共7部分,本篇为第三部分

来源:programmingwithstyle.com,感谢greenluigi1

Goal
在查阅 IVI 许多文件时,我发现了大量与 ccOS 相关的C++ 头文件,位于 /usr/include 目录下。

HowIHackedMyCar 2021款 现代IONIQ (三) Making Software
ccOS 是由 Nvidia 和现代汽车开发的操作系统,预计从 2022 年开始为所有现代汽车提供动力,但我想部分底层系统在之前的现代汽车中已经存在了相当长的时间。一些最古老的头文件中有 2016 年的版权注释。

根据这些头文件,看起来它们提供了一种非常方便的方式与车辆进行交互,提供了查询里程表、电池电压等信息的函数,以及执行启动发动机或锁定/解锁车门等操作的功能。


The Road of Mistakes
我想制作一个基本程序,使用 ccOS 头文件来读取车门的状态,并向它们发送锁定或解锁信号。我在我的 Kali 虚拟机上安装了 Visual Studio Code 和最新版本的 g++ arm 交叉编译器(来自 g++-arm-linux-gnueabi 软件包的 arm-linux-gnueabi-g++)。

我会做一个简单的控制台应用程序,用来读取驾驶员侧门是开着还是关着。

在名为 HBody.h 的一个 ccOS 头文件中,包含了类 HBody。HBody 是一个单例类,其中包含一个静态方法来获取 HBody 的实例。HBody 本身包含一个名为 isDoorOpened 的方法,用于判断特定门是开着还是关着。

HBody 中的所有查询函数都返回一个 HResult,指示它是否成功查询了对象,或者是什么错误阻止了查询。每个查询方法还接受一个输出类型的引用,以提供实际的查询结果。

isDoorOpened 函数接受一个 HDoorPosition 枚举来指定你要查看的门是开着还是关着(前左/右、后左/右或尾门),以及一个 HTriState 的引用,用于指示门是否打开(False、True、Invalid)。

以下是我编写的代码:

#include <iostream>#include <vector>#include <string>#include "HBody.h"
using namespace std;
const char* HResultToString(ccos::HResult result){ switch (result) { case ccos::HResult::INVALID: return "INVALID"; case ccos::HResult::OK: return "OK"; case ccos::HResult::ERROR: return "ERROR"; case ccos::HResult::NOT_SUPPORTED: return "NOT_SUPPORTED"; case ccos::HResult::OUT_OF_RANGE: return "OUT_OF_RANGE"; case ccos::HResult::CONNECTION_FAIL: return "CONNECTION_FAIL"; case ccos::HResult::NO_RESPONSE: return "NO_RESPONSE"; case ccos::HResult::UNAVAILABLE: return "UNAVAILABLE"; case ccos::HResult::NULLPOINTER: return "NULLPOINTER"; case ccos::HResult::NOT_INITIALIZED: return "NOT_INITIALIZED"; case ccos::HResult::TIMEOUT: return "TIMEOUT"; case ccos::HResult::PERMISSION_DENIED: return "PERMISSION_DENIED"; case ccos::HResult::ALREADY_EXIST: return "ALREADY_EXIST"; case ccos::HResult::SOME_UNAVAILABLE: return "SOME_UNAVAILABLE"; case ccos::HResult::INVALID_RESULT: return "INVALID_RESULT"; case ccos::HResult::MAX: return "MAX"; default: return "Other"; }}
int main(){ cout << "Ioniq Test Application"; cout << endl;
ccos::vehicle::general::HBody *body = ccos::vehicle::general::HBody::getInstance();
ccos::vehicle::HTriState doorState; ccos::HResult doorOpenedResult = body->isDoorOpened(ccos::vehicle::HDoorPosition::FRONT_LEFT, doorState); if (doorOpenedResult == ccos::HResult::OK) { cout << "Door Result: " << (doorState == ccos::vehicle::HTriState::TRUE ? "Open" : "Closed"); cout << endl; } else { cout << "isDoorOpened did not return OK. Actual return: " << HResultToString(doorOpenedResult); cout << endl; }
cout << "Finished door test"; cout << endl;}

All I had to do is… (Don’t do this)
现在我只需要编译它。我设置了 VS Code,使用 arm-linux-gnueabi-g++ 编译器设置了一个构建任务,并将系统根目录设置为挂载的 system.img 文件的基础。我运行了任务,但是出现了错误。
HowIHackedMyCar 2021款 现代IONIQ (三) Making Software
忘记链接到 HBody 的库了。原来它被称为 HVehicle。我更新了构建任务以链接它…
HowIHackedMyCar 2021款 现代IONIQ (三) Making Software
通过一番搜索,我发现随编译器提供的标准库版本太新了,不包含 HVehicle 需要的特定版本。最终我创建了一个 specs 文件,避免包含默认的库位置,并手动添加了 /usr/lib/ 和 /usr/lib/arm-telechips-linux-gnueabi/4.8.1/ 文件夹。

再次运行构建任务…
HowIHackedMyCar 2021款 现代IONIQ (三) Making Software
最终,一个可用的构建!我将生成的二进制文件复制到了我的 USB 驱动器上,然后插入车里。
我启动了反向 shell,将二进制文件复制到 /tmp/ 目录,将其标记为可执行,并运行了它。
HowIHackedMyCar 2021款 现代IONIQ (三) Making Software
好的,不错。有一点日志垃圾信息,似乎来自 HBody,但它确实正确地报告了门是关闭的。我打开了我的门然后再次运行它,然后…
HowIHackedMyCar 2021款 现代IONIQ (三) Making Software
是的!我的应用程序正在工作。
现在来点更复杂的东西。
#include <iostream>#include <string>#include "HBody.h"#include "HChassis.h"
using namespace std;
namespace ccOSUtils{ const char *HTriStateToString(ccos::vehicle::HTriState state){ switch (state) { case ccos::vehicle::HTriState::FALSE: return "False"; case ccos::vehicle::HTriState::TRUE: return "True"; case ccos::vehicle::HTriState::INVALID: return "INVALID"; case ccos::vehicle::HTriState::MAX: return "MAX"; default: return "Other"; } }
const char *HResultToString(ccos::HResult result){ switch (result) { case ccos::HResult::INVALID: return "Invalid"; case ccos::HResult::OK: return "OK"; case ccos::HResult::ERROR: return "ERROR"; case ccos::HResult::NOT_SUPPORTED: return "NOT_SUPPORTED"; case ccos::HResult::OUT_OF_RANGE: return "OUT_OF_RANGE"; case ccos::HResult::CONNECTION_FAIL: return "CONNECTION_FAIL"; case ccos::HResult::NO_RESPONSE: return "NO_RESPONSE"; case ccos::HResult::UNAVAILABLE: return "UNAVAILABLE"; case ccos::HResult::NULLPOINTER: return "NULLPOINTER"; case ccos::HResult::NOT_INITIALIZED: return "NOT_INITIALIZED"; case ccos::HResult::TIMEOUT: return "TIMEOUT"; case ccos::HResult::PERMISSION_DENIED: return "PERMISSION_DENIED"; case ccos::HResult::ALREADY_EXIST: return "ALREADY_EXIST"; case ccos::HResult::SOME_UNAVAILABLE: return "SOME_UNAVAILABLE"; case ccos::HResult::INVALID_RESULT: return "INVALID_RESULT"; case ccos::HResult::MAX: return "MAX"; default: return "Other"; } }}
int main(int argc, char *argv[]){ cout << "Ioniq Advanced Test Application" << endl;
if (argc == 1) { cout << "Provide at least 1 argument (doorStatus, doorLock, status, test)" << endl; return 0; }
ccos::vehicle::general::HBody *body = ccos::vehicle::general::HBody::getInstance(); string command = argv[1];
if (command == "doorStatus") { if (argc != 3) { cout << "Expected arguments: doorStatus {fl/fr/rl/rr}" << endl; return 0; }
string doorStr = argv[2]; ccos::vehicle::HDoorPosition doorPosition = ccos::vehicle::HDoorPosition::FRONT_LEFT;
if (doorStr == "fl") { doorPosition = ccos::vehicle::HDoorPosition::FRONT_LEFT; } else if (doorStr == "fr") { doorPosition = ccos::vehicle::HDoorPosition::FRONT_RIGHT; } else if (doorStr == "rl") { doorPosition = ccos::vehicle::HDoorPosition::REAR_LEFT; } else if (doorStr == "rr") { doorPosition = ccos::vehicle::HDoorPosition::REAR_RIGHT; }
ccos::vehicle::HTriState doorState; ccos::HResult doorOpenedResult = body->isDoorOpened(doorPosition, doorState); if (doorOpenedResult == ccos::HResult::OK) { cout << "Door Result: " << (doorState == ccos::vehicle::HTriState::TRUE ? "Open" : "Closed"); cout << endl; } else { cout << "isDoorOpened did not return OK. Actual return: " << ccOSUtils::HResultToString(doorOpenedResult); cout << endl; } } else if (command == "doorLock") { if (argc != 3) { cout << "Expected arguments: doorLock {true/false}" << endl; return 0; }
string shouldBeLockedStr = argv[2]; ccos::HBool shouldBeLocked = false;
if (shouldBeLockedStr[0] == 't') { shouldBeLocked = true; }
cout << "Setting Door Locks to: " << (shouldBeLocked ? "Locked" : "Unlocked") << endl;
ccos::HResult doorLockResult = body->requestDoorLock(shouldBeLocked); if (doorLockResult == ccos::HResult::OK) { cout << "Door Lock Success" << endl; } else { cout << "Door Lock Failure: " << ccOSUtils::HResultToString(doorLockResult) << endl; } } else if (command == "status") { ccos::vehicle::general::HChassis *chassis = ccos::vehicle::general::HChassis::getInstance();
ccos::HFloat odometerReading = 0; chassis->getOdometer(odometerReading);
ccos::HFloat batteryVoltage = 0; chassis->getBatteryVoltage(batteryVoltage);
ccos::HUInt8 percentBatteryRemaining = 0; chassis->getRemainBattery(percentBatteryRemaining);
cout << "Vehicle Status:" << endl; cout << "tOdometer: " << odometerReading << endl; cout << "tBattery Voltage: " << batteryVoltage << "V" << endl; cout << "tBattery Remaining: " << percentBatteryRemaining << "%" << endl; } else if (command == "test") { cout << "Testing methods that might not work" << endl;
ccos::HResult testResult;
cout << "tTesting Wireless Charging Pad State" << endl; ccos::HUInt8 wirelessChargingPadState = 0; testResult = body->getWirelessChargingPadState(wirelessChargingPadState); cout << "tt" << ccOSUtils::HResultToString(testResult) << " - State: " << wirelessChargingPadState << endl;
cout << "tTesting Window State (Driver)" << endl; ccos::vehicle::HWindowType windowType = ccos::vehicle::HWindowType::DRIVER; ccos::vehicle::HTriState windowState; ccos::HUInt8 windowDetail; body->getWindowOpenState(windowType, windowState, windowDetail); cout << "tt" << ccOSUtils::HResultToString(testResult) << " - State: " << ccOSUtils::HTriStateToString(windowState) << "Detail?: " << windowDetail << endl;
cout << "Completed testing methods that might not work" << endl; } else { cout << "Unknown Command" << endl; }
cout << "Completed" << endl; return 0;}

我做了一个更高级的应用程序,它允许我查询特定的门,锁或解锁门,读取汽车的一些基本统计数据,并测试一些可能不起作用的方法(它们在头文件的一个部分下,有一个注释说:“//未完成”:/)

好了,回到VS Code,运行构建任务!
HowIHackedMyCar 2021款 现代IONIQ (三) Making Software
我还是不知道它出了什么问题,于是我回到了谷歌。这个有点麻烦,但最终我发现这些库使用的是旧的ABI,但幸运的是修复起来很容易。我所要做的就是把“-D_GLIBCXX_USE_CXX11_ABI=0”放入我的编译器参数中。

终于编译成功了,我把它放在 IVI 上,它奏效了!我能够运行我的门查询,锁定和解锁门,并运行我的测试。(事实上,这些函数确实没有完成)
HowIHackedMyCar 2021款 现代IONIQ (三) Making Software

GUI
既然我已经完成了基本的命令行程序,那么是时候开始使用GUI应用程序了。我花了大量的时间尝试执行某些事情的变通方法,但这都是非常不必要的。所以我要在这里记录下什么是有效的。

通过我的逆向研究,我知道系统中所有的 GUI 应用程序都是基于 Qt5 的,并且使用了 Helix,这是由 Wind River Systems 开发的应用程序管理系统。如果我想要制作一个肯定可靠的 GUI 应用程序,我需要将其完全整合到 Helix 系统中,就像所有其他应用程序一样。

Qt准备就绪
为了能够编译 Qt5 应用程序,我首先需要建立一个可用的 Qt 编译器设置。我花了太多时间试图避免自己编译 Qt,但最终发现这是最简单、最容易的路径。

为了正确设置 Qt5,我首先安装了 g++,然后下载并提取了 Qt 5.7.1。我想要为 ARM 交叉编译设置 Qt5,所以我还从 Linaro 下载并安装了 GCC 4.9.4。我选择了 Qt 5.7.1,因为我发现我的 IVI 上的原生应用程序使用的是 Qt 5.7,并且是使用大约版本为 4.9 的 GCC 编译的。我希望编译自己的应用程序尽可能地无缝,所以我选择了尽可能接近的版本,同时仍具有最新的补丁,以避免兼容性问题。

然后,我尝试编译 Qt5,事实上尝试了几次,但每次都遇到不同的错误。我发现的第一个错误之一是 Qt 正在将各种文件安装到我的 IVI 的挂载系统根镜像中,但默认情况下,该镜像没有足够的空间来容纳它们。我使用以下命令使用 dd 命令将 system.img 的大小增加了 1GB,然后使用 resize2fs 调整 system.img 中的文件系统大小,以便能够使用新增的空间:


dd if=/dev/zero count=4 bs=256M >> system.imgsudo mount system.img system_imageFULL_SYSROOT_DIR=$(realpath system_image)SYSROOT_MOUNT_DEVICE=$(df | grep $FULL_SYSROOT_DIR | awk '{print $1}')sudo resize2fs $SYSROOT_MOUNT_DEVICE
我还遇到了其他几个错误,一个与缺少libGLESv2有关,我能够通过在系统映像中添加符号链接来修复它,以便Qt可以找到它。
cp system_image/usr/lib/libGLESv2.so.2 system_image/usr/lib/libGLESv2.so
下一个错误是由于无法编译QtQuick,我不完全确定如何修复它,似乎大多数人在网上得到这个错误只是跳过编译QtQuick,所以我也这样做了。最后,我还不得不跳过编译虚拟键盘模块,因为它也无法编译。在解决了这些问题之后,我有了一个混乱的配置命令:
./configure -device arm-generic-g++ -device-option CROSS_COMPILE=/home/greenluigi1/QtDev/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi/bin/arm-linux-gnueabi- -opensource -confirm-license -sysroot /home/greenluigi1/QtDev/system_image -skip declarative -nomake examples -skip virtualkeyboard 
在它配置后,我运行“gmake -j4”并等待Qt编译。幸运的是,它工作了,我能够运行“gmake install”。

自动化
一旦确认一切都正常工作,我创建了几个脚本来完成大部分复杂的设置。因此,如果将来需要设置开发环境,我只需要将脚本提取到一个新的文件夹中,将一个未修改的 system.img 文件复制到同一个文件夹中,然后运行setupDevelopmentEnvironment.sh。

该脚本将下载并安装正确的交叉编译器、QtCreator,并编译正确版本的 Qt。由于系统镜像的挂载是临时的,我还包含了挂载脚本,以便在重新启动前和开发前重新挂载系统镜像。

现在,我就快完成了,我只需要设置 QtCreator 来使用我的设置即可。


QtCreator
QtCreator是用于开发Qt应用程序的IDE。我安装了apt的最新版本,并开始为D-Audio编译配置它。

在QtCreator的设置中,我设置了两个编译器,一个用于C,一个用于c++,并将它们指向我之前从Linario提取的GCC安装。
HowIHackedMyCar 2021款 现代IONIQ (三) Making Software

然后,我在Qt版本选项卡中添加了我的Qt安装,将其指向我的IVI系统映像根目录中的qmake文件。
HowIHackedMyCar 2021款 现代IONIQ (三) Making Software

最后,我添加了一个名为D-Audio 2的新工具包,并将设置指向使用我的特定Qt版本和编译器。
HowIHackedMyCar 2021款 现代IONIQ (三) Making Software
HowIHackedMyCar 2021款 现代IONIQ (三) Making Software
现在我准备为我的IVI开发GUI应用程序。

NOT YET
我几乎准备好了。我还需要弄清楚如何将我的应用程序整合到应用程序管理器 Helix 中。于是我进行了更多的逆向工程。我决定尝试找到系统中最简单的应用程序,并模仿其设置。

我浏览了系统中的所有 GUI 应用程序,并寻找最小的一个。我选择了位于 /usr/share/AMOS/EProfilerApp 中的 EProfilerApp。根据名称,它是一个简单的 GUI 应用程序,用于查看/管理 AMOS,即内置的系统性能分析工具。我将 EProfilerApp 导入到 IDA 中,并开始分析其二进制代码:

HowIHackedMyCar 2021款 现代IONIQ (三) Making Software
好像运气不错,EProfilerApp 还保留了调试信息!由于这一点,逆向工程变得相对容易。这是我发现的情况:

每个 Helix 应用程序的 main() 函数看起来像这样:

int main(int argc, char *argv[]){    QApplication app(argc, argv);    MyApplication myApplication = MyApplication();    myApplication.init(app, argc, argv);
return app.exec();}
QApplication 是来自 Qt5 的常规 QApplication 类,像普通的 Qt5 应用程序一样初始化。然后创建了应用程序的 Helix 应用程序实例,这里是 MyApplication。MyApplication 继承自 Helix 的 ApplicationQt/Application 类。应用程序的 Application 类的唯一责任是创建 Helix 管理的组件。。

在 Helix 中有三种不同类型的组件:

1. 应用视图(App Views)
   – 表示一个用户可以与之交互的 Qt5 屏幕/窗口。这包括全屏窗口以及弹出窗口。
   – 负责创建、显示、隐藏和销毁普通的 Qt5 窗口。
2. 应用服务(App Services)
   – 表示一个后台进程。
3. 事件接收器(Event Receivers)
   – 表示用于处理系统中可能发出的各种事件的处理程序。

每个组件都有自己的名称,遵循类似 Java 包的命名约定(例如:com.mobis.caudio.setupApp.SetupAppView)。Helix 调用应用程序的 Application 类,并传递组件的名称。然后 Application 类检查名称,并创建/返回正确的 AppView/AppService/EventReceiver,如果名称无效,则返回 nullptr。

Hello World But With Buttons
现在是时候真正制作一个 GUI 应用程序了。该应用程序的源代码可以在这里找到。

该应用程序具有一个名为“ExampleGuiAppView”的单个 AppView,其组件名称为“com.greenluigi1.guiExample.TestAppView”。这个 AppView 创建了一个简单的窗口,其中包含 4 个按钮:


Lock
    锁定车辆所有的门。

Unlock
    解锁车辆的门。
    表现得像钥匙的解锁一样。按下一次将解锁驾驶员侧门,按下两次将解锁所有门。

Test
    向 Logcat 日志打印一条测试消息。

Exit
    使用 finish() 函数退出应用程序。


我已经构建并成功编译了。现在是时候在真实硬件上运行它了。

但在此之前,还有一些事情需要做。我们需要“注册”该应用程序,以便 Helix 能够识别它。Helix 的应用程序管理器通过读取 /etc/appmanager/appconf 目录中的 .ini 文件来工作。每个 .ini 文件告诉 Helix 应用程序的组件名称,列出每个 AppView、AppService 和 EventReceiver,并指定您的 EventReceivers 监听的事件。

一个典型的应用程序配置看起来像这样:

[Application]Name=com.company.grouping.appNameExec=/usr/share/app-appName-1.0.0/appName
[TestAppService]#ComponentName : com.company.grouping.appName.TestAppServiceType=AppService
[TestAppView]#ComponentName : com.company.grouping.appName.TestAppViewType=AppView
[TestEventReceiver]#ComponentName : com.company.grouping.appName.TestEventReceiverType=EventReceiverEvent=com.mobis.caudio.ACTION.POWER_OFF
每个 .appconf 文件以一个名为:“[Application]”的组开始,在该组下,设置了应用程序的基本包名称。这让 Helix 知道,如果一个应用程序请求创建以该包名称开头的组件,它将被重定向到您的应用程序。然后设置了 Exec,即可执行文件本身的位置。

在“[Application]”组之后,可以跟随任意数量的组。每个组代表一个新的组件。组的名称指示组件的名称,例如“[TestAppView]”表示它正在定义一个名为“TestAppView”的组件,或者在这种情况下更具体地说是:“com.company.grouping.appName.TestAppView”。在组件组下是该组件的特定设置。每个组件组都有一个类型,可以是 AppView、AppService 或 EventReceiver。每种类型的组件都可以有自己的设置,例如 EventReceiver 类型具有 Event 属性,该属性是 Receiver 订阅的事件的逗号分隔列表。以“#”开头的行是注释,被 Helix 忽略。

我只需要制作自己的 .appconf 文件,以便启动我的应用程序。这是我想到的内容:

[Application]Name=com.greenluigi1.guiExampleExec=/appdata/guiExample
[TestAppView]# ComponentName : com.greenluigi1.guiExample.TestAppViewType=AppView
它定义了一个名为“com.greenluigi1.guiExample”的应用程序,位于/appdata/guiExample,其中包含一个名为“com.greenluigi1.guiExample.TestAppView”的单个 AppView。现在我只需要将应用程序安装到我的汽车上并运行它。

我将已编译的应用程序及其配置文件复制到了我的 USB 驱动器上,并启动了我的反向 shell。我将根目录挂载为读/写,以便可以修改配置文件夹。然后,我将我的 GuiExampleApp.appconf 配置文件复制到 /etc/appManager/appconf/ 文件夹,并将应用程序本身复制到 /appdata/ 文件夹。

然后我发送了重启命令,等待 IVI 重新启动。

现在我只需要启动我的应用程序,但我应该如何做呢?从命令行运行应用程序时,它不会自行执行任何操作。我们需要告诉 Helix 来启动它。

幸运的是,在之前的调查中,我发现了一个已经安装在机器上的命令行工具,可以做到这一点:appctl。appctl 是一个小程序,允许您:


-启动App View/App Service
      -用法:apptl startAppView {componentName} [args…]
      -用法:apptl startAppService {componentName} [args…]
-完成一个App View/App Service
      -用法:apptl finishAppView {componentName}
      -用法:apptl finishAppService {componentName}
-发出事件
      -用法:appctl emitEvent {event} [args…]
所以我要做的就是运行:
appctl startAppView com.greenluigi1.guiExample.TestAppView
我运行了这个命令,几秒钟后:
HowIHackedMyCar 2021款 现代IONIQ (三) Making Software
Bingo!我的应用程序正在运行。这些按钮也可以完美地工作,让我可以锁门或开门。在退出应用程序后,我还转储了日志,并看到我的测试按钮日志和其他调试日志条目已成功写入Logcat文件中。


HowIHackedMyCar 2021款 现代IONIQ (三) Making Software

HowIHackedMyCar 2021款 现代IONIQ (三) Making Software



原文始发于微信公众号(安全脉脉):HowIHackedMyCar 2021款 现代IONIQ (三) Making Software

版权声明:admin 发表于 2024年5月7日 下午5:46。
转载请注明:HowIHackedMyCar 2021款 现代IONIQ (三) Making Software | CTF导航

相关文章