Показаны сообщения с ярлыком assembler. Показать все сообщения
Показаны сообщения с ярлыком assembler. Показать все сообщения

среда, 29 апреля 2015 г.

Анализ задачи №18 от Александра Алексеева (ака GunSmoker)

Кажется я первый раз попал в тупик.
Не то, чтобы я сильно умный, но и задачка — не "Балтика 9".

Первая часть задачи выглядела вот так:

Что не так с этим кодом?

procedure TForm1.Button1Click(Sender: TObject);
var
  Wnd: HWND;
 
  function EnumWindowsProc(const AWnd: HWND; const AParam: LPARAM): BOOL; stdcall;
  begin
    if AWnd = Wnd then
      Caption := 'OK';
    Result := True;
  end;
 
begin
  Wnd := Handle;
  EnumWindows(@EnumWindowsProc, 0);
end;

Приступим.

вторник, 18 ноября 2014 г.

Пара слов о кэшировании данных при чтении и смартпойнтерах

Заначка - что-либо прибережённое, припрятанное про запас ◆ Опять же всё расставив по местам, как и было, достал из-за трюмо тёткину заначку — вскрытую пачку «Любительских», — закурил. Андрей Битов, «Сад», 1960–1963 г. (цитата из Национального корпуса русского языка, см. Список литературы)


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

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

среда, 9 апреля 2014 г.

Анализ приложения защищенного виртуальной машиной

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

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

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

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

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

В качестве реципиента, по совету одного достаточно компетентного товарища, я выбрал немножко старый (но не потерявший актуальности, в силу качества исполнения) keygenme от небезызвестного Ms-Rem.

Вот первоначальная ссылка, где он появился: http://exelab.ru/f/index.php?action=vthread&forum=1&topic=4732
А потом он попал вот сюда: http://www.crackmes.de/users/ms_rem/keygenme_by_ms_rem/
Где данному keygenme был выставлена сложность 8 из 10 (*VERY VERY* hard).
Хотя, если честно, это слегка завышенная оценка - я бы поставил в районе 5-6 баллов.

Пожалуй, начнем.


четверг, 14 ноября 2013 г.

Карта памяти процесса

Задумывались ли вы над тем, как именно используется память, доступная вашей программе, да и вообще, что именно размещается в этих двух-трех гигабайтах виртуальной памяти, с которыми работает ваше ПО?

Спросите, зачем?
Ну как же, для 32-битного приложения 2-3 гигабайта – это ваш лимит за пределы которого без использования AWE вы выбраться не сможете, а контролировать собственные ресурсы все же желательно. Но даже и без этого просто с целью разобраться...

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

Давайте посмотрим, как именно программист работает с памятью при отладке (особенно при отладке стороннего приложения, проще говоря, при реверсе):

1. Как правило, самой частой операцией будет поиск значения в памяти приложения и, к сожалению, данный функционал почему-то не предоставлен в отладчике Delphi (собственно, как и в MS VC++).
2. Модификация системных структур (PEB/TEB/SEHChain/Unwind/директорий PE-файлов etc...) будет происходить гораздо проще, когда поля структур размаплены на занимаемые ими адреса и представлены в читабельном виде.
3. Отслеживание изменений в памяти процесса (практически никем не предоставляемый функционал, реализованный в виде плагинов к популярным отладчикам). Действительно, зачем трассировать до посинения, когда достаточно сравнить два снимка карты памяти, чтобы понять, тут ли происходит нужная нам модификация данных или нет?

Да, собственно, вариантов использования много.

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

Самая удобная реализация от OllyDebug 2, но, к сожалению, она не отображает данные по 64 битам (все еще ждем).

OllyDebug 2 - Process Environment Block Dump

VMMap от Марка Руссиновича выполняет чисто декоративные свойства, да красиво, да за подписью Microsoft, но практически применить выводимые ей данные тяжеловато.

VMMap отображает только самую базовую информацию не вникая в детали.

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

Process Hacker - Memory Dump

Ну а к карте памяти от IDA Pro за столько лет работы с ней я так и не привык (мне не удобно) :)

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

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

воскресенье, 26 мая 2013 г.

Правильное применение сплайсинга при перехвате функций подготовленных к HotPatch

В прошлой статье я рассмотрел пять вариантов перехвата функций включая их вариации.

Правда в ней я оставил не рассмотренными две неприятных ситуации:
1. Вызов перехваченной функции в тот момент, когда ловушка снята.
2. Одновременный вызов перехваченной функции из двух разных нитей.

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

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

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

Перехват сплайсингом через JMP NEAR OFFSET или PUSH ADDR + RET (наиболее уязвимый к данным ошибкам) рассмотрен не будет, т.к. по хорошему, без реализации дизассемблера длин, заставить данный вариант перехвата работать как нужно не получится.


пятница, 26 апреля 2013 г.

Атомарные операции

Буквально на днях ко мне обратились с вопросом.

А зачем нужен префикс LOCK, или его аналог InterlockedDecrement при вызове процедуры _LStrClr из модуля System. Данная процедура декрементирует счетчик ссылок строки и при его обнулении освобождает память, ранее занятую строкой.

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

В принципе предпосылка интересная, но...

воскресенье, 27 января 2013 г.

Реализация перехвата вызовов API

Версия 1.0.1 

Монитор. Отличное слово, правда, объединяющее в себе целую кучу понятий. Ну например впервые данное слово было применено в 1861 году к броненосцу "USS Monitor". Чуть позже данным словом стали называть дисплеи. Еще через некоторое в славную когорту мониторов вошли более привычные нам вещи, наподобие счетчиков производительности, а вместе с ними целая куча разнообразного ПО, основной задачей которого является наблюдение - то есть мониторинг.

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

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

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

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

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

В итоге, дабы не объяснять в следующий раз все заново, я решил сделать обзорную статью где попробую рассказать все максимально простым языком о том, "как это работает" :)

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

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


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

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

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

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

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

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


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

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

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

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

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

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


вторник, 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/

вторник, 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;