Linux GUIs. The Chances.
Introduction |
Here is the investigation of Wordperfect 7.0 for linux, an important tool if you have to write some document but you don't want to get involved in LateX or some other scary unix text processor. Also it could be quite useful if you have old Word 6.0/7.0 documents that must be modified, or utilized some way. I realize there's a lack of a good WYSIWYG editor in linux, WordPerfect is not a perfect program, is overbloated $loware ( its marketing is even worse ) but, except StarOffice, is the only chance available.
This "Wordperfect" snippet of mine is just a small part of this essay, devoted to give a more detailed description of X Applications, especially comercial X Applications using Motif, and this background information will help explainig some techniques for reversing these applications.
Finally the usual review of useful tools of the trade, included an updated version of dasm.
Tools required |
Editres ( available with any linux distribution )
Gdb (apply the same)
Perl 5.00x (apply the same)
Dasm ( available in this essay for instance)
DDD (optional) http://www.cs.tu-bs.de/softech/ddd
Target's URL/FTP |
Wordperfect not main site
http://www.sdcorp.com/demos/linux.htm
Essay |
1. GUI, THE CHANCES.
In the previous linux essays I tried to cover some basic concepts about Reverse Engineering linux apps. The first one was a presentation showing the basic tools that enable you to crack some basic programs, even without making a careful study of linux intrinsics. The second one was an example of how to crack XWindows programs, just using the information available and some common knowledge.
What else ? Well, most commercial overbloated linux applications will not carry a complete symbol table ( as Acrobat Reader does ), therefore you'll need more than "looking for suspicious" names if you want to RE your favourite $loware. AFAIK, registration codes are not very popular in linux, but crippled/time limited programs are, so you need some MessageBox or something for lightning the dark deep sea of instructions. In the second essay we met some important words as "widget", or functions as Xsomething or Xtsomething. I hope at the end of this essay everything will make sense properly.
The GUI in XWindows is rather different from the Windoze thing. XWindows lets you more freedom for using your own GUI, though the Object Oriented Scheme is mostly utilized. The basic frame for building applications is given by Xlib library, which is used by EVERY Xapplication. All these Xsomething functions are inside Xlib library. You can build your GUI with Xlib functions, but like they are low level functions, it seems really hard for programmers to use them and nothing but them. The next obvious step is to introduce the ubiquitous OOP stuff somehow, especially if you can introduce it without introducing anything at all :-D. It seems funny, but it is true, and that's what X Toolkit Intrinsics does. Its goal is to make it easier for programmers, though it just defines the "theory", the skeleton of the object scheme made up of graphic objects named as WIDGETS. So, a widget is the basic frame for a graphic object, may be a button, a scrollist or whatever ( the Windoze concept of window may be useful as a comparison ) that must be build by the application. X Toolkit also start the joint between graphic objects and events ( it sounds better eh ? ). With the OOP behind, you may imagine that widget are classes, inheritance and bla, bla, bla ... The Xtsomething functions come from the X Toolkit Intrinsics.
Each class has its own resources. Resources, another magic word and a wide-dangerous concept if you know Windoze resources ... is not the same. If you don't want to get confused, think of resources as variables of the graphic interface ( that may be initialized by the user ). One kind of resource, very important are callback lists, that joins events with functions.
A third layer is necessary for making up the interface, you know, for drawing the buttons, repainting and so on. At this point of the way, you are not worried by speed, so better use some pre-cooked code; but firstly decide if you want to dig into your pocket for some money or you don't want. In the first group everybody uses Motif libraries and in the second Athenea Widget is very popular. Most commercial $loware uses motif for building their GUIs, but that doesn't mean you must buy motif for running those programs, instead you are punished with fat applications that got motif functions statically-linked. But what is that motif thing ? It turns your scaring-Xwindows application in a Windoze-look-like application, yes, motif apps look like Windoze programs ( more or less ), and then everybody is happy.
Motif is copyrighted by OSF ( Open Software Fundation ), it is a standard. There's a
project for developing free motif ( called lesstif ), but it's not been finished. Anyway
it is worth a visit, even just to get function definition ( include files ):
http://www.lesstif.org
Nowadays outside $loware, there are a growing number of alternative (almost) free GUIs ( another religion ) like GTK and Qt libraries. If we talk about these new graphic libraries, we must talk about Window Managers. They add some look and feel to the windows (borders more or less spartan) and some management capabilities (buttons and functions to handle windows, and some interface to run programs directly ). There are a lot ( some imitate w95 :-), and we'll not get into further details.
What I wanted to remark, is that now there are two projects for building window managers based on GTK and Qt respectively. The philosophy is to extend the capabilities and give the manager with some packed-tools (mail, editor, file manager ... ) that use the same graphic lib, therefore everything looks the same way. This could be a "restricting" approach, but surely will make it easier to linux newbies. Well, the projects are kde (Qt) and gnome (GTK). Now you may realise there are several free alternatives to Motif in the future.
2. SOME SPYING.
-- Ok, I'm the master of MessageBox breakpoints, what do I need in linux ?
-- Wait, wait, you'll see.
First we must learn something about real world, how these programs act, the functions they call. Let's start with Xt details, it is important because motif libraries use them.
Xt must be initialized, so the function XtAppInitialize must be called. The root widget is created, then the rest of the widgets are created mostly with the function XtCreateManagedWidget, and the resources of each widgets are updated by the application or by the resource database. The next step is defining the different callbacks, by means of the XtAddCallback function. Now the widgets are data in memory but need to be shown as a real thing with XtRealizeWidget. At last the app waits for events with XtAppMainLoop, or some XtAppPeekEvent loop.
Yeah, yeah I know there are a lot of dirty details I'm not talking about them, ... I can give you two links with information and tutorials about Xwindows and Motif. In the first one you may get tutorials in practically all the languages you fancy:
http://www.rahul.net/kenton/xsites.framed.html
http://www.cen.com/mw3/code.html
Motif is supposed more high-level functions, and that's true, here you have some motif
functions that may be useful:
XmCreateBulletinBoardDialog
XmCreateDialogShell
XmCreateFileSelectionDialog
XmCreateMessageDialog
XmCreateErrorDialog
XmCreateInformationDialog
XmCreateQuestionDialog
XmCreateWarningDialog
XmCreateMessageBox
Using these functions Motif offers the possibility to run precooked widgets, despite you may create compound objects. Predefined dialog boxes are subclasses of the BulletinBoard class, while customized dialogs just inherit DialogShell class.
The main motif class is called Primitive, and the rest ( labels, pushbuttons and so on ) are derived from it. In the acrobat reader essay you discovered that the function XtSetSensitive was disabling the buttons, but what about a disabled menu. We must know how a menu is built.
A class called MenuShell is the base, then you get a RowColumn widget for putting the elements in a particular position ( do you remember Java Panels ? ). The main options (File, Edit, and so forth) of the pulldown menus are CascadeButtons and the final options ( Open, Save, ... ) are PushButtons ( or ToggleButtons ). So, disabling a menu is the same of disabling a button, and the XtSetSensitive function is used again.
Some comment about the functions above, the name XmCreateMessageBox may be tricky for you, this class has always 3 buttons Ok, Cancel and Help, so perhaps you should think about the basic class DialogShell ...
There's a VERY valuable tool for getting information about widgets hierarchy from an application. That will solve if you don't know if the nagging box you are looking is Bulletin, Dialog, Box or whatever :-). It is editres, that comes with usual distributions. Editres uses the "editres-protocol" to communicate with Xt applications and show a lot of information about the widget tree and its resources. The main idea of the program is to provide a nice editor to change resources on the fly; that will not be the case for us, but we may get a lot of interesting information of the target. Sadly not all apps support "editres-protocol".
If you are lucky after clicking with your mouse over the application ( Get tree option ); over a menu for example you get this tree ( taken from a dubious attempt to get a linux ultraedit: crisp ):
Crisp mcr XmBulletinBoard draw XmRowColumn menu_bar XmCascadeButton File XmCascadeButton View XmCascadeButton Edit XmCascadeButton Find XmCascadeButton Options XmCascadeButton Tools XmCascadeButton Windows XmCascadeButton Help XmDrawingArea container XmDrawingArea toolbar XmDrawingArea ruler XmDrawingArea screen XmScrollBar vscrollbar XmScrollBar hscrollbar XmDrawingArea status_bar
If it is confusing for you it shows pairs of (class,identifier), firstly the parent class called Crisp ( not quite original ), the whole visible window derived from it, and then a menubar, a toolbar, a ruler, the "edit" area and the status bar; therefore the whole graphic interface. The tree may expand even more, but in this tree don't appear widgets not created, I mean, if I open FILE main pulldown menu, menu options are created and widgets are reported to editres, but not before. Of course in the program everything is shown with graphics and so on. You may see the tree labelled with not only classes or identifiers, but with WIDGET IDs and WINDOW ids ( the last one could be got with some tools I described in my second essay ).
What else ? You may as well select one of the widgets and list its resources, especially Callback resources that could be useful ( more below ).
3. EVENTS
The main goal of this part is to give clues for joining events with code, in order to get some starting point for the art of Reverse Engineer. The first idea is to exploit Windoze exploits :-). MessageBox turned to be one of the most important functions in the art of RE, why not carry on in linux ? You've got a good list of functions above, but I want you asking now what happen if the motif library is statically linked. Well, I fear no magic solution. Some programs create on-the-fly a "calling table", where you may identify clearly pairs XmFunction->address; some others just declare the functions in the symbol table , but in some rare cases is not easy.
Many times that's not enough and you must try to deal somehow with events. The whole system bares resemblance with Windows, asynchronous messages are generated by the server as a result of actions like moving the mouse, using the keyboard ... Masking events may be applied in different ways, but through Xt layer and through Xm ( motif ) layer messages may travel in mysterious ways. Anyway, tracing the program's event loop is really a nightmare journey.
Widgets use to manage events by means of translation tables ( previously registered and bla, bla ), more or less a table where you pair events ( keys pressed and mouse buttons up and down ) with functions. If you grep some binary with the pattern "<Key" you may get a good bunch of translation tables, but like you don't get the source code, that use to be a useless effort regarded the events you are interested in, are not that different of absolutely trivial events.
The last chance is callbacks, the name is enough self-explaining. They are function
pointers that are associated to some widget for taking care of one or more of its events.
Sometimes when you create a widget, the callback is supplied along with the creation
function (XmVaCreateSimplePulldownMenu), or may be it is added with function
XtAddCallback. Here is a pseudo definition of this function:
XtAddCallback ( Widget, CallbackResource_name, func_pointer, NULL)
The first var ( that you may get with editres ) is the widget identifier, the second one
is a string to identify the resource ( and the name should be consistent with the action
it takes ) and the third in the function ( the last one is data passed to the function,
but let's put it apart ). Here is a "smart" breakpoint that will supply the
whole XtAddCallback activity without getting the window manager in trouble (consult
previous linux essays for details):
(gdb) br *XtAddCallback
...
(gdb) commands
>silent
>x /s *(int*)($esp+8) ...
this show the callbackR name
>x /x ($esp+4)
... this shows the widget id
>x /x ($esp+0xc)
... this shows the function
>cont
>end
Now you may realize if your favourite widgets have got some interesting callback. A
conditional breakpoint will do for a concrete widget ( though it's hard to guess its
identifier before it's created :-). The callback resource name must match with some of the
resources showed in editres. Some common resource callbacks that apply to practically all
buttons:
* armCallback: the function that draws the button "pushed"
* activateCallback: the function called after a button push-release.
* disarmCallback: the function that restores the graphic of the button once it
is released
Obviously you should be interested in the second one ;-).
The last and strange possibility if to use an event handler. Despite it would not follow
motif "Style guide", could be possible. These handlers are set with the function
pseudodeclared here:
XtAddEventHandler (Widget, Mask, More_mask_stuff, Event_h, NULL)
The function Event_h is called if there's an event that matches mask conditions, obviously events that apply to widget Widget.
4. MORE TOOLS
Though you should already know the basic tools, you must keep an eye on other
tools.
DDD : Data Display Debugger
This is not a new tool, I knew it months ago. Suppossedly it is used by software companies
( Adobe claims to use it :-), but the last version I checked ( 2.2 ) was still buggy, and
believe me, it's annoying to be cracking a program and a debugger at the same time. It is
really not a debugger, but a nice frontend for gdb. Just a few days ago I would tell you
not to use, it but I have just installed version 2.2.3 and, though it keeps old problems,
seems to fix most of the problems that made it crash. Assembler support is one of them
:-).
If you finally try it, you may enjoy a beautiful assembler window, and what they call a
data window ( but is really a watch window :-(. Some other features will make your life
easier. For programs that carry a symbol table is a must.
http://www.cs.tu-bs.de/softech/ddd
NDASM: Network Disassembler
It is more a dumper than a disassembler. You must give it the starting offset of the binary data inside the exe file. Anyway is a kind of universal assembler with which you can build binaries for almost all operative systems using intel processors ( included ELF binaries ).
I'm giving here a new version of dasm with a little fix and some new features ( call/jmp different xref, Function titles when symbol table available ). Again bugs, fixes or suggestions at lluisote@hotmail.com.
#!/usr/bin/perl
;############ MODIFY THIS LINE WITH YOUR PERL LOCATION ############
push(@INC,"/usr/lib/perl5");
require("flush.pl");
;##################################################################
;######## LINUX DISASSEMBLER 2
;######## (C) SiuL+Hacky Feb 1998
;######## You may copy, modify, distribute this program and
;######## is up you to keep this header here
;######## Usage: dasm exe_file dasm_file
;##################################################################
$f_input=$ARGV[0];
$f_output=$ARGV[1];
&printflush(STDOUT, "\nCreating disassembled file ...");
$return=system("objdump -d -T -x --prefix-addresses
".$f_input.">".$f_output."2");
if ($return!=0){
print "\nERROR OPENING OBJDUMP $return";
print "\nUsage: dasm exe_file dasm_file";
print "\nBe sure to get objdump in your path. Check also file
permissions\n";
exit(1);
}
open(INPUT, "<".$f_output."2");
&printflush(STDOUT, "\nReading strings ...");
$_=<INPUT>;
while (!/.rodata/){
$_=<INPUT>;
}
($rubbish, $rest)=split(/.rodata/,$_,2);
($rubbish, $rest)=split(/0/,$rest,2);
@numbers=split(/ /,$rest,5);
$size=hex($numbers[0]);
$starting_address=hex($numbers[1]);
$end_address=$starting_address+$size;
$offset=hex($numbers[3]);
open(CODIGO, "<".$f_input);
seek(CODIGO,$offset,0);
read(CODIGO,$cadena,$size);
close(CODIGO);
$_=<INPUT>;
while (!/SYMBOL TABLE/){
$_=<INPUT>;
}
&printflush(STDOUT, "\nProcessing symbol table ...");
$_=<INPUT>;
while (!/^\n/){
@st_element=split(/ /, $_);
$_=$st_element[$#st_element];
chop;
$symbol_table{$st_element[0]}=$_;
$_=<INPUT>;
}
while (!/\.text/){
$_=<INPUT>;
}
&printflush(STDOUT, "\nProcessing jmps and calls ...");
######### the regex gets rid of possible line information #############
while (<INPUT>){
$_=~ s/<[+_0-9a-zA-Z]>//ge;
$_=~s/ / /g;
if (/j/){
($direccion,$inst,$destino)=split(/ /,$_,3);
$destino=~s/ //g;
chomp($destino);
$salto{$destino}.=($direccion." \; ");
}
elsif (/call/){
($direccion,$inst,$destino)=split(/ /,$_,3);
$destino=~s/ //g;
chomp($destino);
$call{$destino}.=($direccion." \; ");
}
}
seek(INPUT,0,0);
&printflush(STDOUT, "\nWritting references ...\n");
open(OUTPUT, ">".$f_output) || die print "\nError opening write
file\n";
print OUTPUT "FILE REFERENCED\n\n";
while (!/Disassembly of section .text:/){
$_=<INPUT>;
print OUTPUT;
}
$char=".";
$counter=0;
while(<INPUT>){
$counter++;
if ( ($counter % 400)==0){
printflush(STDOUT,$char);
if ( ($counter % 4000)==0){
printflush(STDOUT,"\r");
if ($char eq "."){ $char=" ";}
else { $char=".";}
}
}
$copia=$_;
$_=~s/<[+_0-9a-zA-Z]+>//ge;
$_=~s/ / /g;
($direccion, $inst, $destino)=split(/ /,$_,3);
if ( defined( $symbol_table{$direccion} )){
print OUTPUT "\n";
print OUTPUT "---- Function : ".$symbol_table{$direccion}."
----\n";
}
if (/call/){
$destino=~s/ //g;
chomp($destino);
if ( defined( $symbol_table{$destino} )){
print OUTPUT "\n";
print OUTPUT "Reference to function :
".$symbol_table{$destino}."\n\n";
}
}
if ( defined( $salto{$direccion} )){
print OUTPUT "\n";
print OUTPUT "Referenced from jump at
".$salto{$direccion}."\n\n";
}
if ( defined( $call{$direccion} )){
print OUTPUT "\n";
print OUTPUT "Referenced from call at
".$call{$direccion}."\n\n";
}
if (/\$/){
($instruccion, $operand)=split(/\$/,$_,2);
if (!/push/){
($operand,
$rest)=split(/\,/,$operand,2);
}
chomp($operand);
$offset=hex($operand);
if ( ($offset <= $end_address) &&
($offset >= $starting_address ) ){
$auxiliar=substr($cadena,
$offset-$starting_address);
$length=index($auxiliar,
pack("x") );
$auxiliar=substr($auxiliar, 0,
$length);
$auxiliar=~s/\n//g;
print OUTPUT "\n";
print OUTPUT "Possible
reference to string:";
print OUTPUT
"\n\"$auxiliar\"\n\n"
}
}
print OUTPUT $copia;
}
close(INPUT);
close(OUTPUT);
print "\n";
system("rm ".$f_output."2");
5. Cracking WordPerfect for Linux
In the case of Corel we find the same stupid attitude of Adobe ( or even worse ), trying
to make good moneys and succeding in getting their products unused. If WordPefect would
give for free its product in the linux/unix version, everybody would be using its program.
But instead of that, people prefer to buy StarOffice ( kind of a $uite ), to buy some
Windoze emulator ( as Wabi) and run some Win 3.1 word processor. One of the things is
missed in linux/unix is a good WYSIWYG processor. Then Corel makes a fairly good program (
if overbloated, as usual ) with many of the features of Word 97, but the concurrece
doesn't use it.
I hope this crack will make this program more popular, so I'm gonna give a "ready to use" crack. It's hard to push Micro$osf competitors this way, but if the are stupid ...
You all know the fairly hard ( bought ) protection of Corel Win32 applications. The unix version is apparently developed by SDC ( come on stalk them :-), and the protection is really weak ( may run for 1998 Most Stupid Award ). You'll not learn a great deal, but IT IS (IMHO) AN IMPORTANT TOOL FOR BEING CRACKED.
I'm cracking version 7.0.132. Download it at Corel site, it's a hard download ( more than 30Mb + 10 Mb for language package ). Run the install script, and the executable called "xwp" is a hideous monster of 7277808 bytes. Ok, seat down because after dissasembling it you'll get an alien of 65 Mb ( you better get good RAM ).
When you run the program, a message is shown with "The evaluation period will expire in n days". I don't understand quite well the motif-system used with this program, but don't care, we'll crack it with just the tools we know. First the message is not in the executable, so after some grepping you'll find it inside the file wp/shlib10/wpcerr.us. Let run the program through strace tool ( strace -o output_file xwp ), then look for the name of the file (in the output_file ) and you'll get something like this:
[4010ec0c]
open("/usr/local/flexlm/licenses/license.dat", O_RDONLY) = -1
ENOENT (No such file or directory)
>>> He is heavily seeking this file, what a useless effort
[4010ec0c] open("/usr/local/wp/shlib10/wpcerr.us", O_RDONLY) = 13
[4010eaf4] lseek(13, 3124, SEEK_SET) = 3124
[4010f220] read(13, "^r\0\0", 4) = 4
[4010eaf4] lseek(13, 29278, SEEK_SET) = 29278
[4010f220] read(13, "The evaluation period for this d"..., 2048) = 2048
[4010df0a] close(13)
Don't you think is maravellous ? Now it is very easy to locate the last lseek, because it is the only one in the whole program with offset 29278. And then breakpointing the next read will lead to victory :-).
Let's use an advanced breakpoint in this case. This is the definition of lseek
off_t lseek(int fildes, off_t offset, int whence)
hence, the brk looks like this, (you'll think it looks awful, but I'm just casting a pointer O:-):
(gdb)
br *lseek if *(int*)($esp+8)==29278
Breakpoint 1 at 0x40105ae0
(gdb) cont
... gdb breaks after a while in the middle of some library
(gdb) finish /* get back to xwp code */
Run till exit from #0 0x4010620c in __read ()
0x84f9762 in XMapRaised ()
Ok, therefore the string is read at 84f9762. After some fishing on the stack, you'll get the address of the buffer. In my case ( and should be yours ) is 0874e1a0. The next step is breakpoint this location.
(gdb) awatch *( 0x0874e1a0 + 69 )
( ... remember that the string is "The evaluation period for this demonstration software\nwill expire in •\032\005• day(s)." I take care of the offset of the number of days )
Gdb will break at address 0x853dcbe. This code is overwriting the right value of the number of days left. Using the "back" command ( like softice "stack" ) will be useful for tracking the target:
(gdb) back
#0 0x853dcbe in XMapRaised ()
#1 0xbfffd8f4 in ?? ()
#2 0x84f9531 in XMapRaised ()
#3 0x84f938e in XMapRaised ()
#4 0x850035a in XMapRaised ()
#5 0x81002c5 in XMapRaised ()
#6 0x80ffa5d in XMapRaised ()
#7 0x80510cb in XMapRaised ()
I'm not gonna get you bored with an endless tracking ( if you want it, get back to
Acrobat Reader essay ). As a summary, I'll tell you that the string is completely
overwritten with the same string ( included the number of days ). You may ask yourself how
the string could be copied without breaking. Remember that the brpt was set with an
offset, and the right string may be composed strcating the begining of the string with the
number formatted. After tracing back a couple of functions, you'll land in the middle of
this snippet:
085002c9 pushl %edi
085002ca call 08500600; << returns $ebx=-1 in my case
085002cf addl $0x4,%esp
085002d2 testl %ebx,%ebx
085002d4 jne 085002e0; << if returned value != 0 jump
085002d6 movl $0x1,%eax; << if 0 returned set a flag, hummm
085002db jmp 085004d0
Referenced from jump/call at 085002d4 ;
085002e0 movl 0x10(%ebp),%ecx
085002e3 pushl %ecx
085002e4 call 085008a0; << this function returns the number
<< of days left
085002e9 movl %eax,%edx
Both "important" functions are called just once. It's sad looking the bunch of bytes used in both functions, and then showing this kind of Jurasic protection. There are a thousand ways of cracking this.
Here's a perl script for "correcting" the file. Why perl again ? I think is absurd to write this in asembler. This way it is ready to run and you get the source code ( in case you disslike running binary files compiled by others ).
#!/usr/bin/perl
push(@INC,"/usr/lib/perl5");
;#/***********************************************************/
;#/**** PERL SCRIPT FOR TURNING TEMPORARY WP INTO PERMANENT **/
;#/**** (C) SiuL+Hacky 1998 lluisote@hotmail.com
**/
;#/***********************************************************/
@magic_chain=( 0xe8, 0x31, 3, 0, 0, 0x83, 0xc4, 4, 0x85 );
system("clear");
print "\nThis will crack XWordPerfect 7.0.132";
print "\nthe file should be 4,948,682 bytes long";
print "\notherwise you'll get -Could not find code-";
print "\n(C) SiuL+Hacky 1998";
print "\nPress Enter ...\n";
getc;
$wpfile=$ARGV[0];
open(INPUT, "+<".$wpfile) || die print "\nError opening file:
$wpfile\n";
seek(INPUT, 4948682, 0);
if ( read(INPUT, $_, 9) != 9 ){
print "Error reading file ... \n";
exit(1);
}
(@cadena)=unpack ("C9", $_);
for $n ( 0 .. 9 ){
if ($cadena[$n] ne $magic_chain[$n]) {
printf("Could find original code ...");
close(INPUT);
exit(1);
}
}
seek(INPUT, 4948682, 0);
$a=pack("C", 0xBB);
$b=pack("C", 0);
print INPUT "$a$b$b";
close(INPUT);
Final Notes |
Thogh there's a growing number of linux users, it seems to me there's not a great amount of linux reversers ( at least at assembler level and no source code ). For this reason if any of you reading this, know any new tool or thinks he/she has the knowledge/interest to program one, write to the +hcu or write to me.
Ob Duh |
I WILL bother explaining you that you should BUY this target program if you intend to use it for a longer period than the allowed one. Should you want to STEAL this software instead, you are a moron. This is the kind of software that WE NEED. Many people should register it and allow its Author to write even more interesting stuff!
You are deep inside fravia's page of reverse engineering, choose your
way out:
Our tools
homepage links anonymity
+ORC students'
essays academy database
tools Javascript wars cocktails
antismut CGI-scripts search_forms mail_fravia+
Is reverse engineering legal?