羽夏殼世界——異或加密的實現

寂靜的羽夏發表於2022-04-11

寫在前面

  此係列是本人一個字一個字碼出來的,包括程式碼實現和效果截圖。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閒錢,可以打賞支援我的創作。如想轉載,請把我的轉載資訊附在文章後面,並宣告我的個人資訊和本人部落格地址即可,但必須事先通知我

你如果是從中間插過來看的,請仔細閱讀 羽夏殼世界——序 ,方便學習本教程。

加密原理

  由於展示最基本最簡單的實現,使用演算法加密就沒用複雜的。如果使用比較複雜的加密,首先你在C++程式碼層面和彙編層面要有配套的程式碼,C++負責加密,彙編負責自我解密,否則你加密完了,結果加密後的PE檔案自己又解密不了,這就很尷尬。
  在所有加密演算法,異或加密是最簡單的,也是最好是實現的。我們來介紹異或加密的原理。
  已知兩個數AB,如果A xor B = C,則C xor B = A,其中xor表示異或運算子。如果不理解,這個是入門程式設計的最基本的知識,請自行補缺,這裡我就不嘮叨了。

異或加密的實現

  下面是我們實現異或加密的相關函式:

//
// 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.
// 
// 注意:你不能將該專案用於任何商業用途,除非你獲得了我的授權!該專案用來
// 教初學者什麼是 PE 結構和 PE 檔案加殼程式是如何工作的。
//

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++程式碼層面,加密程式碼區內容相關的程式碼如下:

//異或加密
auto se = (BYTE*)b;
for (UINT i = 0; i < repeat; i++)
{
    se[i] ^= (BYTE)0x55;
}

  ^表示異或運算子,在彙編層面,以64位為例,實現如下所示:

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,這個就是生成花指令,這裡不多說,後面在介紹。還有一個函式比較注意RelocationSection,這個函式是用來生成做重定位的彙編程式碼的,為什麼要有這個函式呢?
  比如我只有異或加密,我們是在硬編碼的層面進行的加密,PE被載入進入的時候如果基址不和預想的那樣,就會查是否有重定位表,如果有的話就解析並修復。但是,我們的程式碼是加密的,而重定位表沒做修改,它就會錯誤的把被加密的硬編碼進行重定位,這個是不能夠允許的。所以我們需要摧毀重定位表,可以看到CWingProtect::Proctect裡面有一個函式DestoryRelocation,這個作用就是用來銷燬它的,不讓PE載入器幫我們做重定位。
  綜上所述,我們需要自己做重定位,我們需要在彙編層面來實現重定位表的修復,我們來看一下相關程式碼:

//
// 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.
// 
// 注意:你不能將該專案用於任何商業用途,除非你獲得了我的授權!該專案用來
// 教初學者什麼是 PE 結構和 PE 檔案加殼程式是如何工作的。
//

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 為載入到記憶體的虛擬基址地址
        //計數
        a.mov(x86::ecx, x86::dword_ptr(x86::rsi, 4));
        a.sub(x86::ecx, 8);
        a.shr(x86::ecx, 1);    //此時為重定位表的真實專案個數
        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 為載入到記憶體的虛擬基址地址
        //計數
        a.mov(x86::ecx, x86::dword_ptr(x86::esi, 4));
        a.sub(x86::ecx, 8);
        a.shr(x86::ecx, 1);    //此時為重定位表的真實專案個數
        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);
    }

    //將所有的節全部改為可寫
    auto length = peinfo.NumberOfSections;
    for (UINT i = 0; i < length; i++)
    {
        ((PIMAGE_SECTION_HEADER)TranModPEWapper(&peinfo.PSectionHeaders[i]))
            ->Characteristics |= IMAGE_SCN_MEM_WRITE;
    }
}

  對於以上程式碼你可能有一些疑問,我這裡說一下:
  為什麼呼叫a.nop()來生成沒有用的指令,這個是我用來方便除錯我生成的ShellCode用的,否則會生成一大坨彙編到後來自己也不清楚自己在除錯啥的,通過這個nop我就可以清楚的直到我到那裡了,如果出錯的話我也方便進行定位。
  此函式最後生成完ShellCode之後又將所有的節全部改為可寫屬性,這是為什麼呢?因為線性記憶體是有屬性的,如果我沒有將其設定可寫,如果它是隻讀記憶體,如果我對它做重定位修改的話,就會報記憶體訪問錯誤,導致程式崩潰。
  怎麼用匯編來解析重定位表,這裡就不贅述了。

ShellCode 編寫注意事項

  在編寫ShellCode程式碼的時候,請一定保證如下原則,避免一些麻煩,否則會出現出乎意料的錯誤:

  1. 除了 eax / rax 其他暫存器用到的話,一定要注意儲存好,因為其它函式呼叫有各種呼叫約定,一定不要影響它們,否則會出錯。為什麼要對 eax / rax 區別對待,因為通常來說它只用做返回值,呼叫函式返回結果一定會修改它,所以大可不必。
  2. 在使用 ASMJIT 生成彙編的時候,使用類似 MOV 的指令的時候,一定要注意如果要寫入多大的資料一定要在目標運算元體現數來,比如要移動 WORD 大小的話,用 ax 就不要用 eax,否則它正常生成彙編指令不報錯,結果和你想生成的程式碼不一樣。
  3. 一定要注意堆疊平衡,這個是非常重要的東西,在64位尤甚,32位的作業系統也是十分注意堆疊平衡的。

下一篇

  羽夏殼世界——壓縮程式碼的實現

相關文章