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入门教程 05

    # Windows x64 ShellCode入门教程 05

    在这篇文章中,我们要做如下事情:

    顺便说一句,我们的代码不会包含任何NULL值。

    • 定位Kernel32模块并收集PE导出表信息
    • 动态定位GetProcAddress函数
    • 使用GetProcAddress的句柄来定位LoadLibraryA函数的地址
    • 使用GetProcAddress的句柄来定位ExitProcess函数
    • 使用LoadLibraryA函数的句柄加载user32.dll
    • 使用user32.dll的句柄,通过GetProcAddress在user32.dll中定位MessageBoxA的地址
    • 弹出messagebox消息框
    • 调用ExitProcess以正常退出程序

    这些内容是我们在前面的系列文章中承诺给读者的。

    在深入探讨之前,我想指出在重新学习x64汇编时发现的一点。也许只是我的个人感受,但在查看Windows 11的PE导出表时,名称索引的地址似乎与函数地址是对应的。这样一来,我们就不再需要借助名称索引的地址来获取序号了。我可能搞错了,但我在两台不同的机器上的Windows 11系统上进行了测试,结果都是一样的。我完全跳过了序号查找这一步,直接将从名称查找地址中获取的索引代入函数查找地址,并没有出现任何问题。这值得思考一下。也许这一点应该由比我更了解Windows内部机制的人来进一步详细阐述。先把这个小想法放在一边,我们继续往下说。

    今天的代码将是本系列中迄今为止最具挑战性的,所以先提前提醒一下。内容很多,但我相信你能做好。我会借鉴上面的要点,将其分成几个部分来讲解。

    # 序言 - 定位Kernel32并收集PE导出表信息

    ;nasm -fwin64 [x64findkernel32.asm]
    ;ld -m i386pep -o x64findkernel32.exe x64findkernel32.obj
    BITS 64
    SECTION .text
    global main
    main:
    sub rsp, 0x28
    and rsp, 0xFFFFFFFFFFFFFFF0
    xor rcx, rcx                      ; RCX = 0
    mov rax, [gs:rcx + 0x60]          ; RAX = PEB
    mov rax, [rax + 0x18]             ; RAX = PEB->Ldr
    mov rsi,[rax+0x10]                ; PEB.Ldr->InLoadOrderModuleList
    mov rsi, [rsi]
    mov rsi,[rsi]
    mov rbx, [rsi+0x30]               ; kernel32.dll base address
    mov r8, rbx                       ; mov kernel32.dll base addr into r8
    xor rcx, rcx                      ; Avoid null bytes from mov edx,[rbx+0x88] by using rcx register to add
    add cx, 0x88ff
    shr rcx, 0x8                      ; RCX = 0x88ff --> 0x88
    mov edx, [rbx+rcx]                ; EDX = [&NewEXEHeader + Offset RVA ExportTable] = RVA ExportTable
    add rdx, r8                       ; RDX = kernel32.dll + RVA ExportTable = ExportTable Address
    mov r10d, [rdx+0x14]              ; Number of functions
    xor r11, r11                      ; Zero R11 before use
    mov r11d, [rdx+0x20]              ; AddressOfNames RVA
    add r11, r8                       ; AddressOfNames VMA
    mov rcx, r10                      ; store number of functions for future use
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26

    # 动态定位GetProcAddress

    mov rax, 0x9090737365726464       ; 'ddress'
    shl rax, 0x10                     ; 7373657264640000
    shr rax, 0x10                     ; 0000737365726464 terminate our string w/ no nulls present in our shellcode!
    push rax
    mov rax, 0x41636F7250746547       ; GetProcA 
    push rax
    mov rax, rsp	
    kernel32findfunction:             ; Loop over Export Address Table to find WinApi names
        jecxz FunctionNameNotFound    ; Loop around this function until we find GetProcAddress
        xor ebx,ebx                   ; Zero EBX for use
        mov ebx, [r11+rcx*4]          ; EBX = RVA for first AddressOfName
        add rbx, r8                   ; RBX = Function name VMA / add kernel32 base address to RVA and get WinApi name
        dec rcx                       ; Decrement our loop by one, this goes from Z to A
        mov r9, qword [rax]           ; R9 = "GetProcA"
        cmp [rbx], r9                 ; Compare first 8 bytes
        jnz kernel32findfunction      ; If not equal, continue loop
        mov r9d, dword [rax + 8]      ; R9 = "ddress"
        cmp [rbx + 8], r9d            ; Compare remaining part
        jz FunctionNameFound          ; If match, function found
    	jnz kernel32findfunction
    FunctionNameNotFound:
        int3
    FunctionNameFound:
        push rcx
        pop r15                       ; getprocaddress position
        inc r15   
        xor r11, r11
        mov r11d, [rdx+0x1c]          ; AddressOfFunctions RVA
        add r11, r8                   ; AddressOfFunctions VMA in R11. Kernel32+RVA for addressoffunctions
        mov eax, [r11+r15*4]          ; Get the function RVA.
        add rax, r8                   ; Found the GetProcAddress WinApi!!!
        push rax                      ; push GetProcAddress temporarily to be used by next segment
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32

    我真的希望不需要这么多行代码就能动态定位kernel32以及你最初的API,但看起来这种情况已经持续很久了。不过值得庆幸的是,代码的其他部分要短得多,也更容易理解,至少在我看来是这样。

    # 使用GetProcAddress句柄找到LoadLibraryA的地址

    ; Prepare arguments for getting handle to LoadLibraryA:
        pop r15                        ; temporary use
        mov r12, r15                   ; save copy of GetProcAddress for future use
        mov rdi, r8                    ; make a copy of kernel32 base address for future use
        mov rcx, r8                    ; RCX = handle to kernel32.dll (first argument)
    ; Load "LoadLibraryA" onto the stack
        mov rax, 0x41797261            ; aryA
        push rax
        mov rax, 0x7262694C64616F4C    ; LoadLibr
        push rax
        mov rdx, rsp                   ; RDX points to "LoadLibraryA" (second argument)
        sub rsp, 0x30                  ; decimal 48 ( 3 x 16 bytes)
        call r15                       ; Call GetProcAddress
        add rsp, 0x30                  ; alignmnent/shadow space adjustments
        mov r15, rax                   ; holds LoadLibraryA!
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    # 使用GetProcAddress句柄查找ExitProcess的地址

    ;getexitprocess
        mov r14, r12                    ; temporary assignment of GetProcess handle
        mov rcx, rdi                    ; RCX = handle to kernel32.dll (first argument)
    ; Load "ExitProcess" onto the stack
        mov rax, 0x90737365             ; 'ess'
        shl eax, 0x8                    ; 0000000073736500
        shr eax, 0x8                    ; 0000000000737365 terminate our string w/ no nulls present in our shellcode!
        push rax
        mov rax, 0x636F725074697845     ; ExitProc 
        push rax
        mov rdx, rsp                    ; RDX points to "ExitProcess" (second argument)
        sub rsp, 0x30
        call r14                        ; Call GetProcAddress
        add rsp, 0x30
        mov r14, rax                    ; holds ExitProcess!
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    # 使用LoadLibraryA句柄定位user32.dll

    ;locate user32.dll
        mov rax, 0x90906C6C             ; add "ll" string to RAX
        shl eax, 0x10                   ; 000000006C6C0000
        shr eax, 0x10                   ; 0000000000006C6C
        push rax                        ; push RAX to stack
        mov rax, 0x642E323372657375     ; Add "user32.d" string to RAX.
        push rax                        ; Push RAX to stack
        mov rcx, rsp                    ; Move a pointer to User32.dll into RCX.
        sub rsp, 0x30
        call r15                        ; Call LoadLibraryA("user32.dll")
        mov rdi, rax                    ; holds User32.dll address
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    # 使用user32.dll句柄和GetProcAddress定位MessageBoxA地址

    ; Prepare arguments for GetProcAddress for MessageBoxA:
        mov rcx, rdi                    ; RCX = handle to user32.dll (first argument)
        mov rax, 0x9041786F             ; Load "oxA" into RAX
        shl eax, 0x8                    ; 0000000041786F00
        shr eax, 0x8                    ; 000000000041786F
        push rax
        mov rax, 0x426567617373654D     ; Load "MessageB" into RAX                  
        push rax
        mov rdx, rsp                    ; RDX points to "MessageBoxA" (second argument)
        sub rsp, 0x30
        call r12                        ; Call GetProcAddress
        mov r15, rax                    ; store MessageBoxA
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    # 弹出MessageBox消息框

    ;messageboxfinally: 
        xor rcx, rcx                    ; hWnd = NULL (no owner window)
        mov rax, 0x9090906D             ; m, 0
        shl eax, 24                     ; 000000006D000000
        shr eax, 24                     ; 000000000000006D
        push rax
        mov rax, 0x3374737973743367     ; g3tsyst3
        push rax
        mov rdx, rsp                    ; lpText = pointer to message
        mov r8, rsp                     ; lpCaption = pointer to title
        xor r9d, r9d                    ; uType = MB_OK (OK button only)
        sub rsp, 0x30
        call r15                        ; Call MessageBoxA
        add rsp, 0x30
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    # 调用ExitProcess

    ;exitcleanly:
        xor ecx, ecx
        call r14                        ;ExitProcess
    
    1
    2
    3

    就是这样!好了,现在我们继续获取我们的shellcode(机器码):

    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\x5b\x3c\x4c\x01\xc3\x48"
    "\x31\xc9\x66\x81\xc1\xff\x88\x48\xc1\xe9\x08\x8b\x14\x0b\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\x6d\x90\x90\x90\xc1\xe0\x18\xc1\xe8\x18\x50"
    "\x48\xb8\x67\x33\x74\x73\x79\x73\x74\x33\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";
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    将shellcode放到我们的C++程序中:

    #include <windows.h>
    #include <iostream>
    
    // Shellcode (as given, formatted for clarity)
    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\x5b\x3c\x4c\x01\xc3\x48"
    "\x31\xc9\x66\x81\xc1\xff\x88\x48\xc1\xe9\x08\x8b\x14\x0b\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\x6d\x90\x90\x90\xc1\xe0\x18\xc1\xe8\x18\x50"
    "\x48\xb8\x67\x33\x74\x73\x79\x73\x74\x33\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";
    
    
    int main() {
        // Allocate executable memory
        void* exec_mem = VirtualAlloc(0, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    
        if (exec_mem == nullptr) {
            std::cerr << "Memory allocation failed\n";
            return -1;
        }
    
        // Copy shellcode to the allocated memory
        memcpy(exec_mem, shellcode, sizeof(shellcode));
    
        // Create a function pointer to the shellcode
        auto shellcode_func = reinterpret_cast<void(*)()>(exec_mem);
    
        // Execute the shellcode
        shellcode_func();
    
        // Free the allocated memory
        VirtualFree(exec_mem, 0, MEM_RELEASE);
    
        return 0;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48

    执行效果:

    虽然这看起来像是一段不必要的大量代码,但从整体来看还不算太糟。我在Notepad++中显示的代码总共有132行。如果我们不需要考虑空值的话,代码会少很多。但是,对于加载一个消息框所需的代码量来说,这真的不算多了😸 不过,当所有部分都整合在一起时,那种感觉确实很棒。接下来是套接字和反向shell的内容!请继续关注,感谢阅读。我真的很感谢大家的支持以及从这些帖子中提出的问题。请继续提问,我们很快再见!

    Windows x64 ShellCode入门教程 04
    Windows x64 ShellCode入门教程程 06

    ← Windows x64 ShellCode入门教程 04 Windows x64 ShellCode入门教程程 06→

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