_________________________________________________________

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