вторник, 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
Удачной оптимизации :)