附 1:
测试碰撞率带接口源码
在之前的Shellcode代码中,
对于必要的一些字符串常量采用的方法是:
将字符串以16进制形式保存在代码中。
而大多数常用的调试工具能够对其进行转译,明眼人一看,诶,这有猫腻啊。
很容易导致我们的“计划”流产。
那顺水推舟的,就接着分享一下鄙人的字符串加密的算法以及需要留意之处。
在Shellcode内加密字符串除了增加保密性以外,很重要的一点是能够缩小体积,
而体积(即字节数)对于Shellcode是至关生死的一项。
在设计加密算法时需要注意碰撞率,即在统一语境下不同源数据生成的密码的雷同率。
为测试本算法碰撞率,分别将Kernal32、user32、ntdll中所有导出函数名加密,
下面是测试结果:
综合考虑体积和碰撞率后,这里尝试写一个简单的字符串加密使用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;
}