Редактор диска своїми руками

// Drive - номер диска (нумерація з нуля).

hFile: = CreateFile (PChar ( '\\. \ PhysicalDrive' + IntToStr (Drive)), GENERIC_READ, FILE_SHARE_READ + FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0,0);
if hFile = INVALID_HANDLE_VALUE then Exit;

Таким чином, ми можемо сприймати фізичний диск як один великий файл.
Друге, що варто зробити - це отримати інформацію про геометрії диска.

const IOCTL_DISK_GET_DRIVE_GEOMETRY = $ 70000;

type
TDiskGeometry = packed record
Cylinders: Int64; // кількість
циліндрів
MediaType: DWORD; // тип носія
TracksPerCylinder: DWORD; // доріжок на циліндрі
SectorsPerTrack: DWORD; // секторів на доріжці
BytesPerSector: DWORD; // байт в секторі
end;

Result: = DeviceIoControl (hFile, IOCTL_DISK_GET_DRIVE_GEOMETRY, nil, 0,
@ DiskGeometry, SizeOf (TDiskGeometry), junk, nil) and (junk = SizeOf (TDiskGeometry));

Функція повертає True якщо операція пройшла успішно, і False в іншому
випадку.

Тепер уже можна приступити до визначення місця розташування логічних дисків на
вінчестері. Почати це потрібно з читання нульового сектора фізичного диска.
Він містить MBR (Master Boot Record), а так само Partition Table. До речі, думаю,
буде цікаво зберегти вміст MBR в файл і подивитися програму завантаження
якимось дізасмом. Але в даний момент нас цікавить тільки Partition Table.

Ця таблиця розташовується в секторі по зсуву $ 1be і складається з чотирьох
однакових елементів, кожен з яких описує один розділ:

TPartitionTableEntry = packed record
BootIndicator: Byte; // $ 80, якщо активний (завантажувальний) розділ
StartingHead: Byte;
StartingCylAndSect: Word;
SystemIndicator: Byte;
EndingHead: Byte;
EndingCylAndSect: Word;
StartingSector: DWORD; // початковий сектор
NumberOfSects: DWORD; // кількість секторів
end;

Відповідно, саму Partition Table можна уявити як масив:

TPartitionTable = packed array [0..3] of TPartitionTableEntry;

Приклади значень поля SystemIndicator:
01 - FAT12
04 - FAT16
05 - EXTENDED
06 - FAT16
07 - NTFS
0B - FAT32
0F - EXTENDED

Тепер можна приступити до розбору структури логічних дисків. зараз
(Та й в подальшому) нам знадобиться функція ReadSectors.

// так як диск для нас - це великий файл, то для переміщення по ньому
// за допомогою SetFilePointer знадобиться 64хразрядная арифметика

function __Mul (a, b: DWORD; var HiDWORD: DWORD): DWORD; // Result = LoDWORD
asm
mul edx
mov [ecx], edx
end;

function ReadSectors (DriveNumber: Byte; StartingSector, SectorCount: DWORD;
Buffer: Pointer; BytesPerSector: DWORD = 512): DWORD;
var
hFile: THandle;
br, TmpLo, TmpHi: DWORD;
begin
Result: = 0;
hFile: = CreateFile (PChar ( '\\. \ PhysicalDrive' + IntToStr (DriveNumber)),
GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if hFile = INVALID_HANDLE_VALUE then Exit;
TmpLo: = __Mul (StartingSector, BytesPerSector, TmpHi);
if SetFilePointer (hFile, TmpLo, @ TmpHi, FILE_BEGIN) = TmpLo then
begin
SectorCount: = SectorCount * BytesPerSector;
if not ReadFile (hFile, Buffer ^, SectorCount, br, nil) then Exit;
Result: = br;
end;
CloseHandle (hFile);
end;

І заодно функція для запису:

function WriteSectors (DriveNumber: Byte; StartingSector, SectorCount: DWORD;
Buffer: Pointer; BytesPerSector: DWORD = 512): DWORD;
var
hFile: THandle;
bw, TmpLo, TmpHi: DWORD;
begin
Result: = 0;
hFile: = CreateFile (PChar ( '\\. \ PhysicalDrive' + IntToStr (DriveNumber)),
GENERIC_WRITE, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if hFile = INVALID_HANDLE_VALUE then Exit;
TmpLo: = __Mul (StartingSector, BytesPerSector, TmpHi);
if SetFilePointer (hFile, TmpLo, @ TmpHi, FILE_BEGIN) = TmpLo then
begin
SectorCount: = SectorCount * BytesPerSector;
if not WriteFile (hFile, Buffer ^, SectorCount, bw, nil) then Exit;
Result: = bw;
end;
CloseHandle (hFile);
end;

PDriveInfo = ^ TDriveInfo;
TDriveInfo = record
PartitionTable: TPartitionTable;
LogicalDrives: array [0..3] of PDriveInfo;
end;

Ну а тепер сам код:

const
PartitionTableOffset = $ 1be;
ExtendedPartitions = [5, $ f];

var
MainExPartOffset: DWORD = 0;

function GetDriveInfo (DriveNumber: Byte; DriveInfo: PDriveInfo;
StartingSector: DWORD; BytesPerSector: DWORD = 512): Boolean;
var
buf: array of Byte;
CurExPartOffset: DWORD;
i: Integer;
begin
SetLength (buf, BytesPerSector);
// читаємо сектор в буфер
if ReadSectors (DriveNumber, MainExPartOffset + StartingSector, 1, @ buf [0]) = 0 then
begin
Result: = False;
Finalize (buf);
Exit;
end;
// заповнюємо структуру DriveInfo.PartitionTable
Move (buf [PartitionTableOffset], DriveInfo.PartitionTable, SizeOf (TPartitionTable));
Finalize (buf); // буфер більше не потрібен

Result: = True;
for i: = 0 to 3 do // для кожного запису в Partition Table
if DriveInfo.PartitionTable [i] .SystemIndicator in ExtendedPartitions then
begin
New (DriveInfo.LogicalDrives [I]);
if MainExPartOffset = 0 then
begin
MainExPartOffset: = DriveInfo.PartitionTable [I] .StartingSector;
CurExPartOffset: = 0;
end else CurExPartOffset: = DriveInfo.PartitionTable [I] .StartingSector;
Result: = Result and GetDriveInfo (DriveNumber, DriveInfo.LogicalDrives [I],
CurExPartOffset);
end else DriveInfo.LogicalDrives [I]: = nil;
end;

Функція заповнює структуру DriveInfo і повертає
True якщо операція пройшла успішно, або False в іншому випадку.
Тепер у нас є така корисна інформація про розділи як початковий
сектор, загальна кількість секторів, а так само файлова система.
У нульовому секторі кожного основного розділу знаходиться BIOS Parameter
Block, содержаший таку інформацію як назва файлової системи, кількість секторів в
кластері і т.д. А так же програма-завантажувач (зберігаємо сектор в файл і дивимося
своїм улюбленим дізасмом).

На щастя в Delphi вони вже є (IntToHex і StrToInt) і залишається їх тільки
правильно використовувати. StrToInt можна використовувати для перетворення рядка,
містить шістнадцяткове число в Integer, якщо дописати попереду символ $.

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

Для отримання доступу до фізичного диску ми відкривали пристрій \\. \ PHYSICALDRIVE,
далі розбирали його структуру. Можна було зробити простіше. Відкривати відразу логічні
диски (\\. \ C:, \\. \ D: і т.д.), але в даному випадку ми б залишили поза увагою деякі
області диска. Наприклад, MBR і розділеного область диска. А взагалі від завдання залежить,
з чим потрібно працювати.

Покажи цю статтю друзям: