Welcome to TechNet Blogs Sign in | Join | Help

Унификация стиля кодирования в команде - тупичок с граблями.

Тема жёванная-пережёванная, так что я не буду подробно останавливаться на том, зачем нужен единый стиль кодирования в команде (или проекте). Основные тезисы:

  • Унифицированный стиль кодирования упрощает сопровождение кода. Это, кажется, единственная причина, зачем он вообще нужен. Все остальное, включая меньшее количество мигреней в минуту на одного эталонного разработчика, - побочные эффекты, направленные опять же на упрощение (читай - повышение эффективности) сопровождения кода.
  • Кажется, нет никакой разницы между разными стилями с точки зрения легкости понимания и написания кода. Любой выбранный стиль, будучи принят в команде упрощает сопровождение кода, при условии, что он один и используется.
  • Единственный способ выработать единый стиль – диктатура в том или ином виде. Можно месяцами спорить о том, где правильно ставить скобки, какие отступы должны быть, можно ли использовать две пустые строки в качестве разделителя и т.д. и т.п. В один прекрасный момент терпение лопается и волевым решением назначается «правильный» стиль.

И вот, скажем, ваша команда достигла этой точки: стиль выбран, в репозитории сидит куча кода, который нужно переписать в соответствии с выбранным стилем. Даже если учесть, что львиная доля правок будет носить косметический характер, их общее количество, необходимое для достижения заветной цели, очень велико. Логичное решение, которое принимает любая вменяемая команда – постепенный переход; вместе с нормальными исправлениями разработчики будут править стиль исправляемого участка кода. На этом почему-то все успокаиваются.

Далее все происходит по одному из двух сценариев:

  1. Разработчики возвращаются к насущным проблемам, все реже вспоминая про выбранный стиль (хотя и пытаясь, время от времени, ему следовать).
  2. После массовых правок кода, наиболее ретивые поклонники стиля наталкиваются на многочисленные конфликты при слиянии кода из разных веток; недолго чешут затылки, понимая, что регулярная морока с конфликтами им совсем не улыбается и решают, что ну его на фиг. Далее всё следует по сценарию №1.

Почему так случается? Очевидно потому, что факт выбора стиля никак не влияет на его интеграцию в процесс написания кода. Типичное «стиль выбран, давайте весь новый код писать в новом стиле» не работает. Аналогичная вещь случается когда пытаются вычистить все предупреждения компилятора если их количество больше 10 на каждый исходный файл проекта. В конце концов, оказывается, что есть более приоритетные задачи, а код вроде и так работает.

Что можно сделать в такой ситуации?

  • Во-первых, нужно выделить дополнительное время на правку кода. Иными словами будьте готовы, что каждое исправление будет занимать на 10% больше времени в течение N месяцев.
  • Во-вторых, нужно, чтобы соответствие вносимых правок принятому стилю контролировалось автоматически, т.е. при каждом коммите/чекине. Казалось бы это очень просто реализуется, существует множество автоматических стилизаторов кода. На практике же выясняется, что либо выбранный стиль не поддерживается ни одним из них, либо стилизатор недостаточно толерантен к вариациям стиля, исправляя то, что исправлять не следовало.
  • В-третьих, скрипты, используемые для запуска стилизатора, должны иметь гибкую систему фильтров, позволяющую задать какие файлы следует проверять, а какие должны игнорироваться при проверке. Фильтры должны храниться вместе в исходными файлами и быть доступны каждому разработчику, имеющему право коммитить/чекинить код. В самом начале должны проверяться, только новые файлы, а также существующие файлы, успешно проходящие проверку стилизатором. По мере внесения исправлений, исправленные файлы будут в список.
  • Аналогично, стилизатор должен позволять контролировать важность того или иного предупреждения. По мере уменьшения предупреждений определённого вида, их можно превратить в ошибки с тем, чтобы гарантированно избежать их появления в будущем.
  • Кроме всего прочего, в документ, описывающий выбранный стиль следует включить рекомендации о том, как избежать массовых правок кода при стилевых правках.

Вот такие вот мысли. Альтернатива всему этому – выработать иммунитет к любому, сколь угодно ужасному, стилю кода. :-)

Cross-posted from blog.not-a-kernel-guy.com.

Хозяйке на заметку.

Чтобы войти на машину с благозвучным названием “6XSD29G-052-18XS_32” под локальным пользователем, не нужно набивать полное имя пользователя как “6XSD29G-052-18XS_32\vasya”, проклиная при этом злого гения, запретившего Copy-Paste в поле ввода имени, и его коллегу, давшему компьютеру такое замечательное имя. Достаточно просто сказать “.\vasya” и все получится.

PS. Знал бы об этом пять лет назад – день жизни сэкономил бы уже наверное. Хотя мог бы и сам догадаться… :-)

Cross-posted from blog.not-a-kernel-guy.com.

Detours.

Сижу, разбираюсь как Detours перехватывает функции Win32 API. Сама идея известна. В начало функции пишется безусловный JMP на функцию перехватчик. Для того, чтобы вызвать оригинальную функции, её код, на место которого помещается JMP, копируется в буфер и дополняется безусловным JMP на первую нетронутую инструкцию.

Интересно, однако, не это, а сколько дополнительных проверок делается, чтобы повысить надежность этого метода:

  1. Поверяется, не указывает ли указатель на перехватываемую функцию, не на саму функцию, а на элемент таблицы импорта. Если так, то в качестве указателя берется imm32 из JMP;
  2. Проверяется длина функции, причем считается, что функция может завершаться RET, JMP (разные варианты) или INT3. Последние два варианта не очень очевидны, хотя при взгляде на код становится ясно, что к чему;
  3. После JMP, завершающего скопированный из функции код, пишется INT3. То же, на всякий случай;
  4. Код установки перехватчиков завернут в транзакцию. Устанавливаются (или снимаются) либо все перехватчики, либо транзакция откатывается. Впрочем, 100% гарантии это все равно не дает;
  5. Ну и плюс ко всему поддерживается статическая установка перехватчиков, как раз чтобы избежать проблем с динамическим перехватом.

В этом списке не хватает только поддержки hotpatching. Код, скомпилированный с поддержкой hotpatching можно править прямо на ходу, не останавливая потоки в процессе.

Cross-posted from blog.not-a-kernel-guy.com.

diStorm64 disassembler.

Наткнулся на хороший open source disassembler, понимающий и 80x86 и amd64, и распространяемый под BSD лицензией: diStorm64.

diStorm is a binary stream disassembler. It's capable of disassembling 80x86 instructions in 64 bits (AMD64, X86-64) and both in 16 and 32 bits. In addition, it disassembles FPU, MMX, SSE, SSE2, SSE3, SSSE3, SSE4, 3DNow! (w/ extensions), new x86-64 instruction sets, VMX, and AMD's SVM! diStorm was written to decode quickly every instruction as accurately as possible. Robust decoding, while taking special care for valid or unused prefixes, is what makes this disassembler powerful, especially for research. Another benefit that might come in handy is that the module was written as multi-threaded, which means you could disassemble several streams or more simultaneously.

В использовании прост как двери: на входе даётся кусок кода, разрядность и его виртуальный адрес, на выходе получается набор инструкций. Для каждой указывается мнемоника, операнды, префиксы и размер. В комплекте идет интерфейсный модуль для Python, что может быть полезно для всяких reverse engineering утилит.

Cross-posted from blog.not-a-kernel-guy.com.

Проверка параметров функции.

Предыдущий пост про параметры функций вызвал на удивление много споров, так что я еще покручусь немного вокруг этой темы. Заранее прошу прощения у тех, кому эта тема оскомину набила. Итак, как следует проверять параметры функции? Вернее даже так, что нужно и, самое главное, что не нужно проверять?

Само собой однозначного ответа на этот вопрос не существует, тем более что этот вопрос вторгается в область coding guidelines, а последних, как известно, существует ровно столько, сколько есть программистов на Земле. Или около того. Как минимум ответ зависит от следующих факторов:

  1. Языка программирования, операционной системы и программного окружения;
  2. Степени доверия вызываемой функции к вызывающему коду.

Пункт №1 мы, во избежание священной войны, положим константой – язык C/C++, OS – Windows, окружение – «обычный» user/kernel mode код.

А вот пункт №2 стоит обсудить детально. Сравним два случая:

  1. Вызов функции внутри одного бинарного модуля;
  2. Вызов сервиса ядра из пользовательского кода.

В отличие от «доверенного» вызова, службы ядра дополнительно проверяют, что:

  1. Все буфера целиком находятся в user space (ProbeXxx функции);
  2. Чтение/запись в user space завершается успешно (все операции с ними помещаются в блок try-except);
  3. Чтение любых данных из user space выполняется только один раз (данные копируются в локальный буфер);
  4. Все параметры, после того, как они были скопированы в kernel space, содержат допустимые значения (проверки на выход на пределы диапазона и прочее).

Очевидно, что дополнительные проверки нужны потому, что пользовательский код менее привилегирован, чем код ядра, и более подвержен сбоям/атакам. Несколько менее очевидно, что дополнительные проверки на самом деле нужны потому, что сами данные приходят из менее привилегированного источника. О том, почему это важно – чуть ниже.

Далее, данные никогда не представлены сами по себе. Данные всегда упаковываются в некий «контейнер». Контейнером могут быть языковые конструкции, например объект или переменная типа «int», или другие данные, например TCP пакет упаковывается в IP пакет. Следует различать проверки целостности контейнера и проверки целостности данных. В примере выше, пункты №№1 – 3 представляют ни что иное, как проверку целостности контейнера (буфера в user space). Проверка целостности самих данных представлена единственным пунктом №4.

Нужно ли при каждом вызове выполнять все проверки? Конечно - нет. Какие проверки можно опустить? В приведенном примере с вызовом функции в пределах одного модуля, можно ожидать, что свойства контейнера с данными не изменятся. А если и изменятся, то только под воздействием внешних факторов, которые мы все равно не в состоянии контролировать (в пределах данного модуля). Соответственно, при «доверенном» вызове проверять целостность контейнера нецелесообразно.

Если отвлечься от абстрактных контейнеров, это означает что большинство указателей при «доверенном» вызове не проверяется. Более того, такая проверка может быть вредна, так как затрудняет отлов ошибок, пряча попытки некорректного разыменования указателей.

Количество проверок целостности самих данных тоже можно сократить до минимума – до одной. В теории, конечно. Для этого используется анализ потоков данных (Data Flow Analysis, DFD), суть которого состоит в том, что:

  1. Идентифицируются все участники обмена данными (т.е. все компоненты кода и внешние источники);
  2. Определяются характер передаваемых данных, а также, откуда и куда они передаются;
  3. Определяется уровень привилегированности каждого участника обмена данными.

Передача данных от менее привилегированного к более привилегированному участнику считается передачей через границу доверия (Trust Boundary), при которой принимающая сторона должна проверить целостность полученных данных, включая как целостность контейнера, так и целостность данных. При составлении DFD диаграммы следует помнить, что данные (например, IP пакет) могут сами выступать в роли контейнера для другого компонента. В таком случае, для инкапсулированных данных граница доверия будет пролегать на один или несколько компонентов дальше.

Итак, резюме:

  1. Следует различать контейнер и сами данные, и понимать, что проверка их целостности может выполняться разными компонентами;
  2. Проверка целостности контейнера выполняется только в случае, если передача данных идет через границу доверия. DFD диаграмма должна помочь в обнаружении всех границ доверия;
  3. Проверка целостности данных производится первым потребителем этих данных.

Cross-posted from blog.not-a-kernel-guy.com.

Смотри, мама, я в телевизоре!

Мое интервью Russian Channel 9:

Alexey Pakhunov at Russian Channel 9

PS: Забавно наблюдать за собой со стороны. Столько ляпов, просто жуть! :-)

Cross-posted from blog.not-a-kernel-guy.com.

Posted by alexeypa | 0 Comments
Filed under:

Undefined behavior – это все, что явно не указано в документации.

Навеяно постом про ExUuidCreate и в частности вот этой фразой:

Well, I suggest MSFT to documented this behavior, or at least explain this case in documentation.

Вкратце, суть статьи сводится к тому, что функция ExUuidCreate может изменять содержимое возвращаемого буфера даже в том случае, если она возвращает ошибку. Далее, в комментариях, завязался спор на тему имеет ли функция право трогать буфер в случае ошибки.

Для начала небольшое объяснение почему эта функция имеет право делать с буфером всё что угодно. ExUuidCreate объявлена следующим образом:

NTSTATUS 
ExUuidCreate(
OUT UUID *Uuid,
);

Т.е. она пишет сгенерированный GUID в буфер, выделенный вызывающей стороной. Тут важно, что параметр объявлен как “OUT” параметр. Посмотрим, что говорит документация:

__out: The function will only write to the buffer. If used on the return value or with _deref, the function will provide the buffer and initialize it. Otherwise, the caller must provide the buffer, and the function will initialize it.

__out (это тоже самое, что и OUT), указывает на то, что:

  1. Вызываемая функция будет только писать в буфер;
  2. Вызывающая сторона отвечает за выделение буфера.

Ни слова о состоянии буфера в случае успешного или неуспешного вызова. Но, состояние буфера в случае успешного выполнения описано в документации на саму функцию:

Uuid: Pointer to a caller-allocated UUID (GUID) structure that is set to a new UUID value.

Далее нам понадобиться немного дедукции. Фактически у нас осталось два случая:

  1. Функция возвращает ошибку, содержимое буфера не изменилось;
  2. Функция возвращает ошибку, содержимое буфера изменилось.

Если считать, что функция не должна изменять содержимое буфера в случае ошибки, то где-то в документации должно быть соответствующее требование. Однако же, такого требования там нет, соответственно правомочны оба варианта. Вывод – состояние “OUT” параметра неопределенно в случае неуспешного вызова.

Какой из этого следует вывод? Мне кажется, он достаточно очевиден: всё, что явно не описано в документации - не определено. В свете этого, можно определить критерии полноты документации: документация полна, если все задуманные аспекты поведения функции (модуля, класса и т.д.) описаны. Если документация описывает детали конкретной реализации, то такая документация избыточна и несколько вредна, так как усложняет внесение изменений в код в будущем.

Cross-posted from blog.not-a-kernel-guy.com.

… и концы в воду.

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

...
> FooBar: parsing Z:\temp\tmp1234.tmp
> FooBar: error XXXX, line 123, Z:\temp\tmp1234.tmp
...

Небольшая заминка была только в том, что утилита аккуратно удаляла «Z:\temp\tmp1234.tmp» вслед за неудачной попыткой его разбора. Действительно, кому придет в голову посмотреть, что там в линии 123, если этот файл и не парсится-то толком, правда?

Ну, файл мы, конечно, восстановили, с проблемой разобрались, но осадок остался. О чем думал писавший эту утилиту не понятно. К сожалению, это далеко не единственный случай, когда возможность понять что произошло приносится в жертву, эээ…, стремлению прибраться за собой. Хотите примеров? Их есть у меня.

nmake.exe позволяет создавать временные файлы с помощью вот такого синтаксиса:

<<
abc
123
<<[KEEP | NOKEEP]

Во время выполнения, текст между скобками копируется в файл, имя которого подставляется в выполняемую команду. Опции KEEP и NOKEEP указывают время жизни файла. По-умолчанию или если указана опция NOKEEP, файл будет удален по завершению выполнения всех команд. Кроме невозможности посмотреть содержимое этого файла (между скобками можно использовать макросы), это означает, что при случае, разработчик не сможет просто скопировать ошибочную команду из лога, чтобы повторить её. Вместо этого ему придется запускать нужную команду или всю последовательность команд через nmake. Это может показаться не очень большой проблемой, но только до того момента, как вам придется разбираться почему вдруг не собирается компонент написанный не понятно кем, несколько лет назад и ни разу не менявшийся с тех пор.

Похожие проблемы возникают сплошь и рядом там, где создаются разного рода response файлы, позволяющие обойти ограничение на размер командной строки. Почему-то авторы скриптов любят их удалять после завершения компиляции. Зачем? Не понятно.

Cross-posted from blog.not-a-kernel-guy.com.

Почему GetModuleFileName возвращает “system32” для 32-х битных библиотек под Wow64?

Вопрос:

32-х битный процесс запущен на 64-х битной системе. Почему для некоторых библиотек GetModuleFileName возвращает путь вида “%SystemRoot%\syswow64”, а для других - “%SystemRoot%\system32”, не смотря на то, что и первые, и вторые находятся в “%SystemRoot%\syswow64”?

Ответ:

Потому что загрузчик понятия не имеет о Wow64 и перенаправлении файловой системы в частности. Загрузчик просто сохраняет полное имя модуля во время загрузки и возвращает его копию в GetModuleFileName. Соответственно если библиотека была загружена как “%SystemRoot%\system32\foobar.dll”, то и GetModuleFileName вернёт эту строку.

Это же верно и для других Win32 функций.

Cross-posted from blog.not-a-kernel-guy.com.

Ценителям логичности и простоты cmd.exe посвящается…

Хорошая статья: Особенности реализации командного процессора cmd.exe операционных систем WinNT. Напрочь отбивает всякое желание связываться с написанием скриптов для cmd.exe. :-)

Cross-posted from blog.not-a-kernel-guy.com.

Малышку верните!

На днях у одной из тестовых машин, занимающих жизненное пространство у меня в офисе, обнаружились проблемы с перегревом. Вернее сказать не у неё, а у машин из той же самой партии компьютеров. Под это дело организовали установку дополнительных кулеров на все подобные машины и, что б два раза не вставать, - обновление BIOS. Я, надо сказать, никаких проблем с перегревом не замечал, но мало ли. Ставят – чего ж отказываться.

И вот в день X приходит человек, ставит дополнительный кулер, обновляет BIOS и уходит. Однако, вскоре после его ухода, обнаруживается одна неприятная штука – 4 ядерный процессор на этой машине стал вдруг одноядерным. По крайней мере, операционная система видит только одно ядро. Ага, подумали сибирские мужики, BIOS обновили, а настройки сбросились в значения по умолчанию. Ничего подобного. Настройка «включить только одно ядро» действительно есть, но выставлена она в «выкл.» - как положено.

Безрезультатно перепробовав разные комбинации «околопроцессорных» настроек в BIOS перешел к следующему подозреваемому. Дело в том, что незадолго до обновления, я установил на той машине другую версию системного ядра вместе с HAL. Надо сказать, что изменения в ядре никак не могли повлиять на количество определяемых процессоров, но всякое бывает. Новое ядро было установлено параллельно со старым (конфигурация была скопирована с помощью BCDEDIT.EXE и для копии были заданы опциями KERNEL и HAL), так что я просто перезагрузился со старым ядром. Не помогло. По-прежнему обнаруживается только один процессор. Значит все-таки BIOS.

Пишу письмо, мол, - «Не хочу я такого апгрейда. Верните всё назад, как было. Да и вообще оно и не перегревалось совсем». Тот же человек возвращается, ставит предыдущую версию BIOS, перезагружает машину и, конечно же, система по-прежнему видит только одно процессорное ядро. Гм.

После еще одного сеанса медитации с бубном, звонков с выяснением, а та ли это версия BIOS и не нужно ли чего еще обновить, пришла в голову светлая мысль перезагрузиться в Windows 2003 и, о чудо! – W2K3 увидел все 4 ядра. Человек обрадованный уходит. Я, соответственно, опечаленный остаюсь.

Выдвигается следующая гипотеза – всё дело в том, что при установке системы не вводился серийный номер. Обычно тестовая OS ставиться с помощью системы автоматического тестирования, которая берет на себя все тяготы активации, а в тот день она немного поломалась, так что операционную систему я ставил руками. На следующий день переустанавливаю систему «как положено» и она видит все четыре ядра. Замечательно! Ставлю модифицированное ядро и HAL – снова виден только один процессор. Бум. Красивая гипотеза про отсутствующий серийный номер идет прахом.

Перезагружаюсь обратно в исходное ядро – теперь там тоже виден только один процессор, что очень странно, ведь я не изменил ни одного файла в системе – только создал дополнительную загрузочную конфигурацию. В конце концов, я догадался проверить версию HAL, который я устанавливал. Тогда-то и выяснилось, что я устанавливал однопроцессорный HAL вместо многопроцессорного. После смены HAL все заработало как положено. К сожалению, ошибка с версией HAL никак не объясняет каким образом новая загрузочная конфигурация повлияла на старую, ведь «HAL» не является глобальной опцией.

Cross-posted from blog.not-a-kernel-guy.com.

Posted by alexeypa | 0 Comments

Они наступают!

Вот примерно так выглядит мой офис на сегодняшний день (снималось телефоном, качество – соответствующее):

My office

Их там десять штук, если считать тот, что попал в кадр только частично.

Cross-posted from blog.not-a-kernel-guy.com.

Posted by alexeypa | 0 Comments
Filed under:

«Большие» функции в коде.

Вопрос из комментариев:

Не могли бы прокомментировать ответ “Lepsik” по порядкам в Microsoft (тред):

автор - Диез

1, 2. Естественно, полтора - это величина условная. Просто большая длина обычно требует более одного движения для полного обзора :)

3. Никто не мешает сделать методы того же класса, но часто удобнее и логичнее разнести код на уровни, т.е. в отдельные классы (а то и в отдельные библиотеки).

Вообще, все это есть у Фаулера :)

это просто у вас программы маленькие. :)

в больших компаниях Microsoft/IBM/SONY, …. таких правил нет. У нас есть методы с телом в сотню экранов. А файл с методом тела процесса больше мегобайта.

За весь Microsoft не скажу. Расскажу, что видел сам.

Во-первых, такие правила, «coding guidelines», - есть. И они не сильно отличаются от тех образцов, что можно найти в Сети. Их специфика больше зависит от истории продукта или группы, ими пользующейся. Скажем, команды, так или иначе относящиеся к организации Microsoft Office, вполне ожидаемо используют схожие стили написания кода. Схожие, но не обязательно одинаковые. Единого, годного на все случаи жизни стандарта нет.

Во-вторых, ограничение на размер функции/метода носит все же рекомендательный характер. Существует масса причин, по которым существование длинных функций может быть оправдано. Скажем функции инициализации ядра, те которые инициализируют различные подсистемы, - длиннющие простыни. Их можно было бы разбить на функции поменьше размером. Логика там не очень сложная, хотя и далеко не линейная. Но это тянет на масштабный рефакторинг кода, преимущества которого не очевидны, а риск поломать что-либо весьма велик.

Далее, функции вроде NtGetSystemInformation представляют собой очень длинный switch, механическое разделение которого на отдельные функции ничего хорошего не даёт. Преимущества же переписывания его на нечто вроде таблицы виртуальных функций совсем не очевидны.

Не стоит забывать про генерируемый код. Автоматические генераторы не ведают про существование «coding guidelines». Ну и много чего ещё есть. Самое главное, что важность соблюдения разумных размеров функций стоит далеко не на первом месте. Куда важнее писать работающий и безопасный код.

Cross-posted from blog.not-a-kernel-guy.com.

Долгое молчание.

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

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

Анализ одной уязвимости в Flash.

Томас Пташек (Thomas Ptacek) анализирует нечеловеческий эксплоит Марка Доуда (Mark Dowd), использующий уязвимость в Flash.

Эксплоит использует целочисленное переполнение, возникающее из-за того, что Flash runtime использует знаковое число, прочитанное из SWF файла, как беззнаковый размер выделяемого блока памяти. В итоге Flash пытается выделить несколько гигабайт памяти, получает в ответ NULL, но не проверяет его, а пишет 32-х разрядное число по смещению от полученного нулевого указателя. Марк вычислил, что если смещение X превышает 0x80000000 и при этом X+4 делится без остатка на 12, то результирующий адрес получается валидным. Записываемое 32-х битное значение получается из исходного 16-ти разрядного числа вычитанием значения еще одной переменной. Иными словами, ни указатель, ни записываемое значение не контролируются хакером полностью.

Тем не менее…

Flash использует ActionScript, который является ни чем иным как версией JavaScript, скомпилированный в байт код. Подменяя байт код, хакер может получить доступ к любому участку памяти в процессе. Проблема только в том, что Flash runtime проверят байт код на корректность перед выполнением и хакер не может модифицировать байт код просто так.

Тем не менее…

Для повышения производительности Flash runtime использует два немного отличающихся набора проверок. Первый используется при загрузке кода, а второй во время выполнения программы. Запись 32-х разрядного числа по смещению от нулевого указателя может изменить таблицу длин байт-кодов, что делает некоторые байт-коды длиннее, вводя в заблуждение загрузчик кода. В тоже время интерпретатор выполняет все инструкции, в том числе и те, что спрятаны хакером внутри ставшими вдруг длинными (с точки зрения загрузчика) байт-кодов! Если бы только не одна сложность. Код, добавленный хакером, не должен менять поведение оригинального кода, иначе это будет не троянский конь, а слон в посудной лавке.

Тем не менее…

Экплоит Марка не нарушает функциональность существующих байт-кодов; внедряемый трояном код восстанавливает измененные при внедрении кода значения в стеке; и к тому же для успешного внедрения используется комбинация байт кодов и x86 инструкций.

Кроме того, этот эксплоит работает под IE и Firefox, не смотря на разные версии Flash, которые использую эти браузеры. И наконец, он работает и на Vista, так как Flash не использует ASLR.

PS: Охренеть, простите за мой французский.

Cross-posted from blog.not-a-kernel-guy.com.

Posted by alexeypa | 0 Comments
More Posts Next page »
 
Page view tracker