Guide to (mostly) Harmless Hacking
Vol. 5 Programmers' Series
No. 4: A nice 'n' easy introduction into the bash shell
_________________________________________________________
By idle (idle@mailexcite.com)
with a little help from his friends (Meino :) (Carolyn Meinel)
The first in the Programmers' Series introduced you to some of
the very
basics of writing shell scripts. Creating files, executing commands
and so
on. This Guide will introduce you to additional important concepts,
but
this time using the bash shell.
Why is shell programming important for hacking? Let's say
you are trying
to get root control of a Hacker Wargame computer (see
http://www.happyhacker.org
for latest information on what computers are
legal to break into). You find an exploit shell script at some
place such
as http://www.rootshell.com
that looks ideal for doing the job -- but it
doesn't work! Maybe your problem was not with the exploit script,
but with
what shell you are using or how your shell is set up. This Guide
will help
you understand more about what makes the bash shell work and how to
make it
do what you want it to do.
******************************************************************
In this Guide you will learn about:
* The shell "environment"
* variables
* processes (parent and child processes)
* the bash prompt -- how to customize it
* tilde expansion
* aliases
* creating your bash startup (profile) file
* how to write scripts in bash
******************************************************************
******************************************************************
Newbie note: Bash is a Unix shell program. It takes all your
commands and
turns them into something a Unix type operating system can understand.
I
(Carolyn Meinel) recommend the tcsh shell, but if you can't use tcsh
right
now, bash is my second favorite shell. Bash stands for "Bourne
Again
Shell," a word play on the Bourne Shell, from which the bash shell
was
adapted. To find out whether you are currently using the bash
shell, in
your shell account give the command "env". It will include an
entry
"SHELL:bash" if you are already using it. If it shows a
different shell,
give the command "bash". If it gives an error message, you don't
have bash.
******************************************************************
You'll soon see that even your most basic shell in any kind of
Unix is
more powerful than DOS, and if you are short of ideas for scripts to
write,
hopefully this will open your mind a little.
Environment Variables
If you have any programming experience at all, or
you have been reading
the Guides like a good little hacker, then you will know about Variables.
Think of these as named pieces of your computer's memory which hold
values.
Depending upon the variables in question, you may or may not have immediate
control of their contents.
Another concept is your environment, which is what you
find when you give
the "env" command. Much like the real world you live in, your environment
in
your shell stores information such as the operating system you are
running,
the version, your home directory, a list of directories to look at
when you
want to run a program, and so on.
Some of the more common bash environment variables that
you will want to
know a bit about are:
PATH
this is a colon-separated list of directories
which bash uses to
search for commands. eg:
/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin:/usr/local/newstuff/progs
HOME
the current users home directory, eg:
/home/bob
CDPATH
a colon-seperated list of directories
to search when you issue the
cd command, eg:
/home:/usr/local/etc:/usr/local/games
if you had a directory under /usr/local/etc
called myGreatProg, you
could issue a cd myGreatProg on the command line and you would be taken
to
the first occurrence of myGreatProg in CDPATH. In this case,
/usr/local/etc/myGreatProg. When it comes down to it, this is not necessary,
it's just a nicety which can confuse you at times. Use with care!
MAILPATH
a colon-separated list of spool files
which bash checks periodically
for new mail, eg:
/usr/spool/mail/bob:/var/spool/bob
PS1 & PS2
primary and secondary prompt strings.
The primary prompt is used at
your standard interactive shell. ie; when you log in or you run bash.
The secondary prompt is used when bash
needs more input to complete a
command. for example, at the prompt, type:
cd \
bash will change the prompt to ">" or
whatever your PS2 is set to,
indicating that you need to type more before it can execute your
instruction. Try typing:
~
and after you press ENTER, the complete
command will have been "cd ~".
This takes you to your home directory.
The backslash "\" indicated
that the command continues on the next line. This can be used in your
scripts as well for long lines that you don't want to get too messy.
RANDOM
This is a nice variable which you can
call upon to give you a random
number. Assigning a number to this will allow you to generate a seed.
This
is a nifty idea, but unfortunately, it is not entirely random. After
executing this a few times, I noticed a pattern, so if you are going
to use
It, take care. Mind you, if you were going to write some really flash
crypto
routine, then you'd probably be using a programming language as opposed
to a
scripting language :)
To set variables, you can use the set command, eg:
set TEMP_VAR=cheese
But this will not export it into the environment
in bash. to do that,
you must use export, eg:
xport TEMP_VAR
You can, however, just use export, thus:
export TEMP_VAR=cheese
Processes
The concept of the environment now comes into full
effect. When you are
in your bash shell and you execute a command or script, that command
INHERITS it's parent process's environment. The shell in this case
is the
parent process, and the command or script you run is the child process.
This
child process can in turn start a child process of it's own. The concept
of
child and parent is relative to which process you are talking about.
Any
process that starts a child becomes that child's parent. Much like
in real
life. You are a child, but may eventually become a parent as well.
Init is the Mother Of All Processes. Think of it
as god if you are
religious. If you're not religious, think of it as a The Big
Bang. Or
anything else that pleases you.
To see how your children and their parents are doing,
use the command
'pstree', or if you don't have that, 'ps -f' to get a list of every
process
running and how it relates to init.
Are we going off on a tangent? Probably...anyway,
when you type a
command, you will notice a variable called $_ is being set. We'll see
soon
what this is actually doing.
Let's try an example. Type this:
set hi_temp="Hi, this is TEMP"
set | less
Note: if you don't have less, use more. ;)
You will see at this point, no variable called $hi_temp.
but at the
bottom, you should see
=hi_temp=Hi, This is TEMP
Then type:
set hi_test="Hi, this is TEST"
set | less
Take note there is now no reference to "hi_temp".
But one of the last
lines should read "_=hi_test=Hi, this is TEST."
The variable $_ is actually just a reminder of the
last command you
typed, not a place to remember set commands. You will find very quickly
that
unless you explicitly use export in the first instance in the shell
(not in
a script file), you will not be able to export to the environment.
This is
because the set command runs as a child process which eats it's own
variables.
You can prove this to yourself by doing the following:
set hi_test="Hi, this is TEST"
ls
set| grep "hi_test"
You will have nothing returned back. Whereas if you had done:
export hi_test="Hi, this is TEST"
ls
set | grep "hi_test"
Bash will return:
hi_test=Hi, this is TEST
Once the variable is in the environment, it can be
accessed by the child
processes. Note that the parent process will not inherit any variables
from
the children. This is very dangerous.
In scripts, if you are setting variables to be used locally
in the
script, you won't have to export. But if you wish for those processes
your
script calls, ie, another script, then you will have to export.
More on Variables
To reference variables on the command line or in
your scripts, you will
have to prepend the variable name with the String symbol, or the dollar
sign
($). Take the following simple example entered at the command line
export HI=hello
echo "$HI"
The output after the echo command is:
hello
If you had just typed 'echo "HI"' as your last command,
what do you
think would have been displayed? All those who replied 'HI' get to
buy
themselves a chocolate fish. Note that after the echo command, no matter
whether we are using variables or not, I use quote marks (") before
and
after the string to be echoed. This is best shown by example. Type
the
following at the prompt:
export HI="*"
echo $HI
You will get the equivalent of the current directory's
listing dumped
out. But if you go:
echo "$HI"
Bash will display
*
Which is what you wanted. IE; the CONTENTS of the
variable. bash likes
to substitute commands before executing. Remember when to use quotes
and you
will be fine.
To display a variable already set in place, you just use echo, eg:
echo "$PS1"
Will probably give you:
\h:\w\$
Don't worry if yours is slightly different. Now you
can create and
reference variables.
The Bashg Prompt
This is the equivalent to using the DOS PROMPT command
or the tcsh
set prompt= command. In bash, the variable that stores what your prompt
is to display is $PS1.
To change this you simply need to do something basic like:
export PS1="this is my computer: "
And next time you look, your prompt will read:
this is my computer:
This prompt will wait for you to enter a command.
You will notice that
this is now set. Press ENTER, type "ls", it stays. Great! But that
is a
little boring.
What you want it to do is display your user name
and/or the current
directory you are in so that you don't have to use pwd (the command
to show
your directory) constantly. Bash allows you to use switches (as
seen when
we did an echo $PS1 earlier) to display lots of useful info. Here is
a list
of the main ones:
\d date (format: ddd mmm dd - eg: Sat
Mar 12)
\h display your computers hostname
\n newline
\s shell name (ie: bash)
\t time (24 hour format: hh:mm:ss -
eg: 16:22:08)
\u current user
\w working directory (this changes on
the fly to the directory
that you change to).
\$ display $ character
\\ display \ character
There are others, but these will be the most useful
to you. Play around
and see what others you can find.
Tilde Expansion
Another neat thing that bash provides is what is
called Tilde Expansion.
The tilde key (~) on it's own is expanded out to the user's home directory,
for example. You can see this by issuing a 'cd ~' on your command line.
Here
is a list of expansions that bash will give you:
~ expands to
$HOME
~bob expands to bob's home directory
~+ $PWD
~- $OLDPWD
These expansions basically replace themselves on the command
line with
the contents of the second column above. Once the command has been
expanded,
bash executes it.
Aliases
One of the things I could not do without is the alias
command. If you
enter any aliases, bash will fill out the text of the alias name with
whatever the alias contents is before executing the command.
For example, when I use ls, I like to see my directory
listing in a
certain way. Colors, for example, are good. I also like slashes (/)
at the
end of directory names just to show they are directories, and I also
like to
view all files starting with a full stop (.) in my listings.
So instead of typing 'ls -Aop' every single time I want
a dir listing, I
just use an alias so that -Aop becomes my default. I enter the command:
alias ls='ls -Aop'
And from then on, bash uses that alias as my default
command. Whenever I
enter the alias name (in this case 'ls'), bash expands that out to
'ls
-Aop'automatically for me.
If I want to remove the alias, I use 'unalias ls'.
Alternatively, I can
opt to type 'unalias -a' to remove all aliases I have set.
Bash Startup File
If you have read the previous guides, you will know
that bash provides
you with some startup files which get run whenever you log into a bash
shell. The file I am talking about is your profile. This can be called
.bash_profile, .bash_login, or .profile and is located in your home
directory.
If you don't have this file, then just create it in your
favorite text
editor. Then you can insert any alias commands you want, and next time
you
log in, they will all be active.
Or, you can write the file now and use the 'source'
command to execute
your profile without logging out:
source .profile
(or substitute for ".profile" whatever your profile is called.)
**********************************************************************
You can kill your account -- warning!
Adding things to .profile should be tested ONLY with the "source
.profile" command!!! And only if you are satisfied with the result,
log out
and login again!!!
Reason: Imagine you wrote an "exit" at the wrong place
(i.e. not
preceding it with a comment-sign), you will never be able to login!
A solution is to login twice, one shell is good for testing
the other one
for recovering from errors!!! You can login twice with the command
"telnet
localhost". Then if you mess up in this second shell you will wind
up back
in your first shell and can delete the profile file before it kills
your
shell again. This saves the embarrassment of having to call tech
support
and confess to having made a big mess of your account.
**********************************************************************
Let's Script!
So now that we've got all that basic stuff out of the way, let's
get into
some serious shell scripting.
You know how to string commands together by now;
you just create a text
file, start listing any commands you want to execute, save the file,
change
its permissions (chmod +x <filename>) and viola, you have a shell
script.
But what if you want to do something more? What if
you want to pass
parameters on the command line? What if you want to check for files
existence before executing a command? What if you want to compare the
values
of two variables?
What indeed? You will need to know a bit about Loop
Constructs and
Conditional Constructs.
Loop constructs are:
until
while
for
Conditional constructs are:
if
case
I will list the syntaxes of each and give basic real
life examples to
help you along your way. None of these are exhaustive, but they will
give
you a good foothold to allow you to further research bash and learn
how to
kick serious butt.
One thing to remember with the following constructs
is that they are not
like most other languages you may be used to. When you do a test, you
are
actually executing commands. Whether these are commands built-in to
bash, or
whether they are other commands it doesn't matter, but you can't get
away
with direct comparisons of variables. You'll see in the examples. Also,
all
semi-colons (;) can be replaced with newlines.
********************************************************************
IMPORTANT NOTE:
bash is VERY space sensitive. Unless you copy any example
scripts
EXACTLY, you will most likely encounter problems.
********************************************************************
UNTIL
Syntax:
until <test>; do <commandlist>; done
Description:
<commandlist> will continue to be executed until <test>
has an exit status which is not zero (ie; until it's not
false)
Example script:
--------
#!/bin/bash
counter=1
until [ "$counter" = "10" ]
do
echo $counter
let counter+=1
done
--------
This script is very simple. It sets a variable called
"counter" and
initializes it to a value of 1. (We don't need to export it as it doesn't
need to be in the environment. Only this script will be using it.)
Then we want to echo a list from 1 to 10, so we do
the comparison. We
put the test arguments into square brackets so that bash knows we are
doing
a comparison. It will keep looping until the variable counter reaches
a
value of "10".
The line "let counter+=1" is saying "take the contents
of the variable
counter, add 1 to it, and save the value back into counter. We could
have
also typed:
let counter=$counter+1
Note where you use the $ preceding the variable names and
when you don't.
When we want to display the actual value in the variable we use $variable.
When we want to reference the variable for the reasons of changing
it, we
just use variable.
The 'let' command allows you to do mathematical equations.
Alternatively, you can wrap the equation in $[]. For the example above,
it
would be:
$[counter+=1]
This is know as Arithmetic Expansion, and I have
found it to be less
reliable than using the let command. Also, using let makes your scripts
that
much more readable in the future.
Mathematical Operators:
So you know what sort of mathematical operators you can use, here is a list:
- + unary minus and plus
! ~ logical and bitwise negation
* / % multiplication, division, remainder
+ - addition, subtraction
<< >> left and right bitwise shifts
<= >= < > comparison
== != equality and inequality
& bitwise AND
^ bitwise exclusive OR
| bitwise OR
&& logical AND
|| logical OR
= *= /= %= += -= <<= >>= &=
^= |=
assignment operators
WHILE
Syntax:
while <test>; do <commandlist>; done
Description:
do <commandlist> while <test> has an exit status
of zero. (ie; true)
Example Script:
--------
#!/bin/bash
counter=1
while [ "$counter"
!= "11" ]
do
echo $counter
let counter+=1
done
--------
We use the same script here using a different method
of testing. This
script will continue to loop while the variable $counter is not 11.
This
means that it will happily process the commandlist until $counter equals
11.
Note that this is a string comparison, not an integer (numerical) comparison.
FOR
Syntax:
for <var> [in <list>]; do <commandlist>; done
Description:
This will execute <commandlist> for each item in
<list>.
If the optional [in <list>] is left out, bash
will replace this with $@.
Example Script:
--------
#!/bin/bash
for i in cheese comics
"computers which don't suck" motorbikes
do
echo "I like $i"
done
--------
This will display:
I like cheese
I like comics
I like computers which don't suck
I like motorbikes
As you can see, each word is treated as a separate
parameter unless you
use quotes to block a group of words.
IF
Syntax:
if <test>; then
<commandlist>
elif <test2>; then
<commandlist>
elif <test...>; then
<commandlist>
else
<commandlist>
fi
Description: This command is rather more
complex in nature. Using elif,
you can include as many tests as you like. If the
script fails to find a match in any of the tests, it
will default to executing the commandlist under else.
The <test> has an extensive list of parameters you can
pass which allow you to test for various things
including whether files exist on your hard drive,
comparing 2 variables, etc.
Example script:
--------
#!/bin/bash
if [ -z $1 ]
then
echo "enter your favorite band name"
exit
fi
if [ "$1" = "manowar"
]
then
echo "the greatest band on the planet!"
elif [ "$1" = "DIO"
]
then
echo "awesome! they rock!"
elif [ "$1" = "KISS"
]
then
echo "ah. that's taking me back..."
else
echo "never heard of $1"
fi
--------
First, if you don't know it already, $1 indicated
the first parameter on
a command line. For example, if you enter 'cd /home/me', "/home/me"
is the
first parameter that you have entered. Inside the script, or the cd
command,
it could be referred to as $1. $2 is second, $3 is the third, etc.
One nice
ability is to use $0, which refers to the calling command - in this
case
"cd." Even batter is $* which stores all command line parameters, except
$0.
OK, next we introduce two kinds of tests in the if
construct. -z checks
to see if the user has entered any parameters, and promptly informs
them
when they forget to do so.
The next one which is used in the rest of the script
compares two
variables. Both are wrapped in quote marks to avoid bash trying to
execute
them as commands.
Another point to note is make sure to separate the
square brackets,
otherwise bash will get confused.
Other useful if lines:
if [ -f ~/.profile ]
(checks to see if you have a .profile file in you current home dir.
If so, the test will pass TRUE)
if [ ! -f ~/.profile ]
(checks to see if ~/.profile is NOT there and return TRUE if the
file is NOT there.)
Take note, this only checks for the existence of
files, not directories.
Can you figure out how to make it check for the existence of directories?
CASE
Syntax:
case <var> in
pattern [| pattern]) <commandlist> ;;
pattern [| pattern]) <commandlist> ;;
esac
Description:
The case command firstly expands whatever is in
<var> and compares it to all the patterns listed. If it finds a
match, it
will execute the appropriate <commandlist>. More than one
pattern can be
included per line by separating them with a pipe (|) symbol.
Also, you can use the pattern * to include everything else. Example script:
--------
#!/bin/bash
case $1 in
cheese | bread | meat)
echo "food!" ;;
viper | stingray | transam)
echo "car!" ;;
*)
echo "I don't know what it is!" ;;
esac
---------
This is fairly straight forward once you get to grips
with it. It pulls
in a parameter that you enter on the command line and tries to figure
out
what it is. If you enter "cheese", "bread" or "meat", our clever script
will
recognize this as a type of food and will tell you so.
If you enter "viper", "stingray" or "transam", it
will recognize it as a
car! Anything else leaves our poor program clueless.
There is also a command called select which was stolen
from the korn
shell. The syntax for this is very similar to case. See if you can
figure it
out and get it working. HINT - read "man bash".
There are many more features of bash, I have not
covered a great
portion, but that is part of the fun. Now go read the man page (this
text
should help you make sense out of it!) and see what other nifty little
features bash has.
I wrote this tutorial using bash version 1.14.7(1). I am about
to upgrade
to 2.02.01 or whatever the latest is to see what differences that gives
me.
Get the latest bash version via ftp at prep.ai.mit.edu
or one of its
mirror sites. This is a good time to brush up your search engine skills
;)
_______________________________________________________________________
Where are those back issues of GTMHHs and Happy Hacker Digests? Check
out
the official Happy Hacker Web page at http://www.happyhacker.org.
We are against computer crime. We support good, old-fashioned hacking
of the
kind that led to the creation of the Internet and a new era of freedom
of
information. So don't email us about any crimes you may have committed!
To subscribe to Happy Hacker and receive the Guides to (mostly) Harmless
Hacking, please email hacker@techbroker.com
with message "subscribe
happy-hacker" in the body of your message.
Copyright 1998 idle (idle@mailexcite.com).
You may forward, print out or
post this GUIDE TO (mostly) HARMLESS HACKING on your Web site as long
as you
leave this notice at the end.
_________________________________________________________
Carolyn Meinel
M/B Research -- The Technology Brokers
http://techbroker.com