forked from zmap/zgrab2
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
RicYaben
committed
Jul 17, 2024
1 parent
571bf96
commit bfa7b6d
Showing
9 changed files
with
755 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package modules | ||
|
||
import ( | ||
"github.com/zmap/zgrab2/modules/coap" | ||
) | ||
|
||
func init() { | ||
coap.RegisterModule() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
package coap | ||
|
||
import ( | ||
"bytes" | ||
"net" | ||
"slices" | ||
"time" | ||
|
||
"github.com/zmap/zgrab2" | ||
"github.com/zmap/zgrab2/modules/coap/message" | ||
) | ||
|
||
type Result struct { | ||
path string | ||
messages []*message.Message | ||
} | ||
|
||
type Probe struct { | ||
header []byte | ||
timeout time.Duration | ||
results []*Result | ||
decoder message.Decoder | ||
conn net.Conn | ||
} | ||
|
||
func (p *Probe) Do(path string) *zgrab2.ScanError { | ||
u := p.getUriPath(path) | ||
pkt := slices.Concat(p.header, u) | ||
msgs, err := p.handle(pkt) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
res := &Result{ | ||
path: path, | ||
messages: msgs, | ||
} | ||
p.results = append(p.results, res) | ||
return nil | ||
} | ||
|
||
func (p *Probe) handle(packet []byte) ([]*message.Message, *zgrab2.ScanError) { | ||
msgs := []*message.Message{} | ||
for b := 0; ; b++ { | ||
msg, err := p.handleBlock(packet, b) | ||
if err != nil { | ||
return nil, err | ||
} | ||
msgs = append(msgs, msg) | ||
block := msg.GetBlock() | ||
if block == nil || !block.More { | ||
return msgs, nil | ||
} | ||
} | ||
} | ||
|
||
func (p *Probe) handleBlock(packet []byte, block int) (*message.Message, *zgrab2.ScanError) { | ||
if b := p.getBlock(block); len(b) > 0 { | ||
packet = append(packet, b...) | ||
} | ||
|
||
if err := p.conn.SetReadDeadline(time.Now().Add(p.timeout)); err != nil { | ||
zgrab2.NewScanError(zgrab2.SCAN_UNKNOWN_ERROR, err) | ||
} | ||
|
||
if _, err := p.conn.Write(packet); err != nil { | ||
return nil, zgrab2.DetectScanError(err) | ||
} | ||
|
||
buf := make([]byte, 1024) | ||
n, err := p.conn.Read(buf) | ||
if err != nil { | ||
return nil, zgrab2.DetectScanError(err) | ||
} | ||
|
||
msg := message.NewMessage() | ||
if err := p.decoder.Decode(buf[:n], msg); err != nil { | ||
return nil, zgrab2.NewScanError(zgrab2.SCAN_APPLICATION_ERROR, err) | ||
} | ||
return msg, nil | ||
} | ||
|
||
func (p *Probe) getUriPath(path string) []byte { | ||
const uriOption int = 11 | ||
|
||
if path == "" { | ||
panic("empty path not allowed") | ||
} | ||
|
||
if path == "/" { | ||
// Option delta and length for an empty path segment | ||
option := (uriOption << 4) | ||
return []byte{byte(option)} | ||
} | ||
|
||
// Transform to bytes and separate by the URI separator | ||
var buf bytes.Buffer | ||
paths := bytes.Split([]byte(path), []byte("/")) | ||
|
||
// Include option delta number, length and value | ||
option := (uriOption << 4) + len(paths[0]) | ||
buf.WriteByte(byte(option)) | ||
buf.Write(paths[0]) | ||
|
||
// Extend the option with length and value | ||
for _, p := range paths[1:] { | ||
buf.WriteByte(byte(len(p))) | ||
buf.Write(p) | ||
} | ||
return buf.Bytes() | ||
} | ||
|
||
func (p *Probe) getBlock(n int) []byte { | ||
if n < 1 { | ||
return []byte{} | ||
} | ||
|
||
var buf bytes.Buffer | ||
b := (12 << 4) + 1 // 193 | ||
c := (n << 4) + 3 | ||
buf.WriteByte(byte(b)) | ||
buf.WriteByte(byte(c)) | ||
return buf.Bytes() | ||
} | ||
|
||
type ProbeBuilder struct { | ||
decoder message.Decoder | ||
header []byte | ||
timeout time.Duration | ||
} | ||
|
||
func (b *ProbeBuilder) Build(conn net.Conn) *Probe { | ||
p := &Probe{ | ||
header: b.header, | ||
decoder: b.decoder, | ||
timeout: b.timeout, | ||
conn: conn, | ||
results: []*Result{}, | ||
} | ||
return p | ||
} | ||
|
||
func (b *ProbeBuilder) setHeader() *ProbeBuilder { | ||
b.header = []byte{ | ||
0x40, // CoAP version and type (version: 1, type: Confirmable) | ||
0x01, // CoAP code (GET) | ||
0x12, 0x34, // Message ID (0x0001) | ||
} | ||
return b | ||
} | ||
|
||
func (b *ProbeBuilder) setDecoder() *ProbeBuilder { | ||
b.decoder = message.NewDecoder() | ||
return b | ||
} | ||
|
||
func (b *ProbeBuilder) setTimeout(t time.Duration) *ProbeBuilder { | ||
b.timeout = t | ||
return b | ||
} | ||
|
||
func newProbeBuilder(timeout time.Duration) *ProbeBuilder { | ||
b := new(ProbeBuilder) | ||
b.setHeader() | ||
b.setDecoder() | ||
b.setTimeout(timeout) | ||
return b | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package coap | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/zmap/zgrab2" | ||
) | ||
|
||
type coapTester struct { | ||
port int | ||
expectedStatus zgrab2.ScanStatus | ||
} | ||
|
||
func (t *coapTester) getScanner() (*Scanner, error) { | ||
var module Module | ||
flags := module.NewFlags().(*Flags) | ||
flags.Port = uint(t.port) | ||
|
||
flags.Paths = "\".well-known/core\",\"/\"" | ||
flags.PathsDelimiter = "," | ||
flags.Timeout = 10 * time.Second | ||
flags.Port = uint(t.port) | ||
|
||
scanner := module.NewScanner() | ||
if err := scanner.Init(flags); err != nil { | ||
return nil, err | ||
} | ||
|
||
return scanner.(*Scanner), nil | ||
} | ||
|
||
func (t *coapTester) runTest(test *testing.T, name string) { | ||
scanner, err := t.getScanner() | ||
if err != nil { | ||
test.Fatalf("[%s] Unexpected error: %v", name, err) | ||
} | ||
|
||
target := zgrab2.ScanTarget{ | ||
Domain: "coap.me", | ||
} | ||
|
||
status, ret, err := scanner.Scan(target) | ||
if status != t.expectedStatus { | ||
test.Errorf("[%s] Wrong status: expected %s, got %s", name, t.expectedStatus, status) | ||
} | ||
|
||
if err != nil { | ||
test.Errorf("[%s] Unexpected error: %v", name, err) | ||
} | ||
|
||
if ret == nil { | ||
test.Errorf("[%s] Got empty response", name) | ||
} | ||
} | ||
|
||
var tests = map[string]*coapTester{ | ||
"success": { | ||
port: 5683, | ||
expectedStatus: zgrab2.SCAN_SUCCESS, | ||
}, | ||
} | ||
|
||
func TestCoAP(t *testing.T) { | ||
for tname, cfg := range tests { | ||
cfg.runTest(t, tname) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package message | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
) | ||
|
||
const ( | ||
OptionBlock1 = 27 // CoAP Block1 Option Number | ||
OptionBlock2 = 23 // CoAP Block2 Option Number | ||
OptionContentFormat = 12 // CoAP Content-Format Option Number | ||
) | ||
|
||
var ( | ||
ErrMalformedMessage = errors.New("malformed message") | ||
ErrInvalidValue = errors.New("invalid value") | ||
ErrSmallBuffer = errors.New("buffer too small") | ||
) | ||
|
||
type Decoder interface { | ||
Decode([]byte, *Message) error | ||
} | ||
|
||
type decoder struct{} | ||
|
||
func NewDecoder() Decoder { | ||
return &decoder{} | ||
} | ||
|
||
func (d *decoder) Decode(data []byte, msg *Message) error { | ||
var buf = new(bytes.Buffer) | ||
buf.Write(data) | ||
|
||
var header = new(Header) | ||
if err := header.Unmarshal(buf); err != nil { | ||
return err | ||
} | ||
msg.Header = header | ||
|
||
var opts = make(Options) | ||
if err := opts.Unmarshal(buf); err != nil { | ||
return err | ||
} | ||
msg.Options = opts | ||
|
||
var payload = new(Payload) | ||
cType := uint16(opts[OptionContentFormat].Value[0]) | ||
if err := payload.Unmarshal(buf, cType); err != nil { | ||
return err | ||
} | ||
msg.Payload = payload | ||
|
||
for _, oID := range []uint16{OptionBlock1, OptionBlock2} { | ||
if opt, ok := opts[oID]; ok { | ||
msg.block = d.makeBlock(opt) | ||
break | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (d *decoder) makeBlock(opt *Option) *Block { | ||
v := int(opt.Value[0]) | ||
return &Block{ | ||
Number: v >> 4, | ||
More: v&0x08 > 0, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package message | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
) | ||
|
||
type Block struct { | ||
Number int | ||
More bool | ||
} | ||
|
||
type Header struct { | ||
MessageID int `json:"message-id"` | ||
Version int `json:"version"` | ||
Type int `json:"type"` | ||
TokenLen int `json:"token-len"` | ||
Token []byte `json:"token"` | ||
Code string `json:"code"` | ||
} | ||
|
||
func NewHeader() *Header { | ||
return &Header{ | ||
MessageID: -1, | ||
Type: -1, | ||
} | ||
} | ||
|
||
func (h *Header) Unmarshal(buf *bytes.Buffer) error { | ||
d := make([]byte, 4) | ||
if _, err := buf.Read(d); err != nil { | ||
return err | ||
} | ||
|
||
h.Version = int(d[0] >> 6) | ||
h.Type = int((d[0] >> 4) & 0x03) | ||
h.TokenLen = int(d[0] & 0x0F) | ||
h.Code = h.getCode(d[1]) | ||
h.MessageID = int(d[2])<<8 | int(d[3]) | ||
h.Token = make([]byte, h.TokenLen) | ||
_, err := buf.Read(h.Token) | ||
return err | ||
} | ||
|
||
func (h *Header) getCode(v byte) string { | ||
// Extract class and detail from the hex value | ||
class := int(v >> 5) // upper 3 bits | ||
detail := int(v & 0x1F) // lower 5 bits | ||
return fmt.Sprintf("%d.%02d", class, detail) | ||
} | ||
|
||
type Message struct { | ||
Header *Header `json:"header"` | ||
Payload *Payload `json:"payload"` | ||
Options Options `json:"options"` | ||
|
||
block *Block | ||
} | ||
|
||
func NewMessage() *Message { | ||
return &Message{ | ||
Header: NewHeader(), | ||
Options: make(Options), | ||
} | ||
} | ||
|
||
func (m *Message) GetBlock() *Block { | ||
return m.block | ||
} |
Oops, something went wrong.