特斯拉TBONE漏洞分析

汽车安全 3年前 (2021) admin
1,061 0 0
本文首发于奇安信攻防社区

社区有奖征稿

· 基础稿费、额外激励、推荐作者、连载均有奖励,年度投稿top3还有神秘大奖!

· 将稿件提交至奇安信攻防社区(点击底部 阅读原文 ,加入社区)

点击链接了解征稿详情

0x00 背景

近日,德国的两名安全研究员Ralf-Philipp Weinmann和Benedikt Schmotzle在CanSecWest会议上公布了Tbone漏洞,该漏洞可实现0-click无接触对Tesla的近距离攻击,通过操控一辆无人机实现了开启Tesla后备箱,解锁等操作。

值得一提的是Tbone作者在没有汽车硬件的情况下,通过Tesla使用的第三方组件ConnMan完成了漏洞挖掘。

通过作者公布的资料,本文对Tbone漏洞进行简略分析与记录。

特斯拉TBONE漏洞分析

(PPT、PDF资源在文末参考链接)

0x01 漏洞简介

  • 漏洞效果: 近场(WIFI信号范围内)获取Tesla信息娱乐系统root权限,操控Tesla车门、后备箱等
  • 影响范围: 2018.42.3 and 2020.4.1版本之间的Tesla(S, 3, X, Y)
  • 漏洞利用流程:

特斯拉TBONE漏洞分析

0x02 通过WIFI介入实现0-click效果

2015年,Mahaffey和Rogers两位研究员指出Tesla Model S会主动连接名为Tesla Service的wifi热点,并且密码为硬编码:

特斯拉TBONE漏洞分析

该硬编码甚至出现在某人推特的个人信息中:

特斯拉TBONE漏洞分析

推特个人信息资料中包含Tesla Service热点密码硬编码

在TBone作者进行漏洞挖掘时,此漏洞依然存在。

攻击者通过hostapd伪造名为Tesla Service的热点,并配置密码为发现的硬编码,Tesla扫描到此wifi热点后会主动进行连接。

通过这个漏洞,攻击者便可与Tesla网络连接管理组件ConnMan进行交互,扩展攻击面,实现0-click的攻击效果。

0x03 漏洞挖掘与分析

Tbone作者通过ConnMan组件中DNS与DHCP模块的两个漏洞,最终实现了RCE。

ConnMan是什么?

ConnMan是一款由C语言编写的开源网络连接管理器,包含诸多网络协议: DHCP, DNS, IPv4, IPv6, NTP, WPAD等

ConnMan源码下载: https://git.kernel.org/pub/scm/network/connman/connman.git/

Tbone漏洞使用的ConnMan版本为1.37, 文章中涉及到的代码片段均截取自connman-1.37.tar.gz

DNS栈溢出漏洞(CVE-2021-26675)

1. AFL fuzz发现crash

使用AFL fuzz ConnMan,配置ASAN做监控,成功发现一处crash:

  1. Reading symbols from src/connmand...done.
  2. Starting program: ./src/connmand < out/crashes/id:000003,sig:06,src:006692,op:havoc,rep:8
  3. [Thread debugging using libthread_db enabled]
  4. Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
  5. =================================================================
  6. ==28665==ERROR: AddressSanitizer: negative-size-param: (size=-1)
  7.    #0 0xf720f9b3 (/usr/lib32/libasan.so.4+0x779b3)
  8.    #1 0x56936544 in uncompress src/dnsproxy.c:1841
  9.    #2 0x5694ba34 in forward_dns_reply src/dnsproxy.c:2086
  10.    #3 0x5694ba34 in fuzz src/dnsproxy.c:2200
  11.    #4 0x5662b475 in main src/dnsproxy.c:2205
  12.    #5 0xf6c88e80 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18e80)
  13.    #6 0x5662b76f (/home/user/connman/src/connmand+0x3e76f)
  14. Address 0xffe197e4 is located in stack of thread T0 at offset 1220 in frame
  15.    #0 0x5694b01f in fuzz src/dnsproxy.c:2189
  16.  This frame has 3 object(s):
  17.    [32, 36) 'uptr'
  18.    [96, 1121) 'uncompressed'
  19.    [1184, 5280) 'buf' <== Memory access at offset 1220 is inside this variable
  20. HINT: this may be a false positive if your program uses some custom stack unwind mechanism
  21. or swapcontext (longjmp and C++ exceptions *are* supported)
  22. SUMMARY: AddressSanitizer: negative-size-param (/usr/lib32/libasan.so.4+0x779b3)
  23. ==28665==ABORTING
  24. [Inferior 1 (process 28665) exited with code 01]

定位到源码文件src/dnsproxy.c

  1. 1    static char *uncompress(int16_t field_count, char *start, char *end,
  2. 2                char *ptr, char *uncompressed, int uncomp_len,
  3. 3                char **uncompressed_ptr)
  4. 4    {
  5. 5        char *uptr = *uncompressed_ptr; /* position in result buffer */
  6. 6
  7. 7        debug("count %d ptr %p end %p uptr %p", field_count, ptr, end, uptr);
  8. 8
  9. 9        while (field_count-- > 0 && ptr < end) {
  10. 10            int dlen;        /* data field length */
  11. 11            int ulen;        /* uncompress length */
  12. 12            int pos;        /* position in compressed string */
  13. 13           char name[NS_MAXLABEL]; /* tmp label */
  14. 14            uint16_t dns_type, dns_class;
  15. 15            int comp_pos;
  16. 16
  17. 17            if (!convert_label(start, end, ptr, name, NS_MAXLABEL,
  18. 18                        &pos, &comp_pos))
  19. 19                goto out;
  20. 20
  21. 21            /*
  22. 22            * Copy the uncompressed resource record, type, class and \0 to
  23. 23             * tmp buffer.
  24. 24             */
  25. 25
  26. 26            ulen = strlen(name);
  27. 27            **strncpy(uptr, name, uncomp_len - (uptr - uncompressed));**
  28. 28
  29. 29            debug("pos %d ulen %d left %d name %s", pos, ulen,
  30. 30                (int)(uncomp_len - (uptr - uncompressed)), uptr);
  31. 31
  32. 32            uptr += ulen;
  33. 33            *uptr++ = '\0';
  34. 34
  35. 35            ptr += pos;
  36. 36
  37. 37            /*
  38. 38             * We copy also the fixed portion of the result (type, class,
  39. 39             * ttl, address length and the address)
  40. 40             */
  41. 41            **memcpy(uptr, ptr, NS_RRFIXEDSZ);**
  42. 42
  43. 43            dns_type = uptr[0] << 8 | uptr[1];
  44. 44            dns_class = uptr[2] << 8 | uptr[3];
  45. 45
  46. 46            if (dns_class != ns_c_in)
  47. 47                goto out;
  48. 48
  49. 49            ptr += NS_RRFIXEDSZ;
  50. 50            uptr += NS_RRFIXEDSZ;

代码第41行,memcpy函数向uptr地址空间拷贝固定10bytes(NS_RRFIXEDSZ)长度数据,但没有检测拷贝操作是不是向目标buffer之外写入数据,导致栈溢出漏洞。

2. 绕过栈保护canary

在PWN2OWN提供的固件中查看ConnMan的二进制发现目标使用了canary栈保护机制。

特斯拉TBONE漏洞分析

栈溢出的利用一般通过溢出存在于栈上的局部变量,让多出来的数据覆盖 ebp、eip等,从而达到劫持控制流的目的。但如果溢出的数据覆盖掉了canary,程序检测到canary发生了变化,则判断发生了栈溢出,导致无法正常攻击利用。

目标ConnMan的canary为64bit,无法进行爆破,所以必须想办法绕过canary机制。

在上文uncompress函数的27行,strncpy函数将name复制到uptr中,并且允许复制的最大长度每次循环会减少,同时,指针uptr根据name的实际字符长度会一直增加。

这表示我们可以找到一个方法,先填充字节到canary,然后向前跳8个或更多字节,之后在41行继续向uptr继续写入。这里Tbone作者并未详细展开如何绕过canary,说法较为模糊,以下是笔者自己猜测见解:

name每次循环可控,uncomp_len,uncompressed可能可控。

当数据覆盖到canary时,构造name长度为8bytes,构造uncomp_len - (uptr - uncompressed))为0。

此时27行strncpy(uptr, name, uncomp_len - (uptr - uncompressed));拷贝0字节。

32行uptr += ulen; uptr指针移动8字节,绕过canary

41行memcpy(uptr, ptr, NS_RRFIXEDSZ);栈溢出覆盖canary之后的内容。

3. 触发栈溢出

此栈溢出只有在转发代理添加域名到非法主机名时才会触发(下方代码第9行,req->append_domain必须为True),Tbone作者在fuzz过程注释掉了req->append_domain检测。

但是,在CID(中控)和AutoPilot(自动驾驶系统)中,所有的进程都使用合法的主机名。

作者注意到ConnMan使用了Web代理自动发现协议(WPAD),该服务在获取DHCP lease后会立即查询wpad.<domain>,此<domain>DHCP Server提供给ConnMan。

  1. 1    static intforward_dns_reply(unsigned char *reply, int reply_len, int protocol,
  2. 2                    struct server_data *data)
  3. 3    {
  4. 4    ...
  5. 5        if (hdr->rcode == ns_r_noerror || !req->resp) {
  6. 6            unsigned char *new_reply = NULL;
  7. 7
  8. 8             /* req->append_domain为True才可进入漏洞流程*/
  9. 9           if (req->append_domain && ntohs(hdr->qdcount) == 1) {

当多个DNS server通过DHCP提供给ConnMan时,ConnMan会发送DNS请求wpad.<domain> 。

攻击者通过在DHCP reply中将DNS Server设置为127.0.0.1,来使WPAD发送的DNS请求通过ConnMan DNS转发代理发送。

此外,在DHCP option中将域名设置为0字节组成的字符串,当DNS转发代理添加0字节组成的域名时,req->append_domain被设置为True,进而成功进入栈溢出漏洞流程。

DHCP信息泄露(CVE-2021-26676)

  1. DHCP协议前置知识

在了解这个漏洞之前,先看一下DHCP基本的交互逻辑。

wiki上对DHCP offer <-> request流程的描述:

特斯拉TBONE漏洞分析

这个场景下,攻击者为server,Tesla为client。

攻击者发送DHCP OFFER(攻击包), Tesla返回DHCP REQUEST(包含泄露的信息)

  1. 漏洞分析

由于目标设置了DEP(数据执行保护,无法执行攻击者在栈中构造的恶意代码)与ALSR(地址随机化),所以必须要搞清楚栈在内存中的地址,ConnMan或其他动态链接库在内存中的分布。即需要找到一个泄露地址的漏洞来配合栈溢出利用。

通过DNS转发功能来触发信息泄露不太现实,因为通过WiFi接口,攻击者只能被动的发送响应,不能主动发送请求。

于是作者转向DHCP代码分析,并且幸运地发现一个监听函数listener_event()会为接收到的DHCP包申请栈空间。

gdhcp/client.c:

  1. static gbooleanlistener_event(GIOChannel *channel, GIOCondition condition,
  2.                            gpointer user_data)
  3. {
  4.    GDHCPClient *dhcp_client = user_data;
  5.    structsockaddr_indst_addr = { 0 };
  6.    structdhcp_packetpacket;
  7.    structdhcpv6_packet *packet6 =NULL;
  8.    uint8_t *message_type = NULL, *client_id = NULL, *option,
  9.        *server_id = NULL;
  10.    uint16_t option_len = 0, status = 0;
  11.    uint32_t xid = 0;
  12.    gpointer pkt;
  13.    unsigned char buf[MAX_DHCPV6_PKT_SIZE];
  14.    uint16_t pkt_len = 0;
  15.    int count;
  16.    int re;
  17.    ...
  18.    if (dhcp_client->listen_mode == L2) {
  19.        **re = dhcp_recv_l2_packet(&packet,**  //接收DHCP数据包
  20.                    dhcp_client->listener_sockfd,
  21.                    &dst_addr);
  22.        xid = packet.xid;

接着向下看这个函数,在一个switch结构体里可以看到处理DHCP服务端响应的代码逻辑:

  1. ...
  2.    switch (dhcp_client->state) {
  3.    case INIT_SELECTING:
  4. ...
  5.        // 从packet中解析出DHCP_SERVER_ID
  6.        **option = dhcp_get_option(&packet, DHCP_SERVER_ID);**  
  7.        dhcp_client->server_ip = get_be32(option);          
  8. ...

dhcp_client->server_ip字段随后会被编码到DHCP数据包中,在send_request函数中发送给DHCP Server。

gdhcp/client.c :

  1. static int send_request(GDHCPClient *dhcp_client)
  2. {
  3.    struct dhcp_packet packet;
  4. ...
  5.    if (dhcp_client->state == REQUESTING)
  6.   // 将dhcp_client->server_ip构造到DHCP Request包的DHCP_SERVER_ID字段中
  7.        **dhcp_add_option_uint32(&packet, DHCP_SERVER_ID,
  8.                dhcp_client->server_ip);**

回过头看一下解析出DHCP_SERVER_ID的函数dhcp_get_option(漏洞点):

  1. uint8_t *dhcp_get_option(struct dhcp_packet *packet, int code)
  2. {
  3.    int len, rem;
  4.    uint8_t *optionptr;
  5.    uint8_t overload = 0;
  6.    /* option bytes: [code][len][data1][data2]..[dataLEN] */
  7.    optionptr = packet->options;
  8.    **rem = sizeof(packet->options);** // 使用packet->options结构体的长度作为需要解析的长度,
  9.                                               // 而不是数据包字段真实字节长度
  10.    ...
  11. }

注意到rem = sizeof(packet->options); 使用packet->options结构体的长度作为需要解析的长度,而不是数据包真实字节长度。

我们可以发送一个DHCP Offer,携带DHCP_SERVER_ID option,并且只设置option的code,不设置data段。

client端在收到DHCP offer时会将offer包中DHCP_SERVER_ID字段取出来放入将要发送的DHCP Request包的DHCP_SERVER_ID字段中。

但由于取DHCP_SERVER_ID字段的函数 dhcp_get_option 取字段时使用的是结构体的长度,所以即使我们在offer中设置DHCP_SERVER_ID字段为0字节,client端还是会从内存中取4字节内容放入DHCP Request包的DHCP_SERVER_ID字段里(此结构体默认4字节)。

这样client端在构造DHCP Request数据包时就会在DHCP_SERVER_ID字段中携带内存中的数据,一次泄露4字节。

通过修改domain name(别的option)的长度,在exp里称之为padding,来使option指针偏移,进而读取栈中其余的数据。

DHCP信息泄露流程为:

特斯拉TBONE漏洞分析

完整的漏洞利用链

1)远程代码执行

通过以上两个漏洞,攻击者获取动态链接库基址与forward_dns_reply()函数栈指针,构造ROP链放在栈中,并使用mprotect函数使栈中代码可执行,最终达到RCE的效果。

2)提权

但此时权限非root,且存在以下问题:

  1. ConnMan进程运行在自己的用户下
  2. 所有的进程被Kafel(拦截syscall)与Apparmor(资源访问限制)限制
  3. ConnMan进程不能开启/bin/sh

通过分析发现ConnMan可以执行受限制的modprobe操作,一是只可以加载Tesla签名后的模块;二是可以加载一些模块的固件,比如BCMDHD

Tbone作者通过加载包含已知漏洞的BCMDHD博通芯片固件,从WIFI芯片层进行攻击,最终获取到Tesla信息娱乐系统的Root权限。

BCMDHD漏洞,参考Google Project Zero文章:Over The Air: Exploiting Broadcom’s Wi-Fi Stack

3)控制车辆

CAN总线操作车辆开关门动作,可参考Keen团队的工作:FREE-FALL: HACKING TESLA FROM WIRELESS TO CAN BUS

Tesla已修复此漏洞,并改用dnsmasq组件。

0x04 参考资料

  1. https://kunnamon.io/tbone/
  2. https://kunnamon.io/tbone/tbone-v1.0-redacted.pdf
  3. https://docs.google.com/presentation/d/1T9NAJTBWkqBGsQlQwM1anbFXRhxJcTiq0O4VfQCtVEk/edit#slide=id.p
  4. https://blog.lookout.com/hacking-a-tesla
  5. https://ctf-wiki.org/pwn/linux/mitigation/canary/
  6. https://googleprojectzero.blogspot.com/2017/04/over-air-exploiting-broadcoms-wi-fi_4.html
  7. https://git.kernel.org/pub/scm/network/connman/connman.git/

0x05 关于我们

天工实验室隶属于奇安信技术研究院,专注于物联网、车联网领域的安全研究,包括物联网协议安全、固件安全、无线安全、智能网联汽车及自动驾驶安全等,服务于国家和社会对网络空间安全的战略需求。团队成员秉承“天工开物、匠心独运”的创新使命和工匠精神,在物联网漏洞挖掘与攻防领域有丰富的经验积累,漏洞研究成果连续在GeekPwn、天府杯等漏洞破解赛事中斩获多个奖项,漏洞挖掘创新型方法发表于Usenix等国际顶级会议。

END

 

【版权说明】本作品著作权归天工实验室所有,授权补天漏洞响应平台独家享有信息网络传播权,任何第三方未经授权,不得转载。

 

敲黑板!转发≠学会,课代表给你们划重点了

复习列表

记一次文件上传的曲折经历

代码审计之eyouCMS最新版getshell漏洞

硬核黑客笔记 – 怒吼吧电磁波 (上)

从WEB弱口令到获取集权类设备权限的过程

一个域内特权提升技巧 | 文末双重福利

php无文件攻击浅析

特斯拉TBONE漏洞分析

分享、点赞、在看,一键三连,yyds。
特斯拉TBONE漏洞分析

点击阅读原文,加入社区,获取更多技术干货!

原文始发于微信公众号(补天平台):特斯拉TBONE漏洞分析

版权声明:admin 发表于 2021年5月28日 上午9:58。
转载请注明:特斯拉TBONE漏洞分析 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...