Flow control
Typically, a payload will start at the first command, and continue executing until the last command.
There are, of course, ways to change this. Generally referred to as "flow control", these options change the course of the payload being run.
If
The simplest flow control is the if statement. Like a word problem in math, an if says simply:
"If this condition is true, perform this action".
What constitutes "true"-ness? As in the majority of programming languages, a true statement is one where the result is not zero.
if [ 1 -eq 1 ]; then
echo "True!"
fiThis will, of course, print "True!" when run, because 1 is equal to 1.
if [ "true" = "not true" ]; then
echo "True!"
fiSimilarly, this will print nothing, as the string true is not equal to the string not true.
Writing if statements
The basic syntax of an if statement is:
if [ condition ]
then
commands
fiThis is usually shortened (as we did above) to simply:
You'll notice the if block ends with fi (if backwards). Bash uses this convention in several places to indicate the end of a special block.
Conditions
In the if statement, [ condition ] is a test that evaluates to true or false. You can test conditions based on file attributes, strings, numbers, or expressions using various comparison operators such as -eq (equal to), -ne (not equal to), -lt (less than), -gt (greater than), -le (less than or equal to), -ge (greater than or equal to), and -z (tests if a string is empty).
Integer comparisons
a -eq b
a is equal to b
a -ne b
a is not equal to b
a -gt b
a is greater than b
a -ge b
a is greater than or equal to b
a -lt b
a is less than b
a -le b
a is less than or equal to b
Notice that we use -eq instead of =, -gt instead of >=, and so on? This is because bash treats strings and numbers differently!
When doing numerical comparisons, be sure to use the right operators!
String comparisons
"$a" = "$b"
a is equal to b
"$a" == "$b"
a is equal to b
"$a" != "$b"
a is not equal to b
-z "$a"
a is empty
For example,
Notice the spaces: You must use spaces around the = or == operators!
File conditions
There are many tests we can perform on files:
-e
File exists
-f
File exists and is a regular file (not a directory or special file type)
-s
File exists and has data (has a size greater than zero)
-d
File is a directory
-b
File is a block device (a special file in Linux that represents hardware like disks and other storage systems)
-c
File is a character device (a special file in Linux that represents hardware like serial ports)
-p
File is a pipe (a special file in Linux that represents a two-way communication system between processes)
-h
File is a symbolic link (essentially a shortcut or pointer to another file)
-r
File has read permission for the user running the test
-w
File has write permission for the user running the test
-x
File has execute permission for the user running the test
f1 -nt f2
File f1 is newer than file f2
f1 -ot f2
File f1 is older than file f2
For example, to determine if a file exists:
If-Else
We already touched on the else structure in the example above: Else handles taking actions if a statement is not true.
To handle multiple tests, we use the elif option ("else if"):
Notice how we can combine elif with else ; when no tests are true, the else block is executed.
For a more practical example:
Condensed return codes
A variant of an if statement can be used when handling the return codes from a process.
Every process has a return code; these are numeric codes that indicate if the process succeeded or failed. A return code of 0 is considered a success, while any other number is considered an error.
Return codes are stored in the internal variable $?. For example:
The ls program will set an error code if the file does not exist.
We could check for this using a traditional if statement:
Often, however, it is much quicker to use the bash shorthand of && and ||. This will execute the next statement only if the previous one succeeded. To implement the same using these shorthand options:
The short methods are not always clearer, but are often very useful for short tests of command success. They're covered more in the Return codes & success section!
Case
To handle multiple options in a more elegant fashion than constant if-else blocks, the case statement is a conditional statement that is used to test a variable or an expression against a series of patterns or values. It is similar to the switch statement in other programming languages.
The basic syntax of the case statement is as follows:
Here, expression is the variable or expression to be tested, and pattern1, pattern2, pattern3, etc., are the patterns or values against which the expression is to be tested.
Each pattern is followed by a list of commands to execute if the expression matches that pattern. The ;; symbol is used to indicate the end of each pattern. In other languages, the ;; is equivalent to a break statement in a switch block.
The | symbol is used to separate multiple patterns that should be treated as equivalent.
The * pattern matches any value, and is used as a default case if none of the other patterns match.
For example, the following script demonstrates the use of the case statement to test the value of a variable called day:
You can see this in action in the NETMODE DuckyScript command:
For
A for loop is used to iterate over a set of values, such as a list of files, or a range of numbers. The general syntax for a for loop is as follows:
Or more condensed,
Here, var is a variable that is used to hold each value from the list as the loop iterates. list is a sequence of items, separated by spaces, that the loop will iterate over.
The commands block contains the instructions to be executed for each iteration of the loop. This block can contain any valid commands, including DuckyScript commands, other loops, conditionals, and function calls.
Here's an example of a simple for loop that iterates over a list of values:
This will iterate over the list of tools ("squirrel", "pineapple", "ducky", "turtle", "bunny"), and for each iteration print the line "Doing a thing with [tool]". This would output:
You can also use a for loop to iterate over a range of values, using the seq command. Here's an example:
This loop will iterate over the numbers 1 to 5, and for each iteration, it will print the message "Counting: [number]". The output of this script will be:
The list of items can be taken from a shell expansion, for instance to iterate over all files in a directory:
Notice we place quotes around the variable $f: This protects against a file with a space (or other special characters) in the name. Without the quotes, it would appear as two arguments to the C2EXFIL command.
While
A while loop is used to execute a block of commands repeatedly as long as a certain condition is true. The general syntax for a while loop is as follows:
or
Here, condition is a test that is evaluated at the beginning of each iteration of the loop. If the condition is true, the commands block will be executed; if it is false, the loop will terminate and execution will continue with the command after the done keyword.
Conditions are evaluated the same as if statement conditions, allowing for quite complex controls.
The commands block contains the instructions to be executed for each iteration of the loop. This block can contain any valid commands, including DuckyScript commands, other loops, conditionals, and function calls.
Here's an example of a simple while loop that iterates as long as a certain condition is true:
This loop will iterate as long as the value of count is less than 5. For each iteration, it will print the message "Counting: [count]", and then increment the value of count by 1. The output of this script will be:
Functions
A function is a block of code that can be called by name from within a script. Functions allow you to modularize your code and reuse it in different parts of your script, making it easier to write and maintain complex scripts.
The general syntax for defining a function is as follows:
Here, function_name is the name you choose for your function, and commands is the block of code that will be executed when the function is called.
You can call a function by simply typing its name, followed by any arguments you want to pass to it (if any):
This is a simple function which adds two numbers:
The function add_numbers takes two arguments, num1 and num2, adds them together, and then prints the result to the console. You would call this function using:
This will output 15 to the console, which is the sum of the two arguments.
You can also use functions to encapsulate complex logic, or to perform tasks that need to be repeated multiple times in your payload. For example, you could write a function to check if a file exists:
This function takes one argument, which is the name of the file you want to check. It then uses the -e operator to test if the file exists, and prints a message indicating whether the file exists or not.
To call this function from within your payload, you could use:
This will output either "File exists" or "File does not exist".
Last updated
Was this helpful?
