Antony Petras
Archive

DKOM: Exposing and manipulating opaque data structures

Background

The Windows kernel is home to many undocumented functions and structures, and there are also many "opaque" structures. What an opaque structure allows, is for drivers to make references to objects without exposing to them details of the structure. It is a stability measure more than a security one, as it is a strong suggestion that the objects should not be manipulated by anything but the kernel. The structures that are the focus of this post are the EPROCESS and ETHREAD data types. These are fundamental to the Windows kernel, and obviously hold information critical to the operation of processes and threads. Being able to freely interact with these opens the doors to a whole lot of interesting and uncharted stuff. Obviously we will be doing this from a kernel-mode driver.

When a driver makes a call to PsGetCurrentProcess(), the return value is a pointer to an EPROCESS object in the kernel. However, while EPROCESS has many members, the driver will find itself unable to do anything with this value other than pass it to other kernel routines. By taking a quick dip into WinDbg we can identify the members of the EPROCESS and ETHREAD data types, and in turn manipulate their values. Note that the precise layout of these types varies wildly between Windows versions and even between minor versions. I would recommend that if your goal is to recreate an accurate definition for these types that you dump the structure yourself, rather than relying wholly on online information. Geoff Chappell breaks down the history of these and many other structures in deeper detail and with more insight than I ever could.

Setting up WinDbg

See my short post here on setting up WinDbg with a VirtualBox virtual machine. Or, there are many other articles on the web to guide you. But I have trawled dead forums to find solutions to a couple of obscure problems so you don't have to.

Dumping the data structures

This part is actually extremely easy. You will need to have nt symbols loaded, as described in the other post.
With your debugger connected, break execution with Ctrl+Break, and then enter the following command:

dt nt!_ETHREAD

This will print out the structure to the console like this:

Reconstructing a partial definition

With this information, depending what you want to do with it or how portable you need it to be, you can just pad the struct out to access just the members you are interested in. For example, maybe your reason for doing this was to find the starting address of any given thread. For that, we can find the entry displayed for StartAddress:

So we know StartAddress is located at 0x5F8 and is a Ptr64 Void (PVOID).
We can then look at the end of the list and see how large the _ETHREAD is:

So we can see that the final member is at 0x770 and is a Ptr64/PVOID (which is an 8 byte type), so the actual end of the ETHREAD will be at 0x778. With these little bits of information we can now create a usable typedef for reading start addresses. For the first pad, we have 0x5F8. For the second pad, we will take the total size, and subtract the size that is taken up by the first pad plus the StartAddress member.
0x778 - (0x5F8 + 0x008) = 0x178
Thus our basic, usable struct would look like:

typedef struct _MYETHREAD {
	char pad0[0x5F8];
	PVOID StartAddress;
	char pad1[0x178];
} MYETHREAD, *PMYETHREAD;

And so now we can cast the result of PsGetCurrentThread() to PMYETHREAD:

PMYETHREAD myThread = (PMYETHREAD)PsGetCurrentThread();

And are now able to access the StartAddress member:

printf("Start address: %p\n", myThread->StartAddress);
>> Start address: 0xFFFFF803DFD45990

As we are working with pointers, we don't really need to pad the tail of the struct out, but if you somehow ended up with a list of MYETHREADs rather than pointers, or wanted to malloc space for one, it would fall apart without padding to the true size.

What exactly is there to manipulate?

The vast majority of the members of kernel objects like these are meaningless to those doing even advanced driver development. They may however be of interest to those developing malware or those researching how to best combat it. A quick and already well-published example is the removal of a thread from the linked list that holds it. You can see that after following these steps, a thread will no longer show up in Process Hacker (a 3rd party tool). Rogue threads can potentially still be found in other ways, such as by dissecting the VAD Tree, or walking the process' call stack, but I won't go into those here as I don't know much about them myself.

Removing a thread from the list

An EPROCESS object contains the head of a circular linked list that leads to its ETHREADs.
The LIST_ENTRY type (which is not opaque) simply looks like this:

typedef struct _LIST_ENTRY {
	PLIST_ENTRY Flink; // Pointer to next entry
	PLIST_ENTRY Blink; // Pointer to previous entry
} LIST_ENTRY, *PLIST_ENTRY;

Note that the LIST_ENTRY objects are actually embedded inside the EPROCESS/ETHREAD structures that they represent. So one would traverse these LIST_ENTRYs, and then, knowing the offset of the LIST_ENTRY within that structure, subtract that offset to get a pointer to the desired object. This calculation can also be done with the CONTANING_RECORD macro.
Also note that walking or manipulating these lists can end in disaster if the kernel is modifying them at the same time.

The EPROCESS-es are linked in the same way that the ETHREADs are, but for simplicity I haven't drawn that here. So this means you can find a pointer to an arbitrary EPROCESS, access its members like we did earlier, walk the circular process list to find the one you want, then walk that's ETHREAD list to find the thread you want to manipulate.

So with the goal of unlinking a LIST_ENTRY, we will simply demonstrate this on the current thread which we already have a reference to, rather than walking to another. Once you are satisfied that it works, you can attempt it on a thread different to your own.

I have modified our original MYETHREAD definition to accommodate accessing the ThreadListEntry member:

typedef struct _MYETHREAD {
	char pad0[0x5F8];
	PVOID StartAddress;
	char pad1[0x88];
	LIST_ENTRY ThreadListEntry;
	char pad2[0xE0];
} MYETHREAD, *PMYETHREAD;

From there we need to obtain a reference to the list entries either side of ours, and then redirect the pointers around our list entry. Note that we must also redirect our outbound pointers back to our entry to avoid a blue screen.

LIST_ENTRY threadEntry = myThread->ThreadListEntry;

PLIST_ENTRY nextThread = threadEntry.Flink;
PLIST_ENTRY prevThread = threadEntry.Blink;

prevThread->Flink = nextThread;
nextThread->Blink = prevThread;

threadEntry.Blink = (PLIST_ENTRY)&threadEntry;
threadEntry.Flink = (PLIST_ENTRY)&threadEntry;

We now have a thread object whose LIST_ENTRY is disconnected from the rest of the process's threads, and so it cannot be walked.

Reconstruction of full definitions

I do intend to eventually publish a tool that turns pasted WinDbg output into typedefs.
In the meantime, you can tediously reverse engineer the WinDbg output!


NetSuite: RESTlet Token Access with C#

Updated to use HMAC-SHA256 for 2022.

The first time I needed to authenticate using tokens in order to interact with a RESTlet, I found myself trawling the web for code samples as the official documentation doesn't quite explain all of the details. I pulled together some information from a few places, and now these resulting code samples are my go-to for any C# work.

Gathering Materials

First of all, you will need this OAuth class. [Out of date now that SHA1 is deprecated. See the link at the end for an up-to-date version] In case the link breaks I will leave a download at the end. It is a short class that you could likely replace with your own implementation if you really wanted, and it just provides some basic OAuth functionality.

You will need the following pieces of information:

  • Netsuite Realm - This is your business ID. You can see it in every url, and you can find it more formally under Setup > Integration > Web Services Preferences, where it is labelled Account ID. This will have _SB1 or something similar in the name when you are using a sandbox - do include that part.
  • Consumer ID & Consumer Secret - These are shown to you one time when you create the integration.
  • Token ID & Token Secret - These are shown to you one time when you create the access token. (Setup > Users/Roles > Access Tokens)
  • The URL of your RESTlet, including deploy_id.

Crafting a GET Request

I think the code is plain enough to speak for itself:

using System;
using System.IO;
using System.Net;
using Newtonsoft.Json;

// Namespace / class stuff omitted.

string NS_realm = "1234567" // 1234567_SB1 for sandbox
string url = "https://1234567.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=123&deploy=1";

HttpWebRequest request = null;
HttpWebResponse response = null;

String header = "Authorization: OAuth ";

try
{
	string consumer_id = "YOUR_CONSUMER_ID"; // (Also called Consumer Key)
	string consumer_secret = "YOUR_CONSUMER_SECRET";
	string token_id = "YOUR_TOKEN_ID";
	string token_secret = "YOUR_TOKEN_SECRET";

	Uri uri = new Uri((url + "&YOUR_QUERY_STRING")); // Omit the query string part if you don't need it.

	OAuthBase req = new OAuthBase();

	string normalized_url;
	string normalized_params;

	string nonce = req.GenerateNonce();
	string time = req.GenerateTimeStamp();

	string signature = req.GenerateSignature(uri, consumer_id, consumer_secret, token_id, token_secret, "GET",
						 time, nonce, out normalized_url, out normalized_params);

	// URL encode any + characters generated in the signature
	if (signature.Contains("+"))
	{
		signature = signature.Replace("+", "%2B");
	}
	
	// Construct the OAuth header		
	header += "oauth_signature=\"" + signature + "\",";
	header += "oauth_version=\"1.0\",";
	header += "oauth_nonce=\"" + nonce + "\",";
	header += "oauth_signature_method=\"HMAC-SHA256\",";
	header += "oauth_consumer_key=\"" + consumer_id + "\",";
	header += "oauth_token=\"" + token_id + "\",";
	header += "oauth_timestamp=\"" + time + "\",";
	header += "realm=\"" + NS_realm + "\"";

}
catch (Exception q)
{
	Console.WriteLine("Configuration error. Check tokens and NBN.");
	return null;
}

try
{
	request = (HttpWebRequest)WebRequest.Create((url + "&YOUR_QUERY_STRING"));
	request.ContentType = "application/json";
	request.Method = "GET";
	request.Headers.Add(header);
}
catch (Exception e)
{
	Console.WriteLine("Couldn't generate request. Check RESTlet URL and NBN.");
	return null;
}

try
{
	WebResponse response = request.GetResponse();
	httpResponse = (HttpWebResponse)response;

	Stream resStream = httpResponse.GetResponseStream();
	StreamReader sr = new StreamReader(resStream);
	var result = sr.ReadToEnd();

	MyObject ResultObject = Newtonsoft.Json.JsonConvert.DeserializeObject<MyObject>((string)result);
	return ResultObject;
}
catch (UriFormatException)
{
	Console.WriteLine("Error forming request. Check tokens, URL, and NBN details.");
	return null;
}
catch (Exception e)
{
	Console.WriteLine("Server or connection error:\n" + e.ToString());
	return null;
}

As you can see, once you get the OAuth format specifics just right, it isn't actually overly complex.

Note that the signature is valid only for a single request, and so the nonce, time, and signature must be regenerated each time.

Crafting a POST Request

The code is very similar, but note the changing of two occurrences of "GET" to "POST", the removal of query strings, and the additional need to write a body.

using System;
using System.IO;
using System.Net;
using Newtonsoft.Json;

// Namespace / class stuff omitted.

string NS_realm = "1234567" // 1234567_SB1 for sandbox
string url = "https://1234567.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=123&deploy=1";

HttpWebRequest request = null;
HttpWebResponse response = null;

String header = "Authorization: OAuth ";

try
{
	string consumer_id = "YOUR_CONSUMER_ID"; // (Also called Consumer Key)
	string consumer_secret = "YOUR_CONSUMER_SECRET";
	string token_id = "YOUR_TOKEN_ID";
	string token_secret = "YOUR_TOKEN_SECRET";

	Uri uri = new Uri(url);

	OAuthBase req = new OAuthBase();

	string normalized_url;
	string normalized_params;

	string nonce = req.GenerateNonce();
	string time = req.GenerateTimeStamp();

	string signature = req.GenerateSignature(uri, consumer_id, consumer_secret, token_id, token_secret, "POST",
						 time, nonce, out normalized_url, out normalized_params);

	// URL encode any + characters generated in the signature
	if (signature.Contains("+"))
	{
		signature = signature.Replace("+", "%2B");
	}
	
	// Construct the OAuth header		
	header += "oauth_signature=\"" + signature + "\",";
	header += "oauth_version=\"1.0\",";
	header += "oauth_nonce=\"" + nonce + "\",";
	header += "oauth_signature_method=\"HMAC-SHA256\",";
	header += "oauth_consumer_key=\"" + consumer_id + "\",";
	header += "oauth_token=\"" + token_id + "\",";
	header += "oauth_timestamp=\"" + time + "\",";
	header += "realm=\"" + NS_realm + "\"";

}
catch (Exception q)
{
	Console.WriteLine("Configuration error. Check tokens and NBN.");
	return null;
}

try
{
	request = (HttpWebRequest)WebRequest.Create(url);
	request.ContentType = "application/json";
	request.Method = "POST";
	request.Headers.Add(header);

	int cust_id = 45;
	int order_no = 234354;
	int quantity = 5;

	using (var streamWriter = new StreamWriter(request.GetRequestStream()))
	{
		// A sample JSON POST body.
		string json = "{\"customer_id\":\"" + cust_id + "\"," +
			      "\"order_number\":\"" + order_no + "\"," +
			      "\"quantity\":\"" + quantity + "\"," +
			      "\"}";
		
		streamWriter.Write(json);
	}
}
catch (Exception e)
{
	Console.WriteLine("Couldn't generate request. Check RESTlet URL and NBN.");
	return null;
}

try
{
	WebResponse response = request.GetResponse();
	httpResponse = (HttpWebResponse)response;

	Stream resStream = httpResponse.GetResponseStream();
	StreamReader sr = new StreamReader(resStream);
	var result = sr.ReadToEnd();

	MyObject ResultObject = Newtonsoft.Json.JsonConvert.DeserializeObject<MyObject>((string)result);
	return ResultObject;
}
catch (UriFormatException)
{
	Console.WriteLine("Error forming request. Check tokens, URL, and NBN details.");
	return null;
}
catch (Exception e)
{
	Console.WriteLine("Server or connection error:\n" + e.ToString());
	return null;
}

Thanks for reading! I hope this provided you a nice easy starting point.

Download links:
OAuthBase.cs
Netsuite.cs

View More Posts