воскресенье, 31 июля 2011 г.

Driver verifier problem

Тестер проверял драйвер моего tdi фильтра под верифаером, со всеми включенными опциями, поймался bsod:

DRIVER_VERIFIER_IOMANAGER_VIOLATION (c9)
The IO manager has caught a misbehaving driver.
Arguments:
Arg1: 00000208, This IRP is about to run out of stack locations. Someone may have forwarded this
    IRP from another stack.
Arg2: ee8e99ff, The address in the driver's code where the error was detected.
Arg3: 86b5ef68, IRP address.
Arg4: 00000000

...

STACK_TEXT: 
f7675fc8 8065fe0b 0000004c 000000c9 f7675fe8 nt!KeBugCheckEx+0x1b
f7676150 80660571 f767642b 8068f090 00040000 nt!ViBugcheckHalt+0xc3
f76763f4 80660657 80693630 00000208 f7676420 nt!VfBugcheckThrowException+0xa1
f76764e4 806639ac 00000208 00000009 ee8e99ff nt!VfBugcheckThrowIoException+0xb5
f7676510 80663dce 86b5efd8 877faf00 00000002 nt!IovpExamineIrpStackForwarding+0x1dc
f7676584 80656116 001b4950 862c30e8 862df168 nt!IovpCallDriver1+0x1de
f76765ac ee8e9b20 f76765e4 ee8e99ff 861edf18 nt!IovCallDriver+0x8e

После анализа оказалось, что верифаер изменяет число stack locations, так что абсолютно корректный код:

static NTSTATUS ForwardIrp( __in PDEVICE_OBJECT deviceObject, __in PIRP irp )
{
    RT_ASSERT( irp );
    RT_ASSERT( deviceObject );   

    IoSkipCurrentIrpStackLocation( irp );

    return IoCallDriver( deviceObject, irp );   
}

Будет работать без верифаера, и будет падать при включенном верифаере.
Проблема и решение также описана тут: http://www.osronline.com/showthread.cfm?link=139736

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

суббота, 30 июля 2011 г.

Windows Logical Prefetcher

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

При старте процесса он отслеживает первые 10 секунд его работы(таймаут задается через реестр), затем запоминает имена файлов, смещения, а также метаданные фс к которым был произведен доступ, после чего данная информация записывается в файл в директории %SystemRoot%\Prefetch.

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

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

Prefetcher может ускорять не только запуск приложений, но и уменьшать boot-time системы.

Настройки префетчера храняться в реестре:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters

Для его включения нужно установить EnablePrefetcher = 3 (Disabled = 0, Application = 1, BootUp = 2, Application AND BootUp = 3), хотя по умолчанию он и так включен.

Там же прописан таймаут работы префетчера при первом запуске - AppLaunchTimerPeriod.

Пример префетч файла: C:\WINDOWS\Prefetch\VMMAP.EXE-3B5AFAED.pf (вывод утилитой от Руссиновича Strings):

SCCA
VMMAP.EXE
C-p
Xa%
0pL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\NTDLL.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\KERNEL32.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\UNICODE.NLS
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\LOCALE.NLS
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\SORTTBLS.NLS
\DEVICE\HARDDISKVOLUME2\INSTALLEDTOOLS\VMMAP\VMMAP.EXE
...
Структура .pf файла проста - заголовок + смещения + юникодные имена файлов.
Также для префетч файла есть несколько требований - это наличие сигнатуры в заголовке(SCCA), сам файл должен быть меньше 16мб, смещения не должны вылезать за пределы файла ( это проверяется ф-цией PfWithinBounds ).

Что касается имени префетч файла, например VMMAP.EXE-3B5AFAED.pf, то 3B5AFAED это хеш от пути, хеш подсчитывается следующей ф-цией:

#define RNDM_CONSTANT    314159269
#define RNDM_PRIME        1000000007

ULONG CcPfHashValue( PVOID Key, ULONG Len )
/*
Routine Description:
    Generic hash routine.

Arguments:
    Key - Pointer to data to calculate a hash value for.
    Len - Number of bytes pointed to by key.

Return Value:
    Hash value.
*/
{
    char *cp = Key;
    ULONG i, ConvKey=0;

    for ( i = 0; i < Len; i++ )
    {
        ConvKey = 37 * ConvKey + (unsigned int)*cp;
        cp++;
    }

    return ( abs( RNDM_CONSTANT * ConvKey ) % RNDM_PRIME );
}

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

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters\HostingAppList: DLLHOST.EXE,MMC.EXE,RUNDLL32.EXE

Ф-ция ответственная за проверку принадлежит или нет процесс данному списку называется CcPfIsHostingApplication.

Что касается реализации, то префетчер запускается при запуске первичного потока, цепочка вызовов выглядит так:

NtCreateThread => PspCreateThread => KeInitThread( ..., PspUserThreadStartup, ...):

VOID PspUserThreadStartup( IN PKSTART_ROUTINE StartRoutine, IN PVOID StartContext )
{
...
    if (CCPF_IS_PREFETCHER_ENABLED())
    {
        //
        // If this is the first thread we are starting up in this process, prefetch the pages likely to be used when initializing the application into the system cache.
        //

        if ((Process->Flags & PS_PROCESS_FLAGS_LAUNCH_PREFETCHED) == 0)
        {
            OldFlags = PS_TEST_SET_BITS(&Process->Flags, PS_PROCESS_FLAGS_LAUNCH_PREFETCHED);

            if ((OldFlags & PS_PROCESS_FLAGS_LAUNCH_PREFETCHED) == 0)
            {
                if (Process->SectionObject)
                {
                    //
                    // Notify cache manager of this application launch.
                    //

                    CcPfBeginAppLaunch( Process, Process->SectionObject );
                }
            }
        }
    }

    //
    // Queue the initial APC to the thread
    //

    KeRaiseIrql (APC_LEVEL, &OldIrql);

    KiInitializeUserApc( PspGetBaseExceptionFrame (Thread),
                         PspGetBaseTrapFrame (Thread),
                         PspSystemDll.LoaderInitRoutine,
                         NULL,
                         PspSystemDll.DllBase,
                         NULL);

    KeLowerIrql (PASSIVE_LEVEL);
...
}

Сам префетчинг происходит через перечисление содержимого директории ZwQueryDirectoryFile с классом FileNamesInformation, и загрузкой секций(CcPfPrefetchSections) через IoCreateFile + ZwCreateSection и метаданных (CcPfPrefetchMetadata), но тут уже идет обращение к ФС через ZwFsControlFile(..., FSCTL_FILE_PREFETCH,...).

Работа префетчера по сбору данных идет асинхронно через рабочие потоки, совершенно прозрачно для процесса.

Остался один неосвещенный вопрос - как именно префетчер узнает к какому файлу(секции) было обращение при первом запуске приложения?
Ответ - через отслеживание ошибок страниц.

Цепочка ф-ций, при страничном фолте:

_KiTrap0E => MmAccessFault => MiDispatchFault => MiCompleteProtoPteFault => CcPfLogPageFault.

VOID CcPfLogPageFault(IN PFILE_OBJECT FileObject, IN ULONGLONG FileOffset, IN ULONG Flags )
{
...
    SECTION_OBJECT_POINTERS *sectionObjectPointer = FileObject->SectionObjectPointer;
...
}

С каждым процессом связана трасса - структура данных, позволяющая вести связи между собираемыми данными. Полученный PSECTION_OBJECT_POINTERS сохраняется в этой структуре, также извлекаются и сохраняются DataSectionObject и ImageSectionObject.

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

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

вторник, 28 июня 2011 г.

Как поймать то, чего нет?

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

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

У меня был на руках только путь к перемещенному файлу. Поэтому пришлось придумывать нечто новое. И это новое было придумано.

Решение основывалось на системном механизме, называемом кеш менеджер. Информации по нему достаточно, поэтому описывать целиком не вижу смысла. Основная идея - кеширование файловых потоков, файловые потоки это не только данные файла, но и метаданные ФС, такие как Extended Attributes/Alternate Data Streams (а вот Reparse Point врятли кешируются).

Для моего случая интересен факт того, что каждый раз при 1й записи ( то есть при создании исполняемого файла ), файловая система вызывала
один из 4х интерфейсов кеш менеджера, чтобы закешировать файловые потоки ( предварительно создав и инициализировав CacheMap и прочие внутренние структуры связанные с файлом ). Узнать закеширован ли тот или иной файл можно, посмотрев соот-ю структуру FILE_OBJECT, а точнее поле PrivateCacheMap, если оно не нулевое - файл закеширован.

У кешменеджера есть рабочие потоки выполняющие операции write-behind/read-ahead. Write-behind скидывает содержимое кеша на диск с интервалом 1-3 секунды, если это не сделано явно. То есть можно записать в файл, а данные все равно какоето время будут в кеше, т.е. в виртуальной памяти, а не на диске. Таким образом накапливая данные в кеше, и сбрасывая их спустя время одномоментно, уменьшается количество дисковых операций ввода-вывода(если быть более точным, то уменьшает число позиционирования головок жесткого диска). Собственно это и есть главная ф-ция кешменеджера. Read-ahead в свою очередь выполняет упреждающее чтение файлов, когда инициирована операция чтения.

Также, с кешем связан еще один важный механизм, называемый fast I/O.
Везде, где возможно, чтение и запись в закешированные файлы разруливается через высокоскоростной механизм fast I/O.
Fast I/O означает чтение и запись кешированных файлов без работы по генерации IRP.
Это дает существенное быстродействие в некоторых случаях(пример - memory-mapped files).

Именно fast i/o запросы к ФС при создании процесса для получения метаданных(уже не помню, но скорее всего там идет получение EA) позволили ловить запуск несуществующих процессов.

Конкретно для моей задачи нужно было ловить FastIoQueryOpen в минифильтре.

Эквивалент операции FastIoQueryOpen для минифильтра это IRP_MJ_NETWORK_QUERY_OPEN.
Поэтому регистрируем эту операцию, устанавливаем Pre и Post колбеки.

В Pre колбеке просто возвращаем FLT_PREOP_SUCCESS_WITH_CALLBACK, чтобы вызвался Post колбек, а уже в нем проверяем статус операции
( на момент Post колбека операция завершена ), и если тот равен STATUS_OBJECT_NAME_NOT_FOUND, то просто сравниваем имя файла для данной операции, со списком сохраненных ранее файлов. Если совпало - это запуск процесса.

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

вторник, 21 июня 2011 г.

hex-rays или собственный декомпилятор?

Наткнулся недавно на ветку http://exelab.ru/f/index.php?action=vthread&forum=3&topic=15481&page=11, в которой веселые ребята собираются реверсить hex-rays (декомпилятор сишного кода ввиде плагина к IDA, если кто не знает).

Собсно маразм данной идеи даже не в том, что нужно реверсить больше мегабайта бинарного кода, а в том, что это вобще предлагается.
Ведь написать аналог hex-rays в сотни раз проще, чем реверсить бинарь, к тому же необязательно делать декомпилятор ввиде плагина и писать на с++, когда есть IDA-python например, питон для подобных целей гораздо предпочтительнее, т.к. придется работать с довольно сложными конструкциями.

Далее я попытался понять, может быть есть какие-то сложности с построением скажем дерева нодов?
Может быть это какаято архи-сложная задача и я чего-то не понимаю?

Открываем свежие версии IDA/IDA-python, смотрим api, видим классы с заманчивыми названиями FlowChart/BasicBlock/GraphViewer.
Таки да, "все уже украдено до нас" (с)
FlowChart возвращает заботливо построенный за нас список BasicBlock's, а BasicBlock содержит помимо node id, start/end адресов для блока, еще и списки из нодов, на которые ссылается текущий и нодов которые ссылаются на текущий.
Таким образом есть вся достаточная инфа для построения бинарного дерева нодов, которое потребуется впоследствии для свертки оного в Сишные конструкции (if/else/switch и т.д.).

Несколько минут шаманства и готов вывод блоков в окно представленное классом GraphViewer:



Таким образом построение нодов если и вызывала какие-то телодвижения на старых версиях IDA/IDA-python, то сейчас делается буквально в несколько строк.

Какие еще проблемы могут возникнуть при написании декомпиля уровня хекс-рейз?

Свертка нодов в if/else? Да ни разу не проблема
Поиск свитчей/циклов/seh'ов? Тысячи раз обсуждалось, куда статей на эту тему, абсолютно ничего сложного
Отслеживание цепочек live-register's? Элементарно делается
Распарсить type library? Тоже ерундовое дело

Даже не знаю что вызовет затруднение, разве что просто сесть и начать?
Имхо, за неделю будет уже что-то подобное хекс-рейзу версии 1.1.
Да, если писать декомпилятор С++ придется изрядно попотеть.
Но не нужно забывать, что hex-rays и сам то С++ непереваривает, это сишный декомпилятор.

четверг, 16 июня 2011 г.

И не баг и не фича

Что же тогда? Архитектурная особенность! Речь пойдет об уязвимостях.

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

Это buffer/heap/integer overflows, format string vulnerabilities, use after free, race conditions и так далее, имя им легион.

Но всех их объединяет одна вещь - так или иначе, они могут быть выявлены автоматически/полуавтоматически, либо на этапе компиляции( компиляторы, статические анализаторы и т.д. ), либо по методу черного ящика ( fuzzing, binary code analysis и т.д. ).

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

Мне вспомнилось всего два примера таких уязвимостей, это:

1) (MS06-001) уязвимость в Windows Metafile files (.wmf) файлах

"Архитектурная особенность" в данной уязвимости позволяет абсолютно документированно содержать код в .wmf файлах и выполнять его, причем сидела эта ф-ция незамеченной со времен Windows 3.0 с 1990го года по 2006й, 16 лет.

2) (MS10-046) уязвимость в .lnk файлах, которую явил миру Stuxnet.

"Архитектурная особенность" в библиотеке shell32.dll приводит к тому, что открываемый ярлык (.lnk файл) приводит к загрузке и выполнению explorer'ом вредоносной .dll(для так называемые dynamic icons) путь к которой содержится в нем же самом.

Эта же особенность, но уже в самом формате .lnk файла позволяет файлу находиться вобще на удаленном компе ( Lnk File Format => File Location Info => Offset to the network volume table => Network share name ) в расшаренных папках.

Такого рода уязвимости будут всегда находиться только человеком, по крайней мере до тех пор, пока не придумают AI :)

p.s. Если вспомните еще какие-либо уязвимости подобного типа - пишите в комментах.

суббота, 21 мая 2011 г.

Анти-обход перехвата APC dispatcher'a

Нашел у себя в закромах интересный способ обхода перехвата диспетчера APC, который придумал Clerk:

Запуск пользовательского потока в обход перехвата диспетчера APC(R3).
 o Восстанавливаем RtlpCalloutEntryList, для исключения глобальных VEH.
 o Создаём удалённый поток.
 o Формируем SEH-фрейм.
 o Взводим TF в контексте.
Поток стартует с взведённым TF. После исполнения первой инструкции диспетчера апк (KiUserApcDispatcher()) генерируется трассировочное исключение. Управление получает диспетчер исключений (KiUserExceptionDispatcher()). Векторные обработчики не вызываются (RtlCallVectoredExceptionHandlers()). Вызывается установленный SEH. Если диспетчер APC захвачен сплайсингом, останов генерируется после исполнения джампа. Если диспетчер APC перенаправлен на диспетчер исключений, посредством установки точки останова, прежде будет вызван SEH(если хэндлер установлен как VEH).

А я затем задумался, можно ли перехватить диспетчер APC в ring3, чтобы даже при установленном TF можно было сплайсить себе наздоровье?

Казалось бы - нет, нереально. Даже учитывая тот факт, что исключение будет сгенерировано лишь на второй инструкции, это ничего не дает.
Да, можно вместо первой записать short jump, но после прыжка опять таки окажемся в SEH.
Сбросить TF флаг наверное также не получится, т.к. lea edi, [esp+arg_C] это всего 4 байта. См. код диспатчера:

.text:7C90EAC0 _KiUserApcDispatcher@20 proc near
.text:7C90EAC0
.text:7C90EAC0 arg_C           = byte ptr  10h
.text:7C90EAC0
.text:7C90EAC0                 lea     edi, [esp+arg_C]
.text:7C90EAC4                 pop     eax
.text:7C90EAC5                 call    eax
.text:7C90EAC7                 push    1
.text:7C90EAC9                 push    edi
.text:7C90EACA                 call    _ZwContinue@8   ; ZwContinue(x,x)
.text:7C90EACF                 nop

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

Речь идет о фиче Single-Stepping on Branches, Exceptions, and Interrupts.

Устанавливаем BTF флаг (Single-step on branches) в регистре IA32_DEBUGCTL MSR.
Intel пишет, что исключение будет сгенерировано процессором только при branch, services an interrupt, or generates an exception, то есть мы отключаем по сути TF флаг до первого бранча. А это дает возможность спокойно перехватывать диспетчер APC сплайсингом, но через call myFunc, а не через джамп. А уже в myFunc можно делать все что угодно, включая сброс TF флага.

p.s. Кстати как дополнение, всплыл способ обнаружения( до этого мне не известный ) VmWare и VirtualBox - они не виртуализируют IA32_DEBUGCTL MSR, т.е. детект выглядит как запись в IA32_DEBUGCTL MSR любого значения и чтение его, прочитанное значение всегда будет нулем в виртуалках.

воскресенье, 15 мая 2011 г.

Про статический анализ бинарного кода

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

Пример:

открываем notepad.exe в IDA, находим кусок высокоуровнего seh'а(scopetable entry):

.text:01001898 stru_1001898    _msEH <0FFFFFFFFh, offset loc_100752A, offset loc_100753E>

Смотрим сами обработчики - ссылок из кода на них нет, но(!) есть data xref:

.text:01007528                 jmp     short loc_1007557
.text:0100752A
.text:0100752A loc_100752A:                            ; DATA XREF: .text:stru_1001898
.text:0100752A                 mov     eax, [ebp+ms_exc.exc_ptr]
.text:0100752D                 mov     ecx, [eax]
.text:0100752F                 mov     ecx, [ecx]
.text:01007531                 mov     [ebp+var_28], ecx
.text:01007534                 push    eax
.text:01007535                 push    ecx
.text:01007536                 call    _XcptFilter
.text:0100753B                 pop     ecx
.text:0100753C                 pop     ecx
.text:0100753D                 retn
.text:0100753E
.text:0100753E loc_100753E:                            ; DATA XREF: .text:stru_1001898
.text:0100753E                 mov     esp, [ebp+ms_exc.old_esp]
.text:01007541                 mov     esi, [ebp+var_28]
.text:01007544                 cmp     [ebp+var_1C], 0
.text:01007548                 jnz     short loc_1007551
.text:0100754A                 push    esi             ; int
.text:0100754B                 call    ds:_exit
.text:01007551 ; ---------------------------------------------------------------------------

IDA прекрасно все находит и резолвит, т.к. есть:

.text:0100739D                 push    70h
.text:0100739F                 push    offset stru_1001898
.text:010073A4                 call    __SEH_prolog

А теперь идем в тот же notepad.exe, делаем поиск по ф-ции GetSystemTimeAsFileTime, по xref прыгаем на место вызова её и видим:

.text:010070CC sub_10070B1     endp
.text:010070CC
.text:010070CC ; ---------------------------------------------------------------------------
.text:010070CF                 db 5 dup(0CCh)
.text:010070D4 ; ---------------------------------------------------------------------------
.text:010070D4                 mov     edi, edi
.text:010070D6                 push    ebp
.text:010070D7                 mov     ebp, esp
.text:010070D9                 sub     esp, 10h
.text:010070DC                 mov     eax, dword_1009604
.text:010070E1                 test    eax, eax
.text:010070E3                 jz      short loc_10070EC
.text:010070E5                 cmp     eax, 0BB40h
.text:010070EA                 jnz     short loc_1007139
.text:010070EC
.text:010070EC loc_10070EC:                            ; CODE XREF: .text:010070E3
.text:010070EC                 push    esi
.text:010070ED                 lea     eax, [ebp-8]
.text:010070F0                 push    eax
.text:010070F1                 call    ds:GetSystemTimeAsFileTime
.text:010070F7                 mov     esi, [ebp-4]

Опачки, ф-ция не распозналась IDA'ой. Превратим принудительно её в ф-цию нажав p, после чего видно, что на ф-цию нет перекрестных ссылок, её никто не вызывает из данного модуля. То есть в графе, если им описать данный модуль по xref'aм, эта ф-ция будет отсутствовать.

На логичный вопрос - зачем она тогда нужна в коде, если ее никто не вызывает? Ответ прост - раз есть, то скорее всего кто-то вызывает( или в микрософте любят компилировать с выключенной оптимизацией =] ), и этот кто-то - просто другой модуль ( CRT ) и делается это динамически:

notepad зовет _initterm, который находится в рантаймовской либе msvcrt.dll:

msvcrt!_initterm+0xb:
77c39d72 8b06            mov     eax,dword ptr [esi]
77c39d74 85c0            test    eax,eax
77c39d76 7402            je      msvcrt!_initterm+0x13 (77c39d7a)
77c39d78 ffd0            call    eax <= вызов нашей ф-ции (0x010070D4)

Можно конечно эвристически определить, что по такому-то адресу находится код, на который никто не ссылается, собс-но так IDA и поступает, но, если представить на секунду, что код может быть в принципе каким угодно ( обфусцированным, зашифрованным и т.д.), то эвристика не поможет, а ссылок нa него нет - итог: fail.

В принципе это все достаточно очевидно, но видимо не для всех(надеюсь данный пример поможет).