天天看點

shc程式的原理--以執行個體分析

有人想加密自己的perl腳本,有人想加密自己的php,有人認為bash程式設計并不是真正的程式設計,因為它們的源代碼都是可見的,不像c程式那樣,一旦經過編譯就再也不可讀了...其實這是一種誤區,其一就是c語言編譯而成的平台相關的elf或者pe檔案并不是完全不可讀,隻是對于應用者不可讀,對于黑客還是可以進行良好反彙編的,其二既然應用者不是專業人士,那麼bash,perl等代碼對于他們也是不可讀的,我老婆就曾經完全看不懂echo abc的含義,其三就是perl也好,php也好,為何要隐藏代碼呢?它們本身就是開源的,為何要隐藏用它們寫成的代碼呢?這也許是受了windows的影響...

     不管怎樣,linux上提供了一個工具,那就是shc,它表面上看來是可以将bash編譯成二進制的形式,讓很多人更放心,可是用心的人仔細看過shc生成的代碼後就不會這麼想了。事實上,如果shc真的能将一個bash腳本轉化為平台相關的比如elf檔案,那說明shc一定要了解bash的文法和關鍵字,而bash腳本中除了使用bash内置的指令外還可以調用任意的别的bash腳本和elf檔案或者perl程式,或者其它的諸如python程式,複雜無比的gcc也不過是了解了c的文法和關鍵字,指望一個shc了解上述的所有是不可能的,比如一個bash腳本中調用一個名字是a的程式,那麼shc是将a連結進來呢還是試圖了解程式a的意義然後用一個等價的c語言函數來代替呢,即使shc完全了解并可以處理了bash,也不能指望它能完全了解并能處理其它的程式或者指令,這完全需要一種人工智能的方式來完成,及其複雜。

     下面我們用一個例子來說明shc真正做了些什麼,在分析代碼之前先說明答案,那就是shc将一個腳本用一段密鑰加密,算法是rc4,然後将加密後的資料和密鑰一起儲存成一些數組,将解密,執行的程式代碼以及上述的加密後的腳本以及密鑰儲存在一個c檔案中,然後編譯這個c檔案成一個可執行的elf檔案(linux平台上),當執行這個elf檔案的時候,它會将加密的腳本數組資料解壓後然後執行之。下面代碼為證:

首先看一個簡單的腳本檔案

#########--simple.sh--#########

#!/bin/bash

echo 1

#########--end--#########

然後下面這個是通過shc -f simple.sh生成的c檔案

#########--simple.sh.x.c--#########

static  long date = 0;

static  char mail[] = "Please contact your provider";

static  int  relax = 0;

typedef char pswd_t[474];

static  char pswd[] = //這裡是密鑰

    "/367/026/340/141/333/034/344/067/103/155/241/324/354/345/056/253"

    ...

    "/125/300/045/273/061/114";

typedef char shll_t[10]; 

static  char shll[] = 

    "/142/255/213/016/240/111/146/224/304/270/321/256/255/314/174/025";

typedef char inlo_t[3];

static  char inlo[] = 

    "/325/233/105/366/212/116/244/207/272/345/242/161/132/177/134/253"

    "/125";

typedef char xecc_t[15];

static  char xecc[] = //這個數組用于混淆代碼,使得反彙編更難

    "/134/317/165/125/034/257/377/004/136/110/115/262/262/061/027/301"

    "/364/157/201/032/052/262/146/240/203";

typedef char lsto_t[1];

static  char lsto[] = 

    "/226/115/117/220/142";

#define TEXT_chk1    "ksjWFsdVl0EsE"

typedef char chk1_t[14];

static  char chk1[] = 

    "/204/245/141/023/147/245/253/366/274/130/145/064/011/134/043/213"

    "/011/226/037/345/232/026/336/045/371/102/333";

typedef char opts_t[1];

static  char opts[] = 

    "/237/314/241/274/355/321/275/002/027/251/044/063/164/302/246/070";

typedef char text_t[20];

static  char text[] = 

    "/150/207/154/160/250/073/136/042/050/230/310/252/236/366/061/372"

    "/300/123/332/054/043/133/223/055/362/262/022";

#define TEXT_chk2    "24JoASCmvuaP"

typedef char chk2_t[13];

static  char chk2[] = 

    "/272/250/101/200/054/030/146/004/003/063/006/172/157/110";

typedef char hide_t[4096];

...

static unsigned char state[256], indx, jndx;

void key(char * str, int len) //設定密鑰,rc4算法是一個流算法而不是諸如des之類的分組算法

{

    unsigned char tmp, * ptr = (unsigned char *)str;

    while (len > 0) {

        do {

            tmp = state[indx];

            jndx += tmp;

            jndx += ptr[(int)indx % len];

            state[indx] = state[jndx];

            state[jndx] = tmp;

        } while (++indx);

        ptr += 256;

        len -= 256;

    }

}

void rc4(char * str, int len) //rc4函數解密了資料,這些資料是在shc中被加密的

    jndx = 0;

        indx++;

        tmp = state[indx];

        jndx += tmp;

        state[indx] = state[jndx];

        state[jndx] = tmp;

        tmp += state[indx];

        *ptr ^= state[tmp];

        ptr++;

        len--;

int chkenv(int argc)

...//這個函數主要用于混淆,使用一個環境變量控制該程式被執行兩次,其實是用exec的方式被執行的。

char * xsh(int argc, char ** argv)  //解密相關資料,最終執行解密的腳本

    char buff[512];

    char * scrpt;

    int ret, i, j;

    char ** varg;

    state_0();

    key(pswd, sizeof(pswd_t));  //設定密鑰,注意,shc每次執行的時候生成的密鑰都是不同的,因為rc4是序列流加密算法,如果密鑰相同,那麼同一段明文将得到同樣的密文,這樣就一破皆破,是以每次密鑰都随機生成。

    rc4(shll, sizeof(shll_t));  //解密結果為指令解釋器:/bin/bash

    rc4(inlo, sizeof(inlo_t));  //解密結果為bash選項:-c,提示腳本在後續的字元串中而不是在檔案中

    rc4(lsto, sizeof(lsto_t));

    rc4(chk1, sizeof(chk1_t));

    if (strcmp(TEXT_chk1, chk1))  //到此為止驗證一下解密是否正确,由于我們事先不知道明文,是以密文解密後的結果也就無從比對進而證明其解密後明文是正确的,由于事先安排一個随機的字元串序列常量,shc中将其按照加密的順序加密并儲存,如果按照相反的順序解密到此後的資料和儲存的字元串相等,就說明解密到此為止是争取的,注意,流算法對加解密順序有着嚴格的要求,決不能亂序。

        return "location has changed!";

    ret = chkenv(argc);

    if (ret < 0)

        return "abnormal behavior!";

    varg = (char **)calloc(argc + 10, sizeof(char *));

    if (ret) {  //這個ret判斷純粹是為了混淆,為了讓該程式再執行一次...

        if (!relax && key_with_file(shll))

            return shll;

        rc4(opts, sizeof(opts_t));

        rc4(text, sizeof(text_t));

        rc4(chk2, sizeof(chk2_t));  //按照流算法,如果前面的text,即腳本本身解密出錯,此處的chk2是正确的可能性也不大,不過個人認為此處使用帶有初始化向量的分組算法更好。

        if (strcmp(TEXT_chk2, chk2))

            return "shell has changed!";

        if (sizeof(text_t) < sizeof(hide_t)) {

            scrpt = malloc(sizeof(hide_t));

            memset(scrpt, (int) ' ', sizeof(hide_t));

            memcpy(&scrpt[sizeof(hide_t) - sizeof(text_t)], text, sizeof(text_t));

        } else {

            scrpt = text;    /* Script text */

        }

    ...  //省略處理指令行參數的混淆過程

    j = 0;

    varg[j++] = argv[0];        

    if (ret && *opts)

        varg[j++] = opts;    

    if (*inlo)

        varg[j++] = inlo;    

    varg[j++] = scrpt;        

    if (*lsto)

        varg[j++] = lsto;    

    i = (ret > 1) ? ret : 0;    

    while (i < argc)

        varg[j++] = argv[i++];    

    varg[j] = 0;            

    execvp(shll, varg); //執行解密後腳本

    return shll;

int main(int argc, char ** argv)

    xsh(argc, argv);

最終證明,shc并沒有将bash腳本編譯成二進制,而僅僅是加密了它,儲存了加密後的它,然後在執行的時候解密之,執行之。

 本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1271780

繼續閱讀