Shellcode和Payload入门102-代码中的加密-一如既往地源码和超详细注释

附 1:
测试碰撞率带接口源码

在之前的Shellcode代码中,
对于必要的一些字符串常量采用的方法是:
将字符串以16进制形式保存在代码中。

而大多数常用的调试工具能够对其进行转译,明眼人一看,诶,这有猫腻啊。
很容易导致我们的“计划”流产。
那顺水推舟的,就接着分享一下鄙人的字符串加密的算法以及需要留意之处。

在Shellcode内加密字符串除了增加保密性以外,很重要的一点是能够缩小体积,
而体积(即字节数)对于Shellcode是至关生死的一项。

在设计加密算法时需要注意碰撞率,即在统一语境下不同源数据生成的密码的雷同率。

为测试本算法碰撞率,分别将Kernal32、user32、ntdll中所有导出函数名加密,
下面是测试结果:
Shellcode和Payload入门102-代码中的加密-一如既往地源码和超详细注释_第1张图片

综合考虑体积和碰撞率后,这里尝试写一个简单的字符串加密使用2字节长的密码,
翠花,上代码:

C源代码

WORD Encode(CHAR* pFuncName)
{
    WORD Code = 0;
    BOOL isOdd = FALSE;// 是否为奇数下标
    WORD times = 1;

    // 循环遍历Kernal32函数名并加密
    while (*pFuncName)
    {
        /*
        思路:
        *********************************************
        将字符串数组奇数下标取出来的字节放在高字节,(<<8)
        将偶数下标取出来的字节放在低字节,
        这是一层加密,

        由于结果有大于百分之一的碰撞,
        逐增加一层加密,
        每次乘以一个从0每次递增1的数,

        后检查带0特例,
        将此些特例进行相应的位移。
        *********************************************
        */

        // 奇数下标pFuncName[奇数]
        if (isOdd)
        {
            Code += (*pFuncName << 8) * times;
            isOdd = FALSE;// 设置下次进入偶数下标
        }
        // 偶数下标pFuncName[偶数]
        else
        {
            Code += *pFuncName * times;
            isOdd = TRUE;// 设置下次进入奇数下标
        }
        ++pFuncName;// 字符串后移一个字节
        ++times;
    }
    // 对于特殊情况的处理
    if (Code >> 8 == 0)
    {
        Code <<= 3;// 左边有0 则填充左边
    }
    else if (Code << 8 == 0)
    {
        Code >>= 3;// 右边有0 则填充右边
    }

    return Code;
}

汇编源代码

WORD Encode_asm(CHAR* pFuncName)
{
WORD code = 0;
_asm{
func_encode:        
    /*
    EAX 32位~24位恒为0可作为比较使用
    EAX 24位~16位在判断当前加密次数是奇数次还是偶数此时作为bool使用
        起始为0,每次加密完一个字节后设置
    AH用于奇数次时保存字节和运算字节使用
    AL用于偶数次时保存字节和运算字节使用

    BX保存每次AX加密结果和最终的加密结果

    CL用于保存字符串按字节加密的次数

    注:密码中尽量不要出现字节0
    */
    XOR EAX, EAX;               // /
    XOR EBX, EBX;               //|
    XOR ECX, ECX;               //| 
    INC ECX;                    // \->Init
    MOV ESI, pFuncName;         // ESI == pFuncName
    DEC ESI;                    // Init CL  == 1 not 0
loop_whiletrue:
    MOV AL, FuncName_byte;      // AL == BYTE pFuncName
    CMP AL, 0;                  // while(*pFuncName)   <----------------------v
    JZ tag_checkbhforzero;      // if ends see if contains 0 in byte --------------v      
    PUSH EAX;                   // PU_1                                       |    | 
    SHR EAX, 16;                // /Pre EAX:0x00 0X XXXX Pos EAX:0x0000 00 0X |    |
    CMP AH, AL;                 // \CMP 00, 0X                                |    |
    POP EAX;                    // PO_1                                       |    |
    JNZ tag_isOdd;              // 0X != 0 is Odd ---------------v            |    |
tag_isEven:                     // /0X == 0 is even number       |            |    |
    IMUL AX, CL;                //|*pFuncName * times            |            |    |
    ADD BX, AX;                 //|code += *pFuncName * times    |            |    |
    OR EAX, 0X10000;            //|0X == 1 next isOdd            |            |    |
    INC ECX;                    //|++times                       |            |    |
    JMP loop_whiletrue;         // \goto while -------------------------------^    | 
tag_isOdd:                      // /is odd number  <-------------^            |    |
    SHL AX, 8;                  //|(*pName<<8)                                |    |
    IMUL AX, CL;                //|(*pName<<8) * times                        |    |
    ADD BX, AX;                 //|code += (*pName<<8) * times                |    |
    XOR EAX, EAX;               //|EAX == 0 == 0X                             |    |
    INC ECX;                    //|++times                                    |    |
    JMP loop_whiletrue;         // \goto while -------------------------------^    |
tag_checkbhforzero:             // /Check for exceptions    <----------------------^
    CMP BH, 0X0;                //|fir BH
    JNZ tag_checkblforzero;     //|if noraml go check BL -----v
    SHL BX, 3;                  //|if not fill left 0         | 
tag_checkblforzero:             //|         <-----------------^
    CMP BL, 0;                  //|sec BL
    JNZ tag_ret;                //|if noraml goto ret -----v
    SHR BX, 3;                  // \if not fill right 0    |
tag_ret:                        // /      <----------------^
    MOV code, BX;               // \Save retVal
tag_elfin:                      //

}                               //
return code;

}

注:
可以看到其实C代码只有几行,汇编却有大量篇幅。
汇编没有经过精简,只是完成了源码的基本逻辑,
而且有意的在尝试更环保的利用32位的寄存器,
将EAX拆成了4个部分使用,
所以将更多的代码用在处理EAX家事上。

我在精简汇编代码的路上,匍匐前进。
——本文作者

附 1:
测试碰撞率带接口源码
// ShellCode_WSA.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include 

WORD FuncNameCodes[0x1000];// 存放整个模块函数名加密结果
WORD Code = 0;             // 存放当前字符串加密结果

WORD Encode(CHAR* pFuncName)
{
    // TODO : 加密代码



    return Code;
}

float TestCrashs(DWORD DllBase, CHAR* ModuleName = "Kernal32.dll")
{
// 找到导出表及其数据
#pragma region Get_ExportTable
CHAR* ModuleBuf = (CHAR*)DllBase;
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)ModuleBuf;
PIMAGE_NT_HEADERS pNT = PIMAGE_NT_HEADERS(pDos->e_lfanew + ModuleBuf);
PIMAGE_OPTIONAL_HEADER pOpt = &pNT->OptionalHeader;
PIMAGE_DATA_DIRECTORY pExportDir = pOpt->DataDirectory + 0;
PIMAGE_EXPORT_DIRECTORY pExport = PIMAGE_EXPORT_DIRECTORY(pExportDir->VirtualAddress + ModuleBuf);
PDWORD pEAT = PDWORD(pExport->AddressOfFunctions + ModuleBuf);
PDWORD pENT = PDWORD(pExport->AddressOfNames + ModuleBuf);
PWORD pEOT = PWORD(pExport->AddressOfNameOrdinals + ModuleBuf);
DWORD NumONames = pExport->NumberOfNames;// 导出表名称表中名称个数
#pragma endregion Get_ExportTable

ZeroMemory(FuncNameCodes, 0x1000);

for (INT i = 0; i < NumONames; ++i)
{
    CHAR* pName = pENT[i] + ModuleBuf;// 取出导出函数名称


#pragma region Encode

    FuncNameCodes[i] = Encode(pName);

#pragma endregion Encode

}

// 
INT TotalNumOfCrash = 0;// 碰撞次数总和
INT nNumOfCrash = 0;    // 碰撞次数
for (INT i = 0; i < NumONames - 1; ++i)
{
    nNumOfCrash = 0;
    for (INT j = i + 1; j < NumONames - i - 1; ++j)
    {
        // 碰撞成功
        if (FuncNameCodes[i] == FuncNameCodes[j])
        {
            ++nNumOfCrash;// 碰撞次数+1
            CHAR* pName1 = pENT[i] + ModuleBuf;// 当前加密函数名
            CHAR* pName2 = pENT[j] + ModuleBuf;// 匹配到的重名函数
            printf("No.[%04d] %s  and   No.[%04d] %s \n", i, pName1, j, pName2);
        }
    }
    if (nNumOfCrash != 0)
    {
        //printf("Number %d has crashed %d times\n", i, nNumOfCrash);
        TotalNumOfCrash += nNumOfCrash;
    }
    // 是否有字节为0的情况
    if (FuncNameCodes[i] >> 8 == 0 || FuncNameCodes[i] << 8 == 0)
    {
        CHAR* pName1 = pENT[i] + ModuleBuf;
        printf("!!!No.[%04d] %s  has 00 \n", i, pName1);
    }
}

// 总共碰撞率
float fCrashRate = (float)TotalNumOfCrash / (float)NumONames;
// 总共碰撞次数
printf("************************************************************************************\n\
In %s crashed total %d times over %d names       crash rate  %0.1f%%\n************************************************************************************\n"
, ModuleName, TotalNumOfCrash, NumONames, (float)TotalNumOfCrash / (float)NumONames * 100);
    return fCrashRate;
}

int _tmain(int argc, _TCHAR* argv[])
{
    // LoadLibrary("KERNAL32.DLL");
    #pragma region LoadLibrary_KERNAL32.DLL
    DWORD KernalBaseAddr = 0;
    WCHAR* wzKERNAL32 = L"KERNAL32.DLL";
_asm
{


tag_shellcode:
    MOV EAX, FS : [0x30];                       // EAX == _PEB
    MOV EAX, [EAX + 0xC];                       // EAX == Ldr _PEB_LDR_DATA
    MOV EAX, [EAX + 0xC];                       // EAX == _List_ENTRY == _LDR_DATA_TABLE_ENTRY
    JMP tag_checkname;
tag_nextModule:
    MOV EAX, [EAX];                             // _LIST_ENTRY == _LIST_ENTRY->(+0x000)Flink == Previous _LDR_DATA_TABLE_ENTRY Addr
tag_checkname:
    MOV EBX, DWORD PTR DS : [EAX + 0x2C + 0x4]; // _UNICODE_STRING->BUFFER
    PUSH EAX;                                   // Save List Addr
    MOV EAX, DWORD PTR DS : [EAX + 0x2C];       // _UNICODE_STRING->Length(word)
    AND EAX, 0X0000FFFF;                        // Save Loword :    Length(word)
    SHR EAX, 2;                                 // Length*2 == bytes
    MOV ECX, EAX;                               // rep cmps times
    MOV ESI, EBX;                               //
    POP EAX;                                    // EAX == _List_ENTRY
    MOV EDI, wzKERNAL32;                        //********************************* Module Name in UNICODE 
    REP CMPS;                                   //
    JNZ tag_nextModule;                         // 
    MOV EAX, DWORD PTR DS : [EAX + 0x18];       // _LDR_DATA_TABLE_ENTRY->DllBase
    MOV KernalBaseAddr, EAX;                    // [EBP-0x4]:PVOID KernalBaseAddr
}
#pragma endregion LoadLibrary_KERNAL32.DLL

DWORD ntdllBaseAddr = (DWORD)LoadLibraryA("ntdll.dll");
DWORD user32BaseAddr = (DWORD)LoadLibraryA("user32.dll");

float fTotalRate = TestCrashs(KernalBaseAddr);
fTotalRate += TestCrashs(ntdllBaseAddr, "ntdll.dll");
fTotalRate += TestCrashs(user32BaseAddr, "user32.dll");

printf("\n>>>>Result : Total crash rate is <%0.1f%%>\n", fTotalRate / 3 * 100);

system("pause");
return 0;

}

你可能感兴趣的:(编程小心得,ShellCode)