Introduction to Bash Scripting
Bourne Again Shell
$ bash script.sh <optional arguments>
$ sh script.sh <optional arguments>
$ ./script.sh <optional arguments>
CIDR.sh
$ ./CIDR.sh inlanefreight.com
#!/bin/bash
# Check for given arguments
if [ $# -eq 0 ]
then
echo -e "You need to specify the target domain.\n"
echo -e "Usage:"
echo -e "\t$0 <domain>"
exit 1
else
domain=$1
fi
# Identify Network range for the specified IP address(es)
function network_range {
for ip in $ipaddr
do
netrange=$(whois $ip | grep "NetRange\|CIDR" | tee -a CIDR.txt)
cidr=$(whois $ip | grep "CIDR" | awk '{print $2}')
cidr_ips=$(prips $cidr)
echo -e "\nNetRange for $ip:"
echo -e "$netrange"
done
}
# Ping discovered IP address(es)
function ping_host {
hosts_up=0
hosts_total=0
echo -e "\nPinging host(s):"
for host in $cidr_ips
do
stat=1
while [ $stat -eq 1 ]
do
ping -c 2 $host > /dev/null 2>&1
if [ $? -eq 0 ]
then
echo "$host is up."
((stat--))
((hosts_up++))
((hosts_total++))
else
echo "$host is down."
((stat--))
((hosts_total++))
fi
done
done
echo -e "\n$hosts_up out of $hosts_total hosts are up."
}
# Identify IP address of the specified domain
hosts=$(host $domain | grep "has address" | cut -d" " -f4 | tee discovered_hosts.txt)
echo -e "Discovered IP address:\n$hosts\n"
ipaddr=$(host $domain | grep "has address" | cut -d" " -f4 | tr "\n" " ")
# Available options
echo -e "Additional options available:"
echo -e "\t1) Identify the corresponding network range of target domain."
echo -e "\t2) Ping discovered hosts."
echo -e "\t3) All checks."
echo -e "\t*) Exit.\n"
read -p "Select your option: " opt
case $opt in
"1") network_range ;;
"2") ping_host ;;
"3") network_range && ping_host ;;
"*") exit 0 ;;
esac
Conditional Execution
Script.sh
#!/bin/bash
# Check for given argument
if [ $# -eq 0 ]
then
echo -e "You need to specify the target domain.\n"
echo -e "Usage:"
echo -e "\t$0 <domain>"
exit 1
else
domain=$1
fi
#!/bin/bash - Shebang.
if-else-fi - Conditional execution.
echo - Prints specific output.
$# / $0 / $1 - Special variables.
domain - Variables.
If-Only.sh
```sh #!/bin/bash
value=$1
if [ $value -gt "10" ] then echo "Given argument is greater than 10." fi
**If-Elif-Else.sh**
```sh
#!/bin/bash
value=$1
if [ $value -gt "10" ]
then
echo "Given argument is greater than 10."
elif [ $value -lt "10" ]
then
echo "Given argument is less than 10."
else
echo "Given argument is not a number."
fi
Several conditions Script.sh
#!/bin/bash
# Check for given argument
if [ $# -eq 0 ]
then
echo -e "You need to specify the target domain.\n"
echo -e "Usage:"
echo -e "\t$0 <domain>"
exit 1
elif [ $# -eq 1 ]
then
domain=$1
else
echo -e "Too many arguments given."
exit 1
fi
Exercise script
#!/bin/bash
# Count number of characters in a variable:
# echo $variable | wc -c
# Variable to encode
var="nef892na9s1p9asn2aJs71nIsm"
for counter in {1..40}
do
var=$(echo $var | base64)
if [ $counter -eq "35" ]
then
echo $var | wc -c
fi
done
bash ./script.sh
Note: above code is not supported by sh
, cannot be executed by sh ./script.sh
#!/bin/bash
# Count number of characters in a variable:
# echo $variable | wc -c
# Variable to encode
var="nef892na9s1p9asn2aJs71nIsm"
counter=0
while [ $counter -le 40 ]
do
var=$(echo $var | base64)
counter=$((counter + 1))
if [ $counter -eq 35 ]; then
echo "Character count at iteration 35: $(echo $var | wc -c)"
fi
done
Arguments, Variables, and Arrays
$ chmod +x cidr.sh
Special Variables
Internal Field Separator (IFS)
IFS | Description |
---|---|
$# | This variable holds the number of arguments passed to the script. (the script's name doesn't count) |
$@ | This variable can be used to retrieve the list of command-line arguments. |
$n | Each command-line argument can be selectively retrieved using its position. For example, the first argument is found at $1. |
$$ | The process ID of the currently executing process. |
$? | The exit status of the script. This variable is useful to determine a command's success. The value 0 represents successful execution, while 1 is a result of a failure. |
Arrays.sh
#!/bin/bash
domains=(www.inlanefreight.com ftp.inlanefreight.com vpn.inlanefreight.com www2.inlanefreight.com)
echo ${domains[0]}
#!/bin/bash
domains=("www.inlanefreight.com ftp.inlanefreight.com vpn.inlanefreight.com" www2.inlanefreight.com)
echo ${domains[0]}
Comparison Operators
String Operators
Operator | Description |
---|---|
== | is equal to |
!= | is not equal to |
< | is less than in ASCII alphabetical order |
> | is greater than in ASCII alphabetical order |
-z | if the string is empty (null) |
-n | if the string is not null |
It is important to note here that we put the variable for the given argument ($1
) in double-quotes ("$1"
). This tells Bash that the content of the variable should be handled as a string. Otherwise, we would get an error.
#!/bin/bash
# Check the given argument
if [ "$1" != "HackTheBox" ]
then
echo -e "You need to give 'HackTheBox' as argument."
exit 1
elif [ $# -gt 1 ]
then
echo -e "Too many arguments given."
exit 1
else
domain=$1
echo -e "Success!"
fi
String comparison operators "< / >
" works only within the double square brackets [[ <condition> ]]
. We can find the ASCII table on the Internet or by using the following command in the terminal. We take a look at an example later.
$ man ascii
Integer Operators
Operator | Description |
---|---|
-eq | is equal to = |
-ne | is not equal to != |
-lt | is less than < |
-le | is less than or equal to <= |
-gt | is greater than > |
-ge | is greater than or equal to >= |
#!/bin/bash
# Check the given argument
if [ $# -lt 1 ]
then
echo -e "Number of given arguments is less than 1"
exit 1
elif [ $# -gt 1 ]
then
echo -e "Number of given arguments is greater than 1"
exit 1
else
domain=$1
echo -e "Number of given arguments equals 1"
fi
File Operators
Operator | Description |
---|---|
-e | if the file exist |
-f | tests if it is a file |
-d | tests if it is a directory |
-L | tests if it is if a symbolic link |
-N | checks if the file was modified after it was last read |
-O | if the current user owns the file |
-G | if the file’s group id matches the current user’s |
-s | tests if the file has a size greater than 0 |
-r | tests if the file has read permission |
-w | tests if the file has write permission |
-x | tests if the file has execute permission |
#!/bin/bash
# Check if the specified file exists
if [ -e "$1" ]
then
echo -e "The file exists."
exit 0
else
echo -e "The file does not exist."
exit 2
fi
Boolean and Logical Operators
We get a boolean value "false
" or "true
" as a result with logical operators. Bash gives us the possibility to compare strings by using double square brackets [[ <condition> ]]
. To get these boolean values, we can use the string operators. Whether the comparison matches or not, we get the boolean value "false
" or "true
".
#!/bin/bash
# Check the boolean value
if [[ -z $1 ]]
then
echo -e "Boolean value: True (is null)"
exit 1
elif [[ $# > 1 ]]
then
echo -e "Boolean value: True (is greater than)"
exit 1
else
domain=$1
echo -e "Boolean value: False (is equal to)"
fi
Logical Operators
Operator | Description |
---|---|
! | logical negotation NOT |
&& | logical AND |
|| | logical OR |
#!/bin/bash
# Check if the specified file exists and if we have read permissions
if [[ -e "$1" && -r "$1" ]]
then
echo -e "We can read the file that has been specified."
exit 0
elif [[ ! -e "$1" ]]
then
echo -e "The specified file does not exist."
exit 2
elif [[ -e "$1" && ! -r "$1" ]]
then
echo -e "We don't have read permission for this file."
exit 1
else
echo -e "Error occured."
exit 5
fi
Exercise Script
#!/bin/bash
var="8dm7KsjU28B7v621Jls"
value="ERmFRMVZ0U2paTlJYTkxDZz09Cg"
for i in {1..40}
do
var=$(echo $var | base64)
# Check if "var" contains "value" AND has more than 113450 characters
if [[ "$var" == *"$value"* ]] && [[ $(echo -n "$var" | wc -c) -gt 113450 ]]
then
echo $var | tail -c 20
# output: 2paTlJYTkxDZz09Cg==
# echo "Last 20 characters: ${var: -20}"
# output: U2paTlJYTkxDZz09Cg==
fi
done
Arithmetic
Arithmetic Operators
Operator | Description |
---|---|
+ | Addition |
- | Subtraction |
* | Multiplication |
/ | Division |
% | Modulus |
variable++ | Increase the value of the variable by 1 |
variable-- | Decrease the value of the variable by 1 |
Arithmetic.sh
#!/bin/bash
increase=1
decrease=1
echo "Addition: 10 + 10 = $((10 + 10))"
echo "Subtraction: 10 - 10 = $((10 - 10))"
echo "Multiplication: 10 * 10 = $((10 * 10))"
echo "Division: 10 / 10 = $((10 / 10))"
echo "Modulus: 10 % 4 = $((10 % 4))"
((increase++))
echo "Increase Variable: $increase"
((decrease--))
echo "Decrease Variable: $decrease"
#!/bin/bash
htb="HackTheBox"
# calculate the length of the variable
echo ${#htb}
Input and Output
Output Control
The tee utility ensures that we see the results we get immediately and that they are stored in the corresponding files.
Flow Control - Loops
For Loops
for variable in file1 file2 file3
do
echo $variable
done
for ip in "10.10.10.170 10.10.10.174 10.10.10.175"
do
ping -c 1 $ip
done
While Loops
WhileBreaker.sh
#!/bin/bash
counter=0
while [ $counter -lt 10 ]
do
# Increase $counter by 1
((counter++))
echo "Counter: $counter"
if [ $counter == 2 ]
then
continue
elif [ $counter == 4 ]
then
break
fi
done
Until Loops
There is also the until
loop, which is relatively rare. Nevertheless, the until
loop works precisely like the while
loop, but with the difference:
- The code inside a
until
loop is executed as long as the particular condition isfalse
.
The other way is to let the loop run until the desired value is reached. The "until
" loops are very well suited for this. This type of loop works similarly to the "while
" loop but, as already mentioned, with the difference that it runs until the boolean value is "False
."
#!/bin/bash
counter=0
until [ $counter -eq 10 ]
do
# Increase $counter by 1
((counter++))
echo "Counter: $counter"
done
Exercise script
#!/bin/bash
# Decrypt function
function decrypt {
MzSaas7k=$(echo $hash | sed 's/988sn1/83unasa/g')
Mzns7293sk=$(echo $MzSaas7k | sed 's/4d298d/9999/g')
MzSaas7k=$(echo $Mzns7293sk | sed 's/3i8dqos82/873h4d/g')
Mzns7293sk=$(echo $MzSaas7k | sed 's/4n9Ls/20X/g')
MzSaas7k=$(echo $Mzns7293sk | sed 's/912oijs01/i7gg/g')
Mzns7293sk=$(echo $MzSaas7k | sed 's/k32jx0aa/n391s/g')
MzSaas7k=$(echo $Mzns7293sk | sed 's/nI72n/YzF1/g')
Mzns7293sk=$(echo $MzSaas7k | sed 's/82ns71n/2d49/g')
MzSaas7k=$(echo $Mzns7293sk | sed 's/JGcms1a/zIm12/g')
Mzns7293sk=$(echo $MzSaas7k | sed 's/MS9/4SIs/g')
MzSaas7k=$(echo $Mzns7293sk | sed 's/Ymxj00Ims/Uso18/g')
Mzns7293sk=$(echo $MzSaas7k | sed 's/sSi8Lm/Mit/g')
MzSaas7k=$(echo $Mzns7293sk | sed 's/9su2n/43n92ka/g')
Mzns7293sk=$(echo $MzSaas7k | sed 's/ggf3iunds/dn3i8/g')
MzSaas7k=$(echo $Mzns7293sk | sed 's/uBz/TT0K/g')
flag=$(echo $MzSaas7k | base64 -d | openssl enc -aes-128-cbc -a -d -salt -pass pass:$salt)
}
# Variables
var="9M"
salt=""
hash="VTJGc2RHVmtYMTl2ZnYyNTdUeERVRnBtQWVGNmFWWVUySG1wTXNmRi9rQT0K"
# Encoding "var" 28 times using base64
for i in {1..28}
do
# cannot use echo -n $var | base64
var=$(echo $var | base64)
done
# Assign salt when i reaches 28
salt=$(echo -n $var | wc -c)
echo "The salt value is $salt."
# Check if $salt is empty
if [[ ! -z "$salt" ]]
then
decrypt
echo $flag
else
exit 1
fi
echo "string" | wc -c counts the exact number of characters in the string returned by echo, that is "string" plus a line break appended by echo, so 7 in that case. ${#var} returns the exact number of characters contained in the var variable. You can obtain the same behavior by specifying the -n flag to echo, which gets rid of the trailing \n.
$ echo 9M | base64
OU0K
$ echo -n 9M | base64
OU0=
The sed (Stream Editor) command is used for text processing in Unix/Linux. It allows you to:
- Search and replace text
- Filter and modify strings
- Delete or insert lines
- Extract specific parts of text
Flow Control - Branches
Syntax - Switch-Case
case <expression> in
pattern_1 ) statements ;;
pattern_2 ) statements ;;
pattern_3 ) statements ;;
esac
Functions
Method 1 - Functions
function name {
<commands>
}
Method 2 - Functions
name() {
<commands>
}
Parameter Passing
PrintPars.sh
#!/bin/bash
function print_pars {
echo $1 $2 $3
}
one="First parameter"
two="Second parameter"
three="Third parameter"
print_pars "$one" "$two" "$three"
Return Values
When we start a new process, each child process (for example, a function in the executed script) returns a return code to the parent process (bash shell through which we executed the script) at its termination, informing it of the status of the execution. This information is used to determine whether the process ran successfully or whether specific errors occurred. Based on this information, the parent process can decide on further program flow.
Return Code | Description |
---|---|
1 | General errors |
2 | Misuse of shell builtins |
126 | Command invoked cannot execute |
127 | Command not found |
128 | Invalid argument to exit |
128+n | Fatal error signal "n " |
130 | Script terminated by Control-C |
255\* | Exit status out of range |
To get the value of a function back, we can use several methods like return
, echo
, or a variable
. In the next example, we will see how to use "$?
" to read the "return code
," how to pass the arguments to the function and how to assign the result to a variable.
Return.sh
#!/bin/bash
function given_args {
if [ $# -lt 1 ]
then
echo -e "Number of arguments: $#"
return 1
else
echo -e "Number of arguments: $#"
return 0
fi
}
# No arguments given
given_args
echo -e "Function status code: $?\n"
# One argument given
given_args "argument"
echo -e "Function status code: $?\n"
# Pass the results of the funtion into a variable
content=$(given_args "argument")
echo -e "Content of the variable: \n\t$content"
Debugging
$ bash -x CIDR.sh
# display the output in more details
$ bash -x -v CIDR.sh