1#!/bin/sh 2# r2docker 3# ======== 4# 5# Requires ~140MB of free disk space 6# 7# Build docker image with: 8# $ ./sys/docker_build_alpine_image.sh 9# 10# Run the docker image: 11# 12# $ r2d() { 13# local rm 14# case "$1" in 15# "-r"|"--rm") rm="--rm" && shift ;; 16# esac 17# docker run --cap-drop=ALL --cap-add=SYS_PTRACE -i \ 18# `--name r2_$(date +%F_%H%M%S%N) $rm -tv $(pwd):/r2 \ 19# r2_alpine:latest $@ 20# } 21# $ r2d # Optional --rm 22# 23# Once you quit the session, get the container id with something like: 24# 25# $ containerid="$(docker ps -a | awk '/r2_alpine/ {print $NF}')" 26# 27# To get into that shell again just type: 28# 29# $ docker start -ai $containerid 30# 31# To share those images: 32# 33# $ docker export $containerid | xz >container.tar.xz 34# $ xz -d <container.tar.xz | docker import - 35# 36# When finished: 37# 38# $ docker rm -f $containerid 39# 40# If you are unwilling to debug a program within Docker, remove 41# --cap-add=SYS_PTRACE from the r2d function. If you need sudo to 42# install more packages within Docker, remove --cap-drop=ALL from the 43# r2d function. 44 45### Helpers begin 46checkdeps() { 47 for d in curl docker jq; do 48 if [ -z "$(command -v "$d")" ]; then 49 echo "[!] $d is not installed" 50 exit 128 51 fi 52 done 53 unset d 54} 55long_opt() { 56 arg="" 57 shift="0" 58 case "$1" in 59 "--"*"="*) arg="${1#*=}"; [ -n "$arg" ] || usage 127 ;; 60 *) shift="1"; shift; [ $# -gt 0 ] || usage 127; arg="$1" ;; 61 esac 62 echo "$arg" 63 unset arg 64 return $shift 65} 66### Helpers end 67 68cleanup() { 69 rm -rf "$tmp_docker_dir" 70 [ "${1:-0}" -eq 0 ] || exit "$1" 71} 72 73usage() { 74 cat <<EOF 75Usage: ${0##*/} [OPTIONS] 76 77Build a Radare2 docker image that uses local user/group IDs. 78 79Options: 80 -b, --branch=BRANCH Use specified radare2 branch (default: 81 master) 82 -h, --help Display this help message 83 --node=VERSION Use specified Node.js version (default: 10) 84 -n, --npm=VERSION Use specified version of r2pipe npm binding 85 (default: newest) 86 -p, --py=VERSION Use specified version of r2pipe python 87 binding (default: newest) 88 89EOF 90 exit "$1" 91} 92 93args="" 94unset help r2pipe_npm r2pipe_py 95github="https://github.com/radareorg/radare2.git" 96r2branch="master" 97 98# Check for missing dependencies 99checkdeps 100 101# Parse command line options 102while [ $# -gt 0 ]; do 103 case "$1" in 104 "--") shift && args="$args${args:+ }$*" && break ;; 105 "-b"|"--branch"*) r2branch="$(long_opt "$@")" || shift ;; 106 "-h"|"--help") help="true" ;; 107 "-n"|"--npm"*) r2pipe_npm="@$(long_opt "$@")" || shift ;; 108 "-p"|"--py"*) r2pipe_py="==$(long_opt "$@")" || shift ;; 109 *) args="$args${args:+ }$1" ;; 110 esac 111 shift 112done 113[ -z "$args" ] || set -- "$args" 114 115# Check for valid params 116[ -z "$help" ] || usage 0 117[ $# -eq 0 ] || usage 1 118if [ "`uname`" = Linux ]; then 119 if [ "$(id -u)" -ne 0 ] && id -Gn | grep -qvw "docker"; then 120 echo "[!] You are not part of the docker group" 121 exit 2 122 fi 123fi 124 125trap "cleanup 126" INT 126 127# Create Dockerfile 128tmp_docker_dir=".docker_alpine" 129gid="$(id -g)" 130gname="$(id -gn)" 131r2commit="$( 132 curl -Ls \ 133 "http://api.github.com/repos/radare/radare2/commits/$r2branch" | \ 134 jq -cMrS ".sha" 135)" 136uid="$(id -u)" 137uname="$(id -nu)" 138mkdir -p $tmp_docker_dir 139cat >$tmp_docker_dir/Dockerfile <<EOF 140# Using super tiny alpine base image 141FROM alpine:latest 142 143# Install bash b/c it's better 144RUN apk upgrade && apk add bash 145 146# Bash is better than sh 147SHELL ["/bin/bash", "-c"] 148 149# Build radare2 in a volume to minimize space used by build 150VOLUME ["/mnt"] 151 152# All one RUN layer, splitting into 3 increases size to ~360MB, wtf 153# 1. Install dependencies 154# 2. Install Node.js and Python bindings (do we need these?) 155# 3. Create id and gid to match current local user 156# 4. Add new user to sudoers without password 157# 5. Add some convenient aliases to .bashrc 158# 6. Clone and install radare2 159# 7. Clean up unnecessary files and packages 160RUN set -o pipefail && \ 161 ( \ 162 apk upgrade && \ 163 apk add \ 164 g++ \ 165 gcc \ 166 git \ 167 libc6-compat \ 168 linux-headers \ 169 make \ 170 ncurses-libs \ 171 nodejs-current \ 172 npm \ 173 py2-pip \ 174 shadow \ 175 sudo \ 176 ) && ( \ 177 npm install -g --unsafe-perm "r2pipe$r2pipe_npm" && \ 178 pip install --upgrade pip && \ 179 pip install r2pipe$r2pipe_py \ 180 ) && ( \ 181 [ "$gname" != "root" ] || \ 182 ( \ 183 echo "alias la=\"\\ls -AF\"" >>/root/.bashrc && \ 184 echo "alias ll=\"\\ls -Fhl\"" >>/root/.bashrc && \ 185 echo "alias ls=\"\\ls -F\"" >>/root/.bashrc && \ 186 echo "alias q=\"exit\"" >>/root/.bashrc \ 187 ) \ 188 ) && ( \ 189 [ "$gname" = "root" ] || \ 190 ( \ 191 groupadd -f $gname && \ 192 (groupmod -g $gid $gname 2>/dev/null || true) && \ 193 useradd -g $gid -mou $uid $uname && \ 194 echo "$uname ALL=(ALL) NOPASSWD: ALL" \ 195 >/etc/sudoers.d/$uname && \ 196 echo "alias la=\"\\ls -AF\"" >>/home/$uname/.bashrc && \ 197 echo "alias ll=\"\\ls -Fhl\"" >>/home/$uname/.bashrc && \ 198 echo "alias ls=\"\\ls -F\"" >>/home/$uname/.bashrc && \ 199 echo "alias q=\"exit\"" >>/home/$uname/.bashrc \ 200 ) \ 201 ) && ( \ 202 cd /mnt && \ 203 git clone -b $r2branch --depth 1 $github && \ 204 cd radare2 && \ 205 git checkout $r2commit && \ 206 ./sys/install.sh && \ 207 make install \ 208 ) && ( \ 209 apk del --purge -r \ 210 g++ \ 211 gcc \ 212 linux-headers \ 213 make \ 214 npm \ 215 py2-pip && \ 216 rm -rf /tmp/* /var/cache/apk/* /var/tmp/* \ 217 ) 218 219# Initialize env 220USER $(id -nu) 221WORKDIR /r2 222 223# Setup r2pm 224RUN set -o pipefail && \ 225 r2pm init && \ 226 r2pm update 227 228CMD ["/bin/bash"] 229EOF 230 231# Tag old images 232echo "[*] Tagging any old r2_alpine images" 233docker images | awk '{print $1":"$3}' | while read -r tag; do 234 case "$tag" in 235 "r2_alpine:"*) docker image tag "${tag#*:}" "$tag" ;; 236 esac 237done 238unset tag 239 240findbase="$( 241 docker images | grep -E "^(docker\.io\/)?alpine +latest " 242)" 243 244# Build image (may take a while) 245echo "[*] Building image..." 246echo "[*] This may take a long time..." 247 248# Pull newest base image and build r2_alpine image 249docker pull alpine:latest 250( 251 if [ ! -d "$tmp_docker_dir" ]; then 252 echo "$tmp_docker_dir not found" 253 fi 254 cd $tmp_docker_dir || exit 3 255 # shellcheck disable=SC2154 256 docker build \ 257 ${http_proxy:+--build-arg http_proxy=$http_proxy} \ 258 ${https_proxy:+--build-arg https_proxy=$https_proxy} \ 259 -t r2_alpine:latest . 260) 261 262# Only remove base image if it didn't already exist 263[ -n "$findbase" ] || docker rmi alpine:latest 264 265echo "[*] done" 266 267old_base="^(docker\.io\/)?alpine +<none>" 268old_r2="^r2_alpine +[^l ]" 269found="$( 270 docker images | grep -E "($old_base)|($old_r2)" 271)" 272if [ -n "$found" ]; then 273 # List old images 274 echo 275 echo "[*] Old images:" 276 docker images | head -n 1 277 278 docker images | grep -E "($old_base)|($old_r2)" | \ 279 while read -r line; do 280 echo "$line" 281 done 282 unset line 283 284 # Prompt to remove old images 285 unset remove 286 echo 287 while :; do 288 echo "Remove old images (y/N/q)?" 289 read -r ans 290 echo 291 case "$ans" in 292 ""|"n"|"N"|"q"|"Q") break ;; 293 "y"|"Y") remove="true"; break ;; 294 *) echo "Invalid choice" ;; 295 esac 296 done 297 298 if [ -n "$remove" ]; then 299 # Remove old images 300 docker images | awk "/$old_r2/ {print \$1\":\"\$3}" | \ 301 while read -r tag; do 302 docker rmi "$tag" 303 done 304 unset tag 305 306 docker images | awk "/$old_base/ {print \$3}" | \ 307 while read -r id; do 308 docker rmi "$id" 309 done 310 unset id 311 fi 312fi 313unset found 314 315cleanup 0 316 317cat <<EOF 318 319It's suggested you add something like the following to your ~/.bashrc: 320 321r2d() { 322 local rm 323 case "\$1" in 324 "-r"|"--rm") rm="--rm" && shift ;; 325 esac 326 docker run --cap-drop=ALL --cap-add=SYS_PTRACE -i \\ 327 --name r2_\$(date +%F_%H%M%S%N) \$rm -tv \$(pwd):/r2 \\ 328 r2_alpine:latest \$@ 329} 330EOF 331