Skip to content

Quickstart

TL;DR#

Release#

Get the latest release at https://github.com/bonjourmalware/melody/releases.

make install            # Set default outfacing interface
make cap                # Set network capabilities to start Melody without elevated privileges
make certs              # Make self signed certs for the HTTPS fileserver
make default_rules      # Enable the default rules
make service            # Create a systemd service to restart the program automatically and launch it at startup 
                        # Note that the script expects that you've installed Melody in /opt/melody

sudo systemctl stop melody  # Stop the service while we're configuring it

Update the filter.bpf file to filter out unwanted packets.

sudo systemctl start melody     # Start Melody
sudo systemctl status melody    # Check that Melody is running    

The logs should start to pile up in /opt/melody/logs/melody.ndjson.

tail -f /opt/melody/logs/melody.ndjson # | jq

From source#

git clone https://github.com/bonjourmalware/melody /opt/melody
cd /opt/melody
make build

Then continue with the steps from the release TL;DR.

Docker#

make certs                        # Make self signed certs for the HTTPS fileserver
make default_rules                # Enable the default rules
mkdir -p /opt/melody/logs
cd /opt/melody/

docker pull bonjourmalware/melody:latest

MELODY_CLI="" # Put your CLI options here. Example : export MELODY_CLI="-s -i 'lo' -F 'dst port 5555' -o 'server.http.port: 5555'"

docker run \
    --net=host \
    -e "MELODY_CLI=$MELODY_CLI" \
    --mount type=bind,source="$(pwd)/filter.bpf",target=/app/filter.bpf,readonly \
    --mount type=bind,source="$(pwd)/config.yml",target=/app/config.yml,readonly \
    --mount type=bind,source="$(pwd)/var",target=/app/var,readonly \
    --mount type=bind,source="$(pwd)/rules",target=/app/rules,readonly \
    --mount type=bind,source="$(pwd)/logs",target=/app/logs/ \
    bonjourmalware/melody

The logs should start to pile up in /opt/melody/logs/melody.ndjson.

Before we start#

Hi !

You'll need the following info :

  • The name of the interface on which you want Melody to listen to
    • wlp3s0, ens3, enp0s25, eth0...
  • All the IP addresses to exclude from monitoring

Tip

You will most likely want to ban at least the address from which you access the server and the addresses used for monitoring (don't forget your hosting provider's).

Clone the repo to get the default rules and configuration files :

git clone https://github.com/bonjourmalware/melody

Tip

You can also write the configuration files later or use CLI options to use it as a standalone binary

Firewall#

Don't forget to check your firewall to ensure you're not blocking packets from reaching the sensor.

Tip

You might want to disengage your hosting provider DDoS protection.

Build from source#

Warning

You need the libpcap headers before building Melody. Install them with :

sudo apt update
sudo apt install libpcap-dev

Build Melody with :

make build

or

go build -ldflags="-s -w -extldflags=-static" -o melody
sudo setcap cap_net_raw,cap_setpcap=ep ./melody

Grab a release#

You can grab the latest release by visiting https://github.com/bonjourmalware/melody/releases/latest.

Docker#

You can also use Docker and pull the image from Docker Hub :

docker pull bonjourmalware/melody:latest

Don't forget to create the logs directory, or the mount will fail :

mkdir -p /opt/melody/logs

Run it with :

make docker_run

or

export MELODY_CLI="" # Put your CLI options here. Example : export MELODY_CLI="-s -i 'lo' -F 'dst port 5555' -o 'server.http.port: 5555'"

docker run \
    --net=host \
    -e "MELODY_CLI=$MELODY_CLI" \
    --mount type=bind,source="$(pwd)/filter.bpf",target=/app/filter.bpf,readonly \
    --mount type=bind,source="$(pwd)/config.yml",target=/app/config.yml,readonly \
    --mount type=bind,source="$(pwd)/var",target=/app/var,readonly \
    --mount type=bind,source="$(pwd)/rules",target=/app/rules,readonly \
    --mount type=bind,source="$(pwd)/logs",target=/app/logs/ \
    melody

Configuration#

Melody configuration#

All the available configuration options are listed with their default values in the config.yml file.

You'll want to look at a few things before getting started :

  • Set the listen.interface to the one on which you want Melody to be listening on

Tip

On most recent linux distribution, you can run route | grep '^default' | grep -o '[^ ]*$' to find the default WAN card. Note that you'll need the net-tools package (sudo apt install net-tools) in order to use the route command.

Tip

On Windows, you'll want an interface name like \Device\NPF_{4E273621-5161-46C8-895A-48D0E52A0B83}.

If you find an interface name with TCP in place of NPF, try swaping both.

See Find Windows interfaces for more details.

Don't forget to wrap your string with ' to prevent the parsing of the escaping \ :

interface: '\Device\NPF_{4E273621-5161-46C8-895A-48D0E52A0B83}'

Note

Note that Melody listen on lo by default. You can override the listening interface with the -i switch.

  • The dummy HTTP/S servers are enabled by default. Disable it if you're not interested by this kind of data, or you're putting Melody next to a web application

  • Default rules are disabled by default. You can enable them by creating a symlink for each rule in the active rule directory specified in the configuration file ($melody/rules/rules-enabled by default)

Tip

To create a symlink, use the following command from the root of the projet :

ln -rs ./rules/rules-available/$rule.yml ./rules/rules-enabled/

Use a wildcard to enable all the rulesets :

ln -rs ./rules/rules-available/*.yml ./rules/rules-enabled/

HTTP/S server#

In order to capture the full HTTP transactions, the client must have a server to connect to. To ease that process, a dummy HTTP/S server is available.

The default configuration is to answer 200 OK on every routes, along with a Server: Apache header.

iptables#

To capture the HTTP traffic your server receives on every ports, I recommend using iptables to redirect the data from every ports to the one Melody is listening on.

Danger

Be very careful while applying these modifications. You must at least exclude your remote connection port using the ! --dports switch, or you will be locked out.

To achieve this, add a rule to your PREROUTING table :

sudo iptables -A PREROUTING -t nat -i $INTERFACE -p tcp -m multiport ! --dports $REMOTE_ACCESS_PORT,$ANOTHER_EXCLUDED_PORT -j REDIRECT --to-port $MELODY_HTTP_PORT

Example :

sudo iptables -A PREROUTING -t nat -i ens3 -p tcp -m multiport ! --dports 1234,5678 -j REDIRECT --to-port 10800

Here the ports 1234 and 5678 have been excluded from the redirection.

Note

Using the sudo iptables -t nat -L PREROUTING -n -v command, you should see something like this :

Chain PREROUTING (policy ACCEPT 1226K packets, 57M bytes)
 pkts bytes target     prot opt in     out     source               destination         
  25M 1243M REDIRECT   tcp  --  ens3   *       0.0.0.0/0            0.0.0.0/0            multiport dports  !1234,5678 redir ports 10800

Important

This is only used to virtually connect the HTTP server to all the ports.

As Melody sits on the data link layer, the program will receive the packets before being handled by network layer programs such as iptables or ufw.

Note

I won't cover it here as I'm now knowledgeable enough on this, but you'll want to look at advfirewall and the portproxy command to replicate this on Windows.

Berkley Packet Filter (BPF)#

The next step is the customization of the filter.bpf file. This is where you filter the data that reaches Melody.

By default, only inbound traffic is allowed and all 127.0.0.0/24 subnet are banned.

Note

You can use the -F|--filter switch to set a filter via CLI.

Tip

If you're using a VPS, you might need to filter out the IP addresses your hosting provider uses to check the status of your server.

Source IP filtering#

Use [src|dst] net <network> to filter packets according to their IP.

Example :

inbound and not net 127.0.0.1

You can specify a range to exclude using the CIDR notation :

inbound and not net 127.0.0.0/24

Port filtering#

Use [src|dst] port <port> to filter packets according to their port.

Example :

not port 1234

You can specify a range to exclude using the CIDR notation and the portrange keyword :

not portrange 1234-5678

Your filter.bpf should look like this :

inbound
and not port 1234
and not net 127.0.0.0/24
and not net 192.0.2.1

Important

Your file should always start with the inbound keyword. I recommend adding your filter rules below, starting with an and keyword.

Advanced#

Here is all you need to know about the BPF syntax and here is a great source of examples to get quickly started.

Rules#

Melody rules are used to apply tags on matching packets. They have multiple use cases, such as monitoring emerging threats, automated droppers, vulnerability scanners, etc.

You can look into the $melody/rules/rule-available and $melody/internal/rules/test_resources folders to quickly find working examples.

Basics#

A rule file can contain multiple rules description and constitute a ruleset.

Here is a rule example that detects CVE-2020-14882 (Oracle Weblogic RCE) scans or exploitation attempts by matching either of the two URI and the HTTP verb :

CVE-2020-14882 Oracle Weblogic Server RCE:
  layer: http
  meta:
    id: 3e1d86d8-fba6-4e15-8c74-941c3375fd3e
    version: 1.0
    author: BonjourMalware
    status: stable
    created: 2020/11/07
    modified: 2020/20/07
    description: "Checking or trying to exploit CVE-2020-14882"
    references:
      - "https://nvd.nist.gov/vuln/detail/CVE-2020-14882"
  match:
    http.uri:
      startswith|any|nocase:
        - "/console/css/"
        - "/console/images"
      contains|any|nocase:
        - "console.portal"
        - "consolejndi.portal?test_handle="
  tags:
    cve: "cve-2020-14882"
    vendor: "oracle"
    product: "weblogic"
    impact: "rce"

This rule is part of the default server.yml ruleset and tag CVE-2020-14882-related packets.

If a packet match, its tags will be appended to the matches field in the packet's log.

See the Rules section for details on the rules syntax.

Important

You must generate a new UUIDv4 for each rule you create.

Output#

Stdout#

You can redirect the output to stdout by using the -s switch.

Example

{
  "tcp": {
    "window": 512,
    "seq": 1906765553,
    "ack": 2514263732,
    "data_offset": 8,
    "flags": "PA",
    "urgent": 0,
    "payload": {
      "content": "I made a discovery today. I found a computer.\n",
      "base64": "SSBtYWRlIGEgZGlzY292ZXJ5IHRvZGF5LiAgSSBmb3VuZCBhIGNvbXB1dGVyLgo=",
      "truncated": false
    }
  },
  "ip": {
    "version": 4,
    "ihl": 5,
    "tos": 0,
    "length": 99,
    "id": 39114,
    "fragbits": "DF",
    "frag_offset": 0,
    "ttl": 64,
    "protocol": 6
  },
  "timestamp": "2020-11-16T15:50:01.277828+01:00",
  "session": "bup9368o4skolf20rt8g",
  "type": "tcp",
  "src_ip": "127.0.0.1",
  "dst_port": 1234,
  "matches": {},
  "inline_matches": [],
  "embedded": {}
}

Dump#

A dump mode is also available with the -d|--dump switch. Similar to tcpdump, it will print raw packets to stdout instead of Melody json lines.

Example

PACKET: 76 bytes, wire length 76 cap length 76 @ 2020-11-16 15:46:00.927899 +0100 CET
- Layer 1 (14 bytes) = Ethernet {Contents=[..14..] Payload=[..62..] SrcMAC=00:00:00:00:00:00 DstMAC=00:00:00:00:00:00 EthernetType=IPv4 Length=0}
- Layer 2 (20 bytes) = IPv4     {Contents=[..20..] Payload=[..42..] Version=4 IHL=5 TOS=0 Length=62 Id=39110 Flags=DF FragOffset=0 TTL=64 Protocol=TCP Checksum=41969 SrcIP=127.0.0.1 DstIP=127.0.0.1 Options=[] Padding=[]}
- Layer 3 (32 bytes) = TCP      {Contents=[..32..] Payload=[..10..] SrcPort=58766 DstPort=1234(search-agent) Seq=1906765476 Ack=2514263732 DataOffset=8 FIN=false SYN=false RST=false PSH=true ACK=true URG=false ECE=false CWR=false NS=false Window=512 Checksum=65074 Urgent=0 Options=[TCPOption(NOP:), TCPOption(NOP:), TCPOption(Timestamps:1712590417/1712586943 0x66140e51661400bf)] Padding=[]}
- Layer 4 (10 bytes) = Payload  10 byte(s)

Note

All the console messages are printed to stderr in order to allow piping Melody's data into jq.