Chapter 15: Compiling and Decompiling Shellcode

image from book  Download CD Content

Overview

True information wars are only beginning. Hackers work underground and brush up their skills. The number of security holes grows explosively; operating systems and server components of applications are patched nearly every day, rapidly becoming larger and more sophisticated. According to the outdated rules of the computing underground, viruses must be developed in Assembly language or even in machine code. Traditionalists simply do not respect those who try to use C, to speak nothing about Delphi. It is much better, they believe, for these hackers not to write viruses, at least at the professional level.

The efficiency of contemporary compilers has reached such a level that by the quality of the code generation they are quickly approaching Assembly language. If the hacker kills the start-up code, then compact, efficient, illustrative , and easily debugged code will be obtained. Hackers characterized as progressionists try to use high-level programming languages whenever and wherever possible, and they resort to Assembly language only when necessary.

Among all components of a worm, only shellcode must be written in Assembly language. The worm body and the payload can be excellently implemented in good old C. Yes, this approach violates 50-year-old traditions of virus writing. Blindly following traditions is not a creative approach! The world is ever-changing, and progressively thinking hackers change with it. Once upon a time, Assembly language (and, before Assembly, machine codes) was an inevitable necessity. Nowadays, both Assembly and machine code are a kind of a magical rite, which isolates all amateurs from the development of "right" viruses.

By the way, standard Assembly translators (such as TASM and MASM) also are not suitable for development of the worm's head. They are much closer to the high-level languages than to the assembler. The unneeded initiative and intellectual behavior of the translator do harm when developing shellcode. First, the hacker cannot see the results of translation of specific Assembly mnemonic. Thus, to find out if zeros are present, it is necessary to consult the manual on the machine commands from Intel or AMD or to carry out the full translation cycle every time. Second, legal Assembly tools do not allow a hacker to carry out a direct far call; consequently, the hacker is forced to specify it using the db directive. Third, control over the dump is principally unsupported and shellcode encryption must be carried out using third-party utilities. Therefore, for developing the worm's head, hackers frequently use HEX editors with a built-in encryptor, such as HIEW or QVIEW. In this case, the machine code of each entered assembly instruction is generated immediately, "on the fly," and, if the translation result is not satisfactory, the hacker can immediately try several other variants. On the other hand, such an approach is characterized by several serious drawbacks.

To begin with, it is necessary to mention that machine code entered using HEX editor practically cannot be edited. Missing a single machine command might cost the hacker an entire day of wasted time and effort. This is because to insert the missing command into the middle of the shellcode the hacker must shift all other instructions and recompute their offsets again. To tell the truth, it is possible to proceed as follows : Insert the jmp instruction pointing to the end of the shellcode into the position of the missing machine command; move the contents overwritten by the jmp command to the end of the shellcode, where the jmp instruction pointed; add the required number of machine commands; and then use another jmp to return control to the previous position. However, such an approach is error-prone . Its application area is more than limited, because only few processor architectures support a forward jmp that doesn't contain parasitic zeros in its body.

Furthermore, HIEW, like most HEX editors, doesn't allow comments, which complicates and slows the programming process. If meaningful symbolic names are missing, the hacker will have to memorize and recall what has recently placed into, say, the [ebp-69] memory cell and whether [ebp-68] was meant instead of [ebp-69] . One misprint would be enough to make the hacker spend the entire day determining why the shellcode became unusable.

Note  

QVIEW is one of the few HEX editors allowing Assembly instructions with comments, which are stored in a special file.

Therefore, experienced hackers prefer to proceed as follows: They enter small fragments of the shellcode in HIEW and then immediately move them into TASM or MASM, using the db directive when necessary. Note that in this case the hacker would have use of this instruction excessively, because most Assembly tricks can be implemented only this way.

A typical Assembly template of the shellcode is shown in Listing 15.1.

Listing 15.1: A typical Assembly template for creating shellcode
image from book
 .386 .model flat .code start:         JMP    short begin get_eip:         POP    ESI         ;...         ; Shellcode here         ; ... begin:         CALL   get_eip end start 
image from book
 

To compile and link the code presented in Listing 15.1, use the following commands:

  • Compiling: ml.exe /c file name .asm

  • Linking: link.exe /VXD file name. obj

Translation of the shellcode is carried out in a standard way, and, in relation to MASM the command line might appear as shown: ml.exe /c file name .asm. The situation with linking is much more complicated. Standard linkers, such as Microsoft Linker ( ml ) bluntly refuse to translate the shellcode into a binary file. In the best case, such linkers would create a standard Portable Executable (PE) file, from which the hacker will have to manually cut the shellcode. The use of the /VXD command-line option of the linker considerably simplifies this task, because in this case the linker will cease to complain about the missing start-up code and will never attempt to insert it into the target file on its own. Furthermore, the task of cutting the shellcode from the resulting VXD file will also become considerably simpler, than doing this with a PE file. By default, the shellcode in VXD files is located starting from the 1000h address and continues practically until the end of the file. Note that 1 or 2 trailing bytes of the tail might be present there because of alignment considerations. However, they do not present any serious obstacle .

Having accomplished the linking, the hacker must encrypt the resulting binary file (provided that the shellcode contains an encryptor). Most frequently, hackers use HIEW for this purpose. Some individuals prefer to use an external encryptor, which usually can be created within 15 minutes, for example, as follows: fopen/fread/for(a = FROM_CRYPT; a < TO_CRYPT; a += sizeof(key)) buf[a] ^= key;/fwrite . Despite all advantages provided by HIEW, it is not free from drawbacks. The main disadvantage of its built-in encryptor is that it is impossible to fully automate shellcode translation. Thus, when it is necessary to frequently recompile the shellcode, the hacker will have to carry out a great deal of manual operations. Nevertheless, there still are lots of hackers who prefer to fuss with HIEW instead of programming an external encryptor that would automate the dull everyday hacking activities.

Finally, the prepared shellcode must be implanted into the main body of the worm, which usually represents a program written in C. The simplest, but not the best, approach consists of linking the shellcode as a usual OBJ file. As was already mentioned, this approach is not free from problems. First, to determine the length of the shellcode, the hacker will need two public labels ” one at the start of the shellcode and one at its end. The difference between their offsets will produce the required value. However, there is another, considerably more serious problem ” encrypting an OBJ file automatically. In contrast to the "pure" binary file, here it is impossible to rely on the fixed offsets. On the contrary, it is necessary to analyze auxiliary structures and the header. This won't make hackers happy. Finally, because of their nontext nature, OBJ files considerably complicate publishing and distribution of the source code of the worm. Therefore (or perhaps simply out of tradition), the shellcode is most frequently inserted into the program through string array because the C programming language supports the possibility of entering any HEX characters (except for zero, which serves as the string terminator).

This might be implemented, for example, as shown in Listing 15.2. It is not necessary to enter HEX codes manually. It is much easier to write a simple converter to automate this task.

Listing 15.2: An example illustrating insertion of the shellcode into the C program
image from book
 unsigned char x86_fbsd_read[] =         "\x31\xc0\x6a\x00\x54\x50\x50\xbO\x03\xcd\x80\x83\xc4"         "\xOc\xff\xff\xe4"; 
image from book
 

Now it is time to describe the problem of taming the compiler and optimizing programs. How is it possible to instruct the compiler not to insert start-up code and RTL code? This can be achieved easily ” it is enough not to declare the main function and enforce the linker to use a new entry point by using the /ENTRY command-line option.

Consider the examples presented in Listings 15.3 and 15.4.

Listing 15.3: Classical variant compiled in a normal way
image from book
 #include <windows.h> main() {         MessageBox(0, "Sailor", "Hello", 0); } 
image from book
 
Listing 15.4: An optimized variant of the program shown in Listing 15.3
image from book
 #include <windows.h> my_main() {         MessageBox(0, "Sailor", "Hello", 0); } 
image from book
 

The program presented in Listing 15.3 is the classical example. Being compiled with default settings ( cl.exe /Ox file name .c ), it will produce an executable file, taking 25 KB. Well, this is not bad? However, do not rush to premature conclusions. Consider an optimized version of the same program, shown in Listing 15.4.

This optimized version must be built as follows:

  • Compiling: cl.exe /c /Ox file .c

  • Linking: link.exe /ALIGN:32 /DRIVER /ENTRY:my_main /SUBSYSTEM:console file .obj USER32.lib

Thus, by slightly changing the name of the main program function and choosing optimal translation keys, it is possible to reduce the size of the executable file to 864 bytes. At the same time, the main part of the file will be taken by the PE header, import table, and interstices left for alignment. This means that when dealing with a fully functional, real-world application, this difference in size will become even more noticeable. However, even in this example the executable file was compressed more than 30 times ” without any Assembly tricks.

Exclusion of RTL leads to the impossibility of using the entire input/output subsystem, which means that it will be impossible to use most functions from the stdio library. Thus, the shellcode will be limited to API functions only.



Shellcoder's Programming Uncovered
Shellcoders Programming Uncovered (Uncovered series)
ISBN: 193176946X
EAN: 2147483647
Year: 2003
Pages: 164

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net