Monitors

Monitors are components that monitor the target for specific behaviour. A monitor can be passive and just observe and provide data or behave more actively, interacting directly with the target. Some monitors also have the capability to start, stop and restart targets.

Detecting a crash or misbehaviour of your target can be a complex, non-straight forward process depending on the tools you have available on your targets host; this holds true especially for embedded devices. Fuzzungus provides three main monitor implementations:

  • ProcessMonitor, a Monitor that collects debug info from process on Windows and Unix. It also can restart the target process and detect segfaults.

  • NetworkMonitor, a Monitor that passively captures network traffic via PCAP and attaches it to the testcase log.

  • CallbackMonitor, which is used to implement the callbacks that can be supplied to the Session class.

  • BusyboxMonitor, a Monitor that collects debug info from process on Busybox.

Monitor Interface (BaseMonitor)

class boofuzz.monitors.BaseMonitor(*args, **kwargs)[source]

Bases: object

Interface for Target monitors. All Monitors must adhere to this specification.

Added in version 0.2.0.

alive(*args, **kwargs)[source]

Called when a Target containing this Monitor is added to a session. Use this function to connect to e.g. RPC hosts if your target lives on another machine.

You MUST return True if the monitor is alive. You MUST return False otherwise. If a Monitor is not alive, this method will be called until it becomes alive or throws an exception. You SHOULD handle timeouts / connection retry limits in the monitor implementation.

Defaults to return True.

Returns:

Bool

get_crash_synopsis()[source]

Called if any monitor indicates that the current testcase has failed, even if this monitor did not detect a crash. You SHOULD return a human- readable representation of the crash synopsis (e.g. hexdump). You MAY save the full crashdump somewhere.

Returns:

str

post_send(target=None, fuzz_data_logger=None, session=None)[source]

Called after the current fuzz node is transmitted. Use it to collect data about a target and decide whether it crashed.

You MUST return True if the Target is still alive. You MUST return False if the Target crashed. If one Monitor reports a crash, the whole testcase will be marked as crashing.

Defaults to return True.

Returns:

Bool

post_start_target(target=None, fuzz_data_logger=None, session=None)[source]

Called after a target is started or restarted.

pre_send(target=None, fuzz_data_logger=None, session=None)[source]

Called before the current fuzz node is transmitted.

Defaults to no effect.

Returns:

None

restart_target(target=None, fuzz_data_logger=None, session=None)[source]

Restart a target. Must return True if restart was successful, False if it was unsuccessful or this monitor cannot restart a Target, which causes the next monitor in the chain to try to restart.

The first successful monitor causes the restart chain to stop applying.

Defaults to call stop and start, return True if successful.

Returns:

Bool

retrieve_data()[source]

Called to retrieve data independent of whether the current fuzz node crashed the target or not. Called before the fuzzer proceeds to a new testcase.

You SHOULD return any auxiliary data that should be recorded. The data MUST be serializable, e.g. bytestring.

Defaults to return None.

set_options(*args, **kwargs)[source]

Called to set options for your monitor (e.g. local crash dump storage). *args and **kwargs can be explicitly specified by implementing classes, however you SHOULD ignore any kwargs you do not recognize.

Defaults to no effect.

Returns:

None

start_target(*args, **kwargs)[source]

Starts a target. You MUST return True if the start was successful. You MUST return False if not. Monitors will be tried to start the target in the order they were added to the Target; the first Monitor to succeed breaks iterating.

Returns:

Bool

stop_target()[source]

Stops a target. You MUST return True if the stop was successful. You MUST return False if not. Monitors will be tried to stop the target in the order they were added to the Target; the first Monitor to succeed breaks iterating.

Returns:

Bool

BusyboxMonitor

class boofuzz.monitors.BusyboxMonitor(target_ip: str, target_port: int, processes_to_monitor: list[str], procmon_path: str, cpu_percentage_threshold: int = 80, mem_percentage_threshold: int = 80, target_user: str = '', target_password: str = '', ssh_command_timeout: int = 2)[source]

Bases: BaseMonitor

This Class aims to implement the python side of an external monitor written in bash, designed for a simple machine like a busybox. It is able to communicate in SSH.

The data sent by the procmon is a JSON, with the following structure :

{
    "cpu_count": 8,
    "total_mem": 16054272,
    "total_swap": 999420,
    "kernel_version": "6.1.0-21-amd64",
    "system_cpu": "0.581154",
    "system_uptime": "9157.06",
    "system_mem_total": 16054272,
    "system_mem_used": 4894352,
    "system_mem_free": 6958548,
    "system_mem_available": 9385460,
    "system_mem_shared": 1431144,
    "system_mem_buff_cache": 4201372,
    "system_swap_total": 999420,
    "system_swap_used": 0,
    "system_swap_free": 999420,
    "firefox": {
        "2993": {
            "process_cpu": "5.22458112198275910876",
            "process_uptime": "8831.33000000000000000000",
            "process_mem": 1314008,
            "process_command_line": "/usr/lib/firefox-esr/firefox-esr",
            "process_ram": 590748,
            "process_vmem": 4722748
        }
    }
}

With a section for each process to monitor, and a subsection for each PID of the process.

If you want to test manually your SSH connection to debug something, the command should look something like this :

sshpass -p $password ssh $username@$ip "<procmon_path> --ssh -a -n '['$process_name']' -r 1"

Or any other options according to procmon.sh -h :

Usage: ./monitors/procmon.sh [option...]

    Data to monitor
    ===============
        -c, --cpu                   Display CPU information
        -s, --sys                   Display system information
        -m, --mem                   Display memory information
        -u, --up                    Display system uptime
        -a, --all                   Display all information. PID or Name required

    Execution options to monitor
    ============================
        -h, --help                  Display help
        -n, --names <name>          Find processes by name
        -t, --timeout <timeout>     Timeout between rounds
        -r, --rounds                Number of rounds to execute. Infinite by default.
        -v, --verbose               Verbose
        -d, --debug                 Debug mode, wait for user input between rounds

    Network options
    ===============
        -i, --ip <ip>               IP address to send the data to. Localhost by default
        -p, --port <port>           Port to send the data to. 65431 by default
        --udp                       Send data via UDP
        --ssh                       Send data via SSH

    /!\ Warning : UDP communication is not reliable. Use SSH for a more reliable communication

As you can read above, the UDP communication was the initial communication method, but it is not reliable. You have to parse multiple JSON at once in your buffer, you don’t have a lot of feedback, and you can’t communication with the script, even just to close it.

For now the communication is done via SSH, which is more reliable, but slower. We open a SSH connection, send the command, read the output, and close the connection. That way we are sure to have the entire output, and we can communicate with the script to give new commands on another round if necessary. And it closes itself after sending its data.

Also note that the errors at connection (timeout, rights…) are printed to the console, so don’t hesitate to execute by hand the SSH command to debug the connection.

Room for improvement : The busybox monitor currently doesn’t uses all the information given by the procmon script.

For crash detection, it uses :

  • The PIDs of the processes to detect if a PID changed

  • process_uptime to get the uptime of the processes and detect crashes

For abnormal behaviour detection, it uses :

  • system_cpu and cpu_core_count to get the CPU usage of the system

  • system_mem_used and system_mem_total to get the memory usage of the system

  • process_cpu and process_mem to get the CPU and memory usage of the processes

It could use :

  • system_mem_shared, system_mem_buff_cache, system_swap_total, system_swap_used, system_swap_free to get more information about the memory usage of the system

  • process_command_line, process_ram, process_vmem to get more information about the processes

Parameters:
  • target_ip (str) – IP of the target

  • target_port (int) – Port of the target

  • target_user (str) – User of the target to connect to SSH

  • target_password (str) – Password of the target. For SSH connections only. If passed, sshpass is used if present. Otherwise, only ssh is used, allowing the use of ssh keys if they are configured.

  • processes_to_monitor (list[str]) – List of processes to monitor

  • procmon_path (str) – Path to the procmon script on the target

alive(fuzz_data_logger: FuzzLogger = None) bool[source]

Check if the target is still alive.

error_array = ['error', 'fail', 'permission denied', 'No route to host', 'Connection refused', 'invalid', 'not found', 'not exist', 'no such', 'denied', 'failed', 'unable', 'timeout', 'unreachable', 'unavailable', 'unauthorized', 'not allowed', 'not permitted', 'not supported', 'rejected', 'not valid', 'not correct', 'not working', 'not running', 'not started', 'not stopped', 'not connected', 'not found', 'not available', 'not reachable']
get_crash_synopsis() str[source]

Get a synopsis of the crash.

Read self.last_crash_synopsis to get the crash synopsis.

returns: str

post_send(target=None, fuzz_data_logger: FuzzLogger = None, session=None) bool[source]

Called after the current fuzz node is transmitted. Use it to collect data about a target and decide whether it crashed.

If UDP, calls alive() to check if the target is still alive. If SSH, restarts the procmon.

returns: Bool. True if the target is still alive, False if it crashed.

post_start_target(target=None, fuzz_data_logger: FuzzLogger = None, session=None)[source]

Called after a target is started or restarted.

restart_target(target=None, fuzz_data_logger=None, session=None)[source]

Restarts the target. Tries to stop it, then start it again.

start_target(fuzz_data_logger: FuzzLogger = None, *args, **kwargs) bool[source]

Starts the procmon script : - If UDP, sends the command via SSH and creates a socket to receive the data that is sent continuously - If SSH, sends the command via SSH, reads the output in STDOUT, and closes the connection to assure that the entire output is read

returns: Bool. True if the target started successfully, False if there was an error.

stop_target() bool[source]

Stops the procmon script : - If UDP, sends a SIGINT to the procmon script via self.process via SSH, and closes the socket - If SSH, closes the process that opened the SSH connection

returns: True if the target stopped successfully, False if there was an error.

Important

The Target side of this monitor is available in the monitors directory at the root of the project, or just below :

  1#!/bin/bash
  2
  3# UTILS
  4## Function to display help
  5function display_help() {
  6    echo "Usage: $0 [option...]
  7    
  8    Data to monitor
  9    ===============
 10        -c, --cpu                   Display CPU information
 11        -s, --sys                   Display system information
 12        -m, --mem                   Display memory information
 13        -u, --up                    Display system uptime
 14        -a, --all                   Display all information. PID or Name required
 15    
 16    Execution options to monitor
 17    ============================
 18        -h, --help                  Display help
 19        -n, --names <name>          Find processes by name
 20        -t, --timeout <timeout>     Timeout between rounds
 21        -r, --rounds                Number of rounds to execute. Infinite by default.
 22        -v, --verbose               Verbose
 23        -d, --debug                 Debug mode, wait for user input between rounds
 24    
 25    Network options
 26    ===============
 27        -i, --ip <ip>               IP address to send the data to. Localhost by default
 28        -p, --port <port>           Port to send the data to. 65431 by default
 29        --udp                       Send data via UDP
 30        --ssh                       Send data via SSH
 31    
 32    /!\ Warning : UDP communication is not reliable. Use SSH for a more reliable communication."
 33}
 34
 35## Log function to print to cli according to verbose mode
 36log() {
 37    local level="$1"
 38    local data="$2"
 39    if [[ $level -le $verbose ]]; then
 40        echo "$data"
 41    fi
 42}
 43
 44## Escaping json characters
 45json_escape() {
 46    local input_string escaped_string
 47
 48    input_string="$1"
 49
 50    # Escape characters according to the specified rules
 51    escaped_string=$(echo "$input_string" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/\t/\\t/g' -e 's/\n/\\n/g' -e 's/\r/\\r/g' -e 's/\x08/\\b/g' -e 's/\x0C/\\f/g')
 52
 53    echo "$escaped_string"
 54}
 55
 56# Print help if no arguments are provided
 57if [ $# -eq 0 ]; then
 58    display_help
 59    exit 0
 60fi
 61
 62# Array of commands to check
 63commands=("ls" "cat" "grep" "awk" "basename" "read" "nc" "getopt")
 64
 65# Initialize command existence variables
 66is_ls=false
 67is_cat=false
 68is_grep=false
 69is_awk=false
 70is_basename=false
 71is_read=false
 72is_nc=false
 73is_getopt=false
 74
 75# Check that each command exists
 76# TODO: Check for command existance before calling functions
 77for cmd in "${commands[@]}"; do
 78    if command -v "$cmd" &> /dev/null; then
 79        declare "is_$cmd"=true
 80        log 1 "  [+]  Command '$cmd' found."
 81    else
 82        log 0 "  [-] Warning: Required command '$cmd' not found. Some data won't be parsed." >&2
 83    fi
 84done
 85
 86# Constances 
 87maxverbose=2
 88regex_decimal_number='^[0-9]+(\.[0-9]+)?$'
 89
 90# Set variables
 91#pid="0"
 92cpu=false
 93sys=false
 94mem=false
 95up=false
 96names=""
 97verbose=0
 98timeout=0
 99debug=false
100ip=127.0.0.1
101port=65431
102udp=false
103ssh=false
104rounds=-1
105
106# Parse command-line options
107OPTIONS=$(getopt -o n:t:i:p:r:dcsmuavh --long names:,rounds:timeout:,ip:,port:,udp,ssh,debug,cpu,sys,mem,up,all,verbose,help -- "$@")
108
109eval set -- "$OPTIONS"
110
111while true; do
112    case "$1" in
113        -h | --help )
114            display_help
115            exit ;;
116        -a | --all )
117            cpu=true
118            sys=true
119            mem=true
120            up=true
121            shift ;;
122        -c | --cpu )
123            cpu=true
124            shift ;;
125        -s | --sys )
126            sys=true
127            shift ;;
128        -m | --mem )
129            mem=true
130            shift ;;
131        -u | --up )
132            up=true
133            shift ;;
134        -n | --names )
135            names=$2
136            # Remove the leading and trailing brackets
137            names="${names#[}"
138            names="${names%]}"
139            # Remove the quotes around the names
140            names="${names//\"}"
141            names="${names//\'}"
142            IFS=', ' read -r -a names_array <<< "$names"
143            shift 2 ;;
144        -v | --verbose )
145            ((verbose+=1))
146            shift ;;
147        -t | --timeout )
148            timeout=$2
149            if ! [[ $timeout =~ ^[0-9]+$ ]]; then
150                echo "Error: Usage: -t|--timeout <number>" >&2; exit 1
151            fi
152            shift 2 ;;
153        -r | --rounds )
154            rounds=$2
155            if ! [[ $rounds =~ ^[0-9]+$ ]]; then
156                echo "Error: Usage: -r|--rounds <number>" >&2; exit 1
157            fi
158            shift 2 ;;
159        -i | --ip )
160            ip=$2
161            if ! [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
162                echo "Error: Usage: -i|--ip <1.2.3.4>" >&2; exit 1
163            fi
164            shift 2 ;;
165        -p | --port )
166            port=$2
167            if ! [[ $port =~ ^[0-9]{1,5}$ ]]; then
168                echo "Error: Usage: -p|--port <port_number>" >&2; exit 1
169            fi
170            shift 2 ;;
171        -d | --debug )
172            debug=true
173            shift ;;
174        --ssh )
175            ssh=true
176            shift ;;
177        --udp )
178            udp=true
179            shift ;;
180        -- )
181            shift
182            break ;;
183        * )
184            echo "Error: Internal error!" >&2
185            exit 1 ;;
186  esac
187done
188
189# Starting procmon
190log 1 "Starting procmon..."
191
192# Check for -t or -d
193if [ "$timeout" -ne 0 ] && [ $debug = true ]; then
194    log 0 "[-] Error: Can't have -t and -d"
195    exit
196fi
197
198# Check for --ssh or --udp
199if [ $ssh = true ] && [ $udp = true ]; then
200    log 0 "[-] Error: Can't have --udp and --ssh"
201    exit
202fi
203
204
205# Max verbose
206if [ $verbose -ge $maxverbose ] ; then
207    verbose=$maxverbose
208fi
209
210# PID
211## Function to get list of PIDs
212get_list_of_pids() {
213    # Read only folders that have integer name
214    # Each number represents the PID of a running process
215    pid_folders=$(ls /proc/ | grep -E '^[0-9]+$')
216
217    # Print each PID
218    for pid in $pid_folders; do
219        echo "$pid"
220    done
221}
222
223## Get PID list that matches the name
224get_pid_list_by_name() {
225    name="$1"
226
227    local -n pid_array=$2
228
229    for pid_dir in /proc/[0-9]*; do
230        # Extract the PID from the directory path
231        pid=$(basename "$pid_dir")
232
233        # Check if the PID is a directory
234        if [ -d "$pid_dir" ]; then
235            # Check if the comm file exists
236            if [ -f "$pid_dir/comm" ]; then
237                # Read the command from the comm file
238                read_buffer=$(cat "$pid_dir/comm")
239
240                # Check if the command matches the provided process name
241                if [[ "$read_buffer" == *"$name"* ]]; then
242                    # Add the PID to the list of matching PIDs
243                    pid_array+=("$pid")
244                fi
245            fi
246        fi
247    done
248}
249
250## Function to get CPU usage and uptime for a PID
251get_pid_cpu_and_uptime() {
252    local pid u_time s_time cu_time cs_time start_time total_time hz up_time cpu
253    # Check if PID is provided
254    pid=$1
255
256    # Check if the process exists
257    if [ ! -d "/proc/$pid" ]; then
258        echo ""
259        return 1
260    fi
261
262    # Read the stat file
263    read -r -a stats <<< "$(cat /proc/"$pid"/stat)"
264
265    # Extract the necessary fields
266    u_time=${stats[13]}
267    s_time=${stats[14]}
268    cu_time=${stats[15]}
269    cs_time=${stats[16]}
270    start_time=${stats[21]}
271
272    # Calculate total time
273    total_time=$((u_time + s_time + cu_time + cs_time))
274
275    # Get clock ticks per second
276    hz=$(getconf CLK_TCK)
277
278    # Get system uptime
279    up_time=$(cat /proc/uptime | awk '{print $1}')
280
281    # Calculate process uptime and CPU usage
282    uptime=$(awk "BEGIN {print $up_time - $start_time / $hz}")
283    cpu=$(awk "BEGIN {print 100 * ($total_time / $hz) / $uptime}")
284
285    # Print the result
286    echo "$cpu $uptime"
287}
288
289## Function to get memory usage for a PID
290get_pid_mem() {
291    local pid data stack rss size total_mem total_ram total_size
292    pid=$1
293
294    # Check if the process exists
295    if [ ! -e "/proc/$pid/status" ]; then
296        echo "Process got terminated"
297        return 1
298    fi
299
300    # Read the status file
301    while read -r line; do
302        case $line in
303            VmData:*)
304                data=${line#VmData:}
305                data=${data%% kB*}
306                ;;
307            VmStk:*)
308                stack=${line#VmStk:}
309                stack=${stack%% kB*}
310                ;;
311            VmRSS:*)
312                rss=${line#VmRSS:}
313                rss=${rss%% kB*}
314                ;;
315            VmSize:*)
316                size=${line#VmSize:}
317                size=${size%% kB*}
318                ;;
319        esac
320    done < "/proc/$pid/status"
321
322    # Calculate total memory
323    total_mem=$((data + stack))
324    total_ram=$((rss))
325    total_size=$((size))
326
327    # Print the result
328    echo "$total_mem $total_ram $total_size"
329}
330
331## Function to get command line that started the process
332get_pid_command_line() {
333    local pid file_name process_cli
334    pid=$1
335    file_name="/proc/$pid/cmdline"
336
337    if [ ! -f "$file_name" ]; then
338        echo "process got terminated"
339        return 1
340    fi
341
342    # Read the command line and remove the null character
343    process_cli=$(tr -d '\0' < "$file_name")
344    echo "$process_cli"
345}
346
347## Function to get process name
348get_pid_process_name() {
349    local pid file_name process_name
350    pid=$1
351    file_name="/proc/$pid/comm"
352
353    if [ ! -f "$file_name" ]; then
354        echo "process got terminated"
355        return 1
356    fi
357
358    process_name=$(cat "$file_name")
359    echo "$process_name"
360}
361
362# SYSTEM INFOS
363# Steps:
364#   Read the file /proc/cpuinfo till you find the line with “cpu cores” in string.
365#   Split the line based on white space.
366#    The last element of the array will be number of cores.
367#    Multiply it by 2 to get the number of procs.
368
369get_system_infos() {
370    # Get the number of CPUs
371    local cpu_core_count cpu_count total_mem total_swap kernel_version
372    cpu_core_count=$(cat /proc/cpuinfo | awk '/cpu cores/ {print $NF}' | uniq)
373    #cpu_count=$((cpu_core_count/2))
374
375    # Get the total memory
376    total_mem=$(grep MemTotal /proc/meminfo | awk '{print $2}')
377
378    # Get the total swap
379    total_swap=$(grep SwapTotal /proc/meminfo | awk '{print $2}')
380
381    # Get the kernel version
382    kernel_version=$(uname -r)
383    echo "$cpu_core_count $total_mem $total_swap $kernel_version"
384
385
386}
387
388# CPU USAGE
389# Steps:
390#   Read the first line of /proc/stat.
391#   Discard the first word which is always cpu.
392#   Add all of the values in the line to get the total time.
393#   Take difference of change in total time and idle time at 1 second interval.
394#   Divide the idle time( fourth value) by total time to get fraction of idle time
395#   Subtract 1 with the idle time to get percentage of non idle time.
396#   Multiple by 100 to get a current cpu percentage.
397
398## Function to read CPU file
399read_cpu_file() {
400    # Extracting the fourth column ("idle") from /proc/stat
401    local idle_time total_time
402    idle_time=$(grep 'cpu ' /proc/stat | awk '{print $5}')
403    # Extracting the total time from /proc/stat
404    total_time=$(grep 'cpu ' /proc/stat | awk '{print $2+$3+$4+$5+$6+$7+$8}')
405    echo "$idle_time $total_time"
406}
407
408# Function to calculate CPU usage
409calculate_cpu() {
410    local last_idle last_total current_idle current_total del_idle del_total cpu_usage
411    read -r last_idle last_total <<< "$1"
412    read -r current_idle current_total <<< "$2"
413    
414    # Calculating the difference in idle and total times
415    del_idle=$((current_idle - last_idle))
416    del_total=$((current_total - last_total))
417    
418    # Calculating CPU usage percentage
419    cpu_usage=$(awk "BEGIN {print (1 - $del_idle / $del_total) * 100}")
420    echo "$cpu_usage"
421}
422
423# Function to get current CPU usage
424get_current_cpu() {
425    last_stats=$(read_cpu_file)
426    sleep 1
427    current_stats=$(read_cpu_file)
428    cpu_usage=$(calculate_cpu "$last_stats" "$current_stats")
429    echo "$cpu_usage"
430}
431
432# MEMORY USAGE
433# Steps :
434#   Read the /proc/meminfo file.
435#   Find all the parameters : MemTotal, MemFree, Buffers, Cached, SwapCached, SwapTotal, SwapFree, Shmem, SReclaimable
436#   Do calculation
437## Function to extract parameter from /proc/meminfo
438extract_parameter() {
439    parameter=$1
440    value=$(grep "^$parameter" /proc/meminfo | awk '{print $2}')
441    echo "$value"
442}
443
444## Function to calculate memory values
445calculate_memory_values() {
446    MemTotal=$(extract_parameter "MemTotal:")
447    MemFree=$(extract_parameter "MemFree:")
448    Buffers=$(extract_parameter "Buffers:")
449    Cached=$(extract_parameter "Cached:")
450    SwapTotal=$(extract_parameter "SwapTotal:")
451    SwapFree=$(extract_parameter "SwapFree:")
452    shared=$(extract_parameter "Shmem:")
453    Slab=$(extract_parameter "SReclaimable:")
454
455    # Calculating used, free, and buff/cache values
456    used=$((MemTotal - MemFree - Buffers - Cached - Slab))
457    free=$MemFree
458    buff_cache=$((Buffers + Cached + Slab))
459
460    # Extracting MemAvailable and shared values
461    MemAvailable=$(extract_parameter "MemAvailable:")
462
463    # Calculating Swap values
464    SwapUsed=$((SwapTotal - SwapFree))
465
466    # Outputting the calculated values
467    echo $MemTotal $used $free $MemAvailable $shared $buff_cache $SwapTotal $SwapUsed $SwapFree
468}
469
470# UPTIME
471read_uptime() {
472    uptime_info=$(head -n1 /proc/uptime)
473    uptime_seconds=$(echo "$uptime_info" | awk '{print $1}')
474    echo "$uptime_seconds"
475}
476
477# MAIN
478main() {
479    local escaped_value system_vars process_vars json count
480
481    system_vars=(cpu_core_count total_mem total_swap kernel_version system_cpu system_uptime system_mem_total system_mem_used system_mem_free system_mem_available system_mem_shared system_mem_buff_cache system_swap_total system_swap_used system_swap_free)
482    process_vars=(process_cpu process_uptime process_mem process_command_line process_ram process_vmem)
483
484    # Start the JSON string
485    json="{"
486
487    count=1
488
489    # While loop to do the following:
490    while [ $count -le "$rounds" ] || [ "$rounds" -eq -1 ]
491    do
492        log 1 "[+] Round $count"
493
494        # Start Json
495        json="{"
496
497        # Get system infos
498        if [ $sys == "true" ] ; then
499            read -r cpu_core_count total_mem total_swap kernel_version  <<< "$(get_system_infos)"
500        fi
501        # Get cpu infos
502        if [ "$cpu" == "true" ]; then
503            read -r system_cpu <<< "$(get_current_cpu)"
504        fi
505        # Get uptime infos
506        if [ $up == "true" ]; then
507            read -r system_uptime <<< "$(read_uptime)"
508        fi
509        # Get memory infos
510        if [ $mem == "true" ]; then
511            read -r system_mem_total system_mem_used system_mem_free system_mem_available system_mem_shared system_mem_buff_cache system_swap_total system_swap_used system_swap_free <<< "$(calculate_memory_values)"
512        fi
513
514        log 1 "  [+] Getting system infos"
515
516        # Loop over the variable names
517        for var in "${system_vars[@]}"; do
518            # Get the value of the variable
519            value=${!var}
520            # Check if the value is a number
521            if [[ $value =~ $regex_decimal_number ]]; then
522                # If it's a number, add it to the JSON string without quotes
523                json+="\"$var\": $value,"
524            else
525                # If it's not a number, add it to the JSON string with quotes
526                read -r escaped_value <<< "$(json_escape "$value")"
527                json+="\"$var\": \"$escaped_value\","
528            fi
529        done
530
531        # Loop over process names
532        for name in "${names_array[@]}"; do
533            log 1 "  [+] Getting process $name infos"
534            local pids=()
535            get_pid_list_by_name "$name" pids
536            # Add a subsection to json for each process
537            json+="\"$name\":{"
538            # Loop over PIDs
539            for pid in "${pids[@]}"; do
540                log 1 "      [+] Getting PID $pid infos for process"
541                if [ "$pid" -ne 0 ]; then
542                    read -r process_cpu process_uptime <<< "$(get_pid_cpu_and_uptime "$pid")"
543                    read -r process_mem process_ram process_vmem <<< "$(get_pid_mem "$pid")"
544                    read -r process_command_line <<< "$(get_pid_command_line "$pid")"
545                    # read -r process_name <<< "$(get_pid_process_name "$pid")"
546                fi
547                # Add a subsection to json for each PID
548                json+="\"$pid\":{"
549                for var in "${process_vars[@]}"; do
550                    value=${!var}
551                    if [[ $value =~ $regex_decimal_number ]]; then
552                        json+="\"$var\": $value,"
553                    else
554                        read -r escaped_value <<< "$(json_escape "$value")"
555                        json+="\"$var\": \"$escaped_value\","
556                    fi
557                done
558                # Remove the trailing , and replace it by },
559                json="${json%,}},"
560            done
561            # Remove the trailing , and replace it by },
562            json="${json%,}},"
563        done
564
565        # Remove the trailing comma and close the JSON string
566        json="${json%,}}"
567
568        log 1 "  [+] Sending to $ip:$port"
569
570        if [ $udp = true ]; then
571            echo -n "$json" | nc -u -w1 "$ip" "$port"
572        else
573            echo  "$json"
574        fi
575        #echo -n "$json" | nc -u -w0 "127.0.0.1" "65431"
576
577        log 2 "$json"
578
579        log 2 "  [+] Data size : ${#json}"
580
581        count=$((count+1))
582
583        # Wait between rounds
584        if [ "$timeout" -ne 0 ]; then
585            log 1 "[+] Sleeping $timeout seconds"
586            sleep "$timeout"
587        elif [ "$debug" = true ]; then
588            log 1 "[+] Waiting for input..."
589            read -r -n 1
590        fi
591
592    done
593}
594
595main

ProcessMonitor

The process monitor consists of two parts; the ProcessMonitor class that implements BaseMonitor and a second module that is to be run on the host of your target.

class boofuzz.monitors.ProcessMonitor(host, port)[source]

Proxy class for the process monitor interface.

In Versions < 0.2.0, boofuzz had network and process monitors that communicated over RPC. The RPC client was directly passed to the session class, and resolved all method calls dynamically on the RPC partner.

Since 0.2.0, every monitor class must implement the abstract class BaseMonitor, which defines a common interface among all Monitors. To aid future typehinting efforts and to disambiguate Network- and Process Monitors, this explicit proxy class has been introduced that fast-forwards all calls to the RPC partner.

Added in version 0.2.0.

alive()[source]

This method is forwarded to the RPC daemon.

get_crash_synopsis()[source]

This method is forwarded to the RPC daemon.

on_new_server(new_uuid)[source]

Restores all set options to the RPC daemon if it has restarted since the last call.

post_send(target=None, fuzz_data_logger=None, session=None)[source]

This method is forwarded to the RPC daemon.

pre_send(target=None, fuzz_data_logger=None, session=None)[source]

This method is forwarded to the RPC daemon.

restart_target(target=None, fuzz_data_logger=None, session=None)[source]

This method is forwarded to the RPC daemon.

set_crash_filename(new_crash_filename)[source]

Deprecated since version 0.2.0.

This option should be set via set_options.

set_options(*args, **kwargs)[source]

The old RPC interfaces specified set_foobar methods to set options. As these vary by RPC implementation, this trampoline method translates arguments that have been passed as keyword arguments to set_foobar calls.

If you call set_options(foobar="barbaz"), it will result in a call to set_foobar("barbaz") on the RPC partner.

set_proc_name(new_proc_name)[source]

Deprecated since version 0.2.0.

This option should be set via set_options.

set_start_commands(new_start_commands)[source]

Deprecated since version 0.2.0.

This option should be set via set_options.

set_stop_commands(new_stop_commands)[source]

Deprecated since version 0.2.0.

This option should be set via set_options.

start_target(*args, **kwargs)[source]

This method is forwarded to the RPC daemon.

stop_target()[source]

This method is forwarded to the RPC daemon.

NetworkMonitor

The network monitor consists of two parts; the NetworkMonitor class that implements BaseMonitor and a second module that is to be run on a host that can monitor the traffic.

class boofuzz.monitors.NetworkMonitor(host, port)[source]

Proxy class for the network monitor interface.

In Versions < 0.2.0, boofuzz had network and process monitors that communicated over RPC. The RPC client was directly passed to the session class, and resolved all method calls dynamically on the RPC partner.

Since 0.2.0, every monitor class must implement the abstract class BaseMonitor, which defines a common interface among all Monitors. To aid future typehinting efforts and to disambiguate Network- and Process Monitors, this explicit proxy class has been introduced that fast-forwards all calls to the RPC partner.

Added in version 0.2.0.

alive()[source]

This method is forwarded to the RPC daemon.

on_new_server(new_uuid)[source]

Restores all set options to the RPC daemon if it has restarted since the last call.

post_send(target=None, fuzz_data_logger=None, session=None)[source]

This method is forwarded to the RPC daemon.

pre_send(target=None, fuzz_data_logger=None, session=None)[source]

This method is forwarded to the RPC daemon.

restart_target(target=None, fuzz_data_logger=None, session=None)[source]

Always returns false as this monitor cannot restart a target.

retrieve_data()[source]

This method is forwarded to the RPC daemon.

set_filter(new_filter)[source]

Deprecated since version 0.2.0.

This option should be set via set_options.

set_log_path(new_log_path)[source]

Deprecated since version 0.2.0.

This option should be set via set_options.

set_options(*args, **kwargs)[source]

The old RPC interfaces specified set_foobar methods to set options. As these vary by RPC implementation, this trampoline method translates arguments that have been passed as keyword arguments to set_foobar calls.

If you call set_options(foobar="barbaz"), it will result in a call to set_foobar("barbaz") on the RPC partner.

Additionally, any options set here are cached and re-applied to the RPC server should it restart for whatever reason (e.g. the VM it’s running on was restarted).

CallbackMonitor

class boofuzz.monitors.CallbackMonitor(on_pre_send=None, on_post_send=None, on_restart_target=None, on_post_start_target=None)[source]

New-Style Callback monitor that is used in Session to provide callback-arrays. Its purpose is to keep the *_callbacks arguments in the session class while simplifying the implementation of session by forwarding these callbacks to the monitor infrastructure.

The mapping of arguments to method implementations of this class is as follows:

  • restart_callbacks –> target_restart

  • pre_send_callbacks –> pre_send

  • post_test_case_callbacks –> post_send

  • post_start_target_callbacks –> post_start_target

All other implemented interface members are stubs only, as no corresponding arguments exist in session. In any case, it is probably wiser to implement a custom Monitor than to use the callback functions.

Added in version 0.2.0.

post_send(target=None, fuzz_data_logger=None, session=None)[source]

This method iterates over all supplied post send callbacks and executes them. Their return values are discarded, exceptions are caught and logged:

  • BoofuzzTargetConnectionReset will log a failure

  • BoofuzzTargetConnectionAborted will log an info

  • BoofuzzTargetConnectionFailedError will log a failure

  • BoofuzzSSLError will log either info or failure, depending on if the session ignores SSL/TLS errors.

  • every other exception is logged as an error.

All exceptions are discarded after handling.

post_start_target(target=None, fuzz_data_logger=None, session=None)[source]

Called after a target is started or restarted.

pre_send(target=None, fuzz_data_logger=None, session=None)[source]

This method iterates over all supplied pre send callbacks and executes them. Their return values are discarded, exceptions are catched and logged, but otherwise discarded.

restart_target(target=None, fuzz_data_logger=None, session=None)[source]

This Method tries to restart a target. If no restart callbacks are set, it returns false; otherwise it returns true.

Returns:

bool