Статья ASM для х86 (5.5) RAW сокеты в XP и Win7

Возможность работы с сокетами предоставляют нам системные "Поставщики услуг". Именно поставщик ограничивает тот или иной функционал, например возможность использовать протокол ICMP или девственные RAW-сокеты. В отношении WinSock, базовых поставщиков всего двое – это библиотека mswsock.dll, со-своей подругой rsvpsp.dll (заправляет функциями NetBIOS).

Поставщик подключается к WinSock при помощи имеющегося в нём интерфейса SPI - "Service Provider Interface". После регистрации поставщика в системе, служба Winsock использует его для реализации определённых функций сокета, например setsockopt(), ioctlsocket(), connect() и прочих для тех типов адресов, которые поставщик указал в качестве им реализуемых. Тип адреса под виндой всегда одинаковый – это AF_INET с константой =2 (для сетей IPv4).

Сторонние поставщики небезопасны и Microsoft относится к ним с презрением. Они могут с лёгкостью обойти надстройки безопасности сетевых протоколов, поскольку имеют прямой доступ к ядру операционной системы, а точнее к транспортной её части WSK (Winsock Kernel, в младенчестве TDI). Финт с установкой самих себя в качестве поставщика многоуровневой службы по модели "Layered Service Provider" (LSP) часто использует малварь и шпионские программы.


5.5.0. Системные ограничения WinSock

На борту Win имеется консольная утилита NETSH (Network Shell). На запрос вида winsock show catalog она отзывается портянкой с описанием услуг базовых поставщиков. Поскольку лог получается большой и не вмещается в консольную форточку, имеет смысл перенаправить вывод в файл. Вот пример, в котором я принебрёг деталями не интересного нам протокола NetBIOS. Как видим – выбор не велик, и попытки заставить сокеты работать с отсутствующими здесь протоколами, обречены на провал:

Bash:
C:\> netsh winsock show catalog > wslog.txt

Элемент поставщика каталога Winsock
---------------------------------------
Тип элемента:           Базовый поставщик услуг
Описание:               MSAFD Tcpip [TCP/IP]
Путь поставщика:        %SystemRoot%\system32\mswsock.dll
Версия:                 2        ; WinSock 2.0
Макс длина адреса:      16       ; размер структуры "sockaddr_in"
Тип адреса:             2        ; AF_INET
Тип сокета:             1        ; SOCK_STREAM
Протокол:               6        ; IPPROTO_TCP

Элемент поставщика каталога Winsock
---------------------------------------
Тип элемента:           Базовый поставщик услуг
Описание:               MSAFD Tcpip [UDP/IP]
Путь поставщика:        %SystemRoot%\system32\mswsock.dll
Макс длина адреса:      16
Тип адреса:             2        ; AF_INET
Тип сокета:             2        ; SOCK_DGRAM
Протокол:               17       ; IPPROTO_UDP

Элемент поставщика каталога Winsock
---------------------------------------
Тип элемента:           Базовый поставщик услуг
Описание:               MSAFD Tcpip [RAW/IP]
Путь поставщика:        %SystemRoot%\system32\mswsock.dll
Макс длина адреса:      16
Тип адреса:             2        ; AF_INET
Тип сокета:             3        ; SOCK_RAW
Протокол:               0        ; IPPROTO_IP

Ого… даже RAW имеется в списке! Круто!
Но бросать чепчики в воздух рановато – всё/это для отвода глаз, чтобы армия юзеров не посчитали систему ущербной. Продуманы из Microsoft не придумали ничего лучше, как ввести RAW-сокеты на уровне приложений, и тут-же ограничить их функционал на уровне ядра. Такой подход вводит в ступор большинство программистов, поскольку мы можем создавать сырьё, но первая-же операция с ними, приводит к ошибке. В точку здесь попадает определение – праздник закрытых дверей.

Однако не всё так плохо, и с барского стола нам тоже кое-что перепало. Нужно запомнить одно правило – в большинстве случаях, операции с сырыми сокетами под WinXP+ возможны только для входящих пакетов. Если стража системы пронюхает исходящий пакет RAW, то безжалостно уничтожит его, не дав возможности даже объясниться. Вот цитата из MSDN

В Win-7, Vista, XP(sp3) возможность отправки трафика по сырым сокетам была ограничена несколькими способами:

• Данные TCP не могут быть отправлены через сырые сокеты.
• Вызов функции bind() с сырым сокетом для протокола IPPROTO_TCP запрещён.
• UDP-трафик с недопустимым адресом отправителя не может быть отправлен через сырые сокеты.
• IP-адрес отправителя для любой исходящей UDP-дейтаграммы должен существовать в сетевом интерфейсе, или датаграмма удаляется.


Эти изменения были сделаны, чтобы ограничить способность вредоносного кода создавать распределённые атаки типа DDoS и возможность отправки TCP/IP-пакетов с поддельным IP-адресом источника.

Вся прелесть сырых сокетов в том, чтобы самому формировать заголовки пакетов – иначе они и даром не нужны. Но что сделали макрушники? Они ввели RAW, но предписывают использовать их только внутри своего узла. Это вообще труба! Да.., мы можем отправлять сырые пакеты с валидным/нашим IP, но это превращает сырой сокет в обычный, и непонятно зачем вообще называть его RAW.


5.5.1. Тёмная сторона сокетов

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

C-подобный:
setsockopt (
   socket           ; дескриптор сокета
   level            ; IPPROTO_TCP/UDP/IP/RAW
   optname          ; имя опции (см.ниже)
   optval           ; указатель на значение опции: 1= вкл, 0= выкл.
   optlen  )        ; длина значения в байтах, обычно 4 (dword)

;== Имя опций ===============
IP_HDRINCL       =  2       ; PROTO_RAW. Если значение =1, то IP-заголовок заполняем сами
IP_DONTFRAGMENT  =  14      ; PROTO_any. Если значение =1 не фрагментировать пакет

TCP_NODELAY      =  1       ; PROTO_TCP. Вкл/выкл алгоритм Nagle
UDP_NOCHECKSUM   =  1       ; PROTO_UDP. Вкл/выкл проверку контрольной суммы
SO_BROADCAST     =  0x0020  ; PROTO_UDP. Широковещательные сообщения на сокете UDP

С дефолтными настройками сокетов, всю черновую работу по инкапсуляции и заполнению заголовков всех уровней выполняет служба Winsock. С одной стороны это освобождает нас от низкоуровневой рутины, с другой – навязывает свои правила. Для таких случаев, у сокетов имеется опция IP-HDRINCL (Include Header, подключать заголовки = да/нет), которая предписывает службе Winsock не вмешиваться в этот процесс и что заголовки пакетов мы планируем формировать сами.

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

Дело в том, что по-умолчанию никто это дело не контролирует, и функции отправки данных send() абсолютно по-барабану, сколько полезных данных (payload) содержит пакет. В нём может быть всего 1-байт, а остальное – заголовки Header. КПД такого обмена стремится к нулю, и только занимает пропускную способность канала. Вот Нейгл и предложил перед отправкой тестить пайлоад, и собирать пакет по краполям, в один баш. Ясно, что при этом получаются задержки, и первыми бросились на амбразуру разработчики сетевых игр, для которых такие тайм-ауты критичны. Опция по-умолчанию отключена и пакеты буферизуются.


5.5.1(1) Общаемся с драйвером сетевой карты

Другой полезной фишкой является возможность передавать команды напрямую драйверу транспорта WSK. Как мы помним, на системах х32 пространство памяти ОЗУ разделено на две равные части – нижняя половина от 0x1000 и до адреса 0x7FFFFFFF выделена в наше юзерское распоряжение, а выше 0х80000000 и до самых небес системное ядро забирает себе. Доступ к ядерной памяти контролируется центральным процессором CPU на аппаратном уровне, и любые попытки вторжения в кернел жёстко пресекаются сторожем страниц PageGuard.

Однако большинство служб и системных сервисов сами тусуются в пространстве юзера, и по роду своей деятельности должны общаться с ядром. Поэтому полностью изолировать юзера на необитаемый остров нельзя, в результате чего появился шлюз в Kernel в виде функции DeviceIoControl(). Эта функция универсальна и не привязана к какому-то одному драйверу. В качестве аргумента она ждёт код-запроса, по которому передаёт сообщение только одному из многочисленных драйверов в системе. Необнародованный список кодов затмит роман "Война и мир", а с документированным листом можно ознакомиться например здесь (для Winsock искать (Ctrl+F) по маске TDI и NDIS).

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

C-подобный:
ioctlsocket (
    socket      ; дескриптор сокета
    cmd         ; код управления
    argp        ; указатель на опцию кода (вкл/выкл)
   );

;// Некоторые коды управления для fn. "ioctlsocket"
;===================================================
SIO_RCVALL                   = 0x98000001  ;// вкл/выкл Promisc
SIO_RCVALL_MCAST             = 0x98000002  ; Promisc для многоадресной рассылки UDP
SIO_RCVALL_IGMPMCAST         = 0x98000003  ; Promisc для IGMP-пакетов
SIO_KEEPALIVE_VALS           = 0x98000004  ;
SIO_ABSORB_RTRALERT          = 0x98000005  ;
SIO_UCAST_IF                 = 0x98000006  ;
SIO_LIMIT_BROADCASTS         = 0x98000007  ;
SIO_INDEX_BIND               = 0x98000008  ;
SIO_INDEX_MCASTIF            = 0x98000009  ;
SIO_INDEX_ADD_MCAST          = 0x9800000A  ;
SIO_FLUSH                    = 0x28000004  ; Удаляет содержимое очереди отправки
SIO_MULTICAST_SCOPE          = 0x8800000A  ; TTL
SIO_GET_BROADCAST_ADDRESS    = 0x48000005  ; Возвращает струк.SOCKADDR для UDP
SIO_ADDRESS_LIST_QUERY       = 0x48000016  ; Cписок лок/интерфейсов, к которым можно привязать сокет
SIO_ROUTING_INTERFACE_QUERY  = 0xC8000014  ; Возвращает адреса интерфейсов, удалённого хоста
FIOASYNC                     = 0x8004667D  ; Вкл уведомлений при ожидании данных.
FIONBIO                      = 0x8004667E  ; Блокирующий режим сокета: 0=да, 1=нет
FIONREAD                     = 0x4004667F  ; Возвращает кол-во байт, доступных для чтения


5.5.2. Историческая справка

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

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

Но беззаботная жизнь на хабах продолжалась не долго.. В локальную сеть, как слон в посудную лавку, ввалился коммутатор Switch. Теперь хоть линия передачи как и прежде одна, зато узлы в ней разделены логически. Виной тому – всего-то крошечный логический элемент с аббревиатурой "И". Он имеет два входа и один выход, на который вешают узел сети. Если "И" на первый "И" на второй вход подать единицу, то затвор на выходе открывается и трафик поступает на подключённый к выходу узел. Таким образом, обычный логический элемент из двух транзисторов полностью перекрыл кислород сниферам, оставив в их владениях периметр только до коммутатора.

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

switch.png


Для простоты понимания, в примере с коммутатором я исключил из схемы дешифратор. Этот электронный компонент может иметь, например, 4-входа и 16-выходов, чего достаточно для 16-портовых свитчей. На 4-входа дешифратора поступает двоичная тетрада последнего байта IP-адреса (см.рис.выше), по которой он подключает один из 16-ти выходных портов (бинарная логика).

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

vpn.png



5.5.3. Практика – пишем сниффер на RAW сокетах

Теперь посмотрим, что представляет из-себя сниффер на генетическом уровне..
Во-первых, нужно перевести сетевую карту в неразборчивый режим Promisc. Для этого задействуем функцию ioctlsocket() с кодом SIO_RCVALL равным 0x98000001, который отключит в контроллере способность фильтровать трафик на свой/чужой. Теперь остаётся в непрерывном режиме принимать пакеты функцией recv() в свой буфер и выводить его содержимое на экран.

После чтения очередной порции трафика, в буфере получим пакет, формат которого сильно зависит от типа протокола. Можно снабдить программу фильтром протоколов, например чтобы прожка принимала только TCP пакеты, UDP или ICMP. Но в демонстрационном примере лучше принимать всё-подряд, а прикрутить фильтр можно будет позже – главное понять суть.

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

hdr_88.png


Чтобы вытянуть из пакета полезные данные payload, нам придётся сначала в IP-заголовке проверить поле "iph_proto", где будет указан код протокола, по которому был отправлен данный пакет. Если это IPPROTO_TCP с кодом 0х06, значит после IP-заголовка идёт 20-байтный TCP-заголовок (см.рис.выше). Для IPPROTO_UDP код будет 0х11, а для ICMP = 0x01. Дамп перехваченного пакета в отладчике выглядит так:

shiff_01.png


Ну и немного о деталях самого исходника..
Чтобы вывести тип протокола на экран, придётся по его коду искать соответствующую строку. Для этого я создал таблицу переходов, где первое значение это код-протокола из поля iph_proto заголовка IP, а второе – адрес строки с его именем в секции данных программы. Получив код я ищу его в таблице, и при совпадении беру следующее значение, которое будет указателем на соответствующую строку. Вот образец этой таблицы:

C-подобный:
protoTable   dd  00, m1          ;// таблица адресов
             dd  01, m2          ;
             dd  02, m3          ;
             dd  06, m4          ;
             dd  17, m5          ;

m1           db 'IP'  ,0         ;// массив строк
m2           db 'ICMP',0         ;
m3           db 'IGMP',0         ;
m4           db 'TCP' ,0         ;
m5           db 'UDP' ,0         ;
unknown      db 'Unknown!',0     ;

Алгоритм всего кода снифера распишем так:
  1. Узнать свой IP и вывести его на экран. Это позволит ориентироваться в адресах – куда, кому, зачем и почему.
  2. Создать сырой RAW-сокет типа IPPROTO_IP, что позволит ловить пакеты всех предыдущих протоколов в стеке TCP/IP. Например если создать сокет типа IPPROTO_UDP, то получим фильтр пакетов и кроме дейтаграмм UDP наш снифер нишиша не получит.
  3. Перевести сетевой контролёр в режим Promisc – без этого ничего не выйдет. Результаты всех операций будем выводить на экран.
  4. Зациклиться на приёме пакетов функцией recv() и внутри каждого цикла парсить приёмный буфер. Здесь я ввёл счётчик.. можете установить его на любое кол-во пакетов.
  5. Вот собственно и все шаги..
Пример кода и результат его работы прилагаю:

C-подобный:
format   pe console
entry    start
include 'win32ax.inc'
;----------
.data
wsa          WSADATA
addr         sockaddr_in

capt         db  13,10,'      Sniffer example v0.1'
             db  13,10,' **************************************',13,10,0
proto        db  13,10
             db  13,10,' Proto == %s',0
sPort        db  13,10,'   Source.....: port %05d',0
src          db  ' ',0xb1,' ip %s',0
dPort        db  13,10,'   Destination: port %05d',0
dest         db  ' ',0xb1,' ip %s',0
ttl          db  13,10,'   TTL........: %d',0
dSize        db  13,10,'   Data size..: %d byte',0
check        db  13,10,'   CheckSum...: %04x',0
succes       db  13,10
             db  13,10,' ~~~~~~'
             db  13,10,' Sniff successful..'
             db  13,10,' Press any key for exit...',0

protoTable   dd  00,m1           ;// таблица адресов
             dd  01,m2           ;
             dd  02,m3           ;
             dd  06,m4           ;
             dd  17,m5           ;
m1           db 'IP'  ,0         ;// массив строк
m2           db 'ICMP',0         ;
m3           db 'IGMP',0         ;
m4           db 'TCP' ,0         ;
m5           db 'UDP' ,0         ;
unknown      db 'Unknown!',0     ;

frmt         db  '%s',0          ; спецификатор для scanf()
s            dd  0               ; дескриптор сокета
flag         dd  1               ; флаг "Promisc-mode"
errMes       db  128 dup(0)      ; под сообщение об ошибках
hostName     db  64  dup(0)      ; имя нашего хоста (узла)
count        dd  50              ; счётчик сбора пакетов
align        16                  ; выравнивание памяти на параграф
buff         IP_Header           ; буфер под пакет сниффера
;----------
.code
start:
;//---- Шапка и получаем имя/IP нашего хоста ------
         invoke  WSAStartup,0x0101,wsa
        cinvoke  printf,capt

         invoke  gethostname,buff,64
         invoke  gethostbyname,buff
         mov     eax,[eax+12]
         mov     eax,[eax]
         mov     eax,[eax]

;//---- Заполняем структуру "sockaddr_in" ---------
         mov     [addr.sin_addr],eax          ; наш IP-адрес
         mov     [addr.sin_port],0            ; порт = произвольный
         mov     [addr.sin_family],AF_INET    ; тип адреса

;//---- Дополнительная информация юзеру -----------
;// покажем текущий наш адрес
         invoke  inet_ntoa,eax
        cinvoke  printf,<' 0. Host IP address.... %s',13,10,0>,eax

;// пробуем создать RAW-IP сокет
        cinvoke  printf,<' 1. Create RAW_socket..',13,10,0>
         invoke  socket,AF_INET,SOCK_RAW,IPPROTO_IP
         mov     [s],eax
         or      eax,eax         ; проверка на ошибку..
         jnz     @f              ; вперёд (forward), если не нуль
         jmp     GetError        ; иначе: покажем код ошибки, и на выход!

;// пробуем привязать к сокету адрес
@@:     cinvoke  printf,<' 2. Bind address.......',13,10,0>
         invoke  bind,[s],addr,16
         or      eax,eax
         jz      @f              ; forward, если нуль (успешно)
         jmp     GetError

;// пробуем включить беспорядочный режим сетевой карты
@@:     cinvoke  printf,<' 3. Set promisc mode...',13,10,0>
         invoke  ioctlsocket,[s],SIO_RCVALL,flag     ;// Receive All Packet
         or      eax,eax
         jz      @sniff          ; нуль = успешно!
         jmp     GetError

;//--------------------------------------------
;//--- Бесконечный цикл приёма пакетов --------
@sniff:  invoke  recv,[s],buff,512,0

;//--- Парсим перехваченный пакет и выводим инфу
;// имя протокола --------
         xor     ebx,ebx                 ;
         mov     bl,[buff.iph_proto]     ; EBX = код протокола
         mov     esi,protoTable          ; ESI = адрес таблицы имён
         mov     ecx,5                   ; число записей в ней
@findProtoName:                          ;
         lodsd                           ; EAX = очередной код из таблицы
         cmp     ebx,eax                 ; сравнить с EBX
         jz      @ok                     ; если совпало.
         add     esi,4                   ; иначе: на сл.элемент в таблице
         loop    @findProtoName          ; промотать ЕСХ-раз..

         mov     eax,unknown             ; облом - неизвестный код протокола.
         jmp     @prn                    ;
@ok:     lodsd                           ; если нашли: EAX = адрес строки с именем
@prn:    cinvoke  printf,proto,eax       ; выводим его на консоль!

;//--- Порт отправителя ---------------
         mov     esi,buff.iph_dest+4     ; он лежит после IP-заголовка
         lodsw                           ; берём его в AX (word)
         push    esi                     ; запомнить указатель на порт получателя
         and     eax,0xffff              ; оставить в EAX только AX
         xchg    ah,al                   ; обменять в АХ байты местами
        cinvoke  printf,sPort,eax        ; вывести порт на косоль!

;//--- IP отправителя (Source) --------
         mov     eax,[buff.iph_src]      ; лежит в IP-заголовке
         invoke  inet_ntoa,eax           ; перевести в строку
        cinvoke  printf,src,eax          ; принт..

;//--- Порт получателя ----------------
         pop     esi                     ; снимаем указатель со-стека
         lodsw                           ; берём порт в АХ
         and     eax,0xffff              ;
         xchg    ah,al                   ; обменять байты местами
        cinvoke  printf,dPort,eax        ; на консоль его..

;//--- IP получателя (Destination) ----
         mov     eax,[buff.iph_dest]     ; см.выше..
         invoke  inet_ntoa,eax           ;
        cinvoke  printf,dest,eax         ;

;//--- Выводим TTL (Time-to-Live) -----
         xor     eax,eax                 ; он размером в байт,
         mov     al,[buff.iph_ttl]       ;  ..и лежит в IP-заголовке
        cinvoke  printf,ttl,eax          ;

;//--- Выводим размер данных ----------
         xor     eax,eax                 ;
         mov     ax,[buff.iph_len]       ; размер хранится там-же
         xchg    ah,al                   ; восстановить порядок байт из LAN в CPU
        cinvoke  printf,dSize,eax        ;

;//--- Контрольная сумма пакета -------
         xor     eax,eax                 ;
         mov     ax,[buff.iph_xsum]      ; у CRC переставлять байты не надо.
        cinvoke  printf,check,eax        ;

         dec     [count]                 ; уменьшить счётчик сниффера пакетов
         jnz     @sniff                  ; повторить, если не нуль..

;//--- Конец программы ----------------
         cinvoke  printf,succes          ; мессага "Всё ОК!"
@exit:  cinvoke  scanf,frmt,frmt+5       ; ждём нажатия клавиши..
         invoke  ExitProcess,0           ; на выход!

;//*****************************************
;//--- Функция вывода ошибок
GetError:
        invoke  WSAGetLastError
        invoke  FormatMessage,0x1000,0,eax,0,errMes,128,0
        invoke  CharToOem,errMes,errMes
       cinvoke  printf,errMes
        jmp     @exit

;//--- Секция импорта  ------
section '.idata' import data readable     ;
library  kernel32,'kernel32.dll',\        ; импортируемые библиотеки
         user32,'user32.dll',\
         wsock32,'wsock32.dll',\          ;  ^^^
         msvcrt,'msvcrt.dll'              ;    ^^^

import   msvcrt, printf,'printf',scanf,'scanf'
include 'api\kernel32.inc'
include 'api\user32.inc'
include 'api\wsock32.inc'

shiff_02.png


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

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