Pages

Advertisement

Sunday, September 30, 2007

Reverse Engineering To Learn .NET Better

Introduction

The Microsoft .NET Framework is a new, exciting programming platform for Windows application developers (and potentially for developers on other Operating Systems, as we shall see). Right from the outset .NET has offered a large volume of functionality, both in terms of the underlying CLR and also the accompanying class library that is used by all .NET applications.

Becoming familiar with .NET can be achieved by making use of the mass of documentation in the .NET Framework SDK. Of course there is also a mass of third party documentation covering various aspects of the .NET Framework available in books and online on the Internet.

However it is sometimes said that a programmer can become most familiar with a system if they know exactly how it works. This can perhaps be best achieved if you have access to the underlying source of the system. Take, for example, C and C++ compilers and the Delphi compiler. These 3GL programming languages come supplied with the full source to their entire run-time library (RTL) as well as any class libraries they may use. Having full source code means any question as to the behaviour or implementation of any library feature can be readily resolved by looking at the pertinent source files.

Of course .NET does not ship with its source, but there are various tricks we can use in an attempt to overcome that hurdle, and get the same results as if we did have access to it. This is the remit of reverse engineering and this paper looks at various approaches that we can take in reverse engineering aspects of .NET, simply to understand its behaviour and operation better.

The Options Available To Us

Most reverse engineering options that are open to us stem from one of the key features of managed executable files: metadata. We'll look at the list of reverse engineering options and then look in detail at each of them. But first we'll go off on a slight tangent.

CLI Implementations

Note that I used the phrase managed executable files, rather than .NET executable files? This was an intentional choice and was intended to emphasise the fact that the Microsoft .NET Framework is not the only platform that can execute managed executables. You should be aware that the Microsoft .NET is one particular implementation of an ECMA (European Computer Manufacturers Association) standard.

ECMA 335 is the standard for the Common Language Infrastructure, or CLI . The Microsoft .NET Framework is one example of an implementation of the CLI (it implements the CLI as well as a whole host of additional tools and classes).

The CLI defines the fact that managed executables use the Portable Executable (PE) File Format, as used by Win32 executables. The PE files contain a standard header and then contain metadata and IL (Intermediate Language) code in special sections of the file. The IL code represents the functionality in the file, which will be compiled into native machine instructions prior to execution (usually) with the JIT (just in time) compiler. A module is an example of such a file. An assembly is one or more modules combined with additional metadata called a manifest, which names and describes the assembly, and lists assemblies it depends upon.

It therefore follows that any implementation of the CLI supports assemblies in the PE format.

At the time of writing there are five implementations of the CLI in existence or under development:

  1. Microsoft .NET Framework . This was the initial CLI implementation and it supports Windows platforms (Windows 98, Windows Me, Windows 2000, Windows XP, Windows Server 2003). It is freely downloadable in binary form (source code is not supplied). You can get just the redistributable version (suitable for deploying to machines to execute managed executables against) or the SDK (which includes additional tools, documentation and developer support). At the time of writing the current version of the .NET Framework is v1.1, which supersedes version v1.0 SP2.

  2. Microsoft .NET Compact Framework (. This is a CLI implementation for running on small devices that run Windows CE .NET . The implementation is much more lightweight than the full desktop .NET Framework and is tuned for the limited memory and storage of small devices. At the time of writing the current version of the .NET Compact Framework is v1.0 and can be used to develop application in conjunction with Visual Studio .NET 2003.

  3. Microsoft Shared Source CLI or SSCLI, codenamed Rotor . This is an implementation of the CLI (along with other parts of the Microsoft .NET Framework) that can run on multiple platforms. The supported platforms are Windows XP, FreeBSD 4.7 and Mac OS X 10.2, but it should also work fine on Windows 2000 and earlier versions of FreeBSD. You can freely download the entire source for SSCLI (over 3,000,000 lines of code) for non-commercial purposes.

  4. Mono, which is a project sponsored by and mainly developed by Ximian and runs on Linux and Windows. This is an implementation of the CLI, but which also endeavours to implement various other parts of Microsoft's .NET Framework such as ASP.NET, ADO.NET and VB.NET (called Basic.NET). It will also endeavour to get some level of support for WinForms for GUI applications. At the time of writing the current version is Mono 0.24, but version 1.0 is expected in Q4, 2003.

  5. Portable.NET from the DotGNU project . While the initial target platform was GNU/Linux, it is also known to run under Windows, Solaris, NetBSD, FreeBSD, and MacOS X. The runtime engine has been tested on the x86, PowerPC, ARM, Sparc, PARISC, s309, Alpha, and IA-64 processors. At the time of writing Portable.NET 0.5.6 was the current version.

Self-describing Assemblies

Getting back to the point made at the start of the previous section, assemblies are rich with metadata, making it easy to peer into them and identify the structure of what's inside. You can identify all the namespaces, classes, their methods, fields and properties, structures, enumerations and so on using a mechanism in .NET called reflection.

There are a variety of utilities that use reflection to show the structure and content of .NET assemblies and we'll bump into them as we look at the various reverse engineering options.

IL Examination

The only thing not directly exposed through metadata is the IL code itself. However this hurdle is easily surmountable with existing code and utilities available to read and display IL opcodes directly from a PE file.

High-level Language Decompilers

As well as being able to look at the low level IL code, which may not be palatable to many programmers, there are various tools which will decompile this IL to a high level language, such as C#, VB.NET or C++ with Managed Extensions. These tools examine the IL to identify patterns that allow them to substitute high level statements in their place.

Source Code

The various non-commercial implementations of the CLI all ship with source code. The most interesting example is Microsoft's own Shared Source CLI. This project started from the commercial .NET code base, although there have been multitudes of changes and some subsystems have been entirely reworked.

That notwithstanding, these source bases often offer a fascinating and educational insight into the working (or potential working) of the Microsoft .NET Framework. In many cases, the use of a high level language decompiler in conjunction with the SSCLI source can be a very productive pairing.

Examining The Assembly IL

Let's start off with (almost) the simplest C# application we can build, the classic Hello World application.


using System;

namespace Hello
{
public class HelloClass
{
public static void Main()
{
Console.WriteLine("Hello world");
}
}
}

After looking at results we get with this simple example, we'll turn our attention to looking at an already implemented method in the .NET Framework Class Library, namely the System.Collections.ArrayList class's InsertRange method. This method has been selected reasonably arbitrarily, although it is an example of a method that performs a reasonable amount of work.

IL Disassembler

The .NET Framework comes with a utility that takes source files containing IL code and metadata directives and compiles them into either modules or assemblies. The utility, ilasm.exe, is called the IL Assembler.

When you install the .NET Framework SDK (or Microsoft Visual Studio .NET or Borland C#Builder, which both include the Framework SDK) you get another utility called ildasm.exe, the IL Disassembler. As the name suggests this tool takes a compiled module/assembly and shows you the IL/metadata that constitutes it.

So rich is the IL code and metadata combination that you can use this utility to produce IL source code files that can be run through ilasm.exe to recreate a fully functioning binary. This process is called round tripping and emphasises the lack of ambiguity in the IL/metadata found in every managed executable.

The IL Disassembler operates in two modes, GUI and console. You can tell it which assembly to disassemble by passing the name on the command-line. If you tell it to generate IL source files using the /OUT command-line parameter it operates as a console application. It also runs as a console application if you pass the /TEXT parameter.

If you just pass the assembly name (or no parameters at all) it launches as a GUI app, which is often more convenient for browsing. When launched with no parameters you can use the File | Open menu (or Ctrl+O) to choose an assembly to disassemble, or alternatively drag a file onto the UI from Windows Explorer.

Running ildasm.exe on the simple Hello World application produces this.

As you can see, the tree view shows the assembly manifest and the simple namespace at the top level. In the namespace is our single class and within the class you can see its single static method, Main, as well as a reference to some other internal class elements. These include the instance constructor (.ctor), which is never used in this simple case as we do not construct an instance of HelloClass. The first item is some metadata to indicate the class does not require a class constructor (sometimes called a type initialiser), which is a method that automatically executes before the class is used, to initialise static fields.

Note that by default ILDasm will display all members of a class. If you only want, for example, public and protected (family) members displayed you can use the appropriate items on the View menu or invoke ildasm.exe with the /VIS=PUB+FAM command-line switch.

Double-clicking on any item in the tree view (or selecting it and pressing Enter) shows you the implementation of the item. The Main method shows up like this:

Since the original C# code was trivial, this should be readily understandable. The Hello world string is loaded onto the stack and the System.Console.WriteLine method is called, using the string on the stack as its parameter.

So we see that simple code is evidently readable enough. However a nice touch we can add is to get the disassembly to include the corresponding source code lines as comments just before the disassembled IL, assuming the assembly was compiled with debug information and the source is available. The View | Source menu item does this or you can use the /SOU command-line switch to do the job.

Now what about something a little more taxing, such as the InsertRange method of the ArrayList class? You can locate this method by running ildasm.exe on the mscorlib.dll assembly. You can find this file in the $(windir)\Microsoft.NET\Framework\$(version) directory, where windir is an environment variable that points to your Windows installation directory and version is a fictitious environment variable that equates to v1.0.3705 for .NET 1.0 and v1.1.4322 for .NET 1.1.

This screenshot shows how to locate the right class within the namespaces. First look in the System namespace, then in the nested System.Collections namespace. Then scroll down the methods until you bump into InsertRange.

Because of the fact that there are several statements in this method you will find the IL code quite verbose. The following listing shows the code:






.method public hidebysig newslot virtual 
        instance void  InsertRange(int32 index,
                                   class System.Collections.ICollection c) cil managed
{
  // Code size       228 (0xe4)
  .maxstack  6
  .locals (int32 V_0)
  IL_0000:  ldarg.2
  IL_0001:  brtrue.s   IL_0018
  IL_0003:  ldstr      "c"
  IL_0008:  ldstr      "ArgumentNull_Collection"
  IL_000d:  call       string System.Environment::GetResourceString(string)
  IL_0012:  newobj     instance void System.ArgumentNullException::.ctor(string,
                                                                         string)
  IL_0017:  throw
  IL_0018:  ldarg.1
  IL_0019:  ldc.i4.0
  IL_001a:  blt.s      IL_0025
  IL_001c:  ldarg.1
  IL_001d:  ldarg.0
  IL_001e:  ldfld      int32 System.Collections.ArrayList::_size
  IL_0023:  ble.s      IL_003a
  IL_0025:  ldstr      "index"
  IL_002a:  ldstr      "ArgumentOutOfRange_Index"
  IL_002f:  call       string System.Environment::GetResourceString(string)
  IL_0034:  newobj     instance void System.ArgumentOutOfRangeException::.ctor(string,
                                                                               string)
  IL_0039:  throw
  IL_003a:  ldarg.2
  IL_003b:  callvirt   instance int32 System.Collections.ICollection::get_Count()
  IL_0040:  stloc.0
  IL_0041:  ldloc.0
  IL_0042:  ldc.i4.0
  IL_0043:  ble        IL_00e3
  IL_0048:  ldarg.0
  IL_0049:  ldarg.0
  IL_004a:  ldfld      int32 System.Collections.ArrayList::_size
  IL_004f:  ldloc.0
  IL_0050:  add
  IL_0051:  call       instance void System.Collections.ArrayList::EnsureCapacity(int32)
  IL_0056:  ldarg.1
  IL_0057:  ldarg.0
  IL_0058:  ldfld      int32 System.Collections.ArrayList::_size
  IL_005d:  bge.s      IL_007c
  IL_005f:  ldarg.0
  IL_0060:  ldfld      object[] System.Collections.ArrayList::_items
  IL_0065:  ldarg.1
  IL_0066:  ldarg.0
  IL_0067:  ldfld      object[] System.Collections.ArrayList::_items
  IL_006c:  ldarg.1
  IL_006d:  ldloc.0
  IL_006e:  add
  IL_006f:  ldarg.0
  IL_0070:  ldfld      int32 System.Collections.ArrayList::_size
  IL_0075:  ldarg.1
  IL_0076:  sub
  IL_0077:  call       void System.Array::Copy(class System.Array,
                                               int32,
                                               class System.Array,
                                               int32,
                                               int32)
  IL_007c:  ldarg.0
  IL_007d:  ldarg.2
  IL_007e:  callvirt   instance object System.Collections.ICollection::get_SyncRoot()
  IL_0083:  bne.un.s   IL_00ba
  IL_0085:  ldarg.0
  IL_0086:  ldfld      object[] System.Collections.ArrayList::_items
  IL_008b:  ldc.i4.0
  IL_008c:  ldarg.0
  IL_008d:  ldfld      object[] System.Collections.ArrayList::_items
  IL_0092:  ldarg.1
  IL_0093:  ldarg.1
  IL_0094:  call       void System.Array::Copy(class System.Array,
                                               int32,
                                               class System.Array,
                                               int32,
                                               int32)
  IL_0099:  ldarg.0
  IL_009a:  ldfld      object[] System.Collections.ArrayList::_items
  IL_009f:  ldarg.1
  IL_00a0:  ldloc.0
  IL_00a1:  add
  IL_00a2:  ldarg.0
  IL_00a3:  ldfld      object[] System.Collections.ArrayList::_items
  IL_00a8:  ldarg.1
  IL_00a9:  ldc.i4.2
  IL_00aa:  mul
  IL_00ab:  ldarg.0
  IL_00ac:  ldfld      int32 System.Collections.ArrayList::_size
  IL_00b1:  ldarg.1
  IL_00b2:  sub
  IL_00b3:  call       void System.Array::Copy(class System.Array,
                                               int32,
                                               class System.Array,
                                               int32,
                                               int32)
  IL_00b8:  br.s       IL_00c7
  IL_00ba:  ldarg.2
  IL_00bb:  ldarg.0
  IL_00bc:  ldfld      object[] System.Collections.ArrayList::_items
  IL_00c1:  ldarg.1
  IL_00c2:  callvirt   instance void System.Collections.ICollection::CopyTo(class System.Array,
                                                                            int32)
  IL_00c7:  ldarg.0
  IL_00c8:  dup
  IL_00c9:  ldfld      int32 System.Collections.ArrayList::_size
  IL_00ce:  ldloc.0
  IL_00cf:  add
  IL_00d0:  stfld      int32 System.Collections.ArrayList::_size
  IL_00d5:  ldarg.0
  IL_00d6:  dup
  IL_00d7:  ldfld      int32 System.Collections.ArrayList::_version
  IL_00dc:  ldc.i4.1
  IL_00dd:  add
  IL_00de:  stfld      int32 System.Collections.ArrayList::_version
  IL_00e3:  ret
} // end of method ArrayList::InsertRange


Of course it's much less intelligible now we have more code, but with the metadata and IL documentation, which can be found in Partitions II and III of the CLI specification respectively or Inside Microsoft .NET IL Assembler we could still work it out. However it would take rather longer than most of us would be prepared for.

Reflector


Lutz Roeder is a developer working at Microsoft and he has his own personal Web site. There you can find a popular tool called Reflector, which is at version 3.0.0.1 at the time of writing. You can load assemblies into Reflector either using the File | Open... menu (or Ctrl+O) or by dragging them onto the UI from Windows Explorer.

Reflector does a similar job to the IL Disassembler in that it displays information about an assembly by reflecting across the metadata. However one key difference is that it only shows you public and protected items by default (you can change this in the options: View | Options...).

When you select a method it will display the method signature at the bottom of the main window in C# syntax by default, although you can switch it to show Visual Basic.NET syntax in the Languages menu. You can disassemble a method by selecting it and then choosing Tools | Disassembler (or by pressing Enter). Since the IL code in a given assembly is fixed, you will get much the same results from any tool:



However as you might be able to see, there are additional facilities in Reflector over ildasm.exe, such as the lists of base types and descendant types, a list of dependencies for the assembly, the ability to search for types in an assembly and a call tree. Also, the disassembly window explains the IL instructions using tooltips when you pause your mouse over them. These all make Reflector a useful tool to have available.

Borland Type Browser


Borland's new C#Builder development tool also includes a reflection browser and IL disassembler called Reflection.exe. By default the tool appears to simply be a reflection browser but it can be enticed into offering IL disassembly by adding a simple registry entry.

In the registry key HKEY_CURRENT_USER\Software\Borland\BDS\1.0\Globals a string value called ShowILDissassembly with a value of 1 will mean an extra Code page is displayed when looking at a method. Note carefully the spelling of ShowILDissassembly before creating the value in the registry.



The SSCLI IL Disassembler Source


The Microsoft SSCLI download is supplied as a massive source tree. It contains the source to the class libraries, the C# compiler the IL assembler and the IL disassembler and many other interesting bits and pieces. The IL disassembler is functionally identical to the commercial .NET Framework version except that it does not offer a GUI interface (SSCLI has no GUI support; it is just for building console applications).

If the SSCLI installation directory is referred to as $(ROTOR_DIR) then the ildasm.exe source is located in $(ROTOR_DIR)\clr\src\ildasm. You can peruse and learn from this source base to see how IL disassembly can be performed. If you need to build a custom IL viewer and can read C++ this would be a good place to start perusing.

You can use the Visual Studio .NET debugger to step through the SSCLI IL disassembler in order to understand how it operates, assuming you have built the SSCLI source base appropriately. If not, you will need to follow the SSCLI instructions in order to get a checked (full debug, optimisations on) or fast checked (full debug info, optimisations off) build of SSCLI first. Your best bet for debugging would be the checked build.

In principle, building SSCLI is actually straightforward thanks to the excellent configuration tools and files used in the source tree. You need to first install a Perl 5.6 implementation, for example ActivePerl (see Reference 11). You also need Visual Studio.NET installed before you can proceed. Installing SSCLI itself is simply a matter of extracting the files from the compressed tarball (i.e. a gzip compressed tar file) they are supplied in. WinZip or WinRar should do the trick.

In a command prompt window change to the SSCLI root installation directory and setup the SSCLI environment. The env.bat batch file will do this, and it takes parameters to set up for checked, fast checked or free mode. Once set up, your command prompt environment will have a variety of environment variables pointing to parts of the SSCLI directory tree, including ROTOR_DIR, which does indeed point to the main installation directory.

To set up the environment for checked mode execute:


env checked


Next you invoke the build process by executing:


buildall.cmd


As you might expect, the build process can be rather lengthy, depending on your hardware, but it should get there in the end.

With SSCLI built you will find ildasm.exe in $(TARGETCOMPLUSSDK)\bin. TARGETCOMPLUSSDK is another environment variable, which is equivalent to $(ROTOR_DIR)\build\v1.x86chk.rotor\sdk in the checked environment.

You can set up a Visual Studio.NET solution for ildasm.exe as follows:





  1. Choose File | Open Solution...





  2. Change the Files of type: entry to Executable files (*.exe)





  3. Locate the SSCLI ildasm.exe in $(TARGETCOMPLUSSDK)\bin





  4. Select the Solution Explorer with View | Solution Explorer or (Ctrl+Alt+L)





  5. Right-click the solution node and choose Properties





  6. Select Common Properties node, then the Debug Source Files node and add in the path to the ildasm source and also to the common SSCLI include files:





  7. View the project properties with Project | Properties





  8. In the Configuration Properties, Debugging node set use the Command Arguments entry to set up the ildasm.exe command-line arguments to refer to the assembly you wish to disassemble





  9. Optionally set the Working Directory option to point to the directory housing the specified assembly





  10. Save the solution using File | Save ildasm.sln (or Ctrl+S)





  11. Close Visual Studio.NET





  12. Create a batch file in the directory containing the ildasm.sln file that looks like this, substituting your SSCLI directory path as appropriate:


    REM Modify this line depending on where SSCLI is installed
    call c:\Tools\sscli\env checked

    REM Modify this line depending on where Visual Studio.NET is installed
    start C:\Tools\VS.NET\Common7\IDE\devenv.exe ildasm.sln


Now you can double-click the batch file and Visual Studio.NET will be launched in an SSCLI checked mode environment and will load your solution. You can then start debugging it as you would with your normal applications:



Lutz Roeder's IL Reader Class


Another option for writing your own IL displaying utility would be to use a helper class made available by Lutz Roeder . The ILReader class and its selection of helper classes are supplied in a C# source file accompanied by an example program that shows them in use.

The ILReader class relies on the calling program having access to the type whose methods require disassembling. The constructor takes two parameters: the target type's module and a class implementing the locally defined IAssemblyLoader interface. This interface defines two methods that the ILReader calls in order to load the assembly that implements the target type, Load and LoadFrom. The example program defines the trivial AssemblyLoader class with simple implementations.

Having constructed an ILReader you call its GetMethodBody method for any method you need to disassemble. GetMethodBody takes a MethodBase descendant (such as MethodInfo), which can be accessed through Type.GetMethod or Type.GetMethods, and returns a MethodBody object whose members provide all the information you require.



The sample program disassembles the System.Object class.

Obfuscators


There is an argument that suggests that programmers entering into the .NET world are opening their applications up to the eyes of the world. The rich metadata along with the type rich IL code allows any managed code in an assembly to be disassembled back to readable IL and there is the worry about intellectual copyright issues with your algorithms on public display.

This is true enough; managed code can indeed be disassembled. However this is not a new problem. Java byte code can also be decompiled in much the same way and this did not impede the acceptance of the Java programming language. JavaScript code in Web pages that produces nice effects can be directly read. Even standard Win32 applications can be readily disassembled by various utilities. Of course, Intel x86 machine code is not as well structured and is more troublesome to correctly and completely disassemble, but the principle holds true.

IL is easier to disassemble because of the richness of the metadata in the assembly, which is there because it makes various internal operations much easier, not because it has to be. The general advice if this is an issue to you is to make use of an obfuscator.

Microsoft Visual Studio.NET 2003 ships with an obfuscator called Dotfuscator from PreEmptive Solutions (installed into PreEmptive Solutions\Dotfuscator Community Edition directory under the main Visual Studio installation directory). Dotfuscator ships in two versions, the Professional Edition and the Community Edition. The latter is the cut-down version that ships with Visual Studio.NET 2003 and basically renames your classes and methods with unhelpful choices of names, whilst the former has many additional features and comes with a price tag. You can find out more about Dotfuscator from the links at Reference 12.

Borland C#Builder ships with a version of another obfuscator called Demeanor for .NET from Wise Owl. This product holds a good reputation in obfuscation circles and you can find out more from the link at . Demeanor is available in an Enterprise Edition, which you pay money for, or the Personal Edition, as supplied with C#Builder. The Personal Edition does simple renaming of methods and classes whilst the Enterprise Edition has additional features that make it an attractive purchase.

Reconstituting The original Source



Viewing IL is all well and good, but despite its completeness, it is still not very readable. Various tools have surfaced which allow you to reconstitute high level statements from the IL in the assembly. They analyse the IL instruction sequences and use this information to build up a representation of the original source code in a given high-level language (some tools support this decompiling process with various high level language targets).

Anakrino / Exemplar


One of the first products to offer decompilation support was Exemplar by Jay Freeman (aka saurik). Exemplar is a command-line tool, which has been superseded by Anakrino, his GUI version (anakrino is a Greek word meaning to examine). You can find these tools at the URL listed in . The current version at the time of writing is 1.0.0.1.

Anakrino doesn't (currently) support drag and drop from Windows Explorer so you must open assemblies with the File | Open... menu item or by pressing Ctrl+O. Drilling down to a method allows you to see its high-level representation in either C# (by default) or managed C++ (if you select Dialect | MC++). Here is the Main method in our trivial test case displayed in C#.



By contrast, this is what it looks like in Managed C++:



This utility is very helpful, but has a number of "quirks". If you switch language (dialect) whilst a method is displayed, the decompiled version is not updated. You must select a method then reselect the previous method to see it decompiled into the new language. Also, as you can in the screenshots above, the bottom pane (coloured light yellow) seems to be unused at present. The equivalent panel in Reflector displays the methods signature/prototype.

When you download Anakrino, it includes the command-line Exemplar tool. You can use this to disassemble our test case assembly as shown here.



Moving onto the ArrayList method, we get much more interesting results here. Anakrino produces this listing as the C# source for the InsertRange method:


public virtual void InsertRange(int index, ICollection c) {
int local0;

if (c == null)
throw new ArgumentNullException("c", Environment.GetResourceString("ArgumentNull_Collection"));
if (index < 0 || index > this._size)
throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index"));
local0 = c.Count;
if (local0 > 0) {
this.EnsureCapacity(this._size + local0);
if (index < this._size)
Array.Copy(this._items, index, this._items, index + local0, this._size - index);
if (this == c.SyncRoot) {
Array.Copy(this._items, 0, this._items, index, index);
Array.Copy(this._items, index + local0, this._items, index * 2, this._size - index);
}
else
c.CopyTo(this._items, index);
this._size = this._size + local0;
this._version = this._version + 1;
}
}


As you can see this is much more meaningful that the pure IL code behind it. All we have lacking here is sensible names for the local variables along with any comments that might have helped us understand the intent of the code. However even without those you can see this is substantially easier to work with now.

Reflector


We have already looked at Reflector in the context of an IL disassembler but from version 3 it also offers high-level language decompilation support. The currently supported languages are C# and VB.NET (selectable in the Languages menu). Decompiling a selected method is much the same as disassembling it. Disassembly is attained by Tools | Disassembler (or Enter), whilst you decompile a method with Tools | Decompiler (or Space).

The results when decompiling the Hello World method are the same as with Anakrino. The following listing is what we get when decompiling ArrayList.InsertRange.


public virtual void InsertRange(int index, ICollection c)
{
int num1;
if (c == null)
{
throw new ArgumentNullException("c", Environment.GetResourceString("ArgumentNull_Collection"));
}
if ((index < 0) || (index > this._size))
{
throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index"));
}
num1 = c.Count;
if (num1 > 0)
{
this.EnsureCapacity((this._size + num1));
if (index < this._size)
{
Array.Copy(this._items, index, this._items, (index + num1), (this._size - index));
}
if (this == c.SyncRoot)
{
Array.Copy(this._items, 0, this._items, index, index);
Array.Copy(this._items, (index + num1),
this._items, (index * 2), (this._size - index));
}
else
{
c.CopyTo(this._items, index);
}
this._size = (this._size + num1);
this._version = (this._version + 1);
}
}


Again, much the same as Anakrino; the only differences are subtle and include:





  • a different local variable name (num1 instead of local0)





  • different brace layout conventions; Anakroni puts the opening brace at the end of a line of code whilst Reflector puts them on their own line, in the same column as the previous line of code started





  • explicit brackets around each condition, such as index < 0



LSW DotNet-Reflection-Browser


DotNet-Reflection-Browser is a commercial utility from Lesser-Software that offers reflection browsing and decompilation support in the style of a SmallTalk System Browser (see Reference 15). It has various other features that are not relevant here; for our purposes you should know that it decompiles to C# or MSIL (i.e. it decompiles and disassembles).



Running it against the InsertRange method produces this code:


public virtual void InsertRange (int index, ICollection c)
{
if (c == null)
{
throw new ArgumentNullException ("c", Environment.GetResourceString ("ArgumentNull_Collection"));
}

if ((index < 0) || (index > this._size))
{
throw new ArgumentOutOfRangeException ("index", Environment.GetResourceString ("ArgumentOutOfRange_Index"));
}

int i = c.Count;
if (i > 0)
{
this.EnsureCapacity ((this._size + i));
if (index < this._size)
{
Array.Copy (this._items, index, this._items, (index + i), (this._size - index));
}

if (this == c.SyncRoot)
{
Array.Copy (this._items, 0, this._items, index, index);
Array.Copy (this._items, (index + i), this._items, (index * 2), (this._size - index));
}
else
{
c.CopyTo (this._items, index);
}

this._size += i;
this._version++;
}

return;
}


Again it is much the same as we have seen before, although there a couple of slight differences:





  • Local variables are declared at the latest point (just before they are required) instead of right at the start





  • Variable incrementing is represented with the ++ operator





  • Explicit return statements are always used to exit methods, even at the end of the method implementation





  • explicit brackets around each condition, such as index < 0



Alternative (And Sometimes Identical) Source Implementations



As detailed earlier there are various implementations of the CLI of which Microsoft's .NET Framework is the most well known and successful. However Microsoft's Shared Source CLI is worth paying some close attention to. This implementation started its life from the same code base that .NET has grown from and, whilst parts of it have been changed for various reasons (such as to aid porting to other platforms or to help keep certain algorithms in the domain of the Microsoft engineers), much of it is still a direct representation of code in the commercial platform.

This is, of course, not true of the other non-Microsoft implementations, but it can still be very fruitful delving into their source trees.

SSCLI


Assuming the SSCLI has been installed into a directory referred to as $(ROTOR_DIR) the source for the ArrayList class can be found in $(ROTOR_DIR)\clr\src\bcl\system\collections\arraylist.cs. The method we are looking at is InsertRange, and the source code for it looks like this:


// Inserts the elements of the given collection at a given index. If
// required, the capacity of the list is increased to twice the previous
// capacity or the new size, whichever is larger. Ranges may be added
// to the end of the list by setting index to the ArrayList's size.
//
/// <include file='doc\ArrayList.uex' path='docs/doc[@for="ArrayList.InsertRange"]/*' />
public virtual void InsertRange(int index, ICollection c) {
if (c==null)
throw new ArgumentNullException("c", Environment.GetResourceString("ArgumentNull_Collection"));
if (index < 0 || index > _size) throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index"));
int count = c.Count;
if (count > 0) {
EnsureCapacity(_size + count);
if (index < _size) {
Array.Copy(_items, index, _items, index + count, _size - index);
}
// Hack hack hack
// If we're inserting a ArrayList into itself, we want to be able to deal with that.
if (this == c.SyncRoot) {
// Copy first part of _items to insert location
Array.Copy(_items, 0, _items, index, index);
// Copy last part of _items back to inserted location
Array.Copy(_items, index+count, _items, index*2, _size-index);
}
else
c.CopyTo(_items, index);
_size += count;
_version++;
}
}


Some key things to note about this code are:





  • it is exactly the same logic as decompiled by Anakrino, Reflector and LSW-DNRB





  • the local variable has a descriptive name





  • there are comments describing what goes on in the code



In cases where the SSCLI still uses the same code as .NET, any effort in working out the gaps left by the decompilers is completely avoided. It is usually worth decompiling with one of the tools, then comparing with the equivalent source in SSCLI to see if the shared source file can be used as a reference to the .NET implementation.

Mono


The Mono implementation is based on reading the CLI specification and implementing the described behaviour. Therefore you don't learn anything about the inner workings of .NET by browsing Mono source, but it does give you an option to see alternative ways to implement the behaviour as outlined in the ECMA CLI specification.

If the Mono 0.24 source files are installed in $(MONO) then the ArrayList source will be found in $(MONO)\mcs-0.24\class\corlib\System.Collections\ArrayList.cs. The InsertRange method implementation looks like this:


public override void InsertRange (int index, ICollection col) {
if (col == null)
throw new ArgumentNullException ();
if (index < 0 || index > Count)
throw new ArgumentOutOfRangeException ();
if (IsReadOnly || IsFixedSize)
throw new NotSupportedException ();

if (index == Count) {
foreach (object element in col)
list.Insert (index++, element);
}
}


As you can see, this is somewhat different in its approach, but does the same thing as far as the CLI class library specification goes.

Portable.NET


The situation with Portable.NET is much the same as with Mono with regard to its implementation. If Portable.NET 0.5.6 is installed in a directory referred to as $(PNET) then you can find the ArrayList source in $(PNET)\pnetlib-0.5.6\runtime\System\Collections\ArrayList.cs. The InsertRange method looks like this:


// Insert the contents of a collection as a range.

2.1191269304&ga_sid=1191269304&ga_hid=1507543749&flash=9&u_h=768&u_w=1024&u_ah=738&u_aw=1024&u_cd=32&u_tz=330&u_java=true" frameborder="0" width="250" scrolling="no" height="250" allowtransparency>


public virtual void InsertRange(int index, ICollection c)
{
int cCount;
IEnumerator enumerator;
if(c == null)
{
throw new ArgumentNullException("c");
}
if(index < 0 || index > count)
{
throw new ArgumentOutOfRangeException
("index", _("ArgRange_Array"));
}
cCount = c.Count;
Realloc(cCount, index);
enumerator = c.GetEnumerator();
while(enumerator.MoveNext())
{
store[index++] = enumerator.Current;
}
count += cCount;
++generation;
}




Reverse Engineering To Learn .NET Better

Technorati Tags: , , ,

Saturday, September 22, 2007

Steps To Deface A Webpage

First of all, I do not deface, I never have (besides friends sites as jokes and all in good fun), and never will. So how do I know how to deface? I guess I just picked it up on the way, so I am no expert in this. If I get a thing or two wrong I apoligize. It is pretty simple when you think that defacing is just replacing a file on a computer. Now, finding the exploit in the first place, that takes skill, that takes knowledge, that is what real hackers are made of. I don't encourage that you deface any sites, as this can be used get credit cards, get passwords, get source code, billing info, email databases, etc.. (it is only right to put up some kind of warning. now go have fun ;)


This tutorial will be broken down into 3 main sections, they are as followed:
1. Finding Vuln Hosts.

2. Getting In.
3. Covering Your Tracks

It really is easy, and I will show you how easy it is.

1. Finding Vuln Hosts


This section needs to be further broken down into two catigories of script kiddies: ones who scan the net for a host that is vuln to a certain exploit and ones who search a certain site for any exploit. The ones you see on alldas are the first kind, they scan thousands of sites for a specific exploit. They do not care who they hack, anyone will do. They have no set target and not much of a purpose. In my opinion these people should either have a cause behind what they are doing, ie. "I make sure people keep up to date with security, I am a messanger" or "I am spreading a political message, I use defacments to get media attention". People who deface to get famous or to show off their skills need to grow up and relize there is a better way of going about this (not that I support the ones with other reasons ether). Anyways, the two kinds and what you need to know about them:

Scanning Script Kiddie: You need to know what signs of the hole are, is it a service? A certain OS? A CGI file? How can you tell if they are vuln? What version(s) are vuln? You need to know how to search the net to find targets which are running whatever is vuln. Use altavista.com or google.com for web based exploits. Using a script to scan ip ranges for a certain port that runs the vuln service. Or using netcraft.com to find out what kind of server they are running and what extras it runs (frontpage, php, etc..) nmap and other port scanners allow quick scans of thousands of ips for open ports. This is a favorate technique of those guys you see with mass hacks on alldas.


Targetted Site Script Kiddie: More respectable then the script kiddies who hack any old site. The main step here is gathering as much information about a site as possible. Find out what OS they run at netcraft or by using: telnet www.site.com 80 then GET / HTTP/1.1 Find out what services they run by doing a port scan. Find out the specifics on the services by telnetting to them. Find any cgi script, or other files which could allow access to the server if exploited by checking /cgi /cgi-bin and browsing around the site (remember to index browse)
Wasn't so hard to get the info was it? It may take awhile, but go through the site slowly and get all the information you can.

2. Getting In

Now that we got the info on the site we can find the exploit(s) we can use to get access. If you were a scanning script kiddie you would know the exploit ahead of time. A couple of great places to look for exploits are Security Focus and packetstorm. Once you get the exploit check and make sure that the exploit is for the same version as the service, OS, script, etc.. Exploits mainly come in two languages, the most used are C and perl. Perl scripts will end in .pl or .cgi, while C will end in .c To compile a C file (on *nix systems) do gcc -o exploit12 file.c then: ./exploit12 For perl just do: chmod 700 file.pl (not really needed) then: perl file.pl. If it is not a script it might be a very simple exploit, or just a theory of a possible exploit. Just do alittle research into how to use it. Another thing you need to check is weither the exploit is remote or local. If it is local you must have an account or physical access to the computer. If it is remote you can do it over a network (internet).


Don't go compiling exploits just yet, there is one more important thing you need to know
Covering Your Tracks


So by now you have gotten the info on the host inorder to find an exploit that will allow you to get access.

So why not do it? The problem with covering your tracks isn't that it is hard, rather that it is unpredictable. just because you killed the sys logging doesn't mean that they don't have another logger or IDS running somewhere else. (even on another box). Since most script kiddies don't know the skill of the admin they are targetting they have no way of knowing if they have additional loggers or what. Instead the script kiddie makes it very hard (next to impossible) for the admin to track them down. Many use a stolden or second isp account to begin with, so even if they get tracked they won't get caught. If you don't have the luxery of this then you MUST use multiple wingates, shell accounts, or trojans to bounce off of. Linking them together will make it very hard for someone to track you down. Logs on the wingates and shells will most likely be erased after like 2-7 days. That is if logs are kept at all. It is hard enough to even get ahold of one admin in a week, let alone further tracking the script kiddie down to the next wingate or shell and then getting ahold of that admin all before the logs of any are erased. And it is rare for an admin to even notice an attack, even a smaller percent will actively pursue the attacker at all and will just secure their box and forget it ever happend. For the sake of arugment lets just say if you use wingates and shells, don't do anything to piss the admin off too much (which will get them to call authoritizes or try to track you down) and you deleting logs you will be safe. So how do you do it?
We will keep this very short and too the point, so we'll need to get a few wingates. Wingates by nature tend to change IPs or shutdown all the time, so you need an updated list or program to scan the net for them. You can get a list of wingates that is well updated at http://www.cyberarmy.com/lists/wingate/ and you can also get a program called winscan there. Now lets say we have 3 wingates:

212.96.195.33 port 23
202.134.244.215 port 1080
203.87.131.9 port 23
to use them we go to telnet and connect to them on port 23. we should get a responce like this:
CSM Proxy Server >
to connect to the next wingate we just type in it's ip:port
CSM Proxy Server >202.134.244.215:1080

If you get an error it is most likely to be that the proxy you are trying to connect to isn't up, or that you need to login to the proxy. If all goes well you will get the 3 chained together and have a shell account you are able to connect to. Once you are in your shell account you can link shells together by:
[j00@server j00]$ ssh 212.23.53.74

You can get free shells to work with until you get some hacked shells, here is a list of free shell accounts. And please remember to sign up with false information and from a wingate if possible.
SDF (freeshell.org) - http://sdf.lonestar.org
GREX (cyberspace.org) - http://www.grex.org
NYX - http://www.nxy.net
ShellYeah - http://www.shellyeah.org
HOBBITON.org - http://www.hobbiton.org
FreeShells - http://www.freeshells.net
DucTape - http://www.ductape.net
Free.Net.Pl (Polish server) - http://www.free.net.pl
XOX.pl (Polish server) - http://www.xox.pl
IProtection - http://www.iprotection.com
CORONUS - http://www.coronus.com
ODD.org - http://www.odd.org
MARMOSET - http://www.marmoset.net
flame.org - http://www.flame.org
freeshells - http://freeshells.net.pk
LinuxShell - http://www.linuxshell.org
takiweb - http://www.takiweb.com
FreePort - http://freeport.xenos.net
BSDSHELL - http://free.bsdshell.net
ROOTshell.be - http://www.rootshell.be
shellasylum.com - http://www.shellasylum.com
Daforest - http://www.daforest.org
FreedomShell.com - http://www.freedomshell.com
LuxAdmin - http://www.luxadmin.org
shellweb - http://shellweb.net
blekko - http://blekko.net

once you get on your last shell you can compile the exploit, and you should be safe from being tracked. But lets be even more sure and delete the evidence that we were there.

Alright, there are a few things on the server side that all script kiddies need to be aware of. Mostly these are logs that you must delete or edit. The real script kiddies might even use a rootkit to automaticly delete the logs. Although lets assume you aren't that lame. There are two main logging daemons which I will cover, klogd which is the kernel logs, and syslogd which is the system logs. First step is to kill the daemons so they don't log anymore of your actions.

[root@hacked root]# ps -def | grep syslogd
[root@hacked root]# kill -9 pid_of_syslogd
in the first line we are finding the pid of the syslogd, in the second we are killing the daemon. You can also use /etc/syslog.pid to find the pid of syslogd.
[root@hacked root]# ps -def | grep klogd
[root@hacked root]# kill -9 pid_of_klogd
Same thing happening here with klogd as we did with syslogd.


now that killed the default loggers the script kiddie needs to delete themself from the logs. To find where syslogd puts it's logs check the /etc/syslog.conf file. Of course if you don't care if the admin knows you were there you can delete the logs completely. Lets say you are the lamest of the script kiddies, a defacer, the admin would know that the box has been comprimised since the website was defaced. So there is no point in appending the logs, they would just delete them. The reason we are appending them is so that the admin will not even know a break in has accurd. I'll go over the main reasons people break into a box:

To deface the website. - this is really lame, since it has no point and just damages the system.
To sniff for other network passwords. - there are programs which allow you to sniff other passwords sent from and to the box. If this box is on an ethernet network then you can even sniff packets (which contain passwords) that are destine to any box in that segment.
To mount a DDoS attack. - another lame reason, the admin has a high chance of noticing that you comprimised him once you start sending hundreds of MBs through his connection.
To mount another attack on a box. - this and sniffing is the most commonly used, not lame, reason for exploiting something. Since you now how a rootshell you can mount your attack from this box instead of those crappy freeshells. And you now have control over the logging of the shell.

To get sensitive info. - some corperate boxes have alot of valueable info on them. Credit card databases, source code for software, user/password lists, and other top secret info that a hacker may want to have.
To learn and have fun. - many people do it for the thrill of hacking, and the knowledge you gain. I don't see this as horrible a crime as defacing. as long as you don't destroy anything I don't think this is very bad. Infact some people will even help the admin patch the hole. Still illegal though, and best not to break into anyone's box.


I'll go over the basic log files: utmp, wtmp, lastlog, and .bash_history
These files are usually in /var/log/ but I have heard of them being in /etc/ /usr/bin/ and other places. Since it is different on alot of boxes it is best to just do a find / -iname 'utmp'|find / -iname 'wtmp'|find / -iname 'lastlog'. and also search threw the /usr/ /var/ and /etc/ directories for other logs. Now for the explanation of these 3.


utmp is the log file for who is on the system, I think you can see why this log should be appended. Because you do not want to let anyone know you are in the system. wtmp logs the logins and logouts as well as other info you want to keep away from the admin. Should be appended to show that you never logged in or out. and lastlog is a file which keeps records of all logins. Your shell's history is another file that keeps a log of all the commands you issued, you should look for it in your $ HOME directory and edit it, .sh_history, .history, and .bash_history are the common names. you should only append these log files, not delete them. if you delete them it will be like holding a big sign infront of the admin saying "You've been hacked". Newbie script kiddies often deface and then rm -rf / to be safe. I would avoid this unless you are really freaking out. In this case I would suggest that you never try to exploit a box again. Another way to find log files is to run a script to check for open files (and then manually look at them to determine if they are logs) or do a find for files which have been editted, this command would be: find / -ctime 0 -print

A few popular scripts which can hide your presence from logs include: zap, clear and cloak. Zap will replace your presence in the logs with 0's, clear will clear the logs of your presence, and cloak will replace your presence with different information. acct-cleaner is the only heavily used script in deleting account logging from my experience. Most rootkits have a log cleaning script, and once you installed it logs are not kept of you anyways. If you are on NT the logs are at C:\winNT\system32\LogFiles\, just delete them, nt admins most likely don't check them or don't know what it means if they are deleted.
One final thing about covering your tracks, I won't go to into detail about this because it would require a tutorial all to itself. I am talking about rootkits. What are rootkits? They are a very widely used tool used to cover your tracks once you get into a box. They will make staying hidden painfree and very easy. What they do is replace the binaries like login, ps, and who to not show your presence, ever. They will allow you to login without a password, without being logged by wtmp or lastlog and without even being in the /etc/passwd file. They also make commands like ps not show your processes, so no one knows what programs you are running. They send out fake reports on netstat, ls, and w so that everything looks the way it normally would, except anything you do is missing. But there are some flaws in rootkits, for one some commands produce strange effects because the binary was not made correctly. They also leave fingerprints (ways to tell that the file is from a rootkit). Only smart/good admins check for rootkits, so this isn't the biggest threat, but it should be concidered. Rootkits that come with a LKM (loadable kernel module) are usually the best as they can pretty much make you totally invisible to all others and most admins wouldn't be able to tell they were comprimised.

In writting this tutorial I have mixed feelings. I do not want more script kiddies out their scanning

hundreds of sites for the next exploit. And I don't want my name on any shouts. I rather would like to have people say "mmm, that defacing crap is pretty lame" especially when people with no lives scan for exploits everyday just to get their name on a site for a few minutes. I feel alot of people are learning everything but what they need to know inorder to break into boxes. Maybe this tutorial cut to the chase alittle and helps people with some knowledge see how simple it is and hopefully make them see that getting into a system is not all it's hyped up to be. It is not by any means a full guide, I did not cover alot of things. I hope admins found this tutorial helpful aswell, learning that no matter what site you run you should always keep on top of the latest exploits and patch them. Protect yourself with IDS and try finding holes on your own system (both with vuln scanners and by hand). Also setting up an external box to log is not a bad idea. Admins should have also seen alittle bit into the mind of a script kiddie and learned a few things he does.. this should help you catch one if they break into your systems.

On one final note, defacing is lame. I know many people who have defaced in the past and regret it now. You will be labeled a script kiddie and a lamer for a long, long time.

Consuming Membership and Profile Services via ASP.NET AJAX

 

ASP.NET 2.0 introduced various application services—such as Membership, Roles, and Profiles—that eliminate a lot of coding that was required to provide the same functionality. However, these services are part of ASP.NET's server-side framework, which could pose a challenge when you use ASP.NET AJAX to consume the services from client-side JavaScript code. Fortunately, ASP.NET AJAX provides an out-of-the-box solution to this problem. This article explains how to use this solution in C# with Visual Studio.

Sample Scenario

Suppose you are developing a new web site and want to implement forms authentication. The web site will have a user registration page, a login page, and one or more pages that you must secure. The user registration and login pages use ASP.NET AJAX for an enhanced user experience. Also, the site must capture details such as birth date and address at the time of registration. This information is to be stored in the Profile of the user.

To develop a web site that fulfills all the above requirements, begin by creating a new ASP.NET AJAX-enabled web site with C# (see Figure 1).


Figure 1. Creating a New ASP.NET AJAX-enabled Web Site

Configuring the Web Site

Before you begin coding, configure the web site for forms authentication as well as Membership and Profile services. Open a web.config file in your Visual Studio IDE and add the following markup inside the connectionStrings section:

<connectionStrings>
   <add name="connstr"
        connectionString="data source=.\sqlexpress;
        initial catalog=northwind;
        integrated security=true"
        providerName="System.Data.SqlClient"/>
</connectionStrings>

You specified a database connection string named connstr that points to a Northwind database. Make sure to change the connection string to match your development environment. I assume that your database is configured for application services using the aspnet_regsql.exe tool. You will use this connection string while configuring membership and profile providers.

Now, add the following markup inside the system.web section:


<system.web>
<authentication mode="Forms">
   <forms loginUrl="Login.aspx"></forms>
</authentication>
<authorization>
   <deny users="?"/>
</authorization>
<membership defaultProvider="p1">
   <providers>
      <add name="p1"
           connectionStringName="connstr"
           type="System.Web.Security.SqlMembershipProvider"
           requiresQuestionAndAnswer="false"/>
   </providers>
</membership>
<profile defaultProvider="p2">
   <providers>
      <add name="p2"
           connectionStringName="connstr"
           type="System.Web.Profile.SqlProfileProvider"/>
   </providers>
   <properties>
      <add name="FullName"/>
      <add name="DOB" type="System.DateTime"/>
      <group name="Address">
      <add name="Street"/>
      <add name="Country"/>
      <add name="PostalCode"/>
      </group>
   </properties>
</profile>
 

Review the above markup carefully, and you'll notice the following:


  • The authentication section sets the authentication mode to Forms. The forms tag sets the URL of the login page by using the loginUrl attribute.
  • The authorization section disables anonymous users by setting the users attribute of the deny tag to "?".
  • The membership section configures a membership provider named p1. (You can change this any name you choose.)
  • The connectionStringName attribute specifies the database that will be used for storing membership information.
  • The type attribute indicates the class that will act as the membership provider. You use the built-in SQL Membership provider called SqlMembershipProvider.
  • The requiresQuestionAndAnswer attribute indicates that you do not intend to accept a secret question and answer from the end user at the time of registration.
  • The profile section configures a profile provider named p2 and various profile properties. The significance of the connectionStringname and type attributes is same as for the membership section. Note, however, that this time the type is a SqlProfileProvider class. The properties section defines profile properties and groups.
  • You defined two simple properties called FullName and DOB and a property group called Address. The Address group further contains three properties: street, country, and postalcode. The DOB property is of type DateTime; therefore, its type attribute is set to System.DateTime.

Now that you have configured your web site for using forms authentication and membership services, it's time to expose Membership and Profile services to the client-side AJAX code. The web.config file will have a pre-defined section called webServices. By default, all its content is commented. You need to un-comment and modify it so that it looks as shown below:



<webServices>
   <authenticationService enabled="true"
   requireSSL="false"/>
   <profileService enabled="true"
   readAccessProperties="FullName,DOB,Address.Street,Address.Country,
                         Address.PostalCode"
   writeAccessProperties="FullName,DOB,Address.Street,Address.Country,
                          Address.PostalCode"/>
</webServices>

The authenticationService tag is used to expose forms authentication and membership services to AJAX code. The enabled attribute governs whether AJAX code can avail membership services. The requireSSL attribute indicates whether the authentication is happening over SSL. Similarly, the Profile service is exposed to AJAX code by using the profileService tag. The readAccessProperties and writeAccessProperties attributes of the profileService tag specify the profile properties that are readable and writable, respectively. Notice how the grouped properties are specified using the dot (.) notion. If you do not include a specific profile property in these attributes, it will not be accessible to the client code.

Applying forms authentication ensures that all the forms of the web site except the login page are secured. However, you want your registration page to be unsecured because new users will need to access it. Do this by adding a location section in the web.config file as shown below:



<location path="register.aspx">
   <system.web>
      <authorization>
         <allow users="*"/>
      </authorization>
   </system.web>
</location>

The path attribute of the location tag specifies a virtual path of a file or folder that is to be configured. It then allows access to all the users using the authorization section and allow tag.

This completes the web site configuration. Now, you will move on to develop the required web forms.


User Registration

First of all, you will create the user registration page. Add a new web form named Registration.aspx. Drag and drop a ScriptManager control from the toolbox (see Figure 2).



Figure 2. Drag and Drop a ScriptManager Control

Also, drag and drop an UpdatePanel and UpdateProgress control on the web form. The UpdatePanel control represents a part of the total web form that can be refreshed without causing a post back of the entire form. The UpdateProgress control is used to display a progress message while the UpdatePanel is being refreshed.

Drag and drop a Label control inside the UpdateProgress control and set its Text property to "Please wait...". Also, set its AssociatedUpdatePanelID property to the ID of the UpdatePanel control. The AssociatedUpdatePanelID property links the UpdateProgress with an UpdatePanel.

Add a table into the UpdatePanel and design it as shown in Figure 3.


Figure 3. Design for Table in the UpdatePanel

The first column of the table contains Label controls that act as prompts for the textboxes. The second column of the table contains TextBox controls. Each TextBox control is validated by using a RequiredFieldValidator control. The TextMode property of the password and confirm password textboxes is set to Password. Similarly, the TextMode property of the street address textbox is set to MultiLine.

There is a Button called "Check Availability" that the end user can use to check the availability of a user ID. The "Check Availability" button will make an AJAX call to the web form to decide whether the specified user ID is available for registration. Set the OnClientClick property of the "Check Availability" button to "return CheckAvailability();" (CheckAvailability() is a client-side JavaScript function that you will write later). This function will call a web method to decide whether the user ID is available for registration. Finally, the Register button will create the user in the system with the help of the Membership features. The Label at the bottom is used for displaying success or error messages.

Now, go in the code behind of the Register.aspx and add a static web method called CheckAvailability. The following is the complete code of the method:



[WebMethod]
public static bool CheckAvailability(string uid)
{
   MembershipUser user = Membership.GetUser(uid);
   if (user == null)
   {
      return true;
   }
   else
   {
      return false;
   }
}

You might be wondering why you added a web method inside a web form. Remember that you have a "Check Availability" button that is supposed to check whether the specified user ID is available for registration. You will be making an AJAX call to do that. ASP.NET AJAX allows you to call web methods defined in web forms via an object called PageMethods. Therefore, you marked the CheckAvailability() method with a [WebMethod] attribute. Note that you must refer to the System.Web.dll and import the System.Web.Services namespace to use the [WebMethod] attribute.

The CheckAvailability() method accepts a user ID and returns true if that ID is available for registration. Inside, it calls the GetUser() method of the Membership object. The GetUser() method returns an instance of the MembershipUser class that represents the specified user. If it returns null, it indicates that the specified user doesn't exist and accordingly true or false is returned to the caller.

When the user clicks the Register button, you need to add user details in the membership and profile tables. Use the Membership and Profile objects to do this BECAUSE ASP.NET AJAX doesn't allow you to create users from client-side code. The following code shows the Click event handler of the Register button:



protected void Button1_Click(object sender, EventArgs e)
{
   try
   {
      MembershipUser user   = Membership.CreateUser
      (TextBox2.Text, TextBox3.Text, TextBox5.Text);
      ProfileCommon pc      = Profile.GetProfile(user.UserName);
      pc.FullName           = TextBox1.Text;
      pc.DOB                = DateTime.Parse(TextBox6.Text);
      pc.Address.Street     = TextBox7.Text;
      pc.Address.Country    = TextBox8.Text;
      pc.Address.PostalCode = TextBox9.Text;
      pc.Save();
      lblMsg.Text           = "User created successfully!";
   }
   catch (Exception ex)
   {
      lblMsg.Text = ex.Message;
   }
}

You call the CreateUser() method of the Membership object to create the user and pass user ID, password, and email. The CreateUser() method returns an instance of MembershipUser representing the newly created user. At this point, the user is not authenticated, so you cannot set the user's profile directly via the Profile object. Instead, you call the GetProfile() method of the Profile object. The GetProfile() method returns an instance of the ProfileCommon class. Through this instance, you set various profile properties. Once all the profile properties are saved, the Save() method of the ProfileCommon class is called to save profile information to the underlying database. A success message is then displayed in a Label control. Any exceptions during the registration process are captured BY using try-catch blocks and an error message is displayed in a Label control.

Now, code the client-side CheckAvailability() function. Switch to the HTML source view of the Register.aspx and add a script block in the HEAD section of the page. Then, add the following functions in the script block:



function CheckAvailability()
{
   var uid=document.getElementById('TextBox2').value;
   if(uid=="")
   {
      alert('Please enter user ID!');
      return false;
   }
   PageMethods.CheckAvailability(uid,OnComplete);
   return false;
}
 
function OnComplete(result)
{
   var lblMsg=document.getElementById('lblMsg');
   if(result)
   {
      lblMsg.innerText="The ID is available!";
   }
   else
   {
      lblMsg.innerText="The ID is unavailable!";
   }
}

The CheckAvailability() function retrieves the user ID textbox using the getElementById() method of the HTML DOM, which accepts the ID of an element and returns a reference to it. The code checks whether the user ID is empty and, if so, displays an error message. It then calls the CheckAvailability() web method via the PageMethods object and passes the specified user ID to it. The PageMethods object is a built-in object provided by ASP.NET AJAX that allows you to call web methods defined in web forms. The second parameter of the CheckAvailability() call is nothing but the name of another JavaScript function (OnComplete in this example) that gets called after the web method call completes. You may find this mechanism a bit odd, but remember that ASP.NET AJAX communication is always asynchronous. The OnComplete() function receives the return value of the web method as a result parameter. It then simply displays a success or error message in a Label control. Note that the CheckAvailability() JavaScript function returns false so that there won't be any post back.

This completes your registration page. To test it, run the Register.aspx in the browser and try creating new users. Also, check how the "Check Availability" button works. Figure 4 shows a sample run of the web form.


(
Full Size Image)

Figure 4. Sample Run of the Web Form

Developing a Login Page

Now that users can register themselves with the web site, you need to provide a facility that enables them to log in and access various pages. To do so, add a new web form called Login.aspx to the web site. Remember that you have set the loginUrl attribute of the forms tag to Login.aspx. Drag and drop a ScriptManager control on it and design the login page as shown in Figure 5 by assembling various controls.


Figure 5. The Login Page Design

As you can see, the login page consists of textboxes for entering a user ID and password. The "Remember Me" checkbox allows you to preserve your logged-in status even after closing the browser window. The TextMode property of the password textbox is set to Password. Further, the OnClientClick property of the Login button is set to "return BeginAuthenticateUser();". BeginAuthenticateUser() is a JavaScript function that uses the ASP.NET AJAX authentication service to authenticate the user. The following is the BeginAuthenticateUser() function:



function BeginAuthenticateUser()
{
   var uid;
   var pwd;
   var isPersistent;
   uid=document.getElementById('TextBox1').value;
   pwd=document.getElementById('TextBox2').value;
   isPersistent=document.getElementById('CheckBox1').checked;
   Sys.Services.AuthenticationService.login
   (uid,pwd,isPersistent,null,null,
   EndAuthenticateUser,OnError,uid);
   return false;
}

The BeginAuthenticateUser() JavaScript function retrieves the user IDs and passwords entered in their respective textboxes. It also retrieves the status of the "Remember Me" checkbox. ASP.NET AJAX provides a built-n class called AuthenticationService that resides in the Sys.Services namespace. Remember that the Sys.Services namespace is defined by the client-side framework of ASP.NET AJAX. The AuthenticationService class offers two methods: login() and logout(). The code above used the login() method, which takes in all eight parameters. Their significance is listed below:

Parameter Significance


  1. A user ID

  2. A password

  3. A boolean value indicating whether an authentication cookie will be persistent

  4. The web page where the user should be redirect after a successful login

  5. Reserved for future use

  6. A callback function that will be called after a successful login (EndAuthenticateUser in this example)

  7. A callback function that will be called in case a login attempt fails (OnError in this example)

  8. A custom value that is passed to the callback functions

If the user is successfully authenticated, the EndAuthenticateUser function will be called. The following is the EndAuthenticateUser function:



function EndAuthenticateUser(result,userContext,methodName)
{
   if(result)
   {
      window.location='default.aspx';
   }
   else
   {
      alert("Unable to login! Please check user id and password!!");
   }
}

The EndAuthenticateUser() function takes three parameters: the result of the login operation, the user context that you passed earlier in the eighth parameter of the login() method, and the method name. Inside, it checks whether the result is true (in other words, the user is successfully authenticated) and, if so, it sets the location property of the windows object to default.aspx. This way, the user is redirected to the default page after a successful login attempt. If there is any error, an error message is displayed using the alert() function.

The OnError() function is called whenever an error occurs when calling the authentication service. This function is shown below:

function OnError(result,userContext,methodName)
{
alert(result.get_message());
}

The function simply displays an error message to the user. The result parameter received is actually an object and has a method called get_message() that returns a descriptive error message.

This completes the login page.

Implementing Logout Functionality

Add another web form called Default.aspx. This web form will allow users to logout and manage their profiles. Firstly, you will implement logout functionality. Drag and drop a ScriptManager control on the Default.aspx and design the web form as shown in Figure 6.



Figure 6. The Web Form Design

The web form consists of a couple of Label controls to display a welcome message to the user. The Logout button allows the user to delete the authentication cookie, thus logging him out. The OnClientClick property of the Login button is set to "return BeginLogOut();" (BeginLogout() is a JavaScript function that you will write later). The "Show My Profile" button toggles the profile panel. The OnClientClick property of the "Show My Profile" button is set to "return BeginProfileLoad();" (you will create the BeginProfileLoad() function later). The profile panel consists of a Panel control containing textboxes to display and show profile property values. It also contains the "Save Profile" button for saving changes made to the profile values. The OnClientClick property of the "Save Profile" button is set to "return BeginSaveProfile();" (the BeginSaveProfile() function will be coded later).

In the Page_Load event of the web form, you set the welcome label to the ID of the user. The following code shows how:

protected void Page_Load(object sender, EventArgs e)
{
Label4.Text = Membership.GetUser().UserName;
}

The code simply retrieves the user name of the current user and assigns it to the label. Note that the GetUser() method of the Membership object returns an object of type MembershipUser. The UserName property of the MembershipUser class is then called. You need to display the user name from the server-side code because ASP.NET AJAX doesn't provide any way to retrieve it via client-side code.

Now, switch to the HTML source view of Default.aspx and write the BeginLogOut() and EndLogOut() JavaScript functions as shown below:



function BeginLogOut()
{
   Sys.Services.AuthenticationService.logout
   (null,EndLogout,OnError,null);
   return false;
}
 
function EndLogout(result)
{
   //nothing here
}

The BeginLogOut() function again uses the AuthenticationService class. This time, it calls the logout() method of AuthenticationService. The logout() method takes the following four parameters:


As before, the BeginLogOut() function returns false so that there is no post back. The EndLogOut() function doesn't perform any action in this example.

Reading Profile Properties

Initially, the profile panel should be hidden from the end user. This is done in the pageLoad() JavaScript function. Note that the pageLoad() function is called by the ASP.NET AJAX framework when the page loads in the browser. You can think of it as a client-side counterpart of server-side Page_Load event. The pageLoad() function is shown below:

function pageLoad()
{
var panel3=document.getElementById('Panel3');
panel3.style.visibility="hidden";
}

The pageLoad() function simply sets the visibility property of the style object to hidden, thus hiding the profile panel.

When you click on the "Show My Profile" button, the profile panel needs to be displayed with the profile property values filled in. The BeginProfileLoad() function does this job:



function BeginProfileLoad()
{
   if(event.srcElement.value=="Show my profile")
   {
      var panel3=document.getElementById('Panel3');
      panel3.style.visibility="visible";
      event.srcElement.value="Hide my profile";
      Sys.Services.ProfileService.load
      (null,EndProfileLoad,OnProfileFailed, null);
   }
   else
   {
      var panel3=document.getElementById('Panel3');
      panel3.style.visibility="hidden";
      event.srcElement.value="Show my profile";
   }
   return false;
}

The BeginProfileLoad() function toggles visibility of the profile panel. If the profile panel is to be displayed, then you must populate various textboxes with profile values. The ASP.NET AJAX framework provides a class called ProfileService that allows you to work with profile properties. The load() method of the ProfileService class loads profile property values. The load() method takes four parameters, which do the following:

Parameter
Significance


  1. An array of property names that are to be loaded. If you have too many profile properties, then it makes sense to load the ones that you really want to use. This will improve the performance of your page.

  2. A callback function that will be called when the load operation is completed

  3. A callback function that will be called if the load operation fails

  4. Custom context information, if any

Once the profile is loaded the EndProfileLoad() function is called. This is the EndProfileLoad():



function EndProfileLoad(numProperties, userContext, methodName)
{
   document.getElementById('TextBox3').value =
      Sys.Services.ProfileService.properties.FullName;
   document.getElementById('TextBox4').value =
      Sys.Services.ProfileService.properties.DOB;
   document.getElementById('TextBox5').value =
      Sys.Services.ProfileService.properties.Address.Street;
   document.getElementById('TextBox6').value =
      Sys.Services.ProfileService.properties.Address.Country;
   document.getElementById('TextBox7').value =
      Sys.Services.ProfileService.properties.Address.PostalCode;
}

The EndProfileLoad() function receives three parameters: number of properties that are loaded, context information that is supplied to the load() function, and method name. It then populates the textboxes with the profile property values. The ProfileService class exposes profile properties via the properties collection. Remember that only the properties specified in the readAccessProperties attribute of the profileService tag of web.config are exposed. The properties and groups are accessed with the familiar dot (.) notion.

Modifying Profile Properties

When profile property values are displayed in various textboxes, the user can change them and click on the "Save Profile" button. Clicking on the "Save Profile" button calls the BeginSaveProfile() function, which is shown here:



function BeginSaveProfile()
{
   Sys.Services.ProfileService.properties.FullName=
      document.getElementById('TextBox3').value;
   Sys.Services.ProfileService.properties.DOB=
      document.getElementById('TextBox4').value;
   Sys.Services.ProfileService.properties.Address.Street=
      document.getElementById('TextBox5').value;
   Sys.Services.ProfileService.properties.Address.Country=
      document.getElementById('TextBox6').value;
   Sys.Services.ProfileService.properties.Address.PostalCode=
      document.getElementById('TextBox7').value;
   Sys.Services.ProfileService.save
   (null,EndSaveProfile,OnProfileFailed,null);
   return false;
}

The code again uses the ProfileService class and its properties collection to assign new values. Once all the profile properties are set, it calls the save() method of the ProfileService class. The save() method takes the same four parameters as the load() method (in other words, the array of properties to write, the callback function to be called after successful saving, the callback function to be called after unsuccessful save, and custom context information). The EndSaveProfile() function simply displays a message box to the user:

function EndSaveProfile(numProperties, userContext, methodName)
{
alert('Your profile is saved successfully!');
}

That's it! You just completed your AJAX-driven membership and profile pages. You now can log in to the web site and test the profile features. Figure 7 shows a sample run of the default.aspx.


Figure 7. A Sample Run of the Default.aspx

Consuming Membership and Profile Features from the Client Side

ASP.NET AJAX provides a handy way to consume membership and profile features. To consume these features, you need to enable them by using the authenticationService and profileService tags of web.config. Once enabled, you can use the AuthenticationService and ProfileService classes to consume them.

BACKLINK