📄 29a-7.006
字号:
How to break the rules
with the class libraries
roy g biv / 29A
About the author:
Former DOS/Win16 virus writer, author of several virus families, including
Ginger (see Coderz #1 zine for terrible buggy example, contact me for better
sources ;), and Virus Bulletin 9/95 for a description of what they called
Rainbow. Co-author of world's first virus using circular partition trick
(Orsam, coded with Prototype in 1993). Designer of world's first XMS swapping
virus (John Galt, coded by RT Fishel in 1995, only 30 bytes stub, the rest is
swapped out). Author of world's first virus using Thread Local Storage for
replication (Shrug, see Virus Bulletin 6/02 for a description, but they call
it Chiton), world's first virus using Visual Basic 5/6 language extensions for
replication (OU812), world's first Native executable virus (Chthon), world's
first virus using process co-operation to prevent termination (Gemini, see
Virus Bulletin 9/02 for a description), world's first virus using polymorphic
SMTP headers (Junkmail, see Virus Bulletin 11/02 for a description), and
world's first viruses that can convert any data files to infectable objects
(Pretext). Author of various retrovirus articles (eg see Vlad #7 for the
strings that make your code invisible to TBScan). Went to sleep for a number
of years. This is my first virus for .NET. It is the world's first 32/64-bit
parasitic EPO .NET virus. :)
Object-oriented language programming
At the first look, object-oriented languages in the .NET framework (C#, C++,
JScript, and Visual Basic) seem to be very strict. There are no pointers in
JScript and Visual Basic, and they are limited in C# and C++. Type conversion
is also restricted.
BitConverter class
Imagine that we have an array of bytes (because only a byte array is allowed
for the buffer when accessing files in the .NET framework). We have read some
bytes from the file and now we want to check for the 'MZ' signature (assuming
little-endian machine). In C, we could use:
if (*((WORD *) &array[0]) == 0x5a4d)
Can we do that in JScript or Visual Basic? No. Can we do that in C++? Yes,
but only for unmanaged arrays, which cannot be used with the class libraries.
Can we do it in C#? No (really yes, but it requires "Unsafe coding", which
causes other problems). We can form the word from the array like this:
if (((array[1] << 8) + array[0]) == 0x5a4d) //C#, C++, JScript
if (((array(1) * &h100) + array(0)) = &h5a4d) then 'Visual Basic
but that requires lots of bytes of code each time to do that. Fortunately,
the class libraries contain a class that exposes methods for exactly this kind
of data access. The class is called BitConverter, and the method we can use
here is called ToInt16(). It takes two parameters: the array and the offset.
So now our code can be replaced by:
if (BitConverter.ToInt16(array, 0) == 0x5a4d) //C#, JScript
if (BitConverter::ToInt16(array, 0) == 0x5a4d) //C++
if (BitConverter.ToInt16(array, 0) = &h5a4d) then 'Visual Basic
Nice. The class exposes other methods, too, such as ToInt32() and ToInt64(),
and also versions for unsigned numbers (ToUInt16(), etc).
Marshal class
Now we want to write large values into a byte array. In C, we could use
*((DWORD *) &array[0]) = dword;
In C#, C++, and JScript, we can store a dword in the array like this:
array[0] = (Byte) (dword);
array[1] = (Byte) (dword >> 8);
array[2] = (Byte) (dword >> 0x10);
array[3] = (Byte) (dword >> 0x18);
In Visual Basic, we can store a dword in the array like this:
array(0) = dword mod &h100
array(1) = (dword \ &h100) mod &h100
array(2) = (dword \ &h10000) mod &h100
array(3) = (dword \ &h1000000) mod &h100
but that requires too many instructions and too many bytes of code. The
better way is to use the Marshal class. It allows us to copy data between
managed data types (.NET base types) and unmanaged data types (eg process
memory). It can also copy from one managed type to another managed type, but
this use is not documented. The method that we can use here is called
WriteInt32(). It takes three parameters: the array, the offset in the array,
and the value. So now our code can be replaced by:
Marshal.WriteInt32(array, 0, dword); //C#, JScript
Marshal::WriteInt32(array, 0, dword); //C++
Marshal.WriteInt32(array, 0, dword) 'Visual Basic
Much better. Accessing process memory is easy, too. The Process class exists
for that purpose. Let's get our module handle:
IntPtr ourbase = Process.GetCurrentProcess().MainModule.BaseAddress; //C#
IntPtr ourbase = Process::GetCurrentProcess()->get_MainModule()->get_BaseAddress(); //C++
var ourbase : IntPtr = Process.GetCurrentProcess().MainModule.BaseAddress //JScript
dim ourbase as IntPtr : Process.GetCurrentProcess().MainModule.BaseAddress 'Visual Basic
Great! But what's an IntPtr?
Marshal class again
The IntPtr type is a special managed type whose size is platform-specific.
That means that it is 32 bits large on a 32-bit platform, and 64-bits large on
a 64-bit platform. The Marshal methods require an IntPtr as the object to use
for reading and writing unmanaged memory. IntPtrs do not support operators
such as + and - (they are like void * in C and C++), but since the Marshal
methods require an offset, this is usually okay. For example, we can read the
lfanew field without trouble:
Int32 lfanew = Marshal.ReadInt32(ourbase, 0x3c); //C#
Int32 lfanew = Marshal::ReadInt32(ourbase, 0x3c); //C++
var lfanew : Int32 = Marshal.ReadInt32(ourbase, 0x3c) //JScript
dim lfanew as Int32 : Marshal.ReadInt32(ourbase, &h3c) 'Visual Basic
and then read bytes in the PE header:
Int32 tlsrva = Marshal.ReadInt32(ourbase, lfanew + 0xc0); //C#
Int32 tlsrva = Marshal::ReadInt32(ourbase, lfanew + 0xc0); //C++
var tlsrva : Int32 = Marshal.ReadInt32(ourbase, lfanew + 0xc0) //JScript
dim tlsrva as Int32 : tlsrva = Marshal.ReadInt32(ourbase, lfanew + &hc0) 'Visual Basic
But what if we want to copy any number of bytes? There is a Copy() method,
and it takes four parameters: the source array, the destination array, the
offset in destination array, and the number of bytes to copy. It does not
support an offset in the source array, so we need to find a way to apply the
+ operator to a IntPtr. Fortunately, the Marshal class exposes a method
called ReadIntPtr(), which can convert a Int32 to a IntPtr, but this usage is
also not documented. To make our pointer, we convert our old IntPtr into a
Int32 by using the ToInt32() method, then add the value that we need, then
pass the result to the ReadIntPtr() method, using an offset of 0:
IntPtr tlsva = Marshal.ReadIntPtr(ourbase.ToInt32() + tlsdata, 0); //C#
IntPtr tlsva = Marshal::ReadIntPtr(__box(ourbase.ToInt32() + tlsdata), 0); //C++
var tlsva : IntPtr = Marshal.ReadIntPtr(ourbase.ToInt32() + tlsdata, 0) //JScript
dim tlsva as IntPtr : tlsva = Marshal.ReadIntPtr(ourbase.ToInt32() + tlsdata, 0) 'Visual Basic
For 64-bit platforms, there is a ToInt64() method, and the ReadIntPtr() method
can also convert an Int64 to an IntPtr. Notice the use of __box for C++.
This is required to convert a value into a managed object, which can then be
passed to the class libraries.
Now we can use the Copy() method to copy bytes from the process memory to our
array. Let's copy 0x29a bytes ;) to offset 6:
Marshal.Copy(tlsva, array, 6, 0x29a); //C#, JScript
Marshal::Copy(tlsva, array, 6, 0x29a); //C++
Marshal.Copy(tlsva, array, 6, &h29a); 'Visual Basic
TLS and MSIL
I thought to make an interesting W32/MSIL hybrid by using TLS to write the
MSIL RVA in the PE header at runtime, but when the MSIL code is run, it fails
the StrongName signature verification. Then I tried storing a hard-coded fake
RVA in the file, and setting it correctly at runtime, but the MSIL code won't
run in that case. Finally, I stored the correct RVA and size in the file, but
if TLS code runs on exit, then one of the MSIL dlls waits infinitely for an
object that never signals and the application won't terminate. So don't use
TLS in MSIL files unless you can solve one of these problems. ;)
GetProcAddress()
.NET files import functions in a way that is simlar to Windows files. There
are AssemblyRefs which are the DLL names, and MemberRefs which are the
function names (there are also TypeRefs, and they hold the class names, but
they have no equivalent). Let us imagine that we want to display our image
base, using only standard library functions (mscorlib). The image base is the
BaseAddress property of the ProcessModule class. The ProcessModule class is
returned by the MainModule property of the Process class. The Process class
is in the System.Diagnostics namespace. The System.Diagnostics namespace is
exported by system.dll. Since system.dll is not a standard library, we need
to find how to load system.dll and get access to its methods, but without
using any function from system.dll to do that. We also have to rely on the
GetType() method, instead of casting the result, because casts use references
to the dll that exports the class. The .NET equivalent of LoadLibrary()
requires the full path of the file to load, if the file is not in the current
directory (this is similar to LoadLibrary()). System.dll is in the .NET
system directory (which is not the same as the Windows system directory), so
we need to find the .NET system directory. We can get the system directory
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -