#!/bin/sh
# ragg2-cc : a shellcode compiler -- pancake - 2011-2016
#
# Supported operating systems:
#  - GNU/Linux
#  - OSX
#  - BSD
# Supported compilers
#  - gcc
#  - clang
# TODO
#  add support for arm
#  add support for nested shellcodes

# Find which compiler is installed
if [ -z "${CC}" ]; then
	for a in llvm-gcc clang gcc ; do
		$a --version >/dev/null 2>&1
		if [ $? = 0 ]; then
			CC="$a"
			break
		fi
	done
	if [ -z "${CC}" ]; then
		echo "Cannot find CC" >&2
		exit 1
	fi
fi

# Get path for sflib
if [ -z "${SFLIBPATH}" ]; then
	SFLIBPATH="$(r2 -hh | grep INCDIR | awk '{print $2}')"/sflib
fi
if [ ! -d "${SFLIBPATH}" ]; then
	echo "Cannot find ${SFLIBPATH}"
	echo "Define SFLIBPATH env var or fix the r2 installation"
	exit 1
fi

# Get local architecture
case "$(uname -m)" in
arm64|aarch64|x86_64)
	B=64
	;;
*)
	B=32
	;;
esac

dohelp() {
	cat<<EOF
Usage: ragg2-cc [-cdsvx] [-a arch] [-b bits] [-k kernel] [-o output] [file.c]
  -a x86   set arch (x86, arm)
  -b 32    bits (32, 64)
  -c       generate compiled shellcode
  -d       enable debug mode
  -k linux set kernel (darwin, linux)
  -o file  set output file
  -s       generate assembly
  -v       show version
  -x       show hexpair bytes
EOF
}

case "`uname`" in
Darwin)
	K=darwin
	;;
*)
	K=linux
	;;
esac

X=0
C=""
D=""
O=""
F=""
ASM=0
A=x86
while : ; do
	[ -z "$1" ] && break
	F=$1
	case "$F" in
	-a) # architecture (x86, mips, arm)
		shift
		A=$1
		[ -z "$A" ] && { echo "Missing argument for -a" ; exit 1; }
		;;
	-b)  # register size (32, 64, ...)
		shift
		B=$1
		[ -z "$B" ] && { echo "Missing argument for -b" ; exit 1; }
		;;
	-k)  # kernel
		shift
		K=$1
		[ -z "$K" ] && { echo "Missing argument for -k" ; exit 1; }
		;;
	-x)  # execute
		X=1
		;;
	-c)  # set configuration option
		C=1
		;;
	-d)  # patch dword (4 bytes) at given offset
		D=1
		;;
	-s)  # show assembler
		ASM=1
		;;
	-o)  # output file
		shift
		O=$1
		if [ -z "$O" ]; then
			echo "Missing argument for -o"
			exit 1
		fi
		;;
	-h)  # help
		dohelp
		exit 0
		;;
	-v)  # version
		ragg2 -v | sed -e 's,2,2-cc,'
		exit 0
		;;
	esac
	shift
done

if [ -z "$F" ]; then
	dohelp
	exit 1
fi

JMP=jmp
case "$A" in
arm|aarch64|arm64|thumb|arm32)
	JMP=b
	;;
mips|mips32|mips64)
	JMP=b
	;;
esac

FMT=elf
if [ "$K" = darwin ]; then
	OBJCOPY=gobjcopy
	FMT=mach0
	ARCH="$A"
	if [ "$ARCH" = x86 ]; then
		if [ "${B}" = 32 ]; then
			ARCH=i386
			TRIPLET=darwin-x86-32
		else
			ARCH=x86_64
			TRIPLET=darwin-x86-64
		fi
		case "$B" in
		32)
			CFLAGS="-arch $ARCH "
			LDFLAGS="-arch $ARCH -shared -c"
			;;
		64)
			CFLAGS="-arch $ARCH"
			LDFLAGS="-arch $ARCH -shared -c"
			;;
		esac
	else
		LDFLAGS="-shared -c"
	fi
	SHDR="
.text
${JMP} _main"
else
	OBJCOPY=objcopy
	SHDR="
.section .text
.globl  main
// .type   main, @function
${JMP} main
"
	if [ "$A" = x86 ]; then
		case "$B" in
		64)
			CFLAGS="-fPIC -fPIE -pie -fpic -m64"
			LDFLAGS="-fPIC -fPIE -pie -fpic -m64"
			TRIPLET=linux-x86-64
			;;
		*) 
			CFLAGS="-fPIC -fPIE -pie -fpic -m32"
			LDFLAGS="-fPIC -fPIE -pie -fpic -m32"
			TRIPLET=linux-x86-32
			;;
		esac
	else
		CFLAGS="-fPIC -fPIE -pie -fpic -nostartfiles"
		LDFLAGS="-fPIC -fPIE -pie -fpic -nostartfiles"
	fi
fi

[ "$A$K" ] && TRIPLET="$K-$A-$B"

case "$K" in
windows)
	#TEXT="__TEXT.__text"
	TEXT=".text"
	FMT=pe
	;;
darwin)
	#TEXT="__TEXT.__text"
	TEXT="0.__text"
	FMT=mach0
	;;
*|linux)
	TEXT=".text"
	FMT=elf
	;;
esac

USE_CLANG=0
case "$K-$A-$B" in
darwin-arm-64)
	CC="xcrun --sdk iphoneos gcc -arch arm64"
	USE_CLANG=1
	TEXT=0.__TEXT.__text
	;;
darwin-arm-32)
	USE_CLANG=1
	CC="xcrun --sdk iphoneos gcc -arch armv7"
	TEXT=0.__TEXT.__text
	;;
esac

OPT=-Os
CFLAGS="${CFLAGS} -nostdinc -include ${SFLIBPATH}/${TRIPLET}/sflib.h"
if [ 1 = "${USE_CLANG}" ]; then
	CFLAGS="${CFLAGS} -fomit-frame-pointer -fno-zero-initialized-in-bss"
else
	CFLAGS="${CFLAGS} -z execstack -fomit-frame-pointer -finline-functions -fno-zero-initialized-in-bss"
fi
LDFLAGS="${LDFLAGS} -nostdlib"


rmtemps() {
	[ -z "$D" ] && rm -f $F.tmp $F.text $F.s $F.o
}

fail() {
	echo "ERROR: $@"
	rmtemps
	exit 1
}

if [ "$D" ]; then
	echo "==> Compile"
	echo "${CC} ${CFLAGS} -o $F.tmp -S ${OPT} $F"
fi
rm -f "$F.bin"
echo ${CC} ${CFLAGS} -o "$F.tmp" -S ${OPT} "$F"
${CC} ${CFLAGS} -o "$F.tmp" -S ${OPT} "$F" || fail
echo "${SHDR}" > $F.s
cat "$F.tmp" \
	| sed -e s,rdata,text, -e s,rodata,text, -e 's,get_pc_thunk.bx,__getesp__,g' \
	| grep -v .cstring | grep -v size | grep -v ___main | grep -v section \
	| grep -v __alloca | grep -v zero | grep -v cfi >> $F.s
rm -f "$F.tmp"
if [ $ASM = 1 ]; then
	echo "$F.s"
	exit 0
fi

echo ==============================

if [ "$D" ]; then
	echo "==> Assemble"
	echo "${CC} ${LDFLAGS} ${OPT} -o $F.o $F.s"
fi
echo "${CC} ${LDFLAGS} ${OPT} -o $F.o $F.s"
${CC} ${LDFLAGS} ${OPT} -o "$F.o" "$F.s" || fail 'compile object'

if [ "$D" ]; then
	echo "==> Link"
	#echo "${OBJCOPY} -j .text -O binary $F.o $.text"
	echo "rabin2 -o '$F.text' -O d/S/${TEXT} $F.o"
fi
rabin2 -o "$F.text" -O d/S/${TEXT} $F.o
if [ ! -f "$F.o" ]; then
	echo "Cannot find $F.o"
	exit 1
fi
if [ "`du $F.text|awk '{print $1}'`" = 0 ]; then
	# use objcopy as falback for rabin2
	echo "FALLBACK: Using objcopy instead of rabin2"
	${OBJCOPY} -j .text -O binary $F.o $F.text || fail
fi
if [ "$C" = 1 ]; then
	if [ "$O" ]; then
		mv "$F.text" "$O"
	else
		O="$F.text"
	fi
	echo "$O"
	exit 0
fi

[ "$X" = 1 ] && exec rax2 -S < "$F.text"

if [ "$D" ]; then
#	hexdump -C $F.text
	rax2 -S - < $F.text
	ls -l $F.text
fi
[ -z "$O" ] && O="$F.bin"
ragg2 -b "$B" -C "$F.text" -f ${FMT} -a $A -o "$O" || fail "ragg2 cannot generate executable. Use -x"
echo "$O"
rmtemps
exit 0
