What You Need to Know

Optimizing Shellcode Development

The first exploit you write will be the most difficult and tedious . As you accumulate more exploits and more experience, you will learn to optimize various tasks in order to reduce the time between finding a bug and obtaining your nicely packaged exploit. This section is a brief attempt to distill our techniques into a short, readable guide to optimizing the development process.

Of course, the best way to speed shellcode development is to not actually write the shellcode ”use a syscall proxy or proglet mechanism instead. Most of the time, however, a simple static exploit is easiest thing to do, so let's talk about how to optimize that and improve its quality.

Plan the Exploit

Before you rush blindly into writing an exploit, it is a good idea to have a firm plan of the steps you'll take to exploit the bug. In the case of a vanilla stack overflow on the Windows platform, the plan might look like the following (depending on how you personally would write this kind of exploit):

  1. Determine offset of bytes that overwrite saved return address.

  2. Determine location of payload relative to registers. (Is ESP pointing at our buffer? Any other registers?)

  3. Find a reliable jmp/call <register> offset for (a) the product version or (b) the various Windows versions and service packs you're targeting.

  4. Create small test shellcode of nops to establish whether corruption is taking place.

  5. If there is corruption, insert jmps into payload to avoid corrupted areas. If there is no corruption, substitute actual shellcode.

Write the Shellcode in Inline Assembler

This trick can save you a large amount of time. Most published exploits contain incomprehensible streams of hex bytes encoded in C string constants. This doesn't help if you need to insert a jmp to avoid a corrupt part of the stack or if you want to make a quick modification to your shellcode. Instead of C constants, try something like the following (this code is for Visual C++, but a similar technique works for gcc):

 char *sploit() {       asm     {           ; this code returns the address of the start of the code           jmp get_sploit get_sploit_fn:           pop eax           jmp got_sploit get_sploit:           call get_sploit_fn ; get the current address into eax            ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;     ; Exploit ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;     ; start of exploit               jmp get_eip     get_eip_fn:               pop edx           jmp got_eip     get_eip:               call get_eip_fn   ; get the current address into edx     call_get_proc_address:               mov ebx, 0x01475533     ; handle for loadlibrary           sub ebx, 0x01010101           mov ecx, dword ptr [ebx] 

And so on. Writing code this way has several advantages:

  • You can comment the assembler code easily, which helps when you need to modify your shellcode six months later.

  • You can debug the shellcode and test it, with comments and easy breakpointing, without actually firing off the exploit. Breakpointing is useful if your exploit does more than just spawn a shell.

  • You can easily cut and paste sections of shellcode from other exploits.

  • You don't need to go through an arcane cut and pasting exercise every time you want to change the code ”simply change the assembler and run it.

Of course, writing the exploit this way changes the harness slightly, so you need a method for determining the length of the exploit. One method is to avoid using instructions that result in null bytes, and then paste instructions at the end of the shellcode.

 add         byte ptr [eax],al 

Remember, the above assembles to two null bytes. The harness can then simply do a strlen to find the exploit length.

Maintain a Shellcode Library

The quickest way to write code is to cut and paste from code that already works. If you're writing an exploit, it doesn't matter so much whether the code you're cutting is your own or someone else's, as long as you understand exactly what it's doing. If you don't understand what a piece of shellcode is doing, it is probably quicker in the long run to write something to perform that task yourself, because you'll then be able to modify it more easily.

Once you have a few working exploits, you will tend to settle into using the same generic payloads, but it is always useful to have other, more complex codes nearby for easy reference. Simply put code snippets into some easily searchable form, such as a hierarchy of directories containing text files. A quick grep and you've got the code you need.

Make It Continue Nicely

Continuation of execution is an exceptionally complex subject, but it's key to writing high-quality exploits. Here is a quick list of approaches to the problem, along with other useful information:

  • If you terminate the target process, does it get restarted? If so, call exit() or ExitProcess() or TerminateProcess() in Windows.

  • If you terminate the target thread, does it get restarted? If so, call ExitThread() , TerminateThread() , or the equivalent. This method works extremely well if you're exploiting a DBMS, because they tend to use pools of worker threads. (Oracle and SQL Server both do this.)

  • If you have a heap overflow, can you repair the heap? This is kind of tricky, but this book lists some good pointers.

In terms of restoring the flow of control, you have a few alternatives:

Trigger an exception handler. Check for exception handlers first on the general principle that the easiest code to write is the code you don't write. If the target process already has a full-featured exception handler that cleans up nicely and restarts everything, why not just call it, or trigger it by causing an exception?

Repair the stack and return to parent. This technique is tricky, because there's probably information on the stack that you can't easily obtain by searching memory. However, this method is possible in some cases. The advantage is that you can ensure that you have no resource leakage. Basically, you find the parts of the stack that have been overwritten when you gained control, restore them to the values they had before you gained control, and run ret .

Return to ancestor . You can normally employ this method by adding a constant to the stack and calling ret . If you examine the call stack at the point where you obtain control, you'll probably find some point in the call tree to which you can ret without a problem. This works well, for example, in the SQL-UDP bug (that was used by the SQL Slammer worm). You will probably leak some resources, however.

Call ancestor. In a pinch , you might be able to simply call a procedure high up in the call tree, for example, the main thread procedure. In some applications, this works nicely. The downside is that you're likely to leak a lot of resources (sockets, memory, file handles) that might make the program unstable later.

Make the Exploit Stable

It is a good idea to ask yourself a series of questions once you've got the exploit working, so that you can determine whether you need to keep working to make it more stable. You should be aiming for industrial-strength exploits that will work in any environment and won't harm the target host in any undesirable ways. This is a good idea in general, but also helps cut overall development time; if you do a good job the first time, you won't have to keep revising your exploit every time a problem occurs.

Here is a quick list; you might want to add more of your own questions:

  • Can you run your exploit against a host more than once?

  • If you script your exploit and repeatedly run it against a single host, does it fail at some point? Why?

  • Can you run multiple copies of your exploit against a host simultaneously ?

  • If you have a Windows exploit, does it work across all service packs of the target OS?

  • Does it work across other Windows OSes? NT/2000/XP/2003?

  • If you have a Linux exploit, does it work across multiple distributions?

  • Do you require users to enter a set of offsets in order for your exploit to work? If so, consider hardcoding a set of common platform offsets in your exploit and allowing the user to select based on a friendly name . Even better, use a technique that makes the exploit more platform independent, such as deriving the addresses of LoadLibrary and GetProcAddress from the PE header in Windows, or not relying on distribution-specific behaviors in Linux.

  • What happens if the target host has a well-configured firewall script? Does your exploit hang the target daemon if IPTables or (on Windows) an IPSec filter ruleset blocks the connection?

  • What logs does it leave, and how can you clean them up?

Make It Steal the Connection

If you're exploiting a remote bug (and if not, why not?), it's best to reuse the connection on which you came in, the one for your shell, syscall proxy data stream, etc. Here are some hints for doing this:

  • Breakpoint the common socket calls ” accept , recv , recvfrom , send , sendto ”and look at where the socket handle is stored. In your shellcode, parse out the handle and reuse it. This might involve using a specific stack or frame offset or possibly brute forcing by using getpeername to find the socket that you're talking to.

  • In Windows, you might want to breakpoint ReadFile and WriteFile as well, since they're sometimes used on socket handles.

  • If you don't have exclusive access to the socket, don't give up. Find out how access to the socket is being serialized and do the same thing yourself. For example, in Windows, the target process will probably be using an Event, Semaphore, Mutex, or Critical Section. In the first three cases, the threads in question will probably be calling WaitForSingle Object(Ex) or WaitForMultipleObjects(Ex) , and in the latter case it must be calling EnterCriticalSection . In all these cases, once you've established the handle (or critical section) that everyone is waiting on, you can wait for access yourself, and play nicely with the other threads.



The Shellcoder's Handbook. Discovering and Exploiting Security
Hacking Ubuntu: Serious Hacks Mods and Customizations (ExtremeTech)
ISBN: N/A
EAN: 2147483647
Year: 2003
Pages: 198
Authors: Neal Krawetz

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