網(wǎng)站首頁 編程語言 正文
加密原理
由于展示最基本最簡單的實現(xiàn),使用算法加密就沒用復雜的。如果使用比較復雜的加密,首先你在C++
代碼層面和匯編層面要有配套的代碼,C++
負責加密,匯編負責自我解密,否則你加密完了,結(jié)果加密后的PE
文件自己又解密不了,這就很尷尬。
在所有加密算法,異或加密是最簡單的,也是最好是實現(xiàn)的。我們來介紹異或加密的原理。
已知兩個數(shù)A
和B
,如果A xor B = C
,則C xor B = A
,其中xor
表示異或運算符。如果不理解,這個是入門編程的最基本的知識,請自行補缺,這里我就不嘮叨了。
異或加密的實現(xiàn)
下面是我們實現(xiàn)異或加密的相關(guān)函數(shù):
// GNU AFFERO GENERAL PUBLIC LICENSE //Version 3, 19 November 2007 //Copyright(C) 2007 Free Software Foundation, Inc. //Everyone is permitted to copyand distribute verbatim copies //of this license document, but changing it is not allowed. // Author : WingSummer (寂靜的羽夏) //Warning: You can not use it for any commerical use,except you get // my AUTHORIZED FORM ME!This project is used for tutorial to teach // the beginners what is the PE structure and how the packer of the PE files works. BOOL CWingProtect::XORCodeSection(BOOL NeedReloc, BOOL FakeCode) { using namespace asmjit; if (_lasterror != ParserError::Success) return FALSE; auto filesize = peinfo.FileSize.QuadPart; CodeHolder holder; /// <summary> /// PointerToRawData /// </summary> auto p = peinfo.PCodeSection->PointerToRawData; /// <summary> /// SizeOfRawData /// </summary> auto sizecode = peinfo.PCodeSection->SizeOfRawData; auto repeat = sizecode; BYTE* shellcode; INT3264 ccount; if (is64bit) { Environment env(Arch::kX64); holder.init(env); x86::Assembler a(&holder); Label loop = a.newLabel(); x86::Mem mem; mem.setSegment(x86::gs); mem.setOffset(0x60); //生成加密 shellcode,此處的 rax = ImageBase a.push(x86::rcx); a.push(x86::rdi); //xor 解密 a.mov(x86::rax, mem); a.mov(x86::rax, x86::qword_ptr(x86::rax, 0x10)); a.mov(x86::rdi, x86::rax); a.add(x86::rdi, peinfo.PCodeSection->VirtualAddress); a.mov(x86::rcx, repeat); a.bind(loop); if (FakeCode) FakeProtect(a); a.xor_(x86::byte_ptr(x86::rdi), 0x55); a.inc(x86::rdi); a.dec(x86::rcx); a.test(x86::rcx, x86::rcx); a.jnz(loop); //確保此時 rax 或 eax 存放的是 ImageBase ,否則是未定義行為 if (NeedReloc) RelocationSection(a); a.pop(x86::rdi); a.pop(x86::rcx); a.ret(); shellcode = a.bufferData(); ccount = holder.codeSize(); } else { Environment env(Arch::kX86); holder.init(env); x86::Assembler a(&holder); Label loop = a.newLabel(); x86::Mem mem; mem.setSegment(x86::fs); mem.setOffset(0x30); //生成加密 shellcode a.push(x86::ecx); a.push(x86::edi); a.mov(x86::eax, mem); a.mov(x86::eax, x86::dword_ptr(x86::eax, 0x8)); a.mov(x86::edi, x86::eax); a.add(x86::edi, peinfo.PCodeSection->VirtualAddress); a.mov(x86::ecx, repeat); a.bind(loop); if (FakeCode) FakeProtect(a); a.xor_(x86::byte_ptr(x86::edi), 0x55); a.inc(x86::edi); a.dec(x86::ecx); a.test(x86::ecx, x86::ecx); a.jnz(loop); //確保此時 rax 或 eax 存放的是 ImageBase ,否則是未定義行為 if (NeedReloc) RelocationSection(a); a.pop(x86::edi); a.pop(x86::ecx); a.ret(); shellcode = a.bufferData(); ccount = holder.codeSize(); } //異或加密 auto se = (BYTE*)b; for (UINT i = 0; i < repeat; i++) { se[i] ^= (BYTE)0x55; } //加密完畢,寫 Shellcode encryptInfo.XORDecodeShellCode = (UINT)peinfo.PointerOfWingSeciton; auto ws = GetPointerByOffset(peinfo.WingSecitonBuffer, peinfo.PointerOfWingSeciton); memcpy_s(ws, ccount, shellcode, ccount); peinfo.PointerOfWingSeciton += ccount; if (!NeedReloc) { auto tmp = (PIMAGE_SECTION_HEADER)TranModPEWapper(peinfo.PCodeSection); tmp->Characteristics |= IMAGE_SCN_MEM_WRITE; } return TRUE; }
在C++代碼層面,加密代碼區(qū)內(nèi)容相關(guān)的代碼如下:
//異或加密 auto se = (BYTE*)b; for (UINT i = 0; i < repeat; i++) { se[i] ^= (BYTE)0x55; }
^
表示異或運算符,在匯編層面,以64位為例,實現(xiàn)如下所示:
a.mov(x86::rax, mem); a.mov(x86::rax, x86::qword_ptr(x86::rax, 0x10)); a.mov(x86::rdi, x86::rax); a.add(x86::rdi, peinfo.PCodeSection->VirtualAddress); a.mov(x86::rcx, repeat); a.bind(loop); if (FakeCode) FakeProtect(a); a.xor_(x86::byte_ptr(x86::rdi), 0x55); a.inc(x86::rdi); a.dec(x86::rcx); a.test(x86::rcx, x86::rcx); a.jnz(loop);
可以看出來匯編寫起來比寫C++
代碼麻煩多了,里面有一些代碼可能有一些其他的考慮,我們這里說一下:
首先是FakeProtect
,這個就是生成花指令,這里不多說,后面在介紹。還有一個函數(shù)比較注意RelocationSection
,這個函數(shù)是用來生成做重定位的匯編代碼的,為什么要有這個函數(shù)呢?
比如我只有異或加密,我們是在硬編碼的層面進行的加密,PE
被加載進入的時候如果基址不和預(yù)想的那樣,就會查是否有重定位表,如果有的話就解析并修復。但是,我們的代碼是加密的,而重定位表沒做修改,它就會錯誤的把被加密的硬編碼進行重定位,這個是不能夠允許的。所以我們需要摧毀重定位表,可以看到CWingProtect::Proctect里面有一個函數(shù)DestoryRelocation
,這個作用就是用來銷毀它的,不讓PE
加載器幫我們做重定位。
綜上所述,我們需要自己做重定位,我們需要在匯編層面來實現(xiàn)重定位表的修復,我們來看一下相關(guān)代碼:
// // GNU AFFERO GENERAL PUBLIC LICENSE //Version 3, 19 November 2007 // //Copyright(C) 2007 Free Software Foundation, Inc. //Everyone is permitted to copyand distribute verbatim copies //of this license document, but changing it is not allowed. // Author : WingSummer (寂靜的羽夏) // //Warning: You can not use it for any commerical use,except you get // my AUTHORIZED FORM ME!This project is used for tutorial to teach // the beginners what is the PE structure and how the packer of the PE files works. void CWingProtect::RelocationSection(asmjit::x86::Assembler& a) { using namespace asmjit; Label loop_xor = a.newLabel(); Label loop_reloc = a.newLabel(); Label loop_rt = a.newLabel(); Label endproc = a.newLabel(); auto rdd = peinfo.PDataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; if (is64bit) { a.nop(); a.push(x86::rdi); a.push(x86::rcx); a.push(x86::rsi); //征用 rsi a.mov(x86::rsi, rdd.VirtualAddress); //重定位表基址 a.add(x86::rsi, x86::rax); a.push(x86::rdx); //征用 rdx a.push(x86::r10); a.mov(x86::r10, peinfo.ImageBase); //PE 加載后,該值會被重定位,只能寫死 a.sub(x86::r10, x86::rax); a.jz(endproc); a.bind(loop_rt); a.mov(x86::edi, x86::dword_ptr(x86::rsi)); //偏移基址地址 a.add(x86::rdi, x86::rax); //此時 rdi 為加載到內(nèi)存的虛擬基址地址 //計數(shù) a.mov(x86::ecx, x86::dword_ptr(x86::rsi, 4)); a.sub(x86::ecx, 8); a.shr(x86::ecx, 1); //此時為重定位表的真實項目個數(shù) a.add(x86::rsi, 8); //將指針指向該索引下的第一個重定位項目 a.bind(loop_reloc); a.dec(x86::rcx); a.mov(x86::dx, x86::word_ptr(x86::rsi, x86::rcx, 1)); a.test(x86::dx, 0xF000); a.jz(loop_reloc); //contine; a.and_(x86::edx, 0xFFF); a.add(x86::rdx, x86::rdi); a.sub(x86::qword_ptr(x86::rdx), x86::r10); //修正 a.cmp(x86::rcx, 0); a.ja(loop_reloc); a.sub(x86::rsi, 8); //重新指向表頭 a.mov(x86::edx, x86::dword_ptr(x86::rsi, 4)); a.add(x86::rsi, x86::rdx); //指向下一個 a.mov(x86::edx, x86::dword_ptr(x86::rsi)); a.test(x86::edx, x86::edx); a.jnz(loop_rt); a.bind(endproc); a.pop(x86::r10); a.pop(x86::rdx); a.pop(x86::rsi); //釋放 rsi 自由身 a.pop(x86::rcx); a.pop(x86::rdi); } else { a.push(x86::edi); a.push(x86::ecx); a.push(x86::esi); //征用 rsi a.mov(x86::esi, rdd.VirtualAddress); //重定位表基址 a.add(x86::esi, x86::eax); a.push(x86::edx); //征用 edx a.push((DWORD32)peinfo.ImageBase); //x86寄存器沒那么多,只能自己維護一個局部變量 a.sub(x86::dword_ptr(x86::esp), x86::rax); a.jz(endproc); a.bind(loop_rt); a.mov(x86::edi, x86::dword_ptr(x86::esi)); //偏移基址地址 a.add(x86::edi, x86::eax); //此時 rdi 為加載到內(nèi)存的虛擬基址地址 //計數(shù) a.mov(x86::ecx, x86::dword_ptr(x86::esi, 4)); a.sub(x86::ecx, 8); a.shr(x86::ecx, 1); //此時為重定位表的真實項目個數(shù) a.add(x86::esi, 8); //將指針指向該索引下的第一個重定位項目 a.bind(loop_reloc); a.dec(x86::ecx); a.mov(x86::dx, x86::word_ptr(x86::rsi, x86::ecx, 1)); a.test(x86::dx, 0xF000); a.jz(loop_reloc); //contine; a.and_(x86::edx, 0xFFF); a.add(x86::edx, x86::edi); a.push(x86::eax); //使用局部變量 a.mov(x86::eax, x86::dword_ptr(x86::esp, 4)); //注意被 push 了一個,所以加個偏移 a.sub(x86::dword_ptr(x86::edx), x86::eax); //修正 a.pop(x86::eax); a.cmp(x86::ecx, 0); a.ja(loop_reloc); a.sub(x86::esi, 8); //重新指向表頭 a.mov(x86::edx, x86::dword_ptr(x86::esi, 4)); a.add(x86::esi, x86::rdx); //指向下一個 a.mov(x86::edx, x86::dword_ptr(x86::esi)); a.test(x86::edx, x86::edx); a.jnz(loop_rt); a.bind(endproc); a.add(x86::esp, 4); //釋放局部變量 a.pop(x86::edx); a.pop(x86::esi); //釋放 rsi 自由身 a.pop(x86::ecx); a.pop(x86::edi); } //將所有的節(jié)全部改為可寫 auto length = peinfo.NumberOfSections; for (UINT i = 0; i < length; i++) { ((PIMAGE_SECTION_HEADER)TranModPEWapper(&peinfo.PSectionHeaders[i])) ->Characteristics |= IMAGE_SCN_MEM_WRITE; } }
對于以上代碼你可能有一些疑問,我這里說一下:
為什么調(diào)用a.nop()
來生成沒有用的指令,這個是我用來方便調(diào)試我生成的ShellCode
用的,否則會生成一大坨匯編到后來自己也不清楚自己在調(diào)試啥的,通過這個nop
我就可以清楚的直到我到那里了,如果出錯的話我也方便進行定位。
此函數(shù)最后生成完ShellCode
之后又將所有的節(jié)全部改為可寫屬性,這是為什么呢?因為線性內(nèi)存是有屬性的,如果我沒有將其設(shè)置可寫,如果它是只讀內(nèi)存,如果我對它做重定位修改的話,就會報內(nèi)存訪問錯誤,導致程序崩潰。
怎么用匯編來解析重定位表,這里就不贅述了。
ShellCode 編寫注意事項
在編寫ShellCode
代碼的時候,請一定保證如下原則,避免一些麻煩,否則會出現(xiàn)出乎意料的錯誤:
- 除了 eax / rax 其他寄存器用到的話,一定要注意保存好,因為其它函數(shù)調(diào)用有各種調(diào)用約定,一定不要影響它們,否則會出錯。為什么要對 eax / rax 區(qū)別對待,因為通常來說它只用做返回值,調(diào)用函數(shù)返回結(jié)果一定會修改它,所以大可不必。
- 在使用 ASMJIT 生成匯編的時候,使用類似 MOV 的指令的時候,一定要注意如果要寫入多大的數(shù)據(jù)一定要在目標操作數(shù)體現(xiàn)數(shù)來,比如要移動 WORD 大小的話,用 ax 就不要用 eax,否則它正常生成匯編指令不報錯,結(jié)果和你想生成的代碼不一樣。
- 一定要注意堆棧平衡,這個是非常重要的東西,在64位尤甚,32位的操作系統(tǒng)也是十分注意堆棧平衡的。
原文鏈接:https://www.cnblogs.com/wingsummer/p/16129215.html
相關(guān)推薦
- 2022-07-11 關(guān)于mv3 - Uncaught TypeError: Cannot read properties
- 2022-07-31 oracle定時任務(wù)定時無效的原因分析與解決_oracle
- 2023-07-26 TypeScript中的keyof、typeof、索引訪問類型、條件類型
- 2022-07-11 Python如何獲取多線程返回結(jié)果_python
- 2022-07-17 一起詳細聊聊C#中的Visitor模式_C#教程
- 2022-12-07 React元素與組件的區(qū)別示例詳解_React
- 2024-01-27 Apache POI 及 alibaba EasyExcel使用
- 2022-02-28 Module not found: Error: Can't resolve 'sass-loade
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學習環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支