Shellcode的编写
做病毒和做保护的人,对shellcode一定不陌生。shellcode不依赖环境,放到任何地方都可以执行的机器码。
shellcode编写注意事项:
1.不能有全局变量:因为shellcode不依赖环境
2.不能使用常量字符串:
首先先定义两个相同含义的字符串
在编写shellcode字符串的过程中,不能使用第一种,接下来看一下他的反汇编代码:
双引号字符串编译器会优先存放在常量区中,如果你要使用,现mov一遍,当你注入别的程序时,别的程序不一定有这样一个常量区;
而第二种看他的反汇编代码可以看到,第二种直接放到堆栈当中,不依赖外部环境。(所以一般看到这种东西,肯定不是正常人写程序)
3.不能使用系统调用:
PE文件有导出表和导入表的概念。
当你需要使用MessageBoxA这个函数时,编译器首先生成User32.dll -> MessageBoxA,之后会 call DWORD PTR[MessageBoxA的地址]
你将shellcode放到别的程序中,别的程序导入表可能不存在这个函数,所以你就无法进行调用。
LoadLibrary函数返回装载 DLL 库模块的实例句柄
GetProcAddress函数返回值是DLL中的输出函数地址
LoadLibrary和GetProcAddress都属于kernel32中的函数。若目标程序导入表还是没有这些函数,那这两个函数你也是无法使用的。
但是我们可以找到输出表而去找函数的地址。
nt!_TEB(部分)
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : Ptr32 Void
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : Ptr32 Void
+0x02c ThreadLocalStoragePointer : Ptr32 Void
+0x030 ProcessEnvironmentBlock : Ptr32 _PEB
+0x034 LastErrorValue : Uint4B
+0x038 CountOfOwnedCriticalSections : Uint4B
+0x03c CsrClientThread : Ptr32 Void
+0x040 Win32ThreadInfo : Ptr32 Void
nt!_PEB(部分)
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x003 SpareBool : UChar
+0x004 Mutant : Ptr32 Void
+0x008 ImageBaseAddress : Ptr32 Void
+0x00c Ldr : Ptr32 _PEB_LDR_DATA
+0x010 ProcessParameters : Ptr32 _RTL_USER_PROCESS_PARAMETERS
+0x014 SubSystemData : Ptr32 Void
+0x018 ProcessHeap : Ptr32 Void
+0x01c FastPebLock : Ptr32 _RTL_CRITICAL_SECTION
+0x020 FastPebLockRoutine : Ptr32 Void
+0x024 FastPebUnlockRoutine : Ptr32 Void
+0x028 EnvironmentUpdateCount : Uint4B
+0x02c KernelCallbackTable : Ptr32 Void
FS:[0x00] 对应的是TEB
TEB结构体中 +0x30是PEB
所以FS:[0x30]对应的是PEB
Ldr : Ptr32 _PEB_LDR_DATA 这个东西是进程加载的模块链表
nt!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY 加载顺序
+0x008 InMemoryOrderLinks : _LIST_ENTRY 内存顺序
+0x010 InInitializationOrderLinks : _LIST_ENTRY 初始化顺序
+0x018 DllBase : Ptr32 Void //模块基地址
+0x01c EntryPoint : Ptr32 Void //模块入口
+0x020 SizeOfImage : Uint4B //模块影像大小
+0x024 FullDllName : _UNICODE_STRING //包含路径的模块名
+0x02c BaseDllName : _UNICODE_STRING
+0x034 Flags : Uint4B
+0x038 LoadCount : Uint2B //该模块的引用计数
+0x03a TlsIndex : Uint2B
+0x03c HashLinks : _LIST_ENTRY
+0x03c SectionPointer : Ptr32 Void
+0x040 CheckSum : Uint4B
+0x044 TimeDateStamp : Uint4B
+0x044 LoadedImports : Ptr32 Void
+0x048 EntryPointActivationContext : Ptr32 Void
+0x04c PatchInformation : Ptr32 Void
注意: +0x024 FullDllName : _UNICODE_STRING 是Unicode编码:
找到PEB -> Ldr:
_asm
{
mov eax,fs:[0x30] //PEB
mov eax,[eax+0x0c] //Ldr
add eax,0x0c
mov pBeg,eax
mov eax,[eax]
mov pPLD,eax
}
遍历链表找到kernel32
while (pPLD != pBeg)
{
pLast = (WORD*)pPLD->BaseDLLName.Buffer;
pFirst = (WORD*)kernel32;
while (*pFirst && *pFirst == *pLast)
{
pFirst++, pLast++;
}
if (*pLast == *pFirst)
{
dwKernelBase = (DWORD)pPLD->DLLBase;
break;
}
}
再遍历kernel32的导出表找到GetProcAddress(LoadLibrary同理)
for (;dwCnt<pIED->NumberOfNames;dwCnt++)
{
pFinded = (char*)((DWORD)dwKernelBase + pAddOfofNames_Raw[dwCnt]);
while (*pFinded && *pFinded == *pSrc)//对比GetProcAddress字符串
{
pFinded++;
pSrc++;
}
if (*pFinded == *pSrc)//对比成功
{
pGetProcAddress = (PGETPROCADDRESS)((DWORD)dwKernelBase + pAddOffun_Raw[pAddOfOrd_Raw[dwCnt]]);
break;
}
pSrc = szGetProcAddr;
有了GetProcAddress函数,你就可以操控任何API函数了
写出shellcode中记得转换成硬编码,记得一定要注意文件中 IMAGE_SECTION_HEADER 中的DWORD Characteristics。