Inside NDS
The following paper -- Inside NDS -- explains the Pandora research project
and what has been discovered so far. It was originally released on June 27,
1997 and was revised on March 10, 1998. In May of 1999 it was revised with
information related to Netware 5. But Pandora really started in early 1997.
At that time L0pht Heavy Industries [www.l0pht.com] released the first
version of L0phtCrack, a program designed to take advantage of Windows NT's
dependency on legacy systems and crack passwords. After Novell tried to rake
Microsoft over the coals by stating that not even administrators could access
password hashes, let alone recover the actual passwords from Netware, Pandora
was born.
Abstract
This document will present a technical view of the layout of Novell's Netware
Directory Services (NDS). The emphasis here is mainly from a security
perspective, and tries to point out several areas of weakness that need to be
reinforced. Novell had originally touted the advanced security features built
into NDS as being superior to other network operating systems, including
statements that passwords cannot be recovered, even by administrators.
Obviously since the release of Pandora Novell has had to rethink this. In
this document, our third revisit to NDS, we will examine how to recover the
passwords, and give a complete layout of NDS, including the new Netware 5
released in September 1998.
Audience
While I am probably more well known for the Netware Hack FAQ and presentation
of security issues related to Netware from an intruder perspective, this
document is geared more toward those simply interested in how things work.
But due to the fact that I am covering security issues, the inference is
still there.
I do assume some basic Netware knowledge, and I make use of some examples of
C code to explain some concepts, therefore knowledge of C coding might be of
some help. I have tried to make this as non-technical as possible, but due to
the material being covered, unless you are a bit head this is probably going
to be a very dry read.
Credits
If you read nothing, or just a paragraph or two, please at least read these
credits. I did have some support during this project, and I want to
acknowledge a few people. For some background help and some code snippets,
itsme [itsme@xs4all.nl] provided a lot. On version 1.0 I bounced ideas off of
Greg Miller [greg.miller@usa.net], Al Grant [ag129@cam.ac.uk], and Rx2
[rx2@usa.net]. Thanks a lot. And with version 2.0 I received a number of
comments regarding the code, including example source code. While I
incorporated a number of features suggested by Thomas Lackner
[alackn01@fiu.edu], it was the changes submitted by Jitsu-Disk
[jitsu@nmrc.org] that drastically changed things. The speed increase is
absolutely astounding. On a Pentium 100 Mhz the speed increase was 76 times
faster. That's not a typo, 76 times faster, not 76% faster. His paper
explaining how he rewrote the itsme-supplied proof-of-concept code has been
included in Pandora since version 2.0.
Lab assistance was provided by Mr. Wizard [otter@fastlane.net] and Fourth
Stooge [stooge@onramp.net]. Mr. Wizard provided several examples of BACKUP.DS
so I had multiple copies to play with, and Fourth Stooge provided hardware in
the form of hard drives for the lab. Without the drive space I would have
been very limited, and without the extra BACKUP.DS files I would not have
been able to accurately cross check the different 4.x Netware versions for
consistancy.
Finally I would like to thank two more people, my wife [grace@nmrc.org] for
naming the project Pandora after hearing my explanation of what the hell I
was doing in the lab for hours at a time, and Marcus Williamson
[71333.1665@compuserve.com] for trying to keep me honest through a series of
email exchanges where he constantly tried to get me to admit that this was
NOT a security breach (that is left for you, the humble reader, to decide).
Tools
There were a number of tools used in the preparation of this document. The
two main tools were a hex dump utility and a hex calculator. By examining the
NDS files in the hex dump utility and playing with some of the values, a
picture of how the files were tied together emerged. I also used a C compiler
and wrote several utilities to extract and examine the data. Most of the
utilities were short programs of little use to the general public, but as I
continued to explore I wrote more useful utilities for NDS extraction. These
utilities, along with a copy of this document were released as a set called
Pandora. Pandora is available from http://www.nmrc.org/files/netware/, or
from a link on the Pandora Home Page at http://www.nmrc.org/pandora.
Background
NDS is a distributed database for Netware 4.x and 5.x that provides access to
all network resources. It allows a user to use a single login to a Netware
environment and approach a group of Netware servers as a single entity. GUI
interfaces provide easy management for administrators.
NDS itself consists of 4 core files. On Netware 4.x, these files include
PARTITIO.NDS, ENTRY.NDS, VALUE.NDS, and BLOCK.NDS. On Netware 5.x, the files
are named slightly differently. The file names have an extension of DSD
instead of NDS, and a new file with an extension of DSB is present. Here is a
chart to explain the difference between the files.
Netware 4.x File Netware 5.x Equivalent File
---------------- ---------------------------
ENTRY.NDS 0.DSD
VALUE.NDS 1.DSD
BLOCK.NDS 2.DSD
PARTITIO.NDS 3.DSD
We will cover the DSB file, usually named 0.DSB, a little later. But I will
state that the 0.DSB file is to keep track of which DSD file serves what
function. However, in this paper when I refer to ENTRY.NDS I am also
referring to 0.DSD unless I specify otherwise. So you may want to keeping
referring to the chart above.
The files are stored on the SYS: volume in a hidden directory called
_NETWARE. This directory cannot be directly accessed from a user login
session, including an administrator.
All objects addressed by the server are located within the ENTRY.NDS file.
All named attributes have a record, and all administrator-created items have
a record. For example, there is a record called USER which contains
information about the USER property itself, and a record for a user called
Admin which contains information about that particular USER object.
Values associated with ENTRY records are stored in one and sometimes two
files. VALUE.NDS will contain up to 16 bytes of data about an ENTRY record.
Why so little? Well, 16 bytes is EXACTLY what is needed for one ACL entry.
ACL entries are the most common VALUE records. If more than 16 bytes of
information is needed, the VALUE record has a pointer to BLOCK.NDS. This
file's records can contain up to 108 bytes of data. If still more room is
needed, extra BLOCK records can be linked together via pointers.
The partition information is contained within PARTITIO.NDS, which is
basically used to keep track of a minimal amount of information that helps
NDS replicate and sync up the data between servers.
Accessing NDS
First off, to explore NDS one must retrieve a copy from a server. This is
actually easier than it seems. The two main ways to get copies of NDS involve
console access, and could interrupt server access during retrieval. If you
are pulling a copy of NDS off of a server, make sure you wait until a time
when user activity is at a minimal. The interruption will mean that access to
network resources that have not been authenticated will not work.
If users are already logged in, they should not notice any interruptions.
This is similiar to the impact encountered during a DSREPAIR.
The first method involves using either RCONSOLE or direct console access and
loading an NLM that allows access to SYS:_NETWARE to retrieve the copies. Two
such NLMs are JCMD.NLM and NETBASIC.NLM. JCMD.NLM is freeware available on
the Internet, but NETBASIC.NLM is the prefered method, as this NLM is bundled
with Netware 4.11. Loading NETBASIC and typing "shell" drops you to a pseudo
DOS-like environment. From here you can simply cd into SYS:_NETWARE, and copy
the *.NDS files to another location on the server (for example SYS:LOGIN).
Remember, DS.NLM must be unloaded to do this on larger systems. I have
received reports of small LANs allowing copies to be made while DS.NLM was
loaded. YMMV.
For the faint of heart, there is another method using DSMAINT.NLM or
DSREPAIR.NLM. If you don't have these NLMs, they are available from Novell's
web site. The by-product of running these NLMs is a backup file containing
the local copy of NDS. If you use DSMAINT, a file called BACKUP.DS is
created. If you use DSREPAIR, a file called DSREPAIR.DIB is created.
It should be obvious, but if your server console is compromised, an intruder
could do this as well. And if the file system is compromised, BACKUP.DS and
DSREPAIR.DIB will be tempting targets. Only keep these files around until you
are sure they are no longer needed, and then delete and purge them.
NDS File Structure
The 4 files -- ENTRY.NDS, VALUE.NDS, BLOCK.NDS, and PARTITIO.NDS -- as well
as their 5.x equivalents -- 0.DSD, 1.DSD, 2.DSD, and 3.DSD -- are binary
files that consist of individual records. Here is the structure (as well as I
can determine without the source code) for each record. Included are my
comments:
typedef unsigned long uint32;
typedef unsigned int uint16;
typedef unsigned char uint8;
/*
* struct for ENTRY.NDS records
*/
typedef struct entry
{
uint32 selfOffset; /* Offset in ENTRY.NDS. If this is
the first record, it is 0x00000000
followed by 0x0000014e for the
second record, etc. */
uint32 checkSum; /* I assume a checksum */
uint32 val1; /* Unsure, 0xfeffffff. */
uint32 val2; /* Unsure, 0xffffffff. */
uint32 peer; /* Offset to a peer record. */
uint32 firstChild; /* Offset to first child record. If
no kids, 0xffffffff. */
uint32 lastChild; /* Offset to second child record. If
no kids, 0xffffffff. */
uint32 firstValue; /* Offset in VALUE.NDS of first
attribute. They are usually kept
in order in VALUE.NDS, but since
they are crossed referenced in
VALUE.NDS they don't have to be.*/
uint32 id; /* The Object ID of the record. */
uint32 partitionID; /* The partition ID of the record. */
uint32 parentID; /* The parent's Object ID, if no
parent it is 0xffffffff. */
uint32 val3; /* No idea. Usually a small number.*/
uint32 val4; /* No idea. 0x00000000. */
uint32 subordinates; /* Number of subordinates. This can
include other objects besides
children. */
uint32 classID; /* The "type" of Object ID. */
uint32 creatTime1, /* When object was created. */
creatTime2;
uint32 modTime1, /* When object was last modified. */
modTime2;
uint8 name[258]; /* Dreaded unicode describing
the record. If a user object
it will be the common name. */
} ENTRY; /* size=334 */
/*
* struct for VALUE.NDS records
*/
typedef struct value
{
uint32 selfOffset; /* Offset in VALUE.NDS. If this is
the first record, it is 0x00000000
followed by 0x00000040 for the
second record, etc. */
uint32 checkSum; /* I assume a checksum */
uint32 val1; /* Unsure, usually 0xfeffffff. */
uint32 val2; /* Unsure, usually 0xffffffff. */
uint32 nextVal; /* The next Value record's offset. */
uint32 firstBlock; /* Offset in BLOCK.NDS if used. */
uint32 entryID; /* Type of record in ENTRY.NDS. */
uint32 typeID; /* Type of VALUE record. */
uint32 val3; /* No idea. Usually a small number.*/
uint32 creatTime1, /* When object was created, */
creatTime2; /* and modified. */
uint32 length; /* Length of data. */
uint8 data[16]; /* Start of data, unless there is a
small amount of data, then it's
all here. */
} VALUE; /* size=64 */
/*
* struct for BLOCK.NDS records
*/
typedef struct block
{
uint32 selfOffset; /* Offset in BLOCK.NDS. If this is
the first record, it is 0x00000000
followed by 0x00000080 for the
second record, etc. */
uint32 checkSum; /* I assume a checksum */
uint32 val1; /* Unsure. */
uint32 nextBlock; /* Next record if data>120. */
uint32 valueOffset; /* Offset in VALUE.NDS (backlink) */
uint8 data[108];
} BLOCK; /* size=128 */
/*
* struct for PARTITIO.NDS records
*/
typedef struct partition
{
uint32 selfOffset; /* Offset in PARTITIO.NDS. If this is
the first record, it is 0x00000000
followed by 0x00000028 for the
second record, etc. */
uint32 checkSum; /* I assume a checksum */
uint32 val1; /* Unsure. */
uint32 id; /* ID of record. */
uint32 entryID; /* ID in ENTRY.NDS */
uint32 replicaID; /* Replica ID (??) in ENTRY.NDS */
uint32 val2; /* Unsure. */
uint32 val3; /* Unsure. */
uint32 timeStamp1, /* Probably used to keep things in sync */
timeStamp2;
} PARTITIO; /* size=40 */
As you can see I've had to guess at a lot of these, but I think there is
enough there to allow you to see what is in NDS. As far as the DSD files with
Netware 5.x goes, the basic structure appears to be the same. However, in
preparation for porting of NDS to non-Intel systems, Novell has begun to
"universally" read and write data to the drive, eliminating the big
endian/little endian problem.
BACKUP.DS and Structure
If you retrieve BACKUP.DS, you need to reconstruct the NDS files into their
original 4 components. The structure of BACKUP.DS is as follows -
|---------------|
| HEADER |