From 1cdd47808494cd27dcd05fccb7f5a17988669402 Mon Sep 17 00:00:00 2001 From: Razvan Deaconescu Date: Mon, 6 Nov 2023 10:29:28 +0200 Subject: [PATCH] data/lab/arena: Add exec-shellcode challenge A challenge to exercise working with mmap() and memory copying. Signed-off-by: Razvan Deaconescu --- content/chapters/data/lab/content/arena.md | 32 ++++++++++++ .../lab/solution/exec-shellcode/src/Makefile | 14 +++++ .../exec-shellcode/src/exec_shellcode.c | 48 +++++++++++++++++ .../lab/solution/exec-shellcode/tests/brk.asm | 15 ++++++ .../support/exec-shellcode/tests/.gitignore | 4 ++ .../lab/support/exec-shellcode/tests/Makefile | 34 ++++++++++++ .../lab/support/exec-shellcode/tests/brk.asm | 12 +++++ .../support/exec-shellcode/tests/getpid.asm | 12 +++++ .../exec-shellcode/tests/graded_test.inc.sh | 41 +++++++++++++++ .../exec-shellcode/tests/helloworld.asm | 25 +++++++++ .../support/exec-shellcode/tests/openfile.asm | 25 +++++++++ .../exec-shellcode/tests/run_all_tests.sh | 23 ++++++++ .../support/exec-shellcode/tests/test_brk.sh | 39 ++++++++++++++ .../exec-shellcode/tests/test_getpid.sh | 39 ++++++++++++++ .../exec-shellcode/tests/test_helloworld.sh | 39 ++++++++++++++ .../exec-shellcode/tests/test_openfile.sh | 52 +++++++++++++++++++ .../data/lab/support/exec-shellcode/utils/log | 1 + .../lab/support/exec-shellcode/utils/utils.h | 1 + 18 files changed, 456 insertions(+) create mode 100644 content/chapters/data/lab/solution/exec-shellcode/src/Makefile create mode 100644 content/chapters/data/lab/solution/exec-shellcode/src/exec_shellcode.c create mode 100644 content/chapters/data/lab/solution/exec-shellcode/tests/brk.asm create mode 100644 content/chapters/data/lab/support/exec-shellcode/tests/.gitignore create mode 100644 content/chapters/data/lab/support/exec-shellcode/tests/Makefile create mode 100644 content/chapters/data/lab/support/exec-shellcode/tests/brk.asm create mode 100644 content/chapters/data/lab/support/exec-shellcode/tests/getpid.asm create mode 100644 content/chapters/data/lab/support/exec-shellcode/tests/graded_test.inc.sh create mode 100644 content/chapters/data/lab/support/exec-shellcode/tests/helloworld.asm create mode 100644 content/chapters/data/lab/support/exec-shellcode/tests/openfile.asm create mode 100755 content/chapters/data/lab/support/exec-shellcode/tests/run_all_tests.sh create mode 100755 content/chapters/data/lab/support/exec-shellcode/tests/test_brk.sh create mode 100755 content/chapters/data/lab/support/exec-shellcode/tests/test_getpid.sh create mode 100755 content/chapters/data/lab/support/exec-shellcode/tests/test_helloworld.sh create mode 100755 content/chapters/data/lab/support/exec-shellcode/tests/test_openfile.sh create mode 120000 content/chapters/data/lab/support/exec-shellcode/utils/log create mode 120000 content/chapters/data/lab/support/exec-shellcode/utils/utils.h diff --git a/content/chapters/data/lab/content/arena.md b/content/chapters/data/lab/content/arena.md index 5207b9a8e5..5ae004786c 100644 --- a/content/chapters/data/lab/content/arena.md +++ b/content/chapters/data/lab/content/arena.md @@ -30,6 +30,38 @@ test_res_10_res_20 ........................ passed ... 25 Total: 100/100 ``` +## Shellcode Executor + +Navigate to the `support/exec-shellcode/` directory. + +Your goal is to update the `src/exec-shellcode` source code file to be able to read and execute shellcodes from a given binary files. +The program thus acts as a shellcode tester. +Shellcodes end up in an `exit()` system call to ensure a graceful exit of the program after running the shellcode. +Use `mmap()` to reserve a virtual page. +Use anonymous mapping (i.e. the `MAP_ANONYMOUS`) flag. +Use the proper permissions required to enable the shellcode to be read from the file into memory and then executed. + +To test the implementation, enter the `tests/` directory and run: + +```console +make check +``` + +As an extra item, add a shellcode for the `brk()` system call in the `tests/brk.asm` file. +It should be a simple shellcode that calls `brk(NULL)`, i.e. with the purpose of getting the current program break. + +In case of a correct solution, you will get an output such as: + +```text +./run_all_tests.sh +test_helloworld ........................ passed ... 25 +test_getpid ........................ passed ... 25 +test_openfile ........................ passed ... 25 +test_brk ........................ passed ... 25 + +Total: 100/100 +``` + ## Memory Support **Manual memory management** (MMM) is one of the most difficult tasks. diff --git a/content/chapters/data/lab/solution/exec-shellcode/src/Makefile b/content/chapters/data/lab/solution/exec-shellcode/src/Makefile new file mode 100644 index 0000000000..52e057a177 --- /dev/null +++ b/content/chapters/data/lab/solution/exec-shellcode/src/Makefile @@ -0,0 +1,14 @@ +CFLAGS ?= -Wall -Wextra +CPPFLAGS ?= -I../utils + +.PHONY: all clean + +all: exec_shellcode + +exec_shellcode: exec_shellcode.o ../utils/log/log.o + +../utils/log/log.o: ../utils/log/log.c ../utils/log/log.h + +clean: + -rm -f exec_shellcode exec_shellcode + -rm -f *~ diff --git a/content/chapters/data/lab/solution/exec-shellcode/src/exec_shellcode.c b/content/chapters/data/lab/solution/exec-shellcode/src/exec_shellcode.c new file mode 100644 index 0000000000..a8bff2a36d --- /dev/null +++ b/content/chapters/data/lab/solution/exec-shellcode/src/exec_shellcode.c @@ -0,0 +1,48 @@ +#include +#include +#include +#include + +#include "utils.h" + +static void *shellcode_mapping; + +static void usage(const char * const argv0) +{ + fprintf(stderr, "Usage: %s shellcode_file\n", argv0); +} + +static void create_shellcode_mapping(void) +{ + /* TODO 2: Create mapping to fit the shellcode. */ + shellcode_mapping = mmap(NULL, sysconf(_SC_PAGESIZE), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + DIE(shellcode_mapping == MAP_FAILED, "mmap"); +} + +static void read_shellcode(const char * const fname) +{ + /* TODO 8: Read content from file in shellcode. */ + FILE *f; + + f = fopen(fname, "rb"); + DIE(f == NULL, "fopen"); + + fread(shellcode_mapping, sysconf(_SC_PAGESIZE), 1, f); + + fclose(f); +} + +int main(int argc, char **argv) +{ + if (argc != 2) { + usage(argv[0]); + exit(EXIT_FAILURE); + } + + create_shellcode_mapping(); + read_shellcode(argv[1]); + + ((void (*)(void)) shellcode_mapping)(); + + return 0; +} diff --git a/content/chapters/data/lab/solution/exec-shellcode/tests/brk.asm b/content/chapters/data/lab/solution/exec-shellcode/tests/brk.asm new file mode 100644 index 0000000000..ae15f2cc68 --- /dev/null +++ b/content/chapters/data/lab/solution/exec-shellcode/tests/brk.asm @@ -0,0 +1,15 @@ +BITS 64 + ; call brk(0) + ; rax <- 12 (__NR_brj) + ; rdi <- 0 + ; TODO 3: Make brk syscall. + mov rax, 12 + xor rdi,rdi + syscall + + ; call exit_group(0) + ; rax <- 231 (__NR_exit_group) + ; rdi <- 0 (exit status) + mov rax, 231 + xor rdi, rdi + syscall diff --git a/content/chapters/data/lab/support/exec-shellcode/tests/.gitignore b/content/chapters/data/lab/support/exec-shellcode/tests/.gitignore new file mode 100644 index 0000000000..8c2194af9c --- /dev/null +++ b/content/chapters/data/lab/support/exec-shellcode/tests/.gitignore @@ -0,0 +1,4 @@ +/brk +/getpid +/helloworld +/openfile diff --git a/content/chapters/data/lab/support/exec-shellcode/tests/Makefile b/content/chapters/data/lab/support/exec-shellcode/tests/Makefile new file mode 100644 index 0000000000..f1602531db --- /dev/null +++ b/content/chapters/data/lab/support/exec-shellcode/tests/Makefile @@ -0,0 +1,34 @@ +SRC_PATH ?= ../src +FULL_SRC_PATH = $(realpath $(SRC_PATH)) +CPPFLAGS = -I. -I$(realpath $(SRC_PATH)) -I../utils +CFLAGS = -Wall -Wextra +# Remove the line below to disable debugging support. +CFLAGS += -g -O0 + +SRCS = $(wildcard *.asm) +SHELLCODES = $(patsubst %.asm,%,$(SRCS)) + +.PHONY: all src check lint clean + +all: $(SHELLCODES) src + +$(SHELLCODES): %:%.asm | src + nasm -o $@ $< + +src: + make -C $(FULL_SRC_PATH) + +check: $(SHELLCODES) + make -C $(FULL_SRC_PATH) clean + make clean + make -i SRC_PATH=$(FULL_SRC_PATH) + ./run_all_tests.sh + +lint: + -cd .. && checkpatch.pl -f src/*.c + -cd .. && checkpatch.pl -f tests/*.sh + -cd .. && cpplint --recursive src/ + -cd .. && shellcheck tests/*.sh + +clean: + -rm -f *~ diff --git a/content/chapters/data/lab/support/exec-shellcode/tests/brk.asm b/content/chapters/data/lab/support/exec-shellcode/tests/brk.asm new file mode 100644 index 0000000000..824430f8eb --- /dev/null +++ b/content/chapters/data/lab/support/exec-shellcode/tests/brk.asm @@ -0,0 +1,12 @@ +BITS 64 + ; call brk(0) + ; rax <- 12 (__NR_brj) + ; rdi <- 0 + ; TODO 3: Make brk syscall. + + ; call exit_group(0) + ; rax <- 231 (__NR_exit_group) + ; rdi <- 0 (exit status) + mov rax, 231 + xor rdi, rdi + syscall diff --git a/content/chapters/data/lab/support/exec-shellcode/tests/getpid.asm b/content/chapters/data/lab/support/exec-shellcode/tests/getpid.asm new file mode 100644 index 0000000000..a1b19a1940 --- /dev/null +++ b/content/chapters/data/lab/support/exec-shellcode/tests/getpid.asm @@ -0,0 +1,12 @@ +BITS 64 + ; call getpid() + ; rax <- 39 (__NR_write) + mov rax, 39 + syscall + + ; call exit_group(0) + ; rax <- 231 (__NR_exit_group) + ; rdi <- 0 (exit status) + mov rax, 231 + xor rdi, rdi + syscall diff --git a/content/chapters/data/lab/support/exec-shellcode/tests/graded_test.inc.sh b/content/chapters/data/lab/support/exec-shellcode/tests/graded_test.inc.sh new file mode 100644 index 0000000000..bce1f05bbe --- /dev/null +++ b/content/chapters/data/lab/support/exec-shellcode/tests/graded_test.inc.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# SPDX-License-Identifier: BSD-3-Clause + +# +# Print test result. Printed message should fit in 72 characters. +# +# Print format is: +# +# description ...................... passed ... NNN +# description ...................... failed ... NNN +# 32 chars 24 chars 6 3 3 +# + +print_test() +{ + func="$1" + result="$2" + points="$3" + + if test "$points" -gt 999; then + points=999 + fi + + printf "%-32s " "${func:0:31}" + printf "........................" + if test "$result" -eq 0; then + printf " passed ... %3d\n" "$points" + else + printf " failed ... 0\n" + fi +} + +run_test() +{ + func="$1" + points="$2" + + # Run in subshell. + (eval "$func") + print_test "$func" "$?" "$points" +} diff --git a/content/chapters/data/lab/support/exec-shellcode/tests/helloworld.asm b/content/chapters/data/lab/support/exec-shellcode/tests/helloworld.asm new file mode 100644 index 0000000000..626f8f38c9 --- /dev/null +++ b/content/chapters/data/lab/support/exec-shellcode/tests/helloworld.asm @@ -0,0 +1,25 @@ +BITS 64 + ; use jmp / call trick to get string address in RCX + jmp hello +back: + ; call write(1, "Hello, World!\n", 14); + ; rax <- 1 (__NR_write) + ; rdi <- 1 (stdout fileno) + ; rsi <- "Hello, World!\n" + ; rdx <- 14 (string length) + mov rax, 1 + mov rdi, 1 + pop rsi + mov rdx, 14 + syscall + + ; call exit_group(0) + ; rax <- 231 (__NR_exit_group) + ; rdi <- 0 (exit status) + mov rax, 231 + xor rdi, rdi + syscall + +hello: + call back + db "Hello, World!", 10, 0 diff --git a/content/chapters/data/lab/support/exec-shellcode/tests/openfile.asm b/content/chapters/data/lab/support/exec-shellcode/tests/openfile.asm new file mode 100644 index 0000000000..a94813af89 --- /dev/null +++ b/content/chapters/data/lab/support/exec-shellcode/tests/openfile.asm @@ -0,0 +1,25 @@ +BITS 64 + ; use jmp / call trick to get filename address in RCX + jmp filename +back: + ; call open("uberfile", O_RDWR | O_TRUNC | O_CREAT, 0644) + ; rax <- 2 (__NR_open) + ; rdi <- "uberfile" + ; rsi <- 578 (O_RDWR | O_TRUNC | O_CREAT - 01102) + ; rdx <- 420 (0644) + mov rax, 2 + pop rdi + mov rsi, 578 + mov rdx, 420 + syscall + + ; call exit_group(0) + ; rax <- 231 (__NR_exit_group) + ; rdi <- 0 (exit status) + mov rax, 231 + xor rdi, rdi + syscall + +filename: + call back + db "uberfile", 0 diff --git a/content/chapters/data/lab/support/exec-shellcode/tests/run_all_tests.sh b/content/chapters/data/lab/support/exec-shellcode/tests/run_all_tests.sh new file mode 100755 index 0000000000..23f26ec56e --- /dev/null +++ b/content/chapters/data/lab/support/exec-shellcode/tests/run_all_tests.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# SPDX-License-Identifier: BSD-3-Clause + +if test -z "$SRC_PATH"; then + SRC_PATH=../src +fi + +export SRC_PATH + +( +./test_helloworld.sh +./test_getpid.sh +./test_openfile.sh +./test_brk.sh +) | tee results.txt + +total=$(grep '\( passed \| failed \)' results.txt | rev | cut -d ' ' -f 1 | rev | paste -s -d'+' | bc) +echo "" +echo -n "Total: " +echo -n " " +LC_ALL=C printf "%3d/100\n" "$total" + +rm results.txt diff --git a/content/chapters/data/lab/support/exec-shellcode/tests/test_brk.sh b/content/chapters/data/lab/support/exec-shellcode/tests/test_brk.sh new file mode 100755 index 0000000000..d3cea1dcfb --- /dev/null +++ b/content/chapters/data/lab/support/exec-shellcode/tests/test_brk.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# SPDX-License-Identifier: BSD-3-Clause + +source graded_test.inc.sh + +shellcode=./brk + +if test -z "$SRC_PATH"; then + SRC_PATH=../src +fi + +test_brk() +{ + if test ! -f "$shellcode"; then + echo "No such file $shellcode" 1>&2 + exit 1 + fi + + objdump -D -M intel -b binary -m i386:x86-64 "$shellcode" > /dev/null 2>&1 + if test $? -ne 0; then + echo "Incorrect shellcode file" 1>&2 + exit 1 + fi + + timeout -k 1 3 "$SRC_PATH"/exec_shellcode "$shellcode" > /dev/null 2>&1 + if test $? -ne 0; then + echo "Program runs unsuccessfully" 1>&2 + exit 1 + fi + + timeout -k 1 3 strace "$SRC_PATH"/exec_shellcode "$shellcode" 2>&1 | grep -A 1 close | tail -2 | grep 'brk(NULL)[ \t]\+= 0x' > /dev/null 2>&1 + if test $? -ne 0; then + echo "brk not called correctly" 1>&2 + exit 1 + fi + exit 0 +} + +run_test test_brk 25 diff --git a/content/chapters/data/lab/support/exec-shellcode/tests/test_getpid.sh b/content/chapters/data/lab/support/exec-shellcode/tests/test_getpid.sh new file mode 100755 index 0000000000..de59539dfb --- /dev/null +++ b/content/chapters/data/lab/support/exec-shellcode/tests/test_getpid.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# SPDX-License-Identifier: BSD-3-Clause + +source graded_test.inc.sh + +shellcode=./getpid + +if test -z "$SRC_PATH"; then + SRC_PATH=../src +fi + +test_getpid() +{ + if test ! -f "$shellcode"; then + echo "No such file $shellcode" 1>&2 + exit 1 + fi + + objdump -D -M intel -b binary -m i386:x86-64 "$shellcode" > /dev/null 2>&1 + if test $? -ne 0; then + echo "Incorrect shellcode file" 1>&2 + exit 1 + fi + + timeout -k 1 3 "$SRC_PATH"/exec_shellcode "$shellcode" > /dev/null 2>&1 + if test $? -ne 0; then + echo "Program runs unsuccessfully" 1>&2 + exit 1 + fi + + timeout -k 1 3 strace "$SRC_PATH"/exec_shellcode "$shellcode" 2>&1 | grep -A 1 close | tail -2 | grep 'getpid()[ \t]\+= [0-9]\+' > /dev/null 2>&1 + if test $? -ne 0; then + echo "getpid() not called (successfully)" 1>&2 + exit 1 + fi + exit 0 +} + +run_test test_getpid 25 diff --git a/content/chapters/data/lab/support/exec-shellcode/tests/test_helloworld.sh b/content/chapters/data/lab/support/exec-shellcode/tests/test_helloworld.sh new file mode 100755 index 0000000000..0a0b3d7276 --- /dev/null +++ b/content/chapters/data/lab/support/exec-shellcode/tests/test_helloworld.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# SPDX-License-Identifier: BSD-3-Clause + +source graded_test.inc.sh + +shellcode=./helloworld + +if test -z "$SRC_PATH"; then + SRC_PATH=../src +fi + +test_helloworld() +{ + if test ! -f "$shellcode"; then + echo "No such file $shellcode" 1>&2 + exit 1 + fi + + objdump -D -M intel -b binary -m i386:x86-64 "$shellcode" > /dev/null 2>&1 + if test $? -ne 0; then + echo "Incorrect shellcode file" 1>&2 + exit 1 + fi + + timeout -k 1 3 "$SRC_PATH"/exec_shellcode "$shellcode" > /dev/null 2>&1 + if test $? -ne 0; then + echo "Program runs unsuccessfully" 1>&2 + exit 1 + fi + + timeout -k 1 3 "$SRC_PATH"/exec_shellcode "$shellcode" | grep 'Hello, World!' > /dev/null 2>&1 + if test $? -ne 0; then + echo "'Hello, World!' not printed" 1>&2 + exit 1 + fi + exit 0 +} + +run_test test_helloworld 25 diff --git a/content/chapters/data/lab/support/exec-shellcode/tests/test_openfile.sh b/content/chapters/data/lab/support/exec-shellcode/tests/test_openfile.sh new file mode 100755 index 0000000000..45b05f79d3 --- /dev/null +++ b/content/chapters/data/lab/support/exec-shellcode/tests/test_openfile.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# SPDX-License-Identifier: BSD-3-Clause + +source graded_test.inc.sh + +shellcode=./openfile + +if test -z "$SRC_PATH"; then + SRC_PATH=../src +fi + +test_openfile() +{ + if test ! -f "$shellcode"; then + echo "No such file $shellcode" 1>&2 + exit 1 + fi + + objdump -D -M intel -b binary -m i386:x86-64 "$shellcode" > /dev/null 2>&1 + if test $? -ne 0; then + echo "Incorrect shellcode file" 1>&2 + exit 1 + fi + + timeout -k 1 3 "$SRC_PATH"/exec_shellcode "$shellcode" > /dev/null 2>&1 + if test $? -ne 0; then + echo "Program runs unsuccessfully" 1>&2 + exit 1 + fi + + test -f "uberfile" > /dev/null 2>&1 + if test $? -ne 0; then + echo "File not created" 1>&2 + exit 1 + fi + + test $(stat --format="%s" "uberfile") -eq 0 > /dev/null 2>&1 + if test $? -ne 0; then + echo "File is not truncated" 1>&2 + exit 1 + fi + + test $(stat --format="%a" "uberfile") = "644" > /dev/null 2>&1 + if test $? -ne 0; then + echo "File permissions not correctly set" 1>&2 + exit 1 + fi + + exit 0 +} + +run_test test_openfile 25 diff --git a/content/chapters/data/lab/support/exec-shellcode/utils/log b/content/chapters/data/lab/support/exec-shellcode/utils/log new file mode 120000 index 0000000000..6b4efecdf4 --- /dev/null +++ b/content/chapters/data/lab/support/exec-shellcode/utils/log @@ -0,0 +1 @@ +../../../../../../common/utils/log/ \ No newline at end of file diff --git a/content/chapters/data/lab/support/exec-shellcode/utils/utils.h b/content/chapters/data/lab/support/exec-shellcode/utils/utils.h new file mode 120000 index 0000000000..284faf5aec --- /dev/null +++ b/content/chapters/data/lab/support/exec-shellcode/utils/utils.h @@ -0,0 +1 @@ +../../../../../../common/utils/utils.h \ No newline at end of file