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

# Windows x64 ShellCode入门教程 02

我本想在第二部分讨论如何移除空字节(NULL bytes),我保证这部分内容一定会有!但我从x64 shellcode和汇编文章的第一部分收到了一些不错的反馈,还有一些关于计算PE偏移量的问题。所以,我想用第二部分来解释我是如何得出代码中使用的特定偏移量的。让我们开始吧!

首先,我们来获取一个像样的PE查看器。我选择使用Pepper作为查看x64二进制文件的PE查看器:Pepper x64 PE查看器 (opens new window)

我将详细讲解PE头的每个部分以及导出表。让我们以本系列文章中的汇编代码作为参考:

mov r8, rbx         ; mov kernel32.dll base addr into r8
1

在我的案例中,Kernel32的基地址是:00007FFA63570000。

所以,00007FFA63570000现在在r8和rbx中

mov ebx, [rbx+0x3C] (move into lower 32 bits of the rbx register, hence why we use ebx)
1

我们的x64dbg调试器将显示:dword ptr ds:[rbx+3C]=[kernel32.00007FFA6357003C]=F8

这是我们的PE签名偏移量,如下图所示。我们首先获取kernel32.00007FFA6357003C所指向的值,即F8,然后将其添加到kernel32中。

add rbx, r8 = 00007FFA635700F8 (PE header/DOS header)
1

现在,我们处于[IMAGE_OPTIONAL_HEADER64][IMAGE_DATA_DIRECTORY]中

mov edx, [rbx+0x88]
1

这是为了获取导出表的偏移量

F8 + 0x88 = 180对我来说,这等于00000000000A3D80,也就是导出表的相对虚拟地址(RVA)。

add rdx, r8
1

00007FFA63613D80 → RVA导出表的地址

mov r10d, [rdx+0x14]          ; r10d (the lower 32 bits of r10) now holds the function count.
1

xor r11, r11                  ; Zero R11 before use
mov r11d, [rdx+0x20]          ; r11d (the lower 32 bits of r11) now holds the AddressOfNames RVA
1
2

00000000000A5814 在x64dbg中

add r11, r8                   ; AddressOfNames VMA
1

00007FFA63615814

mov rcx, r10                      ; r10 has our total function count.  Set RCX loop counter

; Loop over Export Address of Names Table to find WinApi names
kernel32findfunction: 
               
    mov ebx, [r11+rcx*4]                 ; EBX = RVA for first AddressOfName
1
2
3
4
5
6
  • r11 = 函数名的相对虚拟地址(RVA)
  • +rcx = 函数列表中的位置
  • 4 = rcx * 4:由于AddressOfNames中的每个RVA都是4字节的条目,将rcx乘以4可得到正确的偏移量,以检索特定函数名的RVA。

循环结束后,我们的函数名就会被找到,并且函数名中的位置将存储在rcx中。

我们将rcx入栈,然后将其弹出到r15中,这就引导我们来到了这里:

OrdinalLookupSetup:  ;We found our target WinApi position in the functions lookup
   pop r15           ;Winexec position
   js OrdinalLookup
   
OrdinalLookup:   
mov rcx, r15                        ;Winexec location in function names
xor r11, r11                        ;clear r11
mov r11d, [rdx+0x24]                ; AddressOfNameOrdinals RVA
1
2
3
4
5
6
7
8

X64dbg输出= dword ptr ds:[rdx+24]=[kernel32.00007FFA63613DA4]=A7280

add r11, r8                   ; AddressOfNameOrdinals VMA
1

添加到kernel32 = 00007FFA63617280

; Get the function ordinal from AddressOfNameOrdinals
inc rcx
mov r13w, [r11+rcx*2]         ; AddressOfNameOrdinals + Counter. RCX = counter
1
2
3

虚拟内存地址 + rcx(1612)× 2(字节)= WinExec的序号值!!!

;With the function ordinal value, we can finally lookup the WinExec address from AddressOfFunctions.
; Get function address from AddressOfFunctions
xor r11, r11                  ; clear r11
mov r11d, [rdx+0x1c]          ; AddressOfFunctions RVA
1
2
3
4

add r11, r8                   ; AddressOfFunctions VMA in R11. Kernel32+RVA for addressoffunctions
mov eax, [r11+r13*4]        ; Get the function RVA.
1
2

R11 = 相对虚拟内存地址

R13 = Winexec序号(0x64C = 十进制1612)

*** 4(字节)= 1612 = Winexec**

X64dbg = dword ptr ds:[r11+r13*4]=[kernel32.00007FFA636156D8]=608B0

add rax, r8                   ; Found the WinExec WinApi!!!
1

将kernel32添加到我们的WinExec相对虚拟地址中

RAX现在持有WinExec的实际地址!!!

就是这样!现在你可以根据自己的喜好使用Winexec了,而且你应该也对如何遍历PE文件、解析PE头、查看导出目录信息等有了更清晰的认识。谢谢!

Windows x64 ShellCode入门教程 01
Windows x64 ShellCode入门教程 03

← Windows x64 ShellCode入门教程 01 Windows x64 ShellCode入门教程 03→

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