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[] = "\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x31\xc0\xb0\x3b\x0f\x05";
int main() {
printf("shellcode length: %d\n", 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.
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.