How I Hacked my Car Part 3: Making Software

Note: As of 2022/10/25 the information in this series is slightly outdated. Hyundai released DAudio2 134.100.220927 which fixes the found security issues. This latest update contains two update files. One that uses the old, flawed keys and one that uses the new keys. This guide should technically still work using the update file that uses the old keys, but I have not verified it personally. This latest update also removes the header files which makes development for this platform more difficult.
注意:截至 2022 年 10 月 25 日,本系列中的信息略有过时。Hyundai 发布了 DAudio2 134.100.220927,修复了发现的安全问题。此最新更新包含两个更新文件。一个使用旧的、有缺陷的密钥,另一个使用新密钥。从技术上讲,本指南应该仍然可以使用使用旧密钥的更新文件,但我还没有亲自验证过。此最新更新还删除了头文件,这使得该平台的开发更加困难。

If you haven’t read Part 1 and Part 2 please do so.
如果您还没有阅读第 1 部分和第 2 部分,请阅读。

Making Software 制作软件 ⌗

I am a programmer by nature. I now had root access to a cool new linux box so now I must develop software for it.
我天生就是一个程序员。我现在可以root访问一个很酷的新linux盒子,所以现在我必须为它开发软件。

The Goal 目标 ⌗

While looking through many of the IVI’s files, I found tons of really cool C++ header files relating to ccOS in /usr/include.
在浏览许多 IVI 的文件时,我在 /usr/include 中发现了大量与 ccOS 相关的非常酷的 C++ 头文件。
How I Hacked my Car Part 3: Making Software

ccOS is the Connected Car Operating System, an OS developed by Nvidia and Hyundai which is supposed to power all Hyundai vehicles from 2022 onwards, but I guess some of the underlying system was in previous Hyundai vehicles for quite some time. Some of the oldest header files have copyright comments from 2016.
ccOS 是联网汽车操作系统,这是由 Nvidia 和现代汽车开发的操作系统,它应该从 2022 年开始为所有现代汽车提供动力,但我想一些底层系统在以前的现代汽车中已经存在了相当长的一段时间。一些最早的头文件有 2016 年的版权注释。

Based on the header files, it looked like they provided a very convinent way to interact with the vehicle, providing functions to query things like the odometer and battery voltage, as well as perform actions like start the engine or lock/unlock the doors.
根据头文件,它们似乎提供了一种非常方便的方式来与车辆进行交互,提供查询里程表和电池电压等功能,以及执行启动发动机或锁定/解锁车门等操作。

The Road of Mistakes
错误之路 ⌗

I wanted to make a basic program that used the ccOS header files to read the status of the doors as well as send a lock or unlock signal to them. I installed Visual Studio Code and the latest version of the g++ arm cross compiler (arm-linux-gnueabi-g++ from the g++-arm-linux-gnueabi package) onto my Kali VM.
我想制作一个基本程序,使用 ccOS 头文件来读取门的状态,并向它们发送锁定或解锁信号。我将 Visual Studio Code 和最新版本的 g++ arm 交叉编译器(g++-arm-linux-gnueabi 包中的 arm-linux-gnueabi-g++)安装到我的 Kali VM 上。

For the first app I would make a simple console based app that would readout if the driver’s side door was open or closed.
对于第一个应用程序,我将制作一个简单的基于控制台的应用程序,如果驾驶员的侧门打开或关闭,它会读取。

In one of the ccOS header files called HBody.h, contained the class HBody. HBody is a singleton and included a static method to get the instance of the HBody. HBody itself included a method called isDoorOpened allows you to see if a specific door is open or not.
在一个名为 HBody.h 的 ccOS 头文件中,包含类 HBody。HBody 是一个单例,包含一个用于获取 HBody 实例的静态方法。HBody 本身包含一个名为 isDoorOpened 的方法,允许您查看特定门是否打开。

All of the query functions in HBody returned a HResult which indicated if it was able to successfully query the object, or what error prevented it from doing so. Each query method also took in a reference to some output type to provide the actual query results.
HBody 中的所有查询函数都返回一个 HResult,该 HResult 指示它是否能够成功查询对象,或者是什么错误阻止了它这样做。每个查询方法还引用了某些输出类型,以提供实际的查询结果。

The isDoorOpened function takes in a HDoorPosition enum to specify what door you are seeing is opened (Front Left/Right, Back Left/Right, or Tail gate) and a reference to a HTriState which indicates if the door is open (False, True, Invalid).
isDoorOpened 函数采用 HDoorPosition 枚举来指定你所看到的门已打开(左前/右、左后/右或尾门)和对 HTriState 的引用,该引用指示门是否打开(False、True、Invalid)。

Here was the code I came up with:
这是我想出的代码:

#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)
我所要做的就是……(不要这样做)⌗

Now all I had to do is compile it. I setup VS Code with a bulid task that used the arm-linux-gnueabi-g++ compiler and set the system root to be the base of the mounted system.img file. I ran the task but it gave me an error.
现在我所要做的就是编译它。我使用使用 arm-linux-gnueabi-g++ 编译器的 bulid 任务设置了 VS Code,并将系统根目录设置为挂载的 system.img 文件的基础。我运行了任务,但它给了我一个错误。

How I Hacked my Car Part 3: Making Software

Oops, yeah I am not used to C++ development and forgot to link to the HBody’s library. Turns out it is called HVehicle. I updated the build task to link it and…
哎呀,是的,我不习惯C++开发,忘记链接到HBody的库。原来它被称为 HVehicle。我更新了构建任务以链接它并…
How I Hacked my Car Part 3: Making Software

Yeah I didn’t know C++ well enough for this. After a little bit of googling I found out that the std libraries that came with my compiler were too new and didn’t contain the specific version that HVehicle needed. I ended up making a specs file to avoid including the default library locations and manually included the /usr/lib/ and /usr/lib/arm-telechips-linux-gnueabi/4.8.1/ folders.
是的,我对 C++ 的了解还不够。经过一番谷歌搜索后,我发现我的编译器附带的 std 库太新了,并且不包含 HVehicle 所需的特定版本。我最终制作了一个规范文件以避免包含默认库位置,并手动包含 /usr/lib/ 和 /usr/lib/arm-telechips-linux-gnueabi/4.8.1/ 文件夹。

Running the build task one more time and…
再次运行构建任务,然后…
How I Hacked my Car Part 3: Making Software

Finally, a working (probably) build! I copied the resulting binary file to my usb drive and hopped in my car.
最后,一个有效的(可能)构建!我将生成的二进制文件复制到我的 U 盘并跳上我的车。

I fired up the reverse shell, copied the binary to the /tmp/ directory, marked it as executable and ran it.
我启动了反向 shell,将二进制文件复制到 /tmp/ 目录,将其标记为可执行文件并运行它。
How I Hacked my Car Part 3: Making Software

Ok, nice. There was a bit of log spam which appears to be comming from HBody, but it did correctly report the door was closed. I opened my door and ran it again and…
好的,很好。有一些日志垃圾邮件似乎来自HBody,但它确实正确地报告了门已关闭。我打开门,又跑了一遍,然后……
How I Hacked my Car Part 3: Making Software

Yes! My beautiful app is working.
是的!我漂亮的应用程序正在工作。

Now time for something a little more complicated.
现在是时候做一些更复杂的事情了。

#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 << "\t\t" << 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 << "\t\t" << 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;
}

I made a more advanced application which allowed me to query specific doors, lock or unlock the doors, read some basic stats from the car, and test some methods that may not work (They were under a section in the header file which had a comment saying: “// uncompleted” :/)
我做了一个更高级的应用程序,允许我查询特定的车门,锁定或解锁车门,从汽车中读取一些基本统计数据,并测试一些可能不起作用的方法(它们在头文件的一个部分下,该部分有一条评论说:“//未完成”:/)

Well, back into VS Code, run the build task and boom!
好吧,回到 VS Code,运行构建任务并繁荣!
How I Hacked my Car Part 3: Making Software

Oh, well something went boom. I again had no idea what it was erroring on, back to Google I went. This one was a bit of a pain, but eventually I found out that the libraries were using an old ABI, but luckily the fix was easy. All I had to do was put “-D_GLIBCXX_USE_CXX11_ABI=0” into my compiler arguments.
哦,好吧,有些事情发生了。我再次不知道它出错了什么,回到谷歌我去了。这有点痛苦,但最终我发现库使用的是旧的 ABI,但幸运的是修复很容易。我所要做的就是将“-D_GLIBCXX_USE_CXX11_ABI=0”放入我的编译器参数中。

It finally compiled, I threw it on the IVI and it worked! I was able to run my door queries, lock and unlock the doors, and run my tests. (The functions were in fact uncompleted)
它终于编译好了,我把它扔在 IVI 上,它起作用了!我能够运行我的门查询,锁定和解锁门,并运行我的测试。(这些功能实际上尚未完成)
How I Hacked my Car Part 3: Making Software

GUI 图形用户界面 ⌗

Since I completed the basic command line program, it was time for me to take on a GUI application. I spent a ton of time trying to perform workarounds for certain things, but it was all very unecessary. So I am going to document here what actually worked.
由于我完成了基本的命令行程序,是时候使用GUI应用程序了。我花了大量时间尝试对某些事情执行变通方法,但这一切都是不必要的。因此,我将在这里记录实际有效的方法。

Through my reverse engineering and research I knew that all of the GUI applications in the system were Qt5 based and used Helix, an application manager system developed by Wind River Systems. If I wanted to make a GUI based application that definitely worked, I would need to fully incorperate it into the Helix system like all of the other apps.
通过我的逆向工程和研究,我知道系统中的所有 GUI 应用程序都是基于 Qt5 的,并使用了 Helix,这是 Wind River Systems 开发的应用程序管理器系统。如果我想制作一个绝对有效的基于 GUI 的应用程序,我需要像所有其他应用程序一样将其完全整合到 Helix 系统中。

Getting Qt Ready
让Qt做好准备 ⌗

To be able to compile a Qt5 application I needed to get a working Qt compiler setup first. I spent way too much time trying to avoid compiling Qt myself, but it was the simplest and easiest path in the end.
为了能够编译Qt5应用程序,我需要先获得一个有效的Qt编译器设置。我花了太多时间试图避免自己编译Qt,但最终这是最简单和最容易的路径。

To get Qt5 set up correctly I first installed g++, then I grabbed Qt 5.7.1 and extracted it, I wanted to setup Qt5 for ARM cross compiling, so I also downloaded and installed GCC 4.9.4 from Linaro. I used Qt 5.7.1 because I discovered the native apps on my IVI used Qt 5.7 and were compiled with GCC around version 4.9. I wanted to make the compilation of my own apps as seamless as possible, so I used as close of versions as I could while still having the latest patches before compatibility would break.
为了正确设置 Qt5,我首先安装了 g++,然后我抓住了 Qt 5.7.1 并解压了它,我想设置 Qt5 进行 ARM 交叉编译,所以我也从 Linaro 下载并安装了 GCC 4.9.4。我使用Qt 5.7.1,因为我发现我的IVI上的本机应用程序使用Qt 5.7,并在4.9版本左右使用GCC编译。我想使我自己的应用程序的编译尽可能无缝,所以我尽可能使用接近的版本,同时在兼容性中断之前仍然拥有最新的补丁。

I then attempted to compile Qt5, a few times in fact but I was plagued with different errors each time. Once of the first errors I found was that Qt was installing various files into my IVI’s mounted system root image, but the image by default does not have the space to fit them. I used the following commands to increase the size of system.img by 1GB using dd and then resize the filesystem within system.img to be able to use the newly added space with resize2fs:
然后我尝试编译 Qt5,事实上有几次,但每次都受到不同错误的困扰。我发现的第一个错误之一是Qt将各种文件安装到我的IVI挂载的系统根映像中,但默认情况下该映像没有空间来容纳它们。我使用以下命令使用 dd 将 system.img 的大小增加 1GB,然后在 system.img 中调整文件系统的大小,以便能够使用 resize2fs 新添加的空间:

dd if=/dev/zero count=4 bs=256M >> system.img

sudo mount system.img system_image

FULL_SYSROOT_DIR=$(realpath system_image)
SYSROOT_MOUNT_DEVICE=$(df | grep $FULL_SYSROOT_DIR | awk '{print $1}')
sudo resize2fs $SYSROOT_MOUNT_DEVICE

I also encountered a couple of other errors, one related to missing libGLESv2, which I was able to fix by adding a symlink within the system image so Qt could find it.
我还遇到了其他几个错误,其中一个与缺少 libGLESv2 有关,我可以通过在系统映像中添加一个符号链接来修复它,以便 Qt 可以找到它。

cp system_image/usr/lib/libGLESv2.so.2 system_image/usr/lib/libGLESv2.so

The next errors were due to it not being able to compile QtQuick, I wasn’t entirely sure on how to fix it, it appeared most people who gets this error online simply skip compiling QtQuick, so that is what I did too. Finally I also had to skip compiling the virtualkeyboard module because it also failed to compile. After fixing these issues I had my mess of a configure command:
接下来的错误是由于它无法编译QtQuick,我不完全确定如何修复它,似乎大多数在线收到此错误的人都只是跳过编译QtQuick,所以我也是这样做的。最后,我还不得不跳过编译 virtualkeyboard 模块,因为它也无法编译。解决这些问题后,我的配置命令一团糟:

./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 

After it configured I ran “gmake -j4” and waited while Qt compiled. Luckily, it worked and I was able to run “gmake install”.
配置完成后,我运行了“gmake -j4”并等待Qt编译。幸运的是,它起作用了,我能够运行“gmake install”。

Automating It 自动化 ⌗

Once I knew everything worked, I created a couple of scripts to do most of the complicated setup. So if I need to setup a development environment in the future, all I needed to do is extract the scripts to a new folder, copy an unmodified system.img file to the same folder, and then run setupDevelopmentEnvironment.sh.
一旦我知道一切正常,我就创建了几个脚本来完成大部分复杂的设置。因此,如果我将来需要设置开发环境,我需要做的就是将脚本解压缩到新文件夹,将未修改的 system.img 文件复制到同一文件夹,然后运行 setupDevelopmentEnvironment.sh。

The script will download and install the correct cross compilers, QtCreator, & compile the correct version of Qt. Since the mounting of the system.img is temporary, I also included the mount script so it is easy to remount the system image after reboot and before development.
该脚本将下载并安装正确的交叉编译器QtCreator,并编译正确版本的Qt。由于 system.img 的挂载是临时的,因此我还包含了挂载脚本,因此在重新启动后和开发之前很容易重新挂载系统映像。

Now I was in the homestretch, I just needed to set up QtCreator to use my setup.
现在我处于状态,我只需要设置QtCreator即可使用我的设置。

QtCreator

QtCreator is the IDE used to develop Qt applications. I installed the latest version from apt and started configuring it for D-Audio compilation.
QtCreator是用于开发Qt应用程序的IDE。我从apt安装了最新版本,并开始为D-Audio编译配置它。

Within QtCreator’s settings I setup two compilers, one for C and one for C++ and pointed them to the GCC install from Linario I extracted earlier.
在QtCreator的设置中,我设置了两个编译器,一个用于C,一个用于C++,并将它们指向我之前从Linario提取的GCC安装。
How I Hacked my Car Part 3: Making Software

I then added my Qt installation in the Qt Versions tab by pointing it to the qmake file within my IVI’s system image root.
然后,我在Qt版本选项卡中添加了Qt安装,方法是将其指向IVI系统映像根目录中的qmake文件。
How I Hacked my Car Part 3: Making Software

And I wrapped it all up by adding a new kit called D-Audio 2 and pointing the settings to use my specific Qt Version and compilers.
我通过添加一个名为 D-Audio 2 的新套件并指向使用我的特定 Qt 版本和编译器的设置来结束这一切。
How I Hacked my Car Part 3: Making SoftwareHow I Hacked my Car Part 3: Making Software

I was now ready to develop GUI application for my IVI.
我现在已经准备好为我的 IVI 开发 GUI 应用程序了。

Not Yet 还没有 ⌗

Well, I was almost ready. I still had to figure out how to incorperate my app within the application manager, Helix. So I went to do some more reverse engineering. I decided to try to find the simplest app in the system I could find and mimic how it was set up.
好吧,我差不多准备好了。我仍然需要弄清楚如何将我的应用程序整合到应用程序管理器 Helix 中。所以我去做了更多的逆向工程。我决定尝试在系统中找到我能找到的最简单的应用程序,并模仿它的设置方式。

I looked through all of the GUI apps in the system and looked for the smallest ones. I settled on EProfilerApp that was found in /usr/share/AMOS/EProfilerApp. It was a simple GUI app which based on the name is used to view/manage AMOS, the built in system profiling tool. I imported EProfilerApp into IDA and:
我浏览了系统中的所有 GUI 应用程序,并寻找最小的应用程序。我选择了在 /usr/share/AMOS/EProfilerApp 中找到的 EProfilerApp。这是一个简单的 GUI 应用程序,根据名称用于查看/管理内置系统分析工具 AMOS。我将 EProfilerApp 导入 IDA 并:
How I Hacked my Car Part 3: Making Software

EProfilerApp still had its debug information left in! Because of this, it was relatively easy to reverse engineer it. Here is what I discovered:
EProfilerApp 仍然保留了调试信息!正因为如此,逆向工程相对容易。这是我发现的:

Every Helix App’s main() function looks like this:
每个 Helix App 的 main() 函数如下所示:

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    MyApplication myApplication = MyApplication();
    myApplication.init(app, argc, argv);

    return app.exec();
}

QApplication is the normal QApplication class from Qt5 and is initialized like a normal Qt5 app. Then an instance of the app’s Helix application is created, in this case MyApplication. MyApplication inherits from Helix’s ApplicationQt/Application classes. The only responsibility of an app’s Application class is to create components for Helix to manage.
QApplication 是 Qt5 中的普通 QApplication 类,初始化方式与普通 Qt5 应用类似。然后,将创建应用的 Helix 应用程序的实例,在本例中为 MyApplication。MyApplication 继承自 Helix 的 ApplicationQt/Application 类。应用的 Application 类的唯一职责是创建要管理的 Helix 组件。

There are 3 different types of components in Helix:
Helix 中有 3 种不同类型的组件:

  • App Views  应用视图
    • Represents a Qt5 screen/window that has controls a user can interact with. This includes full screen windows as well as popups.
      表示具有用户可以与之交互的控件的 Qt5 屏幕/窗口。这包括全屏窗口和弹出窗口。
    • Is in charge of creating, showing, hiding, & destroying a normal Qt5 window.
      负责创建、显示、隐藏和销毁一个普通的Qt5窗口。
  • App Services  应用服务
    • Represents a background process.
      表示后台进程。
  • Event Receivers  事件接收器
    • Represents a handler for the various events which can be emitted throughout the system.
      表示可在整个系统中发出的各种事件的处理程序。

Each component has its own name, which follows a Java-package style naming convention (Ex: com.mobis.caudio.setupApp.SetupAppView). Helix calls into the app’s Application class and passes in the name of the component. The Application class then checks the name and creates/returns the correct AppView/AppService/EventReceiver or nullptr if the name was not valid.
每个组件都有自己的名称,该名称遵循 Java 包样式的命名约定(例如:com.mobis.caudio.setupApp.SetupAppView)。Helix 调用应用的 Application 类并传入组件的名称。然后,Application 类检查名称并创建/返回正确的 AppView/AppService/EventReceiver 或 nullptr(如果名称无效)。

Hello World But With Buttons
Hello World 但有按钮 ⌗

It was now time to actually make a GUI application. The source code for this application can be found here.
现在是时候实际制作 GUI 应用程序了。此应用程序的源代码可以在这里找到。

The application features a single AppView, creatively called “ExampleGuiAppView”, the component name of it is “com.greenluigi1.guiExample.TestAppView”. This AppView creates a simple window with 4 buttons:
该应用程序具有单个 AppView,创造性地称为“ExampleGuiAppView”,其组件名称为“com.greenluigi1.guiExample.TestAppView”。此 AppView 创建一个包含 4 个按钮的简单窗口:

  • Lock  
    • Locks all of the doors in the vehicle.
      锁上车内的所有车门。
  • Unlock  开锁
    • Unocks the doors in the vehicle.
      拧上车门。
    • Acts like the unlock of the key fob. Once press will unlock the driver’s side, two will unlock all doors.
      就像钥匙扣的解锁一样。一旦按下将解锁驾驶员侧,两个将解锁所有车门。
  • Test  测试
    • Prints a test message to the Logcat log.
      将测试消息打印到 Logcat 日志。
  • Exit  退出
    • Exits the application using the finish() function.
      使用 finish() 函数退出应用程序。

I built it and it compiled successfully. Now it was time to get it running on the real hardware.
我构建了它,它编译成功了。现在是时候让它在真正的硬件上运行了。

There were a couple of things I needed to do before this though. We need to “register” the application so Helix can see it. Helix’s application manager works by reading ini files from the /etc/appmanager/appconf directory. Each ini file tells Helix the application’s component name, lists each AppView, AppService, & EventReceiver, as well as states what events your EventReceivers listen to.
不过,在此之前我需要做几件事。我们需要“注册”应用程序,以便 Helix 可以看到它。Helix 的应用程序管理器通过从 /etc/appmanager/appconf 目录读取 ini 文件来工作。每个 ini 文件告诉 Helix 应用程序的组件名称,列出每个 AppView、AppService 和 EventReceiver,并说明 EventReceiver 侦听的事件。

A standard application config looks like this:
标准应用程序配置如下所示:

[Application]
Name=com.company.grouping.appName
Exec=/usr/share/app-appName-1.0.0/appName

[TestAppService]
#ComponentName : com.company.grouping.appName.TestAppService
Type=AppService

[TestAppView]
#ComponentName : com.company.grouping.appName.TestAppView
Type=AppView

[TestEventReceiver]
#ComponentName : com.company.grouping.appName.TestEventReceiver
Type=EventReceiver
Event=com.mobis.caudio.ACTION.POWER_OFF

Each .appconf file begins with an group called: “[Application]” under it, the base package Name of the application is set. This lets Helix know that if an application requests to create a component starting with that package name, it will be directed to your app. Then Exec is set, which is the location of the executable file itself.
每个 .appconf 文件都以一个名为“[Application]”的组开头,在其下设置应用程序的基本包名称。这让 Helix 知道,如果应用程序请求创建以该包名称开头的组件,它将被定向到您的应用程序。然后设置 Exec,这是可执行文件本身的位置。

After the “[Application]” group, any number of groups can follow. Each group represents a new component. The name of the group indicates the name of the component, for example “[TestAppView]” means it is defining a component called “TestAppView”, or in this case more specifically: “com.company.grouping.appName.TestAppView”. Under the component group is the specific settings for the component. Every component group has a Type which is either AppView, AppService, or EventReceiver. Each type of component can have its own settings, for example the EventReceiver type has the Event property which is a comma seperated list of events the Receiver is subscribing to. The lines which start with a “#” are comments and are ignored by Helix.
在“[应用程序]”组之后,可以跟随任意数量的组。每个组代表一个新组件。组的名称指示组件的名称,例如“[TestAppView]”表示它正在定义一个名为“TestAppView”的组件,或者在本例中更具体地说:“com.company.grouping.appName.TestAppView”。组件组下是组件的特定设置。每个组件组都有一个 Type,即 AppView、AppService 或 EventReceiver。每种类型的组件都可以有自己的设置,例如,EventReceiver 类型具有 Event 属性,该属性是 Receiver 订阅的事件的逗号分隔列表。以“#”开头的行是注释,被 Helix 忽略。

I just needed to make my own .appconf file so I could launch my application. This is what I came up with:
我只需要制作自己的 .appconf 文件,这样我就可以启动我的应用程序。这是我想出的:

[Application]
Name=com.greenluigi1.guiExample
Exec=/appdata/guiExample

[TestAppView]
# ComponentName : com.greenluigi1.guiExample.TestAppView
Type=AppView

It defines an application called “com.greenluigi1.guiExample” that is located at /appdata/guiExample” that contains a single AppView called “com.greenluigi1.guiExample.TestAppView”. Now I just needed to install the app on my car and run it.
它定义了一个名为“com.greenluigi1.guiExample”的应用程序,该应用程序位于“/appdata/guiExample”,其中包含一个名为“com.greenluigi1.guiExample.TestAppView”的 AppView。现在我只需要在我的车上安装该应用程序并运行它。

I copied my compiled application and its config file onto my USB drive and loaded up my reverse shell. I mounted the root as read/write so I could modify the config folder. Then I copied my GuiExampleApp.appconf config file into the /etc/appManager/appconf/ folder and copied the application itself to the /appdata/ folder.
我将编译后的应用程序及其配置文件复制到我的 USB 驱动器上,并加载了我的反向 shell。我将根目录挂载为读/写,以便修改配置文件夹。然后,我将 GuiExampleApp.appconf 配置文件复制到 /etc/appManager/appconf/ 文件夹中,并将应用程序本身复制到 /appdata/ 文件夹中。

I then sent the reboot command and waited for the IVI to come back up.
然后,我发送了重新启动命令并等待IVI恢复。

Now I just had to launch my application, but how could I do that? The application doesn’t do anything on its own running it from the command line. We need to tell Helix to launch it.
现在我只需要启动我的应用程序,但我该怎么做呢?应用程序不会自行执行任何操作,从命令行运行它。我们需要告诉 Helix 启动它。

Luckily, during my earlier investigations I found out a command line tool that was already installed on the machine that does this: appctl. appctl is a small program which allows you to:
幸运的是,在我之前的调查中,我发现了一个命令行工具,它已经安装在执行此操作的机器上:appctl。appctl 是一个小程序,可让您:

  • Start an App View/App Service
    启动应用视图/应用服务

    • Usage: appctl startAppView {componentName} [args…]
      用法:appctl startAppView {componentName} [args…]
    • Usage: appctl startAppService {componentName} [args…]
      用法:appctl startAppService {componentName} [args…]
  • Finish an App View/App Service
    完成应用视图/应用服务

    • Usage: appctl finishAppView {componentName}
      用法:appctl finishAppView {componentName}
    • Usage: appctl finishAppService {componentName}
      用法:appctl finishAppService {componentName}
  • Emit an Event  发出事件
    • Usage: appctl emitEvent {event} [args…]
      用法:appctl emitEvent {event} [args…]

So all I needed to do was run:
所以我需要做的就是运行:

appctl startAppView com.greenluigi1.guiExample.TestAppView

I ran the command and after a couple of seconds:
我运行了命令,几秒钟后:
How I Hacked my Car Part 3: Making Software

Bingo! My app was running. The buttons also worked flawlessly, allowing me to lock or unlock my doors. I also dumped the logs after exiting my app and saw that my test button log and other debug log entries were successfully written in the Logcat file.
宾果游戏!我的应用正在运行。按钮也完美无缺,允许我锁定或解锁我的门。在退出我的应用程序后,我还转储了日志,并看到我的测试按钮日志和其他调试日志条目已成功写入 Logcat 文件。

I now had full control over my car’s IVI, which is certainly a nice feeling. There is still more to be learned about the system though and I might make more posts about it as I find out more information.
我现在可以完全控制我的汽车的 IVI,这当然是一种很好的感觉。不过,关于该系统还有更多需要了解的地方,当我找到更多信息时,我可能会发布更多有关它的帖子。

原文始发于Programming With Style:How I Hacked my Car Part 3: Making Software

版权声明:admin 发表于 2024年6月9日 上午10:05。
转载请注明:How I Hacked my Car Part 3: Making Software | CTF导航

相关文章