날씨가 갑작스럽게 추워진 12월입니다.
추위가 기승을 부리는데 보안관련 사건사고도 끊이지 않고 있네요.
오늘은 수 많은 보안관련 이슈 중 침해사고에 관련된 내용을 조금 전해드릴까 합니다.
보통 침해사고가 일어날 경우 루트킷이나 백도어가 설치되고 관리자가 이를 알아채지 못하도록 바이너리를 변조하는 행위를 하게 됩니다.
ps나 netstat과 같은 명령어들을 변조하면 프로세스 현황이나 네트워크 현황을 정상적으로 출력하지 못하겠죠.
이에 침해사고 분석 시에는 변조되지 않은 바이너리를 업로드하는 등의 형태로 진행하는 경우가 많습니다.
오늘은 /proc 파일시스템을 사용하여 간단하게 프로세스 정보와 네트워크 현황 정보를 볼 수 있는 방법을 알아볼까 합니다.
/proc 파일시스템을 분석하면 서버에 대한 많은 정보들을 알 수 있는데, 굳이 침해사고 분석 케이스만이 아니더라도 활용할 수 있는 부분이 많습니다.
* /proc 파일시스템을 활용한 네트워크 현황 체크하기
#!/bin/sh
#
# mini-netstat.sh
#
# Coded by TeamCR@K in A3Security
# TeamCR@K: 1ndr4, bash205, blarees, alrogia, fr33p13, kgyoun4, rhaps0dy, CrazyPure
#
FILES=("/proc/net/tcp" "/proc/net/tcp6")
for FILE in ${FILES[@]}; do
echo "[*] ------------------- ${FILE}"
echo
echo -e "Local Address\tRemote Address"
for LINE in $(cat ${FILE} | awk -F" " '{ print $2 "|" $3 }' | egrep -v address); do
N=2
IDX=0
LINE_ARRAY=("")
for ((P = 1; P < ${#LINE}; P+=2,N+=2,IDX++)) ; do
C=`echo "${LINE}" | cut -b ${P}-${P}`
if [ "${C}" == ":" ] || [ "${C}" == "|" ]; then
P=${P}-1; N=${N}-1;
continue
fi
LINE_ARRAY[${IDX}]=`echo "${LINE}" | cut -b ${P}-${N}`
done
# /proc/net/tcp
if [ "${#LINE_ARRAY[@]}" == "12" ]; then
START=1
LOCAL_ADDR="$((16#${LINE_ARRAY[3]})).$((16#${LINE_ARRAY[2]})).$((16#${LINE_ARRAY[1]})).$((16#${LINE_ARRAY[0]}))"
LOCAL_PORT="$((16#${LINE_ARRAY[5]}${LINE_ARRAY[6]}))"
RMT_ADDR="$((16#${LINE_ARRAY[11]})).$((16#${LINE_ARRAY[10]})).$((16#${LINE_ARRAY[9]})).$((16#${LINE_ARRAY[8]}))"
RMT_PORT="$((16#${LINE_ARRAY[13]}${LINE_ARRAY[14]}))"
echo -e "${LOCAL_ADDR}:${LOCAL_PORT}\t${RMT_ADDR}:${RMT_PORT}"
# /proc/net/tcp6
elif [ "${#LINE_ARRAY[@]}" == "36" ]; then
LOCAL_ADDR="$((16#${LINE_ARRAY[15]})).$((16#${LINE_ARRAY[14]})).$((16#${LINE_ARRAY[13]})).$((16#${LINE_ARRAY[12]}))"
LOCAL_PORT="$((16#${LINE_ARRAY[17]}${LINE_ARRAY[18]}))"
RMT_ADDR="$((16#${LINE_ARRAY[35]})).$((16#${LINE_ARRAY[34]})).$((16#${LINE_ARRAY[33]})).$((16#${LINE_ARRAY[32]}))"
RMT_PORT="$((16#${LINE_ARRAY[37]}${LINE_ARRAY[38]}))"
echo -e "${LOCAL_ADDR}:${LOCAL_PORT}\t${RMT_ADDR}:${RMT_PORT}"
else
echo "[!] Parse Error: ${LINE}"
fi
done
done
위 쉘 스크립트는 /proc/net/tcp, /proc/net/tcp6 파일의 내용을 파싱하여 현재 네트워크 현황을 알 수 있도록 제작된 스크립트입니다.
해당 쉘 스크립트를 실행하면 다음과 같이 netstat 명령의 결과와 비슷한 출력 결과를 보실 수 있습니다.
[그림 1] mini-netstat.sh 실행화면
/proc/net/tcp와 tcp6 파일은 Hex-Decimal 형태로 되어 있는데, 이를 변환하면 아이피와 포트정보를 알 수 있습니다.
[그림 2] 아이피와 포트정보가 Hex-Decimal 형태로 존재하는 /proc/net/tcp와 tcp6 파일
tcp와 tcp6의 내용을 변환하도록 하는 코드를 짜내느라 애 좀 먹었습니다. ^^;
* /proc 파일시스템을 활용한 프로세스 현황 체크하기
#!/bin/sh
# mini-proc.sh
#
# Coded by TeamCR@K in A3Security
# TeamCR@K: 1ndr4, bash205, blarees, alrogia, fr33p13, kgyoun4, rhaps0dy, CrazyPure
#
ROOT="/proc"
DELIM='|'
# Printable variables
REALPATH=""
CMDLINE=""
_USER=""
_UID=""
RESULT="<PID>${DELIM}<USER>${DELIM}<REALPATH>${DELIM}<CMDLINE>\n"
for PID in $(ls ${ROOT}); do
DIR="$ROOT/$PID"
if [ ! -d "$DIR" ]; then
continue
fi
STATUS="$DIR/status"
############
# 1. Get a valid process
###########
if [ ! -f "${STATUS}" ]; then
continue
fi
############
# 2. Get command line information of the process
###########
CMDLINE=`cat /proc/${PID}/cmdline | sed -e 's/\x00/\x20/g'`
if [ "$CMDLINE" == "" ]; then
CMDLINE=`grep "Name:" ${STATUS} | awk '{ print $2 }'`
if [ "$?" == "0" ]; then
CMDLINE="[${CMDLINE}]"
else
CMDLINE="[Unknown]"
fi
fi
############
# 3. Get real path of process binary
###########
REALPATH=`readlink ${ROOT}/${PID}/exe`
if [ "$?" == "1" ]; then
REALPATH="[UNKNOWN]"
fi
############
# 4. Get UserID information of the process
###########
_UID=`grep Uid /proc/${PID}/status | awk '{ print $2 }'`
_USER=`getent passwd ${_UID} | cut -d: -f1`
LINE="${PID}${DELIM}${_USER}${DELIM}${REALPATH}${DELIM}${CMDLINE}"
RESULT="${RESULT}\n${LINE}"
done
echo -e $RESULT | column -t -s '|'
위 쉘 스크립트는 /proc/${PID}/status 파일과 /proc/${PID}/cmdline 파일을 기반으로 현재 실행되고 있는 프로세스 정보들을 출력하는 스크립트입니다.
해당 쉘 스크립트를 실행하면 다음과 같은 출력형태를 보실 수 있습니다.
[그림 3] mini-proc.sh 실행화면
간단하게 프로세스아이디 및 유저 정보, 프로세스 명을 출력하도록 되어 있습니다.
출력 결과 중 <REALPATH> 항목이 존재하는데 이는 프로세스의 실제 이미지 경로를 출력하도록 되어 있는 항목입니다.
본래 루트킷이나 백도어의 경우 프로세스 명을 숨기도록 작업한 사례들이 종종 있었습니다.
다음 화면은 프로세스 실행 중 ARGV[0] 메모리 영역을 다른 문자열로 Overwrite 하여 프로세스 명을 숨기도록 한 예입니다.
[그림 4] 프로세스 명을 숨기는 케이스
argv[0] 메모리 영역에 "[kjournald]"라는 문자열을 덮어씌워 일반적인 ps 명령으로 보면 정상적인 kernel thread로 오인할 수 있습니다.
<REALPATH> 항목은 위와 같은 경우를 위해 출력하도록 만든 항목입니다.
아래는 프로세스 명을 숨기도록 한 케이스에 mini-proc.sh 스크립트 실행을 적용해보았습니다.
[그림 5] 프로세스 이미지의 실제 경로를 출력한 화면
kernel thread인 경우 프로세스 이미지의 FULL-PATH를 알 수 없어 "[UNKNOWN]"으로 표기됩니다.
그 외의 경우 일반 유저 권한으로 타 계정의 /proc 파일 시스템 파일의 열람권한이 없으므로 "[UNKNOWN]"으로 표기되는 경우가 있는데, 이는 root 권한으로 실행하여 해결 할 수 있습니다.
[그림 6] root 권한으로 실제 프로세스 이미지 경로를 얻어오기
/proc 파일시스템을 활용하면 위에 말씀드린 내용들보다 더 많은 정보들을 얻어올 수 있습니다.
/proc/$$/maps를 보면 현재 프로세스의 메모리 맵핑 정보를 볼 수 있고 /proc/cpuinfo나 /proc/meminfo를 참조하면 하드웨어 스펙을 알 수 있습니다.
또한 /proc/${PID}/fd 를 참조하면 해당 프로세스에서 open한 파일들을 알 수 있기 때문에 간이 lsof를 만드는 것도 가능할 듯 합니다.
linux를 조금 더 알고 싶으신 분들은 지금이라도 /proc 파일시스템에 관심을 가져보는 것은 어떨까요? ^^;
저희가 준비한 내용은 여기까지 입니다.
2014년 막바지에 날씨가 많이 추워졌으니 모두 몸 조심하시고 건강한 겨울 나시길 빌겠습니다.
읽어주셔서 감사합니다.