Compare commits

..

No commits in common. "master" and "v2.41.1" have entirely different histories.

12 changed files with 54 additions and 217 deletions

View file

@ -99,7 +99,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Download Artifacts - name: Download Artifacts
uses: actions/download-artifact@v5 uses: actions/download-artifact@v4
with: with:
pattern: release-artifacts-* pattern: release-artifacts-*
merge-multiple: true merge-multiple: true

View file

@ -16,8 +16,6 @@
<a href="https://github.com/bettercap/bettercap/actions/workflows/test-on-macos.yml"><img alt="Tests on macOS" src="https://github.com/bettercap/bettercap/actions/workflows/test-on-macos.yml/badge.svg"></a> <a href="https://github.com/bettercap/bettercap/actions/workflows/test-on-macos.yml"><img alt="Tests on macOS" src="https://github.com/bettercap/bettercap/actions/workflows/test-on-macos.yml/badge.svg"></a>
<a href="https://github.com/bettercap/bettercap/actions/workflows/test-on-windows.yml"><img alt="Tests on Windows" src="https://github.com/bettercap/bettercap/actions/workflows/test-on-windows.yml/badge.svg"></a> <a href="https://github.com/bettercap/bettercap/actions/workflows/test-on-windows.yml"><img alt="Tests on Windows" src="https://github.com/bettercap/bettercap/actions/workflows/test-on-windows.yml/badge.svg"></a>
<a href="https://hub.docker.com/r/bettercap/bettercap"><img alt="Docker Hub" src="https://img.shields.io/docker/v/bettercap/bettercap?logo=docker"></a> <a href="https://hub.docker.com/r/bettercap/bettercap"><img alt="Docker Hub" src="https://img.shields.io/docker/v/bettercap/bettercap?logo=docker"></a>
<img src="https://img.shields.io/badge/human-coded-brightgreen?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBjbGFzcz0ibHVjaWRlIGx1Y2lkZS1wZXJzb24tc3RhbmRpbmctaWNvbiBsdWNpZGUtcGVyc29uLXN0YW5kaW5nIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjUiIHI9IjEiLz48cGF0aCBkPSJtOSAyMCAzLTYgMyA2Ii8+PHBhdGggZD0ibTYgOCA2IDIgNi0yIi8+PHBhdGggZD0iTTEyIDEwdjQiLz48L3N2Zz4=" alt="This project is 100% made by humans."/>
</p> </p>
</p> </p>

View file

@ -2,7 +2,7 @@ package core
const ( const (
Name = "bettercap" Name = "bettercap"
Version = "2.41.4" Version = "2.41.1"
Author = "Simone 'evilsocket' Margaritelli" Author = "Simone 'evilsocket' Margaritelli"
Website = "https://bettercap.org/" Website = "https://bettercap.org/"
) )

View file

@ -90,12 +90,12 @@ func NewRestAPI(s *session.Session) *RestAPI {
"Value of the Access-Control-Allow-Origin header of the API server.")) "Value of the Access-Control-Allow-Origin header of the API server."))
mod.AddParam(session.NewStringParameter("api.rest.username", mod.AddParam(session.NewStringParameter("api.rest.username",
"user", "",
"", "",
"API authentication username.")) "API authentication username."))
mod.AddParam(session.NewStringParameter("api.rest.password", mod.AddParam(session.NewStringParameter("api.rest.password",
"pass", "",
"", "",
"API authentication password.")) "API authentication password."))

View file

@ -31,20 +31,20 @@ func NewHttpServer(s *session.Session) *HttpServer {
mod.AddParam(session.NewStringParameter("http.server.address", mod.AddParam(session.NewStringParameter("http.server.address",
session.ParamIfaceAddress, session.ParamIfaceAddress,
session.IPv4Validator, session.IPv4Validator,
"Address to bind the HTTP server to.")) "Address to bind the http server to."))
mod.AddParam(session.NewIntParameter("http.server.port", mod.AddParam(session.NewIntParameter("http.server.port",
"80", "80",
"Port to bind the HTTP server to.")) "Port to bind the http server to."))
mod.AddHandler(session.NewModuleHandler("http.server on", "", mod.AddHandler(session.NewModuleHandler("http.server on", "",
"Start HTTP server.", "Start httpd server.",
func(args []string) error { func(args []string) error {
return mod.Start() return mod.Start()
})) }))
mod.AddHandler(session.NewModuleHandler("http.server off", "", mod.AddHandler(session.NewModuleHandler("http.server off", "",
"Stop HTTP server.", "Stop httpd server.",
func(args []string) error { func(args []string) error {
return mod.Stop() return mod.Stop()
})) }))

View file

@ -35,11 +35,11 @@ func NewHttpsServer(s *session.Session) *HttpsServer {
mod.AddParam(session.NewStringParameter("https.server.address", mod.AddParam(session.NewStringParameter("https.server.address",
session.ParamIfaceAddress, session.ParamIfaceAddress,
session.IPv4Validator, session.IPv4Validator,
"Address to bind the HTTPS server to.")) "Address to bind the http server to."))
mod.AddParam(session.NewIntParameter("https.server.port", mod.AddParam(session.NewIntParameter("https.server.port",
"443", "443",
"Port to bind the HTTPS server to.")) "Port to bind the http server to."))
mod.AddParam(session.NewStringParameter("https.server.certificate", mod.AddParam(session.NewStringParameter("https.server.certificate",
"~/.bettercap-httpd.cert.pem", "~/.bettercap-httpd.cert.pem",
@ -54,13 +54,13 @@ func NewHttpsServer(s *session.Session) *HttpsServer {
tls.CertConfigToModule("https.server", &mod.SessionModule, tls.DefaultLegitConfig) tls.CertConfigToModule("https.server", &mod.SessionModule, tls.DefaultLegitConfig)
mod.AddHandler(session.NewModuleHandler("https.server on", "", mod.AddHandler(session.NewModuleHandler("https.server on", "",
"Start HTTPS server.", "Start https server.",
func(args []string) error { func(args []string) error {
return mod.Start() return mod.Start()
})) }))
mod.AddHandler(session.NewModuleHandler("https.server off", "", mod.AddHandler(session.NewModuleHandler("https.server off", "",
"Stop HTTPS server.", "Stop https server.",
func(args []string) error { func(args []string) error {
return mod.Stop() return mod.Stop()
})) }))

View file

@ -16,13 +16,15 @@ import (
"github.com/evilsocket/islazy/fs" "github.com/evilsocket/islazy/fs"
) )
type hookFunc func(q *nfqueue.Nfqueue, a nfqueue.Attribute) int
type PacketProxy struct { type PacketProxy struct {
session.SessionModule session.SessionModule
chainName string chainName string
rule string rule string
queue *nfqueue.Nfqueue queue *nfqueue.Nfqueue
queueNum int queueNum int
queueCb func(q *nfqueue.Nfqueue, a nfqueue.Attribute) int queueCb hookFunc
pluginPath string pluginPath string
plugin *plugin.Plugin plugin *plugin.Plugin
} }
@ -149,7 +151,7 @@ func (mod *PacketProxy) Configure() (err error) {
return return
} else if sym, err = mod.plugin.Lookup("OnPacket"); err != nil { } else if sym, err = mod.plugin.Lookup("OnPacket"); err != nil {
return return
} else if mod.queueCb, ok = sym.(func(q *nfqueue.Nfqueue, a nfqueue.Attribute) int); !ok { } else if mod.queueCb, ok = sym.(hookFunc); !ok {
return fmt.Errorf("Symbol OnPacket is not a valid callback function.") return fmt.Errorf("Symbol OnPacket is not a valid callback function.")
} }

View file

@ -1,7 +1,6 @@
package tcp_proxy package tcp_proxy
import ( import (
"encoding/json"
"net" "net"
"strings" "strings"
@ -56,36 +55,12 @@ func (s *TcpProxyScript) OnData(from, to net.Addr, data []byte, callback func(ca
log.Error("error while executing onData callback: %s", err) log.Error("error while executing onData callback: %s", err)
return nil return nil
} else if ret != nil { } else if ret != nil {
return toByteArray(ret) array, ok := ret.([]byte)
} if !ok {
} log.Error("error while casting exported value to array of byte: value = %+v", ret)
return nil
}
func toByteArray(ret interface{}) []byte {
// this approach is a bit hacky but it handles all cases
// serialize ret to JSON
if jsonData, err := json.Marshal(ret); err == nil {
// attempt to deserialize as []float64
var back2Array []float64
if err := json.Unmarshal(jsonData, &back2Array); err == nil {
result := make([]byte, len(back2Array))
for i, num := range back2Array {
if num >= 0 && num <= 255 {
result[i] = byte(num)
} else {
log.Error("array element at index %d is not a valid byte value %d", i, num)
return nil
}
} }
return result return array
} else {
log.Error("failed to deserialize %+v to []float64: %v", ret, err)
} }
} else {
log.Error("failed to serialize %+v to JSON: %v", ret, err)
} }
return nil return nil
} }

View file

@ -1,169 +0,0 @@
package tcp_proxy
import (
"net"
"testing"
"github.com/evilsocket/islazy/plugin"
)
func TestOnData_NoReturn(t *testing.T) {
jsCode := `
function onData(from, to, data, callback) {
// don't return anything
}
`
plug, err := plugin.Parse(jsCode)
if err != nil {
t.Fatalf("Failed to parse plugin: %v", err)
}
script := &TcpProxyScript{
Plugin: plug,
doOnData: plug.HasFunc("onData"),
}
from := &net.TCPAddr{IP: net.ParseIP("192.168.1.1"), Port: 1234}
to := &net.TCPAddr{IP: net.ParseIP("192.168.1.2"), Port: 5678}
data := []byte("test data")
result := script.OnData(from, to, data, nil)
if result != nil {
t.Errorf("Expected nil result when callback returns nothing, got %v", result)
}
}
func TestOnData_ReturnsArrayOfIntegers(t *testing.T) {
jsCode := `
function onData(from, to, data, callback) {
// Return modified data as array of integers
return [72, 101, 108, 108, 111]; // "Hello" in ASCII
}
`
plug, err := plugin.Parse(jsCode)
if err != nil {
t.Fatalf("Failed to parse plugin: %v", err)
}
script := &TcpProxyScript{
Plugin: plug,
doOnData: plug.HasFunc("onData"),
}
from := &net.TCPAddr{IP: net.ParseIP("192.168.1.1"), Port: 1234}
to := &net.TCPAddr{IP: net.ParseIP("192.168.1.2"), Port: 5678}
data := []byte("test data")
result := script.OnData(from, to, data, nil)
expected := []byte("Hello")
if result == nil {
t.Fatal("Expected non-nil result when callback returns array of integers")
}
if len(result) != len(expected) {
t.Fatalf("Expected result length %d, got %d", len(expected), len(result))
}
for i, b := range result {
if b != expected[i] {
t.Errorf("Expected byte at index %d to be %d, got %d", i, expected[i], b)
}
}
}
func TestOnData_ReturnsDynamicArray(t *testing.T) {
jsCode := `
function onData(from, to, data, callback) {
var result = [];
for (var i = 0; i < data.length; i++) {
result.push((data[i] + 1) % 256);
}
return result;
}
`
plug, err := plugin.Parse(jsCode)
if err != nil {
t.Fatalf("Failed to parse plugin: %v", err)
}
script := &TcpProxyScript{
Plugin: plug,
doOnData: plug.HasFunc("onData"),
}
from := &net.TCPAddr{IP: net.ParseIP("192.168.1.1"), Port: 1234}
to := &net.TCPAddr{IP: net.ParseIP("192.168.1.2"), Port: 5678}
data := []byte{10, 20, 30, 40, 255}
result := script.OnData(from, to, data, nil)
expected := []byte{11, 21, 31, 41, 0} // 255 + 1 = 256 % 256 = 0
if result == nil {
t.Fatal("Expected non-nil result when callback returns array of integers")
}
if len(result) != len(expected) {
t.Fatalf("Expected result length %d, got %d", len(expected), len(result))
}
for i, b := range result {
if b != expected[i] {
t.Errorf("Expected byte at index %d to be %d, got %d", i, expected[i], b)
}
}
}
func TestOnData_ReturnsMixedArray(t *testing.T) {
jsCode := `
function charToInt(value) {
return value.charCodeAt()
}
function onData(from, to, data) {
st_data = String.fromCharCode.apply(null, data)
if( st_data.indexOf("mysearch") != -1 ) {
payload = "mypayload";
st_data = st_data.replace("mysearch", payload);
res_int_arr = st_data.split("").map(charToInt) // []uint16
res_int_arr[0] = payload.length + 1; // first index is float64 and rest []uint16
return res_int_arr;
}
return data;
}
`
plug, err := plugin.Parse(jsCode)
if err != nil {
t.Fatalf("Failed to parse plugin: %v", err)
}
script := &TcpProxyScript{
Plugin: plug,
doOnData: plug.HasFunc("onData"),
}
from := &net.TCPAddr{IP: net.ParseIP("192.168.1.1"), Port: 1234}
to := &net.TCPAddr{IP: net.ParseIP("192.168.1.6"), Port: 5678}
data := []byte("Hello mysearch world")
result := script.OnData(from, to, data, nil)
expected := []byte("\x0aello mypayload world")
if result == nil {
t.Fatal("Expected non-nil result when callback returns array of integers")
}
if len(result) != len(expected) {
t.Fatalf("Expected result length %d, got %d", len(expected), len(result))
}
for i, b := range result {
if b != expected[i] {
t.Errorf("Expected byte at index %d to be %d, got %d", i, expected[i], b)
}
}
}

View file

@ -43,7 +43,7 @@ func NewTicker(s *session.Session) *Ticker {
})) }))
mod.AddHandler(session.NewModuleHandler("ticker off", "", mod.AddHandler(session.NewModuleHandler("ticker off", "",
"Stop the main ticker.", "Stop the maint icker.",
func(args []string) error { func(args []string) error {
return mod.Stop() return mod.Stop()
})) }))

View file

@ -265,8 +265,8 @@ func NewWiFiModule(s *session.Session) *WiFiModule {
mod.AddHandler(probe) mod.AddHandler(probe)
channelSwitchAnnounce := session.NewModuleHandler("wifi.channel_switch_announce BSSID CHANNEL ", `wifi\.channel_switch_announce ((?:[a-fA-F0-9:]{11,}))\s+((?:[0-9]+))`, channelSwitchAnnounce := session.NewModuleHandler("wifi.channel_switch_announce bssid channel ", `wifi\.channel_switch_announce ((?:[a-fA-F0-9:]{11,}))\s+((?:[0-9]+))`,
"Start a 802.11 channel hop attack, all client will be forced to change the channel lead to connection down.", "Start a 802.11 channel hop attack, all client will be force to change the channel lead to connection down.",
func(args []string) error { func(args []string) error {
bssid, err := net.ParseMAC(args[0]) bssid, err := net.ParseMAC(args[0])
if err != nil { if err != nil {

View file

@ -518,6 +518,37 @@ func TestWiFiModuleProbe(t *testing.T) {
} }
} }
func TestWiFiModuleChannelSwitchAnnounce(t *testing.T) {
sess := createMockSession()
mod := NewWiFiModule(sess)
// Test CSA handler
handlers := mod.Handlers()
var csaHandler session.ModuleHandler
for _, h := range handlers {
if h.Name == "wifi.channel_switch_announce bssid channel " {
csaHandler = h
break
}
}
if csaHandler.Name == "" {
t.Fatal("CSA handler not found")
}
// Test with valid parameters
err := csaHandler.Exec([]string{"aa:bb:cc:dd:ee:ff", "11"})
if err == nil {
t.Error("Expected error when running CSA without running module")
}
// Test with invalid channel
err = csaHandler.Exec([]string{"aa:bb:cc:dd:ee:ff", "999"})
if err == nil {
t.Error("Expected error with invalid channel")
}
}
func TestWiFiModuleFakeAuth(t *testing.T) { func TestWiFiModuleFakeAuth(t *testing.T) {
sess := createMockSession() sess := createMockSession()
mod := NewWiFiModule(sess) mod := NewWiFiModule(sess)