ICSim Exploration

  • Author: Dr. Jim Marquardson (jimarqua@nmu.edu)
  • Updated 2024-06-14

It is possible to monitor and generate CAN traffic using the simulated game controller. But sometimes, you lack a controller to generate codes. Tools exist to generate and replay traffic to see the effects on the controller network. In this exercise, you will use tools to generate CAN network traffic and replay that traffic on the network.

Learning Objectives

In this exercise, you will learn to:

  • start ICSim and the controller using a seed,
  • use cangen to generate network traffic,
  • use candump to record network traffic to a file,
  • use canreplay to replay captured traffic, and
  • evaluate data using Linux tools.

Prerequisites

This exercise assumes that the following are available:

  • Kali Linux VM with a graphical user interface,
  • can-utils has been installed,
  • ICSim has been installed to ~/ICSim.

Reset Applications

  • Close the ICSim and controller applications, if they are running.
  • Stop cansniffer if it is running, using control+c.
  • Close all open terminals.
  • There should be no applications running right now.

Setup a CAN Network

  • Open a terminal.
  • Navigate to the ~/ICSim directory.
cd ~/ICSim
  • Create the vcan0 network. This vcan0 network essentially simulates a physical wire to sensors in a bus topology.
sudo sh setup_vcan.sh

Enter the password (kali) if prompted.

  • You may not see any output. The command likely worked. Check for the vcan0 network using ifconfig.
ifconfig

You should see vcan0 in the list of network adapters.

vcan0: flags=193<UP,RUNNING,NOARP>  mtu 72
        unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  txqueuelen 1000  (UNSPEC)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
  • If you run sudo sh setup_vcan.sh and see a message like, "RTNETLINK answers: File exists," this means that the vcan0 network has already been set up. You can ignore this message and continue.

Launch the Simulator

  • Run the following command to launch the vehicle simulator. Ensure that your working directory is ~/ICSim before running this command. You will likely have to press the enter twice in the terminal to return to the terminal prompt.
./icsim vcan0 &

Note that in this scenario, the controller is not being launched initially. The controller generates a lot of traffic in the background, and we want to minimize that traffic right now.

Window Setup

  • Open a second terminal and navigate to the ~/ICSim directory.
cd ~/ICSim
  • At this point, you should have 3 windows: 1) the ICSim graphical user interface, 2) a terminal with the ~/ICSim working directory, and 3) another terminal with the ~/ICSim working directory.

Window Setup

Generate Traffic

The cangen tool can generate and send CAN messages on the network. It can be helpful to see the data generated in real-time using candump.

  • Run the following command in one terminal to display the CAN traffic in real-time. Use this terminal for all candump commands. The other terminal will be used for cansend. Keep the following command running.
candump vcan0
  • Generate random arbitration IDs and random data. Run this command and see if there are any visual changes on the vehicle simulator.
cangen vcan0

This command might run for a very long time before generating anything interesting in the simulator. Basically, cangen is throwing monumental amounts of spaghetti at the wall to see what sticks, and very little will stick.

  • Click on the terminal running cangen, then press control+c to stop the packet generation. Notice that candump is still running, but no data is showing up on the network.
  • Generate random data for a specific arbitration ID. This might be helpful if you discovered an interesting arbitration ID, but are not sure how the data needs to be formatted. The following command generates random data but only for the arbitration ID 188. After running the command, notice how the data is formatted.
cangen vcan0 -I 188
  • Generate data for a specific arbitration ID using a fixed length of data. If you know the data needs to be a specific length, you can avoid generating meaningless data. But, you might not know what length you need before doing more exploration.
cangen vcan0 -I 188 -L 8
  • In the cangen terminal, press control+c to stop the data generation.
  • In the candump terminal, press control+c to stop the data capture. Note that none of this data has been saved to a file. Instead, the data was only displayed on the screen.

Workflow for Data Exploration

One issue with cangen is that it will create a ton of data, and unless you're capturing that data, it is nearly impossible to trace what happened. Therefore, cangen is most useful when used in combination with candump to save the traffic to a file. A challenge is finding the useful CAN data in a large candump file with thousands of unique, random messages. At a high level, the workflow for analyzing data using cangen is as follows.

  • Start capturing data using candump.
  • Generate data using cangen until something happens.
  • Stop capturing.
  • Stop the data generation.
  • Replay portions of the captured data using canplayer until the event is reproduced.
  • You may need to use the controller to reset the conditions. For example, if you recorded a door unlock event, you may need to use the controller to lock the door before replaying events again.

Build the Haystack (of Data)

  • In one terminal, run the following command to start capturing data.
candump vcan0 -l -f explore.txt
  • In another terminal, run this command to generate data.
cangen vcan0
  • Watch the ICSim window for any changes in the interface. For example, you might see the blinker blink, a door unlock, etc.
  • You may need to watch for several minutes.
  • When you see an interesting event, click in the terminal with candump and press control+c to stop capturing packets.
  • Next, click on the terminal running cangen and press control+c to stop generating traffic.

Find the Needle

  • Look at how many packets were captured. The wc command counts words in files, but adding -l tells it to count lines instead of individual words.
wc -l explore.txt

You may have tens of thousands of lines representing individual CAN messages.

  • Explore the end of the file with tail.
tail explore.txt
  • Open a third terminal window and start the controller. If you unlocked a door, you will want to re-lock that door before playing packets back. Otherwise, if the door is unlocked and you replace the unlock code, you will not know if it worked.
cd ~/ICSim
./controls vcan0 &
  • Make a copy of the explore.txt file called needle.txt.
cp explore.txt needle.txt
  • You can split needle.txt into 10 separate files using the split command. One of the 10 files will have the event that you're looking for. The following command splits the file into 10 files, breaking on lines, and using numbers at the end of the new filenames. Note that the option -n l/10 has a lowercase l and not the number 1.
split -n l/10 -d needle.txt needle_
  • Look at the newly created files using ls.
ls

You should see new files like:

needle_00 needle_01 needle_02
needle_03 needle_04 needle_05
needle_06 needle_07 needle_08
needle_09

Together, those files contain all of the data in needle.txt.

  • Play each file back until you see the event triggered. You might want to start with the last file. The following command tells canplayer to use the input file named needle_09.
canplayer -I needle_09
  • Keep replaying the files until you see the event you wanted. You may have to go all the way to needle_00.
canplayer -I needle_08
canplayer -I needle_07
  • In my case, the event was triggered in needle_03. I'll delete needle.txt, rename needle_03 to needle.txt and delete the other files that start with needle_.
rm needle.txt
mv needle_03 needle.txt
rm needle_*
  • The file needle.txt now has 1/10 the data it had previously. So the search space for finding the CAN message that triggered the event is smaller.
  • Split needle.txt into 10 parts again.
split -n l/10 -d needle.txt needle_
  • Replay the packets as you did previously. These now contain much less data, so they should replay quicker.
canplayer -I needle_09
canplayer -I needle_08
  • In my case, the event was triggered in needle_08. I'll repeat the previous process. I'll delete needle.txt, rename needle_08 to needle.txt, and delete the other split files.
rm needle.txt
mv needle_08 needle.txt
rm needle_*
  • Repeat this process until you have a manageable file size. You can check how many lines remain in the files using wc -l.
wc -l needle_01

In this example, the output shows that needle_01 only has 5 lines.

5 needle_01
  • Eventually, the events will be filtered down enough that the needle_ files have just a few lines. For example, after a few rounds, I had the following data in needle_05.
--$ cat needle_05         
(1718224638.986934) vcan0 19B#00000E000000
(1718224638.987106) vcan0 244#000000013B
(1718224638.988156) vcan0 095#800007F400000026
(1718224638.991612) vcan0 166#D0320036
(1718224638.992813) vcan0 158#0000000000000037
  • Once the list is small enough, you can use cansend to test the commands individually.
cansend vcan0 19B#00000E000000
cansend vcan0 244#000000013B
cansend vcan0 095#800007F400000026
cansend vcan0 166#D0320036
cansend vcan0 158#0000000000000037

This is a very iterative process. At its core, it's not a terribly complicated process, but it requires attention to detail. But at this point, you will know the arbitration ID and data that triggered the event.

Scripted Automation

I created a script to automate the process of looking for the "needle" in the "haystack" of events. The script automates the previous process.

  • You can download a script to automate this named sift.sh.
  • The script sift.sh has the following contents.
#!/bin/bash

# This script is used to replay a candump file and find the event that triggered an event.

# Check if the correct number of arguments are provided
if [ "$#" -lt 2 ]; then
    echo "This script takes these parameters:"
    echo "    1. The filename of the candump file"
    echo "    2. The number of parts to split the file into in each iteration"
    echo "    3. (optinal) The noise file to replay before the event"
    echo "Syntax:  $0 filename number_of_splits_per_iteration [noise_file]"
    echo "Example: $0 candump.txt 10"
    echo "Example: $0 candump.txt 12 noise.txt"
    exit 1
fi

filename=$1
num_parts=$2
noise_file=$3
base_temp_name="haystack"

# Check that $num_parts is a number great than 0. Exit if it isn't.
if ! [[ $num_parts =~ ^[0-9]+$ ]] || [ $num_parts -le 0 ]; then
    echo "num_parts must be a number greater than 0. Exiting."
    exit 1
fi

# Check if the input exists, exit if it doesn't
if [ ! -f $filename ]; then
    echo "File $filename does not exist. Please supply a valid file generated using candump. Exiting."
    exit 1
fi

# Check if $filename is empty and exit if it is
if [ ! -s $filename ]; then
    echo "File $filename is empty. Please supply a valid file generated using candump. Exiting."
    exit 1
fi

# Check if $filename is a directory and exit if it is
if [ -d $filename ]; then
    echo "$filename is a directory. Please supply a valid file generated using candump. Exiting."
    exit 1
fi


# if $base_temp_name.txt exists, generate a new name until it is unique
while [ -f $base_temp_name.txt ]; do
    echo "$base_temp_name.txt already exists. Generating a new name..."
    base_temp_name="$base_temp_name$(date +%s)"
    echo "New name: $base_temp_name.txt"
done

cp $filename $base_temp_name.txt
echo "$filename has been copied to $base_temp_name.txt"
line_count=$(wc -l $base_temp_name.txt | awk '{print $1}')
echo "$base_temp_name.txt has $line_count lines"

# Check if the noise_file parameter was supplied
if [ ! -z $noise_file ]; then
    # Check if the noise_file exists, exit if it doesn't
    if [ ! -f $noise_file ]; then
        echo "File $noise_file does not exist. Please supply a valid file generated using candump. Exiting."
        exit 1
    fi

    # Check if $noise_file is a directory and exit if it is
    if [ -d $noise_file ]; then
        echo "$noise_file is a directory. Please supply a valid file generated using candump. Exiting."
        exit 1
    fi

    echo "Removing noise from $base_temp_name.txt using $noise_file..."
    # Because timestamps in column 1 differ, only columns 2 and 3 are compared
    awk 'NR==FNR{a[$2,$3];next} !(($2,$3) in a)' $noise_file $base_temp_name.txt > temp.txt
    mv temp.txt $base_temp_name.txt
    line_count=$(wc -l $base_temp_name.txt | awk '{print $1}')
    echo "$base_temp_name.txt now has $line_count lines"
fi

while true; do
    echo -e "\nSplitting $base_temp_name.txt into $num_parts parts...\n"
    split -n l/$num_parts -d $base_temp_name.txt $base_temp_name.part.

    echo "Ensure the system is in the proper state to begin testing for the event."
    read -p "Enter 'c' to continue: " answer

    # Loops through the files in reverse order, because its more likely the last one has the event
    for file in $(ls -r $base_temp_name.part.*); do
        line_count=$(wc -l $file | awk '{print $1}')
        echo "Processing $file ($line_count lines)"
        # if the line count is less than or equal to 10, print the contents of the file
        if [ $line_count -le 10 ]; then
            cat $file
        fi
        echo "Using canplayer to replay $file..."
        canplayer -I $file
        echo "Done replaying $file"
        read -p "Did you see the event? (y/n/x): " answer
        if [ "$answer" = "y" ] && [ $line_count -eq 1 ]; then
            echo "Here is the event. It will be kept in $base_temp_name.txt:"
            cat $file
                rm $base_temp_name.txt
                mv $file $base_temp_name.txt
                rm $base_temp_name.part.*
            exit 0
        elif [ "$answer" = "y" ]; then
            echo "$file will be retained as $base_temp_name.txt. All other $base_temp_name.part files will be deleted."
            rm $base_temp_name.txt
            mv $file $base_temp_name.txt
            rm $base_temp_name.part.*
            break # Will continue the outer while loop
        elif [ "$answer" = "x" ]; then
            echo "Exiting"
            exit 0
        fi
    done
done
  • In a Kali terminal, make a new folder.
cd ~
mkdir sift
cd sift
  • Download the file to your /home/kali/sift directory. You can open Firefox in Kali and open this web page to download the file.
  • Make the file executable.
chmod +x sift.sh
  • Run the program to view the syntax.
./sift.sh
  • Optionally, create a new "noise" file.
candump vcan0 -l -f noise.txt
  • Stop capturing the noise file after a few seconds or minutes depending on how much noise you want to capture.
  • Start a new capture.
candump vcan0 -l -f event.txt
  • Trigger an event using either the graphical controller or using cangen.
  • Stop the candump capture.
  • You should now have two 3 files in the sift directory: sift.sh, noise.txt, and event.txt.
  • Run the sift program and follow the prompts.
  • This first method does not filter out any noise.
./sift.sh event.txt 10
  • This second method filters out noise to reduce the search space.
./sift.sh event.txt 5 noise.txt

Challenge

  • Go through the process of finding a different event triggered by cangen.
  • You can try splitting the files into different numbers of parts, like 20 instead of 10.
  • Can you develop a more efficient way of finding the needle in the haystack?

Shutting Down

  • Close the ICSim window. You may have to click Yes to confirm closing it.
  • Close all terminals.

Reflection

  • Should the car manufacturers just publish all of the arbitration IDs and data?
  • How else might you find the signal in the noise?