无垠之码

深度剖析代码之道


加密shell脚本

neurobin-shc作为一款通用的SHELL脚本编译器,将换用户输入的SHELL脚本转换为C语言代码,编译链接形成一份剥离符号的可执行程序。shc并不是类似与cc的编译器工具,它只是简单的编码和加密SHELL脚本,生成C语言代码,同时增加比如脚本过期检测,等其他特性。其使用系统的编译器和相关工具生成剥离符号的可执行程序,程序与脚本执行效果相同。

需要注意的是其编译的二进制文件取决于SHELL解释器,shc产生的并非完全独立与系统的二进制程序,其处理后的可执行程序只能在编译主机相同的环境中运行

0.使用


shc支持很多选项,开启特定功能,比如用户可以使用-e指定脚本的有效执行时间,-m定制过期后的技术支持联系信息,-D选择在执行时显示调试信息,-v展示编译过程的参数信息,-S将root执行的脚本设置为setuid,-U使得程序防止strace,ptrace追踪,-r允许生成的二进制文件,能够在相同版本的操作系统运行

下面假设用户需要加密的脚本文件test.sh,使用shc -rf test.sh加密脚本,默认生成可执行文件test.sh.x和C语言文件test.sh.x.c

cat test.sh
#!/bin/bash
echo "hello,world

test.sh  test.sh.x  test.sh.x.c
test.sh.x: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=044499892c9c33d7c91f45feb1413e41dd1e2a27, for GNU/Linux 3.2.0, stripped

test.sh.x.c文件内容如下所示

#if 0
        shc Version 4.0.3, Generic Shell Script Compiler
        GNU GPL Version 3 Md Jahidul Hamid <jahidulhamid@yahoo.com>

        shc -rf test.sh 
#endif

static  char data [] = 
#define      tst1_z     22
#define      tst1       ((&data[1]))
        "\030\115\076\307\352\251\122\334\257\345\123\205\106\331\310\126"
        "\306\365\222\136\257\375\074\131\151\322\311\234"
#define      tst2_z     19
#define      tst2       ((&data[29]))
        "\373\001\053\034\243\243\327\252\343\047\144\311\002\062\041\060"
        "\005\045\255\050\026\056"
#define      shll_z     10
#define      shll       ((&data[50]))
        "\026\324\303\131\264\245\233\155\211\041\365"
#define      chk2_z     19
#define      chk2       ((&data[63]))
        "\343\134\027\267\010\164\350\146\006\173\011\157\012\014\065\350"
        "\070\374\246\344\244\152"
#define      lsto_z     1
#define      lsto       ((&data[83]))
        "\241"
#define      chk1_z     22
#define      chk1       ((&data[84]))
        "\263\160\137\140\034\005\137\221\361\364\205\373\062\214\141\366"
        "\004\177\354\243\302\204\026"
#define      date_z     1
#define      date       ((&data[107]))
        "\212"
#define      msg2_z     19
#define      msg2       ((&data[111]))
        "\124\074\340\040\252\304\020\035\177\177\014\140\114\351\344\073"
        "\344\317\367\045\356\177"
#define      rlax_z     1
#define      rlax       ((&data[130]))
        "\255"
#define      opts_z     1
#define      opts       ((&data[131]))
        "\234"
#define      pswd_z     256
#define      pswd       ((&data[152]))
        "\357\272\107\322\026\261\100\334\314\320\337\023\346\020\111\025"
        "\233\351\027\357\262\242\314\056\113\200\220\263\311\116\231\164"
        "\001\342\261\062\254\213\257\162\322\374\160\203\243\005\063\236"
        "\235\220\174\120\063\111\176\177\311\017\062\223\135\314\010\137"
        "\256\271\221\132\105\101\315\030\076\076\233\341\103\317\177\341"
        "\137\374\061\223\105\257\023\017\276\106\243\034\022\253\173\301"
        "\145\015\034\252\117\352\302\215\050\136\157\153\055\356\114\215"
        "\353\175\040\061\055\064\100\354\172\344\010\215\217\204\116\364"
        "\222\153\237\341\125\141\157\175\277\336\351\355\315\066\172\270"
        "\264\233\351\341\317\052\316\112\016\327\327\236\133\046\223\355"
        "\221\062\317\347\224\076\144\124\034\116\101\352\204\273\242\071"
        "\126\214\032\046\267\351\160\306\300\110\144\034\157\367\011\001"
        "\052\330\350\276\027\115\023\063\233\124\036\040\020\300\131\147"
        "\115\164\215\004\135\376\312\036\107\057\072\266\047\103\267\121"
        "\034\240\020\064\355\044\150\210\170\206\250\210\106\001\357\224"
        "\165\175\230\323\174\143\361\303\222\053\172\272\157\062\013\214"
        "\322\034\300\277\100\050\110\271\256\361\102\365\363\062\211\151"
        "\257\042\074\054\046\370\232"
#define      text_z     34
#define      text       ((&data[418]))
        "\252\356\135\373\375\114\265\121\361\372\222\156\226\243\055\231"
        "\035\244\362\272\374\214\031\277\114\012\305\343\323\073\203\042"
        "\047\167\354\114\031\202\220\354\201\105\037\314"
#define      msg1_z     65
#define      msg1       ((&data[461]))
        "\303\057\210\326\026\230\210\113\110\226\277\361\215\017\204\015"
        "\040\353\156\317\067\270\136\021\057\307\176\277\035\021\052\261"
        "\320\264\037\020\256\021\054\242\230\360\304\367\224\310\357\365"
        "\010\152\041\053\367\163\225\221\162\211\202\264\264\313\370\056"
        "\022\052\234\065\246\133\025\037\054\063\011\104\043\057\074\276"
        "\000\022"
#define      inlo_z     3
#define      inlo       ((&data[537]))
        "\002\114\021"
#define      xecc_z     15
#define      xecc       ((&data[542]))
        "\304\135\155\330\141\311\253\016\333\076\154\063\324\263\057\111"
        "\254\103\344\051"/* End of data[] */;
#define      hide_z     4096
#define SETUID 0        /* Define as 1 to call setuid(0) at start of script */
#define DEBUGEXEC       0       /* Define as 1 to debug execvp calls */
#define TRACEABLE       1       /* Define as 1 to enable ptrace the executable */
#define HARDENING       0       /* Define as 1 to disable ptrace/dump the executable */
#define BUSYBOXON       0       /* Define as 1 to enable work with busybox */
#define MMAP2           0       /* Define as 1 to use syscall mmap2 */

#if HARDENING
static const char * shc_x[] = {
"/*",
" * Copyright 2019 - Intika <intika@librefox.org>",
" * Replace ******** with secret read from fd 21",
" * Also change arguments location of sub commands (sh script commands)",
" * gcc -Wall -fpic -shared -o shc_secret.so shc_secret.c -ldl",
" */",
"",
"#define _GNU_SOURCE /* needed to get RTLD_NEXT defined in dlfcn.h */",
"#define PLACEHOLDER \"********\"",
"#include <dlfcn.h>",
"#include <stdlib.h>",
"#include <string.h>",
"#include <unistd.h>",
"#include <stdio.h>",
"#include <signal.h>",
"",
"static char secret[128000]; //max size",
"typedef int (*pfi)(int, char **, char **);",
"static pfi real_main;",
"",
"// copy argv to new location",
"char **copyargs(int argc, char** argv){",
"    char **newargv = malloc((argc+1)*sizeof(*argv));",
"    char *from,*to;",
"    int i,len;",
"",
"    for(i = 0; i<argc; i++){",
"        from = argv[i];",
"        len = strlen(from)+1;",
"        to = malloc(len);",
"        memcpy(to,from,len);",
"        // zap old argv space",
"        memset(from,'\\0',len);",
"        newargv[i] = to;",
"        argv[i] = 0;",
"    }",
"    newargv[argc] = 0;",
"    return newargv;",
"}",
"",
"static int mymain(int argc, char** argv, char** env) {",
"    //fprintf(stderr,        if (["localhost", "127.0.0.1"].indexOf(window.location.hostname) != -1) {
            document.getElementById('disqus_thread').innerHTML = 'Disqus comments not available by default when the website is previewed locally.';
            return;
        } \"Inject main argc = %d\\n\", argc);",
"    return real_main(argc, copyargs(argc,argv), env);",
"}",
"",
"int __libc_start_main(int (*main) (int, char**, char**),",
"                      int argc,",
"                      char **argv,",
"                      void (*init) (void),",
"                      void (*fini)(void),",
"                      void (*rtld_fini)(void),",
"                      void (*stack_end)){",
"    static int (*real___libc_start_main)() = NULL;",
"    int n;",
"",
"    if (!real___libc_start_main) {",
"        real___libc_start_main = dlsym(RTLD_NEXT, \"__libc_start_main\");",
"        if (!real___libc_start_main) abort();",
"    }",
"",
"    n = read(21, secret, sizeof(secret));",
"    if (n > 0) {",
"      int i;",
"",
"    if (secret[n - 1] == '\\n') secret[--n] = '\\0';",
"    for (i = 1; i < argc; i++)",
"        if (strcmp(argv[i], PLACEHOLDER) == 0)",
"          argv[i] = secret;",
"    }",
"",
"    real_main = main;",
"",
"    return real___libc_start_main(mymain, argc, argv, init, fini, rtld_fini, stack_end);",
"}",
"",
0};
#endif /* HARDENING */

/* rtc.c */

#include <sys/stat.h>
#include <sys/types.h>

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

/* 'Alleged RC4' */

static unsigned char stte[256], indx, jndx, kndx;

/*
 * Reset arc4 stte. 
 */
void stte_0(void)
{
        indx = jndx = kndx = 0;
        do {
                stte[indx] = indx;
        } while (++indx);
}

/*
 * Set key. Can be used more than once. 
 */
void key(void * str, int len)
{
        unsigned char tmp, * ptr = (unsigned char *)str;
        while (len > 0) {
                do {
                        tmp = stte[indx];
                        kndx += tmp;
                        kndx += ptr[(int)indx % len];
                        stte[indx] = stte[kndx];
                        stte[kndx] = tmp;
                } while (++indx);
                ptr += 256;
                len -= 256;
        }
}

/*
 * Crypt data. 
 */
void arc4(void * str, int len)
{
        unsigned char tmp, * ptr = (unsigned char *)str;
        while (len > 0) {
                indx++;
                tmp = stte[indx];
                jndx += tmp;
                stte[indx] = stte[jndx];
                stte[jndx] = tmp;
                tmp += stte[indx];
                *ptr ^= stte[tmp];
                ptr++;
                len--;
        }
}

/* End of ARC4 */

#if HARDENING

#include <sys/ptrace.h>
#include <sys/wait.h>
#include <signal.h>
#include <sys/prctl.h>
#define PR_SET_PTRACER 0x59616d61

/* Seccomp Sandboxing Init */
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <sys/socket.h>

#include <linux/filter.h>
#include <linux/seccomp.h>
#include <linux/audit.h>

#define ArchField offsetof(struct seccomp_data, arch)

#define Allow(syscall) \
    BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, SYS_##syscall, 0, 1), \
    BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW)

struct sock_filter filter[] = {
    /* validate arch */
    BPF_STMT(BPF_LD+BPF_W+BPF_ABS, ArchField),
    BPF_JUMP( BPF_JMP+BPF_JEQ+BPF_K, AUDIT_ARCH_X86_64, 1, 0),
    BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL),

    /* load syscall */
    BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, nr)),

    /* list of allowed syscalls */
    Allow(exit_group),  /* exits a process */
    Allow(brk),         /* for malloc(), inside libc */
#if MMAP2
    Allow(mmap2),       /* also for malloc() */
#else
    Allow(mmap),        /* also for malloc() */
#endif
    Allow(munmap),      /* for free(), inside libc */

    /* and if we don't match above, die */
    BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL),
};
struct sock_fprog filterprog = {
    .len = sizeof(filter)/sizeof(filter[0]),
    .filter = filter
};

/* Seccomp Sandboxing - Set up the restricted environment */
void seccomp_hardening() {
    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
        perror("Could not start seccomp:");
        exit(1);
    }
    if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &filterprog) == -1) {
        perror("Could not start seccomp:");
        exit(1);
    }
} 
/* End Seccomp Sandboxing Init */

void shc_x_file() {
    FILE *fp;
    int line = 0;

    if ((fp = fopen("/tmp/shc_x.c", "w")) == NULL ) {exit(1); exit(1);}
    for (line = 0; shc_x[line]; line++) fprintf(fp, "%s\n", shc_x[line]);
    fflush(fp);fclose(fp);
}

int make() {
        char * cc, * cflags, * ldflags;
        char cmd[4096];

        cc = getenv("CC");
        if (!cc) cc = "cc";

        sprintf(cmd, "%s %s -o %s %s", cc, "-Wall -fpic -shared", "/tmp/shc_x.so", "/tmp/shc_x.c -ldl");
        if (system(cmd)) {remove("/tmp/shc_x.c"); return -1;}
        remove("/tmp/shc_x.c"); return 0;
}

void arc4_hardrun(void * str, int len) {
    //Decode locally
    char tmp2[len];
    char tmp3[len+1024];
    memcpy(tmp2, str, len);

    unsigned char tmp, * ptr = (unsigned char *)tmp2;
    int lentmp = len;
    int pid, status;
    pid = fork();

    shc_x_file();
    if (make()) {exit(1);}

    setenv("LD_PRELOAD","/tmp/shc_x.so",1);

    if(pid==0) {

        //Start tracing to protect from dump & trace
        if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0) {
            kill(getpid(), SIGKILL);
            _exit(1);
        }

        //Decode Bash
        while (len > 0) {
            indx++;
            tmp = stte[indx];
            jndx += tmp;
            stte[indx] = stte[jndx];
            stte[jndx] = tmp;
            tmp += stte[indx];
            *ptr ^= stte[tmp];
            ptr++;
            len--;
        }

        //Do the magic
        sprintf(tmp3, "%s %s", "'********' 21<<<", tmp2);

        //Exec bash script //fork execl with 'sh -c'
        system(tmp2);

        //Empty script variable
        memcpy(tmp2, str, lentmp);

        //Clean temp
        remove("/tmp/shc_x.so");

        //Sinal to detach ptrace
        ptrace(PTRACE_DETACH, 0, 0, 0);
        exit(0);
    }
    else {wait(&status);}

    /* Seccomp Sandboxing - Start */
    seccomp_hardening();

    exit(0);
}
#endif /* HARDENING */

/*
 * Key with file invariants. 
 */
int key_with_file(char * file)
{
        struct stat statf[1];
        struct stat control[1];

        if (stat(file, statf) < 0)
                return -1;

        /* Turn on stable fields */
        memset(control, 0, sizeof(control));
        control->st_ino = statf->st_ino;
        control->st_dev = statf->st_dev;
        control->st_rdev = statf->st_rdev;
        control->st_uid = statf->st_uid;
        control->st_gid = statf->st_gid;
        control->st_size = statf->st_size;
        control->st_mtime = statf->st_mtime;
        control->st_ctime = statf->st_ctime;
        key(control, sizeof(control));
        return 0;
}

#if DEBUGEXEC
void debugexec(char * sh11, int argc, char ** argv)
{
        int i;
        fprintf(stderr, "shll=%s\n", sh11 ? sh11 : "<null>");
        fprintf(stderr, "argc=%d\n", argc);
        if (!argv) {
                fprintf(stderr, "argv=<null>\n");
        } else { 
                for (i = 0; i <= argc ; i++)
                        fprintf(stderr, "argv[%d]=%.60s\n", i, argv[i] ? argv[i] : "<null>");
        }
}
#endif /* DEBUGEXEC */

void rmarg(char ** argv, char * arg)
{
        for (; argv && *argv && *argv != arg; argv++);
        for (; argv && *argv; argv++)
                *argv = argv[1];
}

void chkenv_end(void);

int chkenv(int argc)
{
        char buff[512];
        unsigned long mask, m;
        int l, a, c;
        char * string;
        extern char ** environ;

        mask = (unsigned long)getpid();
        stte_0();
         key(&chkenv, (void*)&chkenv_end - (void*)&chkenv);
         key(&data, sizeof(data));
         key(&mask, sizeof(mask));
        arc4(&mask, sizeof(mask));
        sprintf(buff, "x%lx", mask);
        string = getenv(buff);
#if DEBUGEXEC
        fprintf(stderr, "getenv(%s)=%s\n", buff, string ? string : "<null>");
#endif
        l = strlen(buff);
        if (!string) {
                /* 1st */
                sprintf(&buff[l], "=%lu %d", mask, argc);
                putenv(strdup(buff));
                return 0;
        }
        c = sscanf(string, "%lu %d%c", &m, &a, buff);
        if (c == 2 && m == mask) {
                /* 3rd */
                rmarg(environ, &string[-l - 1]);
                return 1 + (argc - a);
        }
        return -1;
}

void chkenv_end(void){}

#if HARDENING

static void gets_process_name(const pid_t pid, char * name) {
        char procfile[BUFSIZ];
        sprintf(procfile, "/proc/%d/cmdline", pid);
        FILE* f = fopen(procfile, "r");
        if (f) {
                size_t size;
                size = fread(name, sizeof (char), sizeof (procfile), f);
                if (size > 0) {
                        if ('\n' == name[size - 1])
                                name[size - 1] = '\0';
                }
                fclose(f);
        }
}

void hardening() {
    prctl(PR_SET_DUMPABLE, 0);
    prctl(PR_SET_PTRACER, -1);

    int pid = getppid();
    char name[256] = {0};
    gets_process_name(pid, name);

    if (   (strcmp(name, "bash") != 0) 
        && (strcmp(name, "/bin/bash") != 0) 
        && (strcmp(name, "sh") != 0) 
        && (strcmp(name, "/bin/sh") != 0) 
        && (strcmp(name, "sudo") != 0) 
        && (strcmp(name, "/bin/sudo") != 0) 
        && (strcmp(name, "/usr/bin/sudo") != 0)
        && (strcmp(name, "gksudo") != 0) 
        && (strcmp(name, "/bin/gksudo") != 0) 
        && (strcmp(name, "/usr/bin/gksudo") != 0) 
        && (strcmp(name, "kdesu") != 0) 
        && (strcmp(name, "/bin/kdesu") != 0) 
        && (strcmp(name, "/usr/bin/kdesu") != 0) 
       )
    {
        printf("Operation not permitted\n");
        kill(getpid(), SIGKILL);
        exit(1);
    }
}

#endif /* HARDENING */

#if !TRACEABLE

#define _LINUX_SOURCE_COMPAT
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

#if !defined(PT_ATTACHEXC) /* New replacement for PT_ATTACH */
   #if !defined(PTRACE_ATTACH) && defined(PT_ATTACH)
       #define PT_ATTACHEXC     PT_ATTACH
   #elif defined(PTRACE_ATTACH)
       #define PT_ATTACHEXC PTRACE_ATTACH
   #endif
#endif

void untraceable(char * argv0)
{
        char proc[80];
        int pid, mine;

        switch(pid = fork()) {
        case  0:
                pid = getppid();
                /* For problematic SunOS ptrace */
#if defined(__FreeBSD__)
                sprintf(proc, "/proc/%d/mem", (int)pid);
#else
                sprintf(proc, "/proc/%d/as",  (int)pid);
#endif
                close(0);
                mine = !open(proc, O_RDWR|O_EXCL);
                if (!mine && errno != EBUSY)
                        mine = !ptrace(PT_ATTACHEXC, pid, 0, 0);
                if (mine) {
                        kill(pid, SIGCONT);
                } else {
                        perror(argv0);
                        kill(pid, SIGKILL);
                }
                _exit(mine);
        case -1:
                break;
        default:
                if (pid == waitpid(pid, 0, 0))
                        return;
        }
        perror(argv0);
        _exit(1);
}
#endif /* !TRACEABLE */

char * xsh(int argc, char ** argv)
{
        char * scrpt;
        int ret, i, j;
        char ** varg;
        char * me = argv[0];
        if (me == NULL) { me = getenv("_"); }
        if (me == 0) { fprintf(stderr, "E: neither argv[0] nor $_ works."); exit(1); }

        ret = chkenv(argc);
        stte_0();
         key(pswd, pswd_z);
        arc4(msg1, msg1_z);
        arc4(date, date_z);
        if (date[0] && (atoll(date)<time(NULL)))
                return msg1;
        arc4(shll, shll_z);
        arc4(inlo, inlo_z);
        arc4(xecc, xecc_z);
        arc4(lsto, lsto_z);
        arc4(tst1, tst1_z);
         key(tst1, tst1_z);
        arc4(chk1, chk1_z);
        if ((chk1_z != tst1_z) || memcmp(tst1, chk1, tst1_z))
                return tst1;
        arc4(msg2, msg2_z);
        if (ret < 0)
                return msg2;
        varg = (char **)calloc(argc + 10, sizeof(char *));
        if (!varg)
                return 0;
        if (ret) {
                arc4(rlax, rlax_z);
                if (!rlax[0] && key_with_file(shll))
                        return shll;
                arc4(opts, opts_z);
#if HARDENING
            arc4_hardrun(text, text_z);
            exit(0);
       /* Seccomp Sandboxing - Start */
       seccomp_hardening();
#endif
                arc4(text, text_z);
                arc4(tst2, tst2_z);
                 key(tst2, tst2_z);
                arc4(chk2, chk2_z);
                if ((chk2_z != tst2_z) || memcmp(tst2, chk2, tst2_z))
                        return tst2;
                /* Prepend hide_z spaces to script text to hide it. */
                scrpt = malloc(hide_z + text_z);
                if (!scrpt)
                        return 0;
                memset(scrpt, (int) ' ', hide_z);
                memcpy(&scrpt[hide_z], text, text_z);
        } else {                        /* Reexecute */
                if (*xecc) {
                        scrpt = malloc(512);
                        if (!scrpt)
                                return 0;
                        sprintf(scrpt, xecc, me);
                } else {
                        scrpt = me;
                }
        }
        j = 0;
#if BUSYBOXON
        varg[j++] = "busybox";
        varg[j++] = "sh";
#else
        varg[j++] = argv[0];            /* My own name at execution */
#endif
        if (ret && *opts)
                varg[j++] = opts;       /* Options on 1st line of code */
        if (*inlo)
                varg[j++] = inlo;       /* Option introducing inline code */
        varg[j++] = scrpt;              /* The script itself */
        if (*lsto)
                varg[j++] = lsto;       /* Option meaning last option */
        i = (ret > 1) ? ret : 0;        /* Args numbering correction */
        while (i < argc)
                varg[j++] = argv[i++];  /* Main run-time arguments */
        varg[j] = 0;                    /* NULL terminated array */
#if DEBUGEXEC
        debugexec(shll, j, varg);
#endif
        execvp(shll, varg);
        return shll;
}

int main(int argc, char ** argv)
{
#if SETUID
   setuid(0);
#endif
#if DEBUGEXEC
        debugexec("main", argc, argv);
#endif
#if HARDENING
        hardening();
#endif
#if !TRACEABLE
        untraceable(argv[0]);
#endif
        argv[1] = xsh(argc, argv);
        fprintf(stderr, "%s%s%s: %s\n", argv[0],
                errno ? ": " : "",
                errno ? strerror(errno) : "",
                argv[1] ? argv[1] : "<null>"
        );
        return 1;
}

1.原理


分析test.sh.x.c源码,SETUID|DEBUGEXEC|TRACEABLE|HARDENING|BUSYBOXON|MMAP2宏控制相关的安全功能

反调试

TRACEABLE宏控制使用开启反调试功能。untraceable函数实现反调试功能核心逻辑,其中/proc/<pid>/as主要存在于Solaris/SunOS系统中表示目标进程的地址空间,freebsd13以后的版本默认不挂载procfs,需要手动挂载(也就是freebsd13以后版本,不存在/proc/%d/mem文件)——其实在freebsd系统下反调试功能是存在bug的,首先因为open返回最小可用文件描述符,mine被赋值为1,因此不会进行ptrace调用,使得自进程trace父进程,达到反调试的目的,其次因为即使调用成功子进程并没有等待父进程结束而自己退出,导致目标进程的调试状态会内核被清除,此时其他调试器可以附加调试。

调试模式

调试模式受DEBUGEXEC宏控制,挑用debugexec函数,打印当前可执行程序及参数,帮助用户理解脚本文件的执行过程

加固

程序开始的时刻调用hardening函数,函数主要完成以下工作:

  1. 设置进程的dumpable属性,该属性决定当进程因接收到某些信号如段错误而触发core dump行为时,是否允许生成核心转储文件。需要注意的是,当dumpable为0时,进程不能被其他进程使用ptrace附加调试,同时其/proc/[pid]目录下的文件权限也会有所限制

  2. 设置进程的ptracer属性,此选项仅在​Yama Linux安全模块(LSM)启用且处于模式1(“受限 ptrace”,可通过/proc/sys/kernel/yama/ptrace_scope查看)时生效, 查看系统启动的安全模块/sys/kernel/security/lsm,???prctl(PR_SET_PTRACER, -1)???

  3. 检查当前进程的名称是否是属于允许的shell或特权提升工具在执行

  4. xsh作为程序的核心入口函数,首先获取程序的执行路径argv[0](在许多Unix系统里,环境变量 _ 通常会保存上一个运行的命令路径),初始化rc4算法的状态转移表stte。使用key函数混淆初始化状态,使用arc4算法对mask加密的同时,将其加入环境变量,在xsh函数主体中解密相关的运行参数(相关运行时的参数,程序,报错信息都加密在data数据结构中),调用execvp重新加载进程,再次检查运行环境获取第一次运行的环境变量,判断当前环境的一致性,最后运行arc4_hardrun函数编译预制的shc_x内容成动态库,设置环境变量LD_PRELOAD,该库劫持__libc_start_main,从21文件描述符中读取密钥,替换站位参数,最后原始参数内容继续执行

其实程序中的seccomp_hardening函数实现对进程沙箱基本的限制功能(1.禁止进程后续获取其他权限 2.设置系统调用的白名单),但是这些作者可能在后续才会加入

其他选项

MMAP2设置进程在安全加固的函数中是否运行mmap2系统调用(映射更大内存页),BUSYBOXON宏判断是否运行进程使用busybox作为shell的执行环境

2.参考文献

  1. https://github.com/neurobin/shc
  2. https://github.com/yanncam/UnSHc
comments powered by Disqus