Note: This quite old and was written for .net 2.0 beta1 or 2, don't know if things have changed since then. Further info might be found here

Introduction

This article is meant to provide a quick starting point on how to use ICeeFileGen to generate CLI modules.
ICeeFileGen is exported from mscorpe.dll, the corresponding header file is ICeeFileGen.h and all this comes with the .NET Framework SDK.
Unfortunately - besides the header file - there is no documentation provided. For me, the most helpful resource was Benny/29A's article on "MSIL-PE-EXE infection strategies" and the source to his I-Worm.Serotonin. For a good part the code I provide here in part I is just a translation from his assembler code into c++.
Another indispensible sources of information is the shared source CLI (rotor) source code
Note: I assume you know how to use the unmanaged assembly metadata API. This is well documented in the CLI specification and the Framework SDK.
A great starter is this talk (pdf) by John Lam.
For the following example we'll assume you've opened a module with the methods provided by the unmanaged metadata API like IMetaDataDispenserEx and IMetaDataImport. We will now create a new module with the facilities provided by ICeeFileGen and copy all (?) the relevant metadata info from the original module into the newly created one. Of course you could use this method just as well to create entirely new modules or you could remove/add/change implementation details in existing ones.

Alternatives

Depending on what you want to do, other facilities provided by .NET might me more appropiate. For example the profiling api lets you rewrite IL code at runtime. Or have a look the System::CodeDom::Compiler namespace. Here is a cool 'tool' using this technique that lets you write ASP.NET in assembler :)

The code

The following is not meant to be a complete example but rather just the interesting bits you need to get started. As usual, error checking has been removed for better readability. Clean up code is also omitted

The basic steps are:
//first create a ICeeFileGen object

typedef HRESULT (CALLBACK* LPFNCREATEICEEFILEGEN)(ICeeFileGen **);

HRESULT hr = NULL;
HINSTANCE hDLL = NULL; 
LPFNCREATEICEEFILEGEN pCreateICeeFileGen = NULL;  
//mscorpePath contains the path to mscorpe.dll
hDLL = LoadLibrary(mscorpePath);

//mscorpe exports two functions, CreateICeeFileGen and DestroyICeeFileGen
//you'll want to call DestroyICeeFileGen when you're done
pCreateICeeFileGen = (LPFNCREATEICEEFILEGEN) GetProcAddress(hDLL, "CreateICeeFileGen");

//call the exported function
pCreateICeeFileGen(&_pCeeFileGen);
	
//now that we have a valid pointer to an ICeeFileGen we can use the 
//methods defined in ICeeFileGen.h

_pCeeFileGen->CreateCeeFile(&_hCeeFile);
_pCeeFileGen->SetOutputFileName(_hCeeFile, L"c:\\test.exe");

//call link so we can use GetSectionRVA
_pCeeFileGen->LinkCeeFile(_hCeeFile);

//get il section
HCEESECTION hILSection;
_pCeeFileGen->GetIlSection (_hCeeFile, &hILSection);
ULONG rva;
_pCeeFileGen->GetSectionRVA (hILSection, &rva);

ULONG codeSize = 0;
char* pILSectionBlock= NULL;

//get a new block to put the IL Code into 
GetILCodeSize(codeSize);
_pCeeFileGen->GetSectionBlock (hILSection, codeSize, sizeof(DWORD), 
			reinterpret_cast<void**>(&pILSectionBlock));

//get the offset so we can call IMetaDataEmit::SetRVA for each method
unsigned int offset;
_pCeeFileGen->ComputeSectionOffset(hILSection, pILSectionBlock, &offset);

//this will copy all IL code into a byte buffer _pILBuffer
//see below, we provide the RVA of the IL section plus the offset within the section
GetILCode(rva + offset);

//copy the IL code into the new section. 
memcpy(pILSectionBlock, _pILBuffer, codeSize);

//emit the metadata by providing a pointer to the IMetaDataEmit
// we recieved from calling IMetaDataDispenser::OpenScope() earlier
_pCeeFileGen->EmitMetaDataEx(_hCeeFile, _pMetaDataEmit);

_pCeeFileGen->LinkCeeFile(_hCeeFile);
_pCeeFileGen->GenerateCeeFile(_hCeeFile);
_pCeeFileGen->DestroyCeeFile(&_hCeeFile);
Now the IL Code copying code. Here I already have all TypeDefs and MethodDefs in std::vectors so I can just loop over them. Alternatively you could call the IMetaDataImport enum methods here.
You would then call GetMethodProps to get the RVA of each methods implementation. You can translate the RVA into a real address by calling ImageRvaToVa32/ImageRvaToVa64 in imagehlp.lib.
This image shows how the memory is aligned in an IL code section.
You will also need a function that calculates the accumulated size of the IL code so that you can call GetSectionBlock with it as the size parameter. The 'hack' used by the I-Worm.Serotonin (and also here until recently) doesn't work in all cases.
HRESULT GetILCode(unsigned int offset) {
   HRESULT hr = NULL;
   int bufferSize = GetILCodeSize();
   _pILBuffer = new BYTE[bufferSize];
   ZeroMemory(_pILBuffer, bufferSize);

    int codeSize = 0;
   //get all method of all types and add ILCode to buffer
   std::vector<CTypeDef*> typeDefs = _pReader->_typeDefs;
   for (TypeDefIT ii = _pReader->_typeDefs.begin(); ii != _pReader->_typeDefs.end(); ++ii) {
      CTypeDef* pType = *ii;
      for (MethodDefIT jj = pType->_methodDefs.begin(); jj != pType->_methodDefs.end(); ++jj) {
         CMethodDef* pMethod = *jj;
         if (pMethod->_codeSize > 0) {
            int methodSize = pMethod->_codeSize + pMethod->_codeHeaderSize;
            memcpy(_pILBuffer + codeSize, pMethod->_pCodeHeader, methodSize);
            
            hr = _pMetaDataEmit->SetRVA(pMethod->_token, offset + codeSize);  
            //HACK
             if (pMethod->_name == L"Main") {
               hr = _pCeeFileGen->SetEntryPoint(_hCeeFile, pMethod->_token);
            }
            codeSize += methodSize;
            codeSize = _ALIGN4(codeSize);
             if (pMethod->_pStartExtraSections) {
               memcpy(_pILBuffer + codeSize, pMethod->_pStartExtraSections, 
						pMethod->_sizeExtraSections);
               codeSize += pMethod->_sizeExtraSections;
               codeSize = _ALIGN4(codeSize);
            }
         }
      }
   }
   return hr;
}
Well, this is enough to re-assemble simple "hello world" programs. (Hint: you can use peverify.exe to check that the files you create are valid PE files). However in the real world™ things are more complicated. For example, so far we just drop all embedded resources that might have been in the original file! More on that and other shortcomings in part II (if i get around writing it up)
Part II >>