giovedì 14 marzo 2019

Caricamento ritardato di una DLL da risorsa

La tecnica di incorporare una DLL nelle risorse e di caricarla a runtime è stata usata spesso in passato da malware e virus per nascondere i moduli, contenenti codice malevolo, necessari all'esecuzione dello stesso. Il procedimento per implementare tale tecnica è relativamente semplice e richiede poco codice da scrivere manualmente. A tale scopo, in questo articolo, si presenterà un esempio banale ma esplicativo.

L'applicazione che conterrà la DLL incorporata nelle sue risorse ha il seguente sorgente:

#include <Windows.h>
#include <iostream>
#include "ResourceFree.h"
#include "resource.h"
#include "MyDLL_Import.h"
 
int wmain(int argcwchar_t * argv[])
{
 std::cout << "Press any key to Free DLL from Resource..." << std::endl;
 std::getchar();
 
 wchar_t szSaveName[MAX_PATH] = L"MyDLL.dll";
 
 BOOL bRet = FreeMyResource(IDR_MYRES1L"MYRES", szSaveName);
 if (bRet == FALSE)
 {
  std::cout << "Free Resource Error!" << std::endl;
 }
 else
 {
  std::cout << "Free Resource OK!" << std::endl;
 }
 
 std::cout << "Press any key to call Function1 from DLL..." << std::endl;
 std::getchar();
 
 Function1();
 
 std::cout << "Press any key to Exit!" << std::endl;
 std::getchar();
}

Il codice della DLL da incorporare, invece, è il seguente:

#include <Windows.h>
#include "MyDLL_Export.h"
 
BOOL APIENTRY DllMain(HMODULE hModule,
 DWORD  dwReason,
 LPVOID lpReserved
)
{
 switch (dwReason)
 {
 case DLL_PROCESS_ATTACH:
 {
  MessageBox(NULLL"MyDLL.dll Loaded!"L"MyDLL"MB_OK | MB_ICONWARNING);
  break;
 }
 case DLL_THREAD_ATTACH:
 case DLL_THREAD_DETACH:
 case DLL_PROCESS_DETACH:
  break;
 }
 return TRUE;
}
 
void __cdecl Function1(void)
{
 MessageBox(NULLL"Function1 called!"L"MyDLL"MB_OK | MB_ICONWARNING);
}

Una volta compilato il progetto della DLL si trasferiscano la DLL stessa e la relativa libreria di importazione (file con estensione LIB) nella directory dell'applicazione principale. Io ho messo la DLL nella sottocartella .\res ma va bene qualsiasi posizione. Dopodiché aprire la pagina delle proprietà dell'applicazione principale ed impostare LIB e DLL nelle proprietà Linker/Input/Dipendenze aggiuntive e Linker/Input/DLL a caricamento ritardato come mostrato nella figura seguente. In questo modo la DLL non verrà elencata tra i moduli necessari dell'eseguibile e verrà caricata solo quando necessario.


A questo punto è possibile effettuare tutti i passaggi necessari per indicare al compilatore di incorporare la DLL nelle risorse. Tasto destro sul progetto dell'eseguibile e selezionare Aggiungi/Risorsa. Apparirà una finestra come quella mostrata sotto. Premere il tasto Importa... e selezionare la DLL (quella nella sottocartella .\res).


Quando il tipo di file selezionato non è riconosciuto in maniera automatica appare un'altra finestra in cui si chiede di inserire un nome per il tipo di risorsa appena importata (in questo caso io ho optato per MYRES). Lo stesso procedimento è valido anche per altre risorse, tipo le icone che vengono però riconosciute automaticamente e non richiedono di specificare un nome per il tipo.


A questo punto i file resource.h e $(ProjectName).rc dovrebbero far parte del progetto dell'eseguibile e dovrebbero contenere i dati necessari ad incorporare in esso le risorse. Dopo aver controllato che sia tutto a posto (vedi codice sotto), tasto destro del mouse su $(ProjectName).rc e selezionare Compila.

// Estratto da resource.h
 
// ...
 
#define IDR_MYRES1                      102
 
// ...

// Estratto da $(ProjectName).rc
 
// ...
 
#define IDR_MYRES1                      MYRES                      "res\\MyDLL.dll"
 
// ...

Non resta che scrivere il codice per estrarre le risorse, salvarle in memoria e metterle su disco pronte per essere usate. Si faccia riferimento a [1] per la lettura e comprensione del codice, soprattutto per le funzioni FindResource, SizeofResource, LoadResource, LockResource e la macro MAKEINTRESOURCE. Nota: le icone hanno una gestione automatica separata e non richiedono intervento manuale per essere usate.

#include "ResourceFree.h"
 
void FreeRes_ShowError(const wchar_t *pszText)
{
 wchar_t szErr[MAX_PATH] = { 0 };
 wsprintf(szErr, L"%s Error[%d]\n"pszText, GetLastError());
#ifdef _DEBUG
 MessageBox(NULL, szErr, L"ERROR"MB_OK | MB_ICONERROR);
#endif
}
 
 
BOOL FreeMyResource(UINT uiResouceNameconst wchar_t *lpszResourceTypewchar_t *lpszSaveFileName)
{
 HRSRC hRsrc = FindResource(NULLMAKEINTRESOURCE(uiResouceName), lpszResourceType);
 if (hRsrc == NULL)
 {
  FreeRes_ShowError(L"FindResource");
  return FALSE;
 }
 
 DWORD dwSize = SizeofResource(NULL, hRsrc);
 if (dwSize <= 0)
 {
  FreeRes_ShowError(L"SizeofResource");
  return FALSE;
 }
 
 HGLOBAL hGlobal = LoadResource(NULL, hRsrc);
 if (hGlobal == NULL)
 {
  FreeRes_ShowError(L"LoadResource");
  return FALSE;
 }
 
 LPVOID lpVoid = LockResource(hGlobal);
 if (lpVoid == NULL)
 {
  FreeRes_ShowError(L"LockResource");
  return FALSE;
 }
 
 FILE *fp = NULL;
 _wfopen_s(&fp, lpszSaveFileNameL"wb+");
 if (fp == NULL)
 {
  FreeRes_ShowError(L"LockResource");
  return FALSE;
 }
 
 fwrite(lpVoid, sizeof(char), dwSize, fp);
 fclose(fp);
 
 return TRUE;
}

Compilando l'applicazione si possono notare subito due cose. Primo, si può verificare che essa è eseguibile anche senza la presenza della DLL (che ora è di fatto incorporata nell'eseguibile). Secondo, la DLL non è elencata tra i moduli necessari dell'eseguibile, come mostra la seconda l'immagine sotto. Non appena si avvia l'eseguibile ci viene chiesto di premere un tasto per estrarre la DLL dalle risorse.



Effettuata tale operazione ci viene segnalato che la risorsa è stata estratta ed è disponibile su disco (si controlli la cartella in cui è contenuto l'eseguibile e si verifichi che sia effettivamente presente la DLL). A questo punto il modulo non ancora stato caricato. Si prema un tasto per chiamare la funzione Function1 esportata dalla DLL.


La MessageBox, chiamata in DllMain, ci conferma che la DLL è stata appena caricata. Si può avere ulteriore conferma di questo controllando che la DLL sia ora tra i moduli caricati per l'eseguibile (si veda seconda immagine sotto).



Subito dopo aver cliccato OK un'altra MessageBox ci conferma che Function1, definita ed esportata dalla DLL, è stata appena chiamata.





Codice sorgente:
DLL_Resource.zip



Riferimenti:
[1] Documentazione online Microsoft
[2] https://github.com/DemonGan/Windows-Hack-Programming

Nessun commento:

Posta un commento