https://github.com/gemesa/shadow-shell
Cyber lab designed for analyzing shellcode, exploring memory exploits and supporting malware analysis
https://github.com/gemesa/shadow-shell
aarch64 amd64 arm64 assembly buffer-overflow exploit exploit-development frida frida-scripts malware-analysis malware-research memory-exploits reverse-engineering rust shellcode shellcode-development stack-smashing x64 x86-64
Last synced: 15 days ago
JSON representation
Cyber lab designed for analyzing shellcode, exploring memory exploits and supporting malware analysis
- Host: GitHub
- URL: https://github.com/gemesa/shadow-shell
- Owner: gemesa
- License: apache-2.0
- Created: 2023-11-01T11:26:15.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-03-27T20:24:29.000Z (about 1 month ago)
- Last Synced: 2025-03-27T21:31:37.630Z (about 1 month ago)
- Topics: aarch64, amd64, arm64, assembly, buffer-overflow, exploit, exploit-development, frida, frida-scripts, malware-analysis, malware-research, memory-exploits, reverse-engineering, rust, shellcode, shellcode-development, stack-smashing, x64, x86-64
- Language: Assembly
- Homepage: https://shadowshell.io/
- Size: 195 KB
- Stars: 8
- Watchers: 1
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Introduction
shadow-shell is a cyber lab designed for:
- analyzing shellcode
- exploring memory exploits
- supporting malware analysis# Repository structure
The repository contains two main parts:
- **arsenal**: command line tools to support malware analysis
- `shexec`: a shellcode runner that can be combined with other tools like `strace` (Linux), `ProcMon` and `TCPView` (Windows) to analyze shellcode functionality
- `stalk-syscalls.js`: A Frida syscall tracer script that can be used as an alternative to `strace`
- **lab**: experimental code snippets, some are documented while others are not:warning: You should **never** execute untrusted shellcode on your system. Use an emulator, hardened VM or container for this purpose.
```
arsenal/
├── linux/
│ ├── arm64/
│ │ ├── shexec.s: Linux ARM64 shellcode runner
│ │ ├── shcode-hello.s: Linux ARM64 shellcode that prints "Hello!"
│ │ ├── shcode-shell.s: Linux ARM64 shellcode that opens a shell
│ │ └── stalk-syscalls.js: Frida ARM64 syscall tracer script
│ ├── x64/
│ │ ├── shexec.s: Linux x64 shellcode runner
│ │ ├── shcode-hello.s: Linux x64 shellcode that prints "Hello, World!"
│ │ ├── shcode-shell.s: Linux x64 shellcode that opens a shell
│ │ └── stalk-syscalls.js: Frida x64 syscall tracer script
│ └── shexec.c: Linux shellcode runner (used as a reference while implementing shexec.s for different architectures)
└── windows/
└── shexec.c: Windows shellcode runner
``````
lab/
├── linux/
│ ├── buffer-overflow/: proof of concept for a buffer overflow with the goal of executing shellcode
│ └── frida/: experimental Frida scripts
└── windows/
└── shellcode/: proof of concept for basic shellcode execution
```
Both POCs are documented in detail on my blog:- https://shadowshell.io/diving-into-shellcodes
- https://shadowshell.io/shattering-the-stack-0
- https://shadowshell.io/shattering-the-stack-1
- https://shadowshell.io/shattering-the-stack-2# Workflow
## Shellcode analysis
1. **Emulate the shellcode**
Use an emulator like [Qiling](https://github.com/qilingframework/qiling) to analyze the shellcode.2. **Fallback to `shexec` + `strace` / `shexec` + `Sysinternals`**
If you suspect the shellcode may detect the emulator (and behave differently as a result) switch to using `shexec` with `strace` (Linux) or `shexec` + `Sysinternals` (Windows).3. **Fallback to `frida`**
If the shellcode seems to detect tracing by `strace` or `Sysinternals` use `frida`.4. **Fallback to eBPF tracing (Linux only)**
If the shellcode seems to detect tracing by `frida` use [eBPF tracing](https://github.com/gemesa/sys-scout). If you do not want to implement the eBPF probes yourself, you can use tools like [Stratoshark](https://wiki.wireshark.org/Stratoshark).# How to build
## Prerequisites
```
$ sudo dnf install docker
$ sudo dnf install mingw64-gcc
$ sudo dnf install llvm
$ pip install frida-tools
$ rustup target add x86_64-pc-windows-gnu
```## x64 codebase
```
$ make x64
```## ARM64 codebase
I have an x64 PC so to quickly build ARM64 binaries my preference is to use an ARM64v8 Docker container.
```
$ sudo docker build --platform=linux/arm64 -t arm64 .
$ sudo docker run --platform=linux/arm64 --user $(id -u):$(id -g) --rm -it -v "$(pwd)":/workspace arm64 /bin/bash
$ make arm64
```The image is also available on Docker Hub:
```
$ docker pull gemesa/arm64:latest
```Alternatively an ARM64 cross-compiler could also be used.
```
$ dnf search *aarch64*
$ sudo dnf install gcc-aarch64-linux-gnu
$ sudo dnf install binutils-aarch64-linux-gnu
$ sudo dnf install sysroot-aarch64-fc41-glibc
```Quick guide:
```
$ aarch64-linux-gnu-gcc -L /usr/aarch64-redhat-linux/sys-root/fc41/lib64 -L /usr/aarch64-redhat-linux/sys-root/fc41/lib --sysroot=/usr/aarch64-redhat-linux/sys-root/fc41 arsenal/linux/arm64/shexec.s -o shexec
$ aarch64-linux-gnu-as arsenal/linux/arm64/shcode-hello.s -o shcode-hello.o
$ aarch64-linux-gnu-ld shcode-hello.o -o shcode-hello
$ llvm-objcopy -O binary --only-section=.text shcode-hello shcode-hello.bin
```To build the ARM64 binaries simply use the following command (ensure that the ARM64 cross-compiler is installed):
```
$ make arm64x
```# How to run
## Linux ARM64
### `shexec`
#### `strace` (QEMU)
```
$ sudo dnf install qemu-user
$ sudo dnf install qemu-user-static
$ dnf search *aarch64*
$ sudo dnf install qemu-system-aarch64
$ sudo dnf install qemu-system-aarch64-core
$ sudo dnf install qemu-user-static-aarch64
``````
$ qemu-aarch64 -L /usr/aarch64-redhat-linux/sys-root/fc41/usr build/linux/arm64x/shexec build/linux/arm64x/shcode-hello.bin
file size: 52 bytes
Hello!
$ strace qemu-aarch64 -L /usr/aarch64-redhat-linux/sys-root/fc41/usr build/linux/arm64x/shexec build/linux/arm64x/shcode-hello.bin
...
mprotect(0x7f4956b7c000, 4096, PROT_READ) = 0
write(1, "Hello!\n", 7Hello!
) = 7
rt_sigprocmask(SIG_SETMASK, ~[RTMIN RT_1], NULL, 8) = 0
exit_group(0) = ?
+++ exited with 0 +++
```#### `gdb` (QEMU)
```
$ qemu-aarch64 -L /usr/aarch64-redhat-linux/sys-root/fc41/usr -g 1234 build/linux/arm64x/shexec build/linux/arm64x/shcode-hello.bin &
$ gdb
gef➤ set architecture aarch64
gef➤ target remote localhost:1234
(remote) gef➤ b _start
(remote) gef➤ c
```#### `strace` (Docker on arm64 host)
```
$ sudo docker build --platform=linux/arm64 -t arm64 .
$ sudo docker run --platform=linux/arm64 --user $(id -u):$(id -g) --rm -it -v "$(pwd)":/workspace arm64 /bin/bash
$ strace build/linux/arm64/shexec build/linux/arm64/shcode-hello.bin
...
openat(AT_FDCWD, "build/linux/arm64/shcode-hello.bin", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0755, st_size=52, ...}) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0
getrandom("\x96\x76\xa8\x10\x42\x3b\x3b\x85", 8, GRND_NONBLOCK) = 8
brk(NULL) = 0x556f3c0000
brk(0x556f3e1000) = 0x556f3e1000
write(1, "file size: 52 bytes\n", 20file size: 52 bytes
) = 20
mmap(NULL, 52, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f987c6000
read(3, "\1\251\214\322\201\215\255\362\341-\304\362A\1\340\362\341\217\37\370 \0\200\322\341\3\0\221\342\0\200\322"..., 52) = 52
close(3) = 0
mprotect(0x7f987c6000, 52, PROT_READ|PROT_WRITE|PROT_EXEC) = 0
write(1, "Hello!\n", 7Hello!
) = 7
exit(0) = ?
+++ exited with 0 +++
```#### `strace` (native)
```
$ ./build/linux/arm64/shexec build/linux/arm64/shcode-hello.bin
file size: 52 bytes
Hello!
$ echo $$
1918
$ ./build/linux/arm64/shexec build/linux/arm64/shcode-shell.bin
file size: 40 bytes
$ echo $$
19341
$ exit
$ strace ./build/linux/arm64/shexec build/linux/arm64/shcode-hello.bin
...
openat(AT_FDCWD, "build/linux/arm64/shcode-hello.bin", O_RDONLY) = 3
newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=52, ...}, AT_EMPTY_PATH) = 0
newfstatat(1, "", {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}, AT_EMPTY_PATH) = 0
getrandom("\x20\xc3\xbe\x6a\x9d\x68\x76\xf9", 8, GRND_NONBLOCK) = 8
brk(NULL) = 0x55754e8000
brk(0x5575509000) = 0x5575509000
write(1, "file size: 52 bytes\n", 20file size: 52 bytes
) = 20
mmap(NULL, 52, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7facfe6000
read(3, "\1\251\214\322\201\215\255\362\341-\304\362A\1\340\362\341\217\37\370 \0\200\322\341\3\0\221\342\0\200\322"..., 52) = 52
close(3) = 0
mprotect(0x7facfe6000, 52, PROT_READ|PROT_WRITE|PROT_EXEC) = 0
write(1, "Hello!\n", 7Hello!
) = 7
exit(0) = ?
```#### `frida` (native)
```
$ frida -l arsenal/linux/arm64/stalk-syscalls.js -f build/linux/arm64/shexec build/linux/arm64/shcode-hello.bin
____
/ _ | Frida 16.5.9 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://frida.re/docs/home/
. . . .
. . . . Connected to Local System (id=local)
Spawned `build/linux/arm64/shexec build/linux/arm64/shcode-hello.bin`. Resuming main thread!
file size: 52 bytes
syscall @ 0x7f94453ac8, X8: 0xe2 (226)
[Local::shexec ]-> syscall @ 0x7f9452d024, X8: 0x40 (64) -> write()
X0: 0x1
X1: Hello!X2: 0x7
Process terminated
[Local::shexec ]->Thank you for using Frida!
```#### `frida` (Docker on arm64 host)
```
$ sudo docker build --platform=linux/arm64 -t arm64 .
$ sudo docker run --platform=linux/arm64 --user $(id -u):$(id -g) --rm -it -v "$(pwd)":/workspace arm64 /bin/bash
$ frida -l arsenal/linux/arm64/stalk-syscalls.js -f build/linux/arm64/shexec build/linux/arm64/shcode-hello.bin
____
/ _ | Frida 16.5.9 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://frida.re/docs/home/
. . . .
. . . . Connected to Local System (id=local)
Spawned `build/linux/arm64/shexec build/linux/arm64/shcode-hello.bin`. Resuming main thread!
file size: 52 bytes
syscall @ 0x7f9d977b08, X8: 0xe2 (226)
[Local::shexec ]-> syscall @ 0x7f9da4f024, X8: 0x40 (64) -> write()
X0: 0x1
X1: Hello!X2: 0x7
Process terminated
[Local::shexec ]->Thank you for using Frida!
```## Linux x64
### `shexec`
#### `strace` (QEMU)
```
$ sudo dnf install qemu-user
$ sudo dnf install qemu-user-static
``````
$ qemu-x86_64 build/linux/x64/shexec build/linux/x64/shcode-hello.bin
file size: 57 bytes
Hello, World!
$ strace qemu-x86_64 build/linux/x64/shexec build/linux/x64/shcode-hello.bin
...
mprotect(0x7f6f75e27000, 4096, PROT_READ) = 0
write(1, "Hello, World!\n\0", 15Hello, World!
) = 15
rt_sigprocmask(SIG_SETMASK, ~[RTMIN RT_1], NULL, 8) = 0
exit_group(0) = ?
+++ exited with 0 +++
```#### `gdb` (QEMU)
```
$ qemu-x86_64 -g 1234 build/linux/x64/shexec build/linux/x64/shcode-hello.bin &
$ gdb
gef➤ target remote localhost:1234
(remote) gef➤ b _start
(remote) gef➤ c
```#### `strace` (Docker on x64 host)
```
$ sudo docker build -f Dockerfile-x64 -t x64 .
$ sudo docker run --user $(id -u):$(id -g) --rm -it -v "$(pwd)":/workspace x64 /bin/bash
$ strace build/linux/x64/shexec build/linux/x64/shcode-hello.bin
...
openat(AT_FDCWD, "build/linux/x64/shcode-hello.bin", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0755, st_size=57, ...}) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0
getrandom("\x02\xc8\x9f\xe0\xfd\xfa\x65\x34", 8, GRND_NONBLOCK) = 8
brk(NULL) = 0x55d90b4ff000
brk(0x55d90b520000) = 0x55d90b520000
write(1, "file size: 57 bytes\n", 20file size: 57 bytes
) = 20
mmap(NULL, 57, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3a41a40000
read(3, "H\307\300\1\0\0\0H\307\307\1\0\0\0H\2155\25\0\0\0H\307\302\17\0\0\0\17\5H\307"..., 57) = 57
close(3) = 0
mprotect(0x7f3a41a40000, 57, PROT_READ|PROT_WRITE|PROT_EXEC) = 0
write(1, "Hello, World!\n\0", 15Hello, World!
) = 15
exit(0) = ?
+++ exited with 0 +++
```#### `strace` (native)
```
$ ./build/linux/x64/shexec build/linux/x64/shcode-hello.bin
file size: 57 bytes
Hello, World!
$ msfvenom -p linux/x64/exec CMD='echo \"Hello, World!\"' -f raw -o shellcode.bin
$ ./build/linux/x64/shexec shellcode.bin
file size: 57 bytes
Hello, World!
$ strace ./build/linux/x64/shexec build/linux/x64/shcode-hello.bin
...
openat(AT_FDCWD, "build/linux/x64/shcode-hello.bin", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0755, st_size=57, ...}) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x1), ...}) = 0
getrandom("\xae\x28\x5d\xfd\xd1\xae\x82\x68", 8, GRND_NONBLOCK) = 8
brk(NULL) = 0x55c7bbc4d000
brk(0x55c7bbc6e000) = 0x55c7bbc6e000
write(1, "file size: 57 bytes\n", 20file size: 57 bytes
) = 20
mmap(NULL, 57, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f94405b3000
read(3, "H\307\300\1\0\0\0H\307\307\1\0\0\0H\2155\25\0\0\0H\307\302\17\0\0\0\17\5H\307"..., 57) = 57
close(3) = 0
mprotect(0x7f94405b3000, 57, PROT_READ|PROT_WRITE|PROT_EXEC) = 0
write(1, "Hello, World!\n\0", 15Hello, World!
) = 15
exit(0) = ?
```#### `frida` (native)
```
$ frida -l arsenal/linux/x64/stalk-syscalls.js -f build/linux/x64/shexec build/linux/x64/shcode-hello.bin
____
/ _ | Frida 16.5.9 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://frida.re/docs/home/
. . . .
. . . . Connected to Local System (id=local)
Spawned `build/linux/x64/shexec build/linux/x64/shcode-hello.bin`. Resuming main thread!
file size: 57 bytes
Hello, World!
[Local::shexec ]-> syscall @ 0x7f8cf16e3839, RAX: 0xa (10)
syscall @ 0x7f8cf001b01c, RAX: 0x1 (1) -> write()
RDI: 0x1
RSI: Hello, World!RDX: 0xf
syscall @ 0x7f8cf001b028, RAX: 0x3c (60)
```#### `frida` (Docker on x64 host)
```
$ sudo docker build -f Dockerfile-x64 -t x64 .
$ sudo docker run --user $(id -u):$(id -g) --rm -it -v "$(pwd)":/workspace x64 /bin/bash
$ frida -l arsenal/linux/x64/stalk-syscalls.js -f build/linux/x64/shexec build/linux/x64/shcode-hello.bin
____
/ _ | Frida 16.5.9 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://frida.re/docs/home/
. . . .
. . . . Connected to Local System (id=local)
Spawned `build/linux/x64/shexec build/linux/x64/shcode-hello.bin`. Resuming main thread!
file size: 57 bytes
Hello, World!
[Local::shexec ]-> syscall @ 0x7f8c4c216c19, RAX: 0xa (10)
syscall @ 0x7f8c4c30401c, RAX: 0x1 (1) -> write()
RDI: 0x1
RSI: Hello, World!RDX: 0xf
syscall @ 0x7f8c4c304028, RAX: 0x3c (60)
```## Windows x64
### `shexec.exe`
#### Native
```
$ msfconsole
msf6 > info payload/windows/x64/shell_bind_tcp
...
Basic options:
Name Current Setting Required Description
---- --------------- -------- -----------
EXITFUNC process yes Exit technique (Accepted: '', seh, thread, process, none)
LPORT 4444 yes The listen port
RHOST no The target addressDescription:
Listen for a connection and spawn a command shell (Windows x64)
...
msf6 > exit
$ msfvenom -p windows/x64/shell_bind_tcp -f raw -o shellcode.bin
```Open `ProcMon` and `TCPView` then:
```
> .\shexec.exe shellcode.bin
```
# References
- https://chromium.googlesource.com/chromiumos/docs/+/HEAD/constants/syscalls.md
- https://wiki.osdev.org/Calling_Conventions
- https://en.wikipedia.org/wiki/X86_calling_conventions
- https://www.felixcloutier.com/x86/
- https://www.ibm.com/docs/en/aix/7.1?topic=volumes-using-file-descriptors
- https://www.ibm.com/docs/en/i/7.5?topic=extensions-standard-c-library-functions-table-by-name
- https://cs.lmu.edu/~ray/notes/gasexamples/
- https://web.stanford.edu/class/cs107/guide/x86-64.html
- http://unixwiz.net/techtips/x86-jumps.html
- https://cs.brown.edu/courses/cs033/docs/guides/x64_cheatsheet.pdf
- https://stackoverflow.com/questions/38335212/calling-printf-in-x86-64-using-gnu-assembler
- https://github.com/reg1reg1/Shellcode
- https://godbolt.org/
- https://dogbolt.org/
- https://nitesculucian.github.io/2018/07/24/msfvenom-cheat-sheet/
- https://ivanitlearning.wordpress.com/2018/10/14/shellcoding-with-msfvenom/
- https://security.stackexchange.com/questions/176495/executing-a-msfvenom-shellcode-in-c-program
- http://0xdabbad00.com/2012/12/07/dep-data-execution-prevention-explanation/
- https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc
- https://crates.io/crates/windows
- https://github.com/microsoft/windows-rs
- https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/Memory/fn.VirtualAlloc.html
- https://stackoverflow.com/questions/31492799/cross-compile-a-rust-application-from-linux-to-windows
- https://doc.rust-lang.org/core/ptr/fn.copy_nonoverlapping.html
- https://github.com/muhammet-mucahit/Security-Exercises
- https://lettieri.iet.unipi.it/hacking/aslr-pie.pdf
- https://reverseengineering.stackexchange.com/questions/19598/find-base-address-and-memory-size-of-program-debugged-in-gdb
- https://syscall.sh/
- https://developer.arm.com/documentation
- https://gist.github.com/luk6xff/9f8d2520530a823944355e59343eadc1
- https://www.exploit-db.com/exploits/47048
- https://stackoverflow.com/a/18483795
- https://learnfrida.info/advanced_usage/#getting-ret-addresses
- https://frida.re/docs/javascript-api/#memory