Monday, January 31, 2011

Windows Registry with Mono, pt2 - Node Keys

I have had a bit more time on my hands to work on being able to read the registry without using advapi32.dll. Today I was able to hack up a small (incomplete) class for node keys that builds the framework for breaking apart and manipulating the data.

To start off, from this file, we can get the offsets we need to read to get the right data.


the nk-Record
=============

Offset Size Contents
0x0000 Word ID: ASCII-"nk" = 0x6B6E
0x0002 Word for the root-key: 0x2C, otherwise 0x20
0x0004 Q-Word write-date/time in windows nt notation
0x0010 D-Word Offset of Owner/Parent key
0x0014 D-Word number of sub-Keys
0x001C D-Word Offset of the sub-key lf-Records
0x0024 D-Word number of values
0x0028 D-Word Offset of the Value-List
0x002C D-Word Offset of the sk-Record
0x0030 D-Word Offset of the Class-Name
0x0044 D-Word Unused (data-trash)
0x0048 Word name-length
0x004A Word class-name length
0x004C ???? key-name


It's pretty straight forward. In every fragment, we can go to specific offsets and get the data we want. This ends up looking like this:


public NodeKey (string data)
{
ASCIIEncoding enc = new ASCIIEncoding();

byte[] bs = enc.GetBytes(data);

//the lengths we will be working with.
int word = 2;
int dword = word+word; //double word
int qword = dword+dword; //quad word

for (int i = 0; i < bs.Length;)
{
//making sure it is nk
if (i == (int)0x0000) //header
{
if ((int)bs[0] == 110)
{
if ((int)bs[1] == 107)
{
i += word;
continue;
}
else
{
throw new Exception("This may be a damaged nk block. If so, fix the header and try again.");
}
}
else
{

throw new Exception("Not a nk");
}
}

else if (i == (int)0x0002) //is it a root key?
{
if (bs[i] == (byte)0x2C)
{
//It's a root key!
Console.WriteLine("It's a root key!");

}
else
{
//it's not a root key!
Console.WriteLine("It's not a root key!");
}

i += word; //move up 2 elements
continue;
}

else if (i == (int)0x0004) //timestamp in long smb form blegh
{
byte[] blah = new byte[qword];
for (int k = 0;k<qword;k++)
{
blah[k] = bs[i+k];
}

i+= qword;
}
else if (i == (int)0x0010) //offset to parent
{
i += dword;
}
else if (i == (int)0x0014) //number of subkeys
{
i += dword;
}
else if (i == (int)0x001C) //offset to subkey lf blocks
{
i += dword;
}
else if (i == (int)0x0024) //number of values
{
i += dword;
}
else if (i == (int)0x0028) //offset of value list
{
i += dword;
}
else if (i == (int)0x002C) //offset to the sk block
{
i += dword;
}
else if (i == (int)0x0030) //offset to classname
{
i += dword;
}
else if (i == (int)0x0044) //this is trash supposedly
{
i += dword;
}
else if (i == (int)0x0048) //name length
{
i += word;
}
else if (i == (int)0x004A) //class name length
{
i += word;
}
else if (i == (int)0x004C) //key name
{
int length = bs.Length - i;

char[] blah = new char[length];

for (int k = 0; k < length;k++)
{
blah[k] = (char)bs[i+k];
}

Console.WriteLine(blah);

i += length; //we are done.
}
else i+= word; //debugging purposes
}
}



If you notice, however, my code is not complete. I am starting with the most useful stuff first and moving on that way. A more complete class will keep the key name length in a local variable and use that instead of bs.Length when reading the key name later. With the current implementation, I read in too many bytes and grab some extra key headers :-/. You could create properties that are privately set and publicly get'able and set the properties to their respective values, to make it truly object oriented.

Another thing to point out is i is being incremented by the length read each time. It isn't arbitrary. This way next go around we are at the offset we need to be at.

One thing I look forward to implementing is lazy loading of parents and children. If you would like to test this, class, you can see my previous post on initially reading and deciphering the windows registry in C#. Just use this in your for loop instead:


foreach (Match mx in nk.Matches (d)) {

all++;
NodeKey key = new NodeKey(mx.Value);
}

Wednesday, January 5, 2011

Analyzing the Windows NT registry without advapi32.dll using Mono (PoC)

I have been doing some challenges for a contest and one requires analyzing a set of Windows NT registry hives. Regedit really sucks (though it does run in wine). I decided it would be more fun to write a small library that can read the registry hives without relying on p/invoke and advapi32.dll on Windows. I have some small code that carves out the data I need, though I am running into a problem on the software hive supplied. Maybe someone can point me in the right direction.

A lot of my information came from this text file which I found, and have updated some with information that I found missing.

As far as I can tell, there are 6 data types to be carved out of the hives. regf file headers, hbin blocks, node keys, value keys, and lf/h (lh on XP) blocks. There are also security keys (with a sk header) within node keys. The following regex's should carve out the data from the registry files so you may parse out the information you need.


Regex regf = new Regex (@"^regf.{508}");
Regex nk = new Regex (@"nk[\x2c|\x20]\x00.{7}\x01.{64}");
Regex vk = new Regex (@"vk.{3}\x00\x00[\x00|\x80].{64}");
Regex hbin = new Regex (@"hbin.{4}\x00\x10\x00\x00.{8}");
Regex lf = new Regex (@".{4}l[f|h][0-65535].{8}"); //lf or lh on winxp


But in order to search the hive, we need to read it in. This isn't very efficient, and I am aware of this. It works.


using (FileStream fs = File.OpenRead (path)) {
var data = new byte[checked((int)fs.Length)];
int i = 0;
int read;

using (var ms = new MemoryStream (checked((int)fs.Length))) {

while ((read = fs.Read (data, 0, data.Length)) > 0) {
ms.Write (data, 0, read);
i += read;
}

byte[] hive = ms.ToArray ();
char[] cList = new char[fs.Length];

i = 0;
foreach (byte b in hive)
cList[i++] = (char)b;

string d = new string (cList);


int all = 0;

foreach (Match mx in lf.Matches (d)) { //you can change out the regex you want here.
byte[] bb = new byte[mx.Value.Length];
char[] cb = new char[mx.Value.Length];

for (int k = 0; k < mx.Value.Length; k++) {
bb[k] = (byte)mx.Value[k];
cb[k] = (char)bb[k];

}

all++;

//Console.WriteLine (new string (cb));
}

Console.WriteLine (all.ToString ());
all = 0;
}
}


Basically, we read in the hive into a MemoryStream, convert the stream into a byte array, move that into a char array from which we create a string to search for the regexs in. Yes, we store 4 copies of the registry in memory. I am sure there are better ways to do this.

Then we loop through each match and count them. Of course we are working with binary streams, so if you choose to write the data carved out to the console, it will look like random data (to the untrained eye at least).

Running through all the hives supplied, I get this output:



/home/bperry/SAM
nk[\x2c|\x20]\x00.{7}\x01.{64}
47
.{4}l[f|h][0-65535].{8}
0
vk.{3}\x00\x00[\x00|\x80].{64}
36
hbin.{4}\x00\x10\x00\x00.{8}
6
^regf.{508}
1

/home/bperry/software
nk[\x2c|\x20]\x00.{7}\x01.{64}
43147
.{4}l[f|h][0-65535].{8}
6
vk.{3}\x00\x00[\x00|\x80].{64}
54708
hbin.{4}\x00\x10\x00\x00.{8}
2917
^regf.{508}
0

/home/bperry/system
nk[\x2c|\x20]\x00.{7}\x01.{64}
11189
.{4}l[f|h][0-65535].{8}
4
vk.{3}\x00\x00[\x00|\x80].{64}
21926
hbin.{4}\x00\x10\x00\x00.{8}
1121
^regf.{508}
1

/home/bperry/default
nk[\x2c|\x20]\x00.{7}\x01.{64}
554
.{4}l[f|h][0-65535].{8}
0
vk.{3}\x00\x00[\x00|\x80].{64}
1014
hbin.{4}\x00\x10\x00\x00.{8}
58
^regf.{508}
1

/home/bperry/SECURITY
nk[\x2c|\x20]\x00.{7}\x01.{64}
220
.{4}l[f|h][0-65535].{8}
0
vk.{3}\x00\x00[\x00|\x80].{64}
147
hbin.{4}\x00\x10\x00\x00.{8}
10
^regf.{508}
1



The number printed after the regex is the number of matches found. The data is fully carved out, so the only thing left is to break it apart to get the relevant data. If you will notice however, software reports 0 regf file headers, and I cannot figure out why. Any thoughts?