?

Log in

No account? Create an account
mika0x65's Journal
 
[Most Recent Entries] [Calendar View] [Friends]

Below are the 20 most recent journal entries recorded in mika0x65's LiveJournal:

[ << Previous 20 ]
Sunday, November 13th, 2011
6:26 pm
Большое обновление в Mediana. Исправлено какое-то количество серьезных ошибок, чуть большее количество менее серьезных ошибок, замочено куча warning'ов, код облагорожен. Новую версию можно классически взять из SVN: http://mediana.svn.sourceforge.net/viewvc/mediana
Tuesday, January 25th, 2011
4:20 pm
Mediana
Обновил Mediana. Теперь, помимо ННого числа исправлений, дизассемблер содержит флаги доступа к операндам (R/W/X), а также инструкции виртуализации от AMD. Флаги были большим шагом во всех смыслах -- во-первых, их сильно не хватало, во-вторых, добавлять их было довольно тяжело. Новую версию как всегда берем из SVN: http://mediana.svn.sourceforge.net/viewvc/mediana
Sunday, July 11th, 2010
1:21 am
Mediana
В svn лежит новая версия дизассемблера. Полностью изменен вывод, добавлена возможность указания размера входного буфера. Документации пока нет, думаю, будет на следующей неделе.
Monday, July 5th, 2010
3:04 am
IDA bug?
Что-то везет мне на ошибки. Открываю отладочную версию ld.so (загрузчик ELF файлов) в IDA, чтобы прояснить некоторые моменты и вижу, что виртуальный адрес второго сегмента в IDA не соответствует адресу сегмента из заголовка исполняемого. Адрес в заголовке:
 14 .data.rel.ro  00000268  000000000022bbe0  000000000022bbe0  0002bbe0  2**5
                  CONTENTS, ALLOC, LOAD, DATA
IDA же показывает эту секцию по адресу:
.data.rel.ro:000000000021ABE0 ; ===========================================================================
.data.rel.ro:000000000021ABE0
.data.rel.ro:000000000021ABE0 ; Segment type: Pure data
.data.rel.ro:000000000021ABE0 ; Segment permissions: Read/Write
.data.rel.ro:000000000021ABE0 ; Segment alignment '32byte' can not be represented in assembly
.data.rel.ro:000000000021ABE0 _data_rel_ro    segment para public 'DATA' use64
.data.rel.ro:000000000021ABE0                 assume cs:_data_rel_ro
.data.rel.ro:000000000021ABE0                 ;org 21ABE0h


Почему из адреса вычтено 0x11000 я пока не понял, и как бороться не знаю. Завтра попробую вручную загрузить, вероятно так будет проще.
Sunday, June 20th, 2010
2:44 am
ELF loader bug
Разбираю для себя загрузчик ELF'ов. Довольно интересно, хотя много еще не понимаю. Относительно разобрал структуру дескриптора, описывающего загруженный объект в памяти, и даже нашел ошибку в загрузчике. Сам загрузчик является частью glibc и находится в двух каталогах: glibc/elf -- ядро загрузчика и glibc/dlfcn -- frontend загрузчика в виде ф-ий dlopen, dlsym, etc. Код, непосредственно загружающий ELF в память, находится в файле glibc/elf/dl-load.c, ф-ия _dl_map_object_from_fd. Она осуществляет разбор ELF и выполняет вызов mmap для каждого сегмента, описанного в файле.
А теперь немного подробнее про ошибку. Сначала ф-ия проходит по описателям всех сегментов файла, собирая информацию о них в массив внутренних структур:
    struct loadcmd
      {
	ElfW(Addr) mapstart, mapend, dataend, allocend;
	off_t mapoff;
	int prot;
      } loadcmds[l->l_phnum], *c;
Для каждого сегмента типа PT_LOAD ф-ия заполняет структуру loadcmd:
	  c = &loadcmds[nloadcmds++];
	  c->mapstart = ph->p_vaddr & ~(GLRO(dl_pagesize) - 1);
	  c->mapend = ((ph->p_vaddr + ph->p_filesz + GLRO(dl_pagesize) - 1)
		       & ~(GLRO(dl_pagesize) - 1));
	  c->dataend = ph->p_vaddr + ph->p_filesz;
	  c->allocend = ph->p_vaddr + ph->p_memsz;
	  c->mapoff = ph->p_offset & ~(GLRO(dl_pagesize) - 1);
Здесь как раз начинается проблема: loadcmd::mapend содержит файловый размер сегмента, выровненный на границу страницы. Запомним это.
После того, как информация о всех сегментах и из защите собрана, _dl_map_object_from_fd подсчитывает суммарный размер сегментов и отображает их в память с помощью mmap. Однако сегменты не обязаны занимать непрерывный участок памяти, т.е. образ может содержать "пустоты". Ф-ия проверяет это, и если пустоты есть, выполняет mprotect(..., PROT_NONE, ...) таким образом, чтобы оставить только первый сегмент в памяти:
    maplength = loadcmds[nloadcmds - 1].allocend - c->mapstart;

    if (__builtin_expect (type, ET_DYN) == ET_DYN)
      {
	/* This is a position-independent shared object.  We can let the
	   kernel map it anywhere it likes, but we must have space for all
	   the segments in their specified positions relative to the first.
	   So we map the first segment without MAP_FIXED, but with its
	   extent increased to cover all the segments.  Then we remove
	   access from excess portion, and there is known sufficient space
	   there to remap from the later segments.

	   As a refinement, sometimes we have an address that we would
	   prefer to map such objects at; but this is only a preference,
	   the OS can do whatever it likes. */
	ElfW(Addr) mappref;
	mappref = (ELF_PREFERRED_ADDRESS (loader, maplength,
					  c->mapstart & GLRO(dl_use_load_bias))
		   - MAP_BASE_ADDR (l));

	/* Remember which part of the address space this object uses.  */
	l->l_map_start = (ElfW(Addr)) __mmap ((void *) mappref, maplength,
					      c->prot,
					      MAP_COPY|MAP_FILE,
					      fd, c->mapoff);
	if (__builtin_expect ((void *) l->l_map_start == MAP_FAILED, 0))
	  {
	  map_error:
	    errstring = N_("failed to map segment from shared object");
	    goto call_lose_errno;
	  }

	l->l_map_end = l->l_map_start + maplength;
	l->l_addr = l->l_map_start - c->mapstart;

	if (has_holes)
	  /* Change protection on the excess portion to disallow all access;
	     the portions we do not remap later will be inaccessible as if
	     unallocated.  Then jump into the normal segment-mapping loop to
	     handle the portion of the segment past the end of the file
	     mapping.  */
	  __mprotect ((caddr_t) (l->l_addr + c->mapend),
		      loadcmds[nloadcmds - 1].mapstart - c->mapend,
		      PROT_NONE);

	l->l_contiguous = 1;

	goto postmap;
      } 
Именно в этом участке кода и проявляется ошибка:
	  __mprotect ((caddr_t) (l->l_addr + c->mapend),
		      loadcmds[nloadcmds - 1].mapstart - c->mapend,
		      PROT_NONE);
ведь loadcmd::mapend содержит файловый размер сегмента, выравненный на границу страницы. Следовательно, он будет нулем в случае, если первый сегмент файла будет будет иметь нулевой файловый размер и будет выравнен на границу страницы. 'loadcmds[nloadcmds - 1].mapstart - c->mapend' тоже даст неверное значение, равное суммарному размеру загружаемых сегментов, а не (суммарный размер всех загружаемых сегментов) - (размер первого сегмента). Т.е. ошибка проявляется при следующих условиях:
  • Файл должен быть динамической библиотекой (shared object).
  • Первым сегментом файла должен быть сегмент, не имеющий тела в файле (секция .bss).
  • Сегмент должен быть выравнен на границу страницы.
  • Образ не должен быть непрерывным.
Если соблюсти все эти условия, то обращение к переменной, находящейся в секции .bss, вызовет SIGSEGV. Я написал об ошибке разработчикам glibc, там же можно посмотреть пример создания файла: sourceware.org.

А теперь немного ненависти. Черт с ошибкой, все ошибаются, но кто-нибудь знает способ собрать версию glibc, пригодную для отладки? Всего-то надо собрать glibc с опциями '-g' (отладочная информация) и '-O0' (без оптимизации). На днях мне удалось передать опцию '-g' (в Debian это делается через переменную $DEB_BUILD_OPTIONS), а второе не удалось пока никак. В результате при отладке ld.so большая часть переменных просто не видна из-за оптимизации ( по словам gdb). Я уже поговорил на этот счет на freenode.net и irc.debian.org -- никто не знает, как в Debian передавать опции компиляции при сборке glibc. На канале сидит тысяча человек, а максимум, что могут -- сказать, что спрашивать надо на другом канале...
Tuesday, May 25th, 2010
2:53 am
Mediana.Output

Похоже, вывод дизассемблера следует сделать несколько иначе, чем планировалось. Т.к. каждый хочет выводить результаты на свой лад, необходимо сделать вывод более гибким. Archer предложил сделать вывод с помощью форматирующей строки, но это не очень гибко, т.к. если кому-то приспичит выводить непосредственные операнды, например, в четверичной системе счисления, то мне придется дописывать распознавание спецификатора четверичной системы. Другой вариант -- принимать от пользователя массив обработчиков, в котором каждый обработчик отвечает за вывод элемента инструкции. Обработчики вызываются из Mediana. Это позволит пользователю перегружать ф-ии вывода а ля С++. Подход представляется мне так:
int dump_handler(struct STREAM * const stream, struct INSTRUCTION *instr, int operand, int param1, int param2);

int (*handlers)[HANDLERS_NUM](struct STREAM * const, struct INSTRUCTION *, int, int, int);
handlers[0] = dump_prefix;
handlers[1] = dump_mnemonic;
handlers[2] = dump_mnem_op_space;
...

medi_dump(..., handlers, ...);
Возможные обработчики:
prefix                     //префикс инструкции.
mnem                       //мнемоника.
mnem_op_space              //разделитель мнемоники и операндов.
op_op_space                //разделитель операндов.
reg                        //операнд-регистр.
imm                        //непосредственное значение.
dir                        //операнд-дальний адрес.
addr_size                  //квалификатор размера адреса.
addr_open_brace            //открывающая скобка операнда-адреса.
addr_seg_reg               //сегментный регистр адреса.
addr_base                  //базовый регистр адреса.
addr_idx                   //индексный регистр адреса.
addr_multiplier            //множитель индексного регистра адреса.
addr_disp                  //смещение.
addr_base_idx_space        //разделитель базового и индексных регистров.
addr_idx_multiplier_space  //разделитель индексного регистра и множителя.
addr_multiplier_disp_space //разделитель множителя и смещения.
addr_close_brace           //закрывающая скобка.
Если в массиве передано значение NULL, то Mediana вызовет обработчик по умолчанию. Кроме того, думаю, стоит добавить возможность вызова обработчика по умолчанию и передачу результатов его работы в пользовательский обработчик. Не уверен, что необходимы обработчики разделителей, разделители можно выводить в обработчиках предшествующего элемента.
Плюс такого подхода: не требуются никакие опции для вывода, достигается максимальная гибкость. Однако есть и минусы:
  • Придется открыть большое число внутренних ф-ий и структур. Например, служебная структура STREAM должна передавться в пользовательский обработчик, но следует строго запретить модификацию этой структуры пользователем. Кроме того придется открыть большое число массивов указателей на строки для удобства написания пользовательских ф-ий вывода.
  • Для вывода результата необходимо использовать только ф-ии вывода значений, определенные в Mediana. (Вероятно, это получится немного упростить, но надо обдумать.) Это связано с тем, что Mediana всегда ведет подсчет выведенных символов и возвращает их пользователю в качестве результата работы ф-ии medi_dump. Это позволяет перезапросить буфер, отведенный под строку вывода, если он слишком мал.
  • Возрастает общая сложность использования дизассемблера, а значит повышается вероятность ошибок.
Буду рад комментариям и предложениям.
Wednesday, April 28th, 2010
3:58 pm
Mediana
Исправления/обновения

Выполнил часть обещаний из предыдущих пунктов, исправил часть ошибок. А именно:
  • Инструкции inc/dec (0xFE 0x00, 0xFE 0x01, 0xFF 0x00, 0xFF 0x01) теперь поддерживают префикс LOCK.
  • Исправлена ошибка неверного определения наличия смещения относитьельно rip в 64битном режиме.
  • Исправлена ошибка неверного определения 16битных непосредственных операндов в 64битном режиме.
  • Структура ADDR теперь имеет глобальную область видимости, т.к. используется в файле mediana.c отдельно от структуры OPERAND. Теоретически должна исчезнуть проблема компиляции.
  • Теперь Mediana не выводит сегментные префиксы там, где они не были заменены явно. Например вместо 'mov eax, ds:[eax]' теперь будет выводиться 'mov eax, [eax]'. Если сегментный префикс был заменен явно, то он будет выведен: 'mov eax, fs:[eax]'. Чтобы включить вывод всех префиксов, нужно передать в ф-ию medi_dump опцию DUMP_OPTION_SHOW_ALL_SEG. Кроме этого в члене flags структуры OPERAND добавился новый флаг OPERAND_FLAG_SEG_OVERRIDE, который говорит о том, что сегментный регистр операнда-адреса был явно заменен.
    Все это не касается инструкций, в которых сегментный префикс был избыточен, т.е. заменял префикс по умолчанию префиксом по умолчанию.
  • Так же был отключен вывод квалификатор размера операнда-адреса, в инструкциях, где не возникает неоднозначности. Например, в инструкции 'mov eax, dword ptr [eax]' квалификатор 'dword ptr' не обязателен. В тоже время он нужен в инструкции 'inc dword ptr [eax]'. Теперь Mediana не выводит квалификаторы размера в том случае, когда они избыточны. Вернуть вывод квалификатора размера во всех случаях можно опцией вывода DUMP_OPTION_SHOW_SIZE_PTR.


Я не успел подготовить архив для скачивания, поэтому новую версию следует брать из svn.
Monday, March 29th, 2010
12:50 am
Disassembler.Output
Немного о выводе инструкций.
За последние пару недель я получил несколько жалоб на то, что вывод инструкций не очень гибкий.
  • Невозможность включения/отключения префиксов '0x' и постфиксов 'h' перед и после шестнадцатеричных значений.
  • Невозможность управления разделителями между мнемониками и операндами.
  • Невозможность включения/отключения вывода сегментных регистров по умолчанию.
  • Отсутствие интеллектуального вывода размеров указателей ('byte ptr', 'word ptr', etc), т.е. только в том месте, где возникает неоднозначность.
Несколько соображений по этому поводу:
  • В принципе, первые две проблемы решаются с помощью форматирующей строки. Мне эта идея не очень нравится, т.к. требует больших затрат в плане имплементации и абсолютно нерасширяема. Поэтому, думаю, что будет лучше так: в ф-ию вывода dump будут передаваться дополнительные параметры: разделитель между префиксами и мнемоникой, разделитель между мнемоникой и операндами, разделитель между операндами, а также префиксы и постфиксы. Т.е. вызов ф-ии dump теперь будет выглядеть примерно так:
    medi_dump(struct INSTRUCTION *instr,
              unichar_t          *buff,
              int                 bufflen,
              int                 options,
              unichar_t          *pref_mnem_delimiter,
              unichar_t          *mnem_ops_delimiter,
              unichar_t          *ops_delimiter,
              unichar_t          *disp_prefix,
              unichar_t          *disp_postfix,
              unichar_t          *imm_prefix,
              unichar_t          *imm_postfix);
    
    medi_dump(instr, buff, bufflen, options, _UT(" "), _UT("\t"), _UT(", "), _UT("0"), _UT("h"), _UT("0x"), _UT(""));
    
    Что дает такой вывод:
    rep{space}movsb{tab}dword ptr ds:[edi]{, }dword ptr es:[esi]
    
    mov{tab}dword ptr [eax + 0FFFFFFFFh]{, }0xDEADCAFE
    
    Конечно, безобразие в виде одиннадцати параметров никуда не годится, их придется объединить в структуры. Но идея, в целом, ясна, я думаю.
  • Включение/отключение вывода сегментных префиксов по умолчанию требует более серьезных изменений. Дело в том, что при выводе инструкции я не знаю, был ли сегментный префикс переопределен или нет. Скорее всего, для этого придется ввести дополнительный флаг операнда, извещающий о том, что префикс инструкции "противоестественнен" и его требуется показать в любом случае.
  • Более умный вывод размера указателей, решается довольно простой логикой: если (операнд единственный и является адресом) или (один из операндов является адресом, а другой непосредственным операндом), то { выводим размер указателя }. Конечно, такая логика немного ущербна, особенно для инструкций с тремя операндами, но, ничего другого в голову пока не пришло :).

  • Хотелось бы услышать мнение, идеи и предложения общественности по поводу вывода.
Friday, March 26th, 2010
3:08 am
Disassembler.Help
После небольшого перерыва выкладываю справку к дизассемблеру и приведенные в порядок исходники. Создан проект на sf.net, теперь можно нормально скачать дизассемблер (он называется Mediana) и прочие файлы, к нему относящиеся.

Read more...Collapse )
Sunday, January 17th, 2010
2:27 am
Disassembler
+++ Добавилась поддержка Unicode. Макрос UNICODE включает поддержку Unicode, его отсутствие выключает :).

+++ Добавился файл disasm_ctrl.h. Из него я хочу контролировать компиляцию дизассемблера. А именно: включение/выключение ф-ий вывода и отладочного вывода в код, размер упаковки структур. М.б. еще как-нибудь использую. Думаю, это будет удобно.

--- Вскрылась проблема типа long для члена структуры DISASM_INOUT_PARAMS::base. В 64битной Win long имеет размер не восемь байт, а четыре. В linux -- восемь. Можно использовать тип ptrdiff_t, но с ним тоже есть проблемы: http://blogs.msdn.com/david_leblanc/archive/2008/09/02/ptrdiff-t-is-evil.aspx С ptrdiff_t я еще не разобрался по-настоящему, надо будет перечитать статью. Вставлять #ifdef -- головная боль. В принципе, можно использовать int64_t для DISASM_INOUT_PARAMS::base, т.к. сложение базы все равно прозводится с imm64_t:
if (instr ->ops[i].flags & OPERAND_FLAG_REL)
{
     instr ->ops[i].value.imm.imm64 += len + params ->base;
}

но мне это не очень нравится. В общем, надо подумать. Буду рад советам и предостережениям :).

http://www.8fn.net/153424

Update: DISASM_INOUT_PARAMS::base теперь uint64_t.

http://www.8fn.net/696522
Tuesday, January 12th, 2010
11:14 pm
Disassembler
По просьбам трудящихся выкладываю версию в новой записи.

Что появилось/изменилось:

+++ Добавился флаг OPERAND_FLAG_REL. Он означает, что значение операнда высчитывается относительно значения ip/eip/rip (начало следующей инструкции). Это относится к таким инструкциям как jmp/Jcc/call/etc.

+++ Чтобы немного автоматизировать процесс получения абсолютного адреса в инструкциях с флагом OPERAND_FLAG_REL, добавилась опция DISASM_OPTION_APPLY_REL. Беда в том, что она должна знать текущий адрес инструкции, поэтому появилось поле DISASM_INOUT_PARAMS::base. Как теперь выглядит процесс:


DISASM_INOUT_PARAMS params;
...
params.base = 0x00401000; //задаем начальный адрес кода.
params.options = DISASM_OPTION_APPLY_REL; //включаем опцию.
...
res = disassemble(ptr, &instr, ¶ms);
...
params.base += res; //прибавляем к базе размер полученной инструкции.

Теперь инструкция 'jmp 0x0' (0xEB 0x00) будет выглядеть как 'jmp 0x401002'.

+++ Возможность уничтожать смещения с нулевым значением теперь опциональна. Название опции == DISASM_OPTION_OPTIMIZE_DISP. Т.е. если мы хотим видеть инструкцию 'mov eax, [eax + 0x0]' как 'mov eax, [eax]', то включаем опцию. Иначе не включаем :).

=== Немного поменялась работа с операндами инструкций. Теперь для определения типа операнда не нужно делать сдвиг вправо, достаточно просто наложить маску: OPERAND_TYPE_MASK. Например:
switch (op ->flags & OPERAND_TYPE_MASK)
{
case OPERAND_TYPE_REG:
    dump_reg(stream, op ->value.reg.type, op ->value.reg.code, op ->size);
    break;
case OPERAND_TYPE_MEM:
    dump_addr(stream, instr, op);
    break;
case OPERAND_TYPE_IMM:
    dump_imm(stream, op);
    break;
case OPERAND_TYPE_DIR:
    dump_dir(stream, op);
    break;
default:
    di_strncpy(stream, "internal error");
    break;
}

=== Немного поменялись названия структур, чтобы выглядеть более красноречиво :).

Вроде бы, все. Внутренности дизассемблера тоже немного поменялись, если что-то не так -- не пугаемся, сообщаем.

http://www.8fn.net/200175
Saturday, January 2nd, 2010
4:10 am
Disassembler
Довольно долго (мягко скажем) пишу дизассемблер. 16/32/64битный. Т.к. сайта у меня нет, буду выкладывать его здесь -- это намного проще, чем рассылать исходники по почте. Не сделано еще очень много и документации тоже пока нет.

12.11.09:
Прошу прощение за исчезновение, был занят.

Добавился постпроцессинг. Теперь инструкции pause, multibyte nop, movsxd и cmpxchg16b работают нормально. Инструкция cmpxchg16b работает не совсем корректно -- префикс REX определяется как избыточный. С одной стороны в Intel Manual инструкция имеет модификатор f64, который говорит, что в 64битном режиме ее операнд форсируется до 64 бит без префикса REX. С другой стороны инструкция cmpxchg16b -- это инструкция cmpxchg8b, но у установленным REX.W. Для 'cmpxchg16b [rax]' FASM генерирует REX (0x48). Истина где-то рядом. Сегодня, надеюсь, разберусь с этой мелочью.

Очень прошу протестировать добавленные инструкции в разных режимах и с разными типами аргументов. Если кто-нибудь протестирует, отпишитесь, пожалуйста, в любом случае, чтобы я знал что их вообще тестировали :). Заране благодарен.

22.11.09:
Очередное обновление/исправления: http://www.8fn.net/585273

Теперь инструкции, мнемоника которых зависит от размера (неявного) операнда обрабатываются корректно. Такие инструкции имеют один ID для всех размеров операнда. Различать их можно с помощью добавленного поля INSTRUCTION::opsize. Например, инструкции cbw/cwde/cdqe имеют ID 'ID_CBW' и соответствующие значения поля opsize: 2/4/8. Кроме того, наконец-то исправлена ошибка обработки инструкций в 64битном режиме, имеющих свойство D64 и выставленный префикс замены размера операнда. Исправлен обработчик квалификатора размера 'sq_vds'. В выходную инструкцию копируются все свойства инструкции (INSTR_FLAG_IOPL, INSTR_FLAG_RING0, INSTR_FLAG_SERIAL).

Ошибки:
1. Совсем забыл про инструкцию cmpxchg16b (только сейчас вообще вспомнил).
2. Инструкция jcxz/jecxz обрабатывается некорректно, т.к. на размер инструкции влияет не префикс замены размера операнда, а префикс замены размера адреса (это ж надо такое исключение сделать, вот уроды!).

13.12.09:
Инструкции, мнемоника которых зависит от размера (неявного) операнда обрабатываются еще более корректно :). Исправлены ошибки обработки инструкций cmpxchg16b, jcxz, loopnz, loopz, loop.

http://www.8fn.net/881286

16.12.09:
Исправлена ошибка обработки размеров операндов в 64битном режиме (sq_bs, sq_dqp, sq_vs).

http://www.8fn.net/796277

17.12.09:
Исправлено пара мелких, но катастрофических ошибок:

http://www.8fn.net/313456

Тщательно протестировать я не успел, поэтому не пугайтесь, если что, а отписывайтесь :).

20.12.09:
Добавлены инструкции 0F 00 XX, 0F 01 XX, 0F 02, 0F 03, 0F 05.

http://www.8fn.net/105513

02.01.2010:
Добавлены инструкции FPU, исправлено ННое количество ошибок:

http://www.8fn.net/125751

08.01.2010:
Добвлен десяток потерянных SSE2 инструкций, пара инструкций FPU, исправлена ошибка в таблицах трехбайтных инструкций. Инструкции, специфичные для Intel (VMX) теперь имеют arch == ARCH_INTEL.

http://www.8fn.net/108301
Sunday, August 10th, 2008
3:31 am
MetaMorphing?
Кое-что о метаморфинге.

DISCLAIMER.
Скажу сразу, что я не пытался создать самый лучший метаморфер, который спасет любую malware от сигнатурного сканирования. Если угодно, это -- "проба пера". Просто мне пришла довольно забавная (на мой взгляд) мысль, как можно изменить код программы не изменяя ее алгоритма. О чем и пойдет речь далее. Отмечу заранее: это не готовый метаморф. Более того, его сложно (или даже невозможно) применить к уже существующему коду, но можно использовать при написании кода с нуля. По этой же причине я избегаю слов "морфить", "морфинг" или "морфер", т.к., во-первых, до реального морфинга моему коду еще далеко, во-вторых мне просто не очень нравятся эти слова. Впрочем, местами я это правило нарушаю. Отмечу, также, что от вас требуется хотя бы базовое знание формата инструкций x86.

Read more...Collapse )
Monday, December 3rd, 2007
11:48 pm
Superfluous Prefixes
Префиксы

rain
экспериментировал с префиксами repe/repne, после чего повторили эксперимент на мне. И вот что получается: если добавить два противоречащих друг другу префикса в команду scas, то на разных процессорах они ведут себя по-разному. На моем процессоре (Pentium M) выполняется первый префикс, а на процессоре rain'а (AMD Duron) дела обстоят иначе: выполняется второй (т.е. тот, который ближе к опкоду) префикс.

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

format PE GUI

include 'win32a.inc'

        mov al, 'a'
        mov ecx, 0xFFFFFFFF
        mov edi, _str
        ;db 0f2h
        ;repe scasb
        db 0xf3
        repne scasb
        ret

_str db "aaaaabbbbbccccc"

интересно, у кого как отрабатывает порядок следования префиксов?
Friday, October 5th, 2007
3:44 am
Virtual Memory
VM

Наконец-то у меня в голове появился новый материал, достаточно полный, что о нем можно сделать запись в журнале. На этот раз речь пойдет о виртуальной памяти, а точнее, о страничном преобразовании. "А что может быть необчного в страничном преобразовании?" -- спросите вы. Действительно, о правиле "10-10-12" знает любой человек, хоть раз заглядывавший в соответствующую литературу, и писать о нем совершенно не интересно. Но на этот раз речь пойдет о расширениях возможностей страничного преобразования, а именно, о возможности адресовать более четырех гигабайт физических адресов. Эту возможность предоставляют технологии PAE и PSE-36. И, чтобы не отставать от прогресса, поговорим о возможностях страничного преобразования в уже не новой технологии EM64T (т.е. о страничном преобразовании в 64битном адресном пространстве).

Думаю, самым правильным будет рассмотреть все эти технологии в хронологическом порядке их появления.PAECollapse )
Saturday, November 4th, 2006
1:21 am
Продолжим-с...
Итак, после некоторого перерыва мне снова хочется поделиться прочитанным.

Загрузчик для FAT*

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

Для начала приведу краткое описание ограничений и возможностей моего загрузчика.
Загрузчик ищет в корневом каталоге файловых систем FAT* файл с заданным именем, загружает его по адресу сегмент:смещение и передает ему управление, по адресу так же указанному в форме сегмент:смещение. Загрузчик написан на FASM.

Ограничения:
  1. Макисмальный размер файла -- 64КБ.
  2. Сегмент, используемый для загрузки файла, возможно, будет использован полностью в процессе поиска.
  3. В случае, если корневая директория имеет размер более 64К файл, файл возможно не будет найден (только для FAT12/16).
Имя файла задается константой 'ldr_name'. Сегмент для загрузки -- 'LDR_BASE', Смещение для загрузки -- 'LDR_OFFS', смещение точки входа -- 'LDR_ENTRY_POINT'. Все константы можно менять по своему желанию.

Как я писал здесь, код MBR, после нахождения активного раздела загружает первый сектор активного раздела по адресу 0x0:0x7C00 и передает ему управление. Именно в первом секторе активного раздела должен располагаться загрузчик (или начальная его часть), который умеет читать файловую систему раздела, загружать файл (например, ядро) в память и передавать ему управление. Назовем этот загрузчик "загрузчиком файловой системы" (File System Boot Sector -- FSBS), т.к. наболее вероятно, что его код будет зависеть от файловой системы, установленной на раздел. Конечно, для разных ОС действия FSBS различаются, например, FSBS WinNT* ищет в корневом каталоге файловой системы файл NTLDR, загружает его и передает ему управление. А уже NTLDR в свою очередь производит подготовку и загрузку ядра. Или же, FSBS может занимать более одного сектора -- в этом случае первыми его действиями будут загрузка и исполнение остальной части FSBS. Важно понимать одно -- загрузчик MBR просто читает первый сектор активного раздела, загружает его по адресу 0x0:0x7C00 и передает ему управление.

Итак, значит в задачу входит поиск некоторого файла в некоторой файловой системе, загрузка оного в память и передача ему упраления. Т.к. наиболее простой файловой системой с моей т.з. является FAT*, то я написал загрузочный код для FAT*. Не буду вдаваться в подробности организации FAT* -- это все неплохо описано здесь и здесь. Первый документ -- общее описание, позволяет понять что такое FAT, ее основную идею и основные структуры, второй является спецификацией и содержит более детальное описание, с примерами кода.

Надо заметить, что рамки, налагаемые на размер кода довольно узкие. Размер сектора -- 512 байт (надеюсь, меньше не бывает), за вычетом двух байт на сигнатуру, шестидесятидвух байт на параметры файловой системы (в случае FAT12/16) и девяноста байт в случае FAT32. В итоге на все про все -- 420 байт для FAT32 и 448 байт для FAT12/16. Несмотря на это, мне очень хотелось не превращать код в месиво инструкций, а сохранить некоторую структуру кода при минимальном его объеме. Т.е. иметь хотя бы несколько ф-ий. Сохранить структуру, думаю, отчасти, у меня получилось, а вот с размером я потерпел поражение: FAT12/32 занимают весь FSBS. Так что если вы найдете способ выкрасть хотя бы несколько байт, не меняя общей структуры -- сообщите пожалуйста, буду благодарен.

Сам загрузчик можно взять здесь:
FAT12
FAT16
FAT32
В случае, если ссылки сдохнут -- напишите в комментариях -- обновлю.

Теперь, после длинного предисловия можно приступать к описанию загрузчика.
Для чтения секторов с винчестера загрузчик использует три функции: две из них подготавливают параметры LBA и CHS -вызовов (prepare_LBA_call/prepare_CHS_call), третья (read_sectors) косвенно, по адресу в переменной 'PREPARE_ROUTINE_OFFS', вызывает одну из этих двух функций перед выполнением прерывания int 0x13. При получении управления загрузчик проверяет возможонсти BIOS выполнять LBA-вызов и в зависимости от результата сохраняет в 'PREPARE_ROUTINE_OFFS' либо адрес prepare_LBA_call либо prepare_CHS_call. Далее действия загрузчика для FAT12/16 и FAT32 несколько различаются. Дело в том, что организация корневого каталога в FAT12/16 и FAT32 имеет значительные отличия -- в FAT12/16 корневой каталог занимает жестко заданное количество секторов, следующие сразу за таблицами FAT, и является "системной" частью файловой системы, а в FAT32 корневой каталог разжалован до статуса обычного файла, номер его начального сектора хранится в блоке параметров файловой системы. Т.е. если раздел, с установленной FAT12/16 можно условно разделить на четрые части:
  1. Параметры файловой системы.
  2. Таблицы FAT.
  3. Корневой каталог.
  4. Данные.
то для FAT32 пункт три будет отсутствовать. Соответственно, загрузчик FAT12/16 для чтения корневого каталога:
  1. Высчитывает количество зарезервированных секторов + размер таблиц FAT, определяя тем самым его физическое расположение.
  2. Высчитывает размер корневого каталога и загружает его целиком в память.
Вот здесь мне пришлось пойти на небольшую сделку с совестью -- загрузчик не способен загружать данные размером более одного сегмента, т.е. 64К. Теоретически, размер корневого каталога ничем не ограничен. Т.е. возможна ситуация, когда чтение слишком большого корневого каталога приведет перезаписи уже считанной его части. К счастью, размер корневого каталога обычно не превышает шестнадцати килобайт, так что по этому поводу можно особо не беспокоиться.

В FAT32 корневой каталог представлен в виде обычного файла. Загрузчик читает его по кластерам с помощью функций read_cluster и read_next_clst_num, (подробнее о них дальше) и выполняет поиск файла 'LDR_NAME', в каждом считанном кластере. Это удобно тем, что максимальный размер кластера равен 64К, т.е. проблемы слишком большого корневого каталога в FAT32  не будет.

После чтения корневого каталога загрузчик выполняет в нем поиск файла с именем, заданным константой 'LDR_NAME'. Если файл с именем 'LDR_NAME' не найден загрузчик выводит сообщение 'LDR lost' и останавливается.

Когда искомый файл найден, загрузчик приступает к чтению его цепочки кластеров. К счастью, больших отличий между FAT12/16/32 на этой стадии нет. Для чтения используются две функции: 'read_cluster' и 'read_next_clst_num'. Как можно догадаться из названий, функция 'read_cluster' загружает кластер по переданному ей номеру, а функция 'read_next_clst_num' -- возвращает следующий номер кластера. Т.к. загрузчик не умеет оперировать сегментными регистрами (на это не хватило места), то чтение секторов, принадлежащих кластерам файла и сектора, содержащего следующий номер кластера происходит в одном сегменте. Алгоритм чтения цепочки кластеров выглядит так:
  1. Считать номер следующего кластера не увеличивая указатель буфера чтения. Сохранить номер следующего кластера в регистре.
  2. Считать текущий кластер, увеличить указатель буфера чтения на размер кластера.
  3. Если номер следующего кластера содержит признак конца цепочки перейти к пункту 4, иначе -- к пункту 1.
  4. Передать управление прочитанному файлу.
При написании read_next_clst_num для FAT12 вылезла небольшая проблема -- запись в таблице FAT для FAT12 может пересекать границу двух секторов. Спецификация предлагает просто загружать два подряд идущих сектора, что в моем случае не приемлемо. Если размер кластера равен одному сектору и размер сектора равен 512 байт, то в случае, когда считано уже 63,5КБ чтение двух секторов подряд приведет к перезаписи первых 512 байт считанного файла -- произойдет "зацикливание" адреса при переполнении. Чтобы этого избежать нужно проверять, не пересекает ли запись FAT границу секторов, и, в случае если пересекает, "дочитывать" следующий сектор не увеличивая указатель буфера чтения. Ничего особо страшного в этом нет, но ННое количество байт этот код занимает.

После того, как файл считан загрузчик передает ему управление по адресу 'LDR_BASE:LDR_ENTRY_POINT'.

Немного наблюдений:
  1. Есть относительно простой (но совсем ненадежный!) способ спрятать файл без хуков, фильтров и т.д. -- для этого нужно поменять сигнатуру записи удаленного файла в каталоге с 0xE5 на 0x0. Все записи, содержащиеся в этом каталоге после записи с сигнатурой 0x0 не будут отображены. Сигнатура 0x0 означает удаленную запись и служит признаком того, что после нее записей в каталоге больше нет. Способ не надежен по той причине, что такая запись может быть перезаписана при создании в каталоге нового файла. Помимо этого chkdsk разоблачает такие штучки за раз, и, считая это ошибкой файловой системы восстанавливает "спрятанные" файлы в файлы *.CHK. В общем, 'just for fun'.
  2. Остается открытым вопрос, когда драйвер помечает свободными кластеры, принадлежащие каталогу и содержащие только записи удаленных файлов. Интереса ради я создал 65533 пустых файла в каталоге, потом удалил. Драйвер даже в ус не подул. А вместе с тем, 2МБ на винчестере эти записи занимают.
  3. Драйвер FAT от BSD 4.5 при создании файлов не старается разместить их кластера подряд (в отличие от драйвера от MS), а напротив задает кластерам какие-то фантастические номера. Сделано ли это от большого ума или наоборот я не разбирался. Для тестирования, впрочем, весьма удобно. Помимо этого драйвер FAT32 от BSD 4.5 преподнес мне неприятный сюрприз: он считает, что 2 байта перед сигнатурой 0xAA55 в FSBS также являются сигнатурами и должны быть обнулены. Об этом ни слова нет в спецификации. Пришлось срочно выискивать возможность уменьшить объем кода для FAT32 на 2 байта.
  4. Драйвер FAT от MS не производит никаких действий над секторами принадлежащими последнему кластеру файла, но лежащими "за пределом" размера файла. Т.е. если файл занимает 10 байт и размер кластера 8 секторов, то 7 последних секторов кластера останутся нетронутыми до тех пор, пока файл не "подрастет". А до того их можно свободно использовать. Помимо этого, если раздел занимает количесто секторов, не кратное размеру кластера, то в конце раздела останется незанятое место.
    1.  
Thursday, October 19th, 2006
1:44 am
I'm back!

Написал небольшой загрзучик. Завтра надо описать все поподробнее.
Saturday, April 15th, 2006
3:35 pm
Программка :)
Забавная программка на FASM'е -- копирует сама себя в стеке и передает управление в новую копию. Пока не свалится от переполнения :).

format PE

include 'win32a.inc'

entry start

start:
call $ + 5
mov eax, [esp]
add eax, 0x13
mov ecx, 0x6
l00p:
push dword [eax]
sub eax, 0x4
dec ecx
jge l00p
jmp esp
call ExitProcess

data import
library kernel32,'KERNEL32.DLL'
import kernel32,ExitProcess,'ExitProcess'
end data
Wednesday, March 1st, 2006
2:53 am
MBR
MBR*

Задача MBR (Master Boot Record) предельно проста. Однако, чтобы лучше понять как происходит загрузка компьютера и что именно находится в компетенции MBR надо заглянуть "чуть-чуть назад" и "чуть-чуть вперед".

Итак, при включении компьютера управление получает BIOS. BIOS производит настройку и программирование устройств на самом низком уровне -- контроллеров и т.п. Для нас самым интересным и важным, пожалуй, является тот факт, что BIOS настраивает т.н. "вектор прерываний" -- область памяти, содержащую таблицу, по которой процессор сопоставляет номер прерывания и адрес процедуры его (прерывания) обработки. В том числе происходит и назначение уже известного нам обработчика 0x13ого прерывания. Подробнее о действиях BIOSа в процессе загрузки можно прочитать тут.
Интересно отметить, что процессоры x86 начинают выполнение инструкций в реальном режиме, с адреса FFFF:0, оставляя всего лишь 16 байт на инструкцию. Соответственно, разместить там возможно всего несколько инструций. Наиболее верятно, это будет инструкция "jmp", после выполнения которой регистр счетчика инструкций (IP/EIP) должен указать куда-либо "пораньше". К сожалению, я так и не понял, каким образом происходит отображение кода, записанного в микросхеме BIOSа в память. Соответственно, вопрос, как и кем интсрукция jmp, а также код, на который она прыгнет  будут отображены в память остается открытым.

После того, как все готово к запуску системы BIOS загружает "первый" сектор винчестера в память, по адресу 0x7C00 и передает туда управление (jmp). Почему я взял слово "первый" в кавычки? Потому что сейчас довольно сложно сказать о физическом расположении этого сектора. Если смотреть на адрес сектора с т.з. LBA -- это будет нулевой сектор. Если с т.з. *CHS/AssistedLBA -- головка 0, цилиндр 0, сектор 1. Впрочем, это даже и не важно. Главное понимать, что BIOS считает с винчестера в память определенный и заранее известный сектор, после чего передаст ему управление. Нетрудно догадаться, что именно там и находится MBR.

Небольшое лирическое отступление.
А для чего же нужна MBR?

Винчестеры по некоторым причинам иногда необходимо разбивать на разделы, или "партиции". Например, если мы хотим установить две ОС на один винчестер необходимо разделить его между ними. Тогда, каждая ОС может разместить свою файловую систему на одном (или нескольких) разделах, не повреждая данных файловой системы другой ОС. Собственно в MBR хранится таблица разделов, а так же код, обрабатывающий таблицу и выполняющий на основании обработки некоторые действия. Необходимо отметить одно из важнейших понятий MBR -- активный раздел. Активный раздел это раздел, который содержит код, необходимый для продолжения загрузки системы. Как правило, активным можеть быть только один раздел. Скорее всего, это будет раздел с загрузчиком ОС, или загрузочный менеджер, типа LILO или GRUB (в случае, когда на винчестере имеется несколько ОС). Собственно, все действия кода MBR сводятся к поиску активного раздела в таблице разделов, загрузке первых его 512 байт в память и передаче им управления.

Теперь, имея представление о том, для чего нужна MBR и что она делает можно разобрать ее структуру более детально.
Начнем с того, что MBR занимает ровно 512 байт. Из них 64 байта отведено под таблицу разделов, 2 байта -- сигнатура MBR и 446 -- код, обрабатывающий таблицу разделов. На описание одного раздела отводится 16 байт. Соответственно, максимальное число разделов, которые возможно описать в MBR равно 4. Все эти данные скомпонованы в MBR таким образом:

Таблица 1.
Смещение Размер Описание
0 446 Исполняемый код MBR.
446 16 Элемент таблицы разделов №1
462 16 Элемент таблицы разделов №2
478 16 Элемент таблицы разделов №3
494 16 Элемент таблицы разделов №4
510 2 Сигнатура MBR

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

Таблица 2.
Смещение Размер Описание
0 1 Признак активности. Определяет, является ли раздел активным (0x80) или нет (0x0)
1 3 Координаты начала раздела. Хранятся в формате CHS. Первый байт описывает номер головки, 6 младших бит второго байта описывают номер сектора и 2 старших бит второго байта и 8 бит третьего -- номер цилиндра.
4 1 Идентификатор файловой системы. Несмотря на название это поле не обязательно описывает ФС, для которой предназначен раздел. Идентификатор может описывать тип раздела, например, "LBA VFAT (DOS Extended)", или "DOS Extended". Играет весьма важную роль в определении того, какие поля элемента таблицы разделов должны быть использованы в определении границ раздела. (Подробности ниже.)
5 3 Координаты конца раздела. Формат в точности соответствует формату поля "Координаты начала раздела".
8 4 Количество секторов (не байт!), предшествующее разделу.
12 4 Количество секторов (не байт!), занимаемое разделом.


Теперь, имея всю необходимую информацию рассмотрим картину работы кода MBR в целом:
  1. BIOS заканчивает подготовку оборудования, считывает MBR с устройства, указанного в настройках BIOSа как загрузочное, размещает ее в памяти по линейному адресу 0x7C00 и передает ей управление (jmp). Перед передачей управления MBR BIOS должен проверить ее сигнатуру и сообщить о некорректности MBR если сигнатура не равна 0x55AA. (Однако, не все BIOSы это делают.)

  2. Код MBR (первые 446 байт) разбирает таблицу разделов в поисках активного (0x80) раздела.

  3. Если активный раздел не найден, код должен вывести соответсвующее сообщение и остановиться. Опять же, не все MBR это делают.
    Если же активный раздел присутствует, то MBR выполнит следующие шаги:
    • Переместит себя "вниз", на линейный адрес 0x600.
    • Считает первые 512 байт активного раздела (загрузочный сектор файловой системы) по линейному адресу 0x7C00 (т.е. на свое предыдущее место).
    • Проверит сигнатуру загруженного сектора. Вот тут вот тоже когда как, по-моему...
    • Передаст управление в загрузочный сектор файловой системы (jmp).
      Начиная с этого момента действия зависят от ОС, установленной на активном разделе.

В общем, ничего особо сложного и сверхестественного MBR не выполняет. Отметим несколько ключевых моментов, которые, время от времени, вызывают небольшие недоразумения.
  • Во-первых, MBR может описывать только 4 раздела и не более. Каждый раздел, в свою очередь может содержать какое-то количество логических разделов, однако, это зависит от ОС, использующей раздел и в самой MBR никак не отображено.

  • Во-вторых, код MBR, теоритически не должен взаимодействовать с пользователем -- его задача описана выше. Однако, т.к. таблица разделов включена в MBR, то в принципе код MBR может хозяйничать как ему вздумается.

Немного технических интересностей.
  1. По каким-то неизвестным мне причинам, дорожка винчестера, на которой размещается MBR, никогда не может быть использована каким-либо разделом. Т.е. мы всегда имеем 62 свободных и никем не занятых сектора, находящихся сразу за MBR. Причем, эти сектора всегда будут свободны, независимо от разметки винчестера. Подозреваю, что они оставлены для возможности расширения MBR, но точно не уверен. Это пространство (31,5 КБ -- весьма прилично для кода на ассемблере) запросто может быть использовано хакерами, желающими спрятать данные. Файловыми системами они повреждены не будут, и, более того, файловые системы даже и не подозревают об их существовании. Слышал, что некоторые вирусы активно используют эту дорожку. Изначально (при отсутствии вирусов :) ) эти 62 сектора заполнены нулями.

  2. Если внимательно присмотреться к таблице 2, то можно увидеть, что координаты начала и конца раздела хранятся в формате CHS и на них стандартно отводится 3 байта. А это опять приводит нас к ограничению 8 ГБ. К счастью, помимо этих значений, таблица содержит избыточные данные -- количество секторов перед разделом и размер раздела в секторах. Именно они и используются для определения расположения раздела в том случае, если его размер или позицию не возможно описать в формате CHS. В этом случае в поля "начало раздела" и "конец раздела" (CHS) просто записываются максимальные значения. Здесь надо быть осторожным: если раздел будет использоваться ОС, которая работает с винчестером через старый интерфейс int0x13 (например, DOS), то она будет явно удивлена столь странной геометрией. (И возможно испортит данные других ОС). Для различения разделов, расположение которых может быть описано в формате CHS от тех, чье не может, используется идентификатор файловой системы. Например, 05 -- "DOS Extended" (координаты корректны!) и 0F "LBA VFAT (DOS Extended)"  (координаты не корректны). Т.о. ОС может определить, как ей высчитывать расположение раздела.

  3. Максимальный размер раздела сейчас. Собственно, это ограничение вытекает из предыдущего пункта: Современные ОС определяют расположение раздела по двум последним полям элемента таблицы разделов. А это значит, что максимальный размер раздела не может быть более 2^41 байт (2 террабайта), а также его расположение не может быть "дальше" этого значения. Соответственно, когда винчестеры достигнут этих размеров нас ждут очередные изменения в ПО. Думаю, что особенно сильно изменится формат MBR.


*Понимание материала достигнуто благодаря статьям:
http://www.mossywell.com/boot%2Dsequence
http://www.tldp.org/HOWTO/Large-Disk-HOWTO.html
FreeBSD: ручная разметка диска
Tuesday, February 14th, 2006
2:16 pm
Extended Int13
Extended Int13*

Итак, это последнее, что я могу сказать о способах адресации данных на винчестере. Насколько я понимаю, ничего кардинально нового после этого добавлено не было.

Для начала, стоит кратко обобщить то, что есть на данный момент.

1.
С некотрого времени винчестеры и использующие их приложения использовали относительно простую схему:
Приложение ->BIOS ->Controller.
Приложение взаимодействовало с BIOSом, адресуя данные в формате CHS. При этом существовало сугубо программное ограничение на максимальный размер винчестера -- 504 МБ. В общей сложности на размер адреса (в BIOSе) отводилось 20 бит. Размер сектора -- 512 байт. Присутствие контроллера абстрагировало нас от физического размещения данных.

2.
Через некоторое время 512 МБ стали критически малы -- требовалось расширить возможный диапазон адресации, сохранив при этом совместимость с существующим ПО. Выход был найден путем использования 4 "лишних" бит, отведенных в BIOSе для номера головки и введением множителя. Система получила название E(nhanced)CHS и позволяла адресовать уже 8 ГБ.
Взаимодействие приложений с винчестером происходило теперь так:
Приложение ->BIOS (ECHS ->CHS) ->Controller.

3.
Т.к. Win95 && DOS имели некоторые проблемы, связанные с невозможностью адресовать 256 головок, перед трансляцией ECHS происходила "пре-трансляция" RevisedECHS.
Приложение ->BIOS (RevisedECHS ->ECHS ->CHS) ->Controller.

4.
LBA. LBA представляет собой новый формат адресации дисковых блоков, представляя все пространство дисковых блоков винчестера в виде линейной последовательности от 0 до 2^64 и избавляя нас от понятия геометрии вообще, помогая еще больше абстрагироваться от физической реализвции носителя информации. Помимо этого, важно понимать, что LBA -- это именно новый режим адресации, (не больше но и не меньше), описывающий схему работы с контроллером винчестера.

5.
Для возможности работы приложений, использующих BIOS для доступа к винчестеру, поддерживающему LBA используется режим AssistedLBA, выполняющего преобразование CHS-значения в LBA-значение.


Итак, мы имеем: приложения, использующие BIOS для доступа к винчестеру, и передающие ему CHS-значения, и контроллер, умеющий работать с CHS и возможно LBA значениями. BIOS все еще имеет программное ограничение на максимальный адрес сектора, отводя для адреса 3 байта, т.е. 2^24. При размере сектора 512 байт это 8ГБ.

Extended Int 13
Надо отметить, что большинство современных ОС (Windows и *NIX) не используют BIOS в процессе работы с винчестером -- сразу после загрузки работа с винчестером ведется через драйверы винчестера, которые не имеет названных ограничений. Однако проблемы могут возникнуть в случае, когда диск разбит на разделы и активный раздел находится "вне досягаемости", т.е. его начало лежит "дальше" 8ГБ, т.к. код, получающий управление от BIOSа (MBR) должен загрузить первые 512 байт активного раздела с помощью int13 и передать ему управление.


Чтобы полностью снять ограничение 8ГБ и дать возможность программам использующим работающими с функциями BIOSа использовать всю мощь LBA, BIOS был дополнен расширенным прерыванием int13. Теперь для указания номера требуемого сектора необходимо заполнить шестнадцатибайтную структуру (пакет), 8 байт которой отведены под LBA-адрес, и передать адрес структуры в функцию BIOSа. Предельно просто.
Взаимодействие с винчестером в таком случае будет выглядеть так:
Приложение ->(Extended Int 13, LBA вызов)BIOS ->Controller.

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


*Понимание материала достигнуто благодаря статье http://www.mossywell.com/boot%2Dsequence
[ << Previous 20 ]
About LiveJournal.com