文章目录
-
- [WIndows ShellCode开发 第四章 动态API调用](#WIndows ShellCode开发 第四章 动态API调用)
WIndows ShellCode开发 第四章 动态API调用
不检验直接跳到这一章,最好是将第一章命令仔细看完然后再来这一章,因为已经写过一遍的缘故,其中代码可以只会将重要部分进行注释,并且逻辑讲解也会少很多
消息框汇编程序
asm
BITS 64
SECTION .text
global main
main:
sub rsp,0x28
and rsp,0xFFFFFFFFFFFFFFF0
xor rcx,rcx
mov rax,[gs:rcx+0x60] ; PEB
mov rax,[rax+0x18] ; PEB->LDR
mov rsi,[rax+0x10] ; PEB->LDR->InLoadOrderModuleList
mov rsi,[rsi]
mov rsi,[rsi]
mov rbx,[rsi+0x30] ; kernel32.dll base
mov r8,rbx ; kernel32.dll base --> r8
mov eax,[rbx+0x3C] ; 获取 PE Header 的偏移 (e_lfanew)
add rax,r8 ; RAX = PE Header 的实际地址
mov edx,[rax+0x88] ; 获取导出表 (Export Directory) 的 RVA
add rdx,r8 ; RDX = 导出表的实际内存地址 (Export Directory VA)
mov r10d,[rdx+0x14] ; 函数总数
xor r11,r11
mov r11d,[rdx+0x20] ; AddressOfNames RVA
add r11,r8
mov rcx,r10 ; 计量器的总数
mov rax,0x9090737365726464 ; 'ddress'
shl rax,0x10
shr rax,0x10 ; 前面两个0x90是占位符,避免ShellCode中出现空字节(0x00)
push rax
mov rax,0x41636F7250746547 ; GetProcA
push rax
mov rax,rsp
kernel32findfunction:
jecxz FunctionNameNotFound ; 计量总数到零跳转到FunctionNameNotFound
xor ebx,ebx
mov ebx,[r11+rcx*4]
add rbx,r8
dec rcx
mov r9,qword[rax] ; qword[rax] 表示读取8字节内容,为"GetProcA"
cmp [rbx],r9 ; [rbx]读取与r9相对应的字节数进行匹配
jnz kernel32findfunction ; 第一次粗滤匹配
mov r9d,dword[rax+8] ; 读取rax+8地址(GetProcA后面那几个字节`ddress`)的四字节数据,读取四个字节并进行比较是因为核心字节已经读取到了可以拿来进行匹配,并避免了读取到零字符。
cmp [rbx+8],r9d ; r9d 是32位的,所以[rbx+8]自动读取4字节数据与r9d内进行比较
jz FunctionNameFound
jnz kernel32findfunction
FunctionNameNotFound:
int3
FunctionNameFound:
push rcx
pop r15
inc r15
xor r11,r11
mov r11d,[rdx+0x1c] ; AddressOfFunctions RVA
add r11,r8
mov eax,[r11+r15*4]
add rax,r8
push rax
pop r15
mov r12,r15 ; 将GetProcAddress地址存一下后续需要用到r15
mov rdi,r8
mov rcx,r8 ; 给 GetProcAddress 准备第一个参数
mov rax,0x41797261 ; 构造LoadLibraryA字符串,后4字节(aryA)先压栈
push rax
mov rax,0x7262694C64616F4C ; LoadLibr
push rax
mov rdx,rsp ; 给 GetProcAddress 准备第二个参数
sub rsp,0x30
call r15
add rsp,0x30
mov r15,rax ; GetProcAddress 函数执行完毕后,会将返回值(即目标函数的地址)写入 rax 寄存器,因此此时的RAX的值成为 LoadLibraryA 函数的地址,然后存入r15
mov r14,r12 ; 将 GetProcAddress地址 存到r14
mov rcx,rdi ; 将 kernel32.dll 基址作为第一参数,(基地址=DLL句柄)
mov rax,0x90737365 ; 0x90作为占用符,避免空字节
shl eax,0x8
shr eax,0x8
push rax
mov rax,0x636F725074697845 ; ExitProc
push rax
mov rdx,rsp ; 第二个参数存入`rdx`
sub rsp,0x30
call r14
add rsp,0x30
mov r14,rax ; 将RAX中ExitProcess函数地址传给R14
mov rax, 0x90906C6C ; 构造 "ll" 字符串,前两个字节直接用nop填充
shl eax,0x10
shr eax,0x10
push rax
mov rax, 0x642E323372657375 ; 构造 "user32.d" 字符串
push rax
mov rcx,rsp ; 将构造好的字符串传给rcx作为第一参数
sub rsp,0x30
call r15 ; 调用LoadLibraryA 加载 user32.dll
mov rdi,rax ; 将user32.dll基地址保存到rdi
mov rcx,rdi
mov rax,0x9041786F ; "oxA"字符串
shl eax,0x8
shr eax,0x8
push rax
mov rax,0x426567617373654D ; 'MessageB'字符串
push rax
mov rdx,rsp ; 将MessageBoxA作为第二参数
sub rsp,0x30
call r12 ; 调用GetProcAddress
mov r15,rax ; 将 MessageBoxA 函数地址传入r15
xor rcx,rcx
mov rax,0x90909073
shl eax,24
shr eax,24
push rax
mov rax,0x6B6E616C62656E6F
push rax
mov rdx,rsp ; 第二参数,lpText
mov r8,rsp ; 第三参数,lpCaption
xor r9d,r9d
sub rsp,0x30
call r15
add rsp, 0x30
xor ecx,ecx
call r14
直接编译链接
nasm -f win64 mesg.asm -o mesg.obj
gcc -m64 mesg.obj -o mesg.exe -lkernel32 -nostartfiles
双击mesg.exe,成功弹窗
接下来将这些编译为ShellCode在加载器执行
nasm.exe -f win64 mesg.asm -o mesg.o
$shellcode = ""; (objdump -D mesg.o | Select-String "^ ").Line | ForEach-Object { $_.Split("`t")[1] -split " " | Where-Object { $_ -match "^[0-9a-f]{2}$" } | ForEach-Object { $shellcode += "\x" + $_ } }; $shellcode
简单加载器C语言
#include <windows.h>
#include <stdio.h>
#include <signal.h>
unsigned char shellcode[] =
"\x48\x83\xec\x28\x48\x83\xe4\xf0\x48\x31\xc9\x65\x48\x8b\x41\x60\x48\x8b\x40\x18\x48\x8b\x70\x10\x48\x8b\x36\x48\x8b\x36\x48\x8b\x5e\x30\x49\x89\xd8\x8b\x43\x3c\x4c\x01\xc0\x8b\x90\x88\x00\x00\x00\x4c\x01\xc2\x44\x8b\x52\x14\x4d\x31\xdb\x44\x8b\x5a\x20\x4d\x01\xc3\x4c\x89\xd1\x48\xb8\x64\x64\x72\x65\x73\x73\x90\x90\x48\xc1\xe0\x10\x48\xc1\xe8\x10\x50\x48\xb8\x47\x65\x74\x50\x72\x6f\x63\x41\x50\x48\x89\xe0\x67\xe3\x20\x31\xdb\x41\x8b\x1c\x8b\x4c\x01\xc3\x48\xff\xc9\x4c\x8b\x08\x4c\x39\x0b\x75\xe9\x44\x8b\x48\x08\x44\x39\x4b\x08\x74\x03\x75\xdd\xcc\x51\x41\x5f\x49\xff\xc7\x4d\x31\xdb\x44\x8b\x5a\x1c\x4d\x01\xc3\x43\x8b\x04\xbb\x4c\x01\xc0\x50\x41\x5f\x4d\x89\xfc\x4c\x89\xc7\x4c\x89\xc1\xb8\x61\x72\x79\x41\x50\x48\xb8\x4c\x6f\x61\x64\x4c\x69\x62\x72\x50\x48\x89\xe2\x48\x83\xec\x30\x41\xff\xd7\x48\x83\xc4\x30\x49\x89\xc7\x4d\x89\xe6\x48\x89\xf9\xb8\x65\x73\x73\x90\xc1\xe0\x08\xc1\xe8\x08\x50\x48\xb8\x45\x78\x69\x74\x50\x72\x6f\x63\x50\x48\x89\xe2\x48\x83\xec\x30\x41\xff\xd6\x48\x83\xc4\x30\x49\x89\xc6\xb8\x6c\x6c\x90\x90\xc1\xe0\x10\xc1\xe8\x10\x50\x48\xb8\x75\x73\x65\x72\x33\x32\x2e\x64\x50\x48\x89\xe1\x48\x83\xec\x30\x41\xff\xd7\x48\x89\xc7\x48\x89\xf9\xb8\x6f\x78\x41\x90\xc1\xe0\x08\xc1\xe8\x08\x50\x48\xb8\x4d\x65\x73\x73\x61\x67\x65\x42\x50\x48\x89\xe2\x48\x83\xec\x30\x41\xff\xd4\x49\x89\xc7\x48\x31\xc9\xb8\x73\x90\x90\x90\xc1\xe0\x18\xc1\xe8\x18\x50\x48\xb8\x6f\x6e\x65\x62\x6c\x61\x6e\x6b\x50\x48\x89\xe2\x49\x89\xe0\x45\x31\xc9\x48\x83\xec\x30\x41\xff\xd7\x48\x83\xc4\x30\x31\xc9\x41\xff\xd6";
// 异常处理函数
void handler(int sig) {
printf("Exception occurred! (signal %d)\n", sig);
exit(1);
}
int main() {
printf("Loading Shellcode...\n");
printf("Shellcode size: %d bytes\n", sizeof(shellcode));
// 注册异常处理
signal(SIGSEGV, handler);
signal(SIGILL, handler);
// 申请可执行内存
void* exec = VirtualAlloc(0, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (exec == NULL) {
printf("VirtualAlloc failed: %d\n", GetLastError());
return 1;
}
memcpy(exec, shellcode, sizeof(shellcode));
printf("Shellcode at: 0x%p\n", exec);
printf("Press ENTER to execute...");
getchar();
printf("Executing...\n");
// 执行shellcode
((void(*)())exec)();
printf("Shellcode returned (should not happen)\n");
VirtualFree(exec, 0, MEM_RELEASE);
return 0;
}
gcc -o loader.exe loader.c -m64
