A C Lib Wrappers Access Example

There are many times when you'll need to write a wrapper DLL to gain access to a Win32 API call. We've discussed many of the scenarios for using this technique earlier in the chapter, so let's look at the example.

You'll run into more than a few situations when you must gain access to one or more types of security information that the .NET Framework doesn't provide. For example, you might need to know the security information for the local user. Unfortunately, the functions required to access those security features reside in one or more C libraries such as ADVAPI32.LIB. This file is only accessible from within a C application.

The example application shows how to get around this problem. You need to build a separate managed Visual C++ DLL that handles access to the library in question, then access the DLL function from within your application. The first step is to create the required projects. Make sure you add a reference to the Visual C++ DLL in your C# or Visual Basic project's References folder. You'll also need to add a using statement for the Visual C++ DLL at the beginning of your C# or Visual Basic application. The example found in the \Chapter 03\ C#\AccessToken and \Chapter 03\VB\AccessToken folders of the source code CD will provide you with the details of this setup.

Note The examples in this section assume a familiarity with underlying security concepts such as the use of the Security Access Control List (SACL) and Discretionary Access Control List (DACL). We'll discuss issues regarding the Access Control Entries (ACEs) and you'll learn how to manage access tokens. If you aren't familiar with these topics, make sure you read the security theory sections of the help files starting with "Windows NT Security in Theory and Practice"

(ms-help://MS.VSCC/MS.MSDNVS/dnwbgen/html/msdn_seccpp.htm). The help file has a four-part theory section that will tell you everything you need to understand the examples.

There are a number of ways to create a connection between a C library and your C# application. In some cases, you can create a one-for-one set of function calls. For example, this works well when you want to call the console library routines because they don't exchange pointers—just data. However, the security API calls are a little more complicated, so you'll find that you need to perform a little more work to create the interface. Listing 3.2 shows the Visual C++ DLL code. Remember, this is a managed DLL, so you have access to both managed and unmanaged functionality—a real plus in this situation. You'll find the source code for the wrapper DLL in the \Chapter 03\C#\AccessToken\SecurityAPI of the CD.

Listing 3.2: The Visual C++ DLL Code for User Security Access

// Obtain the size of the data structure for a particular // token information class.

int GetTokenSize(TOKEN_INFORMATION_CLASS TIC, IntPtr *ReturnLength)

HANDLE TokenHandle = NULL; // Handle to the process token.

HRESULT hr = 0; // Operation Result Value.

// Obtain a handle for the current process token, hr = OpenProcessToken(GetCurrentProcess(),

TOKEN_QUERY, &TokenHandle);

// Obtain the size of the token for the desired // token information class, hr = GetTokenInformation(TokenHandle,

// Return the size of the token information.

*ReturnLength = IntPtr((int)RL);

// Free the token handle. CloseHandle(TokenHandle);

return hr;

// Obtain the date for a particular token information // class. The calling application must provide a properly // sized buffer.

int GetTokenData(TOKEN_INFORMATION_CLASS TIC, IntPtr *TokenData, IntPtr TokenDataLength, IntPtr *ReturnLength)

HANDLE TokenHandle = NULL; // Handle to the process token.

HRESULT hr = 0; // Operation Result Value.

VOID* lpTokenData; // Token Data Holder.

// Obtain a handle for the current process token, hr = OpenProcessToken(GetCurrentProcess(),

TOKEN_QUERY, &TokenHandle);

// Allocate memory for the return data. lpTokenData = malloc(TokenDataLength.ToInt32());

// Obtain the size of the token for the desired // token information class, hr = GetTokenInformation(TokenHandle,

TIC, lpTokenData,

(DWORD)TokenDataLength.ToInt32(), &RL);

// Return the size of the token information. *ReturnLength = IntPtr((int)RL);

// Return the token data. *TokenData = IntPtr(lpTokenData);

// Free the data holder. //free(lpTokenData);

// Free the token handle. CloseHandle(TokenHandle);

return hr;

// Convert the TOKEN_USER structure to a SID string, int ConvertTokenUserToSidString(IntPtr TokenData,

String **SIDString)

HRESULT hr = 0; // Operation Result Value.

TOKEN_USER *TU; // Token user data structure.

LPTSTR SIDValue; // The string version of the SID.

VOID *Temp; // A temporary pointer.

// Convert the IntPtr to a TOKEN_USER structure. Temp = TokenData.ToPointer(); TU = (TOKEN_USER*)Temp;

// Convert the SID to a string.

hr = ConvertSidToStringSid(TU->User.Sid, &SIDValue);

// Return the string value of the SID. *SIDString = new String(SIDValue);

// Free the memory used by SIDValue. LocalFree(SIDValue);

return hr;

// Convert a TOKEN_USER structure to user account information, int ConvertTokenUserToUserData(IntPtr TokenData,

String **UserName,

String **Domain)

HRESULT

TOKEN_USER

VOID

LPTSTR

LPTSTR

SID NAME USE

*TU; // Token user data structure.

*Temp; // A temporary pointer.

lpUserName; // The user name value.

lpDomain; // The user's domain.

// Length of the data return values.

DWORD UserNameLength = 40;

DWORD DomainLength = 40;

// Convert the IntPtr to a TOKEN_USER structure.

Temp = TokenData.ToPointer();

// Allocate memory for the return values.

lpUserName = (LPTSTR)malloc(40);

lpDomain = (LPTSTR)malloc(40);

// Find the user account information.

hr = LookupAccountSid(NULL,

TU->User.Sid, lpUserName,

&UserNameLength, lpDomain,

&DomainLength,

// Return the user account information.

*UserName = new String(lpUserName);

*Domain = new String(lpDomain);

// Free the local variables.

free(lpUserName);

free(lpDomain);

return hr;

// Free unmanaged memory used by the application.

void FreePointer(IntPtr Pointer) {

free(Pointer.ToPointer());

One of the features of this example is that it uses as many generic function calls as possible to reduce the amount of Visual C++ code required to handle any given task. The GetTokenSize() and GetTokenData() both fall into this category. You can use them to obtain any of a number of token types. The example concentrates on the user token—the one that contains security information for the current user, but you can use these two functions to gain access to any other supported token as well.

The GetTokenSize() function begins by using the OpenProcessToken() function to retrieve the token for the current process. Every process the user opens contains a copy of the user's token. However, the system and other external processes can also open processes, so the only certain way to retrieve a copy of the user's token is to look at the current process. Notice that we've opened the token for query purposes only and that we obtain a handle to the current process using the GetCurrentProcess() function.

Once the code obtains a token handle, it can retrieve information about the token. The purpose of the GetTokenSize() function is to tell the caller how much memory to allocate for the token information, not to actually retrieve the information. The caller must provide one of several TOKEN_INFORMATION_CLASS enumeration values as input to the GetTokenSize() function. We'll visit these values later. For now, the enumeration is used as input to the GetTokenInformation() function, which also requires the token handle and a variable to return the length. If this were an information retrieval call, the code would also need to supply a pointer to a buffer to receive the information and the length of that buffer.

Warning Always close all handles and free all allocated memory when working with unmanaged code. Every call you make to the Win32 API, including the security API, is a call to unmanaged code. Notice the call to CloseHandle() in the example code. This call frees the token handle before the GetTokenSize() function returns.

The GetTokenData() function works much like the GetTokenSize(). In this case, the caller must provide a pointer to a buffer used to store the data. However, you need to consider how the GetTokenInformation() function works before you proceed. The GetTokenInformation() is general purpose—it returns more than one type of data depending on the kind of token you request. As a result, it returns a VOID* that the application must typecast to another kind of information. We'll see how this works later. The point, for now, is that GetTokenData() must allocate the memory for the GetTokenInformation() call and that you can't free this memory within the function as you would normally (notice the commented free(lpTokenData) call within the code that shows where you'd normally free the buffer).

The data buffer returned by GetTokenInformation() contains a TOKEN_USER data structure. This data structure contains a security identifier (SID) that we'll use to obtain three pieces of information about the user. The ConvertTokenUserToSidString() function accepts the buffer as input, typecasts it to a TOKEN_USER data structure, then uses the data structure to make a ConvertSidToStringSid() call. The resulting LPTSTR, SIDValue, is used to create a String value (SIDString). Notice that the code requires a double pointer (**) to SIDString to create a reference to it. This is an idiosyncrasy of Visual C++ that you need to consider when creating wrapper functions such as this one. Also notice that the function uses LocalFree() to free the memory used by SIDValue. That's because the memory for SIDValue is actually allocated by the ConvertSidToStringSid() function. We'll see later that locally allocated memory is freed using the free() function.

The final wrapper function, ConvertTokenUserToUserData(), retrieves the user name and domain using the SID. In this case, the code relies on the LookupAccountSid() function, which requires two locally allocated buffers. Notice the use of the malloc() function with appropriate typecasting and the use of the free() function calls to free the memory later.

The example does show one instance where there's a direct correlation between a Win32 API function and the wrapper function. The FreePointer() function simply calls the free() function used earlier to free memory signified by a pointer.

The C# and Visual Basic code required to use all of these wrapper functions is almost mundane compared to the wrapper code. The code calls the various wrappers to obtain a user token, use it to access the user's SID, name, and domain, and then display that information in a message box. Listing 3.3 shows the code to perform these tasks.

Listing 3.3: Obtaining the User SID, Domain, and Name public enum TOKEN_INFORMATION_CLASS {

TokenUser = 1,

TokenGroups,

TokenPrivileges,

TokenOwner,

TokenPrimaryGroup,

TokenDefaultDacl,

TokenSource,

TokenType,

TokenImpersonationLevel,

TokenStatistics,

TokenRestrictedSids,

TokenSessionId,

TokenGroupsAndPrivileges,

TokenSessionReference,

TokenSandBoxInert

private void btnTest_Click(object sender, System.EventArgs e)

int Result;

SecurityWrapper SW = new SecurityWrapper(); IntPtr TokenSize = new IntPtr(0);

IntPtr TokenData = new IntPtr(0);

String SIDString = null;

String UserName = null;

String Domain = null;

// Get the size of the data structure. The return value of // this call is always 0. The call has actually failed because // it didn't retrieve the user information token.

Result = SW.GetTokenSize((int)TOKEN_INFORMATION_CLASS.TokenUser, ref TokenSize);

// Get the token data. The return value of this call should always // be 1. The call has succeeded in returning the user token. Result = SW.GetTokenData((int)TOKEN_INFORMATION_CLASS.TokenUser, ref TokenData, TokenSize, ref TokenSize);

// Obtain the SID String.

Result = SW.ConvertTokenUserToSidString(TokenData, ref SIDString);

// Obtain the user account information.

Result = SW.ConvertTokenUserToUserData(TokenData, ref UserName, ref Domain);

// Free the memory used by the token data. SW.FreePointer(TokenData);

// Display the output.

MessageBox.Show("User Name:\t" + UserName + "\r\nDomain:\t\t" + Domain + "\r\nSID:\t\t" + SIDString, "Local Account Information", MessageBoxButtons.OK, MessageBoxIcon.Information);

The TOKEN_INFORMATION_CLASS enumeration shows the types of data you can request using the GetTokenSize() and GetTokenData() methods. The example code uses TokenUser. However, you can also gain access to the process privileges, owner, group association, statistics, and other kind of information. In short, the technique shown in this section is the tip of a much larger iceberg.

The btnTest_Click() method is straightforward. The GetTokenSize() and GetTokenData() methods work together to obtain the TokenData pointer—which is a pointer to the TOKEN_USERdata structure discussed earlier. However, as far as C# is concerned, TokenData is simply a pointer to some data. It could point to any of the data structures used by any of the TOKEN_INFORMATION_CLASS enumeration members. It's only during the call to the ConvertTokenUserToSidString() and ConvertTokenUserToUserData() functions that the code becomes specific to the TOKEN_USER data structure. Figure 3.1 shows the output of this example.

Warning The code must free the memory the TokenData variable points to before it exits. Otherwise, the application will leak memory. The Visual C++ DLL contains a special function, FreePointer(), for this purpose. Any DLL you create should contain a special function that accomplishes this same task.

Figure 3.1: The output of the example program is simple, but demonstrates token access.

Was this article helpful?

0 0

Post a comment