Cve-2020-0022 简单分析记录

背景

cve-2020-0022是安卓蓝牙模块的一个漏洞,出现在Bluedroid蓝牙协议栈的HCI层,影响安卓10以下的系统,
并在安卓10上造成DoS

分析记录

android内核调试环境搞不起来。。orz先把坑开了慢慢搞

国外公布了漏洞详情,给了一个没有ROP的exp,我翻译了一下文章

参考文章

国外的分析文章翻译

原文链接:CVE-2020-0022 an Android 8.0-9.0 Bluetooth Zero-Click RCE – BlueFrag

蓝牙是移动设备的一个主要部分,智能机和智能手表/耳机通过蓝牙连接,但是有很多设备会默认接受附近的任何未授权设备的连接。蓝牙数据包会在蓝牙芯片(也叫控制器)中进行处理,然后传递给主机(Android,Linux等),因此蓝牙芯片的固件和主机的蓝牙子系统是黑客们想实现RCE的目标之一。

在大部分传统蓝牙的实现代码中,都有一个特性是通过蓝牙的ping进行应答。攻击者只需要知道蓝牙设备的地址,即使是目标设备设置为不可见,也通常会在收到通过地址的连接请求后接受连接,例如攻击者可以运行L2PING与目标建立一个L2CAP连接,发送ECHO请求。

接下来我们会讲解一个Android 9中蓝牙的zero-click近距离RCE的利用代码,CVE编号是CVE-2020-0022,在2019年11月3日报告该问题前,我们完成了最新版本的三星Galaxy S10e上拿到一个远程shell的全部步骤。这个漏洞的利用点在Android 10中仍然存在,但是我们在Bionic(Android的Libc)中用了另一个漏洞,让漏洞利用起来更容易了。这个漏洞在1.2.2020,A-143894715的安全补丁中被修复。这里有个完整的PoC的演示视频:

前期工作

通过SEEMOO实验室(德国的一个移动网络安全实验室)的开源项目internalbluefrankenstein,我们花了很多时间去研究Braodcom的蓝牙固件,Internalblue是由Dennis Mant编写的,通过交互的方式来调试固件。在这个项目中通过逆向工程的方法来理解固件的工作细节。

在进一步的分析中,我们构建了Frankenstein,用来仿真固件来进行fuzz。要实现固件仿真,一个关键部分是理解蓝牙核心调度器(BCS,Bluetooth Core Scheduler)。这个组件很有意思,它处理了数据包和payload的头部,并且管理实时任务(time-critical task)。这些低级功能无法通过主机来访问,甚至在固件本身的线程组件中也无法访问。通过访问BCS,我们甚至可以将原始的无线数据帧注入到仿真的固件中。

在Frankenstein的fuzz中,我们更关注的是在蓝牙配对前产生的漏洞。在协议部分我们发现了两个漏洞,一个在传统的蓝牙中,一个在低功耗的蓝牙(BLE,Bluetooth Low Energy)中。第一个堆溢出的漏洞是在蓝牙扫描结果(EIR,Extended Inquiry Result)的过程处理中产生,影响在2010-2018年构建的固件中,甚至可能更早CVE-2019-11516,我们在2019年4月向Broadcom提供了完整的RCE的PoC,在报告提交后,Broadcom表示他们已经知道这个问题,实际上最新的三星Galaxy S10e中有一个我们不知道的补丁,因为它刚发布。第二个堆溢出的漏洞影响蓝牙4.2后的所有的BLE分组数据单元(PDUs,Packet Data Units)。我们在2019年6月向Boardcom提供了PoC,它破坏了堆,但是遗漏了一个通过更多数据吞吐实现的原语。据我们所知,这个问题到2020年2月还没有修复。

在研究PoCs和如何将大量数据放入堆中的办法时,我们还研究了传统蓝牙的异步无连接数据包(ACL,Asynchronous Connection-Less)。它主要用于数据传输,如音乐流,数据共享或者更常见的L2CAP。在固件中,ACL的处理相对简单一些。还有更复杂的处理程序和专有协议扩展,比如Jiska Classen发现的链接管理协议(LMP,Link Management Protocol)的类型混淆漏洞CVE-2018-19860

Fuzzing ACL

本文中描述的这个漏洞是在ACL中触发的。我们对这个协议进行了Fuzz测试,对数据包的payload头和包进行了位翻转。最初的fuzzer是通过在固件中hook函数bcs_dmaRxEnable实现的。该函数由BCS ACL任务调用。bcs_dmaRxEnable能将无线数据帧复制到传输缓冲区,在此函数之前,数据包和payload头已经写入了相应的硬件寄存器中了。因此我们可以通过在传输之前就修改整个数据包,从而在固件中构建一个简单的蓝牙fuzz工具。

在一开始的设置中,我们通过无线方式,在Linux主机上对Android设备运行L2PING,并且对蓝牙固件fuzzer中随机翻转头部的比特位。当我们试图崩溃蓝牙设备时,安卓的蓝牙守护进程却崩溃了。在日志中我们观察到几个这样的崩溃报告:

pid: 14808, tid: 14858, name: HwBinder:14808_  >>> com.android.bluetooth <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x79cde00000
    x0  00000079d18360e1  x1  00000079cddfffcb  x2  fffffffffff385ef  x3  00000079d18fda60
    x4  00000079cdd3860a  x5  00000079d18360df  x6  0000000000000000  x7  0000000000000000
    x8  0000000000000000  x9  0000000000000000  x10 0000000000000000  x11 0000000000000000
    x12 0000000000000000  x13 0000000000000000  x14 ffffffffffffffff  x15 2610312e00000000
    x16 00000079bf1a02b8  x17 0000007a5891dcb0  x18 00000079bd818fda  x19 00000079cdd38600
    x20 00000079d1836000  x21 0000000000000097  x22 00000000000000db  x23 00000079bd81a588
    x24 00000079bd819c60  x25 00000079bd81a588  x26 0000000000000028  x27 0000000000000041
    x28 0000000000002019  x29 00000079bd819df0
    sp  00000079bd819c50  lr  00000079beef4124  pc  0000007a5891ddd4

backtrace:
    #00 pc 000000000001ddd4  /system/lib64/libc.so (memcpy+292)
    #01 pc 0000000000233120  /system/lib64/libbluetooth.so (reassemble_and_dispatch(BT_HDR*) [clone .cfi]+1408)
    #02 pc 000000000022fc7c  /system/lib64/libbluetooth.so (BluetoothHciCallbacks::aclDataReceived(android::hardware::hidl_vec<unsigned char> const&)+144)
    [...]

看起来似乎是reassemble_and_dispatch函数中的memcpy以负数长度执行了。memcpy的简单实现如下:

void *memcpy(char *dest; char *src, size_t *n) {
for (size_t i=0; i<n; i++)
dst[i] = src[i];
}

参数中的长度n的类型是size_t,是一个无符号整数,如果我们传一个负数n进去,它会因为补码表示法变成一个很大的正数,导致了memcpy在一个无限循环中不断复制内存,进而遇到一些未映射的内存而崩溃。

L2CAP 分片

蓝牙在不同的层中进行分段,在分析整个崩溃时,我们主要关注蓝牙控制器和主机之间传递的L2CAP数据包的分片。对于主机和蓝牙控制器之间的命令和配置,使用了主机控制接口(HCI,Host Controller Interface)。

L2CAP作为ACL包通过与HCI相同的UART(通用异步收发传输器,Universal Asynchronous Receiver/Transmitter)连接发送。它需要按照最大ACL数据包长度进行分割,在主机上的驱动程序进行固件初始化期间,HCI命令读取缓冲区大小,在Boardcom的芯片上这个长度是1021,主机的驱动程序在向固件发送数据包的时候需要按照这个大小限制来发送。同样的,固件也拒绝L2CAP的输入,因为这些输入没有被正确的按照大小分片。由于分片和重组都是在主机上,但是固件本身也有数据包的大小限制,所以L2CAP对于主机和蓝牙控制器的堆利用很有意思。

如果接收到的L2CAP数据包的长度大于缓冲区的最大长度1021,这个数据包会被重组,部分包保存在名为partial_packets的map中,使用连接句柄作为key。接着分配一个足够大的缓冲区来容纳最后的数据包,然后把接收到的数据复制到该缓冲区。最后接收到的分片的结尾保存在partial_packet->offset中。

下面的包设置了一个延续标志,表明这是一个包的分片,它是ACL头连接句柄中的第12位,如果收到这样的一个包,包内容就会被复制到之前的偏移量。

static void reassemble_and_dispatch(UNUSED_ATTR BT_HDR *packet) {
[...]
packet->offset = HCI_ACL_PREAMBLE_SIZE;
uint16_t projected_offset =
partial_packet->offset + (packet->len - HCI_ACL_PREAMBLE_SIZE);
if (projected_offset >
partial_packet->len) { // len stores the expected length
LOG_WARN(LOG_TAG,
"%s got packet which would exceed expected length of %d."
"Truncating.",
__func__, partial_packet->len);
packet->len = partial_packet->len - partial_packet->offset;
projected_offset = partial_packet->len;
}
memcpy(partial_packet->data + partial_packet->offset,
packet->data + packet->offset, packet->len - packet->offset);

[...]
}

这个步骤导致了负长度的memcpy,如上面的代码所示,在某种情况下,我们得到了一个数据包,只剩下两个字节的缓冲区来接收,如果延续比预期的长,packet->length会被截断来避免缓冲区溢出,长度设置为要复制的字节数。

由于我们需要跳过HCI和ACL前导码,我们使用HCI_ACL_PREAMBLE_SIZE(4)作为包的偏移量,并且从要复制的字节数中减去它,导致了memcpy的负长度为-2。

意料之外的Leak

上述的bug似乎无法利用,因为我们得到了一个无限循环的memcpy,但是我们偶尔会在不同的位置产生crash,比如下列崩溃位于同一线程中,但是无法通过无限循环复制来解释。因此我们希望在某个地方找到另一个bug:

pid: 14530, tid: 14579, name: btu message loo  >>> com.android.bluetooth <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x7a9e0072656761
    x0  0000007ab07d72c0  x1  0000007ab0795600  x2  0000007ab0795600  x3  0000000000000012
    x4  0000000000000000  x5  0000007a9e816178  x6  fefeff7a3ac305ff  x7  7f7f7f7f7fff7f7f
    x8  007a9e0072656761  x9  0000000000000000  x10 0000000000000020  x11 0000000000002000
    x12 0000007aa00fc350  x13 0000000000002000  x14 000000000000000d  x15 0000000000000000
    x16 0000007b396f6490  x17 0000007b3bc46120  x18 0000007a9e81542a  x19 0000007ab07d72c0
    x20 0000007ab0795600  x21 0000007a9e817588  x22 0000007a9e817588  x23 000000000000350f
    x24 0000000000000000  x25 0000007ab07d7058  x26 000000000000008b  x27 0000000000000000
    x28 0000007a9e817588  x29 0000007a9e816340
    sp  0000007a9e8161e0  lr  0000007a9fde0ca0  pc  0000007a9fe1a9a4

backtrace:
    #00 pc 00000000003229a4  /system/lib64/libbluetooth.so (list_append(list_t*, void*) [clone .cfi]+52)
    #01 pc 00000000002e8c9c  /system/lib64/libbluetooth.so (l2c_link_check_send_pkts(t_l2c_linkcb*, t_l2c_ccb*, BT_HDR*) [clone .cfi]+100)
    #02 pc 00000000002ea25c  /system/lib64/libbluetooth.so (l2c_rcv_acl_data(BT_HDR*) [clone .cfi]+1236)
    [...]

我们花了几个晚上来跟踪这些崩溃,并且修改了fuzz使其可以被重放。然而,通过重发数据包来重现这些有意思的崩溃是不可能的。调试期间的主要问题是我们没有使用地址清理器来编译Android。这会检测到任何内存损坏错误,因为它在一个随机位置崩溃前发生的。所以在经历一些失败后我们决定修改一些比特位,通过保持L2PING的payload不变,我们可以用它和响应包的payload进行比对,如果数据同时发生变化,则会损坏内存,但是不会导致崩溃。运行一段时间后我们得到了这样的错误响应:

通过这种检测方法,我们就可以可靠地重放。下面的数据包分组可以触发:

1. 剩余2字节用于延续的L2CAP数据包,包含A
2. 延续比预期的2字节长的数据包,包含B

在Android的Logcat中,我们可以看到以下错误消息:

bt_hci_packet_fragmenter: reassemble_and_dispatch got packet which would
exceed expected length of 147. Truncating.

这个触发的错误看起来与上面的BUG很相似。注意,只有最后的字节被破坏了,包的开头仍然是正确的。这个情况不能用源码和我们目前所知道的来解释。直接的缓冲区溢出保持前两个字节不变或者以这种受控的方法来覆盖指针和偏移量都是不可能的。此时,我们决定在packet_fragmenter中设置断点,以观察包中数据的修改位置。我们用GDB脚本来调试,reassemble_and_dispatch+1408reassemble_and_dispatch+1104是之前说的reassemble_and_dispatch中的两个memcpy

b reassemble_and_dispatch
commands; x/32x $x0; c; end

b dispatch_reassembled
commands; x/i $lr; x/32x $x0; c; end

b *(reassemble_and_dispatch+1408)
commands; p $x0; p $x1;p $x2; c; end

b *(reassemble_and_dispatch+1104)
commands; p $x0; p $x1; p $x2; c; end

对于第一个包含A的数据包,我们观察到以下日志。它按预期接收数据,第一个memcpy的长度为0x52字节触发。这个长度在包的内部BT_HDR结构中也是可见的,并且是正确的。ACL和L2CAP头文件中包含的长度比触发包重新组装的实际payload长2个字节。HCI头中的连接句柄是0x200b,表示连接句柄0x0b的开始包

第二个包也在reassemble_and_dispatch中正确到达,并且连接句柄改为0x100b,表示它是一个延续包。memcpy的第三个参数是上面指出的0xfffffffffffffffe,即-2,由于memcpy把第三个参数看作无符号整数,所以这个memcpy将导致崩溃。

但显然程序继续运行,破坏了部分包的最后66个字节,被破坏的包被传递给dispatch_reassembled

memwtf(,,-2);

如果我们仔细研究一下memcpy的实际实现,就会发现它比上面显示的简单的字符级memcpy要复杂的多。复制整个内存字比复制单个字节要更加有效。这个实现更进一步,在将寄存器写入目标位置之前,用64字节的内存内容填充寄存器。这样实现起来更复杂,并且得考虑奇数长度和未对齐地址边缘的情况。

(ps.这部分可以参考swing的博客)

在这个memcpy实现中,存在一个对负长度的奇怪行为。当我们试图复制到目标缓冲区末尾的时候,我们用之前的第二个数据包覆盖L2Ping请求的最后66个字节。我们编写这个简短的PoC是为了测试memcpy的行为。

int main(int argc, char **argv) {
if (argc < 3) {
printf("usage %s offset_dst offset_src\n", argv[0]);
exit(1);
}

char *src = malloc(256);
char *dst = malloc(256);

printf("src=%p\n", src);
printf("dst=%p\n", dst);

for (int i=0; i<256; i++) src[i] = i;
memset(dst, 0x23, 256);

memcpy( dst + 128 + atoi(argv[1]),
src + 128 + atoi(argv[2]),
0xfffffffffffffffe );

//Hexdump
for(int i=0; i<256; i+=32) {
printf("%04x: ", i);
for (int j=0; j<32; j++) {
printf("%02x", dst[i+j] & 0xff);
if (j%4 == 3) printf(" ");
}
printf("\n");
}
}

该行为已在Unicorn中模拟aarch64 memcpy实现进行了分析。 相关代码如下所示:

prfm    PLDL1KEEP, [src]
add srcend, src, count
add dstend, dstin, count
cmp     count, 16
b.ls    L(copy16)           //Not taken as 0xfffffffffffffffe > 16
cmp count, 96
b.hi    L(copy_long)        //Taken as as 0xfffffffffffffffe > 96

[...]

L(copy_long):
and tmp1, dstin, 15         //tmp1 = lower 4 bits of destination
bic dst, dstin, 15
ldp D_l, D_h, [src]
sub src, src, tmp1
add count, count, tmp1      /* Count is now 16 too large.  */
                            //It is not only too large
                            //but might also be positive!
                            //0xfffffffffffffffe + 0xe = 0xc
ldp A_l, A_h, [src, 16]
stp D_l, D_h, [dstin]
ldp B_l, B_h, [src, 32]
ldp C_l, C_h, [src, 48]
ldp D_l, D_h, [src, 64]!
subs    count, count, 128 + 16  /* Test and readjust count.  */
                                //This  will become negative again
b.ls    2f                      //So this branch is taken

[...]


/* Write the last full set of 64 bytes.  The remainder is at most 64
bytes, so it is safe to always copy 64 bytes from the end even if
there is just 1 byte left.  */
//This will finally corrupt -64...64 bytes and terminate
2:
ldp E_l, E_h, [srcend, -64]
stp A_l, A_h, [dst, 16]
ldp A_l, A_h, [srcend, -48]
stp B_l, B_h, [dst, 32]
ldp B_l, B_h, [srcend, -32]
stp C_l, C_h, [dst, 48]
ldp C_l, C_h, [srcend, -16]
stp D_l, D_h, [dst, 64]
stp E_l, E_h, [dstend, -64]
stp A_l, A_h, [dstend, -48]
stp B_l, B_h, [dstend, -32]
stp C_l, C_h, [dstend, -16]
ret

因为我们处理的是一个非常大的count值(INT_MAX-2),它总是大于dst和src之间的距离。因此在我们的例子中,将永远不会调用_memcpy,这使得这个bug无法在Android 10上利用。

泄露更多数据

如上所述,我们基本上可以用源地址前面的内容覆盖包的最后64个字节。源缓冲区之前的20个字节总是BT_HDRacl_hdrl2cap_hdr。因此我们可以自动泄露远程设备的连接句柄。

未初始化内存的内容取决于第二个包的缓冲区的位置及其大小。通过重复发送常规的L2Ping回送请求,我们可以尝试将自己的包数据放在第二个包前面。这允许我们使用任意数据控制数据包的最后44个字节。通过缩短第一个包,可以控制整个包结构,包括包头。第一个数据包是这样的:

在触发bug后,损坏的包是下图中所示的。包含“X”的包就是我们放在源缓冲区前面的包。注意,除了BT_HDR中的长度外,包长度现在是0x280,而不是0x30。packet->len字段必须保持原来的长度,否则重新组装的时候需要更多的数据。

这将会导致更有用的泄露。注意,这是一种只针对数据的攻击,不需要执行代码或者其他任何附加信息。
它还可以将任意的L2CAP流量组合注入到任何活动的连接句柄中。成功的泄露可能如下图所示:

为了绕过地址随机化(ASLR),我们需要一些基本的库的地址。我们偶尔会在堆上发现来自libicuuc.so的对象,有如下结构:

  • 一些堆指针
  • 指向libicuuc.so中的uhash_hashUnicodeString_60指针
  • 指向libicuuc.so中的uhash_compareUnicodeString_60指针
  • 指向libicuuc.so中的uhash_compareLong_60的指针
  • 指向uprv_deleteUObject_60中的uprv_deleteUObject_60的指针

我们可以使用这些函数的偏移量来检测泄露中的这些结构。

控制执行流,Payload的位置

有几个库,比如libbluetooth.so,受到了Clang的调用流完整性(CFI,Call Flow Integrity)的保护。这样可以保护边界防止我们用任意地址覆盖堆上的vtables函数,只有属于受影响对象的函数才是可以调用的,尽管如此,在断开连接时,我们偶尔会在破坏堆后触发以下崩溃:

signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x37363534333231
    x0  3837363534333231  x1  000000750c2649e0  x2  000000751e50a338  x3  0000000000000000
    x4  0000000000000001  x5  0000000000000001  x6  00000075ab788000  x7  0000000001d8312e
    x8  00000075084106c0  x9  0000000000000001  x10 0000000000000001  x11 0000000000000000
    x12 0000000000000047  x13 0000000000002000  x14 000f5436af89ca08  x15 000024747b62062a
    x16 000000750c2f55d8  x17 000000750c21b088  x18 000000750a660066  x19 000000751e50a338
    x20 000000751e40dfb0  x21 000000751e489694  x22 0000000000000001  x23 0000000000000000
    x24 000000750be85f64  x25 000000750a661588  x26 0000000000000005  x27 00000075084106b4
    x28 000000750a661588  x29 000000750a65fd30
    sp  000000750a65fd10  lr  000000750c264bb8  pc  000000750c264c5c

backtrace:
    #00 pc 00000000000dbc5c  /system/lib64/libchrome.so (base::WaitableEvent::Signal()+200)
    #01 pc 00000000000add88  /system/lib64/libchrome.so (base::internal::IncomingTaskQueue::PostPendingTask(base::PendingTask*)+320)
    [...]
    #09 pc 00000000002dd0a8  /system/lib64/libbluetooth.so (L2CA_DisconnectRsp(unsigned short) [clone .cfi]+84)
    #10 pc 0000000000307a08  /system/lib64/libbluetooth.so (sdp_disconnect_ind(unsigned short, bool) [clone .cfi]+44)
    #11 pc 00000000002e39d4  /system/lib64/libbluetooth.so (l2c_csm_execute(t_l2c_ccb*, unsigned short, void*) [clone .cfi]+5500)
    #12 pc 00000000002eae04  /system/lib64/libbluetooth.so (l2c_rcv_acl_data(BT_HDR*) [clone .cfi]+4220)
    [...]

在泄露过程中我们不仅向反方向溢出了,而且还破坏了在受影响缓冲区之后存储的数据。
在本例中,我们覆盖了保存在X0中的指针。通过查看代码中的位置,我们使指令在X0控制的分支寄存器之前崩溃。

dbc5c: f9400008 ldr x8, [x0] // We control X0
dbc60: f9400108 ldr x8, [x8]
dbc64: aa1403e1 mov x1, x20
dbc68: d63f0100 blr x8 // Brach to **X0

如果我们知道一个可以存储任意数据的地址,我们就可以控制执行流了。libchrome.so没有用CFI编译,我们的包数据必须保存在堆的某个地方,但是我们还需要一种方法来搜索地址进行RCE。这是通过把连接句柄作为key,把部分数据包保存在哈希Map中实现的:

BT_HDR* partial_packet = (BT_HDR*)buffer_allocator->alloc(full_length + sizeof(BT_HDR));
[...]
memcpy(partial_packet->data, packet->data, packet->len);
[...]
partial_packets[handle] = partial_packet;

这将在堆上分配一个对象,包含key(连接句柄)和指向我们数据包的一个指针。最后我们就可以泄露这个对象,得到指向我们缓冲区的指针。这时候Key是已知的,我们就可以用它在泄露的信息中找到我们的对象。通过使用允许的最大数据包大小,我们有几百个字节来保存ROP链和payload。

这种方法并不完全可靠,但是在30%-50%的情况下有效。即使是蓝牙守护进程自动重启然后再开一个同样的进程。因此,地址空间在引导的时候是随机的,即使我们的守护进程崩溃,也会用相同的地址布局重新启动,因此我们可以反复尝试攻击来获得RCE。

调用System()

即使我们知道了libicuuc.so的绝对地址,库之间的偏移也是随机的。因此我们只能在这个库中使用gadget。libicuuc.so的调用中没有什么有用的函数,比如system(),在这一点上,Fabian Beterke指出,应该去库导入中找一些有用的内容。

我们没有直接使用system()或者execve(),但是我们有disym()可以用,这个函数需要一个句柄(比如空指针)和一个函数名作为参数。它解析并返回该函数的地址,可以用来获取system()的地址,因此,我们需要执行一个函数调用,并且控制它的返回,在ROP中,这通常不是问题,因为Gadget总是以返回结束。但是,我们没有办法在ROP中使用栈迁移,因此我们用C++的对象调用来执行相关操作,这些操作通常和X8或者X19有关系。结果就是我们的payload中有很多相对引用,为了跟踪已经使用的偏移量,我们实现了一个set_ptr(payload, offset, value)的函数,如果已经使用了payload中的给定偏移量则抛出错误,我们还跟踪了寄存器的值来简化整个过程。为了从dlsym清晰地返回,我们用了一个名为u_cleanup_60的解析器,它遍历一个函数列表,如果指针不是空的就调用这个地址,并清除它。这很方便,因为我们可以调用dlsym,并且可以控制返回后的执行,而不需要用栈迁移。

ldr x8, [x19, #0x40]; cbz x8, #0xbc128; blr x8; str xzr, [x19, #0x40];
ldr x8, [x19, #0x48]; cbz x8, #0xbc138; blr x8; str xzr, [x19, #0x48];
ldr x8, [x19, #0x50]; cbz x8, #0xbc148; blr x8; str xzr, [x19, #0x50];
ldr x8, [x19, #0x58]; cbz x8, #0xbc158; blr x8;

披露和结束语

这个漏洞最初是在2019年11月3日发送给Android的安全团队的,包括一个PoC,它于2020年2月1日修复,并且得到了Android安全团队的认可。我要感谢这个团队协调这个过程并且提供了一个解决方案。同时,我也要感谢Jiska Classen和Fabian Beterke的帮助,此外,我们还想对Swing的博客发出一声欢呼,因为它是第一个对我们知识进行颠覆的博客,它颠覆了关于漏洞的关键思想。(Swing大哥牛逼!!(破音!!))

测试脚本可以在这里下载。ROP链已从该利用脚本中删除。压缩包包括以下文件:

  • python2 simple_leak.py target 预料之外的泄露章节的PoC
  • python2 fancy_leak.py target,泄露更多数据部分的PoC
  • python2 memcpy.py libc.so memcpy的测试脚本
  • python2 exploit.py target remote_libicuuc.so 删掉了ROP链的利用脚本