ShellCode原理以及编写
0x0 ShellCode编写注意事原理
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的字符,甚⾄只能使⽤字母数
0x1 ShellCode编写原理
1.Windows ShellCode原理
1)获取kernel32.dll 基地址;
2)定位 GetProcAddress函数的地址;
3)使⽤GetProcAddress确定 LoadLibrary函数的地址;
4)然后使⽤ LoadLibrary加载DLL⽂件(例如user32.dll);
5)使⽤ GetProcAddress查某个函数的地址(例如MessageBox);6)指定函数参数;
7)调⽤函数。
0x2 ShellCode编写过程
1.PEB与ASLR机制
PEB是⼀个位于所有进程内存中固定位置的结构体。此结构体包含关于进程的有⽤信息,如可执⾏⽂件加载到内存的位置,模块列表(DLL),指⽰进程是否被调试的标志,还有许多其他的信息。
shell代码它可能随着新的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.Kernle32.dll地址获取
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链表Fin k移动到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地址
3.Kernel32.dll导出表
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
4.GetProcAddress函数名称与序号获取
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
5. GetProcAddress函数地址获取
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 地址
6.获得LoadLibrary函数地址
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
7.加载user32.dll动态库
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
8.获得MessageBoxA函数地址
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调⽤
9.使⽤MessageBoxA弹出LiPengYu
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
10.结束ShellCode
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
11.完整ASM代码
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
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论