Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 50 additions & 45 deletions bin/linux/linux_procmemdump.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
#!/bin/bash
#!/usr/bin/env bash
# Hal Pomeranz ([email protected]) -- 2022-11-26
# Distributed under the Creative Commons Attribution-ShareAlike 4.0 license (CC BY-SA 4.0)

# shellcheck disable=SC2004
# Enable strict mode
set -o errexit # Exit on error
set -o errtrace # Exit on error in functions
set -o nounset # Exit on undefined variables
set -o pipefail # Exit on pipe failures
# set -o xtrace # Uncomment for debugging

# Set safe field separator
# nosemgrep: ifs-tampering
IFS=$'\n\t'

usage() {
cat <<"EOM"
cat <<EOM
Dump process memory sections and extract ASCII strings from running processes

Usage: $0 [-p pid] [-b] [-s] [-u] -d dumpdir
Expand All @@ -15,82 +24,78 @@ Usage: $0 [-p pid] [-b] [-s] [-u] -d dumpdir
-b Encode memory sections by swapping bytes (protect from AV)
-s Output strings only, do not write out memory section data
EOM
exit 1;
exit 1
}

strings_only=0
swap_bytes=0
outputdir=""
user_pid=""
uac_path=""

while getopts "bd:p:su" opts; do
case ${opts} in
b) swap_bytes=1
;;
d) outputdir=${OPTARG}
;;
p) user_pid=${OPTARG}
;;
s) strings_only=1
;;
u) uac_path="sections"
;;
*) usage
;;
b) swap_bytes=1 ;;
d) outputdir=${OPTARG} ;;
p) user_pid=${OPTARG} ;;
s) strings_only=1 ;;
u) uac_path="sections" ;;
*) usage ;;
esac
done

[[ -z "${outputdir}" ]] && usage # Specify an output directory or error
[[ -z "${outputdir}" ]] && usage # Specify an output directory or error

if [[ -n "${user_pid}" ]]; then # One user specified PID or all PIDS?
if [[ -n "${user_pid}" ]]; then # One user specified PID or all PIDs?
input_files="/proc/${user_pid}/maps"
else
input_files="/proc/[0-9]*/maps"
fi

# Dumping both strings and memory sections is accomplished using a FIFO to split the output
# into two streams. If we're only extracting strings, we don't need this.
#
fifo_path=""
if [[ ${strings_only} -eq 0 ]]; then
fifo_path="${outputdir}/.tempfifo"
mkdir -p "${outputdir}"
sudo mkdir -p -v "${outputdir}"
mkfifo "${fifo_path}"
fi

# Add a trap to ensure FIFO is removed on exit
trap '[[ -n "${fifo_path}" && -e "${fifo_path}" ]] && rm -f "${fifo_path}"' EXIT

# Loop over all processes
for mapfile in ${input_files}; do
pid=$(echo "${mapfile}" | cut -f3 -d/)
[[ ${pid} -eq $$ ]] && continue # Don't dump our own process
[[ ${pid} -eq $$ ]] && continue # Don't dump our own process

thisoutput="${outputdir}/${pid}" # Where the output for this process goes
mkdir -p "${thisoutput}/${uac_path}"
thisoutput="${outputdir}/${pid}" # Where the output for this process goes
mkdir -p -v "${thisoutput}/${uac_path}"

# Process the regions for this process
# shellcheck disable=SC2002,SC2034,SC2162
cat "${mapfile}" | sed 's/-/ /' | while read start end flags junk; do
[[ "${flags}" =~ ^r ]] || continue # Skip unreadable sections

start_page=$((16#${start} / 4096)) # convert byte offset to page offset

# If we are only dumping strings, then don't bother tee-ing data into a FIFO
# If we are dumping memory regions with bytes swapped, then use "dd conv=swab" to read from FIFO
# Otherwise just read the FIFO with "cat"
#
# Output from the main "dd" command is aggregated into "strings" at the end of the loop.
# All data is gzip-ed to save space.
#
while read -r start end flags _; do
[[ "${flags}" =~ ^r ]] || continue # Skip unreadable sections

start_page=$((16#${start} / 4096)) # convert byte offset to page offset

dump_memory() {
if ! dd if="/proc/${pid}/mem" bs=4096 skip="${start_page}" count=$((16#${end} / 4096 - start_page)) 2>/dev/null; then
printf "Error reading memory for PID %d\n" "${pid}" >&2
return 1
fi
}

if [[ ${strings_only} -gt 0 ]]; then
dd if="/proc/${pid}/mem" bs=4096 skip=${start_page} count=$((16#${end} / 4096 - ${start_page}))
dump_memory
elif [[ ${swap_bytes} -gt 0 ]]; then
dd if="${fifo_path}" conv=swab | gzip >"${thisoutput}/${uac_path}/${start}-${end}.mem.swab.gz" &
dd if="/proc/${pid}/mem" bs=4096 skip=${start_page} count=$((16#${end} / 4096 - ${start_page})) | tee "${fifo_path}"
dump_memory | tee "${fifo_path}" |
{ dd conv=swab 2>/dev/null | gzip >"${thisoutput}/${uac_path}/${start}-${end}.mem.swab.gz"; } &
else
# shellcheck disable=SC2002
cat "${fifo_path}" | gzip >"${thisoutput}/${uac_path}/${start}-${end}.mem.gz" &
dd if="/proc/$pid/mem" bs=4096 skip=${start_page} count=$((16#${end} / 4096 - ${start_page})) | tee "${fifo_path}"
dump_memory | tee "${fifo_path}" |
gzip >"${thisoutput}/${uac_path}/${start}-${end}.mem.gz" &
fi
done 2>/dev/null | strings -a | gzip >"${thisoutput}/memory_strings.txt.gz"
done < "${mapfile}" 2>/dev/null | strings -a | gzip >"${thisoutput}/memory_strings.txt.gz"
done

# Get rid of the FIFO now that we're done
[[ -e "${fifo_path}" ]] && rm -f "${fifo_path}"
# Wait for background processes to finish
wait