[1999-08-31]

The Reversal of NetNanny

Written by Saruman of DFR Research & Engineering.

 

"Thinking men cannot be ruled" -- Ayn Rand

 

Abstract

This essay is a technical review by means of reverse-engineering of the evaluation version of NetNanny v3.10[NNL98]. We take a look under the hood as to better understand how this censorware works and what its intentions are. This is primarily meant as a learning aid, because of which we are somewhat verbose as to how the reversal was done, hoping this essay can help computer litterate people with a desire to learn something about how a program can be reversed. Students of reverse engineering aside, this essay may also be of interest to anyone who's about to invest in this particular or any other censorware. The shortcomings and limitations we come to describe are shared between most - if not all - of these products, and understanding those might help a potential buyer to choose which - if any - censorware to purchase.

 

 

Introduction

This work is divided into seven parts, starting with section 1 - Installation and a first look around, which is just that. We then do some old-school very basic cryptanalysis in section 2 - log file analysis. After the short warm up we continue over section 3 - The key recovery debacle where we describe how easy it is to get sidetracked and reach wrong conclusions, something we quickly repair in section 4 - Bang the bare bone metal, where our target meet with SoftIce - our beloved systems debugger - for the first time, and we proceed to successfully reverse-engineer a chunk of assembly into high-level language. In section 5 - A higher level approach we reveal a dirty little secret NetNanny Ltd has been withholding from us. We then develop nnhack - a NetNanny tool for the unfortunate soul who have to live by NetNannys oppression, but no more! - a theme we continue in section 6 - Disabling a running target where we give a short lecture on how to disable NetNanny using nothing but household tools. In section 7 - Final analysis we discuss the security of this program - and also of software like this in general - and try to give some suggestions as how to increase overall security and lessen the vulnerability to attacks such as the ones we've mounted. All good things must end, and with section 8 - Closing words we say our goodbyes, give a few pointers and leave you with nothing but a whole lot of new knowledge and a few references to follow up on.

Readers who have no interest in the low-level techniques we use might want to skip to section five and read from there. You will miss out on the techniques, but you should be able to follow the tread from there to it's conclusion in our final analysis anyhow.

A note to the scholars amidst our readers: We are laymen, moving through many different fields while using a language that is not our native one. This is not a work of science. We may enter or touch upon your area of expertise, and we are certain that we somewhere misuse a technical term, give a substandard explanation or what not. Please forgive us our ignorance, for we wish not to remain with it. You can help us by simply sending in your critique of our work; constructive criticism may result in updates to this essay. Thank you.

 

 

Section 1 - Installation and a first look around

Suspicious of the intentions of this program we start by doing a backup of the Windows directory, including the registry. These kinds of targets are known to infest the system, replace system dlls and what not. Better safe than sorry. Installation can then proceed as normal.

NetNanny have two major functions, first it hooks into winsock and check every hostname against it's internal list of banned sites. If a match is made NetNanny may - depending on how it's been configured:

NetNanny can also be configured to react to certain keywords and phrases. NetNanny does this by scanning the editing fields and other so called 'controls' in the application being monitored. When a match is made NetNanny may

By running the target we get to know it and can map out what we want to know about it. Obviously we want to learn how to disable the running target. We want to know how to extract the administrator password so that we can provide the tools and knowledge for a user to bypass the software. One must ask oneself why the lists of banned sites and keywords are encrypted. We can see that the program does give us easy access to the banlists from within it, but how can we be sure that we really get to see everything? Well, we can't, so we will also want to decrypt the file containing all the lists of sites and keywords banned.

We can see that the file 'wnn3b.dex' is the source of the banlists, it's of the right size, got a tiny header and the data is obviously encrypted. We use the internal viewer to copy and paste the hitlists into a textfile, and then we compare the size against the encrypted file. We notice a difference; the encrypted file is 376,155 bytes including a 633 byte header, and our extracted lists amount to just 303,018 bytes, meaning we have a 20% difference, or roughly 70Kb. There could be something hidden here.

We also notice that the logfile is encrypted, which is really great for us because it's small, it's easy to manipulate and there's a handy little function in the target to view the decrypted log, which gives us easy access to some know-plaintext. This is where we will start.

 

 

Section 2 - Log file analysis

The file 'Wnn3.log' in the installation path keep a log of alerts and changes to the configuration. This file is a binary (in the meaning "non-ASCII") and encrypted. To facilitate reversal we proceed by first documenting this unknown file format.

First we see what is clearly a 0x18 byte header, We designate this TLogHeader.

TLogHeader (0x18 bytes)
Offset Size Description
0x0000 4 Offset of first entry (or size of header if you will). Normally { 0x18 }
0x0004 4 { 0x01F4 }
0x0008 4 { 0x01FF }
0x000C 4 Number of records in log
0x0010 4 same as above?
0x0014 4 same as above?

After which follows one or more TLogEntries:

TLogEntry (0x79 bytes)
Offset Size Description
0x0000 4 Entry number
0x0004 4 Date
0x0017 2 RecordType
0x0033 32 Msg/List Item
0x0065 14 Program/User

So what did we do to get here? How can we know they layout of the fields when the record is encrypted? Simple, we started out by looking over the log file. Even though it's encrypted we can see from the header, the size of the file and how it increase in 0x79 byte blocks that it's ordered into records. Further more, we can see from how the encrypted data repeats that they're using the same key - or one very similar - to encrypt every record.

We then began mapping out the start and size of each field by probing the file. First, we change the first byte of the encrypted data. Viewing the file in NetNanny now showed that the "Entry#" changed. We revert back, and instead probe at offset 3. Again, the "Entry#" change, this time in a big way. We conclude that the first four byte is used to hold the field "Entry#". We proceed to probe some more, but it is very tedious and really only good for getting an initial feel about how to file is lain out.

We found the start of all but the fourth field in this way. However, it does not get us any closer to discovering the encryption algorithm, for that we need our trusty toolbox of SoftIce, IDA and Hiew... or we need a little experience, a hunch, and a bit of luck.

A very common encryption method used in software like this, is to exclusively-or (also known as 'addition modulo 2') the datastream against a running key - possibly of fixed size - but common is also to use a Pseudo Random Number Generator (PRNG) to generate a 'random' series of bytes against which the data is encrypted, in effect the seed for the PRNG is the key in that case. This is very easy to write, it's fast, and it often gives what at first impression looks like highly secured encrypted data. This is the kind of encryption that, in the words of Schneier "stop your kid sister from reading your files"[BS96]. Obviously it won't stop a person with the right tools and a little basic knowledge of cryptography for very long.

Since this method is so very common, we decided to try and find out if this was the kind of kid-sister encryption used by NetNanny, it would certainly be suitably ironic if it were. Of course it must be noted that it really doesn't matter much just how good a encryption-algorithm a program such as this use. As long as it distributes the key and ciphertext with it, it will always be easy to reverse.

We start by extracting the first two records into separate files which we designate block 1 and block 2. As we can see, they are very much alike, but not identical - if they were, this excercise would be useless. We have highlighted the differences between the blocks.

Block 1 Block 2
24 7A CF 24-3F 22 83 4F-CD 22 77 CC-21 76 CB 20
75 CA 1F 74-06 5B B0 0B-5A AF 04 59-AE 03 58 AD
02 57 AC 01-56 AB 00 55-AA FF 54 A9-FE 53 A8 FD
52 A7 FC 61-A6 FB 50 A5-FA 4F A4 F9-4E A3 F8 4D
A2 F7 4C A1-F6 4B A0 F5-4A 9F F4 49-9E F3 48 9D
F2 47 9C F1-46 9B F0 45-9A EF 44 99-EE 43 98 ED
42 97 EC 41-96 8E 24 F1-93 3F 94 E9-3E 93 E8 3D
92 E7 3C 91-E6 3B 90 E5-3A
27 7A CF 24-98 3B 83 4F-CD 22 77 CC-21 76 CB 20
75 CA 1F 74-06 5B B0 06-5A AF 04 59-AE 03 58 AD
02 57 AC 01-56 AB 00 55-AA FF 54 A9-FE 53 A8 FD
52 A7 FC 02-D3 98 33 C0-89 3C A4 F9-4E A3 F8 4D
A2 F7 4C A1-F6 4B A0 F5-4A 9F F4 49-9E F3 48 9D
F2 47 9C F1-46 9B F0 45-9A EF 44 99-EE 43 98 ED
42 97 EC 41-96 8E 24 F1-93 3F 94 E9-3E 93 E8 3D
92 E7 3C 91-E6 3B 90 E5-3A

We then xor the blocks against each other - which constitutes a ciphertext only attack - the output firmly confirms our suspicions This is what we see:

0000: 03 00 00 00-A7 19 00 00-00 00 00 00-00 00 00 00
0010: 00 00 00 00-00 00 00 0D-00 00 00 00-00 00 00 00
0020: 00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00
0030: 00 00 00 63-75 63 63 65-73 73 00 00-00 00 00 00
0040: 00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00
0050: 00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00
0060: 00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00
0070: 00 00 00 00-00 00 00 00-00

As we know from cryptoschool, what we get when we xor two ciphertexts enciphered using the same key - as we suspect is the case here - against each other, is the equality of plaintext1 xor plaintext2. If we do not feel that the neat layout of fields separated with zeroes we see above is proof enough that our guess were correct, let's do what is know as a know plaintext attack to confirm our findings. Do we have any know plaintext? Certainly.

If you remember, we probed the first byte of the log record and found out it stores the entry number. We then know that the first byte of the first record must be a binary one (it could be zero, but it's less likely), and that the first byte of the second record should read a binary two. There is our known plaintext. We then do 1 xor 2, which yields 3, which is what we have in our two xor'ed plaintexts above. Congratulations, you have just passed the first exercise of Classical Cryptanalysis 101.

More apparent in our example however, is the string 'cuccess' seen starting at offset 0x33. How is it that we get plaintext? Simple. The field we see is "Msg/List Item" where only one of our records had a string and the other was simply an ascii '0' followed by a bunch of binary zeros. 'key xor zero = key', which means that we in our trial decryption got 'cipher xor key' which of course gives us the correct plaintext, sans the first byte.

With this scheme, for any location of which we have know plaintext, we can also deduce the key. At this stage it would take some work as we must figure out how the date is stored and also what RecordType is. We can guess that the four bytes used by the date mean that it is a UNIX-datetime stamp, we can also guess that the two-byte RecordType field is low-order integers, indexing an array of strings. Searching 'wnn3.exe' for the string 'Log File Cleared' (on of the strings appearing in the log) confirms this. Assuming this is correct, we can potentionally know all plaintext for a record, and thus we can work out the complete key.

This however, is not the path we shall choose. Why? Well, since we have part of the key we made a search in the executable for it, and found nothing. This suggests that the key is calculated in some way. It's either that, or the key itself is encrypted which is - although possible - not very likely.

 

 

Section 3 - The key-recovery debacle

Before we leave the basics of cryptanalysis behind, let's check if the banlist file is protected in the same way. This is a little detour but it's perfectly safe and it might save us some time up ahead. We must first capture some files in a for us favourable state, so let's do just that. We begin by copying the contents of the "Words and Phrases #1" list to an external file by selecting all the words and copying over the clipboard into a file. We call this file 'plain1'. Now we make a backup of the file 'Wnn3b.dex' and call it 'dex1'. Now we change the first word in the list by replacing its characters by X. We let NetNanny save this modification. Now we compare our old snapshot of Wnn3b.dex, the file 'dex1' with our newly modified one and discover a whole range of changes. If we look at the end of the list of changes we see that at offet 0x5B30F there is a cluster of as many changed characters as we ourselves changed in the banlist. We make note of this offset since this is where we believe the encrypted list "Words and Phrases #1" begin in the larger file.

Now we use a tool to copy out the section from that offset - in our case 0x5B30F - of 'dex1', down to the very end. We call this file 'cipher1'. If this extracted file is about the size of our plain text file 'plain1', then we know we are on the right track. In fact, we want the files to match exactly in size. When we did this just now, we were off by one byte. This means that either we have one byte to many at the end, or we have one to many at the beginning. We chose - pretty much arbitrary, but we might have been one off on the offset earlier - to remove the byte at the top of the file.

We now xor these two files - 'plain1' and 'cipher1' against each other. Assuming our guesses as to how the files are encrypted holds, xoring the plaintext against the ciphertext should give us the key. Below is a small portion of the output, and boy does that look suspicious or what? First we cannot but notice the symmetry in the low nibble of every byte as we read the key in rows. We now try to detect if this is a pseudo-OTP or if the key repeats. We do this by taking the first two bytes and searching for them.

0000: 5C B1 06 5B-B0 05 5A AF-04 59 AE 03-58 AD 02 57
0010: AC 01 56 AB-00 55 AA FF-54 A9 FE 53-A8 FD 52 A7
0020: FC 51 A6 FB-50 A5 FA 4F-A4 F9 4E A3-F8 4D A2 F7
0030: 4C A1 F6 4B-A0 F5 4A 9F-F4 49 9E F3-48 9D F2 47
0040: 9C F1 46 9B-F0 45 9A EF-44 99 EE 43-98 ED 42 97
0050: EC 41 96 EB-40 95 EA 3F-94 E9 3E 93-E8 3D 92 E7
0060: 3C 91 E6 3B-90 E5 3A 8F-E4 39 8E E3-38 8D E2 37
0070: 8C E1 36 8B-E0 35 8A DF-34 89 DE 33-88 DD 32 87
0080: DC 31 86 DB-30 85 DA 2F-84 D9 2E 83-D8 2D 82 D7
0090: 2C 81 D6 2B-80 D5 2A 7F-D4 29 7E D3-28 7D D2 27
00A0: 7C D1 26 7B-D0 25 7A 0C-61 B6 0B 60-B5 0A 5F B4
00B0: 09 5E B3 08-5D B2 07 5C-B1 06 5B B0-05 5A AF 04

We find one match at offset 0xB7. We can instantly see that this is a good candidate, the streams match! Working from the assumption then that the key size is 0xB7 bytes, we go to offset 0xB7*2 = 0x16E and yes indeed, again our friend 5C B1

0150: 29 7E D3 28-7D D2 27 7C-0E 63 B8 0D-62 B7 0C 61
0160: B6 0B 60 B5-0A 5F B4 09-5E B3 08 5D-B2 07 5C B1
0170: 06 5B B0 05-5A AF 04 59-AE 03 58 AD-02 57 AC 01
0180: 56 AB 00 55-AA FF 54 A9-FE 53 A8 FD-52 A7 FC 51

The symmetry is startling, if we look a little closer we see that the key is counting. To see this, first look at the key as built from three-byte entities, and then picture those as integers, ie, swap the bytes around. Like this:

0x06B15C , 0x05B05B  , 0x04AF5A , 0x03AE59, etc.

Clearly this is a decreasing numerical series. Let's try and subtract n+1 against n and see what that gives us.

0x06B15C - 0x05B05B = 0x010101
0x05B05B - 0x04AF5A = 0x010101
0x04AF5A - 0x03AE59 = 0x010101

we also try the two last entries of the key

0x08B35E - 0x07B25D = 0x010101

Seems like we're on to something, definitely. But this is nothing like the key used for the log file. Different keys, indeed.

To further explore the keychange mechanism we do a clean backup of our 'Wnn3b.dex' file, we then open the first banlist using NetNanny, enter a space, remove it again and close the list, letting NetNanny save the 'changes'. We compare the latest 'Wnn3b.dex' against our copy made. We discover that there is only one change, at offset 0x19 the byte has changed from 0x5B to 0x5D. We do the same procedure again only to see the same effect now from 0x5D to 0x5F. This demonstrates that the same PT encrypts to the same CT every time, the 'generation counter' - as we shall call the offset 0x19 for now - does not affect the key in the slightest.

We now make a minor modification to the first phrase in the banlist. We change 'Adult check' into '@dult check'. This constitutes a one-bit change. We compare the two latest versions of our banfile against each other. We find that our trusty companion at 0x19 continues it's count, and we also find a one-bit change in the ciphertext at offset 0x5DDAD, from 0x1D to 0x1C. We know that the original PT is 'A' which gives us a key of 0x41 xor 0x1D = 0x5C, which equals that of 0x40 xor 0x1C. Have we ever seen this 0x5C before? Yes we have, it the start of that 5C B1 key we discovered earlier.

The attacks described above are somewhat similar to the insertion-attack described by Schneier in section 9.7 of AC[BS96].

At this stage it's time to reverse as much as possible of the structure of the wnn3b.dex file. We want to know where the encrypted messages start, their length and later we want to know where they end up in the program. We do this simply by employing our sturdly hex-editor and our brains. If you have the opportunity, look in the 'wnn3b.dex' file yourself, so that you get a feel for the layout. Not wanting to repeat big chunks of hex-data we will describe each block in the order of which they are found in the file, not necessarily in the order in which we mapped them out.

Here's the first part of the file:

TDex.Header.Rootdata

0000: 79 02 69 CD-AB 69 2C 01-33 2E 31 30-20 33 00 01 yi-½i,3.10 3 
0010: 00 45 03 00-00 58 00 00-00 49 00 00-00 2A 00 32 E X I * 2

When reversing unknown file formats we work mostly on intuition and some basic knowledge on how binary datafiles are usually laid out. We search first for blocks of data that might be related. We then try to tie these blocks together by searching for references to their location and size. The first few bytes of a file are usually of some importance since they often contain some kind of identifier which might give clues. Not so in this case though. What you see above is what we feel is the first chunk of related data in the header. Why this is is pretty obvious once you look at the rest of the header (see below). When we look at the whole beginning part of the file we notice that at offset 0x0279 the structure of the data quickly changes form; we can safely assume that the header ends there and the actual file contents start. If we look at the first two bytes we see that they must mean HeaderSize. Our next stop come at offset 0x8 where we can see the string "3.10 3", obviously a version string, although the " 3" may be a revision level, it might also not be part of the version string at all.

At offset 0x0020 begin what looks like three different chunks of data. We first discovered the utility of the second and third, after which this the first block - seen below - made much more sense.

TDex.Header.BlockIDDir

0020: 00 10 01 00-80 00 00 01-80 FF FF 00-00 21 00 00
0030: 81 22 00 00-81 00 10 00-00 00 20 00-00 00 40 00
0040: 00 01 40 00-00 02 40 00-00 03 40 00-00 04 40 00
0050: 00 05 40 00-00 06 40 00-00 07 40 00-00 08 40 00
0060: 00 09 40 00-00 20 00 00-81 20 01 00-00 FF FF 00
0070: 00 00 10 00-80 00 20 00-80 00 40 00-80 01 40 00
0080: 80 02 40 00-80 03 40 00-80 04 40 00-80 05 40 00
0090: 80 06 40 00-80 07 40 00-80 08 40 00-80 09 40 00
00A0: 80 0A 40 00-80 0B 40 00-80 0C 40 00-80 0D 40 00
00B0: 80 0E 40 00-80 0F 40 00-80 10 40 00-80 11 40 00
00C0: 80 12 40 00-80 13 40 00-80 00 00 00-00 00 00 00

While there is structure to this block, it must be seen in the light of the following two. We will come back to this one later. Please note that we have omitted rows of zeroes, such as the one at 0x00D0.

TDex.Header.BlockOffsetDir

00E0: 00 00 00 00-00 00 00 00-00 79 02 00-00 7D 02 00
00F0: 00 B3 02 00-00 C4 02 00-00 66 03 00-00 D0 03 00
0100: 00 4D 0E 00-00 80 44 00-00 D8 B9 00-00 40 2F 01
0110: 00 9B A4 01-00 F8 19 02-00 53 8F 02-00 B7 04 03
0120: 00 06 7A 03-00 70 EF 03-00 DB 64 04-00 35 A6 04
0130: 00 5E A6 04-00 1A A7 04-00 61 A7 04-00 1D AA 04
0140: 00 95 B1 04-00 B5 B8 04-00 A1 CD 04-00 4D D6 04
0150: 00 69 E9 04-00 89 F5 04-00 45 03 05-00 79 0D 05
0160: 00 3D 1E 05-00 AD 27 05-00 C9 38 05-00 95 42 05
0170: 00 49 54 05-00 B1 5D 05-00 7D 6F 05-00 81 7E 05
0180: 00 C5 89 05-00 89 95 05-00 85 A4 05-00 61 A9 05

Again we see the end of the header referenced, this time at offset 0x00E9. The first time we saw that number - 0x279 - was at offset 0x0000. Since this occurence is in the middle of the block we can guess that it's a pointer. From the way the following words increase in value we can reach the correct - as it will prove - conclusion that this is an array of pointers to the start of encrypted blocks.

The observant reader will notice that the offset we made our KP attack against earlier is not in this list. That is correct, 0x5B30F is nowhere to be found. First and foremost; the list we use here are the original unedited one, but even if we check the one we did use we would not find our offset, though, we would find a close match in 0x5B2D5. This of course leads us to the insight that there are 58 bytes between the start of the block and the actual first letter of the first word in the banlist. This must be a header of some kind. What exactly we cannot know lest we decrypt it. More on that later.

Where we have pointers, we often have lengths too. Those words are way to large at the end to be lengths so we look elsewere. "But maybe the lengths are calculated", you object. Yes, that could be the case, but it is very rarely so. Anyway, this discussion is made void as we glimpse the following chunk of data.

TDex.Header.BlockSizeDir

01B0: 00 04 00 00-00 36 00 00-00 11 00 00-00 A2 00 00
01C0: 00 6A 00 00-00 7D 0A 00-00 33 36 00-00 58 75 00
01D0: 00 68 75 00-00 5B 75 00-00 5D 75 00-00 5B 75 00
01E0: 00 64 75 00-00 4F 75 00-00 6A 75 00-00 6B 75 00
01F0: 00 5A 41 00-00 29 00 00-00 BC 00 00-00 47 00 00
0200: 00 BC 02 00-00 78 07 00-00 20 07 00-00 EC 14 00
0210: 00 AC 08 00-00 1C 13 00-00 20 0C 00-00 BC 0D 00
0220: 00 34 0A 00-00 C4 10 00-00 70 09 00-00 1C 11 00
0230: 00 CC 09 00-00 B4 11 00-00 68 09 00-00 CC 11 00
0240: 00 04 0F 00-00 44 0B 00-00 C4 0B 00-00 FC 0E 00
0250: 00 DC 04 00-00 74 09 00-00 00 00 00-00 00 00 00
0260: 00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00
0270: 00 00 00 00-00 00 00 00-00 DC 32 87-DC 8A 7D D5
0280: 98 61 C7 9F-1E C0 82 6A-89 D3 60 C6-8C 7E C1 96
0290: 7C C4 F8 6E-D8 90 27 75-9D 6C 26 83-64 32 80 62

Yes, here they are. The block-lengths. At offset 0x01B1 they begin.

For your reference we've included the last two rows and then some to show how the rest of the file reads. You can clearly see the change in structure at 0x279 where the encrypted data begin.

Now, what about the first of these three last chunks? Well, since it's very much like BlockOffsetDir and BlockLengthDir - it's just as large and contain as many entries, we must assume that they are related in some way. We also know since earlier that this file store a lot of different things, everything from the different kinds of banlists - of which there seem to be two: one for words and phrases and one for URLs -  to configuration options and users/passwords. It is only logical that somewhere there must be some kind of ID so that the program can know where to find a particular type of data. It would not be logical to have this under the encryption of each block as that would require lot's of seeking and unneccesary reading. No, we can safely assume that we at offset 0x0021 have a BlockIDDirectory.

Also, there are many more entries in these directories than what can be expected. After all, there are only twelve different banlists, but we can count to 0x2A (42d) entries in the directories above!

With this new knowledge we again look at TDex.Header.Rootdata to see if we can fill in some blanks.

0000: 79 02 69 CD-AB 69 2C 01-33 2E 31 30-20 33 00 01 yi-½i,3.10 3 
0010: 00 45 03 00-00 58 00 00-00 49 00 00-00 2A 00 32 E X I * 2

See offset 0x001D? So close to the directories, it cannot be a considence. At offset 0x001D we have the actual count of entries in the three different directories. Do you see the word after it? The value 0x32 is interesting. After playing around with NetNanny we see that these lists may grow. Say for instance that we add a word to some banlist. If the banlist grows NetNanny cannot simply overwrite the old one. Also, rewriting the whole file would be time-consuming (not very much though).

The whole idea of using directories like these - with length information - revolves around the idea that you can write a block to the file without having to rewrite the whole file, under the condition that the new data has not grown larger. If it have, then what can you do? You can either rewrite the whole file - that is called a lazy approach - or you can write the new data to the end of the file and then either change the directory information to point there, or mark the old entry as 'deleted' and write a new one. This is what NetNanny will do. If we add to one of the banlists we will find that the counter of directory-entries as 0x001D will increase to 0x2B, and a new entry will appear in the directories. This is fine in the short run, but the directories cannot grow without bounds - they are after all tagged onto each other.

All this brings us back to that value at 0x001F. You see, if you calculate the maximum number of entries one directory can contain without overwriting the following one, you will see that you get... that's right: 0x32. In our example we calculate this by 0x00EB-0x0021 = 0xC8 bytes, giving 0xC8/4 = 0x32 entries. Another four bytes of the header mapped out. Not too hard this reversing thing, right?

Where do we go from here? Well, we write software to decrypt the whole file of course.

This first tool will be pretty crude and it's only purpose is to use the directories of the .dex file to locate each block of data, and then decrypt that and write it out to file for further inspection. We know how to find the encrypted blocks, and our PT attack on a banlist provided us with the key, "5C B1 ...". The implementation is trivial and as such, left as an exercise for the ambitious student.

Or is it really? You write the code and the key does not work, what's wrong?! Did you forget to calculate a new first byte of the key? Remeber, our PT attack did not start on a encrypted block boundary since there were a 58 byte header first and the key is larger than that. The correct key begin not with "5C B1", but with "DD 32". If you want to, you can check this yourself by going back to our old table of the key here.

We now have everything needed so that we can finally decrypt all of that pesky wnn3.dex file and get a glimpse of that promised land.

Or so we thought. Look at the excerpt below.

### Block #6 (ID: 00001000) at 0x000003D0, length 0x00000A7D ###

Words and Phrases #1 GC
Adult Check
adult entertainment
adult gif
Adult ID
adult images
adult links
adult movies
adult pics
AdultCheck
AdultSights
amateur sex
amateur videos
amate¶7ï², en

When we look at the output we see that the decryption is not 100% - there are lot's of garbled sections - and it seems to get worse too! All this work, and nothing to show? Not good, not good at all. Obviously this key is not at all static, it undergoes some small changes, seen in the decryption as strings of garbled text. So, did we lose the war? No, not by a long shot.

Remember the last paragraph of section 2? "This suggests that the key is calculated in some way." Oh yes, how correct we were.

This is where we leave the pure Euclidian approach of "pen, paper and an unmarked ruler" behind and start up our heavier tools. We must get into the executable, into the decryption-routine and there try to find out what happens to the key. No, this battle is far from over. We might have lost the first skirmish of the battle, but a new day is setting, and we've just received reinforcements.

 

 

Section 4 - Bang the bare bone metal

How can we find the encryption code in the executable? There's lot's of ways. We could use the method we used with CyberSitter[DFR98]; putting a breakpoint on CreateFileA, waiting for the correct filename to show up, change the BP to ReadFile and then tag along to see what happens. But we won't, especially since the exact same procedure wouldn't work with NetNanny being a 16-bit application (Delphi 1) and all.

So what did we do? We loaded wnn3.exe into IDA, our favourite disassembler, and before too long we found what we were looking for, a line reading:

cseg04:19B9 26 30 07         xor     es:[bx], al

Dead giveaway. Apart from the usual register clearing stuff, xors are pretty rare and easy to find, if you know how to look for them. In our case we were searching for the string 'xor     es:' in the disassembly listing. Had that failed we would have searched for 'xor     [' instead, which would match against any writes in the data segment. Let's take a look at the whole block of code with our comments.

11BD7: mov  d,[bp-0A],0		; initialize counter. Track this one.
11BDF: jmps 011C00		; start first round
11BE1: push d,[bp-0A]		; push counter
11BE5: push w,[bp+08]
11BE8: push w,[bp+06]
11BEB: nop
11BEC: push cs
11BED: call 011C15		; update key (returns key in al)
11BF0: add  sp,08
11BF3: les  bx,[bp-06]        	; get address of decryption buffer
11BF6: add  bx,[bp-0A]		; calculate offset of byte to decrypt
11BF9: xor  es:[bx],al		; decrypt byte, key in al.
11BFC: inc  d,[bp-0A]		; increase counter.
11C00: les  bx,[bp+0A]
11C03: mov  eax,es:[bx+06]	; get size of buffer in eax
11C08: cmp  eax,[bp-0A]		; is end of buffer?
11C0C: jg   011BE1		; .. no, decrypt next byte

So, it's a basic loop which decrypts one byte for each round, and also calls another function to update the key. That is where we must go. To really see how this works the dead listing of IDA will not do. So we decide to take NetNanny on a spin in SoftIce. How can we break in on this routine? Simple, we add a breakpoint to it by entering the special opcode 0xCC (int 3) into the code and then setting a breakpoint in SoftIce for that interrupt using 'bpint 3'. So we patch the executable at offset 0x11BD7 and set our breakpoint and the run NetNanny. We are thrown into SoftIce as soon as the program tries to start decrypting a buffer. To be able to run the code we quickly re-patch the byte in memory by writing 'a' <enter> 'db 66' <enter> <enter>, where '0x66' is the byte we overwrote with 0xCC earlier.

If we set a breakpoint at the xor and let NN run a couple of times we will see that the key used begins 0xDD, 0x32, etc.. we've seen this before. Of course the call above is what is modifying AL, so we will focus on that for now.

Offset 0x11C15 thru 0x11C1F is just initialization code and not included in this listing.

11C22: les  bx,[bp+06]			; Get buffer address
11C25: cmp  d,es:[bx][06AA],0x100	; Compare seed
11C2F: jge  011C3B			; ..too small?
11C31: les  bx,[bp+06]
11C34: mov  al,es:[bx][06AA]
11C39: jmps 011C72			; exit routine
11C3B: les  bx,[bp+06]
11C3E: cmp  d,es:[bx][006AA],0x10000    ; Compare seed
11C48: jge  011C6E			; ..too large?

So what does this first part do? The first instruction fetches the offset of some unknown buffer into memory. In reality this one shows to be data read from the header of the .dex file, the first 32 bytes, which we've called TDex.Header.Rootdata. The word referenced is found at offset 0x11 of the header. In our wnn3b.dex file this value is 0x345. As we shall see this is the cryptographic key on which the encrypted data hangs. The first check makes sure this key is larger 255. If it is not we pass through the jump and the low byte of the key is loaded into al and then the updatekey()-routine exits, passing back al to be used in the decryption. In reality this never happens, this seems to be a safeguard of some kind, to make sure that the next stage does not fail.

After this come another check of the key. This time they make sure it has not grown larger than a word, or 65535. If it has we jump to 11C6E, which simply returns zero for the key to use for decryption. This mean that if they were to encrypt a buffer larger than 64K, the data at byte 65536 and up would not be encrypted/decrypted (we must not forget that this routine is also used to encrypt data). Since this is a 16-bit application, bound by 64Kb segments, that is not very likely to happen, and indeed, if we set a breakpoint at 0x11C6E we will see that it is never reached, not when starting NetNanny in any case, and that's when all buffers are decoded.

11C4A: les  bx,[bp+06]
11C4D: mov  ax,es:[bx][06AA]	; Get seed
11C52: add  ax,[bp+0A]		; Add counter to seed.
11C55: push ax			; push calculated key
11C56: push word ptr[bp+08]
11C59: push word ptr[bp+06]
11C5C: nop
11C5D: push cs
11C5E: call 011C77		; call calckey()
11C61: add  sp,006
11C64: mov  [bp-04],ax		; mask out the value to return. Probably
11C67: mov  al,[bp-04]		; a typecast from word to byte/char.
11C6A: and  al,0FF		; note: compiler generated useless code
11C6C: jmps 011C72
11C6E: mov  al,0		; key wrapped, return zero.

We are getting close, this whole routine seems just to be your typical maintaince routine, the real magic is in that call to 0x011C77. What we will see is that only the parameter from 'push ax' above are used (again, init-code snipped):

11C84: mov  cx,[bp+0A]		; get current key
11C87: mov  ax,cx		; store key in ax for future use..
11C89: mov  bx,0xB1		; .. and begin expr2
11C8C: cwd
11C8D: idiv bx                  ; integer division
11C8F: imul dx,0xAB		; final calc of expr2
11C93: mov  ax,cx		; get key again, begin expr3
11C95: mov  bx,0xB1
11C98: push dx			; store value of expr2
11C99: cwd
11C9A: idiv bx
11C9C: add  ax,ax		; final calc of expr3
11C9E: pop  dx			; retrive value from expr2
11C9F: sub  dx,ax		; start expr1. expr1=(expr2-expr3)
11CA1: movsx eax,dx
11CA5: mov  [bp-06],eax		; typecast(?) expr1 from long to integer
11CA9: mov  ax,[bp-06]		;   -""-
11CAC: imul ax,07FFF		; final calculation - return expr1 in ax

Once we've broken down the listing into expressions as complex as we feel that we can handle - you can see our division in the comments - we can proceed to reverse engineer them one by one, which yields something like this high-level language (Pascal) listing.

Function NetNannyKeyUpdate(const CurrKey: Integer): Integer;
var
 expr3, expr2, expr1, expr0 :Integer;

begin
 expr2:=(CurrKey mod $B1)*$AB;
 expr3:=(CurrKey div $B1)*2;
 expr1:=expr2-expr3;
 expr0:=expr1*$7FFF;
 NetNannyKeyUpdate:=expr0;
end;

The above is a direct translation from the disassembly, and after regression tests between our high-level code and the assembly code showed 100% success, we could reduce the function further into this single one-line statement:

XorKey = (((CurrKey mod 177)*171)-((CurrKey div 177)*2))*32767;

Where only the last eight bits are then used to decrypt each byte, and CurrKey is increased by one for each iteration.

This is also the algorithm used to encrypt the log file. The initial key can be found at offset 0x08 of TLogHeader. Our key read 0x1FF.

Victory at last.

 

 

Section 5 - A higher level approach

With the algorithm in hand we can now consolidate our acquired knowledge and write a program to decrypt the wnn3b.dex file. We call our program nndec, you will find the source and executable in our revofnn archive. The program is trivial and we will not discuss it's implementation here.

Let's instead go directly to studying the output. Here's the first few lines:

wnn3b.dex, with key 0x0345, contains 0x002A blocks.

### Block #0 (ID: 80000110) at 0x00000279, length 0x00000004 ###


### Block #1 (ID: 80010000) at 0x0000027D, length 0x00000036 ###

WORDPAD.EXE
NETSCAPE.EXE
IEXPLORE.EXE
EXPLORER.EXE

Here in Block #1 we find a list of the applications that are to be monitored for keywords and phrases. The list of blocks then goes on and on with keywords, newsgroups and URLs until we arrive at Block #18 and are treated some binary data. The header reads

### Block #18 (ID: 00000120) at 0x0004A65E, length 0x000000BC ###

We will look at the block data in hex:

80 FE E6 F2-EF A5 F3 E4-EF A5 F3 80-EE F4 80 EF
F2 80 D4 E8-E5 A0 C1 E4-ED E9 EE E9-F3 F4 80 80
80 80 80 80-80 80 80 80-80 80 80 80-80 80 80 80
80 80 FE E6-F2 EF EE F4-E4 EF EF F2-80 80 80 80
80 80 D7 8E-80 80 CC 6C-07 B2 34 18-AB 6B 02 A7
79 0E A1 72-1F B2 38 05-AC 3A 22 FF-FF 01 F3 E1
F2 F5 ED E1-EE 80 80 80-80 80 80 80-80 80 80 80
80 80 80 80-80 80 80 80-80 80 80 80-80 80 80 80
80 80 80 80-80 80 80 80-80 80 80 80-80 80 80 80
ED F9 F0 E1-F3 F3 F7 E4-80 80 80 80-80 80 80 80
80 80 80 80-EB EC EF F0-F0 E5 F2 80-80 80 80 80
80 80 80 80-80 80 80 80-22 FF FF 02-8D 8A A3 A3

See anything suspicious? No? Look again. Actually, it's easier if you can see the ASCII representation, so don't feel too down if you don't see this. See all those 0x80s? Those are the key. Literally. What we see is a second layer of encryption. Having done a fair share of reversing in our lives we easily spot this one. This is another "xor-encryption" with the static key 0x80. We can go back to the great computer game Maniac Mansion, a game published way back in 1988 and find something similar, although that time the key was 0xFF. As we said, this kind of encryption is very common. Let's decrypt this block and see what we find.

00 7E 66 72-6F 25 73 64-6F 25 73 00-6E 74 00 6F  ~fro%sdo%s nt o
72 00 54 68-65 20 41 64-6D 69 6E 69-73 74 00 00 r The Administ
00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00
00 00 7E 66-72 6F 6E 74-64 6F 6F 72-00 00 00 00   ~frontdoor
00 00 57 0E-00 00 4C EC-87 32 B4 98-2B EB 82 27 W Lýç2¦ÿ+Ùé'
F9 8E 21 F2-9F 32 B8 85-2C BA A2 7F-7F 81 73 61 ¨Ä!_ƒ2©à,¦ó&127;&127;üsa
72 75 6D 61-6E 00 00 00-00 00 00 00-00 00 00 00 ruman
00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00
00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00
6D 79 70 61-73 73 77 64-00 00 00 00-00 00 00 00 mypasswd
00 00 00 00-6B 6C 6F 70-70 65 72 00-00 00 00 00     klopper
00 00 00 00-00 00 00 00-A2 7F 7F 82

Ooops. What we have found here is my administrator account 'saruman' and my password 'mypasswd' and.. wait a minute, that string '~frontdoor' looks a little suspicious, and not so little either! We start NetNanny and enter ~frontdoor for the password and... we're allowed entry. We've found a backdoor.

This is not all that surprising really. Users want their data secure, but not so secure they cannot retrieve it if they forget their password. This is of course a paradox as far as security goes, real security from encryption can only be had when the key and only the key opens the lock. Unless this backdoor varies from installation to installation, and we see no reason to believe that, then we have just opened the lock to every NetNanny v3.10 installation in the world, and possibly earlier versions too.

The string 'klopper' is just some old junk and of no importance, unless you count an old password as important.

By adding a user we could determine that a block with ID 0x120 is the 'accounts' block and one with 0xFFFF is marked as deleted. We could also determine the size of an account as 0x5E bytes, with offset 0 holding the username and offset 0x32 the password. The accounts rights are also stored here somewhere, but we already have the master key and do not really need to figure all those little things out.

The master key will - by the way - work many situations, also when NetNanny triggers on a bad word or phrase and prepare to shut the application down. Depending on configuration you may get the chance to bypass the shutdown, and this password will work there too. The one place it does not function is to activate the uninstall program.

We have developed a small Win32 assembly hack which you can download and run. It will decrypt and display each user account defined in NetNanny. We call this hack for nnhack. You can download and run the executable, it's only 4Kb, or get our neat little package which also includes the complete source. The program will locate the needed file automatically if you have NetNanny installed, so you can run it from anywhere, even directly from your webbrowser if it will let you. You can also feed the program the filename to decode on the commandline. A note of warning is in place: running executables downloaded from the net is always a risk in itself, you never know what they might do (well, not unless you reverse them :-). Be careful.

To explore the last of the uncharted territory, we create a table of all the blocks. Remember, it's not the block number that is important, it's the ID.

Block ID Offset Length Comments
0 0x80000110 0x00000279 0x0004
1 0x80010000 0x0000027D 0x0036 Applications monitored for keywords
2 0x0000FFFF 0x000002B3 0x0011 Deleted block
3 0x81000021 0x000002C4 0x00A2 WSOCK32.DLL:...
4 0x81000022 0x00000366 0x006A WS32_32.DLL:...
5 0x00001000 0x000003D0 0x0A7D Words and Phrases #1
6 0x00002000 0x00000E4D 0x3633 Internet News Groups #1
7 0x00004000 0x00004480 0x7558 Internet WEB Sites #1
8 0x00004001 0x0000B9D8 0x7568 Internet WEB Sites #2
9 0x00004002 0x00012F40 0x755B Internet WEB Sites #3
10 0x00004003 0x0001A49B 0x755D Internet WEB Sites #4
11 0x00004004 0x000219F8 0x755B Internet WEB Sites #5
12 0x00004005 0x00028F53 0x7564 Internet WEB Sites #6
13 0x00004006 0x000304B7 0x754F Internet WEB Sites #7
14 0x00004007 0x00037A06 0x756A Internet WEB Sites #8
15 0x00004008 0x0003EF70 0x756B Internet WEB Sites #9
16 0x00004009 0x000464DB 0x415A Internet WEB Sites #10
17 0x81000020 0x0004A635 0x0029 Applications monitored for hostnames
18 0x00000120 0x0004A65E 0x00BC User accounts/passwords
19 0x80001000 0x0004A761 0x02BC
20 0x80002000 0x0004AA1D 0x0778
21 0x80004000 0x0004B195 0x0720
22 0x80004001 0x0004B8B5 0x14EC
23 0x80004002 0x0004CDA1 0x08AC
24 0x80004003 0x0004D64D 0x131C
25 0x80004004 0x0004E969 0x0C20
26 0x80004005 0x0004F589 0x0DBC
27 0x80004006 0x00050345 0x0A34
28 0x80004007 0x00050D79 0x10C4
29 0x80004008 0x00051E3D 0x0970
30 0x80004009 0x000527AD 0x111C
31 0x8000400A 0x000538C9 0x09CC
32 0x8000400B 0x00054295 0x11B4
33 0x8000400C 0x00055449 0x0968
34 0x8000400D 0x00055DB1 0x11CC
35 0x8000400E 0x00056F7D 0x0F04
36 0x8000400F 0x00057E81 0x0B44
37 0x80004010 0x000589C5 0x0BC4
38 0x80004011 0x00059589 0x0EFC
39 0x80004012 0x0005A485 0x04DC
40 0x80004013 0x0005A961 0x0974

We admit not much time went into this part, but here it goes. These 0x8000xxxx blocks seem to index the banlists and are used by wnldr32.exe - the 32-bit driver hooking into winsock. If you simply change their IDs to 0x0000FFFF - in effect marking them as deleted/nonexistant - you will find that you can reach banned sites even though NetNanny is running. The remarkable thing is that these indeces will not regenerate. We couldn't get them to anyway. This means that while on the surface everything will look like it's working - and while you can edit the banlists - NetNanny still won't stop you from reaching them. A NetNanny installation hacked in this way would have to be empirically tested by the administrator in order to make sure it's functioning - opening up the administrator software and checking if the "On" button is lit, or checking the task-manager for wnldr32.exe, just wouldn't be enough.

It would be logical if 0x8000xxxx IDs are related to their 0x0000xxxx counterparts. Notice that they are all multiples of four in length, this suggests that any data they hold are also in multiples of four.

Figuring out the exact functioning of these blocks we leave as an exercise. See if you can see any pattern/correlation in size between the banlists and their indeces. Do some probing, and when all else fails - crack the executable open.

In this section it might be fun to comment on a bug we found. You can create an account with a password of 19 characters, but you if you do, you cannot log into the account again. Fixing this bug is also left as an exercise :-)

 

 

Section 6 - Disabling a moving target

This section could grow to fill a whole new essay, so we will not get into the nitty gritty here. The goal is simply to disable NetNanny while it's running, assuming we do not have access to the main NetNanny executable (it might have been hidden or even deleted). We also assume we - for some reason - cannot disable it by closing it from the task-manager. In short, we picture a scenario where we are severely handicapped.

NetNanny is a 16-bit application, and it relies on a 16-bit DLL named 'WNDRV16.DLL' to do some of it's low-level work. It also use a set of 32-bit modules to take care of important tasks such as hooking into winsock2.dll. Communication between 32-bit and 16-bit applications are tricky because of the Windows architecture, and the result is often a bloated array of 'thunking code'. Brian Long explains the term 'thunk' in [BL96];

"A thunk is a small nugget of code that acts as a mediator between two other pieces of essentially unrelated code. 16-bit compiled code is not capable of calling 32-bit code directly, and the converse is also true. A thunk of some sort is used to set things up so that one side can call the other in a defined fashion."

The good news is that we don't have to write our own thunking code - NetNanny will be happy to go our bidding.

To disable NetNanny 3.10 all you need to do is to open the 'wnn3.ini' file in the %windir% directory, and there change the key 'Session' under the section 'Communication' to read '0' (stopped/disable) instead '1' (running/enable). This modification is then communicated to the different loaded modules (there are five of them in total) by calling the two executables 'Wncom16.exe' and 'Wncom32.exe'.

There are some bad news too. Disabling NetNanny is not the same thing as unloading it, and it will continue to monitor for 'unallowed' applications. Fortunately, this too is stored away in the 'wnn3.ini' file, and even though NetNanny will tell you that the only way to start an application after it's been banned is to reboot the computer, you will only have to edit the wnn3.ini file some more and remove any referense to the application. This is how it may look:

[Communication]
Session=1
Shutdown32=OPERA

[Applications]
Disable0=OPERA

Simply remove everything but the first two lines and with a little luck, you'll be back in business.

When we started writing this section we first thought to develop our own code to interact directly with the low-level modules (this explains the blurb about thunking, which we left in as trivia). However, since the above procedure is so simple to do, the development of custom software have been put on ice. A 32-bit assembly program for disabling the 16-bit portion of NetNanny is available on request.

 

Cracking

The date of installation is recorded in the four byte file %windir%\system\WNEV31.DAT stored in UNIX-time. This file is created if not found by writing the current date + 0x278D00 (approx 30 days) into it. On subsequent runs the file is read and the current date is compared against the expirey date. Extension of the so called trial period is thus trivial:

This is a simple sidenote - but representative of the effort put into securing this product - we are at loss as to why anyone would like to run this program for even one day, much less thirty.

 

 

Section 7 - Final analysis

Overall this product recieve the score of mediocre.

The biggest problem is that it does not fill any real purpose, it does not accomplish the things it's touted to in any meaningful way. Can pornography be accessed thru a computer running NetNanny? Certainly. We've run empiric tests which show that NetNanny does not hinder the access of pornography in any way worth mentioning. The banlists are extremely small compared to those of other censorwares, and while this may be good in one way - maybe a human did review every single on of those banned URLs - it also means that no significant portion of all the internets pornography carrying sites are included. The original banlist includes a little less than 12,000 URLs, some of which are questionable, why is a section of the Internet Movie Database (us.imdb.com) banned? Why the mailinglists of Cornell University (listproc@cornell.edu)?
These 12,000 links or so may sound like a lot to you, but compared to the size of the internet it is nothing. Run this test; go to a search-engine such as AltaVista and enter a few chosen keywords of "obscene" nature, then read off the number of hits you get. A search for 'porn naked women' just gave us 4,414,900 hits. If you just enter 'porn' you'll get around 18 million hits. Granted, some of those are benign - this document would be a hit for such a search - but it gives us a sense of scope; the twelve thousand URLs of NetNanny are just a tiny fraction of the million pages available. Research[KBAB98] indicate that the size of the web (static pages only) in late 1998 was around 275 million pages1 - a sizable chunk of which are porn related and indexed by search engines - and the growth rate is phenomenal. When you see it like that, those 0.011 million URLs banned by NetNanny sure look tiny.

One might be led to believe that this is just an academic number game, NetNanny would never let one actually do one of these searches, Right? Wrong. The other big feature of NetNanny - the banning of specific keywords - is so badly implemented as to making it right out worthless. The searches above were made on a "NetNanny Enabled" computer, with the browser correctly registered with NetNanny for monitoring. Hard to believe? You don't have to take our word for it, you can always download NetNanny yourself and take it for a testdrive. We're actually surprised that NetNanny Inc allow that, it must really hurt their sales more than they would like to admit. The 'scanning' of bad words works off a timer, and there's plenty of time typing in a whole range of keywords and sending the request before NetNanny reacts - if it ever do - we could not get it to ban the entryfield of altavista, not under Opera nor under Internet Explorer. It did catch keywords in the addressfield, but that is not where the search string is being entered.

The basic engineering of NetNanny just isn't all that great. Being a 16-bit application under a 32-bit architecture and OS is not very elegant, nor is it very efficient. NetNanny is limited memory wise and must adhere to the horrid 64Kb segment limit of 16-bit applications. That is why the banlists are partitioned as they are, and this is why the individual list cannot grow beyond 64Kb. One must remember that it is possible - even in a 16-bit application - to hide those limits to the user. NetNanny Inc took the easy way out, and the customer is paying the price in terms of elegancy and flexibility. As far as we have been able to determine, the application is limited to a maximum of 640Kb of URL data (64Kb in 10 lists). As you can probably deduce, those 4+ million porn related URLs can never fit, not in plain text anyway. Actually this is bound to be a problem for censorware products in the future, if not now. The sheer amount of data is staggering; to ban one million links you would need approximately 25.3Mb (calculated from the average length of URLs included in the standard NetNanny banlist; 25.3 characters). Not an astronimical number in any way, but pretty unwieldy.

Since this list is seldom searched (and extremely rarely updated) the searching of this volume itself is no problem, advanced data structures and algorithms such as trees and binary searching solved that problem a long time ago. Also, URLs are quite redundant and thus compress well. You could even go so far as to not store the URLs themselves but their hashes. This would reduce the size of the database, but the cost is the introduction of possible false collisions, and it would of course make it impossible to list and edit the list, which is the main thing of importance to us. In any event, the database would need to be stored on disk, and that - under a operating system with no inherent security - makes it an easy target for all kinds of attacks.

Actually, that argument is valid for everything run or stored under Windows'9x (and to some extent NT too), so when talking about the security of the database one must ask 'against which enemy?'. Well, on one hand we have the companies desire to not have their banlists made available to their competitors - this is less of an issue for NetNanny Inc as we can see - but more of an issue for companies such as Solid Oak with their CyberSitter. They regard these URLs as their proprietary data, in other words - anyone of their competitors are their enemy, as are we and anyone else who can reverse their way into extracting those banlists. Another enemy, the one NetNanny seems to be trying to divert, is the user - or victim as we see it. These two enemies are distinctly different, and two different systems are employed to hinder them.

The reverse-engineer face the encryption obstacle. To be able to extract the banlists he must get in under the encryption.

The user face the authorization obstacle. To configure and/or control the censorware the user must be authorized to do so, and such users are recognized through their use of the proper passwords.

There is not much to do against the first enemy. It would be mighty arrogant to believe that this enemy can be hindered. You cannot hide anything in a scenario where the enemy have complete control of the environment, code and data. You can slow an attack down though, and that seem to be what the censorware producers are trying to do with their pet ciphers and security through obscurity tactics. But if they must do it they could at least be intelligent about it. Here's a subtle hint for you: If you must have a backdoor, do not store the plaintext password anywhere.

The second enemy is the little kid trying to do something his parents doesn't want him to, or the student doing research in a library, being hindered by a malfunctioning or over zealous censorware. And while this enemy are a little easier to control - a password will stop most of them - they can also adopt the knowledge of the first enemy. It is therefore logical to assume that  the censorware producers would want to limit the amount of useful information that can be communicated between the first and second enemy, thus it is not a good idea to leave a backdoor or plaintext passwords hanging around. Such information is easily communicated - we have just told you and potentially everyone on the internet about the backdoor in NetNanny, now anyone anywhere can bypass NetNanny v3.10 - a direct result of their faithful adoption of the Security Through Obscurity philosophy.

So they remove the backdoor, or use a secure hash for it which would mean that we would - most probably - not be able to tell you a password, only that there exist a backdoor. That would not help you much. The next problem is the existance of administrator passwords in the plain, same problem basically - while we cannot communicate the contents directly, we can communicate the knowledge and tools to reveal said passwords, as we have done here in this essay. The solution to this problem is the same as before, hashing.

Of course, this only moves the battle to the next level, while we wouldn't be able to communicate a password directly, we might be able to write hash-cracking tools. Both the implementation and the hash algorithm itself would have to be sound if this approach were to increase security of the censorware in any meaningful way. This mean that the programmers would have to abandon security through obscurity and instead embrace some real security. If this will be done remains to be seen.

There is also the problem of securing the in-memory running program. As reverse-engineers we can unload the program "against it's will" so to speak, or even patch the program in memory as to disable it. Under Windows'9x the hacker is god, and the operating system goes his bidding. Just as it should be. :-)

There is a third enemy here - one we feel we must mention - and that is the customer. Our previous attack on CyberSitter (and by good fortune, ScienoSitter) was primarily about him/her. Some of these censorware producers are religiously and/or politically motivated, and sometimes they do not want that to surface. Our research into NetNanny suggests this is not the case here. There does not seem to be anything hidden deep down, no tripple-encrypted URLs. That is good, and while we wouldn't want to thank NetNanny Inc for 'not being crooked', we feel that we should at least make note of it.

In the end the censorware producers will never be able to keep something like that secret for a prolonged period of time, and we encourage them not to even try. If you cannot be honest about what your program do and how it functions, then question must be brought as to your motives and overall judgement.

In light of the encryption used in NetNanny, you might be amused to learn that the same company also peddle a product called PCNanny, which - and we quote a blurb off their site - "gives you, the administrator, the ability to prevent unwanted changes to your system setup. This may be as simple as protecting your desktop settings or as sophisticated as file password protection. Using PC Nanny ensures that only you have access to crucial parts of your system or files, sensitive material and personal correspondence.". We have not examined this product - that's a whole other essay - so we will not comment on it further, except for recommending [MC98].

The efficiency of NetNanny in blocking sites are about the same level as the efficiency of the program's own protection, which is to say close to zero. With NetNanny in control, you can rest easy only if you consider the blocking of 12000 sites - out of more than 4 million (with new ones added every day) - adequate.

This we leave for the customer to decide.

 

A word to potential buyers

If your child is seeking out pornography on the net, why don't you talk to him or her about it? What do you fear?

  "Very few beings really seek knowledge in this world. Mortal or immortal,
   few really ask. On the contrary, they try to wring from the unknown the
   answers they have already shaped in their own minds -- justifications,
   confirmations, forms of consolation without which they can't go on.
   To really ask is to open the door to the whirlwind. The answer
   may annihilate the question and the questioner." -- Anne Rice

Most memes are your friends, only through information can you hope to break free and grow up to be a human being to the fullest; free thinking, upright and informed about how the world works. It is our belief that hindering the natural curiosity and inquisitiveness of a child is doing him or her a disservice. Hiding something from the view of someone does not make that something disappear, and it certainly does nothing to lessen the curiosity of the someone. Instead explain things. Let the kid be a kid, there can be nothing gained by suppressing facts of life.

 

 

Section 8 - Closing words

This concludes the essay _The Reversal of NetNanny_. We want to thank you for your time, hoping that you've learnt something here, we know we did.

So why did we do this? Well, we are information junkies, we feed on knowledge. That is the essence of why this was written. Of course, there's also the intellectual stimulation, excitement and out right recognition one may gain in the process, but mostly we feel that Censorwares are inherently evil, since they strive to shield people from knowledge. As long as they continue to hide vital information we will be around, bringing that information out into the light, proving again and again that there ain't no such thing as security through obscurity.

In this essay we have disclosed many ways of bypassing NetNanny, from entering it via the administrator backdoor password to running our custom software to display the available user accounts. Armed with the knowledge here you can easily think out five more ways of bypassing it, everything from simply removing the low-lever drivers, to changing the encrypted passwords by hand. Another way to bypass NetNanny is to rename the executables of the programs that are being monitored. These general approaches may also be applicable to other products of this type, remember that.

We believe reverse-engineering to be an important tool in bringing out the truth about consumer software. With software there is no table of contents on the back of the package reading "two 16-bit modules, three 32-bit modules and one backdoor.". On the contrary, consumer software are often black-boxes with no source and no technical documentation. As a consumer you have the right to know what it is you are installing. Since the producers doesn't seem to understand this, the spreading of reverse-engineering techniques to the "masses" is the Right Thing to do.

"When ignorance reigns, life is lost."[RATM92].

 

Further reading

If you found this interesting you might enjoy our older essay The Penetration of CyberSitter'97. You'll find a copy here.

There are a couple of good places to read up on censorwares, one is www.censorware.org and another is www.peacefire.org, a site which - incidentally - is banned by NetNanny.

 

Credits

This essay was written and researched by Saruman of DFR Research & Engineering, and first published on the web in 1999-08-24. Credits goes out to Datarescue for "IDA - The Interactive Disassembler" and also NuMega for SoftIce the systems debugger. We must also - despite the recent conversion into shareware - recommend the best hex-editor available: Hiew, created by Eugene "SEN" Suslikov.

 

Greetings also to all DFR members, supporters and friends around the world. You know who you are.

 

Footnotes

1. Possibly a very conservative number. The LA-Times article "Web Travelers Follow Beaten Paths to Similar Sites" mentions "[...] each of the Web's 800 million-plus pages, [...]" in passing, but give no references for this number.

 

References

[NNL98] Net Nanny Ltd., "Net Nanny v3.10", Nov 24, 1998.
[BS96] Bruce Schneier, "Applied Cryptography, 2:nd Edition", 1996.
[RATM92] Rage Against The Machine, Self-titled album, "Township Rebellion".1992. [search]
[DFR98] Saruman and Bobban, "The Penetration of CyberSitter'97", Apr 1998.
[BL96] Brian Long, "Calling 16-Bit Code From 32-Bit In Windows 95", 1996-98.

[KBAB98] Krishna Bharat & Andrei Broder, Update to "Estimating the Relative Size and Overlap of Public Web Search Engines", Apr 1998
[MC98] Matt Curtin, "Snake Oil Warning Signs: Encryption Software to Avoid", 1996-98