Chapter 10: Shellcoding Problems

image from book  Download CD Content

An attempt at implementing shellcode inevitably makes the attacker face numerous obstacles and limitations. Some of them can be bypassed using cunning hacks, and others must be tolerated and interpreted as an integral part of the cruel and blind forces of nature.

Invalid Characters

Overflowing string buffers ( especially those related to console input and the keyboard) imply strict limitations on their contents. Among these limitations, the most disappointing one is that the zero character can be encountered only once within a string, namely, at its end (although, this limitation is not in force for Unicode strings). This complicates the process of preparing the shellcode and doesn't allow you to choose arbitrary target addresses. The code that doesn't use 0 bytes is called zero-free code, and the process of preparing such code is, in fact, hacking aerobatics.

The Art of Overwriting Addresses

Consider a situation, in which the overflowing buffer is followed by a vulnerable pointer to the called function (or by a this pointer) and the information, in which the intruder is interested, is located at the following address: 00401000h . Because only one character overwrites the pointer, in other words, the zero character, it is impossible to write the required value directly and the hacker must invent various tricks.

First, in the 32-bit operating system, the family to which Windows NT and most UNIX clones belong, the stack, the data, and the code are located in a narrow range of addresses: 00 100000h to ~ 00 x00000h . This means that at least one zero character is already present ” this is the most significant byte of the address. Depending on the processor architecture, it can reside both at the least significant and at the most significant addresses. The family of x86 processors stores it in the most significant addresses, which is a favorable circumstance from the attacker's point of view, because it allows him or her to force the vulnerable application to any 00XxYyZzh address, provided that Xx , Yy , and Zz are nonzero values.

Hackers use a creative approach. An urgently-needed 00401000h address is not available directly, even in theory. However, perhaps something else will do. For example, it is possible to start function execution from a byte other than the first one. Functions with classical prologue (which are prevalent ) start with the push ebp instruction, which stores the value of the EBP register on the stack. If this hasn't been done, then the function will inevitably crash when exiting. However, this will be of no importance to the hacker, because by that time the function would already have carried out its mission (that is, everything that the attacker wanted it to do). The situation would be much worse if the parasitic zero character is encountered in the middle of address or even worse if it is encountered twice, for example: 0050 0000 h .

In some cases, the following method of correcting the existing addresses might be helpful. Assume that the pointer being overwritten contains the 0050 00 FAh address. In this case, to achieve the desired result, the attacker must overwrite only the least significant symbol of the address, by replacing FAh with the zero character.

As a variant, it is possible to look for the jump (or call) command to the required function in the disassembled listing. There is a nonzero probability that such a command would be located at a "correct" address. Provided that the target function is called more than once, and from different locations (which usually is the case), then there is a high probability that at least one of those addresses will be suitable.

It is also necessary to account for some functions not always cutting off the <CR> character from the input string, thus totally disarming attackers . In this case, direct input of target addresses becomes practically impossible. (What of interest can be found at addresses such as 0A XxYyh? ) Correction of existing addresses, although it remains possible, becomes useless because it is unlikely that the attacker would encounter a suitable pointer (the attacker in this case is limited by the ??000A address, where ?? is the previous value of the vulnerable pointer). The only way out in this case is to fully overwrite all 4 bytes of the pointer, along with 2 further bytes. After doing so, the attacker will gain the possibility of forcing any application to accept any FfXxYyZz address, where Ff is greater than 00h . Usually, this region belongs to the operating system and system drivers. With a nonzero probability, here it is possible to find a command that passes control by the target address. In the simplest case, this will be the call address or the jump address (which occurs rarely). In a more general case, this will be call register or jmp register . Both are 2-byte commands ( FF Dx and FF Ex , respectively). There are hundreds of such sequences in memory. The most important issue is that at the moment of the call to the overwritten pointer, and consequently at the moment of passing control to the call register /jmp register command, the chosen register would contain the required target address.

Built-in functions for console input interpret some characters in a specific way. For example, the character with the 008 code deletes the character before the cursor. When such characters are encountered, the function crashes before these characters are written into the vulnerable buffer. It is also necessary to be prepared to encounter a situation, in which the target program checks the correctness of the input data and discards all nonprintable characters or (even worse) converts them to uppercase or lowercase letters . The probability of a successful attack (if this is not a DoS attack) becomes negligibly small.

Preparing the shellcode. When the overflowing string buffer is used for transmitting binary shellcode (such as the head of the worm), the problem of zero characters is urgent. Zero characters are present in machine commands, as well as at the ends of the strings passed to the system functions as main arguments (as a rule, these are cmd.exe or /bin/sh) .

To eliminate zeros in operands of machine instructions, hackers have to resort to address arithmetic. For example, mov eax, 01h (B8 00 00 00 01) is equivalent to eax, eax/inc eax (33 C0 40) . By the way, the latter variant is even shorter. Text strings (along with the terminating zero) also can be formed directly on the stack, for example, as shown in Listing 10.1.

Listing 10.1: Placing string arguments in the stack with dynamic generation of the terminating zero
image from book
 00000000: 33C0            XOR        EAX, EAX 00000002: 50              PUSH       EAX 00000003: 682E657865      PUSH       06578652E ; "exe." 00000008: 682E636D64      PUSH       0646D632E ; "dmc." 
image from book
 

As a variant, it is possible to use the xor eax, eax/mov [xxx], eax command that inserts the terminating zero into position xxx , where xxx is the address of the end of the text string computed using some suitable method (see the section " Searching for Yourself " ).

Encryption of the shellcode (which in most cases is reduced to the trivial XOR operation) is a more efficient means of eliminating zeros. The main problem is selecting a suitable encryption key, because no byte of the code being encrypted must turn into a zero character. Because x XOR x == 0 , any single-byte key that doesn't coincide with any byte of the shellcode will do. If the shellcode contains the entire set of all possible values, from 00h to FFh , then it is necessary to increase the key length to a word or double word, choosing it so that no byte of its range matches any of the bytes being encrypted. How do you build such a range? (Don't suggest the brute-force method.) This isn't too difficult. It is enough to compute the frequency, at which each of the shellcode characters is encountered, choose the four characters that are the least likely to occur, write their offsets from the start of the shellcode in a column, and compute the remainder from division by four. Again, write the resulting values in a column, choosing the ones that are not encountered there. These will be the positions of the chosen byte in the key. If you do not understand, don't worry. Consider this algorithm on a practical example.

Assume that in the code the symbols 69h, ABh, CCh, and DDh are the least likely to occur. They appear in the positions shown in Listing 10.2.

Listing 10.2: The table of offsets of the " low-frequency " characters counted from the beginning of the encrypted code
image from book
 Character        Offsets of the positions of all entries -------------------------------------------------------- 69h              04h, 17h, 21h ABh              12h, 1Bh, 1Eh, 1Fh, 27h CCh              01h, 15h, 18h, 1Ch, 24h, 26h DDh              02h, 03h, 06h, 16h, 19h, 1Ah, 1Dh 
image from book
 

After computing the remainder from division by four over each of the offsets, the sequence of values in Listing 10.3 will be obtained.

Listing 10.3: The table of remainders from offset division by four
image from book
 Character        Remainder from division of the offsets by four --------------------------------------------------------------- 69h              00h, 03h, 00h ABh              02h, 03h, 02h, 03h, 03h CCh              01h, 01h, 00h, 00h, 00h, 02h DDh              02h, 03h, 02h, 02h, 01h, 02h, 01h 
image from book
 

Thus, you have obtained four data sequences, representing the positions of overlapping of the encrypted character over the range in which it turns to zero. Zero characters are not allowed; therefore, it is necessary to record all values that are not encountered in each data row, as shown in Listing 10.4.

Listing 10.4: The table of suitable positions of the key characters in the range
image from book
 Character        Suitable positions in the range ------------------------------------------------ 69h              01h, 02h ABh              00h, 01h CCh              03h DDh              00h 
image from book
 

Now, it becomes possible to compose the range on the basis of obtained characters, combining them to ensure that every character is encountered only once within the range. Look, the DDh character can be encountered only in position 00h, the CCh character can appear only in position 03h , and the remaining two characters can be in either of the two remaining positions. This means that the required combination will be either DDh ABh 69h CCh or DD 69hABh 69h . If it turns out to be impossible to choose the range, then it will be necessary to increase its length. There is no need to carry out all of the computations manually. This work can be delegated to the computer.

Before passing control to the encrypted code, it must first be decrypted. This task is delegated to the decryptor, which must satisfy the following requirements:

  • The decryptor must be as compact as possible.

  • The decryptor must not depend on its position (in other words, it must be fully relocatable).

  • The decryptor must not contain zero characters in its body.

For example, the Love San worm proceeds as shown in Listing 10.5.

Listing 10.5: Disassembled listing of the shellcode decryptor taken from the Love San worm
image from book
 .data:0040458B EB 19          JMP   short loc_4045A6 .data:0040458B ; Jump into the middle of the code .data:0040458B ; to carry out call back (call forward .data:0040458B ; contains invalid zero characters). .data:0040458D .data:0040458D sub_40458D     proc  near ; CODE XREF: sub_40458D + 19p .data:0040458D .data:0040458D 5E             POP   ESI  ; ESI := 4045ABh .data:0040458D ; Pop the return address from the stack, .data:0040458D ; which was placed there .data:0040458D ; by the call command. .data:0040458D ; This is necessary to determine .data:0040458D ; the decryptor's location in the memory. .data:0040458E 31 C9          XOR   ECX, ECX .data:0040458E ; Reset the ECX register to zero. .data:0040458E ; .data:00404590 81 E9 89 FF FF SUB   ECX, -77h .data:00404590 ; Increase ECX by 77h (decrease ECX by -77h). .data:00404590 ; The XOR ECX, ECX/SUB ECX, -77h combination .data:00404590 ; is equivalent to MOV ECX, 77h, .data:00404590 ; except that its machine representation .data:00404590 ; doesn't contain zeros. .data:00404596 .data:00404596 loc_404596:            ; CODE XREF: sub_40458D + 15j .data:00404596 81 36 80 BF 32 XOR   dword ptr [ESI], 9432BF80h .data:00404596 ; Decrypt the next double word using a specially .data:00404596 ; selected range. .data:00404596 ; .data:0040459C 81 EE FC FF FF SUB   ESI, -4h .data:0040459C ; Increase ESI by 4h (go to the next double word). .data:0040459C ; .data:004045A2 E2 F2          LOOP  loc_404596 .data:004045A2 ; Loop until there is something to decrypt. .data:004045A2 ; .data:004045A4 EB 05             JMP   short loc_4045AB .data:004045A4 ; Pass control to the decrypted shellcode. .data:004045A4 ; .data:004045A6 loc_4045A6:                     ; CODE XREF:   j .data:0040458B ; .data:004045A6 E8 E2 FF FF FF    CALL          sub_40458D .data:004045A6 ; Jump backward, pushing the return address .data:004045A6 ; (the address of the next executable instruction) .data:004045A6 ; to the top of the stack, then pop it .data:004045A6 ; into the ESI register, which is equivalent to .data:004045A6 ; MOV ESI, EIP; however, there is no such command .data:004045A6 ; in the x86processor language. .data:004045A6 ; .data:004045AB ; Start of the decrypted code 
image from book
 


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