VexTrio的浏览器指纹识别

admin 2024年3月28日11:42:48评论8 views字数 33269阅读110分53秒阅读模式

大家好,今天我们将看看我在查看基于 Javascript 注入的恶意软件时遇到的一些东西:浏览器指纹识别

VexTrio的浏览器指纹识别

为此,我们将看看 VexTrio 的指纹识别阶段,VexTrio 是一种恶意 TDS(流量分配系统),目前注入全球网页,可将访问者重定向到一系列不同的欺诈页面。它的功能可能最好是这样描述的:

  • 恶意软件开发

  • 了解更多详细课程直接翻到最后或者添加作者微信

VexTrio 参与者将恶意 JavaScript 代码注入易受攻击的 WordPress 网站,然后将访问者重定向到可能有害的内容。访问者通过涉及欺诈域的重定向链,其目的是跟踪受害者并有条件地将他们发送到提供风险软件、间谍软件、广告软件、诈骗、色情图像或其他有害程序的登陆网页。(来源:Infoblox)

为了决定将打开感染 VexTrio 的页面的用户引导到哪里,恶意 TDS 使用几种不同的方法来对潜在受害者的浏览器进行指纹识别。根据所使用的 JavaScript 链,这可以从仅允许用户在访问受感染站点时满足特定条件时被重定向开始。

1.用户必须从搜索引擎访问WordPress网站。例如,可以 https://www.google.com/ 引荐来源网址。

2. Cookie 在用户的 Web 浏览器中启用。

3. 用户在过去 24 小时内未访问过 VexTrio 受感染的网页。

(来源:Infoblox)

现在,感染链的这一初始步骤已经确保了几件事。首先,通过检查引荐来源标头,VexTrio 恶意软件不太可能意外重定向网站管理员或网站的普通用户,因为这两个用户可能不会通过搜索引擎访问他们的网站,而是直接打开 URL。

其次,通过确保启用用户的浏览器 Cookie,恶意软件可以确保其功能,因为它使用多个 cookie,具体取决于其重定向链。其中之一用于识别 24 小时内没有受害者被重定向超过一次。这就引出了第三点,它可能是为了在雷达下飞行,因为恶意重定向行为不是持久的,而是每 24 小时才发生一次,这使得用户不太可能向管理员投诉。它还可以确保更具侵入性攻击的潜在受害者,例如导致恶意软件的 VexTrio,不会在短时间内多次被感染,从而减少攻击者方面反复感染的噪音。

但是,这篇博文的目的不是阐明最初的 VexTrio 注入页面,而是用于将受害者重定向到 VexTrio TDS 可以提供的不同类型的欺诈的几种方法。

为此,一旦潜在受害者通过上述检查,恶意软件就会利用在第二阶段 JavaScript 文件中提供的几种指纹识别方法。这些指纹识别方法主要用于确保潜在受害者是合法的,而不是研究人员或网络爬虫。每次检查运行后,恶意软件都会返回一个字符串值,然后 VexTrio 服务器使用该值来决定为受害者提供服务的重定向。幸运的是,VexTrio 背后的参与者试图让他们的代码保持干净。这使我们能够一次查看所有检查,因为这是一个巨大的功能,该函数仅用于启动每个单独的检查并处理结果。最初,函数名称都是通用的,从 A1 到 A29

function chk() {
  try {
    if (CHECK_COMPARE_AVAILABLE_WINDOW_SIZE_TO_CURRENT_WINDOW_SIZE().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (CHECK_COMPARE_ISO_CODE_LANGUAGE_ARRAY_VS_ISO_CODE_PRIMARY_LANGUAGE().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (CHECK_COMPARE_OSCPU_TO_DEVICE_USERAGENT_STRING().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (CHECK_OSCPU_NOT_UNDEFINED_AND_BROWSER_NOT_FIREFOX().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (CHECK_COMPARE_PLATFORM_USERAGENT_DEVICEINUSERAGENT().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (CHECK_PLUGINSUNDEFINED_BUT_USERAGENTOS_NOT_WINDOWS().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (CHECK_BROWSER_BUILDNUMBER_AGAINST_BROWSERTYPE().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (CHECK_LENGTH_OF_EVAL_FUNCTION_AGAINST_BROWSER_TYPE().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (CHECK_Mozilla_UNIQUE_TOSOURCE_FKT_AGAINST_BROWSER_TYPE().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (CHECK_Compare_DEVICEUSERAGENTSTRING_to_BrowserType_to_WEBGL_DEBUG_TOKENS().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (CHECK_Webdriver_in_Navigator_Interface().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (CHECK_Permission_DENIED_in_State_Prompt().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (DETECT_NAVIGATORPERMISSIONS_PROPERTIES_ODD().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (CHECK_SPOOKYOSCHECK_likely_Devtools().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (DETECT_Phantom_in_Window().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (DETECT_BROWSERAUTOMATION_via_domElements().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (DETECT_PHANTOMAS().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (DETECT_Selenium_DOM_based().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (DETECT_NodeJS_Buffer().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (DETECT_Chromium_based_automation_driver().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (CHECK_setTimeout_Integrity().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (CHECK_setInterval_Integrity().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (CHECK_XMLHTTPRequest_1().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (CHECK_XMLHTTPRequest_2().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (CHECK_UserAgent_not_automation().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (CHECK_NumberofLogicalProcessors_IOS().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (CHECK_Browser_VoiceList().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (CHECK__STACKTRACE_Behavior().split(":")[1] === "1") {
      return "a0:" + 1;
    } else if (CHECK_VirtualBox().split(":")[1] === "1") {
      return "a0:" + 1;
    } else {
      return "a0:" + 0;
    }
  } catch (c) {
    return "a0:e";
  }
}

现在,我将尝试引导您完成这些检查,以解释它们的作用以及它们的工作原理。这可能意味着要查看大量的 JavaScript 和浏览器文档。因此,请系好安全带,迎接未来的疯狂。首先,让我们看一下最常用的帮助程序函数:

function CHECK_DEVICE_IN_USERAGENT_STRING() {
  var c = navigator.userAgent.toLowerCase();
  var d;
  if (c.indexOf("windows phone") >= 0) {
    d = "Windows Phone";
  } else if (c.indexOf("win") >= 0) {
    d = "Windows";
  } else if (c.indexOf("kaios") >= 0) {
    d = "Kaios";
  } else if (c.indexOf("android") >= 0 || c.indexOf("spreadtrum") >= 0) {
    d = "Android";
  } else if (c.indexOf("linux") >= 0 || c.indexOf("cros") >= 0) {
    d = "Linux";
  } else if (c.indexOf("iphone") >= 0 || c.indexOf("ipad") >= 0) {
    d = "iOS";
  } else if (c.indexOf("mac") >= 0) {
    d = "Mac";
  } else {
    d = "Other";
  }
  return d;
}

function DETECT_Browser_Type() {
  var c = navigator.userAgent;
  var d;
  if (c.indexOf("OPR/") !== -1 || c.indexOf("Opera") !== -1) {
    d = "Opera";
  } else if ((c.indexOf("MSIE") !== -1 || c.indexOf("Trident") !== -1) && c.indexOf("MAXTHON") === -1) {
    d = "Internet Explorer";
  } else if (c.indexOf("Edge") !== -1 || c.indexOf("EdgA") !== -1) {
    d = "Edge";
  } else if (c.indexOf("SamsungBrowser") !== -1) {
    d = "Samsung Browser";
  } else if (c.indexOf("UCBrowser") !== -1) {
    d = "UC Browser";
  } else if (c.indexOf("Android") !== -1 && c.indexOf("Chrome") === -1 && c.indexOf("Firefox") === -1) {
    d = "Android Browser";
  } else if (c.indexOf("Chrome") !== -1 || c.indexOf("CriOS") !== -1) {
    d = "Chrome";
  } else if (c.indexOf("Safari") !== -1 && c.indexOf("Chrome") === -1) {
    d = "Safari";
  } else if (c.indexOf("Firefox") !== -1) {
    d = "Firefox";
  } else {
    d = "Other";
  }
  return d;
}

这两者都非常简单,并且基于潜在受害者的用户代理标头工作。

User-Agent 请求标头是一个特征字符串,它允许服务器和网络对等方标识请求用户代理的应用程序、操作系统、供应商和/或版本。(来源:Mozilla.org)

因此,让我们看一个示例 UserAgent 字符串,以更好地理解我们可以根据此标头识别的内容:

Mozilla/5.0 (Windows NT 10.0;Win64的;x64) AppleWebKit/537.36 (KHTML,如 Gecko) Chrome/121.0.0.0 Safari/537.36

让我们将其分成几个部分,并尝试单独理解所有内容:

  1. Mozilla/5.0:在大多数 UserAgent 字符串的开头使用的历史字符串,可以忽略。
  2. Windows NT 10.0:Windows NT 是 Windows 内核的名称。10.0 是上面提出 Web 请求的人使用的内核版本。使用在线资源,我们可以很容易地识别发出此 Web 请求的人使用的是 Windows 操作系统。更具体地说,是 Windows 10 或 Windows 11 的最新版本。(从技术上讲,它也可能是 Windows Server 2016、2019 或 2021)。
  3. Win64的;x64:这为我们提供了使用的 Windows 处理器体系结构。从攻击者的角度来看,这可能很重要,因为 x64 恶意软件二进制文件将无法在 x32 操作系统上运行。
  4. AppleWebKit/537.36:这是浏览器的渲染引擎和版本。渲染引擎用于将网站的源代码转换为视觉表示。在本例中,它似乎是 AppleWebKit。现在,从历史上看,UserAgent 字符串的这一部分曾经是准确的,因为很长一段时间以来,所有基于 Apple 和 Chromium 的浏览器都使用 AppleWebKit。然而,在 2013 年,谷歌开始创建和使用他们自己的 AppleWebKit 分支(称为 Blink)。由于某种原因,这从未在 Web 浏览器的 UserAgent 中真正宣布过,因此现在上面的内容可能是 Safari、Chrome 或其他基于 Chromium 或 WebKit 的浏览器。我们唯一可以明确排除的是Mozilla的Firefox,它使用Gecko并正确地宣布了这一点。
  5. (KHTML如Gecko):另一个历史信息。从技术上讲,AppleWebKit 本身是另一个名为 KHTML 的浏览器渲染引擎的分支。当 Apple 在 2005 年左右发布 WebKit 时,他们希望确保新的渲染引擎被所有主要的 Web 服务器所接受。因此,他们只是简单地将像 Gecko 这样的 KHTML 放入字符串中,以宣布他们的新渲染引擎的行为方式与 Mozilla 的 Gecko 引擎相同。
  6. Chrome/121.0.0.0:这是使用上述 UserAgent 标头的实际浏览器。Google Chrome 版本 121
  7. Safari/537.36:这是另一个历史信息。当 Google 发布 Chrome 时,他们希望确保所有网站也接受他们的请求。因此,他们复制了当时的 Safari UserAgent,并在其中添加了自己的 Chrome 版本 (6.)。

如您所见,有很多信息需要从UserAgent解压缩。这只是一个例子。下面的UserAgent也完全没问题:

  • Dalvik/2.1.0 (Linux;U;安卓 9.0;中兴BA520 Build/MRA58K)
  • Mozilla/5.0 (iPhone;CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1 Mobile/15E148 Safari/604.1
  • Mozilla/5.0 (Windows NT 10.0;Win64的;x64 的;rv:122.0) 壁虎/20100101 Firefox/122.0

正如你很容易看到的,它们与我们之前看到的有很大不同。但是,它们都具有 2 个特征:

  1. 包含有关所用操作系统的精确详细信息的支架
  2. 通过尝试查找字符串中未在其他用户代理中表示的确切部分来精确识别使用的浏览器的潜力。

但这里需要注意的重要一点是,从技术上讲,发出 Web 请求的软件可以决定是否设置以及设置哪个 UserAgent。wget、curl 或 python-request 库等常用工具都会设置单独的 UserAgent,如果没有被告知这样做,这为 Web 服务器提供了一种识别不常见 Web 请求的简单方法。如果您想了解更多关于UserAgents的历史,我强烈建议您阅读这篇文章。

回到 VexTrio,我们现在可以了解前两个呈现的指纹识别功能的作用。函数“CHECK_DEVICE_IN_USERAGENT_STRING”用于解析从受害者那里收到的UserAgent并识别操作系统。为此,它会将用户代理转换为所有小写字母,然后遍历它以识别唯一标识操作系统的特征字符串。如果未标识 OS,则会将返回值设置为“其他”。“DETECT_Browser_Type”功能用于识别受害者的浏览器(Chrome、Firefox、Safari 等)。如果你仔细观察,你会发现它利用了前面讨论的 UserAgent 字符串的一些历史功能。例如,如果标题中没有“Chrome”字符串,则浏览器仅被标识为“Safari”。正如我们刚刚了解到的,这就是 Safari 和 Chrome 标头之间的确切区别,因为 Google 刚刚在 Safari UserAgent 字符串中添加了 Chrome 版本。同样,如果未识别浏览器,则将其设置为“其他”。

正如我们将在本文中看到的那样,VexTrio 持续使用上述两个功能。但为了保持秩序,我现在将按照 A1 到 A29 的顺序检查每张支票。

检查 1:CHECK_COMPARE_ISO_CODE_LANGUAGE_ARRAY_VS_ISO_CODE_PRIMARY_LANGUAGE

function CHECK_COMPARE_ISO_CODE_LANGUAGE_ARRAY_VS_ISO_CODE_PRIMARY_LANGUAGE() {
  if (typeof navigator.languages !== "undefined") {
    try {
      var c = navigator.languages[0].substr(0, 2);
      if (c !== navigator.language.substr(0, 2)) {
        return "a1:" + 1;
      }
    } catch (d) {
      return "a1:e";
    }
  }
  return "a1:" + 0;
}

VexTrio 执行的第一个实际检查是将用户的首选语言 (navigator.language) 的值与 browsers.languages 数组的第一个数组进行比较。更准确地说,它比较了所述元素的 ISO 代码表示的前 2 个字母。在 Firefox 中,这两个值应始终相同。但是,对于其他浏览器来说,这不一定是真的。对于 VexTrio 代码,如果 ISO 代码不同,则返回值为“a1:1”(检查 a1 = 错误),如果值相同或未定义 navigator.languages 数组,则返回“a1:0”(检查 a1 = 良好)。我相信 VexTrio 的作者可能没有考虑过这个检查。虽然我的初衷是说,如果 UI/首选语言与语言数组中的第一个条目不匹配,浏览器的行为会很奇怪,但这并不一定表明某些事情很奇怪。例如,Brave 浏览器的反跟踪功能故意将这些值设置为不同,并且与 Mozilla Firefox 不同的浏览器没有义务使这些值相等。目前,我不知道VexTrio试图通过此检查完成什么,但我非常愿意接受建议。

检查 2:CHECK_COMPARE_AVAILABLE_WINDOW_SIZE_TO_CURRENT_WINDOW_SIZE

function CHECK_COMPARE_AVAILABLE_WINDOW_SIZE_TO_CURRENT_WINDOW_SIZE() {
  try {
    if (window.screen.width < window.screen.availWidth || window.screen.height < window.screen.availHeight) {
      if (window.screen.width === window.screen.availHeight && window.screen.height === window.screen.availWidth || window.screen.width === window.screen.availHeight + 20 && window.screen.height === window.screen.availWidth) {
        return "a2:" + 0;
      } else {
        return "a2:" + 1;
      }
    } else {
      return "a2:" + 0;
    }
  } catch (c) {
    return "a2:e";
  }
}

在第二次检查中,VexTrio 通过检查总高度与可用高度和总宽度与可用宽度,将当前窗口表示的大小与可用屏幕的大小进行比较。

要理解此检查,让我们首先了解比较的值。根据我的研究,值 window.screen.availWidth 和 window.screen.availHeight 返回可用于呈现网页的浏览器窗口的可用宽度和高度。通常,这是屏幕高度/宽度的像素数减去用于 Web 浏览器静态功能(如任务栏、搜索栏或其他浏览器 UI 功能)的像素值。

相比之下,window.screen.height 和 window.screen.width 显示操作系统屏幕分辨率设置中配置的最大屏幕大小。这些值不考虑任何形式的静态呈现的浏览器 UI 功能。

从逻辑上讲,对于普通的 Web 浏览器,应该始终期望可用的 Height 和可用宽度值小于 height 和 width 值,因为每个“普通”Web 浏览器都会呈现 UI 功能,从而减少可用的宽度和高度。上述检查的第一部分正是使用这个假设来检查浏览器的合法性。如果高度和宽度比大于可用的高度和宽度值,则通过检查,函数返回 a2:0(检查 2 通过)。

但是,如果检查没有通过,就不能立即说浏览器不合法。执行辅助检查以检查 width 值是否等于 availHeight 值(或 availHeight 值 +20 像素)以及 height 值是否等于 availWidth 值。我还没有弄清楚为什么这些值也是合法的浏览器值。我最初的假设是,这可能与旋转屏幕有关,其中 availHeight 可能是宽度,反之亦然。但是,在我的笔记本电脑旋转屏幕上进行的初始测试并未导致所描述的行为,因为值会针对旋转屏幕自动调整。也许这在某些移动设备上有所不同?欢迎在下面发表评论。

检查 3:CHECK_COMPARE_OSCPU_TO_DEVICE_USERAGENT_STRING

function CHECK_COMPARE_OSCPU_TO_DEVICE_USERAGENT_STRING() {
  try {
    var d = navigator.oscpu;
    var f = CHECK_DEVICE_IN_USERAGENT_STRING();
    if (typeof d !== "undefined") {
      d = d.toLowerCase();
      if (d === "" && f === "Kaios") {
        return "a3:" + 0;
      } else if (d.indexOf("win") >= 0 && f !== "Windows" && f !== "Windows Phone") {
        return "a3:" + 1;
      } else if (d.indexOf("linux") >= 0 && f !== "Linux" && f !== "Android") {
        return "a3:" + 1;
      } else if (d.indexOf("mac") >= 0 && f !== "Mac" && f !== "iOS") {
        return "a3:" + 1;
      } else if ((d.indexOf("win") === -1 && d.indexOf("linux") === -1 && d.indexOf("mac") === -1) !== (f === "Other")) {
        return "a3:" + 1;
      } else {
        return "a3:" + 0;
      }
    } else {
      return "a3:" + 0;
    }
  } catch (g) {
    return "a3:e";
  }
}

检查 3 是一个相当简单的比较检查。它利用 navigator.oscpu 属性。此已弃用的属性返回一个字符串,该字符串标识用于运行当前正在访问网页的浏览器的操作系统。下表显示了 oscpu 属性的预期值以及它所引用的 OS。

图 1:oscpu 值(来源:mozilla.org)

现在,使用 navigator.oscpu 属性和前面讨论的从 UserAgent 标头检索 OS 的方法,VexTrio 有 2 种接收 OS 的方法。Check 3 现在采用这两种方法,并在 navigator.oscpu 不返回“undefined”的情况下比较它们的结果。如果 navigator.oscpu 返回的 OS 与 UserAgent 值相同,则通过检查 (a3:0)。如果不是,VexTrio会认为访问者是不合法的,因为它表明有人在摆弄UserAgent或浏览器。

检查4:CHECK_OSCPU_NOT_UNDEFINED_AND_BROWSER_NOT_FIREFOX

function CHECK_OSCPU_NOT_UNDEFINED_AND_BROWSER_NOT_FIREFOX() {
  try {
    var c = navigator.oscpu;
    var d = DETECT_Browser_Type();
    if (typeof c !== "undefined" && d !== "Firefox") {
      return "a4:" + 1;
    } else {
      return "a4:" + 0;
    }
  } catch (f) {
    return "a4:e";
  }
}

现在,前面讨论的检查有一个问题。“navigator.oscpu”功能已基本停产,目前仅受 Firefox 和 Firefox for Android 支持。在所有其他浏览器上,“navigator.oscpu”返回“undefined”。VexTrio 通过将返回值与 UserAgent 浏览器类型检查的结果(如上所述)进行比较来利用此检查。如果识别出的浏览器不是 Firefox,但 navigator.oscpu 的结果类型不是未定义的,则 VexTrio 会自动假设有问题。因此,检查未通过 (a4:1)。

检查 5:CHECK_COMPARE_PLATFORM_USERAGENT_DEVICEINUSERAGENT

function CHECK_COMPARE_PLATFORM_USERAGENT_DEVICEINUSERAGENT() {
  try {
    var c = navigator.platform.toLowerCase();
    var d = navigator.userAgent.toLowerCase();
    var f = CHECK_DEVICE_IN_USERAGENT_STRING();
    if (c === "" && f === "Kaios") {
      return "a5:" + 0;
    } else if (d.indexOf("maui") >= 0 && c.indexOf("pike") >= 0) {
      return "a5:" + 0;
    } else if (d.indexOf("j2me/midp") >= 0 && c.indexOf("pike") >= 0) {
      return "a5:" + 0;
    } else if (c === "arm" && f === "Windows Phone") {
      return "a5:" + 0;
    } else if (c.indexOf("win") >= 0 && f !== "Windows" && f !== "Windows Phone") {
      return "a5:" + 1;
    } else if ((c.indexOf("linux") >= 0 || c.indexOf("android") >= 0 || c.indexOf("pike") >= 0) && f !== "Linux" && f !== "Android" && f !== "Kaios") {
      return "a5:" + 1;
    } else if ((c.indexOf("mac") >= 0 || c.indexOf("ipad") >= 0 || c.indexOf("ipod") >= 0 || c.indexOf("iphone") >= 0) && f !== "Mac" && f !== "iOS") {
      return "a5:" + 1;
    } else if (c === "macintel" && d.indexOf("iphone") >= 0) {
      return "a5:" + 1;
    } else {
      var g = c.indexOf("win") < 0 && c.indexOf("linux") < 0 && c.indexOf("mac") < 0 && c.indexOf("iphone") < 0 && c.indexOf("pike") < 0 && c.indexOf("ipod") < 0 && c.indexOf("ipad") < 0;
      if (g !== (f === "Other")) {
        return "a5:" + 1;
      }
    }
    return "a5:" + 0;
  } catch (h) {
    return "a5:e";
  }
}

检查 5 再次是比较检查。它采用本机浏览器属性 navigator.platform 和 navigator.useragent 的值,以及前面描述的函数的值,该函数从收到的 UserAgent 返回 OS。然后,该函数根据这些简单的值执行多项检查。例如,它检查 navigator.platform 返回的操作系统是否包含典型的基于 Apple 的平台指标(Mac、iPad、iPod 或 iPhone),但在 UserAgent 中没有提到 Mac 或 iOS 操作系统,这很奇怪,因为所有 4 个平台都是基于 Mac 或 iOS 的。因此,如果为真,则检查将不会通过(a5:1)。

我不打算解释在此函数中完成的每一项检查,因为它们都遵循基于比较的检查模式来检测 navigator.platform 和基于 UserAgent 的平台值之间的异常情况。

检查 6:CHECK_PLUGINSUNDEFINED_BUT_USERAGENTOS_NOT_WINDOWS

function CHECK_PLUGINSUNDEFINED_BUT_USERAGENTOS_NOT_WINDOWS() {
  try {
    var c = CHECK_DEVICE_IN_USERAGENT_STRING();
    if (typeof navigator.plugins === "undefined" && c !== "Windows" && c !== "Windows Phone") {
      return "a6:" + 1;
    } else {
      return "a6:" + 0;
    }
  } catch (d) {
    return "a6:e";
  }
}

检查 6 基于 navigator.plugins 值。现在,navigator.plugins 属性非常有趣。根据 Mozilla 开发者文档,除 Safari 外,它已在所有浏览器中弃用。在 Safari 上,此属性返回所有已安装的浏览器插件及其描述的实际数组,而在所有其他浏览器上,它似乎返回 PDF 插件的硬编码列表。

图 2:navigator.plugins 的弃用状态(来源:mozilla.org)

现在,尽管所有常见浏览器似乎都具有某种形式的 navigator.plugins 属性返回值,无论是插件的真实列表还是硬编码的 pdf 值列表,检查 6 假设在 Windows 和 Microsoft Phone 下,该值可能是未定义的。我不知道为什么会这样。但是,尽管 UserAgent OS 检查函数返回的 OS 不是 Windows 或 Windows Phone,但 VexTrio 会检查属性的返回值是否未定义。在这种情况下,检查失败并返回 a6:1,否则检查通过。我最好的猜测是,有些浏览器自动化工具不会费心定义 navigator.plugins 数组,因此可以使用此检查被检测为非法浏览器。

检查 7:CHECK_BROWSER_BUILDNUMBER_AGAINST_BROWSERTYPE

function CHECK_BROWSER_BUILDNUMBER_AGAINST_BROWSERTYPE() {
  try {
    var c = navigator.productSub;
    var d = DETECT_Browser_Type();
    if ((d === "Chrome" || d === "Safari") && c !== "20030107") {
      return "a7:" + 1;
    } else if (d === "Opera" && c !== "20030107" && typeof c !== "undefined") {
      return "a7:" + 1;
    } else {
      return "a7:" + 0;
    }
  } catch (f) {
    return "a7:e";
  }
}

检查 7 是针对 Chrome、Safari 和 Opera 浏览器的特定检查。同样,快速浏览一下Mozilla开发人员文档,使此检查非常容易理解。

read-only 属性返回当前浏览器的内部版本号。Navigator.productSub

在 IE 上,此属性返回 undefined。

在 Apple Safari 和 Google Chrome 上,此属性始终返回 。(此外,在发布 v. 15 之后,Opera 上也有)20030107

分析的 VexTrio 示例的检查 7 利用了 Safari、Chrome 和 Opera 返回固定版本号的事实,确保如果标识的浏览器是 Chrome、Safari 或 Opera,则返回的属性值确实是预期版本“20030107”。如果不是,则假设存在某种操作,并且检查失败,并显示 a7:1。

检查 8:CHECK_LENGTH_OF_EVAL_FUNCTION_AGAINST_BROWSER_TYPE

function CHECK_LENGTH_OF_EVAL_FUNCTION_AGAINST_BROWSER_TYPE() {
  try {
    var c = DETECT_Browser_Type();
    var d = CHECK_DEVICE_IN_USERAGENT_STRING();
    var f = eval.toString().length;
    if (f === 37 && c !== "Safari" && c !== "Firefox" && c !== "Other" && d === "iOS" && c !== "Chrome") {
      return "a8:" + 1;
    } else if (f === 39 && c !== "Internet Explorer" && c !== "Other") {
      return "a8:" + 1;
    } else if (f === 33 && c !== "Chrome" && c !== "Opera" && c !== "Edge" && c !== "UC Browser" && c !== "Samsung Browser" && c !== "Other" && c !== "Android Browser") {
      return "a8:" + 1;
    } else {
      return "a8:" + 0;
    }
  } catch (g) {
    return "a8:e";
  }
}

检查 8 是一个非常有趣的检查。它依赖于这样一个事实,即在不同浏览器中使用的 eval 函数在转换为字符串时似乎具有不同的长度。显然,eval 函数有 3 种不同的长度,可以与已知浏览器相关联。通过使用前面讨论的 UserAgent 函数和 eval 函数字符串的长度,该检查可确保如果识别出 eval 函数的已知长度之一,则浏览器和操作系统可以与所述长度直接相关。例如,如果 eval 函数字符串的长度为 39,则浏览器只能是 Microsoft Explorer 或“Other”。如果长度为 39,但浏览器不是 2 之一,则 VexTrio 认为它没有查看合法访问者。

检查 9:CHECK_Mozilla_UNIQUE_TOSOURCE_FKT_AGAINST_BROWSER_TYPE

function CHECK_Mozilla_UNIQUE_TOSOURCE_FKT_AGAINST_BROWSER_TYPE() {
  try {
    var c = DETECT_Browser_Type();
    var d;
    try {
      throw "a";
    } catch (f) {
      try {
        f.toSource();
        d = true;
      } catch (g) {
        d = false;
      }
    }
    if (d && c !== "Firefox" && c !== "Other") {
      return "a9:" + 1;
    } else {
      return "a9:" + 0;
    }
  } catch (h) {
    return "a9:e";
  }
}

测试 a9(为了方便起见,我将其命名为“CHECK_Mozilla_UNIQUE_TOSOURCE_FKT_AGAINST_BROWSER_TYPE”^^)是一个使用名为 toSource() 的过时浏览器函数的检查。在弃用之前,此函数可用于将作为参数传递的函数转换为其源代码表示形式。但是,任何现代浏览器都不再使用此功能。try/catch 语句的工作方式是,如果函数存在,则 d 设置为 true,else 设置为 false。如果 d 为 true,但浏览器不是 Firefox 或“其他”,则 VexTrio 认为有问题并且测试失败 (a9:1)。我不确定为什么在这里会出现Firefox例外。也许 toSource() 函数在最新版本之前一直受 Firefox 支持,因此对它们进行分类也会使许多使用较旧 Firefox 版本的潜在用户/受害者被整理出来。

无论如何,对于所有未实现该功能的浏览器,此检查都通过了,因此所有普通浏览器都应该以 a9:0 通过检查。

间奏曲:两个额外的辅助函数

在我们继续分析VexTrio的浏览器指纹检查之前,我们需要看一下在接下来的指纹识别步骤中使用的两个额外的帮助程序功能。

function HELPER_WEBGL_DEBUG_TOKENS_FOR_RENDERER_AND_VENDOR() {
  try {
    var c = document.createElement("canvas");
    var d = c.getContext("webgl");
    var e = d.getExtension("WEBGL_debug_renderer_info");
    var f = d.getParameter(e.UNMASKED_VENDOR_WEBGL);
    var g = d.getParameter(e.UNMASKED_RENDERER_WEBGL);
    return [f, g];
  } catch (h) {
    return false;
  }
}

首先,我们看一个非常有趣的小技巧。它利用调试功能来接收有关受害者的信息。因此,让我们了解我们正在查看的内容。

上面函数做的第一件事是创建一个类型的“canvas”的新 DOM 元素。在此上下文中,Canvas 元素是一个 HTML 元素,可由“画布脚本 API”或“WebGL API”操作,以在网页上创建图形和动画。

现在,在创建该元素之后,如果我们想开始从 JavaScript 上下文操作画布,我们需要访问其上下文。这可以通过 getContext 函数完成。VexTrio 使用的函数确实将新创建的 canvas 元素的绘图上下文设置为“WebGL”。

现在,其原因在于 WebGL 绘图上下文的某个扩展,称为“WEBGL_debug_renderer_info”。WebGL API 的此扩展用于调试画布绘图,方法是为开发人员提供两个常量字符串,表示用于呈现网站的当前图形卡。常量字符串称为 UNMASKED_VENDOR_WEBGL 和 UNMASKED_RENDERER_WEBGL,可以通过扩展的 getParameter() 函数访问。
换句话说,通过使用这种方法,上述功能(以及VexTrio)能够识别潜在受害者的显卡。我们稍后将看到这些信息的有用性。

var voiceslist = "";
function populateVoiceList() {
  try {
    var c = speechSynthesis.getVoices();
    if (c.length !== 0) {
      var d = "";
      for (var f = 0; f < c.length; f++) {
        d = d + " " + c[f].name;
      }
      voiceslist = d;
    } else {
      setTimeout(function () {
        populateVoiceList();
      }, 5);
    }
  } catch (g) {}
}
populateVoiceList();

第二个新的帮助程序函数是关于 speechSynthesis 功能的。该函数尝试访问 Web 浏览器的 speechSynthesis 接口以获取所有可用语音的列表,对其进行迭代,并将所有可用语音的所有名称连接到一个字符串中。如果无法立即检索语音列表,它会在 5 秒超时后重试该过程,这很可能是由于不同 Web 浏览器中潜在的加载延迟。如果发生任何错误,函数将静默退出。请注意,该函数在页面加载时直接执行。语音列表字符串将在以后的检查中使用。

检查 10:CHECK_Compare_DEVICEUSERAGENTSTRING_to_BrowserType_to_WEBGL_DEBUG_TOKENS

function CHECK_Compare_DEVICEUSERAGENTSTRING_to_BrowserType_to_WEBGL_DEBUG_TOKENS() {
  try {
    var c = CHECK_DEVICE_IN_USERAGENT_STRING();
    var d = DETECT_Browser_Type();
    var f = HELPER_WEBGL_DEBUG_TOKENS_FOR_RENDERER_AND_VENDOR();
    if (!f) {
      return "a10:" + 0;
    } else if (c === "iOS" && f[0].indexOf("Apple") === -1 && f[0].indexOf("Imagination Technologies") === -1) {
      return "a10:" + 1;
    } else if (c === "Mac" && f[0].indexOf("Intel") === -1 && f[0].indexOf("ATI Technologies") === -1 && f[0].indexOf("NVIDIA Corporation") === -1 && f[0].indexOf("Apple") === -1) {
      return "a10:" + 1;
    } else if (c === "Android" && (f[0] === "Google Inc. (NVIDIA)" || f[0] === "Google Inc. (Intel)" || f[0] === "Google Inc. (Google)" || f[0] === "Google Inc." || f[0].indexOf("NVIDIA Corporation") !== -1)) {
      return "a10:" + 1;
    } else if (c === "Windows" && d === "Edge" && f[0].indexOf("Microsoft") === -1) {
      return "a10:" + 1;
    } else if (c === "Windows" && (d === "Chrome" || d === "Firefox") && f[0].indexOf("Google Inc") === -1) {
      return "a10:" + 1;
    } else if (f[0].indexOf("VMware") !== -1) {
      return "a10:" + 1;
    } else {
      return "a10:" + 0;
    }
  } catch (g) {
    return "a10:e";
  }
}

Check 10 是第一个使用新引入的帮助程序函数的帮助程序函数,该函数通过 WebGL 调试属性接收显卡详细信息。某些操作系统通常绑定到某些显卡供应商。因此,通过使用最初讨论的函数从用户代理字符串中识别操作系统和浏览器,VexTrio 将识别的操作系统与显卡供应商进行比较。例如,如果标识的操作系统是 Android,则检查将确保显卡供应商 (f[0]) 为“Google Inc. (NVIDIA)”、“Google Inc. (Intel)”、“Google Inc. (Google)”、“Google Inc.”或包含字符串“NVIDIA Corporation”。如果这些假设值均未给出,则检查假定有问题并返回 a10:1。对 iOS、Mac 和 Windows 及其各自假定的显卡供应商值进行了类似的检查。

此外,在通过检查之前,将检查显卡供应商值中是否存在字符串“VMware”,以确保访问浏览器未在 VMWare 虚拟机中运行。这种检查很可能是为了逃避恶意软件分析师试图在虚拟环境中分析VexTrio恶意软件的窥探。

检查 11:CHECK_Webdriver_in_Navigator_Interface

function CHECK_Webdriver_in_Navigator_Interface() {
  try {
    var c;
    browser = DETECT_Browser_Type();
    os = CHECK_DEVICE_IN_USERAGENT_STRING();
    c = "webdriver" in navigator && navigator.webdriver;
    if (c) {
      return "a11:" + 1;
    } else {
      return "a11:" + 0;
    }
  } catch (d) {
    return "a11:e";
  }
}

VexTrio 武器库的下一次检查相当简单。它可能是恶意软件常用的“IsDebuggerPresent”Windows API 调用的 Web 等效项,用于简单地询问执行它的系统是否存在调试器。为了理解这个函数,让我们先看一下 navigator.webdriver 属性的 Mozilla 开发者文档。

图 3:Navigator.webdriver 属性 docu(来源:mozilla.org)

如您所见,该属性是一个简单的布尔值,如果浏览器与自动化(或调试)方案中常用的不同标志一起启动,则返回 true。VexTrio只是问浏览器:“嘿,你是自动运行的吗?”如果为真,则访问者不合法,无法通过检查(a11:1)。

间奏曲 2:getPermissionStatus 函数

接下来的两项检查依赖于浏览器授予访问的网页的浏览器权限集。您可能知道,有些网站具有一组需要额外权限的某些功能,例如访问设备的麦克风、网络摄像头或以弹出消息的形式发送通知。大多数现代浏览器在如何处理网页的此类权限请求方面都有默认行为。以下是Google Chrome浏览器授予网站“medium.com”的默认权限的示例。

图 4:Google Chrome 授予 medium.com 的部分浏览器权限集

如您所见,大多数权限默认设置为“询问”,这意味着如果网站请求权限,浏览器将通过弹出窗口询问用户是否应允许网页获得所需的权限。VexTrio要做的下一件事就是使用这些权限。

var permissions = false;
getPermissionStatus();
function getPermissionStatus() {
  try {
    browser = DETECT_Browser_Type();
    if (browser !== "Samsung Browser" && browser !== "Firefox") {
      navigator.permissions.query({
        name: "notifications"
      }).then(function (c) {
        if (Notification.permission === "denied" && c.state === "prompt") {
          permissions = true;
        } else {
          permissions = false;
        }
      });
      return permissions;
    }
  } catch (c) {}
}

更具体地说,如果 VexTrio 将浏览器识别为不是 Firefox 也不是 Samsung Browser,它会使用“navigator.permissions.query()”方法查询“通知”权限。现在,query() 方法不是立即返回结果,而是返回一个“promise”对象。promise 对象是允许在 JavaScript 中使用异步操作的对象。基本上,在知道对象将具有结果以及结果是什么之前,您定义了一个对象。然后,您可以使用此承诺上的“.then”来定义异步操作完成后将执行的操作,具体取决于它完成的状态。要处理的可能结果状态为“已满足”或“已拒绝”。但是,VexTrio 仅为成功的查询操作(已完成)定义操作。如果查询已完成,结果将存储在“c”变量中。对于此函数,“c”将是一个 PermissionStatus 对象,该对象具有以下属性:

图 5:PermissionStatus 对象的属性(源代码:mozzilla.org)

然后,此对象将与“Notifications.permission”的返回值一起使用,以将布尔值分配给“permissions”变量。以下 if 语句负责此赋值:

if (Notification.permission === "denied" && c.state === "prompt") {
          permissions = true;
        } else {
          permissions = false;

现在,我仍然不能 100% 确定,但我相信仅当 Notification 对象的 permission 属性与 PermissionStatus 对象的查询结果状态之间的关系出现异常时,才会满足将权限变量设置为 true 的条件。在正常情况下,应符合以下情况之一:

  • Notification.permission === “默认” && c.state === “提示”
  • Notification.permission === “拒绝” && c.state === “拒绝”
  • Notification.permission === “已授予” && c.state === “已授予”

似乎没有合法的方式让基于 Chromium 的浏览器设置“拒绝”和“提示”组合。

检查 12:CHECK_Permission_DENIED_in_State_Prompt

function CHECK_Permission_DENIED_in_State_Prompt() {
  try {
    if (permissions) {
      return "a12:" + 1;
    } else {
      return "a12:" + 0;
    }
  } catch (c) {
    return "a12:e";
  }
}

因此,当“permissions”设置为 true 时,VexTrio 完成的下一个检查将失败。

检查 13:DETECT_NAVIGATORPERMISSIONS_PROPERTIES_ODD

function DETECT_NAVIGATORPERMISSIONS_PROPERTIES_ODD() {
  try {
    var c = window.navigator.permissions;
    if (c.query.toString().replace(/s+/g, "") !== "function query() { [native code] }".replace(/s+/g, "")) {
      return "a13:" + 1;
    }
    if (c.query.toString.toString().replace(/s+/g, "") !== "function toString() { [native code] }".replace(/s+/g, "")) {
      return "a13:" + 1;
    }
    if (c.query.toString.hasOwnProperty("[[Handler]]") && c.query.toString.hasOwnProperty("[[Target]]") && c.query.toString.hasOwnProperty("[[IsRevoked]]")) {
      return "a13:" + 1;
    }
    if (c.hasOwnProperty("query")) {
      return "a13:" + 1;
    }
    return "a13:" + 0;
  } catch (d) {
    return "a13:e";
  }
}

下一次检查再次涉及浏览器权限,但是,在这种情况下,VexTrio 对所涉及的函数背后的代码实现更感兴趣。首先,它获取当前窗口的 permissions 对象。然后,它使用 .toString() 方法将前面讨论的“query”方法转换为字符串表示形式。此方法应返回对象的字符串表示形式。有趣的是,它返回以下字符串:“function toString() { [native code] }”。我从 Web 开发人员控制台复制了此行为:

图 6:本机代码结果

执行此检查可能是为了检查查询函数是否未纵。引用官方文档:“如果在内置函数对象上调用该方法,则由 或其他非 JavaScript 函数创建的函数返回本机函数字符串”。toString()Function.prototype.bind()toString()

“toString()”函数本身也是如此。在正常的浏览器条件下,预计会出现相同的“toString(){ [native code] }”结果。如果 2 个 toString() 方法中的任何一个没有预期的结果,则检查失败 (a13:1)。

此外,第二种方法用于检查某些属性的所有权。引用文档:

如果指定的属性是对象的直接属性,则该方法将返回 — 即使值为 或 。如果该属性是继承的,或者根本没有声明,则该方法将返回。hasOwnProperty()truenullundefinedfalse

现在完成的指纹检查是为了确保查询方法的 .toString() 值的属性 “[[Handler]]”、“[[Target]]” 和 “[[IsRevoked]]” 不归对象本身所有。query() 函数本身与权限对象的关系也是如此。

我必须承认,我并不完全清楚此检查的原因,因为它涉及对在浏览器 javascript 上下文中转换为字符串的方法的行为方式的深入了解。但是,可以肯定的是,这样做是为了确保所有前面提到的属性都符合预期,并且没有任何迹象表明某种浏览器自动化或浏览器虚拟化已经干预了本机属性,并且浏览器是VexTrio的“合法访问者/受害者”。如果这些检查失败,将导致预期的返回值“a13:1”,表示 VexTrio 不信任访问浏览器。

检查 14:CHECK_SPOOKYOSCHECK_likely_Devtools

function CHECK_SPOOKYOSCHECK_likely_Devtools() {
  try {
    os = CHECK_DEVICE_IN_USERAGENT_STRING();
    browser = DETECT_Browser_Type();
    if (browser === "Chrome" && os !== "iOS") {
      var c = 0;
      var d = /./;
      d.toString = function () {
        c++;
        return "spooky";
      };
      console.debug(d);
      if (c > 1) {
        return "a14:" + 1;
      } else {
        return "a14:" + 0;
      }
    } else {
      return "a14:" + 0;
    }
  } catch (f) {
    return "a14:e";
  }
}

在分析VexTrio的指纹识别方法时,“幽灵检查”可能是最让我头疼的检查之一。直到现在,我只能通过做一个假设来真正解释它。

该检查针对的是 Google Chrome 浏览器,前提是该浏览器未安装在 iOS 设备上。如果为真,则检查将创建一个值为“/./”的正则表达式对象。每次调用 toString 方法时,都会重写此对象的 toString 方法以递增计数器 c,然后返回一个字符串“spooky”。

然后,使用 console.debug(d) 将对象 d 记录到控制台。这里的诀窍是 console.debug() 函数利用覆盖的 toString() 函数将对象打印到控制台。这样一来,计数器 c 就会增加 1。但是,如果计数器增加超过 1,则 VexTrio 将假定犯规并且检查失败。可悲的是,我没有解释在什么情况下计数器应该增加一个以上。我假设这可能发生在某种虚拟/编码环境中,或者如果有人将调试函数挂钩以获得一些附加功能。如果有更多JavaScript和浏览器自动化知识的人站出来解释它,我将不胜感激。如果可用,我将确保将缺失的信息添加到这篇博文中。

如果 toString 函数未多次调用,或者访问者未使用 Chrome 或在 iOS 下使用 Chrome,则测试通过。

检查 15:DETECT_Phantom_in_Window

function DETECT_Phantom_in_Window() {
  try {
    function c() {
      return ["callPhantom" in window, "_phantom" in window, "phantom" in window];
    }
    result = c().some(function (d) {
      return d;
    });
    if (result) {
      return "a15:" + 1;
    } else {
      return "a15:" + 0;
    }
  } catch (d) {
    return "a15:e";
  }
}

幸运的是,我们继续使用一种更简单的检查方式,即确保 VexTrio 代码不是在 phantomjs 上下文中执行的。PhantomJS 是一个无头 Web 浏览器,可使用 JavaScript 编写脚本。为了确保 VexTrio 不在 phantomjs 的上下文中执行,代码会检查自己的 DOM 是否包含以下任何属性:

  • callPhantom:PhantomJS 曾经提供的一种方法,用于从脚本中调用本机代码。
  • _phantom:一个较旧的、现已弃用的属性,曾经被 PhantomJS 使用过。
  • phantom:表示 PhantomJS 脚本 API。

如果存在其中任何一个,布尔值“true”将被记录到返回的数组中。否则,将记录值“false”。然后,.some() 函数用于执行函数,以防任何返回值为 true。在这种情况下,检查失败,因为 VexTrio 记录了使用 phantomjs 的明显仿真迹象。
否则检查通过 (a15:0)

检查 16:DETECT_BROWSERAUTOMATION_via_domElements

function DETECT_BROWSERAUTOMATION_via_domElements() {
  try {
    var c = ["__webdriver_evaluate", "__selenium_evaluate", "__webdriver_script_function", "__webdriver_script_func", "__webdriver_script_fn", "__fxdriver_evaluate", "__driver_unwrapped", "__webdriver_unwrapped", "__driver_evaluate", "__selenium_unwrapped", "__fxdriver_unwrapped"];
    var d = ["webdriver", "_phantom", "__nightmare", "_selenium", "callPhantom", "callSelenium", "_Selenium_IDE_Recorder", "__stopAllTimers"];
    for (var f in d) {
      var g = d[f];
      if (window[g]) {
        return "a16:" + 1;
      }
    }
    ;
    for (var h in c) {
      var i = c[h];
      if (window.document[i]) {
        return "a16:" + 1;
      }
    }
    ;
    try {
      if (window.external && window.external.toString() && window.external.toString().indexOf("Sequentum") != -1) {
        return "a16:" + 1;
      }
      if (window.document.documentElement.getAttribute("selenium")) {
        return "a16:" + 1;
      }
      if (window.document.documentElement.getAttribute("webdriver")) {
        return "a16:" + 1;
      }
      if (window.document.documentElement.getAttribute("driver")) {
        return "a16:" + 1;
      }
    } catch (j) {
      "a16:" + 0;
    }
    return "a16:" + 0;
  } catch (k) {
    return "a16:e";
  }
}

下一个指纹识别函数检查是否存在各种窗口和 dom 属性,这些属性可能指示浏览器自动化的使用。这包括对 Selenium、Webdriver、PhantomJS 和Nightmare 的检查。为此,第一步是检查数组中是否有任何值:

["webdriver", "_phantom", "__nightmare", "_selenium", "callPhantom", "callSelenium", "_Selenium_IDE_Recorder", "__stopAllTimers"];

作为窗口属性存在。如果是这样,则检查失败,因为所有这些都是自动化的明显迹象。人们可能会问自己,为什么还要进行检查 15,因为除了一个 PhantomJS 检测值之外,所有值都在检查 16 中。但我想有人只是在这里复制粘贴代码,而没有真正考虑过它。

然后,检查继续另一个“坏字符串”数组:

["__webdriver_evaluate", "__selenium_evaluate", "__webdriver_script_function", "__webdriver_script_func", "__webdriver_script_fn", "__fxdriver_evaluate", "__driver_unwrapped", "__webdriver_unwrapped", "__driver_evaluate", "__selenium_unwrapped", "__fxdriver_unwrapped"];

这一次,请确保这些都不显示为保存在 windows.document 对象中的 DOM 副本的属性。同样,所有值都与前面提到的自动化工具相关联。如果存在任何检查,则检查失败并返回 a16:1。

在完成这些相当简单的基于字符串的检查后,还需要进行 4 次检查。
第一个检查是否存在 window.external 属性,该属性在转换为字符串时包含单词:“Sequentum”。这可能是试图阻止自动化解决方案和 https://www.sequentum.com/ 网页抓取。

然后,我们分别查看对值“selenium”、“webdriver”和“driver”的检查。对于每个值,指纹检查可确保它不会作为网页根元素(通常是第一个 <html> 标记)的属性给出。被检测到的一个例子是:

<!DOCTYPE html>
<html lang="en" driver="true">
<head>
    <title>Sample Page with Driver Attribute</title>
</head>
<body>
    <header>
        <h1>Welcome to My Page</h1>
    </header>
    <main>
        <p>This is a simple HTML page to demonstrate the "driver" attribute on the root element.</p>
    </main>
</body>
</html>

同样,如果存在任何属性,则检查失败(a16:1)。
如果前面描述的检查均未失败,则检查通过。

此检查的所有部分都是众所周知的。一些提到:

  • https://medium.com/@browserscan/browser-fingerprints-101-automation-detection-322ac81156ae
  • https://chris124567.github.io/2020-07-22-reverse-engineering-distil-networks-antibot-javascript/

检查 17:DETECT_PHANTOMAS

function DETECT_PHANTOMAS() {
  try {
    function c() {
      return ["__phantomas" in window];
    }
    result = c().some(function (d) {
      return d;
    });
    if (result) {
      return "a17:" + 1;
    } else {
      return "a17:" + 0;
    }
  } catch (d) {
    return "a17:e";
  }
}

好吧,第三次是一种魅力。与 Check15 和 Check16 相同的方法用于检查字符串“__phantomas”是否存在作为窗口属性。如果是,则检查失败,如果否,则通过检查。
这是 phantomas 工具的检测方法。

检查 18:DETECT_Selenium_DOM_based

function DETECT_Selenium_DOM_based() {
  try {
    for (var c in window.document) {
      if (c.match(/$[a-z]dc_/) && window.document[c].cache_) {
        return "a18:" + 1;
      }
    }
    return "a18:" + 0;
  } catch (d) {
    return "a18:e";
  }
}

这个检查有点难以理解,但这主要是由于使用了正则表达式,而且我对它可能要搜索的内容缺乏了解。然而,整体功能非常简单。VexTrio 使用正则表达式搜索 window.document 的所有属性。如果属性的名称与正则表达式匹配,并且该属性具有子属性.cache_则检查失败。(检查的第二部分提示将搜索到的属性假定为对象本身。

现在的困难是要理解正则表达式实际上在寻找什么。
正则表达式匹配以“$”符号开头的字符串,后跟任何小写字母 a-z,后跟“dc_”。

好吧,我还没有在浏览器指纹识别的上下文中真正提到这个正则表达式。我敢肯定,在某个地方可以找到这个秘密的答案,但谷歌并不是搜索正则表达式的最佳搜索引擎。
无论如何,我问了一些对 JavaScript 有更多了解的人,他们认为这很可能是检测 Selenium 浏览器自动化工具包的另一种方法。

检查 19:DETECT_NodeJS_Buffer

function DETECT_NodeJS_Buffer() {
  try {
    if (window.Buffer !== undefined) {
      return "a19:" + 1;
    } else {
      return "a19:" + 0;
    }
  } catch (c) {
    return "a19:e";
  }
}

检查 19 很简单。如果窗口。定义了缓冲区属性,检查失败,访问者在VexTrio眼中是不合法的。

谷歌搜索窗口。与 JavaScript 相关的缓冲区返回许多暗示 NodeJS 框架的结果。因此,我认为此检查旨在检测 NodeJS 框架和使用 NodeJS 打开 VexTrio 登录页面的访问者,暗示研究人员、浏览器自动化或网络抓取框架使用 NodeJS。

检查 20:DETECT_Chromium_based_automation_driver

function DETECT_Chromium_based_automation_driver() {
  try {
    if (window.domAutomation || window.domAutomationController) {
      return "a20:" + 1;
    } else {
      return "a20:" + 0;
    }
  } catch (c) {
    return "a20:e";
  }
}

检查 20 通过检查是否存在 window.domAutomation 或 window.domAutomationController 属性来检测 Web 驱动程序的存在。如果存在这两者中的任何一个,则浏览器可能是自动的,并且检查失败,否则将通过 (a20:0)。

检查21:CHECK_setTimeout_Integrity&&检查22:CHECK_setInterval_Integrity

function CHECK_setTimeout_Integrity() {
  try {
    if (setTimeout.toString().replace(/s+/g, "") !== "function setTimeout() { [native code] }".replace(/s/g, "")) {
      return "a21:" + 1;
    } else {
      return "a21:" + 0;
    }
  } catch (c) {
    return "a21:e";
  }
}

function CHECK_setInterval_Integrity() {
try {
if (setInterval.toString().replace(/s+/g, "") !== "function setInterval() { [native code] }".replace(/s/g, "")) {
return "a22:" + 1;
} else {
return "a22:" + 0;
}
} catch (c) {
return "a22:e";
}
}

另一个检查基于 Check13 中所述的 toString() 方法。这次用于检查 setTimeout 函数是否未纵。这可能会检测到任何使用 setTimout() 函数的虚拟化解决方案或自动化框架。

Check 22 中的 setInterval 函数也是如此。

如果任何函数未返回预期的 { native code } 字符串,则检查失败。

检查 23:CHECK_XMLHTTPRequest_1和检查 24:CHECK_XMLHTTPRequest_2

function CHECK_XMLHTTPRequest_1() {
  try {
    var c = "kl";
    var f = "IsCO";
    var g = "RSRequest";
    var h = c + f + g;
    if (window.XMLHttpRequest.prototype.open.toString().indexOf(h) !== -1) {
      return "a42:" + 1;
    } else {
      return "a42:" + 0;
    }
  } catch (i) {
    return "a42:e";
  }
}
function CHECK_XMLHTTPRequest_2() {
  try {
    var c = "klI";
    var d = "sCOR";
    var f = "SRequest";
    var g = c + d + f;
    if (window.XMLHttpRequest.prototype.send.toString().indexOf(g) !== -1) {
      return "a43:" + 1;
    } else {
      return "a43:" + 0;
    }
  } catch (h) {
    return "a43:e";
  }
}

接下来的两项检查涉及 XMLHttpRequests 的处理。更具体地说,它们确保 XMLHttpRequests 的 open() 和 send() 方法在转换为字符串时不包含值“klISCORSRequest”。现在,虽然我可以告诉你,XMLHttpRequests 是一种在已经加载的页面上动态加载 WebContent 的方法。我还可以告诉你,send() 方法用于向服务器发送请求,而 open() 方法用于初始化或重新打开请求。我可以告诉你,CORS是跨域资源共享的缩写,它与如何以及是否允许服务器从外部HTTP请求访问内容有关。可悲的是,我无法告诉您的是“klISCORSRequest”属性的使用位置。谷歌搜索该术语会导致零结果。CORS 协议和 XMLHttpRequests 的官方文档也没有引用它。唯一可以肯定的是,如果在 XMLHttpRequest send() 和 open() 方法的属性中遇到“klISCORSRequest”,那么 VexTrio 不喜欢它。相应的检查失败,返回 a42:1/a43:1。

旁注:您可能已经注意到,此时 VexTrio 突然停下来按数字顺序计算支票。VexTrio 没有继续检查 23 和 24,而是使用检查 42 和 43。对我来说,这表明这些指纹检查很可能是从外部来源复制粘贴的。我们将在本文的结论部分回到这一点。

检查 25:CHECK_UserAgent_not_automation

function CHECK_UserAgent_not_automation() {
  try {
    var c = "phantomjs";
    var d = "headless";
    var f = "avira";
    var g = "googleweblight";
    var h = navigator.userAgent.toLowerCase();
    if (h.indexOf(c) !== -1 || h.indexOf(d) !== -1 || h.indexOf(f) !== -1 || h.indexOf(g) !== -1) {
      return "a60:" + 1;
    } else {
      return "a60:" + 0;
    }
  } catch (i) {
    return "a60:e";
  }
}

另一个可以很容易地集成到以前使用受害者的 Web 请求 UserAgent 的检查中的检查中。
这一次,VexTrio 确保其中没有找到字符串“phantomjs”、“headless”、“avira”和“googleweblight”。

特此

  • phantomjs:表示使用 PhantomJS 实现浏览器自动化
  • headless:表示浏览器在无头模式下运行(因此没有任何用户 UI,这是浏览器自动化中的常见情况
  • avira:表示已知安全供应商 Avira 发出的 Web 请求。我的工作理论是,使用 Avira 安全插件的浏览器可能会发送此类请求。
  • googleweblight:对 GoogleWebLight 代理服务的检查(已于 2022 年停产)。这种检查可能完全没用。

如果在 UserAgent 中遇到这些字符串中的任何一个,则检查 25 (60) 失败,返回 a60:1,如果检查通过,则函数返回 a60:0。

检查 26:CHECK_STACKTRACE_Behavior

function CHECK_STACKTRACE_Behavior() {
  var c;
  var d;
  try {
    document.createElement(0);
  } catch (e) {
    try {
      d = e.stack.split("n");
      c = d.length >= 2 ? !!d[1].match(/Ob[cej]{3}t.a[lp]{3}y[(< ]{3}an[oynm]{5}us>/) : true;
    } catch (f) {}
    if (c) {
      return "a78:" + 1;
    } else {
      return "a78:" + 0;
    }
  }
}

从技术角度来看,这项检查非常酷。这是一种使用额外隐身插件检测 Puppeter的方法,或多或少直接来自 Github Issue:
https://github.com/berstend/puppeteer-extra/issues/318#issuecomment-699700974
,该问题已被修复。
基本上,检查的工作原理如下:

  1. 进行了故意错误的 document.createElement() 调用。
    通过将 Integer 作为 createElement 需要字符串的参数传递,该函数会遇到错误。
  2. 第一个错误例程用于捕获错误并评估堆栈跟踪,当浏览器遇到 JavaScript 错误时,通常会包含该跟踪。堆栈跟踪被拆分为几行。然后,如果有两行以上,则在正则表达式上检查第二行是否匹配 “/Ob[cej]{3}t.a[lp]{3}y[(< ]{3}an[oynm]{5}us>/”
    这与返回的错误完全匹配,其中堆栈已被上述插件操作。
  3. 如果正则表达式在返回超过 2 行 Stacktrace 的条件下匹配,则指纹确实导致发现自动化并且测试失败 (a78:1),否则它通过。

我相信这是 VexTrio 武器库中最有趣的检查之一。使用 Stacktraces 通过故意引发错误来识别浏览器自动化是需要记住的。

检查 27:CHECK_NumberofLogicalProcessors_IOS

function CHECK_NumberofLogicalProcessors_IOS() {
  try {
    if (CHECK_DEVICE_IN_USERAGENT_STRING() === "iOS" && window.navigator.hardwareConcurrency !== undefined && window.navigator.hardwareConcurrency > 4) {
      return "a86:" + 1;
    } else {
      return "a86:" + 0;
    }
  } catch (c) {
    return "a86:e";
  }
}

让我们回到一些更简单的检查。检查 27 是基于逻辑处理器数量的指纹。如果设备是使用 iOS 操作系统的设备,并且定义了 window.navigator.hardwareConcurrency 属性,则不允许返回大于 4 的值。根据我的研究,自从 A11 Apple 处理器以来,可能从 iPhone 8 开始的所有 iPhone 都有 6 个或更多逻辑处理器。奇怪的是,iPhone 8 是在 Safari 放弃对 hardwareConcurrency() 方法的支持的同一年发布的。因此,虽然我最初希望此检查能够阻止某些 iOS 用户作为合法用户通过,但我认为由于 iPhone 8 试图以这种方式打印 Safari 用户很可能会导致他们仍然作为受害者被传递,因为 hardwareConcurrency() 方法可能会返回未定义。
至少,如果您在 iOS 上使用 Chrome 和 iPhone 8 及更高版本,您很可能会失败并被整理出更恶意的有效负载。

无论如何,如果您通过检查并且看起来像无辜的猎物,则该函数返回 A86:0,否则它是 A86:1。

检查 28:CHECK_Browser_VoiceList

function CHECK_Browser_VoiceList() {
  var c = CHECK_DEVICE_IN_USERAGENT_STRING();
  if (voiceslist.toLowerCase().indexOf("lekha") !== -1 && (c.indexOf("Win") !== -1 || c === "Kaios" || c === "Android" || c === "Linux")) {
    return "a89:" + 1;
  } else {
    return "a89:" + 0;
  }
}

还记得语音列表变量 VexTrio 之前填写了一些检查吗?好吧,这里将再次使用。从本质上讲,似乎有一个用于浏览器的语音包,称为“lekha”。它仅在 Apple 设备下使用,如果用户代理说您是 Windows、Kaios、Linux 或 Android 用户,则在语音列表中遇到它意味着有问题。因此,如果此语音包可用,并且您使用的是上述任何非苹果设备,则无法通过检查 (a89:1)。否则你就过去了(a89:0)。

检查 29:CHECK_VirtualBox

function CHECK_VirtualBox() {
  try {
    var c = HELPER_WEBGL_DEBUG_TOKENS_FOR_RENDERER_AND_VENDOR();
    if (c[1].indexOf("VirtualBox") !== -1) {
      return "a92:1";
    } else {
      return "a92:0";
    }
  } catch (d) {
    return "a92:e";
  }
}

我们终于完成了最后一次检查。它再次利用前面提到的 WebGL 调试值技巧来获取显卡渲染器和供应商。然后检查显卡供应商的值是否出现字符串“VirtualBox”。如果找到它,VexTrio 假定它是在从虚拟机运行的浏览器中打开的。这足以让访问者看起来不够无辜,以至于成为他们更具欺诈性的有效载荷的受害者。因此,检查失败(a92:1)。如果在显卡供应商字符串中未发现“VirtualBox”,则通过检查 (a92:0)。

结论

总之,VexTrio 利用 29 种不同的功能来检查访问受感染网页的访问者/受害者的合法性。然后,本文开头给出的一个中心功能负责评估指纹识别结果。如果前面描述的任何检查的返回值以“:1”结尾,则该分析的结果为 a0:1。否则,如果所有检查都已通过,则为 a0:0,如果分析函数无法正确执行,则为 a0:e。然后将最终结果转换为 JSON 对象,经过 AES CBC 加密(密钥位于剩余的 VexTrio 源代码中),并作为令牌值发送到服务器。然后,服务器负责评估指纹结果以及剩余的 VexTrio 脚本发送的一些附加参数。由于服务器端代码处于隐藏状态,因此无法说出在服务器端执行了哪些步骤。但是,由于这些隐藏机制,服务器将使用进一步的JavaScript进行响应,将受害者重定向到各种骗局和广告。

就我个人而言,我相信从前面的分析中可以得出一些有趣的结论。首先,看到 VexTrio 用于对现代 Web 浏览器进行指纹识别的不同方法非常有趣。VexTrio所做的许多检查也可能被不同的恶意软件家族使用。

根据不同指纹识别方法的呈现方式,我假设它们中的大多数实际上是从第三方来源复制粘贴的。虽然我能够为某些检查证明这一点(参见:检查 26),但我还没有找到包含上述所有技术的来源。如果有人发现一个开源脚本,请不要犹豫,伸出援手。

我注意到的另一件事是上述指纹识别方法中使用的功能量,这些方法在Mozilla开发人员文档中被标记为“已弃用”。我在 Check 6 的分析中证明了这一点。总的来说,如果所有浏览器供应商都跟进弃用状态,则 29 项检查中有 7 项将停止工作。

此外,WebGL 文档中还明确提到了检索检查 10 和 29 中使用的 WebGL 渲染调试变量的安全隐患。
这似乎是“可用性”与“安全性”问题的真实例子。

图 7:WebGL 调试令牌问题(来源:registry.khronos.org)

我总是觉得看到这样的决策问题以及如何处理它们很有趣。在这种情况下,VexTrio中的指纹识别是一个更大的辩论中的负面例子。我不想在这里指责,而是强调要确定安全性和隐私是否比可用性更重要并不总是那么容易,并且上述指纹识别方法为 Web 开发社区所熟知。

但现在,让我们重新写这篇文章。我希望你在途中学到了一些东西,并喜欢阅读。谁知道呢,也许你们中的一些人甚至会摆弄给定的代码示例,以发现其他指纹识别方法或如何应对它们?:)
我一如既往地对任何形式的反馈感到高兴,并对改进我工作的建议持开放态度。

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年3月28日11:42:48
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   VexTrio的浏览器指纹识别https://cn-sec.com/archives/2610345.html

发表评论

匿名网友 填写信息