背景
硬件黑客和修补匠之间有着悠久的传统。一旦一个平台被黑客入侵,一旦一个小工具被修改,一旦一个设备被理解,总会有人提出某个特定的问题。这个问题是:“它能运行《毁灭战士》吗?”
毁灭战士
Doom (1993) 是 id Software 制作的第一人称射击游戏,最初是为 MS-DOS 制作的。 《毁灭战士》是帮助定义第一人称射击游戏类型的游戏之一。事实上,第一人称射击游戏的早期术语之一就是“毁灭战士克隆”。《毁灭战士》是在计算机非常薄弱的时代制作的,至少与今天相比是这样。在那个时代,CPU 的单位是兆赫而不是千兆赫,内存的单位是兆字节而不是千兆字节。由于线性时间的概念,它受到当时技术的限制。由于这些限制,《Doom》被编写为高度优化和可移植的。最终在这些相对较弱的机器上运行得非常好。
1997 年末,《Doom》的源代码发布。这种开源、令人印象深刻但仍然易于运行的游戏的结合,使得应用程序完美风暴,可以移植到几乎任何有屏幕的东西上。因为互联网就是互联网,模因最终围绕着几乎任何小玩意或小玩意都可以运行《毁灭战士》这一理念而形成。无论是在恒温器、示波器、桌面电话,甚至妊娠测试上运行。因为我只是互联网上一个卑微的黑客,渴望获得那种甜蜜的互联网影响力。我当然必须将《毁灭战士》移植到我被黑的汽车上。
端口
使用第 3 部分中可以找到的信息。我设置了用于 DAudio2 开发的 QTCreator IDE,并使用我的 DAudio2 Gui 模板应用程序作为入门模板。
《毁灭战士》和《毁灭战士》有很多风格,并且其中的许多移植可以用作新移植的起点。我决定使用 doomgeneric,这是专门为便于移植而设计的 Doom 版本。
doomgeneric 声称我所要做的就是创建一个 doomgeneric_myPlatform.cpp 文件并实现 5 个简单的函数,就这样,Doom 就会被移植。
“我要做的就是”笑话
是的,事情没那么简单,但也没有那么简单。就像有多少编程项目最终完成一样,我一路上遇到了许多障碍和困难。
但现在,回到开始:
我需要/可以实现 5/6 个功能:
doomgeneric 有一些示例端口,很有帮助。其中之一是 X11/xlib 端口。该端口具有 DGSleepMs()、DGGetTicksMs() 的相当通用的实现,以及一些使 DG_GetKey() 更容易的很好的帮助方法。我决定为我的端口复制这些。
剩下 3 个函数需要实现:DGInit()、DGDrawFrame() 和一些 DG_GetKey() 逻辑。
我创建了 doomgeneric_daudio.cpp 文件并从初始化逻辑开始。
启动
为了简单起见,我在此文件中创建了 main() 函数,并复制了代码以从模板代码初始化我的 TestGuiApplication。我还将所有“测试”占位符文本重命名为“Doom”。
此时,我决定当我有 main() 函数时,不需要单独的 DGInit() 函数来初始化事物。我删除了它并将其中的一些内容移至 main() 中。按照 doomgeneric 的 Readme.md 中的说明,我添加了对 doomgenericCreate() 的调用;
在创建这个 main() 函数时,我遇到了一些复杂的情况,即使它很简单。主要的一个是参数处理。
DAudio2 GUI 应用程序的工作方式有点奇怪。它们不能像大多数 Linux 发行版那样从命令行启动。它们的启动由名为 Helix 的窗口管理器处理,并且参数的格式经过标准化,以允许在应用程序中启动特定的 AppView 或 AppService。
因此,我不能只是将普通参数传递到 doomgeneric_Create() 中,因为它可能会产生意想不到的副作用。因为这是一个愚蠢的小演示,所以我只是硬编码了自己的参数并将其传递出去。
图片漂亮吗?
下一步是将框架绘制到屏幕上。
经过一些研究,我发现我可以使用 QLabel 通过此过程显示帧缓冲区:
-
使用帧缓冲区生成 QImage
-
使用QImage制作QPixmap
-
使用QPixmap作为QLabel的背景图片
doomgeneric 写入的缓冲区称为 DGScreenBuffer。我在主窗口上创建了一个名为 bufferQImage 的字段,并将其设置为引用 DGScreenBuffer。然后我用它制作了一个 QPixmap,用作我的 displayLabel 的背景图像。
每当有新的帧要渲染时,我还需要更新此 QLabel。我创建了一个名为refreshDrawingBuffer 的函数,它调用了displayLabel 上的更新函数。根据我的研究,这应该会导致 QLabel 重新绘制自身。
我连接了 DGDrawFrame() 函数来调用refreshDrawingBuffer() 函数,并设置了一个 QTimer 来重复调用 doomgenericTick() 函数,这应该使游戏运行。
有效吗?
我现在应该已经有了绘制和运行 Doom 演示所需的代码。
为了测试这一点,我需要获取一个 WAD 文件。 WAD 文件是 Doom 引擎的游戏数据文件。它们包含游戏中使用的关卡、地图、敌人和纹理。我找到了原始的 DOOM.WAD 文件并将其下载到我的闪存驱动器中。
为了让 Doom 引擎加载这个 WAD 文件,我必须告诉它该文件的位置。我在参数中硬编码了一条路径,告诉 Doom 读取“/appdata/DOOM.WAD”。
我编译了应用程序并进入了我的车。
为了让 Helix 注册我的应用程序,我必须在 /etc/appmanager/appconf 文件夹中为其添加一个 .appconf 文件。我创建了一个包含以下内容的 DAudio2Doom.appconf 文件并将其复制到该文件夹中:
[Application]
Name=com.greenluigi1.doom
Exec=/appdata/DAudio2Doom
[DoomAppView]
# ComponentName : com.greenluigi1.doom.DoomAppView
Type=AppView
然后我运行以下命令来复制我的 DAudio2Doom 编译的二进制文件,将其标记为可执行文件,重新启动系统以便它可以读取新的 .appconf 文件,最后启动应用程序。
cp /run/media/B208-FF9A/DAudio2Doom /appdata
chmod +x /appdata/DAudio2Doom
reboot
appctl startAppView com.greenluigi1.doom.DoomAppView
运行最后一个命令后,应用程序启动,但很快就发现它无法正常工作。
当我期待《毁灭战士》的第一帧时,出现了一个完全空白的屏幕。
然后整个主机重新启动……
由于 Helix 启动应用程序的方式,在此平台上进行调试有点困难。所以我目前的调试方法是将日志语句放在各处。
因此,为了找出问题所在,是时候记录所有内容了。
我创建了几个方法来帮助记录日志,然后从 Doom 的日志记录函数中调用它们
void DG_Log(const char* logMessage)
{
__android_log_print(ANDROID_LOG_DEBUG, "DAudio2Doom", logMessage);
}
int DG_Log_printf(const char *__restrict __format, ...)
{
int result;
va_list args;
va_start(args, __format);
result = __android_log_vprint(ANDROID_LOG_DEBUG, "DAudio2Doom", __format, args);
va_end(args);
return result;
}
int DG_Log_vprintf(const char *__restrict __format, va_list ap)
{
return __android_log_vprint(ANDROID_LOG_DEBUG, "DAudio2Doom", __format, ap);
}
再次运行并提取日志后,我发现了几个问题,例如我忘记复制 DOOM.WAD 文件。我还发现,当 Doom 遇到诸如找不到有效的 WAD 文件之类的错误时,它会调用 abort() 函数。
应用程序自行中止后,主机中的应用程序看门狗发现应用程序损坏并在几秒钟后重新启动主机。
我禁用了 abort() 调用并复制了 WAD 文件。我更新了二进制文件,并启动了应用程序,却发现另一个空白屏幕。它仍然找不到 WAD 文件。我尝试了一些方法,例如更改参数(使用“-iwad”而不是“-file”),但没有任何效果。因此,我更新了 D_FindIWAD() 函数,使其始终返回硬编码路径“/appdata/DOOM.WAD”,从而消除了错误。
但空白屏幕仍然存在。我仍然缺少一些东西。
我有根据地猜测帧缓冲区或我读取它的方式有问题。我检查的第一件事是帧缓冲区的格式。
帧缓冲区格式 ⌗
帧缓冲区只是一堆形成图像的颜色数据。存储颜色信息时,您必须选择一种格式。格式决定了每个像素占用多少数据、存储什么类型的颜色信息以及存储颜色的顺序。
我当前正在使用 QImage::Format_ARGB32 这意味着 QT 期望数据以以下格式存储:
-
Alpha(透明度):1 字节
-
红色:1字节
-
绿色1字节
-
蓝色1字节
这使得每个像素总共有 4 个字节(或 32 位)的颜色数据。
但我什至不确定这是否正确,当时这只是一个猜测。
于是我查看了doomgeneric提供的例子,看到了RGB888的SDL格式的引用。
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB888, SDL_TEXTUREACCESS_TARGET, DOOMGENERIC_RESX, DOOMGENERIC_RESY);
我更新了代码以使用 QImage::Format_RGB888 格式并运行它。
我至少可以看到那里有东西。这是很明显的《毁灭战士》开始屏幕,只是真的被损坏了。
我循环浏览了其他几个 QImage::Formats 但似乎没有任何效果。我还查看了 Doom 的代码,看起来每个像素都应该是 ARGB,但正如我之前看到的那样,这不起作用。
为了确定格式,我添加了一个“转储”按钮,它将 DG_ScreenBuffer 的内容转储到我的闪存驱动器。
再次加载应用程序后,我转储帧缓冲区并使用名为 RAW 像素查看器的工具来查看数据。
在摆弄参数后,我最终得到了一个工作图像。主机一直在运行,只是无法正常显示!它也显然正在运行游戏演示,这意味着游戏滴答功能工作正常。
图像的格式是BGRA,忽略alpha颜色通道。
如果我们不忽略 Alpha 通道,情况如下:
Doom 不处理任何透明度数据,因此 Alpha 通道始终设置为 0。这意味着我的程序将其读取为完全透明。这就是为什么我在某些格式上出现黑屏的原因。
它“正确”地显示了图像,只是它是不可见的。 😐
有了这些信息,很明显我的代码有两个问题。一是格式,二是图像在屏幕上没有更新的原因。
我将格式设置为 QImage::RGB32,它应该只读取 RGB 并忽略 Alpha 通道。格式不写为 QImage::BGR32 的原因是因为头单元使用小端格式。实际上,这意味着通道以相反的顺序存储和读取,即 BGR 而不是 RGB,这正是我想要的。
接下来,我必须弄清楚为什么屏幕上的图像没有更新。经过几个小时的谷歌搜索和测试各种东西后,我发现更新调用不会刷新 QLabel 的图像,除非 QPixmap 本身被更新。然后我更改了函数,将 QLabel 的 Pixmap 重复设置为我之前制作的 bufferQImage。
它是……美丽
启动新的二进制文件后,我看到了 Doom 在我的车里运行的美丽景象。
现在我只需要连接一些输入,我很快就可以玩了!
有趣的部分 – 输入
我决定尝试通过合并一些连接到主机的输入来使这个端口尽可能有趣,而不是仅仅连接一个无聊的旧键盘。
正如之前的文章中提到的,我在较旧的固件更新中发现了大量头文件,这些文件使我能够访问许多与车辆交互的 API。不幸的是,最新的固件更新中不再提供这些头文件。但幸运的是,现代并没有真正更新任何有价值的东西,所以旧的头文件仍然有效。
(注意,不要告诉现代,但您仍然可以在最新的韩国版本固件中找到文件,只需单击带有“DN8”文本的深蓝色按钮即可。更新甚至未加密,因此您只需解压 zip 并直接获取 system.img 文件,请注意,更新较旧,因此如果它们有效或无效,您的里程数可能会有所不同。)
我查看了许多文件并记下了其中一些可用于游戏输入的文件。以下是引起我注意的:
HChassis::getSteeringAngle(); // Or IHChassisListener::onSteeringAngleChanged();
HChassis::getAcceleratorPedalState(); // Or IHChassisListener::onAcceleratorPedalStateChanged();
HBody::isTurnSignalSwitchOn(); // Or IHBodyListener::onTurnSignalStateChanged()
IHModeChangeListener::onKeyEvent();
HSeat::isSeatBeltBuckleLatched();
我最初尝试了 IHChassisListener 和 IHBodyListener 回调函数。不幸的是我无法让它们工作,所以我直接检查 get() 函数。
使用我之前制作的“转储”按钮,我连接到每个功能并检查它们是否有效。
3/5 不是最好的,但无论如何,有效的才是最重要的。
有了这些新信息,我将以下输入映射到 Doom:
然后,我使用 doomgeneric 的 X11 示例代码中提供的键队列将这些输入连接到 Doom 中。
它可以运行《毁灭战士》吗?是的,它可以!
就像这样,我的车上运行了一个可以正常运行的《Doom》。
完美吗?不,首先没有音频,我承认输入不是最好的。我使用的一些按键也会被后台应用程序接收,如果您想继续前进,它们会执行诸如更改歌曲之类的操作。只要没有播放任何媒体源(通过按下音量旋钮),它就可以正常工作。
哦,转动方向盘时轮胎会移动,所以最好不要进行长时间的游戏,否则你的轮胎会被磨坏。 :p
但对于这个小演示来说,它确实很有趣!
源代码发布
我的汽车黑客冒险的未来
我不是算命先生,不知道未来会怎样。但至少对于我的主机来说,我已经完成了大部分我想做的事情。
我仍然有一些未来可能会探索的应用程序想法,如果发布新的固件更新,我也会尝试破解这些想法。
但在那之前,我会忙着玩《毁灭战士》。
原文始发于微信公众号(车联网攻防日记):【车联网】现代汽车Ioniq SEL渗透测试(8)