diff --git a/modules/can/can.go b/modules/can/can.go index a96ecaa3..df12de7a 100644 --- a/modules/can/can.go +++ b/modules/can/can.go @@ -79,6 +79,15 @@ func NewCanModule(s *session.Session) *CANModule { return mod.Inject(args[0]) })) + mod.AddHandler(session.NewModuleHandler("can.fuzz ID_OR_NODE_NAME", `(?i)^can\.fuzz\s+(.+)$`, + "If an integer frame ID is specified, create a randomized version of it and inject it. If a node name is specified, a random message for the given node will be instead used.", + func(args []string) error { + if !mod.Running() { + return errors.New("can module not running") + } + return mod.Fuzz(args[0]) + })) + return mod } diff --git a/modules/can/can_fuzz.go b/modules/can/can_fuzz.go new file mode 100644 index 00000000..19888b8e --- /dev/null +++ b/modules/can/can_fuzz.go @@ -0,0 +1,93 @@ +package can + +import ( + "context" + "fmt" + "math/rand" + "strconv" + "strings" + "time" + + "github.com/dustin/go-humanize" + "go.einride.tech/can" + "go.einride.tech/can/pkg/descriptor" +) + +func (mod *CANModule) Fuzz(id string) error { + rncSource := rand.NewSource(time.Now().Unix()) + rng := rand.New(rncSource) + + // let's try as number first + frameID, err := strconv.Atoi(id) + dataLen := 0 + frameData := ([]byte)(nil) + + if err != nil { + if mod.dbc != nil { + // not a number, use as node name + fromSender := make([]*descriptor.Message, 0) + for _, msg := range mod.dbc.Messages { + if msg.SenderNode == id { + fromSender = append(fromSender, msg) + } + } + + if len(fromSender) == 0 { + return fmt.Errorf("no messages defined in DBC file for node %s", id) + } + + idx := rng.Intn(len(fromSender)) + selected := fromSender[idx] + mod.Info("selected %s > (%d) %s", id, selected.ID, selected.Name) + frameID = int(selected.ID) + } else { + return err + } + } + + // if we have a DBC + if mod.dbc != nil { + if message, found := mod.dbc.Message(uint32(frameID)); found { + mod.Info("found as %s", message.Name) + + dataLen = int(message.Length) + frameData = make([]byte, dataLen) + if _, err := rand.Read(frameData); err != nil { + return err + } + } else { + avail := []string{} + for _, msg := range mod.dbc.Messages { + avail = append(avail, fmt.Sprintf("%d (%s)", msg.ID, msg.Name)) + } + return fmt.Errorf("message with id %d not found in DBC file, available ids: %v", frameID, strings.Join(avail, ", ")) + } + } else { + dataLen = rng.Intn(int(can.MaxDataLength)) + frameData = make([]byte, dataLen) + + if _, err := rand.Read(frameData); err != nil { + return err + } + + mod.Warning("no can.dbc_path is set, creating frame with %d bytes of random data", dataLen) + } + + frame := can.Frame{ + ID: uint32(frameID), + Length: uint8(dataLen), + IsRemote: false, + IsExtended: false, + } + + copy(frame.Data[:], frameData) + + mod.Info("injecting %s of CAN frame %d ...", + humanize.Bytes(uint64(frame.Length)), frame.ID) + + if err := mod.send.TransmitFrame(context.Background(), frame); err != nil { + return err + } + + return nil +} diff --git a/modules/can/can_recon.go b/modules/can/can_recon.go index 53f1955f..77ecef8d 100644 --- a/modules/can/can_recon.go +++ b/modules/can/can_recon.go @@ -1,7 +1,6 @@ package can import ( - "context" "errors" "fmt" "os" @@ -57,7 +56,7 @@ func (mod *CANModule) Configure() error { mod.Warning("no can.dbc_path specified, messages won't be parsed") } - if mod.conn, err = socketcan.DialContext(context.Background(), mod.transport, mod.deviceName); err != nil { + if mod.conn, err = socketcan.Dial(mod.transport, mod.deviceName); err != nil { return err } @@ -67,6 +66,52 @@ func (mod *CANModule) Configure() error { return nil } +func (mod *CANModule) onFrame(frame can.Frame) { + msg := Message{ + Frame: frame, + } + + // if we have a DBC database + if mod.dbc != nil { + // if the database contains this message id + if message, found := mod.dbc.Message(frame.ID); found { + msg.Name = message.Name + + // find source full info in DBC nodes + sourceName := message.SenderNode + sourceDesc := "" + if sender, found := mod.dbc.Node(message.SenderNode); found { + sourceName = sender.Name + sourceDesc = sender.Description + } + + // add CAN source if new + _, msg.Source = mod.Session.CAN.AddIfNew(sourceName, sourceDesc, frame.Data[:]) + + msg.Signals = make(map[string]string) + + // parse signals + for _, signal := range message.Signals { + var value string + + if signal.Length <= 32 && signal.IsFloat { + value = fmt.Sprintf("%f", signal.UnmarshalFloat(frame.Data)) + } else if signal.Length == 1 { + value = fmt.Sprintf("%v", signal.UnmarshalBool(frame.Data)) + } else if signal.IsSigned { + value = fmt.Sprintf("%d", signal.UnmarshalSigned(frame.Data)) + } else { + value = fmt.Sprintf("%d", signal.UnmarshalUnsigned(frame.Data)) + } + + msg.Signals[signal.Name] = str.Trim(fmt.Sprintf("%s %s", value, signal.Unit)) + } + } + } + + mod.Session.Events.Add("can.message", msg) +} + func (mod *CANModule) Start() error { if err := mod.Configure(); err != nil { return err @@ -77,44 +122,7 @@ func (mod *CANModule) Start() error { for mod.recv.Receive() { frame := mod.recv.Frame() - msg := Message{ - Frame: frame, - } - - if mod.dbc != nil { - if message, found := mod.dbc.Message(frame.ID); found { - msg.Name = message.Name - - sourceName := message.SenderNode - sourceDesc := "" - if sender, found := mod.dbc.Node(message.SenderNode); found { - sourceName = sender.Name - sourceDesc = sender.Description - } - - _, msg.Source = mod.Session.CAN.AddIfNew(sourceName, sourceDesc, frame.Data[:]) - - msg.Signals = make(map[string]string) - - for _, signal := range message.Signals { - var value string - - if signal.Length <= 32 && signal.IsFloat { - value = fmt.Sprintf("%f", signal.UnmarshalFloat(frame.Data)) - } else if signal.Length == 1 { - value = fmt.Sprintf("%v", signal.UnmarshalBool(frame.Data)) - } else if signal.IsSigned { - value = fmt.Sprintf("%d", signal.UnmarshalSigned(frame.Data)) - } else { - value = fmt.Sprintf("%d", signal.UnmarshalUnsigned(frame.Data)) - } - - msg.Signals[signal.Name] = str.Trim(fmt.Sprintf("%s %s", value, signal.Unit)) - } - } - } - - mod.Session.Events.Add("can.message", msg) + mod.onFrame(frame) } }) } diff --git a/modules/can/can_show.go b/modules/can/can_show.go index a1735f6a..eafc3736 100644 --- a/modules/can/can_show.go +++ b/modules/can/can_show.go @@ -1,6 +1,7 @@ package can import ( + "fmt" "time" "github.com/bettercap/bettercap/v2/network" @@ -27,6 +28,7 @@ func (mod *CANModule) getRow(dev *network.CANDevice) []string { return []string{ dev.Name, dev.Description, + fmt.Sprintf("%d", dev.Frames), humanize.Bytes(dev.Read), seen, } @@ -40,7 +42,7 @@ func (mod *CANModule) Show() (err error) { rows = append(rows, mod.getRow(dev)) } - tui.Table(mod.Session.Events.Stdout, []string{"Name", "Description", "Data", "Seen"}, rows) + tui.Table(mod.Session.Events.Stdout, []string{"Name", "Description", "Frames", "Data", "Seen"}, rows) if len(rows) > 0 { mod.Session.Refresh() diff --git a/network/can_device.go b/network/can_device.go index f1894f69..ff474a95 100644 --- a/network/can_device.go +++ b/network/can_device.go @@ -11,6 +11,7 @@ type CANDevice struct { LastSeen time.Time Name string Description string + Frames uint64 Read uint64 } @@ -18,7 +19,8 @@ type canDeviceJSON struct { LastSeen time.Time `json:"last_seen"` Name string `json:"name"` Description string `json:"description"` - Read uint64 `json:"description"` + Frames uint64 `json:"frames"` + Read uint64 `json:"read"` } func NewCANDevice(name string, description string, payload []byte) *CANDevice { @@ -27,6 +29,7 @@ func NewCANDevice(name string, description string, payload []byte) *CANDevice { Name: name, Description: description, Read: uint64(len(payload)), + Frames: 1, } return dev @@ -41,6 +44,7 @@ func (dev *CANDevice) MarshalJSON() ([]byte, error) { Name: dev.Name, Description: dev.Description, Read: dev.Read, + Frames: dev.Frames, } return json.Marshal(doc) @@ -54,4 +58,6 @@ func (dev *CANDevice) AddPayload(payload []byte) { if payload != nil && sz > 0 { dev.Read += uint64(sz) } + + dev.Frames += 1 }