Archive for the ‘Development’ Category

Microsoft CCI Framework for Deobfuscating .Net binaries. (Part 3)

February 18th, 2010 by John Hernandez

Renaming parts of the assembly.

So I promised this last week, but I’ve been busy on a new project. Below is some code that shows renaming of methods. This is a solution to renaming classes within namespaces. It iterates over each namespace renaming classes from class1 -> classN. This is more useful for human readability and tracing logic. I leave it as an exercise to the reader to figure out how to rename other parts of the assembly. But hey if you really need it an get stuck, let me know!

I’ll be posting a tool at some point that does all these different actions for you. Hopefully I’ll have a early release out by mid next month. I’m currently learning WPF well enough to build in visulalizations of the control flow graph. That way after a mutator is applied you can visually see the results.

There is a dictionary in the mutator class that uses the namespace string as a key in order to know which class # i left off at. I test on the string length < 2 because the obfuscators I’ve seen that do this trick tend to just rename everything to some obscure unicode code point of length 1. Just a easy stop condition. You can use any stop condition that suits your needs.

View Code CSHARP
public override NamespaceTypeDefinition Visit(NamespaceTypeDefinition namespaceTypeDefinition)
{
  string key = namespaceTypeDefinition.ContainingUnitNamespace.Name.Value;
  if (!classDict.ContainsKey(key))
  {
     classDict.Add(key, 0);
  }
  if (namespaceTypeDefinition.Name.Value.Length < 2)
  {
     int i = classDict[key];
     namespaceTypeDefinition.Name = this.host.NameTable.GetNameFor(String.Format("Class{0}", i));
     i++;
     classDict[key] = i;
  }
  return base.Visit(namespaceTypeDefinition);
}

Microsoft CCI Framework for Deobfuscating .Net binaries.

February 3rd, 2010 by John Hernandez

We had an issue recently crop up with an obfuscated .Net binary. I’ve been meaning to spend more time reversing .Net protected binaries so I start looking in it. Unfortunately everything I was reading on the forums and internet seemed difficult. Having recently read a little about Microsoft’s CCI framework, I thought this might be the best solution to the problem. Using a hex editor and looking for patterns seems hokey and a bit impractical.

So the first thing I decided to try was removing the SuppressIldasmAttribute attribute.  Below is some example code doing just that using CCI and rewriting the file. This produces an executable that works and doesn’t require just hex editing out the attribute leaving an executable that doesn’t run.

View Code CSHARP
static void Main(string[] args)
{
     var host = new PeReader.DefaultHost();
     var module = host.LoadUnitFrom(args[0]) as IModule;
     var attributeRemover = new AttributeRemover(host);
     module = attributeRemover.Visit(module);
     Stream peStream = File.Create(module.Location ".fixed");
     PeWriter.WritePeToStream(module, host, peStream);
     Console.Out.WriteLine("Finished");
}
 
/*
* Removes the static attribute atm SuppressIldasmAttribute.. can be modified to remove any attribute.
*/
 
public class AttributeRemover : MetadataMutator
{
 
     PlatformType pt;
 
     public AttributeRemover(IMetadataHost host)
                              : base(host)
     {
         pt = new PlatformType(host);
     }
 
     public override List<ICustomAttribute> Visit(List<ICustomAttribute> customAttributes)
     {
          for (int i = 0; i < customAttributes.Count; i++  )
          {
               if (customAttributes[i].Type.ToString() == "System.Runtime.CompilerServices.SuppressIldasmAttribute")
               {
                    customAttributes.RemoveAt(i);
                    break;
               }
          }
          return base.Visit(customAttributes);
     }
}

As you can see it requires very little code. Anyways that’s enough for this post. I also have some more code I’ll be posting that uses CCI to rename methods/class/methods from their “mangled names” and code that removes invalid OpCodes so reflector works at the IL level. I’m still working on code that goes through creates a optimized methods to remove the invalid jumps such that C# code can hopefully be reconstructed. We’ll see how that goes.

Getting Around Conditionally Banned APIs When Using Microsoft’s banned.h Header File

December 8th, 2009 by Ramsey Dow

This code sample makes use of banned.h, a Microsoft-supplied header file that deprecates dangerous CRT functions. Microsoft also poisons these functions on UNIX if you include banned.h there. This is a Good Thing, but what about the fact that they banned strlen? The banned API page states:

For critical functions, such as those accepting anonymous Internet connections, strlen must also be replaced.

That’s good advice for cases where you want to operate on untrusted data. In those cases they tell you that you should use strnlen_s. The problem is, banned.h straight out bans strlen. There is no way to tell it that hey, this particular invocation is safe because I control the buffer in all aspects. Nope, sorry. You can’t use strlen. Or can you?

Here is a code sample that uses banned.h to deprecate unsafe APIs, yet still manages to invoke strlen when necessary. The sample works in both Visual Studio on Windows and GCC on UNIX.

 
//
 
//  banned_test.c
//  20091208 ramsey@casabasecurity.com
//
//  A sample program that illustrates how to "grandfather in" banned APIs
//  for use when they are marked deprecated (Windows) or poisoned (UNIX)
//  by the compiler.
//
//  to compile on Windows:
//  cl /GS /W4 /WX banned_test.c
//
//  to compile on UNIX:
//  gcc -Wall -Werror banned_test.c
 
#if defined _WIN32
 
#include <windows.h>
#endif    // _WIN32
 
#include <stdio.h>
#include <string.h>
#if defined _WIN32
 
size_t my_strlen(const char *string)
{
  size_t len;
  #pragma warning(push)
  #pragma warning(disable:4995)
  len = strlen(string);
  #pragma warning(pop)
  return len;
}
 
#else
#define my_strlen strlen
#endif    // _WIN32
 
#include "banned.h"
 
int main(int ac, char **av)
{
  char *str = "foo";
  #if defined _WIN32
  UNREFERENCED_PARAMETER(ac);
  UNREFERENCED_PARAMETER(av);
  #endif    // _WIN32
  #if defined _WIN32
  (void)printf("len is %Id\n", strlen(str));
  #else
  (void)printf("len is %zd\n", strlen(str));
  #endif    // _WIN32
  return 0;
}

Note that this code requires the use of Microsoft’s banned.h header file, which can be downloaded here. Stick it in the same directory as the above source file.

To compile the sample in Windows from a Visual Studio Command Prompt:


cl banned_test.c /GS /W4 /WX

As expected, this program will generate an error when run:


banned_test.c

banned_test.c(50) : error C2220: warning treated as error - no 'object' file generated

banned_test.c(50) : warning C4995: 'strlen': name was marked as #pragma deprecated

Now edit banned_test.c and change the strlen on line 50 to my_strlen and recompile:


cl banned_test.c /GS /W4 /WX

It should compile without error. Now run it and you should see:


len is 3

Nifty.

The same code works without change on UNIX (tested on NetBSD):


gcc -Wall -Werror banned_test.c

As with the Windows example, running the program will generate an error, as expected:


banned_test.c:52:31: error: attempt to use poisoned "strlen"

Again, change the occurrence of strlen (this time on line 52) to my_strlen and recompile. It will work and when run, it will say:


len is 3

What’s going on here is simple. While we are banning use of the strlen function, we are still allowing its use selectively through a wrapper that we have “grandfathered in.” This is easy to accomplish in UNIX: we simply

#define my_strlen strlen

prior to including banned.h and use that function call entry point instead. Problem solved. This is not as easy to accomplish with Windows, however, as cl.exe has no notion of “grandfathering in” deprecated APIs. So what we do is wrap strlen in another function. We ignore the deprecation warning that occurs where we make the call to strlen through the judicious application of some Visual Studio-specific pragma instructions. Now all need to do is call in to our new function entry point. We’re good to go. The Windows solution requires a little more work up front, but turns out to be not so hard to accomplish after all.

Preventing Security Development Errors: Lessons Learned at Windows Live by Using ASP.NET MVC

November 23rd, 2009 by Chris Weber

Casaba had the opportunity to contribute to a new Microsoft paper regarding ASP.NET MVC security. It's online through the SDL pages, and here's the paper's direct link. A short summary of the paper follows.

The SDL preaches 'secure by default'. When Windows Live moved to ASP.Net MVC, they used that opportunity to build mitigations into the framework that prevent developers from making accidental errors which result in security flaws. Specifically, they targeted these three security issues – XSRF, Open redirects and JSON hijacking.

For XSRF, the mitigation was that all HTTP requests are checked for a canary by default except for HTTP GET requests. Developers can also opt-out specific pages or functionality. This automatic ‘on-by-default’ canary checking prevents accidental errors which lead to XSRF.

For Open redirects, Windows Live added a wrapper around the Redirect result in ASP.Net MVC which checks a list of approved domains. This way when a developer called Redirect and forgot to ensure it was safe, the wrapper would cover them automatically.

For JSON hijacking, they ensure that the JSON result included a canary check by default. This prevented developers from being able to return JSON without a canary, thus preventing JSON hijacking.

On the Importance of Good Developer Documentation

November 20th, 2009 by Ramsey Dow

Programmers rely on documentation. It's how we learn to use APIs. Misusing APIs is a leading source of vulnerability. You might think that documentation is a cure to this ailment. Unfortunately, as someone who has been in software development for a long time, I can tell you that documentation quality is not always what it should be. API documentation serves as a reference. I have yet to meet the programmer who can recall every nuance about every API for all the languages they program in. (Were such a programmer to exist, its name might well be Robby the Robot.)

Recently I was converting strings using the mbstowcs_s and wcstombs_s functions. (These are from from the bounds checking extensions to the C Library specified in ISO/IEC TR 24731-1.) These functions allow you to convert multibyte character sequences to and from wide character sequences. These functions are available to C and C++ programmers using Microsoft's Visual Studio compiler. (I am not yet aware of any UNIX compatible compiler that supports the draft TR 24731-1 standard.)

Since these two functions convert strings, it is worth looking at the parameters they expect. (Not doing so is a sure fire way to do something stupid, like enable a buffer overflow.) Looking at the relevant parameters for these two functions, we see:

mbstowcs_s:
[in] sizeInWords
      The size of the wcstr buffer in words.
[in] count
      The maximum number of wide characters to store in the wcstr buffer, not including the terminating null, or _TRUNCATE.

wcstombs_s:
[in] sizeInBytes
      The size in bytes of the mbstr buffer.
[in] count
      The maximum number of bytes to be stored in the mbstr buffer, or _TRUNCATE.

Does count in wcstombs_s account for the terminating NULL or not? Failure to account for this could introduce an off-by-one error which, in turn, may lead to an exploitable condition, such as a buffer overflow. How can we determine this from the documentation? Well, in its current state, we can't. This is what we call a “doc bug.”

Luckily, Microsoft includes the source code for the C Runtime with most Visual Studio SKUs. Assuming you installed Visual Studio in Program Files, you should be able to find the CRT source code in Program Files\Microsoft Visual Studio 9.0\VC\crt\src. CRT source code is included with all Visual Studio SKUs except for the Express Editions. Luckily for Express Edition users, the forthcoming Visual Studio 2010 release finally opens up the CRT sources to Express Edition users. If you are using an Express Edition of VS2008 or earlier, consider grabbing the VS2010 Express beta from here.

In any case, if you have the CRT source code, it is easy to track down the source for wcstombs_s and check to see if the terminating NULL is intended to be accounted for or not. Looking into wcstombs.c we discover this bit of text in the comment for the wcstombs_s function:

size_t n = maximum number of bytes to store in s (not including the terminating NULL)

Clearly, the terminating NULL is not meant to be included. This is as we suspected, but now we have verified it instead of blindly assuming that it would be the case. As security practitioners we should be careful not to make assumptions. Verify instead!

This documentation bug has been reported to Microsoft. With any luck it will get addressed prior to the VS 2010 release on March 22, 2010.

Use the Source, Luke!

October 20th, 2009 by Ramsey Dow

If there's one thing that I've learned throughout the years as a programmer, it is not always safe to trust the documentation. In fact, there is an old saying, “Use the source, Luke!” When possible, you should do just that.

While looking over the CERT Secure C Coding Standard I noticed the following recommendation: ERR30-C. Set errno to zero before calling a library function known to set errno, and check errno only after the function returns a value indicating failure. CERT goes on to write, “[s]ome functions lack documentation regarding errno in the C99 standard.” They follow this up with an example for Windows: “[i]n this compliant solution, errno is not checked because fopen() makes no promise of setting it.” This would be fine, were it true. However, it is false. Let us take a closer look.

It is true that the symbol, errno, appears nowhere in the MSDN documentation for fopen. However, one need only look to fopen.c (included with all commercial Visual C implementations) to see that errno.h is #include'd and errno is indeed set for locked streams, bad names (e.g., empty string), et al.

The use of errno is not as robust in the case of Microsoft's fopen implementation as it is in the implementation on my NetBSD box, but that's not the point. The point is that CERT stated something was true based on documentation when in fact, it was not true. The lesson here is that one cannot simply rely on assumptions based on documentation, one must also look to the source to see what is happening.

In the case of Microsoft's C and secure C runtimes, the source code is available for you to look at, provided you have Visual Studio installed. (Caveat: you don't get the CRT source code if you install Visual C++ Express.) I found the code living on my box under Program Files at Microsoft Visual Studio 9.0\VC\crt\src.

Of course, if you're programming on Windows you should prefer fopen_s to fopen anyway. For the record, the MSDN documentation for fopen_s clearly states that it returns an errno_t, which is the Secure CRT's answer to errno.

Update: I just found out from a source inside the Visual Studio team at Microsoft that Visual Studio 2010 Beta 2's Express Edition SKU contains the CRT source code. That's good news. You can get more information on Visual Studio 2010 Beta 2 here, and you can download it here.

A Vim plugin for highlighting APIs banned by the Microsoft SDL

August 23rd, 2009 by Ramsey Dow

I do a lot of programming, so I live in my editor. I use Vim. If you also use Vim then I've got something to share with you: a new syntax plugin that highlights function calls banned by Microsoft's Security Development Lifecycle (SDL). You can obtain the banned.vim syntax plugin from the Vim script archive.

The banned.vim syntax plugin will highlight C function calls that have been banned by the SDL. It adds functionality to the existing C and C++ Vim syntax plugins. Banned APIs, such as strcpy and others, will appear visually in Vim as if they were errors. It is my hope that this extra attention will cause you to reconsider using the banned API and replace it instead with a safer alternative. Although many of these banned function calls are Windows-specific, there are quite a few that are also available in UNIX and should be avoided. Details on the APIs banned by Microsoft's SDL can be found on Microsoft's site.

Here's a screen shot of banned.vim in action. In this case we're editing str_cat.c, one of the entries from the 2008 SANS Awards for Finding Coding Books with Secure programming Flaws. Notice the banned APIs in the code below?

banned.vim in action

Installing banned.vim is easy. First, you need to know what your runtimepath is, which varies from operating system to operating system. If you don't know what your runtimepath is, check the Vim documentation. Second, create the directory structure after/syntax in your runtimepath directory if it doesn't already exist. Third, copy banned.vim into runtimepath/after/syntax as both c.vim and cpp.vim. That's all there is to installation. There is no need to edit your .vimrc or anything.

I would like to thank Rob Mooney for suggesting this plugin in the first place.

Let me see that certificate a little more closely. Part 1 – Validating the Server’s Certificate

June 11th, 2008 by Brian Lewis

If you are developing a client to a server service that communicates over SSL such as a Web Service then it is your job to ensure your server is the "real deal" and not some rouge server or man-in-the-middle. How do you do that? Validate the server's certificate. Make sure the certificate is for the domain you are accessing, make sure the certificate chain is valid, and make sure the certificate is signed by a trusted certificate authority (CA). Sound like a pain? Well it isn't. You get a lot for a little with the right API calls.

WinHttpReceiveResponse in C++ will return FALSE if the certificate has one of the following errors:

WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED

Certification
revocation checking has been enabled, but the revocation check failed to verify
whether a certificate has been revoked. The server used to check for revocation
might be unreachable.

WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT

SSL certificate is invalid.

WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED

SSL certificate was revoked.

WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA

The function is unfamiliar with the Certificate Authority that generated the server's certificate.

WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID

SSL certificate common name (host name field) is incorrect, for example, if you entered www.microsoft.com and the common name on the certificate says www.msn.com.

WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID

SSL certificate date that was received from the server is bad. The certificate is expired.

WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR

The application experienced an internal error loading the SSL libraries.

However, WinHttpReceiveResponse does not return these errors directly as a call to GetLastError() will only return ERROR_WINHTTP_SECURE_FAILURE if there is a problem with the server's certificate. You must use the CallBack WINHTTP_STATUS_CALLBACK to access the specific errors listed above.


public WINHTTP_STATUS_CALLBACK myOwnAsyncCallback( __in HINTERNET hInternet,
__in DWORD_PTR dwContext,
__in DWORD dwInternetStatus,
__in LPVOID lpvStatusInformation,
__in DWORD dwStatusInformationLength)
{
if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_SECURE_FAILURE)
// We have a certificate issue but which one? Take action before each break. This function must be thread safe and reentrant.
switch(*(DWORD*)lpvStatusInformation)
{
case WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED:
break;
case WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT:
break;
case WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED:
break;
case WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA:
break;
case WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID:
break;
case WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID:
break;
case WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR:
break;
}
}
HINTERNET hSession = WinHttpOpen(L"A WinHTTP Example Program/1.0",
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS, 0);
WINHTTP_STATUS_CALLBACK isCallback = WinHttpSetStatusCallback( hSession, WINHTTP_STATUS_CALLBACK)myOwnAsyncCallback,WINHTTP_CALLBACK_FLAG_SECURE_FAILURE,
NULL);
//The rest of your code including call WinHttpReceiveResponse

For more information see
http://msdn.microsoft.com/en-us/library/cc185684(VS.85).aspx
http://msdn.microsoft.com/en-us/library/aa383917(VS.85).aspx
http://msdn.microsoft.com/en-us/library/aa384266(VS.85).aspx
http://msdn.microsoft.com/en-us/library/aa384115(VS.85).aspx

It all comes back to the basics

April 29th, 2008 by Samuel Bucholtz

Recently there has been a lot of talk in the security community about the Flash ActionScript exploit written by Mark Dowd (http://documents.iss.net/whitepapers/IBM_X-Force_WP_final.pdf). I will not go into a breakdown of the exploit as others have already done a great job of blogging about it. What I would like to discuss is two big takeaways that even programmers who are not "uber-hackers" can appreciate.

The first takeaway is the importance of understanding and implementing the fundamentals. The exploit above occurs because of a NULL dereference triggered by an out of memory return by malloc(). I remember in my first C programming class when I was working with a partner an a project and he was trying to properly implement malloc(). I had already been using C for a few years and always checked the return of a function. I could not figure out why he was not checking to make sure sufficient memory had been allocated. He gave me the excuse that it would require a lot of code to check the return value and that since the data structure was small the allocation would fail. Nonsense, as I demonstrated by filling up the 640K of standard memory. He then argued that we would be better off implementing XMS or EMS to access the other 3MB of "high" memory, a silly argument if ever there was one.

When non-security professionals describe what hackers do they often say that hackers look for ways to bypass restrictions and go around roadblocks. This is a somewhat fair description, but what they may not know is that often hackers are merely taking advantage of mistakes or the faulty reasoning of programmers who tried to skip or work around something simple or basic that they felt would be too much work to implement properly. The original "hacks" were simply ways of getting things done quicker, easier, or more elegantly but sometimes there is a fundamental reason for doing things one way and the "hack" just gets you into hot water. Anyone who has ever picked up a C programming book and looked at the function info for malloc() knows that it is NOT guaranteed to return the memory requested. Obviously, it is a BAD idea to simply assume that it succeeded in allocating all the memory requested. Know your functions/methods and how to properly implement them.

The second takeaway for the average programmer is the need to integrate and leverage the latest in security functionality in your code from the ground up. What do I mean by this? Follow-up research on the exploit has shown that if DEP had been turned on and opted-in the exploit would not have worked. DEP marks portions of memory as NX (No Execute). Such areas in memory will trigger a processor fault if an attacker attempts to execute shellcode they have somehow loaded into such memory. Is DEP a panacea- No. But it provides a second layer of defense. Now you might say- DEP is a system setting that users or admins or Microsoft can turn on or off, there is nothing I can do as a programmer. This is not true. First of all, you need to write your application to make sure it works properly with DEP turned on. You need to test your application to verify this. You need to inform users so they know they can safely use DEP with your application.

A number of other features like ASLR (Address space layout randomization), /GS (canary based buffer overflow detection), etc. are provided now by Visual Studio and other compilers or by the latest versions of the Windows operating system. Developers should be building their code to use these basic security tools that are in most cases so easily integrated. None of these features can prevent all security threats, but there are many applications out there using outdated compilers or failing to implementeven the simplest of automated defenses for lack of awareness or a fear of performance degradation. To the former- browse through just a few of the numerous security respurce out there and then review your compiler/linkers security related flags; to the latter- given the massive code bloat in this object oriented development world compared with the (good) old days of hand tuned assembly in a COM file what is a one or two percent more overhead :) .

Using ASP.Net session handling with secure sites (set the secure flag)

February 4th, 2008 by Samuel Bucholtz

One of the common problems we see with many web applications is reliance on ASP.Net sessionID without understanding the security ramifications. ASP.Net provides web developers with a powerful means of tracking user state and identity with very little coding. Rather than creating your own custom authentication cookie, handling the trickiness of forms auth or mapping your cookie to a Windows identity, password policy implementation, not to mention creating server objects to store the state for a given user, ASP.net does it all for you.

ASP.Net offers two methods of tracking session state- URL or cookie. URL based methods are used in cases where it is expected that some users will have disabled cookies and still need a server-side session to track state. This has become less common as more and more of the web relies on cookies. In addition the URLs look ugly and are considered unacceptableby many usability gurus.

The second method is a cookie sent as a header to the server. This cookie is sent over HTTP or HTTPS and is used by ASP.net to link an incoming request to the server-side state. So you are running your site on SSL, where is the problem? By default, the SessionID is just a cookie the browser sends it when making any response to the domain. If you go to https://yourapp/application, you will be sent a cookie over SSL that I cannot see. If I e-mail you a link to click for http://yourapp/application, I will see the cookie sent over HTTP as long as your server responds on port 80.

What you want to do is set the 'secure' flag on the cookie. You have many options for doing this: adsutil set w3svc/1/AspKeepSessionIDSecure 1 will tell ASP.net to mark the session cookie as Secure. When a cookie is marked as secure it will not be sent by the web browser unless the connection to the server is over https. You must be aware that the user will now have no session state if they browse to the site using http your application will need to redirect http requests to https in order to access the session state.

Is the ASP.Net session ID the only cookie I can protect in this way? No. You can use a web.config configuration to customize the security of all your cookies (http://msdn2.microsoft.com/en-us/library/ms228262.aspx). You will also be able to set cookies to be HttpOnly which adds its own element of security and is supported by newer browsers.

Finally, you can set both the secure flag and the HttpOnly flag for any other cookies you set programmatically through ASP.Net with http://msdn2.microsoft.com/en-us/library/ms228262.aspx.

A few other things to remember-

ASP.Net sessions expire after 20 minutes UNLESS a new request is seen. Otherwise they can remain until the server is recycled.

SessionIDs can be reused. When stored as a cookie the sessionID will go to any machine hosting the same parent domain. They will NOT have the server-side state though unless some clustering or back-end logic handles sharing state across servers. If you want to ensure that reuse does not happen, rather than using Session.Abandon you must overwrite the ASP.Net session cookie with an empty cookie value. To properly end a session or force a user to start a new one use Session.Abandon.

For more information checkout – http://msdn2.microsoft.com/en-us/library/ms972969.aspx