有人想加密自己的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中被加密的
{
unsigned char tmp, * ptr = (unsigned char *)str;
jndx = 0;
while (len > 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指令碼編譯成二進位,而僅僅是加密了它,儲存了加密後的它,然後在執行的時候解密之,執行之。