Spelunking Utilities

 < Day Day Up > 



One of the interesting things about the .NET Framework is that .NET code is very open. That is, even if you don’t have the original source code for a library or an application, you can find out quite a bit about that source code. This can come in handy when you have a question about how something works. In the first part of this chapter, I’ll show you a variety of ways to go exploring inside .NET code that’s already on your computer. Later on in the chapter, I’ll discuss some of the things that software publishers can do to make it harder to explore within their code; obfuscation can be an important tool in protecting intellectual property.

Ildasm

As you probably already know, all .NET code is executed by the Common Language Runtime (CLR). The CLR doesn’t understand anything about the plethora of .NET languages—Visual Basic .NET (VB.NET), C#, managed C++, and so on. Rather, each of those languages is compiled into a single unified format named Microsoft Intermediate Language (MSIL). The CLR, in turn, can execute MSIL code.

The .NET Framework SDK includes several utilities that let you work with MSIL code. One of these, Ildasm, is a disassembler for MSIL code. That is, it can take a file full of MSIL and turn it into something at least moderately human readable. I’ll demonstrate Ildasm by looking at one of the Download Tracker components. Here is a part of the source code for DBAccess.cs, in the DBLayer component of Download Tracker:

 /// <summary>  ///     Save a download in the database  /// </summary>  /// <param name="d" type="DownloadTracker.DownloadEngine.Download">  ///     <para>  ///         Information on the download  ///     </para>  /// </param>  /// <returns>  ///     True on success, false on failure  /// </returns>  public bool SaveDownload(DownloadTracker.DownloadEngine.Download d)  {      OleDbConnection cnn = new OleDbConnection(       @"Provider=Microsoft.Jet.OLEDB.4.0; Data Source=" +       this.DatabaseLocation);      OleDbCommand cmd = cnn.CreateCommand();      try      {          cmd.CommandType = CommandType.Text;          cmd.CommandText = "INSERT INTO [Downloads]([ProductName], "  +           [FileName], [DownloadDate], [Version], [Description], "  +           [Rating], [Size])" +           " VALUES('" + d.ProductName + "', '" + d.FileName +           "', #" + DateTime.Now.ToShortDateString() + "#, '" +           d.Version + "', '" + d.Description + "', '" + d.Rating +           "', " + d.Size + ")";          cnn.Open();          cmd.ExecuteNonQuery();          // Retrieve the new autonumber value into the download object          cmd.CommandText = "SELECT @@IDENTITY";          d.ID = (int)cmd.ExecuteScalar();          return true;      }      catch (Exception e)      {          return false;      }      finally      {          if(cnn.State == ConnectionState.Open)              cnn.Close();      }  }  /// <summary>  ///     Update a download in the database  /// </summary>  /// <param name="d" type="DownloadTracker.DownloadEngine.Download">  ///     <para>  ///         The download whose information is to be uploaded.    The Download.ID property must be supplied.  ///     </para>  /// </param>  /// <returns>  ///     True on success, false on failure  /// </returns>  public bool UpdateDownload(DownloadTracker.DownloadEngine.Download d)  {      OleDbConnection cnn = new OleDbConnection(       @"Provider=Microsoft.Jet.OLEDB.4.0; Data Source=" +       this.DatabaseLocation);      OleDbCommand cmd = cnn.CreateCommand();      try      {          cmd.CommandType = CommandType.Text;          cmd.CommandText = "UPDATE [Downloads] SET [ProductName] = '" +          d.ProductName + "', " +              "[FileName] = '" + d.FileName + "', [Version] = '" + d.Version +              "', [Description] = '" + d.Description + "', [Rating] = '" +    d.Rating +              "', [Size] = " + d.Size + " WHERE [DownloadID] = " + d.ID;          cnn.Open();          cmd.ExecuteNonQuery();          return true;      }      catch (Exception e)      {          return false;      }      finally      {          if(cnn.State == ConnectionState.Open)              cnn.Close();      }  } 

To use Ildasm, select Start Programs Microsoft Visual Studio .NET 2003 Visual Studio .NET Tools Visual Studio .NET Command Prompt. This will open a command prompt with the environment variables and path set up to run .NET utilities. Type ildasm at the command prompt and press Enter to open the Ildasm window. Select File Open, browse to the DBLayer.dll file, and click Open. This will disassemble the MSIL file to a tree view. Figure 7.1 shows Ildasm with all of the tree view nodes expanded.

click to expand
Figure 7.1: A .NET file in Ildasm

Each of the nodes in the Ildasm tree represents a particular piece of the source code—the constructor, methods, properties, and so on. Double-clicking any of the nodes without children will open a separate window that displays the relevant portion of the MSIL. Figure 7.2 shows the disassembly window for the SaveDownload method.

click to expand
Figure 7.2: Disassembling a method with Ildasm

If you inspect the disassembly, you’ll find that it includes all of the literal strings (though not comments) and objects from the original source code. In fact, it also includes all of the logic from the source code, though it can take some deciphering to figure out what’s going on.

For most developers, Ildasm is mostly useful as a demonstration of the open nature of .NET code. But there are easier alternatives to learning MSIL if you just want to figure out what a class does or how it works.

Reflector

With enough practice, you could learn to translate MSIL to the original C# code. But why bother? That’s the sort of routine task for which computers are well suited. Lutz Roeder’s free Reflector for .NET (www.aisto.com/roeder/dotnet/) can take a .NET assembly and show you the source code that created the assembly.

Figure 7.3 shows Reflector with the DBLayer.dll file loaded. On the left you can see a tree view of the classes and members included in the file. On the right, Reflector is displaying the source code for the SaveDownload method, which it automatically decompiled for me.

click to expand
Figure 7.3: Exploring with Reflector

Reflector has a bunch of other features to help you find your way around a .NET library: It can disassemble to MSIL as well as decompile to C# or VB.NET, it will show you call trees for a method, and it has flexible search features that let you quickly find classes and members of interest. Here is Reflector’s version of SaveDownload. Although it differs from the original source that you saw earlier in the chapter, it’s pretty easy to tell what’s going on here, without needing to learn the details of MSIL.

 public bool SaveDownload(Download d)  {      OleDbConnection connection1;      OleDbCommand command1;      Exception exception1;      bool flag1;      object[] array1;      DateTime time1;      connection1 = new OleDbConnection(string.Concat(   "Provider=Microsoft.Jet.OLEDB.4.0; Data Source=",   this.DatabaseLocation));      command1 = connection1.CreateCommand();      try      {          command1.CommandType = 1;          array1 = new object[15];          array1[0] = "INSERT INTO [Downloads]([ProductName],   [FileName], [DownloadDate], [Version], [Description],   [Rating], [Size]) VALUES('";          array1[1] = d.ProductName;          array1[2] = "', '";          array1[3] = d.FileName;          array1[4] = "', #";          time1 = DateTime.Now;          array1[5] = time1.ToShortDateString();          array1[6] = "#, '";          array1[7] = d.Version;          array1[8] = "', '";          array1[9] = d.Description;          array1[10] = "', '";          array1[11] = d.Rating;          array1[12] = "', ";          array1[13] = d.Size;          array1[14] = ")";          command1.CommandText = string.Concat(array1);          connection1.Open();          command1.ExecuteNonQuery();          command1.CommandText = "SELECT @@IDENTITY";          d.ID = ((int) command1.ExecuteScalar());          flag1 = true;      }      catch (Exception exception2)      {          exception1 = exception2;          flag1 = false;      }      finally      {          if (connection1.State == 1)          {              connection1.Close();          }      }      return flag1;  } 

You can do a lot more with Reflector than just look at your own source code, however. It’s a great tool for answering questions about how the .NET Framework functions, and to help you write better code in your own classes. For instance, consider the tail end of the SaveDownload method:

 finally  {      if(cnn.State == ConnectionState.Open)          cnn.Close();  } 

The intent here should be obvious: If the connection is open at the end of the method, close it. But as you may know, there’s a second way to finish using an object: the Dispose method. The Dispose method takes a single Boolean parameter. If the parameter is True, then it releases unmanaged resources as well as managed resources. Depending on what your application is doing, this might be critical, because unmanaged resources will hang around until the garbage-collection process decides to destroy the object. If those resources are scarce, this might cause problems for your application, or for the rest of your system.

But the .NET Framework help is maddeningly nonspecific about whether the OleDbCommand.Dispose method actually has any unmanaged resources to dispose of. So, should I call Close, or Dispose, or both, when I’m done with a connection? Rather than speculating or guessing, I can use Reflector to actually decompile the relevant parts of the .NET Framework into C# code and find out for sure!

As Figure 7.4 shows, Reflector can drill into the Framework classes just as well as it can into code you write. You may notice that many of the members of the class are shown in lighter type. These are internal or hidden members, and they’re not documented. But their decompiled code can make for interesting reading!

click to expand
Figure 7.4: Reflecting into the .NET Framework

Here’s the code that Reflector shows for the OleDbConnection.Close method:

 public void Close()  {      if (this.objectState != 0)      {          this.DisposeManaged();          this.OnStateChange(1, 0);      }  } 

Similarly, I can use Reflector to discover the code for the Dispose method:

 protected override void Dispose(bool disposing)  {      if (disposing)      {          if (this.objectState != 0)          {              this.DisposeManaged();              if (base.DesignMode)              {                  OleDbConnection.ReleaseObjectPool();              }              this.OnStateChange(1, 0);          }          if (this.propertyIDSet != null)          {              this.propertyIDSet.Dispose();              this.propertyIDSet = null;          }          this._constr = null;      }      base.Dispose(disposing);  } 

It’s apparent from this that the Dispose method is doing some work that the Close method is not, so if I want to minimize my application’s resource usage, I should call Dispose when I’m done with OleDbConnection objects. But should I call just Dispose, or both Dispose and Close? Well, you’ll see that all that Close does is call the DisposeManaged method, which is also called by the Dispose method. So there’s no need to call both, and I can just replace the calls to Close with calls to Dispose.

RULE

When you’re not sure what part of the .NET Framework does, don’t speculate: Look at the source code and find out.

start sidebar
The .NET Arms Race: Reflection vs. Obfuscation

The feature of .NET that allows this sort of deep analysis of .NET code is called reflection. If you investigate the System.Reflection namespace, you’ll find well-documented APIs to return information on classes and members. Between reflection and MSIL, it’s possible to find out quite a bit of information about the structure of a .NET application, even without the source code.

While powerful, this facility can be a problem for the independent software vendor who wants to make a living. Many developers are worried that with all of their source code open for inspection they won’t be able to maintain a competitive advantage. To combat this, another group of .NET utilities have grown up: obfuscators. Obfuscation is the process of taking source code and automatically changing it to make it harder to understand, before it’s compiled.

For example, Visual Studio .NET 2003 includes a version of an obfuscation utility named Dotfuscator. Dotfuscator, among other things, changes the names of all classes and members in your application to arbitrary strings so that they don’t give anything away. It also gives the same name to unrelated members, as long as they don’t have identical signatures. The free version is somewhat limited, but the manufacturer will also sell you an upgrade to a more robust version. Other obfuscation utilities can even change loops or logical tests so that they have the same effect that they originally did but are harder to decipher.

Quite a few obfuscators are available for .NET applications. These include:

  • Demeanor for .NET (www.wiseowl.com/products/Products.aspx)

  • Dotfuscator (www.preemptive.com/dotfuscator/index.html)

  • Salamander .NET Obfuscator (www.remotesoft.com/salamander/obfuscator.html)

  • Spices .NET (www.9rays.net/cgi-bin/components.cgi?act=1&amp;cid=86)

Only you can decide whether your code is truly earthshaking enough to require obfuscation. If you do want to obfuscate your shipping code, though, you’ll need to plan your process carefully. Ideally, you should build an unobfuscated version of your code to test and debug with, and an obfuscated version to ship. Of course, you should also make sure that the obfuscated version passes all of the unit tests, just in case the obfuscation utility does something unexpected.

Keep in mind, though, that obfuscation is only a way to increase the difficulty of understanding your code; it will not prevent a truly determined investigator from figuring out what’s going on. Even if you come up with a scheme that encrypts your code on the drive, the code will have to be decrypted to run, and the curious developer could look at the copy in memory instead of the copy on the drive. The bottom line is that any code (.NET or otherwise) that you install on the customer’s hard drive is susceptible to reverse engineering with enough effort. If an algorithm is truly top-secret and proprietary, the best bet is not to install it at all. Instead, implement it on your own servers and use a web service or other remote interface to let clients call the algorithm and get back results without ever running the code on their own computers.

I’ll look more at obfuscators in Chapter 14, “Protecting Your Intellectual Property.”

end sidebar

Experimenting with New Code

While looking at source code is a useful way to see how something works, there are times when you really need to run the code for a complete understanding. But writing a complete Visual Studio .NET solution to test the behavior of a single component is a lot of work. When you’re just exploring, it’s useful to have some tools that let you instantiate a new component and explore its behavior without a lot of overhead. I’ll show you a pair of these tools in this section.

nogoop .NET Component Inspector

One of the best tools I’ve found for this is the nogoop .NET Component Inspector ($19.95 from www.nogoop.com/product_1.html). You might think of .NET Component Inspector as a generalized test bench for all sorts of .NET components and classes. By using this utility, you can find out what a new bit of code does without the bother of writing a dedicated test application.

Figure 7.5 shows the .NET Component Inspector in action. In this case, I’ve decided to investigate the SqlConnection object. The steps for doing so were easy:

  1. Right-click the System.Data assembly on the GAC tab and select Open. This loads the namespace information for the particular assembly.

  2. Drill down in the Assemblies/Types tree to locate the SqlConnection object. The object is shown in boldface to indicate that it’s creatable. I dragged SqlConnection to the Objects pane to create a new instance of the object.

  3. Right-click on the object and select Event Logging to log its events.

  4. Click in the ConnectionString property. The Parameters window shows me that this property requires a string, so I type in the string and click Set Prop.

  5. Click in the Open method. Then click the Invoke Method button in the Parameters window. This actually calls the Open method on a live copy of the object, and the property values and Event Log tab get automatically updated.

click to expand
Figure 7.5: Using nogoop .NET Component Inspector

As you can see, the .NET Component Inspector also has a Control Design Surface pane. That’s because it also lets you test .NET controls in either design mode or run mode. Figure 7.6 shows an instance of the Windows.Forms.ProgressBar control being tested in the .NET Component Inspector framework.

click to expand
Figure 7.6: Testing a control

Snippet Compiler

Another useful tool for exploratory development is the free Snippet Compiler (www.sliver.com/dotnet/SnippetCompiler/). Unlike .NET Component Inspector, Snippet Compiler works by letting you write code to exercise a new component—but it does so with a minimum of overhead. When you install Snippet Compiler, it will locate an icon in the tray area of your Windows Taskbar. Double-click the icon to open the minimal integrated development environment (IDE) shown in Figure 7.7.

click to expand
Figure 7.7: Snippet Compiler

Snippet Compiler lets you work in either C# or VB.NET, and it can handle console, Windows Forms, or ASP.NET snippets. It’s got a reasonable selection of IDE features (like marking errors and color-coding), and it lets you get code up and running quickly. You wouldn’t want to try to build an entire project with this tool, but for those times when you want to answer a simple question about how some code works, it’s the perfect complement to a more full-featured IDE like Visual Studio .NET.

By using Ildasm or Reflector to inspect source code and .NET Component Inspector or Snippet Compiler to investigate the behavior of a control or component, you can easily evaluate new pieces of code that you’re thinking of incorporating into your applications—whether they’re supplied in source code form or as compiled components.



 < Day Day Up > 



Coder to Developer. Tools and Strategies for Delivering Your Software
Coder to Developer: Tools and Strategies for Delivering Your Software
ISBN: 078214327X
EAN: 2147483647
Year: 2003
Pages: 118

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net