skip to Main Content

I am learning about shellcode development in C with an example from here. I can compile the assembly code and get de opcodes, also I can run successfully the ELF compiled with NASM, but I get a segmentation fault when I run the C test application with the embedded shellcode. I have Ubuntu 20.04 64 bits.

This is the assembly code, I can run ./shellcode and get a shell without errors.

; https://mcsi-library.readthedocs.io/articles/2022/06/linux-exploitation-x64-shellcode/linux-exploitation-x64-shellcode.html
; shellcode.asm
; nasm -f elf64 -o shellcode.o shellcode.asm
; ld -m elf_x86_64 -s -o shellcode shellcode.o

section .text
global _start                 ; we inform the system where the program begins

_start:
  xor rdx, rdx                ; zero out rdx
  push rdx                    ; push it onto the stack
  mov rax, 0x68732f2f6e69622f ; we can push 'hs//nib/' as one value, after all it is 64-bit
  push rax                    ; we push it onto the stack, so it lands at some address on the stack
  mov rdi, rsp                ; that address is where esp points to, so we store it in rdi => pointer to '/bin/sh'
  push rdx                    ; we push 0, as it will be the null termination of the array
  push rdi                    ; the address of '/bin/sh' is pushed onto the stack, it lands under another stack address
  mov rsi, rsp                ; we store that address into rsi. So rsi contains a pointer to a pointer to '/bin/sh'
  xor rax, rax                ; zero out eax to keep it clean
  mov al, 0x3b                ; 59 DEC, we move it to the lowest eax part to avoid nulls.
  syscall                     ; all arguments are set up, syscall time

I get the opcodes using this script, and I get the same opcodes of the original post.

#!/bin/bash
# extract elf opcodes

if [ -z "$1" ]
then
    echo "Usage: $0 <path to executable>"
    exit
fi

objdump -d $1|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'

And this is the tester.c with the embedded shellcode, which launches Segmentation fault.

// tester.c
// shellcode tester program
// gcc -m64 -z execstack -fno-stack-protector -o tester tester.c
// https://mcsi-library.readthedocs.io/articles/2022/06/linux-exploitation-x64-shellcode/linux-exploitation-x64-shellcode.html

#include <stdio.h>
#include <string.h>

unsigned char code[] = "x48x31xd2x52x48xb8x2fx62x69x6ex2fx73x68x50x48x89xe7x52x57x48x89xe6x48x31xc0xb0x3bx0fx05";

int main() {
    printf("shellcode length: %dn", strlen(code));
    int (*ret)() = (int(*)())code;
    ret();
}

I have tested with -no-pie, -fno-pie, running with setarch `uname -m` -R ./tester to disable memory layout randomization and nothing.

2

Answers


  1. There’s a bug in the shell script that extracts the shellcode.

    Running objdump -d on the object file will spit out the following disassembly:

    x1.o:     file format elf64-x86-64
    
    
    Disassembly of section .text:
    
    0000000000000000 <_start>:
    
       0:   48 31 d2                xor    %rdx,%rdx
       3:   52                      push   %rdx
       4:   48 b8 2f 62 69 6e 2f    movabs $0x68732f2f6e69622f,%rax
       b:   2f 73 68 
       e:   50                      push   %rax
       f:   48 89 e7                mov    %rsp,%rdi
      12:   52                      push   %rdx
      13:   57                      push   %rdi
      14:   48 89 e6                mov    %rsp,%rsi
      17:   48 31 c0                xor    %rax,%rax
      1a:   b0 3b                   mov    $0x3b,%al
      1c:   0f 05                   syscall 
    

    This output is passed through a pipeline which cuts out header lines and the byte count prefixes to result in this:

        48 31 d2                xor    %rdx,%rdx
        52                      push   %rdx
        48 b8 2f 62 69 6e 2f    movabs $0x68732f2f6e69622f,%rax
        2f 73 68 
        50                      push   %rax
        48 89 e7                mov    %rsp,%rdi
        52                      push   %rdx
        57                      push   %rdi
        48 89 e6                mov    %rsp,%rsi
        48 31 c0                xor    %rax,%rax
        b0 3b                   mov    $0x3b,%al
        0f 05                   syscall 
    

    Then the next command in the pipeline is this:

    cut -f1-6 -d' '
    

    This grabs the first 6 words (representing byte values) in each line. The problem is that the third line has 7 byte values, so the last one got chopped off.

    This results in a missing byte in the resulting opcodes, meaning you weren’t running the code you though you were.

    Change that pipeline command to this:

    cut -f1-7 -d' '
    

    And you’ll get the expected machine code bytes.

    Login or Signup to reply.
  2. Here’s a simpler and less error-prone pipeline to generate the shellcode array for C:

    shellcode.asm:

    BITS 64
    section .text
    global _start                 ; we inform the system where the program begins
    
    _start:
      xor rdx, rdx                ; zero out rdx
      push rdx                    ; push it onto the stack
      mov rax, 0x68732f2f6e69622f ; we can push 'hs//nib/' as one value, after all it is 64-bit
      push rax                    ; we push it onto the stack, so it lands at some address on the stack
      mov rdi, rsp                ; that address is where esp points to, so we store it in rdi => pointer to '/bin/sh'
      push rdx                    ; we push 0, as it will be the null termination of the array
      push rdi                    ; the address of '/bin/sh' is pushed onto the stack, it lands under another stack address
      mov rsi, rsp                ; we store that address into rsi. So rsi contains a pointer to a pointer to '/bin/sh'
      xor rax, rax                ; zero out eax to keep it clean
      mov al, 0x3b                ; 59 DEC, we move it to the lowest eax part to avoid nulls.
      syscall                     ; all arguments are set up, syscall time
    

    The only change here is adding BITS 64 to explicitly specify the use of 64-bit mode.

    Then, run these two commands:

    nasm shellcode.asm -o shellcode
    xxd -i shellcode > shellcode.h
    

    and then your C code can be simply

    #include <stdio.h>
    #include <string.h>
    #include "shellcode.h"
    
    int main() {
        printf("shellcode length: %dn", shellcode_len);
        int (*ret)() = (int(*)())shellcode;
        ret();
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search