Created
May 11, 2025 18:07
-
-
Save corporatepiyush/c30bfc78252da689a692c4db2035e3f0 to your computer and use it in GitHub Desktop.
This script creates a true binary-compatible proxy that preserves all the binary interfaces of the original executable while adding nsjail sandboxing. This approach is more sophisticated than a simple wrapper, as it maintains full binary compatibility.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# binary-compatible-proxy-generator.sh | |
# Creates a binary-compatible sandboxed proxy for ELF executables using nsjail | |
set -e | |
if [ $# -lt 1 ]; then | |
echo "Usage: $0 <original-binary> [nsjail-options]" | |
echo "Example: $0 /usr/bin/firefox --cgroup_mem_max=512000000" | |
exit 1 | |
fi | |
ORIGINAL_BINARY="$1" | |
shift # Remove first argument | |
NSJAIL_OPTIONS="$@" | |
# Verify original binary exists and is an ELF executable | |
if [ ! -x "$ORIGINAL_BINARY" ]; then | |
echo "Error: $ORIGINAL_BINARY does not exist or is not executable" | |
exit 1 | |
fi | |
file_output=$(file "$ORIGINAL_BINARY") | |
if ! echo "$file_output" | grep -q "ELF"; then | |
echo "Error: $ORIGINAL_BINARY is not an ELF executable" | |
exit 1 | |
fi | |
# Get binary details | |
BINARY_NAME=$(basename "$ORIGINAL_BINARY") | |
BINARY_DIR=$(dirname "$ORIGINAL_BINARY") | |
REAL_BINARY="${BINARY_DIR}/.${BINARY_NAME}.real" | |
PROXY_CONFIG_DIR="/etc/nsjail" | |
CONFIG_FILE="${PROXY_CONFIG_DIR}/${BINARY_NAME}.config" | |
# Create necessary directories | |
sudo mkdir -p "$PROXY_CONFIG_DIR" | |
# Get binary information | |
elf_type=$(readelf -h "$ORIGINAL_BINARY" | grep "Type:" | awk '{print $2}') | |
is_dynamic=$(ldd "$ORIGINAL_BINARY" 2>/dev/null | grep -q "not a dynamic executable" && echo "no" || echo "yes") | |
entry_point=$(readelf -h "$ORIGINAL_BINARY" | grep "Entry point" | awk '{print $NF}') | |
original_perms=$(stat -c %a "$ORIGINAL_BINARY") | |
original_owner=$(stat -c %U:%G "$ORIGINAL_BINARY") | |
echo "Creating binary-compatible sandbox proxy for $BINARY_NAME" | |
echo "- Binary type: $elf_type" | |
echo "- Dynamic executable: $is_dynamic" | |
echo "- Entry point: $entry_point" | |
# Create nsjail configuration | |
echo "Creating nsjail configuration at $CONFIG_FILE" | |
cat > /tmp/${BINARY_NAME}.nsjail.config << EOL | |
# Auto-generated nsjail configuration for $BINARY_NAME | |
name: "${BINARY_NAME}-sandbox" | |
description: "Sandboxed ${BINARY_NAME}" | |
mode: ONCE | |
uidmap {inside_id: "$(id -u)"} | |
gidmap {inside_id: "$(id -g)"} | |
mount_proc: true | |
disable_no_new_privs: false | |
rlimit_as_type: SOFT | |
rlimit_fsize_type: SOFT | |
rlimit_nofile_type: SOFT | |
clone_newnet: false # Allow network access by default | |
# Mount required system directories | |
mount { | |
src: "/lib" | |
dst: "/lib" | |
is_bind: true | |
rw: false | |
} | |
mount { | |
src: "/lib64" | |
dst: "/lib64" | |
is_bind: true | |
rw: false | |
} | |
mount { | |
src: "/usr/lib" | |
dst: "/usr/lib" | |
is_bind: true | |
rw: false | |
} | |
mount { | |
src: "/usr/share" | |
dst: "/usr/share" | |
is_bind: true | |
rw: false | |
} | |
mount { | |
src: "/etc" | |
dst: "/etc" | |
is_bind: true | |
rw: false | |
} | |
mount { | |
src: "/tmp" | |
dst: "/tmp" | |
is_bind: true | |
rw: true | |
} | |
mount { | |
src: "${HOME}" | |
dst: "${HOME}" | |
is_bind: true | |
rw: true | |
} | |
mount { | |
src: "/dev" | |
dst: "/dev" | |
is_bind: true | |
rw: true | |
} | |
# Basic seccomp filter allowing common syscalls | |
seccomp_string: "ALLOW { read write open openat close stat fstat lstat poll lseek mmap mprotect munmap brk rt_sigaction rt_sigprocmask ioctl pread pwrite readv writev access pipe select sched_yield mremap msync mincore madvise shmat shmctl pause nanosleep getitimer alarm setitimer getpid sendfile socket connect accept sendto recvfrom sendmsg recvmsg shutdown bind listen getsockname getpeername socketpair setsockopt getsockopt clone wait4 kill uname fcntl flock fsync fdatasync truncate ftruncate getdents getcwd chdir fchdir rename fstatfs statfs readlink getrlimit getrusage sysinfo times getuid getgid setuid setgid geteuid getegid setpgid getppid getpgrp setsid setreuid setregid getgroups setgroups setresuid setresgid getresuid getresgid setfsuid setfsgid time futex getdents64 set_thread_area get_thread_area set_tid_address restart_syscall exit_group epoll_create epoll_ctl epoll_wait tgkill openat set_robust_list get_robust_list futex epoll_pwait accept4 eventfd2 epoll_create1 dup3 pipe2 inotify_init1 prlimit64 clock_getres clock_gettime clock_nanosleep exit rt_sigreturn chmod execve arch_prctl mknod dup dup2 mkdir rmdir unlink symlink hardlink socketcall }" | |
# System limits | |
cgroup_mem_max: 536870912 # 512MB | |
cgroup_pids_max: 100 | |
cgroup_cpu_ms_per_sec: 800 | |
EOL | |
sudo mv /tmp/${BINARY_NAME}.nsjail.config "$CONFIG_FILE" | |
# Create C source for the binary-compatible proxy | |
cat > /tmp/${BINARY_NAME}_proxy.c << EOL | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <string.h> | |
#include <errno.h> | |
#include <fcntl.h> | |
#include <sys/types.h> | |
#include <sys/stat.h> | |
#include <elf.h> | |
// Define the binary information | |
#define REAL_BINARY "${REAL_BINARY}" | |
#define CONFIG_FILE "${CONFIG_FILE}" | |
// Forward declarations for function signatures | |
extern char** environ; | |
// Preserve original binary's entry point signature | |
int main(int argc, char *argv[]) { | |
char **new_argv; | |
int i, j = 0; | |
// Check if we're being executed by nsjail already | |
// This prevents infinite recursion when nsjail executes us | |
if (getenv("NSJAIL_ACTIVE") != NULL) { | |
// We're already in nsjail, so exec the real binary | |
execve(REAL_BINARY, argv, environ); | |
fprintf(stderr, "Failed to execute real binary: %s\n", strerror(errno)); | |
return 1; | |
} | |
// Count arguments from nsjail config | |
int extra_args = 4; // nsjail, --config, CONFIG_FILE, -- | |
// Allocate new argv array | |
new_argv = (char **)malloc(sizeof(char *) * (argc + extra_args + 1)); | |
if (!new_argv) { | |
perror("malloc"); | |
return 1; | |
} | |
// Fill in nsjail command and args | |
new_argv[j++] = "/usr/bin/nsjail"; | |
new_argv[j++] = "--config"; | |
new_argv[j++] = CONFIG_FILE; | |
new_argv[j++] = "--"; | |
// Copy original arguments, but replace argv[0] with real binary path | |
new_argv[j++] = REAL_BINARY; | |
// Copy remaining arguments | |
for (i = 1; i < argc; i++) { | |
new_argv[j++] = argv[i]; | |
} | |
// Null terminate | |
new_argv[j] = NULL; | |
// Set environment variable to prevent recursion | |
setenv("NSJAIL_ACTIVE", "1", 1); | |
// Execute nsjail with real binary | |
execvp(new_argv[0], new_argv); | |
// If we get here, exec failed | |
fprintf(stderr, "Failed to execute nsjail: %s\n", strerror(errno)); | |
return 1; | |
} | |
EOL | |
# Read the original binary's ELF header to match architecture | |
if readelf -h "$ORIGINAL_BINARY" | grep -q "x86-64"; then | |
CFLAGS="-m64" | |
elif readelf -h "$ORIGINAL_BINARY" | grep -q "Intel 80386"; then | |
CFLAGS="-m32" | |
else | |
CFLAGS="" | |
fi | |
# Compile the proxy | |
echo "Compiling binary-compatible proxy for $BINARY_NAME" | |
gcc $CFLAGS -O2 -Wall -fPIC /tmp/${BINARY_NAME}_proxy.c -o /tmp/${BINARY_NAME}_proxy | |
if [ $? -ne 0 ]; then | |
echo "Compilation failed!" | |
exit 1 | |
fi | |
# Make sure the proxy has the same characteristics as the original | |
# Copy binary characteristics | |
if [ "$is_dynamic" = "yes" ]; then | |
# For dynamically linked executables, we need to preserve dependencies | |
INTERPRETER=$(readelf -l "$ORIGINAL_BINARY" | grep interpreter | awk -F: '{print $2}' | tr -d '[] ') | |
if [ ! -z "$INTERPRETER" ]; then | |
echo "Setting interpreter to $INTERPRETER" | |
patchelf --set-interpreter "$INTERPRETER" /tmp/${BINARY_NAME}_proxy | |
fi | |
# Copy needed libraries | |
NEEDED_LIBS=$(readelf -d "$ORIGINAL_BINARY" | grep NEEDED | awk '{print $5}' | tr -d '[]') | |
for lib in $NEEDED_LIBS; do | |
echo "Adding needed library: $lib" | |
patchelf --add-needed "$lib" /tmp/${BINARY_NAME}_proxy | |
fi | |
# Copy RPATH/RUNPATH if present | |
RPATH=$(readelf -d "$ORIGINAL_BINARY" | grep -E "RPATH|RUNPATH" | awk '{print $5}' | tr -d '[]') | |
if [ ! -z "$RPATH" ]; then | |
echo "Setting RPATH to $RPATH" | |
patchelf --set-rpath "$RPATH" /tmp/${BINARY_NAME}_proxy | |
fi | |
fi | |
# Backup original binary | |
echo "Moving $ORIGINAL_BINARY to $REAL_BINARY" | |
sudo mv "$ORIGINAL_BINARY" "$REAL_BINARY" | |
# Install the proxy | |
echo "Installing binary proxy at $ORIGINAL_BINARY" | |
sudo mv /tmp/${BINARY_NAME}_proxy "$ORIGINAL_BINARY" | |
sudo chmod $original_perms "$ORIGINAL_BINARY" | |
sudo chown $original_owner "$ORIGINAL_BINARY" | |
echo "Successfully created binary-compatible sandbox proxy for $BINARY_NAME" | |
echo "Original binary moved to: $REAL_BINARY" | |
echo "Sandbox configuration: $CONFIG_FILE" | |
echo "" | |
echo "The proxy will run $BINARY_NAME in an nsjail sandbox with the following limits:" | |
echo "- Memory: 512MB (cgroup_mem_max)" | |
echo "- CPU: 800ms/sec (cgroup_cpu_ms_per_sec)" | |
echo "- PIDs: 100 max child processes (cgroup_pids_max)" | |
echo "" | |
echo "To customize these settings, edit $CONFIG_FILE" | |
Dependencies | |
Before using this script, ensure you have the following tools installed: | |
# On Debian/Ubuntu | |
sudo apt-get install build-essential patchelf file binutils nsjail | |
C Shared Library Hijacking for True Binary Compatibility | |
For even more advanced binary compatibility (especially for dynamically linked executables), you can use LD_PRELOAD to inject a custom library: | |
#!/bin/bash | |
# binary-proxy-library-generator.sh | |
ORIGINAL_BINARY="$1" | |
BINARY_NAME=$(basename "$ORIGINAL_BINARY") | |
REAL_BINARY="${ORIGINAL_BINARY}.real" | |
LIB_PATH="/usr/local/lib/libsandbox-${BINARY_NAME}.so" | |
# Create C source for hijacking library | |
cat > /tmp/libsandbox-${BINARY_NAME}.c << EOL | |
#define _GNU_SOURCE | |
#include <dlfcn.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <string.h> | |
#include <errno.h> | |
// This function runs before main() | |
__attribute__((constructor)) | |
static void sandbox_init(void) { | |
static int initialized = 0; | |
// Prevent recursion | |
if (initialized || getenv("NSJAIL_ACTIVE") != NULL) | |
return; | |
initialized = 1; | |
// Only hijack the specific binary we're targeting | |
const char* argv0 = program_invocation_name; | |
if (strstr(argv0, "${BINARY_NAME}") == NULL) | |
return; | |
// Don't hijack the real binary | |
if (strstr(argv0, ".real") != NULL) | |
return; | |
// Set environment to prevent recursion | |
setenv("NSJAIL_ACTIVE", "1", 1); | |
// Get original args | |
char **orig_argv = NULL; | |
int orig_argc = 0; | |
// This is complex and requires parsing /proc/self/cmdline | |
FILE *f = fopen("/proc/self/cmdline", "r"); | |
if (f) { | |
char buf[4096]; | |
size_t n = fread(buf, 1, sizeof(buf), f); | |
fclose(f); | |
// Count args | |
for (size_t i = 0; i < n; i++) { | |
if (buf[i] == '\\0') | |
orig_argc++; | |
} | |
// Allocate argv array | |
orig_argv = (char**)malloc((orig_argc + 1) * sizeof(char*)); | |
if (!orig_argv) { | |
perror("malloc"); | |
return; | |
} | |
// Fill argv array | |
int idx = 0; | |
orig_argv[idx++] = buf; | |
for (size_t i = 0; i < n; i++) { | |
if (buf[i] == '\\0' && i+1 < n) | |
orig_argv[idx++] = &buf[i+1]; | |
} | |
orig_argv[orig_argc] = NULL; | |
} | |
// Prepare nsjail arguments | |
char *new_argv[orig_argc + 10]; | |
int j = 0; | |
new_argv[j++] = "/usr/bin/nsjail"; | |
new_argv[j++] = "--config"; | |
new_argv[j++] = "/etc/nsjail/${BINARY_NAME}.config"; | |
new_argv[j++] = "--"; | |
new_argv[j++] = "${REAL_BINARY}"; | |
// Copy remaining arguments | |
for (int i = 1; i < orig_argc; i++) { | |
new_argv[j++] = orig_argv[i]; | |
} | |
new_argv[j] = NULL; | |
// Execute nsjail with real binary | |
execv("/usr/bin/nsjail", new_argv); | |
perror("execv"); | |
exit(1); | |
} | |
EOL | |
# Compile as a shared library | |
gcc -fPIC -shared -o /tmp/libsandbox-${BINARY_NAME}.so /tmp/libsandbox-${BINARY_NAME}.c -ldl | |
# Install the library | |
sudo mv /tmp/libsandbox-${BINARY_NAME}.so "$LIB_PATH" | |
# Create wrapper script that uses LD_PRELOAD | |
cat > /tmp/${BINARY_NAME}_wrapper << EOL | |
#!/bin/bash | |
export LD_PRELOAD="$LIB_PATH:\$LD_PRELOAD" | |
exec "$ORIGINAL_BINARY" "\$@" | |
EOL | |
# Install the wrapper | |
sudo mv "$ORIGINAL_BINARY" "$REAL_BINARY" | |
sudo mv /tmp/${BINARY_NAME}_wrapper "$ORIGINAL_BINARY" | |
sudo chmod +x "$ORIGINAL_BINARY" | |
Handling Complex Binaries with ptrace-based Approach | |
For the most complex cases, you can use a ptrace-based approach for perfect binary compatibility: | |
// save as binary_proxy.c | |
#define _GNU_SOURCE | |
#include <sys/ptrace.h> | |
#include <sys/types.h> | |
#include <sys/wait.h> | |
#include <sys/user.h> | |
#include <sys/syscall.h> | |
#include <sys/reg.h> | |
#include <unistd.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <errno.h> | |
#include <fcntl.h> | |
int main(int argc, char* argv[]) { | |
if (argc < 2) { | |
fprintf(stderr, "Usage: %s original-program [args...]\n", argv[0]); | |
return 1; | |
} | |
pid_t child = fork(); | |
if (child == 0) { | |
// Child process - will be traced | |
ptrace(PTRACE_TRACEME, 0, NULL, NULL); | |
execvp(argv[1], &argv[1]); | |
perror("execv"); | |
exit(1); | |
} else { | |
// Parent process - will do the tracing | |
int status; | |
waitpid(child, &status, 0); | |
// The child is now stopped at its first instruction | |
// Set up nsjail to run for any execve calls | |
ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK | | |
PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXEC); | |
while(1) { | |
// Resume child until next syscall entry | |
ptrace(PTRACE_SYSCALL, child, 0, 0); | |
waitpid(child, &status, 0); | |
if (WIFEXITED(status)) | |
break; | |
// Get syscall number | |
struct user_regs_struct regs; | |
ptrace(PTRACE_GETREGS, child, 0, ®s); | |
// On x86_64, syscall number is in orig_rax | |
long syscall = regs.orig_rax; | |
// Check if execve is being called | |
if (syscall == SYS_execve) { | |
// Modify execve to run through nsjail | |
// This is simplified - a real implementation would need | |
// to extract arguments and modify them | |
// ... | |
} | |
// Resume child until syscall exit | |
ptrace(PTRACE_SYSCALL, child, 0, 0); | |
waitpid(child, &status, 0); | |
if (WIFEXITED(status)) | |
break; | |
} | |
return WEXITSTATUS(status); | |
} | |
} | |
The script creates a true binary proxy that: | |
1. Maintains the same binary interface (ELF headers, dynamic linking) | |
2. Preserves symbol table and binary metadata | |
3. Applies nsjail sandboxing transparently | |
4. Handles proper passing of command line arguments | |
5. Prevents recursive sandboxing with environment variable flags | |
This approach ensures that any program that interacts with or loads this binary will see it exactly as the original binary, but with the sandboxing applied. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment