While loops are also fairly common and execute code while an expression is true. While loops have a simple format and, like if, use the square brackets ([]) for the test:
1 2 3 4
while [ <some test> ] do <perform an action> done
Let’s re-create the previous example with a while loop:
kali@kali:~$ cat ./while.sh #!/bin/bash # while loop example counter=1 while [ $counter -lt 10 ] do echo"10.11.1.$counter" ((counter++)) done kali@kali:~$ chmod +x ./while.sh kali@kali:~$ ./while.sh 10.11.1.1 10.11.1.2 10.11.1.3 10.11.1.4 10.11.1.5 10.11.1.6 10.11.1.7 10.11.1.8 10.11.1.9
This is not the output we expected. This is a common mistake called an “off by one” error. In the example above, we used -lt (less than) instead of -le (less than or equal to), so our counter only got to nine, not ten as originally intended.
The ((counter++)) line uses the double-parenthesis (( )) construct to perform arithmetic expansion and evaluation at the same time. In this particular case, we use it to increase our counter variable by one. Let’s re-write the while loop and try the example again:
In computer programming, loops help us with repetitive tasks that we need to run until a certain criteria is met. Iteration is particularly useful for penetration testers, so we recommend paying very close attention to this section. In Bash, the two most predominant loop commands are for and while. We will take a look at both.
For loops are very practical and work very well in Bash one-liners. This type of loop is used to perform a given set of commands for each of the items in a list. Let’s briefly look at its general syntax:
1 2 3 4
for var-name in <list> do <action to perform> done
The for loop will take each item in the list (in order), assign that item as the value of the variable varname, perform the given action between do and done, and then go back to the top, grab the next item in the list, and repeat the steps until the list is exhausted.
Let’s take a look at a more practical example that will quickly print the first 10 IP addresses in the 10.11.1.0/24 subnet:
1 2 3 4 5 6 7 8 9 10 11
kali@kali:~$ for ip in $(seq 1 10); doecho 10.11.1.$ip; done 10.11.1.1 10.11.1.2 10.11.1.3 10.11.1.4 10.11.1.5 10.11.1.6 10.11.1.7 10.11.1.8 10.11.1.9 10.11.1.10
In this Bash one-liner, we used the seq command to print a sequence of numbers, in this case the numbers one through ten. Each number is then assigned to the ip variable, and then each IP address is displayed to the screen as the for loop runs multiple times, exiting at the end of the sequence. Another way of re-writing the previous for loop involves brace expansion using ranges. Brace expansion using ranges is written giving the first and last values of the range and can be a sequence of numbers or characters. This is known as a “sequence expression”:
1 2 3 4 5 6 7 8 9 10 11
kali@kali:~$ for i in {1..10}; doecho 10.11.1.$i;done 10.11.1.1 10.11.1.2 10.11.1.3 10.11.1.4 10.11.1.5 10.11.1.6 10.11.1.7 10.11.1.8 10.11.1.9 10.11.1.10
There is a lot of potential for this type of loop. Displaying IP addresses to the screen may not seem very useful, but we can use the same loop to run a port scan using nmap (which we discuss in detail in another module). We can also attempt to use the ping command to see if any of the IP addresses respond to ICMP echo requests, etc.
Boolean logical operators, like AND (&&) and OR (||) are somewhat mysterious because Bash uses them in a variety of ways. One common use is in command lists, which are chains of commands whose flow is controlled by operators. The “|” (pipe) symbol is a commonly-used operator in a command list and passes the output of one command to the input of another. Similarly, boolean logical operators execute commands based on whether a previous command succeeded (or returned True or 0) or failed (returned False or non-zero). Let’s take a look at the AND (&&) boolean operator first, which executes a command only if the previous command succeeds (or returns True or 0):
In this example, we first assigned the username we are searching for to the user2 variable. Next, we use the grep command to check if a certain user is listed in the /etc/passwd file, and if it is, grep returns True and the echo command is executed. However, when we try searching for a user that we know does not exist in the /etc/passwd file, our echo command is not executed.
When used in a command list, the OR (||) operator is the opposite of AND (&&); it executes the next command only if the previous command failed (returned False or non-zero):
1 2 3 4 5
kali@kali:~$ echo$user2 bob kali@kali:~$ grep $user2 /etc/passwd && echo"$user2 found!" || echo"$user2 not found !" bob not found!
In the above example, we took our previous command a step further and added the OR (||) operator followed by a second echo command. Now, when grep does not find a matching line and returns False, the second echo command after the OR (||) operator is executed instead. These operators can also be used in a test to compare variables or the results of other tests. When used this way, AND (&&) combines two simple conditions, and if they are both true, the combined result is success (or True or 0). Consider this example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
kali@kali:~$ cat ./and.sh #/bin/bash # and example if [ $USER == 'kali' ] && [ $HOSTNAME == 'kali' ] then echo"Multiple statements are true!" else echo"Not much to see here..." fi kali@kali:~$ chmod +x ./and.sh kali@kali:~$ ./and.sh Multiple statements are true! kali@kali:~$ echo$USER && echo$HOSTNAME kali kali
In this example, we used AND (&&) to test multiple conditions and since both variable comparisons were true, the whole if line succeeded, so the then branch executed. When used in a test, the OR (||) boolean operator is used to test one or more conditions, but only one of them has to be true to count as success. Let’s take a look at an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
kali@kali:~$ cat ./or.sh #!/bin/bash # or example if [ $USER == 'kali' ] || [ $HOSTNAME == 'pwn' ] then echo"One condition is true, this line is printed" else echo"You are out of luck!" fi kali@kali:~$ chmod +x ./or.sh kali@kali:~$ ./or.sh One condition is true, this line is printed kali@kali:~$ echo$USER && echo$HOSTNAME kali kali
In this example, we used OR (||) to test multiple conditions and since one of the variable comparisons was true, the whole if line succeeded, so the then branch executed.
Conditional statements allow us to perform different actions based on different conditions. The most common conditional Bash statements include if, else, and elif. The if statement is relatively simple–it checks to see if a condition is true–but it requires a very specific syntax. Pay careful attention to this syntax, especially the use of required spaces:
1 2 3 4
if [ <some test> ] then <perform an action> fi
In this listing, if “some test” evaluates as true, the script will “perform an action”, or any commands between then and fi. Let’s look at an actual example:
1 2 3 4 5 6 7 8 9 10 11 12
kali@kali:~$ cat ./if.sh #!/bin/bash # if statement example read -p "What is your age: " age if [ $age -lt 16 ] then echo"You might need parental permission to take this course!" fi kali@kali:~$ chmod +x ./if.sh kali@kali:~$ ./if.sh What is your age: 15 You might need parental permission to take this course!
In this example, we used an if statement to check the age entered by a user. If the entered age was less than (-lt) 16, the script would output a warning message. The square brackets (“[“ and “]”) in the if statement above are actually a reference to the test command. This simply means we can use all of the operators that are allowed by the test command. Some of the most common operators include:
Operator
Description: Expression True if…
!EXPRESSION
The EXPRESSION is false.
-n STRING
STRING length is greater than zero
-z STRING
The length of STRING is zero (empty)
STRING1 != STRING2
STRING1 is not equal to STRING2
STRING1 = STRING2
STRING1 is equal to STRING2
INTEGER1 -eq INTEGER2
INTEGER1 is equal to INTEGER2
INTEGER1 -ne INTEGER2
INTEGER1 is not equal to INTEGER2
INTEGER1 -gt INTEGER2
INTEGER1 is greater than INTEGER2
INTEGER1 -lt INTEGER2
INTEGER1 is less than INTEGER2
INTEGER1 -ge INTEGER2
INTEGER1 is greater than or equal to INTEGER 2
INTEGER1 -le INTEGER2
INTEGER1 is less than or equal to INTEGER 2
-d FILE
FILE exists and is a directory
-e FILE
FILE exists
-r FILE
FILE exists and has read permission
-s FILE
FILE exists and it is not empty
-w FILE
FILE exists and has write permission
-x FILE
FILE exists and has execute permission
With the above in mind, our previous example using if can be rewritten without square brackets as follows:
1 2 3 4 5 6 7 8 9 10 11 12
kali@kali:~$ cat ./if2.sh #!/bin/bash # if statement example 2 read -p "What is your age: " age iftest$age -lt 16 then echo"You might need parental permission to take this course!" fi kali@kali:~$ chmod +x ./if2.sh kali@kali:~$ ./if2.sh What is your age: 15 You might need parental permission to take this course!
Even though this example is functionally equivalent to the example using square brackets, using square brackets makes the code slightly easier to read. We can also perform a certain set of actions if a statement is true and another set if it is false. To do this, we can use the else statement, which has the following syntax:
1 2 3 4 5 6
if [ <some test> ] then <perform action> else <perform another action> fi
Let’s extend our previous “age” example to include the else statement:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
kali@kali:~$ cat ./else.sh #!/bin/bash # else statement example read -p "What is your age: " age if [ $age -lt 16 ] then echo"You might need parental permission to take this course!" else echo"Welcome to the course!" fi kali@kali:~$ chmod +x ./else.sh kali@kali:~$ ./else.sh What is your age: 21 Welcome to the course!
Notice that the else statement was executed when the entered age was greater than (or more specifically “not less than”) sixteen. The if and else statements only allow two code execution branches. We can add additional branches with the elif statement which uses the following pattern:
1 2 3 4 5 6 7 8 9
if [ <some test> ] then <perform action> elif [ <some test> ] then <perform different action> else <perform yet another different action> fi
Let’s again extend our “age” example to include the elif statement:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
kali@kali:~$ cat ./elif.sh #!/bin/bash # elif example read -p "What is your age: " age if [ $age -lt 16 ] then echo"You might need parental permission to take this course!" elif [ $age -gt 60 ] then echo"Hats off to you, respect!" else echo"Welcome to the course!" fi kali@kali:~$ chmod +x ./elif.sh kali@kali:~$ ./elif.sh What is your age: 65 Hats off to you, respect!
In this example, the code execution flow was slightly more complex. In order of operation, the then branch executes if the entered age is less than sixteen, the elif branch is entered (and the “Hats off..” message displayed) if the age is greater than sixty, and the else branch executes only if the age is greater than sixteen but less than sixty.
Command-line arguments are a form of user input, but we can also capture interactive user input while a script is running with the read command. In this example, we will use read to capture user input and assign it to a variable:
1 2 3 4 5 6 7 8 9 10
kali@kali:~$ cat ./input.sh #!/bin/bash echo"Hello there, would you like to learn how to hack: Y/N?" read answer echo"Your answer was $answer" kali@kali:~$ chmod +x ./input.sh kali@kali:~$ ./input.sh Hello there, would you like to learn how to hack: Y/N? Y Your answer was Y
We can alter the behavior of the read command with various command line options. Two of the most commonly used options include -p, which allows us to specify a prompt, and -s, which makes the user input silent. The latter is ideal for capturing user credentials:
1 2 3 4 5 6 7 8 9 10 11
kali@kali:~$ cat ./input2.sh #!/bin/bash # Prompt the user for credentials read -p 'Username: ' username read -sp 'Password: ' password echo"Thanks, your creds are as follows: "$username" and "$password kali@kali:~$ chmod +x ./input2.sh kali@kali:~$ ./input2.sh Username: kali Password: Thanks, your creds are as follows: kali and nothing2see!
Not all Bash scripts require arguments. However, it is extremely important to understand how they are interpreted by Bash and how to use them. We have already executed Linux commands with arguments. For example, when we run the command ls -l /var/log, both -l and /var/log are arguments to the ls command. Bash scripts are no different; we can supply command-line arguments and use them in our scripts:
1 2 3 4 5 6
kali@kali:~$ cat ./arg.sh #!/bin/bash echo"The first two arguments are $1 and $2" kali@kali:~$ chmod +x ./arg.sh kali@kali:~$ ./arg.sh hello there The first two arguments are hello and there
Here we created a simple Bash script, set executable permissions on it, and then ran it with two arguments. The $1 and $2 variables represent the first and second arguments passed to the script. Let’s explore a few special Bash variables:
Variable Name
Description
$0
The name of the Bash script
$1 - $9
The first 9 arguments to the Bash script
$#
Number of arguments passed to the Bash script
$@
All arguments passed to the Bash script
$?
The exit status of the most recently run process
$$
The process ID of the current script
$USER
The username of the user running the script
$HOSTNAME
The hostname of the machine
$RANDOM
A random number
$LINENO
The current line number in the script
Some of these special variables can be very useful when debugging a script. For example, we might be able to obtain the exit status of a command to determine whether it was successfully executed or not.
Variables are named places to temporarily store data. We can set (or “declare”) a variable, which assigns a value to it, or read a variable, which will “expand” or “resolve” it to its stored value.
We can declare variable values in a number of ways. The easiest method is to set the value directly with a simple name=value declaration. Notice that there are no spaces before or after the “=” sign:
1
kali@kali:~$ first_param=Super
Declaring a variable is pointless unless we can reference it. To do this, we precede the variable with the “$” character. Whenever Bash encounters this syntax in a command, it replaces the variable name with its value (“expands” the variable) before execution:
1 2 3 4
kali@kali:~$ first_param=Super kali@kali:~$ last_param=Hero kali@kali:~$ echo$first_param$last_param Super Hero
Variable names may be uppercase, lowercase, or a mixture of both. However, Bash is casesensitive so we must be consistent when declaring and expanding variables. In addition, it’s good practice to use descriptive variable names, which make our scripts much easier to read and maintain.
Be advised that Bash interprets certain characters in specific ways. For example, this declaration demonstrates an improper multi-value variable declaration:
1 2
kali@kali:~$ greeting=Hello World bash: World: command not found
This was not necessarily what we expected. To fix this, we can use either single quotes (‘) or double quotes (“) to enclose our text. However, Bash treats single and double quotes differently. When encountering single quotes, Bash interprets every enclosed character literally. When enclosed in double quotes, all characters are viewed literally except “$”, “`”, and “" meaning variables will be expanded in an initial substitution pass on the enclosed text.
A simple example will help clarify this:
1 2 3 4 5 6
kali@kali:~$ greeting='Hello World' kali@kali:~$ echo$greeting Hello World kali@kali:~$ greeting2="New $greeting" kali@kali:~$ echo$greeting2 New Hello World
In this example, the single-quote-enclosed declaration of greeting preserved the value of our text exactly and did not interpret the space as a command delimiter. However, in the double-quoteenclosed declaration of greeting2, Bash expanded $greeting to its value (“Hello World”), honoringthe special meaning of the “$” character. We can also set the value of the variable to the result of a command or program. This is known as command substitution, which allows us to take the output of a command or program (what would normally be printed to the screen) and have it saved as the value of a variable. To do this, place the variable name in parentheses “()”, preceded by a “$” character:
1 2 3
kali@kali:~$ user=$(whoami) kali@kali:~$ echo$user kali
Here we assigned the output of the whoami command to the user variable. We then displayed its value. An alternative syntax for command substitution using the backtick, or grave, character (`) is shown below:
1 2 3
kali@kali:~$ user2=`whoami` kali@kali:~$ echo$user2 kali
The backtick method is older and typically discouraged as there are differences in how the two methods of command substitution behave. It is also important to note that command substitution happens in a subshell and changes to variables in the subshell will not alter variables from the master process. This is demonstrated in the following example:
In this example, first note that we changed the shebang, adding in the -x flag. This instructed Bash to print additional debug output, so we could more easily see the commands that were executed and their results. As we view this output, notice that commands preceded with a single “+” character were executed in the current shell and commands preceded with a double “++” were executed in a subshell. This allows us to clearly see that the second declarations of var1 and var2 happened inside a subshell and did not change the values in the current shell as the initial declarations did.
The GNU Bourne-Again Shell (Bash) is a powerful work environment and scripting engine. A competent security professional skillfully leverages Bash scripting to streamline and automate many Linux tasks and procedures. In this module, we will introduce Bash scripting and explore several practical scenarios.
A Bash script is a plain-text file that contains a series of commands that are executed as if they had been typed at a terminal prompt. Generally speaking, Bash scripts have an optional extension of .sh (for ease of identification), begin with #!/bin/bash and must have executable permissions set before they can be executed. Let’s begin with a simple “Hello World” Bash script, file named hello-world.sh:
1 2 3
#!/bin/bash # Hello World Bash Script echo"Hello World!"
This script has several components worth explaining: • Line 1: #! is commonly known as the shebang, and is ignored by the Bash interpreter. The second part, /bin/bash, is the absolute path to the interpreter, which is used to run the script. This is what makes this a “Bash script” as opposed to another type of shell script, like a “C Shell script”, for example. • Line 2: # is used to add a comment, so all text that follows it is ignored. • Line 3: echo “Hello World!” uses the echo Linux command utility to print a given string to the terminal, which in this case is “Hello World!”.
Next, let’s make the script executable and run it:
The chmod command, along with the +x option is used to make the script executable. ./helloworld.sh is used to actually run it. The ./ notation may seem confusing but this is simply a path notation indicating that this script is in the current directory. Whenever we type a command, Bash tries to find it in a series of directories stored in a variable called PATH. Since our home directory is not included in that variable, we must use the relative path to our Bash script in order for Bash to “find it” and run it.
Now that we have created our first Bash script,next we will explore Bash in a bit more detail.
Due to the requirements of my work, I needed to design an open software system, and immediately thought of the Python language. As a result, I designed a simple and easily extensible plugin system.
if choice == "1": plugin_name = input("Please input the name of plugin: ") manager.load_plugin(plugin_name)
elif choice == "2": manager.run_plugins()
elif choice == "3": break
Then, you create a directory named ‘plugins.’ In this directory, you can add your plugin, which must define a class named ‘MyPlugin’ and must have a function named ‘run’. Here is the simple code:
1 2 3 4 5 6 7
#my_plugin
classMyPlugin: def__init__(self): pass defrun(self): print("MyPlugin is running")
The following is the running process:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
1. Add Plugin 2. Run Plugin 3. Quit Your Select: 1 Please input the name of plugin: my_plugin plugin my_plugin loaded. 1. Add Plugin 2. Run Plugin 3. Quit Your Select: 2 MyPlugin is running 1. Add Plugin 2. Run Plugin 3. Quit Your Select: 3 PS C:\Users\xxx\Desktop\yyy\plugin-system>
You can use this model to design a big and an opened software system.
pop esi ; Pop '/bin/sh' from the stack into esi xor eax, eax ; Set eax to NULL mov byte [esi + 7], al ; Null-terminate '/bin/sh' using the low byte of eax lea ebx, [esi] ; Load the address of '/bin/sh' into ebx mov dword [esi + 8], ebx lea ecx, [esi + 8] ; Load the address of the argv array into ecx mov dword [esi + 12], eax lea edx, [esi + 12] ; Load the address of the NULL terminator into edx mov al, 0x0b ; Set al to 0x0b, the system call number for execve int 0x80 ; Trigger the syscall
callShellcode:
call shellcode db '/bin/sh'
After saving this code to a file named “shellx.asm,” you need to compile it using NASM to obtain the hexadecimal representation of the code. Use the following commands:
This will generate a “shellx” file. However, it’s not executable yet. You’ll need to use the “objdump” command to extract the hexadecimal code. You can use a Bash script like this:
1 2 3 4
for i in $(objdump -d "$1" | tr'\t'' ' | tr' ''\n' | egrep '^[0-9a-f]{2}$'); do echo -n "\x$i" done echo -e "\n"
To validate your assembly code, you’ll need a C program as follows: