Friday, April 22, 2011

Fravia+ Reverse Engineering


This page doesn't appear to be an article and therefore may not display well in the Article View. You may want to switch to the Full Web Page view.

If you know there should be an article here, help improve the article parser by reporting this page. Thanks!

(Last update: October 1999)

Software Protection, an impossible dream?

An introduction

I would like to start, with this section of my site, a good "special" reversing laboratory for software protection. As my readers know, we have already tried this some time ago with the Our Protections section. Yet that section didn't seem to florish, so I'm now re-organizing various 'threads' into a coeherent "software protection" project. I hope that this will help Fravias and protectors alike... no cracker should underestimate the difficulty (and the beauty) of writing a good protection, exactly as no protector should underestimate the difficulty (and the beauty) of reversing a difficult snippet of software. Cracking and protecting are two very important sciences in their own right, but I have noticed that the development of good protections suffers a 'psychological' penalty. "There's no point in protecting or overprotecting: a determined cracker can crack any protection!"; "nothing can guarantee absolute security"; "Any protection scheme can be defeated"; "protection measures can be easily worked around", and so on and so on... stalk the protectors on usenet and you'll see this kind of typical whining.
Well, any protection scheme can be cracked... may be. Yet let's devise schemes that will make things a lot HARDER for the typical cracker to mess with... and who better than some good +crackers can teach protectors how to protect?
It's almost winter, snow is in the air! C'mon: let's push another snowball down the Webhill!

Software Protection, an impossible dream?

All the advices you may need

Mark's famous 14 protector's commandments
  • 1 Never use meaningful file or procedure names such as IsValidSerialNum (duh.) If you do use functions for checking purposes, place at least some required code that your program really needs, in such a function. When the cracker disables the function, the program will produce incorrect results.
  • 2 Don't warn the user right after a violation is made. Wait later, maybe until the next day or two (crackers hate that).
  • 3 Use checksums in DLL's and in the EXE. Have them check each other. Not perfect but it just makes it harder to crack.
  • 4 Pause a second or two after a password entry to make brute force cracking unfeasible. Simple to do, but rarely done.
  • 5 Self-heal your software. You know, error correction like modems and hard drives use. The technology has been around for years, and no one uses it on their software? The best thing about this is that if the cracker used a decompiler, they may be looking at a listing that is no longer valid.
  • 6 Patch your own software. Change your code to call different validation routines each time. Beat us at our own game.
  • 7 Store serial numbers in unlikely places, like as a property of a database field.
  • 8 Store serial numbers in several places
  • 9 Don't rely on the system date. Get the date of several files, like SYSTEM.DAT, SYSTEM,DA0 and BOOTLOG.TXT and compare them to the system date. Require that the time be greater than the last run.
  • A Don't use literal strings that tell the user that their time is expired. These are the first things to look for. Build strings dynamically or use encryption.
  • B Flood the cracker with bogus calls and hard-coded strings. Decoys are fun.
  • C Don't use a validation function. Every time you validate the user, write your validation code inline with the current process. That just makes more cracking for the cracker.
  • D When using hard-coded keys or passwords, make them look like program code or function calls (i.e., "73AF" or "GetWindowText"). This actually works very well and confuses some decompilers.
  • E Finally, never reveal your best protection secrets :-)

Tidbit's 'common sense' rules
  • 1. No nagscreens. Making enemies among your customers is very stupid, indeed.
  • 2. Create important menus and dialog boxes dynamically, whether in something like turbo-vision, xforms or m$windoze. Use your own format of rcdata like borland did in delphi if you don't want to write too much code.
  • 3. If your program doesn't save data in "crapware" edition, don't include a "grayed" menu item. No saving means no saving. That's it.
  • 4. The only way to tell user that he is unregistered should be in the "about" dialog. The latter should also be created dynamically.
  • 5. Avoid using any kind of string resources like "this crap is unregistered, blah blah"
  • 6. Link statically unless your program is a complex one (at least of m$word 2.0 complexity)
  • 7. Don't loose time on writing anything that will kill disassemblers or debuggers. Take my word on it - doing it is worthless, people who made them or HCU'ers :-) will soon find the way around, so shift your interest to more important stuff.
  • 8. Use your language of choice fully. Even if it's visual basic, learn it thoroughly first. Using some advanced constructs it is possible to make debugging a nightmare.
  • 9. Use run time library fully when writing the beta versions, in final release rewrite some functions at least to make crackers life harder.
  • 9a) Example: many ocx'es for vbasic take PLENTY of space, and they are really easy to hook to. Use only ones which you need, usually much of ocx crap can be rewritten to use plain windows api.
  • 10. Take SOME time to THINK about protecting your software. Is it worth the protection? Wouldn't it be better to IMPROVE YOUR SOFTWARE, rather than improving protections?
  • 11. Try to embed at least part of the protection inside the data manipulation. Data structures can take ages to understand basing only on disassembly listing, they also are more error-prone for cracker. Crackers usually take notes on loose sheets of paper, and not everyone reads his own handwriting 100% accurately.
  • 12. A protection that mangles data is a good one usually.
  • 12a) Example: Got a charting program. The crapware shouldn't print. Disabling printing and later on enabling it basing on some registration# is the most often committed suicide. Let your thingo print. When creating data structures for printing, mangle them in some way. Unmangle them just before printing, using reg# or something other for that purpose. Even more, make this mangling subtle. Assume that you've got a pie chart to print. Don't alter anything, but add some not too big random numbers to values of data series - this is mangling then. The chart will look "not that bad", but will be otherwise unuseable (if the changes are random and on the order of 20%, for example). Finding such protection, if its connection with reg# is not self-evident can take much time. One has to delve inside your data structures and find that dreaded mangling and unmangling code. Not too easy, especially if you program high level, not in asm.
  • 13. When mangling data, use some tricks so that it is not self-evident where that reg# comes from into unmangling code.
  • 13a) Example: I did it for a dos platform once. The reg# (in a "go/no-go" protection) was specified as a command line argument. The command line parser was clean and it was the only place where command line was ever referenced. In some other place the "irrevelant" code was using a "bad" version of borland pascal move routine from rtl (move copies a range of data from here to there). The routine in rtl was altered to copy a few bytes more and to wrap around the segment boundary. The "irrevelant" code called copy with parameters set so that it would copy some useless crap together with command line somewhere else, and that "somewhere" was left unused for some time. It was evaluated in the wholly other part of program. Athough setting bpx on memory range would find that, it was not THAT evident that one should place the "enable#" on command line (it was nowhere mentioned since I needed to protect from my coworkers only).
  • 14. Be economically inclined. Calculate whether you need to write your "to be protected" program at all. Isn't there (albeit big) a commercial, widely available package that does the same thing? If you need that tiny program for yourself only, in 99% of cases it's cheaper to use existing package that supports scritping (to tailor it to your needs). If you want to sell it, check your market. The problem of protecting software vanishes if no one will use your software. Don't overestimate your work's "importance to the world". Search the net. There's nothing like you want to do? Search more, then. Start work only when you're sure there's need.
  • 15. Always explore your language of choice on the level proposed (or, more often these times, pushed) by the language developers. If you're real programmer that doesn't want his "hello world" app to be 2megs+ big, shift down to the api level asap - ie. after exploring all the nice "visualties" the commercial types might be pushing you to use.
  • 15a) Example: This pertains mostly to things like borland c++, delphi and m$ vc++ - write your app using the existing user interface crap (foundation classes etc). When the innards are debugged (hah, they never are, but if you at least don't find any bugs in the first 20 minutes of post-build testing), rewrite your user interface to use windows api directly. Do it cleverly, think first, think a lot, and you'll usually get an app that is at most 1/4 of the original size, and that works much faster. Do you believe that corel draw 3 was written using nearly pure api? Such a HUGE app? - you say it's impossible. No, it quite is, ever more in the 32bit world when you don't need to spend so much time moving data around the 16bit segment boundary (corel 3.0 was for windows 3.x - it was 16 bit).
  • 16. What does p.15 have to do with protections, you think. Oh well, it does have much. Since your code doesn't use the "prepackaged" stuff, it's more personal, more custom. Since each programmer's style of programming is different, it take more time for a potential cracker to find out what's going inside your app. Alas, all this is useless if cracker doesn't have to know the innards to deprotect. So always protect in a way that is tied in MANY PLACES to the innards (data structures etc).
  • 17. A monolithic app that doesn't reference to much more than kernel or device drivers is usually harder to crack. There are less dependencies to get from watching the .exe and .dll's with quickview for example. Try to export only the callbacks needed by windoze for example. Check what goes to the outside world. Try to include no or little details of registration procedure. Your code should give as little information in "clear text" to the cracker as possible.
  • 18. For god's sake, don't give away the working code! Try to provide users with:
    - The crapware version, that DOESN'T have the "missing features" compiled in. This thing should also have no nag screens nor "enter reg# here" suff - it's obvious, since it's the CRAPWARE ONLY.
    - The full version, that HAS the full functionality compiled in but that also requires user to enter the reg# once somewhere. This version should be only given away to those that have paid. At least there is no way to get the thing to crack from your site, since you only expose the crapware for lamers. Alas, your "full" version, maybe even cracked, will be to be found shortly on warez sites, but not everyone knows where to search, so you at least can be proud that your app has been stolen by "the enlightened" :-)
  • 19. As has been said many times: don't overuse the "registered", "unregistered", "registration code", etc. words - both in clear text and in encrypted form. If possible, invent some clever algorith to generate these on the fly basing on some variables, definitely don't use the "bool Is_Registered" flag anywhere!
  • 19a) Notes: It would be quite nice if the program will be having some sample (bad?) reg# stored inside in some place(s). The user, after entering the registration#, should have no IMMEDIATE indication of any kind that the software is registered now. THis indication shouldn't also be deferred using timer, like in unix login. It's childish easy to find the "sleep x" code. Each function, like save, print, etc. that depends on registration, should check the reg# by itself, and it should do it in such way that user nor cracker doesn't see that something is going wrong or good. As said before: no disabled stuff. If saving is disabled, either don't provide it at all, or make the unregistered save work just like real one, just that data isn't written to disk or is written badly. Consider such scheme: user isn't presented with any "this funct disabled" box, the save works ok (save dialog opens, save completed "%" bar scrolls as usually), but you may for example lseek back to the beginning of file after writing each record if the user isn't reg#. This approach is hard to crack since such attempt requires to delve DEEP into the program's internals and to find what is going bad and where. People also think sometimes that their version is simply damaged and throw their program away, whereas that dreaded save might work ok when registered.
  • 19b) Notes: CLEARLY INDICATE facts mentioned in 19a), like that: Reminder: This program, when not registered, won't save nor print any data. These two functions will operate ONLY when the CORRECT registration number is entered in the registration dialog box. Warning: we restate that save and print options WON'T WORK until the CORRECT registration number is specified. All other features are unaffected in the unregistered version.
    Note: You cannot verify that correct registration number has been entered. Program doesn't display any warinings that you've entered invalid number. You know that it is ok when save and printing works, else please retype the registration number and try again.">> This will indicate:
    a) to the user that he definitely WILL enable the program when the CORRECT number has been entered,
    b) to the hacker that it is going to be a tough job since the registration # checker sits somewhere else.
  • 19c) Implementation notes: Move your reg# to the checker routine in an unsuspicious way. If you can, don't use the "existing" textbox and gettexta, implement the reg# entering routine by hand, it would be quite wise even to refrain from using the existing text rendering procedures and render the number on-screen as it is entered using your own renderer. You can edit your own font resource or use some preexisting font, then just encode it in some way so that it will be unreadable by resource workshops, even better include it in the source as static constants. Although it is still easy to find where the "drawbitmap" equivalent was used to paint the reg#, it is definitely harder to understand the whole underlying routine, especially noticing where does that reg# go. Using a multi-layered approach here is the most feasible one:
    - dynamically create accelerator keys in your 'register me' dialog box, these accells should be for keys used in reg# entry (0-9,a-z for example)
    - each accell should call different routine, if feasible (makes breakpointing tougher)
    - each routine should store the flag that given char was entered somewhere else, it would be nice if each keypress would modify some global variable, in some way that is decodeable for you, but not to the cracker (at first glance)
    - then there should be some kind of 'monitoring' routine that acts accordingly, paining the characters on the dialog box and taking actions upon backspace and enter, for example
    - yet another routine should collect all entered characters and create a reg# and store it in some untrivial place. using a .vxd here to manipulate the virtual memory to make some 'backup' copies in a not-easily-debuggeable way is a nice idea. a .vxd can work like a tiny embedded debugger, bpxing on the place where that reg# goes. it can then copy it to some quite other place, all that happening in the background and not to be noticed easily. the cracker will of course (at the first try, at least) try to check where this location is accessed. since it is accessed nowhere, he will scream: how the hell does this app know that the reg# has been entered, if it even does not access it? oh well, just a tiny .vxd or even a background thread has copied it somewhere else. they'll get at it later, but at first it can stir crackers minds, though.
  • 20. Notes on registration numbers:
    - balance between security, feasiblity, programmability and end-user headaches
    - too long, non-alphanumeric reg#'s tend to be continuously entered badly. at least provide a "non-persistent" reg# entry field so that user will rewrite the reg# each time, possibly correctly at last. many people will just "glance-compare" the entered reg# and the one (possibly) emailed to them, arriving at the final thought that they did enter it correctly, whereas the font is too small or they are too tired to notice that this '1' and 'l' have been interchanged (in a reg# like 'l83jjd_0)pH1lTe' )
    - refrain from any user feedback. the reg# entry box should accept strings of any length, without any validation. don't give crackers the knowledge about reg#. sometimes knowing that it's 10 chars long or that is contains only uppercase alphabet helps, so don't help them
    - the reg# verification scheme (I'm pretty sorry about it, but it just is like that) needs to take into account the number of prospective users, and thus you oughta do some "market analysis"
    - if your reg# is 10 numbers long, there are 10^10 possible reg#'s. but since your app might find let's say only 10^4 (10'000) users, you should invent an algorithm that assigns each one of 10^4 users one of 10^10 reg#'s, and does it somewhat uniformly. This prevents people and programs (some .vxd based "macro" players, like the one presented some time ago in DDJ, for example) to be used for brute force approach. If there are only 10^4 users and you allow 10^9 "valid" reg#s out of 10^10, on average each 10th reg# tried brute-force will be valid, whereas on the case of 10^4 prospective users, that many valid reg#'s and space of 10^10 reg#s, on average only each 10^6th reg# tried brute force will be valid. Ever calculated how much time it would take to brute-force search 10^6 numbers, even using a fast machine and extremenly fast macro player (keystroke generator simulating reg# entry and checking for results)?
    - the assignment operator that assigns user# to reg# shouldn't be trivial, and it's implementation should be done in asm by someone experienced both in maths (topology known by +ORC helps here, also :-) and asm. check your operator. create graphs of how it works. understand your own work, especially its drawbacks and vulnerabilities
    - be inventive. don't use anything that seems simple, quick and effective. unless you've come with something like Einstein's relativity theory, your approach is yes, simple, yes, quick, but no, not effective, and yes, easy to crack. I'm sorry but we aren't geniuses and developing a good protection scheme takes time, it took time myself, and still takes although i'm creating protections for fun since 5 years or so. It will definitely take some time you, dear reader, and don't believe your self-confidence. Protections written by self-confident and unexperienced prgrmrs end up in the "most stupid protection" page of Fravia. a nice and exposed place, and with what a neighboorhood, but a protection ending there is still the "most stupid". not a nice definition of your work, huh? :-]'
  • 21. Play same game twice. It helps. If you invent some nice and hard to crack memory move-around scheme to protect reg# inside your data space, do the same to parts of your data, even better using the same routine just with other parameters. Crackers are lived up to the fact that protection algorithms are always used for protections only. If integrity of your data will rely, at least partly, on proper working of your protection code, crackers get a tough work to do. it's called functional verification of the "okayness" of protection code. you don't checksum nor crc it, but simply call it "from the other place" and let it process the protection-unrelated data. if it will process this data ok, then cracker mustn't have altered it. this places end on easy cracks like "change that jump here and that cmp there to nops"..
  • 22. Strainer to you: does lotus 1-2-3 for dos "diskette" protection (it let you install it up to 3 times without "zapping" it from hd first) work still in win95? if yes, why? if not, why? analyze the code, I've learnt MUCH from it. it's only dos code, it's probably 100 times cracked already, but there are some niceties to learn from it. not a very good protection scheme, has many holes in it, but remember what +ORC said: a cracker well educated in historical code is nearly always a perfect one :-))) (ok, ok, i know, there's nothing perfect aside from moskovskaya in this bad world, but, can't we joke at times ? 8-0=)


More tips you might take into consideration
  • Use a serial which is several KB long of arithmetical transforms, to drive anyone trying to crack it insane. This makes a keygenerator almost impossible - Also, brute force attacks are blocked very efficiently.
  • Caution with the Runtime libary! Use it fully when writing the beta versions, in the final release rewrite some functions at least to make crackers life harder.
  • Mangle data. Protection that mangles data is usually a good one. Example: Imagine a charting program .. e.g., just disabling printing and later on enabling it basing on some registration# is the most often committed suicide. Let your thingo print. When creating data structures for printing, mangle them in some way. Unmangle them just before printing, using reg# or something other for that purpose. Even more, make this mangling subtle. Assume that you've got a pie chart to print. Don't alter anything, but add some not too big random numbers to values of data series - this is mangling then. The chart will look "not that bad", but will be otherwise unuseable (if the changes are random and on the order of 20%, for example). Finding such protection, if its connection with reg# is not self-evident can take much time. One has to delve inside your data structures and find that dreaded mangling and unmangling code.
  • Traps. Do a CRC check on your EXE. If it is modified then don't show the typical error message, but wait a day and then notify the user using some cryptic error code. When (and if) they contact you with the error code, you know that it is due to the crack. Be aware: such traps could also be activated due to virus infection or incorrect downloads. Don't blame a potential customer for software piracy.
  • The rcr/rcl trick If a rcr/rcl is performed on a value, it becomes much more of a pain to crack - you can't reverse it with by negating it's effects without knowing what the value of the carry flag was before the original operation. If the carry flag is created as a result of some other pain in the neck operation, you are probably onto a winner.
  • Stick conditional jumps in. Everywhere. Conditional jumps are not fun to reverse engineer. No loops, but jumps which conditionally bypass/include portions of your wonderful key manipulation code. There is no easy inverse operation to be performed here.
  • Use portions of the code as magic number tables. (preferably critical sections). You have no idea how annoying this can be, if you're like most crackers and like to change things around using softice (a popular cracking tool).
  • Play with the cracker's mind. This one is fun :-) Stick series of nops in, as though you were doing self-modifying code (oh my god! what the heck! nops? Aha! Self-modifying code! Idiot spends next three years trying to find the code that should be there.). Pepper the code with junk instructions. Cut the code up into little pieces and put them all over the executable, with (preferably conditional) jumps between them. - Anything which you would find a pain in the neck.
  • Detect SoftIce. Early. Now crash the computer. You can crash a pentium or a pentium with MMX even without a vxd by the opcode: F0 0F C7 C8 (illegal form of cmpxchg8b instruction with lock prefix). Beyond that, we have to resort to the tried and true methods. Using a vxd, take the CPU out of protected mode. Windows doesn't like that. Wonder why?

Software Protection, an impossible dream?

The Shrinkers discussion

On Richard Fellner's page I found this snippet:
Don't rely on "EXE-packers". For almost any tool which compresses EXE files (Shrinker, WWPack32, NeoLite - to list the most popular ones) there's an uncompressor around, so compressors capable for software-protection should at least support configurable encryption. Unpackers for the above (and other) tools are not very wide-spreaded, however, don't rely on them as your program's one and only "protection"!
Now the question is: is this true?

A shrinker, like the one from Blinkinc should be seen as a 'first line of defense' for a protector. The application will be uncompressed when it is loaded and cannot therefore be decompiled from disk. This makes the possibility to mess with it a little more complicated for the cracker, especially if, after having compressed the application with Shrinker or with WWpack32 you checksum (twice) the compressed EXE.

The standard binary post-processors seem at the moment to be:
and WWrap32
And for these shrinker there are corresponding unshrinkers in the scene... where are they?

Well, a first good tool (yet not for beginners) is procdump a very good unpacker by Riz+la, Stone and G-rom (btw: in the unpack.txt companion file you'll find VERY USEFUL information about the most common commercial' packers). I'm presenting here version 1.1, build 4 from 11 October 1998. Visit Stone's page to fetch more recent versions.

Software Protection, an impossible dream?


Anti-Cracking sites

Vitas Ramanchauskas' site:
Some interesting techniques and original ideas

Richard Fellner's anti-crack tips
(most of them have been shamelessly stolen from my site :-)

Rob Beckers' How to Battle Warez:
A VERY interesting part about site tracking and elementary/intermediate stalking techniques

Anti-Cracking discussions

You may be interested in my Counter Intelligence page.

Cracking discussions

You may be interested in my Why crackers crack? (Reversing Fravias' psychology) 'late November' thread.


[Let's melt softice? Pro and contra] [Funny tricks against idiots]

1. Let's melt softice? Pro and contra

Here you'll be able to check David Eriksson's original (Mid-97) meltice! A tool for detecting softice written in C, and here you'll have the same code ported by PhR to Pascal/Delphi (with Hoffmeister's corrections).
Anyway such an approach is of limited use. You may succeed in annoying some casual crackers, yet the fact that Numega chose to name their kernel drivers that way doesn't mean much... there is nothing that prevents any reveser from renaming them...

So I publish the snippets above because this can give some ideas to good protectors. Go beyond and prepare some good code for Sice detection (or even some 'retaliating code'... come to think of it, if I were a protection scheme and if I would detect on a computer -say- softice, wdasm and smartcheck I would know that I should ring all possible defcom red alert bells... but READ (and head) THE FOLLOWING CAVEAT!

First of all you should UNDERSTAND what Softice is... many 'sunday' programmers don't have the slightest clue...

SoftICE is - same as WDEB386.EXE of Microsoft a completely different story, from turbodebugger... much to shareware-authors and driver developers dismay.
First of all, SoftICE is started before Windows starts (more exactly - you run winice.exe and winice in turn runs This applies to win9x . configured to run as a boot driver, kernel driver, or a dynamic loadable (kernel) driver under NT.
When the gui starts SI is already present and can be invoked via ALT-D (preset). So there is no "present-not present" thing with softice, it sits beneath windows and waits till you need it. as the bulk of softice consists of ring 0 software, you're not limited in what you can view. driver writers for that reason quite routinely start their machine with si present.
Therefore you go about detecting it like you'd detect any other vxd - seeking the VMM's DDB and then just walking the linked list of DDBs in the case of Win9x, and examining the list of loaded drivers in NT.
The problem rather is, what are you going to do if you find it? Nuke the machine?
The world increasingly seems to fill up with authors, who think, just because somebody (mostly by accident) installed one of their vanity proggies, they got the right to nuke others peoples machines.
As it's part of my job to work with softice I just can't work with some programs without reversing them at the moment - o.k. so what, but what will annoy almost all VXD authors is if you just go about rebooting the machine if they start your software... maybe putting a link into the autostart group before.
Therefore do me and people like me a favor please: forget that debugger detection crap, do a little bit of math and firgure out how to en- and decrypt your software at runtime (as it is to valuable to be looked at by other people), be creative and contructive instead of just destructive.

Btw, there is no law against people debugging and reversing your software, as strange as this may seem to you, but there surely is a law against deliberatly risking to damage other peoples' property.

2. Funny tricks against idiots.

Publish the following on alt.2600 (or IRC, or wherever lamers abound) as standard answer to all zombies' questions regarding software deprotection:
"There is a file in the root directory that controls all copy protection called (or under NT, I believe it's 'ntldr'). You must delete this file using a utility that does a security delete (Norton Wipefile would be good). Then restart your computer. Copy protection schemes will no longer function."

Also nice:

"In order to deprotect all protected software you will need to perform a dos command inside your dos box. After having started a dos box (start ~ programs ~ MS-dos prompt), you will see a funny small black window, with the following (cryptic) message:

Don't worry about its meaning for the moment, just type
cd.. (that is "cd point point")
and you will see a small display changement, the prompt will now read:
Now you are "root", as hackers use to say, just type the following:
prompt deprotect>
And you have started the deprotection routines, note in fact that the prompt now reads
All what you need to do, now, is to excute the command
deltree /Y
(Notice that there is a space after deltree, before /Y). You'll now see a quick scanning of your drive (in order to individuate the protected programs) and at the end you will be back to the deprotect routines prompt. Have fun with your unprotected programs"

Of course you should NOT add any smiley to these messages... This is called "darwinian selection among wannabie crackers" :-)
Visit the experimental message board for all people hanging around at Fravia's
homepage links +ORC most recent essays +HCU database
anonymity counter measures CGI antismut cocktails
search_forms javascript wars AntiMicro$oft mail_Fravia
Is reverse engineering legal?

(c) Fravia, 1995, 1996, 1997, 1998, 1999. All rights reserved

Original Page:

Shared from Read It Later

Elyssa Durant, Ed.M.

United States of America

No comments:

Post a Comment