пятница, 7 декабря 2012 г.

Нужны ли недокументированные API?


В чем прелесть высокоуровневых языков программирования?
Программист перестает задумываться о том "как оно на самом деле фунциклирует" и выдает на гора тонну не сопровождаемого кода, который иногда даже работает, но периодически подтормаживает. Более того, частенько, он даже не собирается об этом задумываться, так как у него банально отсутствуют необходимые знания (увы, так сейчас учат).

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

А потом, выполнив очередной умный "паттерн" он начинает разбираться - где же тормозит алгоритм. Причем, если программист более настырен, он изучает реализацию VCL и иногда даже докапывается до сути, где получается так, что тормоза упираются в вызовы известных ему по документации API, пройдя к которым он со спокойной душой останавливается и закрывает тикет в багтрекере фразой: "функция ХХХ тормозит, вариантов обхода нет".

Не встречались с ситуацией?
Значит повезло...


четверг, 29 ноября 2012 г.

Изучаем отладчик, часть третья


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

Сразу-же оговорюсь, в противостоянии приложение/отладчик, всегда победит последний :)
Но, только в том случае, если им будет пользоваться грамотный специалист, а с такими спецами бороться практически бесполезно (ну, если вы конечно не обладаете как минимум такой же квалификацией).

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

Вот что-то такое мы и рассмотрим, только в очень упрощенной форме.

понедельник, 19 ноября 2012 г.

Изучаем отладчик, часть вторая


В первой части статьи были рассмотрены некоторые нюансы работы  с интегрированным отладчиком Delphi - не все конечно, но наиболее необходимые разработчику. Теперь задача выглядит несколько иначе: рассмотрим его работу изнутри на примере его исходного кода. Для того чтобы не сильно утомлять вас описанием API функций и не разжевывать все этапы отладки, описывать его работу я буду на примере класса TFWDebugerCore. Часть не особо важных моментов я опущу, при желании вы их сможете уточнить просмотрев код данного класса.

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

Если же вы ранее никогда не сталкивались с самостоятельной реализацией отладчика, но заинтересованы в ней, то как минимум вы должны начать с данной ссылки: Debugging and Error Handling
По ней вы сможете узнать об основных аспектах отладки, как-то структурной обработке исключений, работой с отладочной информацией, минидампами. Работой с образом исполняемого файла, заголовками, секциями, картой памяти процесса, что такое RVA и VA и прочее-прочее.
Но это только если захотите разобраться во всей этой кухне.

Я же попробую описать только часть ее более простым языком, дабы у вас была точка, от которой можно было-бы оттолкнуться, если вы вдруг заинтересовались, ну и конечно, если вы реализуете защиту приложения, то вникнуть в тонкости работы отладчика вам необходимо как минимум (а иначе-то как по другому?).

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

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


среда, 24 октября 2012 г.

Изучаем отладчик, часть первая


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

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

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

Объем статьи получился неожиданно большим, поэтому я разбил ее на три части:

  1. В первой части будут рассмотрены возможности интегрированного в IDE Delphi отладчика, даны рекомендации по наиболее оптимальному его использованию и общие советы по конфигурации среды. Материал данного раздела предназначен как начинающим разработчикам, так и более подготовленным специалистам.
  2. Во второй части статьи будет рассмотрена изнаночная сторона работы отладчика на примере его исходного кода, подробно рассмотрены механизмы, используемые им при отладке приложения, показаны варианты модификаций памяти приложения, производимые отладчиком во время работы.
  3. В третьей части статьи будет рассмотрено практическое использование отладчика на примере обхода защиты приложения, использующего некоторый набор антиотладочных трюков.
Собственно, приступим.


среда, 12 сентября 2012 г.

Итоги семинара Embarcadero посвященному выпуску Delphi XE3 в Москве 12.09.2012

Сразу оговорюсь, я работаю в Delphi 2010, поэтому я шел на семинар с достаточным интересом,  ожидая увидеть значительные нововведения в ХЕ3 по сравнению с 2010. Нет, ну конечно я следил за изменениями в ХЕ и ХЕ2, но как-то так получилось, что данные продукты у меня не получилось пощупать собственноручно. Подготавливаться к семинару начал загодя. В связи с тем что в последнее время в интернете прошла череда достаточно неприятных новостей, рисующих достаточно туманную для Delphi перспективу, сразу же составил примерный список вопросов по данным пунктам. (А новости были разноплановые, от полного разгона команды разработчиков и передаче разработке в Румынию, до недавней, о запрещении разработки Client/Server на версии Professional). Так-же в список были включены вопросы о FireMonkey в связи с претензиями к ее стабильности и т.п. Часть вопросов помогли составить участники форума "Мастера Дельфи".

Вопросов получилось почти два десятка (на часть из них ответы к сожалению получить не удалось, по разным причинам) и с учетом того что на секцию вопросов отводилось в программе семинара всего 30 минут, становилось понятно, что если даже буду их задавать один я, то все равно не получится уложиться в отведенный промежуток времени, а ведь есть еще и другие участники со своим "наболевшем" :)

Изначально на семинар собиралась достаточно большая команда разработчиков с форума "Мастера Дельфи", но как-то так получилось что в итоге к отелю "Корстон", что на улице Косыгина, мы прибыли в составе "малого пехотного подразделения" всего лишь из трех человек :) Отсидев вступительную часть Джона Томаса было принято стратегическое решение, чтобы узнать необходимую информацию и не пропустить семинар, мы решили разделиться. Оставив одного из коллег в зале, со вторым я выдвинулся в сторону курилки, где и была организована засада :)


вторник, 11 сентября 2012 г.

Уход из под отладчика срывом стэка


Версия 1.0.1 

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

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

Немножко поделюсь и я своими наработками с обоих сторон баррикад :)
А именно способом ухода от отладчика через срыв стека.

В момент анализа стороннего ПО мы работаем как правило со отладчиком. Одним из главных элементов отладки является наличие валидного стэка отлаживаемой нити. На основании стэка нити отладчик (а если ему не под силу, то дополнительные скрипты) достаточно подробно строят стэк вызовов процедур, на базе которого происходит часть анализа кода. На стэке лежат параметры вызова функций. На том-же стэке хранятся SEH фреймы. Ни один вызов функции не обходится без стэка. Собственно стэк наше все :) (Глава 6. Базовые сведения о потоках)

В случае если вы каким либо образом обнаружили отладчик, то не стоит сразу делать TerminateProcess(), его отловят и вернут как было. Если сломать "механизм стэка", то отладка вашего приложения в большинстве случаев станет не возможна. А отловить код поломки уже на порядок сложнее, чем перехватить пресловутый Halt или TerminateProcess.
Смысл стрыва стека сводится к генерации необрабатываемого исключения PAGE_FAULT, после чего процесс можно только закрыть. Дальнейшая его отладка бесперспективна.

Здесь я приведу четыре классических варианта срыва стэка:

1. Что есть стэк с точки зрения программиста в конкретной точке кода?
Это два регистра, EBP -  Base Pointer и ESP - Stack Pointer.
Изменив эти два значения на любые произвольные, мы разрушим стэк::
  asm
    mov ebp, 0
    mov esp, 0
  end;
2. Стек всегда растет вниз, в то время как сверху остаются некие данные в том числе и адреса возврата и SEH фреймы. Все эти данные доступны для чтения и модификации. Удалив их мы разрушим стэк.

procedure _FillChar(var Dest; count: Integer; Value: Byte);
asm
{     ->EAX     Pointer to destination  }
{       EDX     count   }
{       CL      value   }
 
        PUSH    EDI
 
        MOV     EDI,EAX { Point EDI to destination              }
 
        MOV     CH,CL   { Fill EAX with value repeated 4 times  }
        MOV     EAX,ECX
        SHL     EAX,16
        MOV     AX,CX
 
        MOV     ECX,EDX
        SAR     ECX,2
        JS      @@exit
 
        REP     STOSD   { Fill count DIV 4 dwords       }
 
        MOV     ECX,EDX
        AND     ECX,3
        REP     STOSB   { Fill count MOD 4 bytes        }
 
@@exit:
        POP     EDI
end;
 
procedure TForm1.btnKillUpStackClick(Sender: TObject);
var
  P: Pointer;
begin
  _FillChar(P, MaxInt, 0);
end; 

Здесь используется реализация  FillChar из Delphi 7. В более старших версиях данная продцедура выполнена несколько другим способом и не дает необходимого эффекта.

3. Границы стека всегда обрамлены страницами с флагом PAGE_GUARD. Это можно наглядно увидеть в данном примере. Механизм работы данного флага следующий, при обращении к участку памяти с данным флагом срабатывает исключение Access Violation и данный флаг снимается. После чего при повторном обращении к этому участку срабатывает PAGE_FAULT.
В данном коде используется локальный статический массив для ускорения переполнения стека.

procedure TForm1.btnKillStackOnGuardClick(Sender: TObject);
 
  procedure T;
  var
    HugeBuff: array [0..10000] of DWORD;
  begin
    if HugeBuff[0] <> HugeBuff[10000] then
      Inc(HugeBuff[0]);
    T;
  end;
 
begin
  try
    T;
  except
    T;
  end;
end;

4. Срыв стека на рекурсивном вызове SEH фрейма. Логика данных фреймов проста, после установки они обрабатывают все исключения до тех пор, пока не будут сняты. В Delphi они представлены в частично обрезанном виде в качестве оберток try..finally/except. Идея заключается в том, что после установки SEH фрейма мы не производим его удаления и в нем-же генерируем ошибку, заставляя рекурсивно вызывать самого себя. В результате мы имеем переполнение стека, плавно перерастающее в PAGE_FAULT.

procedure TForm1.btnKillStackOnSEHClick(Sender: TObject);
begin
  asm
    lea   eax, @KillStack
    push eax
    push dword ptr [fs:0]
    mov   [fs:0], esp
    xor eax, eax
    mov eax, [eax]
  @KillStack:
    mov eax, 0
    call eax
  end;
end;

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

Подсветка кода выполнена при помощи: http://highlight.hohli.com/

среда, 5 сентября 2012 г.

Простой способ обнаружения эмуляторов ключа Guardant


При работе с ключом защиты Guardant (не важно какой модели) разработчик использует соответствующие API, при этом от него скрыт сам механизм работы с устройством, не говоря уже о протоколе обмена. Он не имеет на руках валидного хэндла устройства, пользуясь только адресом шлюза (т.н. GuardantHandle) через который идет вся работа. В случае если в системе присутствует эмулятор ключа (особенно актуально для моделей до Guardant Stealth II включительно) используя данный шлюз разработчик не сможет определить, работает ли он с реальным физическим ключом, или его эмуляцией. 

Задавшись в свое время вопросом: "как определить наличие физического ключа?", мне пришлось немного поштудировать великолепно поданный материал за авторством Павла Агурова в книге "Интерфейс USB. Практика использования и программирования". После чего потратить время на анализ вызовов API функций из трехмегабайтного объектника, линкуемого к приложению, в котором собственно и сокрыта вся "магия" работы с ключом. В итоге появилось достаточно простое решение данной проблемы не требующее использования оригинальных Guardant API. Единственный минус - все это жутко недокументированно и техническая поддержка компании Актив даже не будет рассматривать ваши вопросы, связанные с таким использованием ключей Guardant. Ну и конечно, в какой-то момент весь данный код может попросту перестать работать из-за изменений в драйверах Guardant.
Но пока что, на 5 сентября 2012 года, весь данный материал актуален и его работоспособность проверена на драйверах от версии 5.31.78, до текущей актуальной 6.00.101.


понедельник, 3 сентября 2012 г.

Теория использования электронных ключей защиты.

Версия 1.02
Security is a process, not a product. 
© Bruce Schneier


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

    Цель данной статьи - попробовать дать направление для фантазии разработчику ПО, приступившему к реализации защиты приложения с использованием ключей защиты. Впрочем, она может заинтересовать и практиков.

    По большей части все сказанное в статье было опробовано на ключах семейства Guardant, поэтому некоторые нюансы, как то предпродажная подготовка ключа, относится именно к ним. Общий-же подход к применению един для всех остальных линеек, Hasp, SenseLock, RocKey и т.п.

Что я могу сделать изначально?
Итак - у вас есть ключ и комплект разработчика (SDK).
Как правило в это-же время у вас уже есть на руках "горящий проект", который должен быть сдан уже вчера и полное отсутствие времени на изучение возможностей самого ключа. Начальство, конечно не поймет, что сам ключ еще нужно "освоить". Поэтому вам не остается ничего другого, как воспользоваться утилитами "автозащиты", поставляемыми вместе с SDK, навесив нечто, что "теоретически" воспрепятствует взломщику изучить Ваше ПО.

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

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

Что есть ключ?
1. это черный ящик - все что вы храните в нем будет недоступно взломщику (с оговорками, конечно)
2. это криптография блоков данных с закрытым алгоритмом и/или ключом (шифрование/хеширование/эцп)
3. реальная привязка к железу (не нужно думать о смене процессора и материнской платы - мы привязаны к ключу)
4. возможность создания триала как по времени, так и по количеству запусков (не на всех ключах)
5. изъятие кода приложения из его состава и исполнение внутри ключа (защита от отладки данной части кода, так-же не на всех ключах)

Как подготовить ключ к работе и начать его использовать?
Прежде чем использовать ключ, нужно его подготовить к работе. Разработчику ключ передается с настройками по умолчанию. Это означает что ваш ключ отличается от демонстрационного только кодами доступа.  Для исправления данного недостатка, ключ требует перепрошивки. На этапе перепрошивки разработчик помещает в память ключа свой собственный набор алгоритмов, со своими собственными уникальными дескрипторами, свой набор защищенных ячеек и свой уникальный блок данных в открытой памяти ключа, доступный только для чтения.
Если этого не сделать, то есть большая вероятность очень быстрого создания табличного эмулятора, т.к. для его создания пригодится обычный демонстрационный ключ распространяемый бесплатно в составе SDK.

Что не нужно ждать от ключа защиты?
Большинство взломов ПО происходит из-за беспечности разработчиков, чересчур полагающихся на наличие "железа" в их схеме защиты. Не стоит делать ставку на сам ключ - он действительно просто "железка", пользуясь которой неумело, разработчик может больше навредить, чем усилить защиту проекта. Вся ваша хитрая алгоритмическая защита, на которую было потрачено полгода времени, не выдержит и получаса, если вы будете делать ошибки при использовании самого ключа.

Как не допустить ошибок при работе с ключом?
1. Не полагайтесь только на проверку наличия ключа:
После перепрошивки ключа, используя SDK, вы можете определить факт его наличия в компьютере. Но нельзя полагаться только на проверку его наличия. Очень часто встречаются программы использующие ключи защиты только на уровне "есть устройство - работаем, нет  - закрываемся". Проблема в том, что примеры эмуляции наличия устройств идут в составе DDK, поэтому обойти такую проверку не составит труда, да и в принципе (зачем подключать "тяжелую артиллерию") достаточно банального "занопливания" такой проверки в теле исполняемого файла.

2. Используйте аппаратные алгоритмы ключа:
В составе SDK, поставляемого в комплекте разработчика, есть АПИ, реализация которых выполнена полностью софтверно, т.е. без участия аппаратного ключа защиты. Такие функции, конечно гораздо более производительны, чем аппаратное преобразование через ключ, но построив защиту только с их участием, разработчик должен задуматься, а зачем ему тогда нужен собственно САМ ключ?

3. Не используйте внешнюю библиотеку:
В составе SDK поставляются объектные файлы, линкуемые к приложению и библиотека. При разработке кода защиты рекомендуется использовать только объектные файлы, т.к. если вы будете работать с ключами посредством библиотеки, взломщик, зная описание функций, сможет примерно за час написать полноценный ее аналог (шлюз) и без всяких сложностей с драйвером снимет весь ваш обмен данными с ключом прямо в UserMode.

4. Не используйте устаревшие версии аппаратных алгоритмов:
Базовый алгоритм ключа Guardant Stealth I не соответствует сегодняшним требованиям к безопасности. Для совместимости со старыми программами он все еще поддерживается. Но, если вы будете использовать его в своих приложениях, то очень скоро обнаружите в интернете полноценный эмулятор вашего продукта. Поэтому при защите приложения ориентируйтесь на алгоритм GSII64 и его вариации.

5. Думайте :)


вторник, 21 августа 2012 г.

Отключение главной нити приложения от отладчика и уход от перехвата CreateFile()

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

Рассмотрим некий частный случай.
Например, когда требуется определить критическое место в приложении, допустим, открытие самого себя с целью проверить контрольную сумму приложения, устанавливается BP на API функцию CreateFile(), где мы будем ждать входного параметра с путем к нашему исполняемому файлу, после чего в отладчике пройдем до кода, вызвавшего данную функцию и приступим непосредственно к анализу.

Можно поступить даже еще проще, взяв утилиту Process Monitor, за авторством небезызвестных Марка Руссиновича и Брюса Когсвела.

Данная утилита, абсолютно спокойно показывает полный стек вызовов, включая интересующие нас адреса возврата.


Нам остается только запустить отладчик и установить BP на нужный адрес.


Правда данная утилита показывает уже адреса возврата, а не сам адрес вызова непосредственной функции. Но об этом позже.

Представьте, что тело нашего приложения уже изменено. Самым простым решением обхода проверки контрольной суммы приложения, будет подмена параметра lpFileName в перехваченной функции CreateFile() на путь к не измененному телу приложения. После данной операции не нужно даже изучать механизм расчета контрольной суммы, не важно что там применяется, проверка цифровой подписи, MD5 хэш или банальная CRC32. Т.к. данный алгоритм будет работать с телом оригинального приложения - все проверки будут успешно пройдены.

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

Одним из вариантов обхода CreateFile() является прямой вызов соответствующей функции ядра, в обход kernel32->kernelbase->ntdll. Результат такого вызова можно увидеть на картинке:


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

Для реализации данного алгоритма давайте разберемся, что происходит при вызове вот такого кода (delphi7 + Windows 7 32бит):

hFile := CreateFile(PChar(ParamStr(0)), GENERIC_READ,
    FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL, 0);

понедельник, 20 августа 2012 г.

Эмулируем GetLocalTime

Обычным приемом при анализе стороннего приложения, является установка BP на API функциях и исследование вызывающего данные функции кода. Как контрприем, применяется эмуляция  API  функций внутри тела приложения.
Например, при анализе триального приложения с привязкой ко времени, BP будет установлен на функции GetLocalTime(). Если проэмулировать ее вызов, то можно немного усложнить анализ кода приложения и/или уйти от различных утилит подменяющих дату, принцип которых построен на перехвате API . Ну как пример вот таких.

Вот так выглядит код эмуляции данной функции:
type
  KSYSTEM_TIME = packed record
    LowPart: DWORD;
    High1Time: Integer;
    High2Time: Integer;
  end;

  PKUSER_SHARED_DATA = ^KUSER_SHARED_DATA;
  KUSER_SHARED_DATA  = packed record
    TickCountLow: ULONG;
    TickCountMultiplier: Integer;
    InterruptTime: KSYSTEM_TIME;
    SystemTime: KSYSTEM_TIME;
    TimeZoneBias: KSYSTEM_TIME;
  end;

  TIME_FIELDS = packed record
     Year,
     Month,
     Day,
     Hour,
     Minute,
     Second,
     Milliseconds,
     Weekday: Short;
  end;

procedure GetLocalTime_Emul(var lpSystemTime: TSystemTime);
const
  MM_SHARED_USER_DATA_VA = $7FFE0000;
  USER_SHARED_DATA: PKUSER_SHARED_DATA = PKUSER_SHARED_DATA(MM_SHARED_USER_DATA_VA);

  Magic10000: LARGE_INTEGER = (LowPart: $E219652C; HighPart: Integer($D1B71758));
  SHIFT10000 = 13;

  Magic86400000: LARGE_INTEGER = (LowPart: $FA67B90E; HighPart: Integer($C6D750EB));
  SHIFT86400000 = 26;

  WEEKDAY_OF_1601 = 1;

Нюансы оптимизации. Битовый сдвиг вправо.

Оптимизация ради оптимизации не мной придуманное понятие. Столкнулся тут на днях...
Деление - достаточно трудоемкая для процессора операция.
Деление на два = битовый сдвиг значения на бит вправо, при этом сами затраты - 1 такт.
Казалось-бы оптимизация.
Но что мы получим в случае -1 (0xFFFFFFFF)?
На делении, как и полагается - ноль, а на сдвиге как и положено 0x7FFFFFFF
Удачной оптимизации :)