一:基本
对于shellcode来说,它是独立的,可以附加在各种各样的进程中,所以他的每句代码都是独立,只与shellcode中的代码有联系,与外部代码都是隔绝的。
在生成exe的时候,编译器将函数转为真正实现这个函数功能的入口地址(绝对地址),由于shellcode是附加在其他进程上的,每个进程是不一样的,这个地址可能是其他功能,所以运用绝对地址不符合。所以需要动态调用,先获得函数的地址,在进行执行。
类似于这种:
GetProcAddress(LoadLibraryA(“kernel32.dll”), “CreateFileA”);
但是,LoadLibraryA与GetProcAddress的地址也需获取,在windows操作系统下,每一个进程都会对ntdll、kernel32、kernelbase做一个系统内部的加载,所以只需寻找内部加载的地址,就可以得到LoadLibraryA(“kernel32.dll”)的地址。
1,得到LoadLibraryA(“kernel32.dll”)
FS寄存器指向当前活动线程的TEB结构(线程结构)
偏移 说明
000 指向SEH链指针
004 线程堆栈顶部
008 线程堆栈底部
00C SubSystemTib
010 FiberData
014 ArbitraryUserPointer
018 FS段寄存器在内存中的镜像地址
020 进程PID
024 线程ID
02C 指向线程局部存储指针
030 PEB结构地址(进程结构)
034 上个错误号
偷了张图片:
模块在内存顺序:
__declspec(naked) DWORD getKernel32()
{
__asm
{
mov eax,fs:[30h] //得到PEB
mov eax,[eax+0ch] //PEB_LDR_DATA
mov eax,[eax+14h] //得到inmemoryodermodulelist,第一个模块
mov eax,[eax]
mov eax,[eax] //第三个模块kernel32.dll
mov eax,[eax+10h] //得到Baseaddress,kernel的基址
ret
}
}
2,得到GetProcAddress地址
GetProcAddress这个函数也在kernel32中,
FARPROC _GetProcAddress(HMODULE hModuleBase)
{
PIMAGE_DOS_HEADER lpDosHeader = (PIMAGE_DOS_HEADER)hModuleBase;
PIMAGE_NT_HEADERS32 lpNtHeader = (PIMAGE_NT_HEADERS)((DWORD)hModuleBase + lpDosHeader->e_lfanew);
if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size){
return NULL;
}
if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress) {
return NULL;
}
PIMAGE_EXPORT_DIRECTORY lpExports = (PIMAGE_EXPORT_DIRECTORY)((DWORD)hModuleBase + (DWORD)lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
PDWORD lpdwFunName = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNames);
PWORD lpword = (PWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNameOrdinals);
PDWORD lpdwFunAddr = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfFunctions);
DWORD dwLoop = 0;
FARPROC pRet = NULL;
for (; dwLoop <= lpExports->NumberOfNames - 1; dwLoop++) {
char* pFunName = (char*)(lpdwFunName[dwLoop] + (DWORD)hModuleBase);
if (pFunName[0] == 'G'&&
pFunName[1] == 'e'&&
pFunName[2] == 't'&&
pFunName[3] == 'P'&&
pFunName[4] == 'r'&&
pFunName[5] == 'o'&&
pFunName[6] == 'c'&&
pFunName[7] == 'A'&&
pFunName[8] == 'd'&&
pFunName[9] == 'd'&&
pFunName[10] == 'r'&&
pFunName[11] == 'e'&&
pFunName[12] == 's'&&
pFunName[13] == 's')
{
pRet = (FARPROC)(lpdwFunAddr[lpword[dwLoop]] + (DWORD)hModuleBase);
break;
}
}
return pRet;
}
3,字符串打散
字符串常量是一个固定地址,之前提到,为了避免使用绝对地址,改为使用字符数组,并且最后记得加上截断字符,避免越界。
4,函数生成位置
在单文件中,函数地址顺序与在代码编辑时的函数定义顺序有关。
在多文件中,在这个vcxproj下面。
二,编写shellcode
基本框架:
#include "pch.h"
#include <windows.h>
#include <stdio.h>
FARPROC getProcAddress(HMODULE hModuleBase);
DWORD getKernel32();
int EntryMain()
{
//声明定义GetProcAddress
typedef FARPROC(WINAPI *FN_GetProcAddress)(
_In_ HMODULE hModule,
_In_ LPCSTR lpProcName
);
//获取GetProcAddress真实地址
FN_GetProcAddress fn_GetProcAddress = (FN_GetProcAddress)getProcAddress((HMODULE)getKernel32());
//声明定义CreateFileA
typedef HANDLE(WINAPI *FN_CreateFileA)(
__in LPCSTR lpFileName,
__in DWORD dwDesiredAccess,
__in DWORD dwShareMode,
__in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,
__in DWORD dwCreationDisposition,
__in DWORD dwFlagsAndAttributes,
__in_opt HANDLE hTemplateFile
);
//将来的替换,地址全部动态获取
//FN_CreateFileA fn_CreateFileA = (FN_CreateFileA)GetProcAddress(LoadLibrary("kernel32.dll"), "CreateFileA");
//带引号的字符串打散处理
char xyCreateFile[] = { 'C','r','e','a','t','e','F','i','l','e','A',0 };
//动态获取CreateFile的地址
FN_CreateFileA fn_CreateFileA = (FN_CreateFileA)fn_GetProcAddress((HMODULE)getKernel32(), xyCreateFile);
char xyNewFile[] = { '1','.','t','x','t','\0' };
fn_CreateFileA(xyNewFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
}