Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save corporatepiyush/c30bfc78252da689a692c4db2035e3f0 to your computer and use it in GitHub Desktop.
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.
#!/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, &regs);
// 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