THMag 04/Packeur et unpackeur : une autre vision

Un article de HackaWiki.

Packeur

Le programme principal se résume à :

BOOL CPackeurApp::InitInstance()
{
 CWinApp::InitInstance();

C_Chapeau   *pt_chapeau = new C_Chapeau;
 pt_chapeau->Full_Encodage();
 delete pt_chapeau;
 return FALSE;
}

Si l’on regarde l’enchaînement de la fonction « Full_Encodage » on retrouve la description faite plus avant :

int C_Chapeau::Full_Encodage()
{
 // Assignation et vérification des fichiers en entrée
 int status = Assigned_File();
 if (status == TRUE)
  {
	char *pt_out;
	DWORD count_out;
	pt_G3D = new G3D;
	Ouvre_fichier_lecture(output_file_ended);
	count_out = Compresse_Buffer((char *) m_pFileData, &pt_out);
	// ".g3d"
	Transforme(0,&pt_G3D->buffer_local[0]);
	// "Inject_"
	Transforme(1,&pt_G3D->buffer_local_1[0]);
	// "%s%s%s"
	Transforme(2,&pt_G3D->buffer_local_2[0]);
	// "rb"
	Transforme(3,&pt_G3D->buffer_local_3[0]);
	// "wb+"
	Transforme(4,&pt_G3D->buffer_local_4[0]);
	pt_G3D->Work(input_file_to_encoded, pt_out, count_out);
	delete pt_G3D;
  }
}

Les chaînes de caractères n’apparaissent pas en clair dans l’exécutable, elles sont encodées. La fonction « Transforme » réalise le décodage juste avant leur utilisation. L’encodage est très simple et ne met en œuvre aucun algorithme particulier juste un masque (définie par un define) et un complément à FFh. L’initialisation d’une chaîne caractère se résume donc à :

[…]
// "wb+"
 table_constante[4][0] = (char)~('w' + MY_KEY);
 table_constante[4][1] = (char)~('b' + MY_KEY);
 table_constante[4][2] = (char)~('+' + MY_KEY);
 table_constante[4][3] = (char)~(MY_KEY);
[…]

void C_Chapeau::Transforme(int ligne, char *pt_out)
{
 int local = 0;
 if ( ligne > MAX_LIGNE) return;
  while (table_constante[ligne][local] != ~(MY_KEY))
   {
      *pt_out = ~(table_constante[ligne][local]) - MY_KEY;
       local++;
        if ( local > MAX_COLONNE) 
          {
          *pt_out=0x00;
           return;
          }
         pt_out++;
         }
         *pt_out = 0x00;
}

fonction « Compresse_Buffer»  réalise successivement la compression d’un buffer puis l’encodage de ce même buffer :
[…]
// Calcul de la future taille compressée
 wts	= pt_huff->Dictionary((BYTE*)pt_in,m_nDocLength,&dwbt[0],&dwc[0]);
 DWORD dwLength	 = pt_huff->CountCompress((BYTE*)pt_in,m_nDocLength,wts,&dwc[0]);
 // Allocation du buffer temporaire pour Huufamn
 pt_inter = (char *)GlobalAlloc(GMEM_FIXED,dwLength);
 // au boulot maintenant que tout est prèt
 pt_huff->Compress((BYTE*)pt_in,m_nDocLength,wts,&dwbt[0],&dwc[0],(BYTE*)pt_inter);
 // Allocation du buffer temporaire pour Base 64	
 DWORD dwRunLength = pt_base->EncodedRunLength((BYTE*)pt_inter,dwLength);
 // Allocation du buffer temporaire
  pt_out = (char *)GlobalAlloc(GMEM_FIXED,dwRunLength);
 pt_base->RunLengthEncode((BYTE*)pt_inter,dwLength,(BYTE*)pt_out);
 GlobalFree(pt_inter);
 return(dwRunLength);
[…]

La classe principale de la classe d’insertion d’un buffer dans une section est réalisée par la fonction « WORK ». Le tout est toujours aussi simple quand on connait le structure des fichiers exécutables (format Pe pour Windows)

[…]
void GInsert::Work(CString exe_in, char *pt_in_crypted, unsigned int lg_ressource_crytree)
{
….
// on change le nom du fichier de sortie à partir du nom du fichier d'entré
 exe_out = Transform(exe_in) ;
exe=fopen( exe_in ,&buffer_local_3[0]); 
 if(exe) 			// si pas d'erreur
 {
   //on lit les entetes
    fread(&Dos_header,sizeof(IMAGE_DOS_HEADER),1,exe);
    fseek(exe,Dos_header.e_lfanew,0);
    fread(&Nt_header,sizeof(IMAGE_NT_HEADERS),1,exe);
……

 //et les entêtes des sections
  for(a = 0 ;a < ( nb_sections + 1) ; a++) 
  { fwrite(&tab_sections[a],sizeof(IMAGE_SECTION_HEADER),1,result); }
  // on doit faire du padding car la taille du
  // headers est  par exmple 1000h et on doit
  // écrire de l'index courant à cette limite
  long longueur = Nt_header.OptionalHeader.SizeOfHeaders - sizeof(IMAGE_SECTION_HEADER);
  for(a = ftell(exe) ; a < longueur ; a++) { fwrite("\x00",1,1,result); }
…..
  //on met ensuite les datas de notre section 
   fwrite((char *)pt_in_crypted,lg_ressource_crytree,1,result);
   //padding sur notre section
   longueur = tab_sections[nb_sections].SizeOfRawData;
   for(a = lg_ressource_crytree ; a < longueur ; a++)  { fwrite("\x00",1,1,result); }
   fclose(result);
[…]


Unpackeur

Le programme principal se résume à :

BOOL CUnPackeurApp::InitInstance()
{
 CWinApp:InitInstance();
 //Une seule instance ?
 if ( Seul() == TRUE)
  {
    // I'm alone
     C_Chapeau   *pt_chapeau = new C_Chapeau;
     pt_chapeau->Full_Decodage("K:\\Application(s)\\wwwwww\\Inject_packeur_vide"); // Phase de test
     //pt_chapeau->Full_Decodage(m_pszAppName); // Appel Réel
     delete pt_chapeau;
  }

 return FALSE;
}

Tout comme dans la phase précédente on retrouve :

[…]
 // Assignation et vérification des fichiers en entrée
 //".exe"
 Transforme(7,&buffer_local[0]);
 File_in = File_in + &buffer_local[0];
 if (Recherche(File_in) == TRUE)
  {
     // "".g3d""
      input_file_to_encoded = File_in;
     Transforme(0,&pt_load->buffer_local[0]);
     status = pt_load->Open_File(input_file_to_encoded);
      if ( status == TRUE)
      {
      count_out = Decompresse_Buffer((char *)pt_load->buffer_data, &pt_out_local);
      GetTempFileName(0x00,0x00,0,&buffer_file[0]);
       buffer_file[(strlen(buffer_file)-1) ]= 'e';
       buffer_file[(strlen(buffer_file)-2) ]= 'x';
       buffer_file[(strlen(buffer_file)-3) ]= 'e';
       GetCurrentDirectory(MAX_PATH,&Dir[0]);
       Transforme(8,&buffer_texte[0]);
       sprintf(&buffer_final[0],&buffer_texte[0],&Dir[0],&buffer_file[1]);
       Ouvre_fichier_ecriture(&buffer_final[0],pt_out_local, count_out);
       Lancement_avec_wait(&buffer_file[1]);
       DeleteFile(&buffer_final[1]);
       free(pt_load->buffer_data);
      }
 delete pt_load;
m_pFileData = m_hFile = m_hFileMapping = m_hFileMapping = NULL;
[…]

Le fichier extrait de la section est ensuite écris sur le disque puis lancé. Après son exécution, on le détruit sur le disque du, pour ne laisser aucune trace. La fonction de décompression est symétrique par rapport à celle de compression.

 […]
DWORD C_Chapeau::Decode()
{
 // Calcul de la taille décompressée
 DWORD dwCount = pt_base->DecodedRunLength((BYTE*)pt_in);
 // Allocation du buffer temporaire pour Base 64
 pt_inter = (char *)GlobalAlloc(GMEM_FIXED,dwCount);
 // Au boulot maintenant que tout est prêt
 pt_base->RunLengthDecode((BYTE*)pt_in,(BYTE*)pt_inter);
 // Calcul de la taille décompréssé pour Base 64
 DWORD dwUnCount = pt_huff->GetSize((BYTE*)pt_inter);
 // Allocation du buffer temporaire pour Huffman
  pt_out = (char *)GlobalAlloc(GMEM_FIXED,dwUnCount);
 // au boulot maintenant que tout est prêt
 DWORD dwLength = pt_huff->UnCompress((BYTE*)pt_inter,(BYTE*)pt_out);
// Libération du buffer temporaire
 GlobalFree(pt_inter);
 return (dwLength);
}
[…]

Le lancement d’un sous process est des plus courant avec une fonction prévu à cet effet. Le process appelant est en attente de fin d’exécution du process lancé.

[…]
Lancement_avec_wait(CString Fichier)
{
 // Mise forme des différentes variables nécessaires
 sprintf(nom_fichier_commande,"%s",Fichier);
 GetCurrentDirectory(MAX_PATH,&dir[0]);
 memset(&suInfo, 0, sizeof(suInfo));
 suInfo.cb = sizeof(suInfo);
 // Lancement du sous process
 bWorked = ::CreateProcess(NULL, nom_fichier_commande, NULL, NULL, FALSE,
    NORMAL_PRIORITY_CLASS, NULL, dir, &suInfo, &procInfo);
 if (bWorked == FALSE)
  {
    dwResult = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
     FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwError, LANG_NEUTRAL, (LPTSTR) &pstrError, 0,
     NULL);
     Transforme(9,&buffer_texte[0]);
     if (dwResult == 0) pstrError = &buffer_texte[0];
         Transforme(10,&buffer_texte_1[0]);
         str.Format(&buffer_texte_1[0], pstrError, dwError);

        if (dwResult != 0) ::LocalFree(pstrError);
        AfxMessageBox(str);
       // Erreur lors du lancement
       return (FALSE);
  }
   else
   { // on attends
       DWORD dwReturn = ::WaitForSingleObject(procInfo.hProcess, INFINITE);
   }
  // tout c'est bien passé
 return (TRUE);
}
[…]


Commentaire

Je tiens à ajouter un commentaire à cet article. En tant que reverser amateur, je peux affirmer que ce type de packeur (ou protector) est totalement inefficace. En effet, il se contente d'unpacker l'exécutable sur disque, il suffit alors de modifier le loader pour qu'il n'efface pas l'exécutable créé, et ensuite de récupérer cet exécutable. Généralement les packers font toutes leurs manipulations en mémoire, ce qui leur permet d'une part d'aller plus vite (car temps d'accès bcp plus court), et d'autre part de compliquer la tâche du reverser, en utilisant des espaces mémoire hors sections par exemple, ou encore des détournements divers.

Au fait, cet article a mystérieusement disparu du wiki, après publication du mag 04... --195.221.243.132 8 jun 2006 à 23:48 (CEST)