Pwn2Own Automotive: Popping the CHARX SEC-3100

Our previous post explored some of the bugs we discovered in the CHARX SEC-3100 ControllerAgent service for Pwn2Own Automotive. We’ll now walk through how these bugs were weaponized to produce a fully remote exploit.
我们之前的文章探讨了我们在Pwn2Own Automotive的CHARX SEC-3100 ControllerAgent服务中发现的一些错误。我们现在将介绍这些漏洞是如何被武器化以产生完全远程攻击的。

We left off with a use-after-free (UAF) primitive. Notably however, the UAF occurs on process teardown (a “one-shot” style bug), and we don’t have any information leaks to easily deal with ASLR (address space layout randomization).
最后,我们使用了释放后使用(UAF)原语。然而,值得注意的是,UAF发生在进程拆除(一个“一次性”风格的错误),我们没有任何信息泄漏,可以轻松处理ASLR(地址空间布局随机化)。

If you want to try exploiting a similar bug on your own, we’ve hosted a challenge with an adapted version of the same bug pattern on our in-browser WarGames platform here.
如果你想尝试自己利用类似的错误,我们已经在我们的浏览器WarGames平台上举办了一个挑战赛。

Pwn2Own Automotive: Popping the CHARX SEC-3100

Traversing a Freed List
遍历已释放列表

To recap, the C++ destructor ordering bug from the first post results in a UAF during object destruction, which occurs within exit handlers during process teardown. We initiate the exit handlers with a null dereference bug (which triggers a signal handler calling exit). A half-destructed object holds a freed std::list, which is then iterated over to find a list node entry with a specific ID.
回顾一下,第一篇文章中的C++析构函数排序错误导致对象析构期间的UAF,这在进程拆除期间发生在退出处理程序中。我们用一个null解引用错误(它触发了一个调用 exit 的信号处理程序)来启动退出处理程序。一个半析构的对象保存一个释放的 std::list ,然后迭代它以找到一个具有特定ID的列表节点条目。

More specifically, the C++ list contains ClientSession objects, and the intent of the list iteration is to find the session to shut down and clean up. In other words, we are traversing a freed list.
更具体地说,C++列表包含 ClientSession 对象,列表迭代的目的是找到要关闭和清理的会话。换句话说,我们正在遍历一个自由列表。

The C++ standard library implements std::list as a linked list, where each node has next / prev pointers followed by the data type inlined:
C++标准库将 std::list 实现为一个链表,其中每个节点都有next / prev指针,后跟内联的数据类型:

std::list<T> {
    std::list_node<T>* head;
    std::list_node<T>* tail;
}

std::list_node<T> {
    std::list_node<T>* next;
    std::list_node<T>* prev;
    T val;
}

In the case of ClientSession, the nodes look like this, having size 0x60:
在 ClientSession 的情况下,节点看起来像这样,大小为 0x60 :

Pwn2Own Automotive: Popping the CHARX SEC-3100

The list destructor iterates over each node, starting at the head, and calls delete on each one. However, for the list “root” itself, it leaves the head / tail pointers alone (i.e. pointing at free memory). Clearing them would be unnecessary, as a destructed list is invalid anyway.
list析构函数从头节点开始遍历每个节点,并对每个节点调用 delete 。然而,对于列表“根”本身,它只留下头/尾指针(即指向空闲内存)。清除它们是不必要的,因为析构列表无论如何都是无效的。

After the list is destructed, the ClientConnectionManagerTcp destructor ends up notifying the ControllerAgent to invalidate a connection ID. The invalidation function iterates over the (now-destructed) list, looking for the session with matching connection ID.
在列表被析构之后, ClientConnectionManagerTcp 析构函数最终通知 ControllerAgent 使一个连接ID无效。无效函数遍历(现在被析构的)列表,寻找具有匹配连接ID的会话。

The pseudocode for the list traversal looks something like this:
列表遍历的伪代码看起来像这样:

void ControllerAgent::on_client_removed() {
void ControllerAgent::on_client_removed(){
    // client_sessions list already destructed! cur pointing at now-freed first list node
// client_sessions列表已经被析构!指向现在释放的第一个列表节点的cur
    std::list_node<ClientSession>* cur = this->client_sessions.head;
std::list_node <ClientSession>* cur = this->client_sessions.head;


    while (cur != &this->client_sessions) {
while(cur!&this->client_sessions){
        if (cur->m_isConnectionAssigned
if(cur->m_isConnectionError)
                && m_clientConnection->vtable->get_connection_id() == <expected ID>) {
&& m_clientConnection->vtable->get_connection_id()==<expected ID>){
            // unassign the connection …
// unassign连接.
            break;
打破;
        }
        cur = cur->next;
curr = curr->next;
    }
}


Assuming we can control a node along this traversal, we can easily hijack control flow with the virtual call to get_connection_id.
假设我们可以控制一个节点沿着这个遍历,我们可以很容易地劫持控制流与虚拟调用 get_connection_id 。

Controlling a List Node
控制列表节点

To understand how we can control a node, consider the now-freed head of the list, at which the list traversal starts. When this node is freed during the list destructor, the chunk will have a size class of 0x68, and will be placed into the tcache bin of that size. Fully understanding glibc tcache internals isn’t necessary here; it suffices to say that a tcache bin is just a singly-linked list of free chunks of the same size, where the next pointer is placed at offset 0 in the free chunk.
为了理解我们如何控制一个节点,考虑现在释放的列表头,列表遍历从这里开始。当这个节点在列表析构函数中被释放时,块的大小类为 0x68 ,并将被放入该大小的tcache bin中。这里不需要完全理解glibc tcache的内部结构;只要说tcache bin只是相同大小的空闲块的单链表就足够了,其中下一个指针放置在空闲块中的偏移量0处。

So when the head node is freed, the allocator will essentially do
因此,当头节点被释放时,分配器实际上将执行

p->next = tcache_bin->head;
tcache_bin->head = p;

Conveniently, the next pointer from the tcache’s perspective is in the same location as the next pointer for the std::list_node. This pointer will be overwritten with whatever is currently the head of the tcache bin, while the remaining contents remain untouched (technically a tcache “key” will also be written at offset 8, which overlaps the prev pointer, which we don’t care about).
方便的是,从tcache的角度来看, next 指针与 std::list_node 的 next 指针位于相同的位置。这个指针将被当前tcache bin的头部覆盖,而其余内容保持不变(技术上tcache“key”也将写入偏移量8,与 prev 指针重叠,我们不关心)。

To sum up, when triggering the UAF list traversal above, the first node will be the mostly-untouched now-freed head of the list, while the remaining iterations will be traversing the tcache bin.
总而言之,当触发上面的UAF列表遍历时,第一个节点将是列表中最不受影响的、现在已释放的头部,而其余的迭代将遍历tcache bin。

It now becomes clear that controlling a list node comes down to two things:
现在很清楚,控制列表节点归结为两件事:

  • ensuring the head list node is not “assigned” so the list traversal continues to the 2nd “node” (really a tcache chunk)
    确保头列表节点没有被“分配”,所以列表遍历继续到第二个“节点”(实际上是一个tcache块)
  • placing something in the tcache prior to the std::list<ClientSession> destructor, so we control the 2nd “node”
    在tcache中放置一些东西在 std::list<ClientSession> 析构函数之前,所以我们控制第二个“节点”

The first point is simply logistical in nature. We can connect two clients, then disconnect the first to unassign the session, then finally use the null deref to trigger the exit-handler UAF.
第一点是简单的后勤性质。我们可以连接两个客户端,然后断开第一个客户端以取消分配会话,最后使用null deref触发exit-handler UAF。

Populating the Tcache 填充Tcache

We’ve mentioned briefly that there’s a configAccess operation supported by the JSON TCP messaging. This operation provides read/write access to a small set of configuration variables.
我们已经简要地提到了JSON TCP消息传递支持的 configAccess 操作。此操作提供对一小组配置变量的读/写访问。

Internally, the variables are managed by a ConfigurationManager (a sub-struct of the monolithic ControllerAgent), and are stored as pairs of std::string. Setting these string variables provides a very convenient allocation primitive, and the strings will be freed during the ConfigurationManager destructor, which occurs prior to the list destructor.
在内部,变量由 ConfigurationManager (单片 ControllerAgent 的子结构)管理,并存储为 std::string 对。设置这些字符串变量提供了一个非常方便的分配原语,并且字符串将在列表析构函数之前的 ConfigurationManager 析构函数期间被释放。

The only obstacle to overcome is that normally, the configuration strings can’t contain null bytes… A useful trick for dealing with this is realizing that assigning new values to a std::string does not necessarily cause a reallocation.
要克服的唯一障碍是,通常情况下,配置字符串不能包含空字节..

The standard library implementation will only allocate a new backing store if the current one is too small. Otherwise, the new string will simply get copied into the existing allocation, with a null byte tacked on the end.
如果当前的后备存储太小,标准库实现将只分配一个新的后备存储。否则,新字符串将简单地复制到现有的分配中,并在末尾添加一个空字节。

For example, with an existing string of AAAA, assigning a new string BB will result in the same allocation being reused, now containing BB\0A.
例如,对于现有的字符串 AAAA ,分配新的字符串 BB 将导致相同的分配被重用,现在包含 BB\0A 。

So far, the general plan of attack is:
到目前为止,总的进攻计划是:

  • Set a config value to a string of size 0x60
    将配置值设置为大小为 0x60 的字符串
  • Repeatedly assign smaller strings to embed nulls and construct a fake std::list_node<ClientSession>
    重复分配较小的字符串来嵌入空值并构造一个假的 std::list_node<ClientSession>
  • Trigger null deref => exit handlers / destructors
    触发null deref => exit handlers / destructors
  • Config string with fake node is freed, placed in tcache
    释放带有假节点的配置字符串,将其放入tcache
  • The list of sessions is destructed, first node freed into tcache
    会话列表被析构,第一个节点被释放到tcache中

    • The next pointer becomes whatever was in the tcache, i.e. the fake node
      下一个指针变成tcache中的任何内容,即伪节点
  • UAF list traversal goes to 2nd fake node (the freed config string)
    UAF列表遍历到第二个假节点(释放的配置字符串)
  • Hijacked virtual call from fake node’s m_clientConnection
    来自假节点 m_clientConnection 的虚拟呼叫被劫持
Pwn2Own Automotive: Popping the CHARX SEC-3100

This leaves one crucial question unanswered: with ASLR, we don’t know where to point the fake m_clientConnection, where gadgets are, etc. There are plenty of large buffers in the BSS (e.g. TCP input) in which to place our fake object, if only we knew where they were in memory.
这留下了一个关键问题没有回答:使用ASLR,我们不知道在哪里指向假的 m_clientConnection ,小工具在哪里,等等。BSS中有很多大的缓冲区(例如TCP输入)来放置我们的假对象,只要我们知道它们在内存中的位置。

Without any information leaks on hand, we’ll need to get creative…
在没有任何信息泄露的情况下,我们需要创造性地…

ASLR Entropy ASLR熵

As we saw when discovering the UAF, a system monitor / watchdog restarts the controller agent if it exits, so a brute-force approach of guessing the ASLR slide, crashing, and restarting seems like a viable option. The CHARX SEC-3100 runs 32-bit ARM Linux, which only has 8 bits of ASLR randomization by default.
正如我们在发现UAF时所看到的,如果控制器代理退出,系统监视器/看门狗会重新启动控制器代理,因此猜测ASLR滑动、崩溃和重新启动的暴力方法似乎是一个可行的选择。CHARX SEC-3100运行32位ARM Linux,默认情况下只有8位ASLR随机化。

That’s only 256 possible slides, which is relatively brute-forceable.
只有256个可能的幻灯片,这是相对蛮力。

One caveat is that each iteration could take upwards of 6 seconds, putting the average / expected runtime until successful exploitation over 25 minutes… a bit too long for comfort. Instead of naively brute-forcing ASLR, we can do much better.
一个警告是,每次迭代可能需要6秒以上,将平均/预期的运行时间放在25分钟以上,直到成功利用……有点太长了。我们可以做得更好,而不是天真地野蛮地强迫ASLR。

ASLR “Bypass”: BSS Spraying
ASLR“旁路”:BSS喷涂

A noticeable feature of the controller agent binary was how massive its BSS segment was, roughly 0x1b3000 bytes, or 435 pages. This invited the idea that we could place multiple fake objects in various pages at the same page offset, such that if any of the payloads ended up at the correct address, the exploit would succeed.
控制器代理二进制文件的一个值得注意的特征是它的BSS段有多大,大约是 0x1b3000 字节,或者435页。这就引出了一个想法,即我们可以在不同的页面中以相同的页面偏移量放置多个假对象,这样,如果任何有效载荷最终位于正确的地址,则利用就会成功。

To illustrate, imagine if we had fake objects at un-slid addresses 0x1040 and 0x2040, then guessed address 0x2040 as the pointer in our fake list node. This guess would work both with an ASLR slide of 0 or 0x1000. This alone would double the probability of success. With n fake objects, the probability becomes n / 256.
为了说明,想象一下,如果我们在未滑动的地址 0x1040 和 0x2040 处有假对象,然后猜测地址 0x2040 作为我们的假列表节点中的指针。这种猜测在ASLR幻灯片为0或 0x1000 的情况下都有效。仅此一点,成功的可能性就增加了一倍。对于 n 假对象,概率变为 n / 256 。

Pwn2Own Automotive: Popping the CHARX SEC-3100

The next logical step is to find some large structure(s) in the BSS that can hold these fake objects.
下一个合乎逻辑的步骤是在BSS中找到一些可以容纳这些假对象的大型结构。

Closer inspection revealed that over 80% of the BSS belonged to a structure V2GMessageReqMsg, with a size of over 0x167000 bytes. This structure holds the most recently parsed TCP JSON message with operation v2gMessage. So how much of this structure can we control?
仔细检查发现,超过80%的BSS属于结构 V2GMessageReqMsg ,大小超过 0x167000 字节。此结构包含最近解析的TCP JSON消息,操作为 v2gMessage 。那么,我们能控制这个结构的多少呢?

Populating the V2G Structure
构建V2 G结构

V2G (vehicle-to-grid) is a protocol related to electric vehicles selling power back to the grid, and the JSON messaging expects keys like salesTariff and consumptionCost. This structure contains several arrays within sub-structures, which in turn contain other arrays. These nested arrays explain the structure’s large size.
V2 G(vehicle-to-grid)是一种与电动汽车向电网出售电力相关的协议,JSON消息需要像 salesTariff 和 consumptionCost 这样的键。此结构在子结构中包含几个数组,子结构又包含其他数组。这些嵌套数组解释了结构体的大尺寸。

Since this structure is intended to be populated from user input (over TCP), many of the fields can be controlled. However, each fake object must use controllable fields all at the same page offset, which introduces significant constraints. The sub-structures are not of an aligned size, so an easily controllable field in one page may be impossible to control in the next page. Similarly, 8 bytes may be controlled in one page, but only 4 in others, etc.
由于此结构旨在通过用户输入(通过TCP)填充,因此可以控制许多字段。但是,每个伪对象必须使用相同页面偏移量的可控字段,这引入了重要的约束。子结构的尺寸不对齐,因此一个页面中容易控制的字段可能不可能在下一个页面中控制。类似地,在一个页面中可以控制8个字节,但在其他页面中仅控制4个字节,等等。

To relax the constraints somewhat, we’ll have fake objects of only 4 bytes. In other words, each fake object is solely a vtable. Each of these vtables could then point into unconstrained “raw” buffers for TCP / UDP / HomePlug packet input.
为了放松一些限制,我们将使用只有4个字节的伪对象。换句话说,每个伪对象都是一个vtable。然后,这些vtable中的每一个都可以指向用于TCP / UDP / HomePlug分组输入的不受约束的“原始”缓冲区。

Accommodating these constraints means only being able to use certain fields for our V2G JSON input message. We end up crafting a message looking something like the JSON blob below, where the {"": 0} objects are padding to advance to the next page:
排除这些约束意味着只能为我们的V2 G JSON输入消息使用某些字段。我们最终制作了一个看起来像下面JSON blob的消息,其中 {"": 0} 对象被填充以前进到下一页:

Pwn2Own Automotive: Popping the CHARX SEC-3100

In this way, we were able to populate fake objects in 83 pages, accounting for 83 of the 256 possible ASLR slides. That gives our exploit a probability close to 1 in 3 for bypassing ASLR, a huge improvement over a naive brute force of 1 in 256.
通过这种方式,我们能够在83页中填充假对象,占256张可能的ASLR幻灯片中的83张。这使得我们的漏洞绕过ASLR的概率接近1/3,比1/256的原始蛮力有了巨大的改进。

The V2G messaging required some other initialization to take place after program startup, so each brute force iteration takes longer waiting for setup to complete, resulting in ~15 seconds per iteration. That said, the enhanced probability is well worth the per-iteration slowdown.
V2 G消息传递需要在程序启动后进行一些其他初始化,因此每次暴力迭代需要更长的时间等待设置完成,导致每次迭代约15秒。也就是说,提高的概率是值得每次迭代放缓的。

COP Chain COP链

The increased probability of having our fake object at the guessed address is great, but we’ve also restricted ourselves to a fake object with just a vtable and no additional payload. Our primitive has evolved from a simple UAF into an arbitrary virtual call, but with only 4 controlled bytes at the “this” argument.
在猜测的地址上拥有假对象的可能性增加了,但是我们也限制了自己使用一个只有一个vtable而没有额外负载的假对象。我们的原语已经从一个简单的UAF演变成一个任意的虚拟调用,但是在“this”参数处只有4个受控字节。

To turn this into full code execution, we’ll use a sequence of COP (call-oriented programming) gadgets, to eventually achieve an arbitrary call with an arbitrary argument.
为了将其转换为完整的代码执行,我们将使用一系列COP(面向调用的编程)小工具,最终实现带有任意参数的任意调用。

We start with control of r0 and the 4 bytes within (the vtable of the fake object). We jump into the following gadget (where we really only care about the highlighted instructions):
我们首先控制 r0 和其中的4个字节(伪对象的vtable)。我们跳转到下面的小工具(我们只关心突出显示的指令):

0x498148:    mov     r4, r0
0x498148: mov r4,r0
0x49814a:    ldr     r7, [r0, #0]
0x49814a: LDR r7,[r0,#0]
0x49814c:    mov     r5, r1
0x49814c: mov r5,r1
0x49814e:    mov     r6, r2
0x49814e: mov r6,r2
0x498150:    mov     r1, r3
0x498150: mov 且r\r3
0x498152:    ldr     r2, [sp, #24]
0x498152: LDR r2,[sp,#24]
0x498154:    ldr     r3, [r7, #20]
0x498154: LDR r3,[r7,#20]
0x498156:    blx     r3
0x498156: BLX R3


This gadget essentially does mov r4, r0, then transfers control to a different vtable function (the next gadget). This next gadget will be:
这个gadget基本上执行 mov r4, r0 ,然后将控制权转移到另一个vtable函数(下一个gadget)。下一个小工具将是:

0x4a32ea:    ldr     r0, [r4, #0]
0x4a32ea: LDR r0,[r4,#0]
0x4a32ec:    ldr     r3, [r0, #0]
0x4a32ec: LDR r3,[r0,#0]
0x4a32ee:    ldr     r3, [r3, #8]
0x4a32ee: LDR r3,[r3,#8]
0x4a32f0:    blx     r3
0x4a32f0: BLX R3


This gadget dereferences r4, and treats the value as a C++ object, dispatching a virtual call. In combination with the previous gadget, we’ve essentially done a simple dereference r0 = *r0.
这个小工具取消引用 r4 ,并将该值视为C++对象,分派一个虚拟调用。结合前面的小工具,我们基本上完成了一个简单的解引用 r0 = *r0 。

Crucially, this will treat what was previously the fake vtable, as a full-blown 2nd fake object. Since this 2nd fake object will be in an unconstrained buffer, we now have an arbitrary virtual call with a fully-controlled “this” argument (containing arbitrary fields).
至关重要的是,这将把以前的假vtable当作一个完整的第二个假对象。由于第二个伪对象将在一个无约束的缓冲区中,我们现在有一个带有完全控制的“this”参数(包含任意字段)的任意虚拟调用。

From here, we’ll trigger the mov r4, r0 gadget once more, before jumping into our final gadget, which is part of a function that loops over an array of function pointer / argument pairs. The gadget is a bit longer, but we’ve highlighted the path from r4 control to the function pointer invocation:
从这里开始,我们将再次触发 mov r4, r0 小工具,然后跳转到最后一个小工具,它是一个函数的一部分,该函数循环遍历一个函数指针/参数对数组。这个小工具有点长,但我们突出显示了从 r4 控件到函数指针调用的路径:

0x458d12:    ldrh    r1, [r4, #8]
0x458d12: 自由民主共和国 r1,[r4,#8]
0x458d14:    ldr     r3, [r4, #4]
0x458d14: LDR r3,[r4,#4]
0x458d16:    ldr     r4, [sp, #0]
0x458d16: LDR r4,[sp,#0]
0x458d18:    cbnz    r1, 0x458d24
0x458d18: cbnz r1,0x458d24
0x458d1a:    b.n     0x458d06
0x458d1a: B.n 0x458d06
0x458d1c:    subs    r1, #1
0x458d1c: 潜艇 r1,#1
0x458d1e:    add.w   r3, r3, #16
0x458d1e: 添加.w r3,r3,#16
0x458d22:    beq.n   0x458d06
0x458d22: beq.n. 0x458d06
0x458d24:    ldrd    r2, r0, [r3]
0x458d24: ldrd r2、r0、[r3]
0x458d28:    eors    r2, r4
0x458d28: eors r2,r4
0x458d2a:    tst     r2, r0
0x458d2a: TST r2,r0
0x458d2c:    bne.n   0x458d1c
0x458d2c: bne.n 0x458d1c
0x458d2e:    ldr     r2, [r3, #12]
0x458d2e: LDR r2,[r3,#12]
0x458d30:    cmp     r2, #0
0x458d30: CMP r2,#0
0x458d32:    beq.n   0x458d06
0x458d32: beq.n. 0x458d06
0x458d34:    ldr     r0, [r3, #8]
0x458d34: LDR r0,[r3,#8]
0x458d36:    mov     r1, r6
0x458d36: mov r1、r6
0x458d38:    blx     r2
0x458d38: BLX R2


We end up with an arbitrary call with an arbitrary first argument, which we can simply direct to system (which is already present in the PLT) to spawn a connect-back shell.
最后,我们得到了一个带有任意第一个参数的任意调用,我们可以简单地将其指向 system (它已经存在于PLT中)以生成一个连接返回shell。

To sum up the COP chain:
总结一下COP链:

  • “bootstrap” from fake vtable into fully-controlled 2nd fake object, using r0 = *r0 gadget pair
    使用 r0 = *r0 小工具对,从伪vtable“引导”到完全受控的第二个伪对象

    • mov r4, r0 gadget followed by
    • ldr r0, [r4] gadget  ldr r0, [r4] 小工具
  • call system using gadget accepting function pointer / argument structure
    调用 system 使用小工具接受函数指针/参数结构

    • mov r4, r0 gives r4 control  mov r4, r0 赋予 r4 控制权
    • next gadget fetches function pointer / argument from r4
      下一个gadget从 r4 获取函数指针/参数

Constructing Fake Objects
构建假对象

We have a path from fake 4-byte object to 2nd fake object to system, but it is worth noting that each of these 2nd fake objects must exist separately, one for each ASLR slide. Each 2nd fake object must have different vtable pointers, gadget addresses, commandline-for-system string argument addresses, etc…
我们有一个从4字节伪对象到第二个伪对象再到 system 的路径,但值得注意的是,这些第二个伪对象中的每一个都必须单独存在,每个ASLR幻灯片一个。每个第二个假对象必须有不同的vtable指针,gadget地址,命令行系统字符串参数地址等。

The 2nd fake objects will be spread out across 3 “raw” buffers:
第二个假对象将分布在3个“原始”缓冲区中:

  • TCP input TCP输入
  • UDP broadcast input UDP广播输入
  • HomePlug packet input HomePlug数据包输入

The received UDP input and HomePlug packets are completely raw, but the TCP buffer undergoes some string processing, namely the messages are newline-delimited. This is not fundamentally restrictive, but requires additional iterations to embed null bytes (similar to the std::string behavior), and the payload can’t contain newlines.
接收到的UDP输入和HomePlug数据包是完全原始的,但TCP缓冲区会进行一些字符串处理,即消息是以换行符分隔的。这并没有本质上的限制,但是需要额外的迭代来嵌入空字节(类似于 std::string 行为),并且有效负载不能包含换行符。

With all the payloads in place, we end up with something conceptually like this:
在所有有效载荷都到位之后,我们最终得到了这样的概念:

Pwn2Own Automotive: Popping the CHARX SEC-3100

Depending on the ASLR slide, only one (or none) of the fake object pairs will actually be at the right addresses, so only one set of arrows shown would be valid at once.
根据ASLR幻灯片,只有一个(或没有)假对象对实际上位于正确的地址,因此一次只有一组箭头有效。

Putting Everything Together
把一切放在一起

With a strategy defined, implementing the exploit becomes wrapping everything in the necessary brute force logic.
有了定义的策略,实现漏洞利用就变成了将所有内容都包装在必要的蛮力逻辑中。

To recap the exploit flow in one place, we perform the following steps in a loop:
为了在一个地方概括漏洞利用流程,我们在一个循环中执行以下步骤:

  1. Wait for the agent to perform V2G-prerequisite initialization
    等待代理执行V2 G先决条件初始化
  2. Use a config value std::string to craft a fake list node
    使用配置值 std::string 手工创建一个假列表节点
  3. Populate V2GMessageReqMsg in the BSS with many fake objects
    在BSS中使用许多假对象填充 V2GMessageReqMsg

    • each is at the same page offset, and only 4 bytes for a vtable
      每一个都在相同的页偏移量上,并且对于一个vtable只有4个字节
    • each vtable points at a 2nd fake object in a raw buffer
      每个vtable指向原始缓冲区中的第二个伪对象
  4. Populate raw BSS buffers with 2nd fake objects (TCP / UDP / HomePlug packets)
    使用第二个假对象(TCP / UDP / HomePlug数据包)填充原始BSS缓冲区
  5. Trigger HomePlug null deref to kick off exit handlers (ControllerAgent destructor)
    触发HomePlug null deref以启动退出处理程序( ControllerAgent 析构函数)
  6. Config value with fake list node is freed into tcache
    带有假列表节点的配置值被释放到tcache中
  7. List destructor frees list nodes
    列表析构函数释放列表节点

    • head node’s next pointer overwritten with next tcache pointer, the fake list node
      头节点的下一个指针被下一个tcache指针覆盖,伪列表节点
  8. ClientConnectionManagerTcp destructor ends up invoking UAF list traversal
    ClientConnectionManagerTcp 析构函数结束调用UAF列表遍历

    • UAF list traversal uses guessed fake object pointer in crafted fake list node
      UAF列表遍历在精心编制的伪列表节点中使用猜测的伪对象指针
  9. Hijacked virtual call on guessed fake object address
    在猜测的假对象地址上劫持虚拟调用

    • if ASLR slide was accounted for, address will be valid, otherwise will crash
      如果考虑了ASLR幻灯片,则地址将有效,否则将崩溃
    • enter COP chain invoking system
      输入COP链调用 system
  10. Check for connect-back shell, otherwise wait for service to restart and repeat
    检查连接回shell,否则等待服务重新启动并重复

With the BSS spray technique giving an 83 in 256 probability of success, and roughly 15 seconds required for each iteration attempt, the average / expected runtime for the exploit is under a minute.
由于BSS喷射技术的成功概率为83/256,每次迭代尝试大约需要15秒,因此漏洞利用的平均/预期运行时间不到一分钟。

For reference, you can find the full exploit code here.
作为参考,你可以在这里找到完整的漏洞利用代码。

原文始发于Jack Dates:Pwn2Own Automotive: Popping the CHARX SEC-3100

版权声明:admin 发表于 2024年7月25日 上午10:06。
转载请注明:Pwn2Own Automotive: Popping the CHARX SEC-3100 | CTF导航

相关文章