(

.Net Unpacking

Data

by "Pnluck"

 

15/04/2006

UIC's Home Page

Published by Quequero

Un uomo non può essere sconfitto, può essere distrutto ma non sconfitto

Grassie pn!

Mai arrendersi, mai arrendersi alla forza apparentemente schiacciante del nemico (churchill)

....

Home page (se presente): Pnluck's Home
E-mail: Pnluck's Mail
Pnluck, UIN, #crack-it, #pmode,#cryptorev

....

Difficolt�

( )NewBies (X)Intermedio ( )Avanzato ( )Master

 


Introduzione

Presto i programmi .NET si diffonderanno a causa della semplicità e della potenza di questo Framework, ma cmq c'è un problema non indifferente: è possibile vedere direttamente il codice sorgente di un programma. Come al solito per ogni problema c'è una soluzione: la nascita di packer (anche di crypter e protector) per .NET, ma questo causa un problema a noi reverser, ma cmq detto prima...

Tools usati

C'è lo creeremo noi, ma cmq ho usato WinHex(!google) e il Cff per un primo approcio manuale all'unpacking

URL o FTP del programma

Un programma .NET qualsiasi packato o questo unpackme

Essay

Per prima cosa unpackiamo manualmente questo crackme solo per mostrarvi la tecnica di generic unpacking, inventanta dal mitiko Ntoskrnl. Facciamo partire il crackme, carichiamolo nel WinHex ed andiamo alla ricerca della stringa UNICODE: "Assembly Version", la seconda volta che la trovate (la prima stringa appartiene al packer) fate destro e poi "end of block", e poi risalite fino al "MZ": qui fate di nuovo destro e poi "beginning of code". Ora dumpiamo in questo modo destro -> Edit -> copy block -> into new file and save the file. Non chiudete nulla, xkè il programma nonj è interamente dumpato. Aprite il dump col Cff e ricavatevi il size originale del file, poi nel WinHex impostate "MZ" come l'inizio del blocco e poi aggiungete al suo addr il size da voi ricavato, lì fate end of block e dumpate.
Come avete visto, dumpare un programma .NET packato è una sciocchezza, creare un generic unpacker ancora di più: eccovi il source

 

typedef struct _METADATA_SECTION
{
	DWORD Signature; 
	WORD MajorVersion;
	WORD MinorVersion;
	DWORD Reserved;
	DWORD Lenght;
	char Version[0xC];
	WORD Flags;
	WORD NumberOfStreams;
	//STREAM_HEADERS per gli stream bisogna definire una classe invece di una struttura
} METADATA_SECTION, *PMETADATA_SECTION;


//the definition of the class: 


class Cndd
{
private:
   //for the class
   TCHAR FileName[260];


   //for Debug Mode
   HANDLE hProc;
   STARTUPINFO sI;
   PROCESS_INFORMATION pI;


   //PE
   HANDLE hFile;
   BYTE *BaseAddress;
   DWORD FileSize, BR;
   IMAGE_DOS_HEADER *ImageDosHeader;
   IMAGE_NT_HEADERS *ImageNtHeaders;
   IMAGE_SECTION_HEADER *ImageSectionHeader;
   TCHAR bo[30];
   
   //PE .net
   IMAGE_COR20_HEADER *ImageNetDirectory;
   METADATA_SECTION   *MetadataSection;
   DWORD NetDir, MetaDir;


   //for Region;
   LPVOID		lpMem;
   MEMORY_BASIC_INFORMATION mbi;
   DWORD ImgBase;


public:
	//var
	bool isNet;


	//Funzioni Principali 
	Cndd(TCHAR *FileName);  //open the software
	bool RunApp(CList *obj);          //debug

  //Debug
	 bool SysBpWait(DEBUG_EVENT &DebugEv);
	 bool WaitExit(DEBUG_EVENT &DebugEv); 

	 //To find Domains
	 int GetRegionList();       //get domains
	 int GetDumpSize(BYTE *BaseAddres);          

};

//Controlliamo se � un .NET

Cndd::Cndd(TCHAR *FileName)
{
	wcscpy(this->FileName,FileName);

	hFile = CreateFile(this->FileName, GENERIC_READ, FILE_SHARE_READ, 0,
         OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

      if (hFile == INVALID_HANDLE_VALUE)
      {
         isNet = false;
         return;
      }

      FileSize = GetFileSize(hFile, NULL);

      BaseAddress = (BYTE *) malloc(FileSize);

	  if (!ReadFile(hFile, BaseAddress, FileSize, &BR, NULL))
      {
          free(BaseAddress);
          CloseHandle(hFile);
	      isNet = false;
          return;
      }

      ImageDosHeader = (IMAGE_DOS_HEADER *) BaseAddress;

      // Dos Header
      if (ImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
      {
         isNet = false;
         free(BaseAddress);
         CloseHandle(hFile);
         return ;
      }

   ImageNtHeaders = (IMAGE_NT_HEADERS *)
      (ImageDosHeader->e_lfanew + (DWORD) ImageDosHeader);

   //PE Header
   if (ImageNtHeaders->Signature != IMAGE_NT_SIGNATURE)
   {
      isNet = false;
      free(BaseAddress);
      CloseHandle(hFile);
      return ;
   }
   
  
   //offset of .NET in data directory
   NetDir = RvaToOffset(ImageNtHeaders,
	   ImageNtHeaders->OptionalHeader.DataDirectory
	   [IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].VirtualAddress);

   if (NetDir == NULL )
   {
      isNet = false;
      free(BaseAddress);
      CloseHandle(hFile);
      return;
   }

   //get .net struct
   ImageNetDirectory = (IMAGE_COR20_HEADER*) ( NetDir + (DWORD)BaseAddress );

   //offset of MetaData dir
   MetaDir = RvaToOffset(ImageNtHeaders,
	   ImageNetDirectory->MetaData.VirtualAddress);
 
   //get .NET's MetaData
   MetadataSection = (METADATA_SECTION*) ( MetaDir + (DWORD)BaseAddress );

   if(MetadataSection->Signature != 0x424A5342)
   {
	  isNet = false;
      free(BaseAddress);
      CloseHandle(hFile);
      return;
   }

   //It is useful to find domains
   int x = ImageNtHeaders->FileHeader.NumberOfSections -1;
   ImageSectionHeader = IMAGE_FIRST_SECTION(ImageNtHeaders);
   ImgBase = ImageNtHeaders->OptionalHeader.ImageBase + ImageSectionHeader[x].VirtualAddress+ ImageSectionHeader[x].SizeOfRawData;
   
   //ok is progie .NET
   isNet = true;

   //i close all
   free(BaseAddress);
   CloseHandle(hFile);  
}

//If the software is a .NET, we can start to debug it 
//Debug function
bool Cndd::RunApp()
{
	ZeroMemory( &sI, sizeof(sI) );
    sI.cb = sizeof(sI);
    ZeroMemory( &pI, sizeof(pI) );

	if(!CreateProcess(
        this->FileName,
        NULL,NULL,NULL,
        true,
        DEBUG_PROCESS,
        NULL,NULL,
        &sI,&pI)) return false;

    hProc = pI.hProcess;

	DEBUG_EVENT DebugEv;

	//i wait the system bp
	if(!this->SysBpWait(DebugEv)) return false;
   
	//and now the ExitProcess
	if(!this->WaitExit(DebugEv)) return false;

    return true;
}

//On Exit Process I'll try to find all domains of the debugged app. 
....
else if(DebugEv.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)
		{
			dwContinueStatus = DBG_CONTINUE;
			isExit = false;

			//get domains
			this->GetRegionList();
		}
....


int Cndd::GetRegionList()
{
    //for output
	TCHAR Saddr[10];

    //how find
	TCHAR howfind[] = L"Assembly Version";
	DWORD Mz = 0X00905A4D;

    //some var
	TCHAR readbyte[33];
	TCHAR output[40];
	BYTE *Environment,*save;
	DWORD jnk,secure_find, sottrai;

     LPVOID MaxMem = (LPVOID)0x10000000; //top of memory where search
     lpMem = (LPVOID)ImgBase;    //where start to search

	 while(lpMem < MaxMem)
	 {
		 //get info for the region
		 VirtualQueryEx(hProc,lpMem,&mbi,sizeof(MEMORY_BASIC_INFORMATION));

		//if infos are correct, I go up
		 if(mbi.State != MEM_RESERVE && mbi.RegionSize > 0x2000)
		 {
			 //Alloc  memory
			 Environment = (BYTE*) VirtualAlloc(NULL,mbi.RegionSize,MEM_COMMIT,PAGE_READWRITE);
			 //save address of alloced memory
			 save = Environment;

			 //if I can't read it, I continue
			 if(!ReadProcessMemory(hProc,lpMem,Environment,mbi.RegionSize,&jnk)) 
			 {
				 VirtualFree(save, 0, MEM_RELEASE);
				 lpMem = (LPVOID) ((DWORD)mbi.BaseAddress + (DWORD)mbi.RegionSize);
				 continue;
			 }

			 //I try to find the magic word
             secure_find = ((DWORD)Environment + mbi.RegionSize)-60;

			 while((DWORD)Environment < secure_find)
			 {
				 memcpy(&readbyte,Environment,32);

				 if(!memcmp(readbyte,howfind,32))
				 {
					//If I find it, I try to find the dos signature
					sottrai = Find(Mz,save,(DWORD)mbi.RegionSize,4);
					if(sottrai != 0)
					 {
				         //now i try to dump
						 swprintf(Saddr,9,L"%08X",(DWORD)mbi.BaseAddress);
						 wcscpy(output,Saddr);
						 wcscat(output,L".exe");
				         hFile = CreateFile(output,GENERIC_WRITE, FILE_SHARE_WRITE, 0,CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);

                         //get dos signature address
						 sottrai -= (DWORD)save ;
                         Environment = save + sottrai;
                         
						 //get domains' size
                         sottrai = this->GetDumpSize(Environment);

						 if(sottrai == 0) sottrai = (DWORD)mbi.RegionSize;   //for security

						 //and i make a dumop =)
				         WriteFile(hFile,Environment,sottrai,&jnk,NULL);   
				         CloseHandle(hFile);
				         
				     }
					 break;
				 }
				 Environment++;
			 }

			 VirtualFree(save, 0, MEM_RELEASE);
		  }

		 lpMem = (LPVOID) ((DWORD)mbi.BaseAddress + (DWORD)mbi.RegionSize);
	 }

return 1;
}

//Now some useful function: 
int Cndd::GetDumpSize(BYTE *BaseAddres)
{
	ImageDosHeader = (IMAGE_DOS_HEADER *) BaseAddres;

	// Dos Header
   if (ImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
   {
      isNet = false;
      free(BaseAddress);
      CloseHandle(hFile);
      return 0;
   }

   ImageNtHeaders = (IMAGE_NT_HEADERS *)
      (ImageDosHeader->e_lfanew + (DWORD) ImageDosHeader);

   // PE Header
   if (ImageNtHeaders->Signature != IMAGE_NT_SIGNATURE)
   {
      isNet = false;
      free(BaseAddress);
      CloseHandle(hFile);
      return 0;
   }
   
   //get last section
   int x = ImageNtHeaders->FileHeader.NumberOfSections -1;
   ImageSectionHeader = IMAGE_FIRST_SECTION(ImageNtHeaders);

   //ret the size of dump
   return  ImageSectionHeader[x].PointerToRawData + ImageSectionHeader[x].SizeOfRawData;
}

//find a dword in a memory block
DWORD Find(DWORD how_find, BYTE* Environment, DWORD E_space, DWORD sizebytestofind) 
{
	DWORD isfind = 0;
    DWORD max_addr;
	DWORD readbyte;

	max_addr = ((DWORD)Environment + E_space)-60; //for security

			 while((DWORD)Environment < max_addr)
			 {
				 memcpy(&readbyte,Environment,sizebytestofind);

				 if(!memcmp(&readbyte,&how_find,sizebytestofind))
				 {
					 isfind = (DWORD)Environment;
					 break;
				 }
				 Environment++;
			 }
return isfind;
}

//Now an Example on how you can used this class: 

//open the file
	try {
	Cndd *NetExe = new Cndd(ofn.lpstrFile);
	} 
	catch(...)
	{
		MessageBox(0,L"Bad Alloc",L"Error",0);
        //exit
	} 

	//is a .net?
	if(NetExe->isNet != true)
	{
		MessageBox(0,L"This file isn't a .NET executable",L"Error",0);
		//exit
	}
	
	//Start to debug the progie
	if(!NetExe->RunApp(ListBox)) 
    {
		MessageBox(0,L"Error on Running",L"Error",0);
		//exit
    }
//exit
		
Tutto in C++, bello vero =)

Il mio Net Domains Dumper lo potete trovare nella sezione download della uic o sulla mia home o su quella dei PMODE
                                                                                                                Pnluck

Note finali

Ringrazio Nt(perkè mi ha spiegato lui la tecnica, ed anche xkè ha fatto anche lui un net unpacker, solo che il mio è un pò mejo), Quake che mi ha svegliato dal sonno in cui dormivo, tutti i ragazzi del chan e del forum, e perchè no tutti quei bastardi che dicono che sul mio sito ci sono i virus.

Disclaimer

Vorrei ricordare che il software va comprato e  non rubato, dovete registrare il vostro prodotto dopo il periodo di valutazione. Non mi ritengo responsabile per eventuali danni causati al vostro computer determinati dall'uso improprio di questo tutoria e del mio codice(soprattutto di ques'utlimo). Questo documento � stato scritto per far diffondere più info possibili sulle protezioni che le software house offrono per i programma .NET

Reversiamo al solo scopo informativo e per migliorare la nostra conoscenza della programmazione.