commit 12f9c9bb930efca0d46259a2aa3cbd18a8d1e8de Author: sose Date: Sun Jun 13 22:53:53 2021 +0000 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a64c2f8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.*.swp +.*.swo diff --git a/image/Dockerfile b/image/Dockerfile new file mode 100644 index 0000000..55b0869 --- /dev/null +++ b/image/Dockerfile @@ -0,0 +1,55 @@ +FROM debian:buster + +ARG USERNAME +ARG HOST_IP +ARG SLBR_PORT +ARG INFO_PORT +ARG LOG_PORT +ARG SSHD_PORT + +RUN (test -n "$USERNAME" || (echo "USERNAME not set" && false)) \ + && (test -n "$SLBR_PORT" || (echo "SLBR_PORT not set" && false)) \ + && (test -n "$INFO_PORT" || (echo "INFO_PORT not set" && false)) \ + && (test -n "$LOG_PORT" || (echo "LOG_PORT not set" && false)) \ + && (test -n "$HOST_IP" || (echo "HOST_IP not set" && false)) + +COPY --chown=root ./suicidebash.bashrc /etc/bash.bashrc +COPY --chown=root ./slbr.env /etc +COPY --chown=root ./package.list /package.list +COPY --chown=root ./insults.txt /insults.txt + +COPY ./bin/progress /usr/local/bin/ +COPY ./bin/submit /usr/local/bin/ +COPY ./banner /etc/motd +COPY ./pubkey /pubkey + +RUN apt remove bash-completion \ + && rm -rf /usr/share/bash-completion + +RUN apt update \ + && apt install -y $(cat /package.list) \ + && rm /package.list + +RUN sed -i "s/{{ HOST_IP }}/$HOST_IP/g" /etc/bash.bashrc /usr/local/bin/progress /usr/local/bin/submit \ + && sed -i "s/{{ SLBR_PORT }}/$SLBR_PORT/g" /etc/bash.bashrc /usr/local/bin/progress /usr/local/bin/submit \ + && sed -i "s/{{ INFO_PORT }}/$INFO_PORT/g" /etc/bash.bashrc /usr/local/bin/progress /usr/local/bin/submit \ + && sed -i "s/{{ LOG_PORT }}/$LOG_PORT/g" /etc/bash.bashrc /usr/local/bin/progress /usr/local/bin/submit \ + && mkdir /home/$USERNAME \ + && mkdir /run/sshd \ + && mkdir /home/$USERNAME/.ssh \ + && cat /pubkey > /home/$USERNAME/.ssh/authorized_keys \ + && rm /pubkey \ + && echo "PasswordAuthentication no" >> /etc/ssh/sshd_config \ + && echo "Port $SSHD_PORT" >> /etc/ssh/sshd_config + +COPY ./clues /home/$USERNAME +COPY ./rules.md /home/$USERNAME + +RUN useradd $USERNAME -d /home/$USERNAME \ + && chown -R $USERNAME:$USERNAME /home/$USERNAME \ + && chmod -R +x /usr/local/bin/ \ + && chsh $USERNAME -s /bin/bash + +#USER $USERNAME + +CMD ["/usr/sbin/sshd", "-D"] diff --git a/image/banner b/image/banner new file mode 100644 index 0000000..209239b --- /dev/null +++ b/image/banner @@ -0,0 +1,13 @@ +Welcome to: +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░█▀▀░█░█░▀█▀░█▀▀░▀█▀░█▀▄░█▀▀░░░█░░░▀█▀░█▀█░█░█░█░█░░ +░▀▀█░█░█░░█░░█░░░░█░░█░█░█▀▀░░░█░░░░█░░█░█░█░█░▄▀▄░░ +░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀▀░░▀▀▀░░░▀▀▀░▀▀▀░▀░▀░▀▀▀░▀░▀░░ +░█▀▄░█▀█░▀█▀░▀█▀░█░░░█▀▀░░░█▀▄░█▀█░█░█░█▀█░█░░░█▀▀░░ +░█▀▄░█▀█░░█░░░█░░█░░░█▀▀░░░█▀▄░█░█░░█░░█▀█░█░░░█▀▀░░ +░▀▀░░▀░▀░░▀░░░▀░░▀▀▀░▀▀▀░░░▀░▀░▀▀▀░░▀░░▀░▀░▀▀▀░▀▀▀░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ + +For rules, `cat ~/rules.md` +To submit your solution for a challenge, run `submit ""` +To see your current progress, run `progress` diff --git a/image/bin/progress b/image/bin/progress new file mode 100755 index 0000000..31a2bd2 --- /dev/null +++ b/image/bin/progress @@ -0,0 +1,20 @@ +#!/bin/bash +scoreboard="$(nc -q0 -d {{ HOST_IP }} {{ INFO_PORT }})" +my_ip="$(ip addr | grep 'scope global eth0' | cut -f6 -d ' ')" +total_challenges="$(echo "$scoreboard" | head -n1)" +completed_challenges="$( + echo "$scoreboard" \ + | grep "$my_ip" \ + | cut -f2- -d ' ' \ + | tr -d ' ' \ + | tr ',' '\n' \ + | sort \ + | tr '\n' ',' \ + | sed 's/,/, /g' \ + | sed 's/^, //' \ + | sed 's/, $//' +)" +num_completed="$(echo $completed_challenges | grep -oE '[0-9]' | wc -l)" +echo "There are $total_challenges total challenges" +echo "You have completed the following challenges: $completed_challenges" +echo "You have completed $num_completed of the $total_challenges total challenges" diff --git a/image/bin/submit b/image/bin/submit new file mode 100755 index 0000000..92536bf --- /dev/null +++ b/image/bin/submit @@ -0,0 +1,58 @@ +#!/bin/bash + +help_text=" +submit.sh - submit an slbr solution to be verified +usage: ./submit +for example: ./submit 3 \"this is the solution for challenge 3\" +" + +challenge_no="$1" +submitted_solution="$2" + +[ -z "$submitted_solution" ] && echo "$help_text" && exit + +echo "Submitting \"$submitted_solution\" as the solution to challenge #$challenge_no..." + +my_ip="$(ip addr | grep 'scope global eth0' | cut -f6 -d ' ')" + +scoreboard="$(nc -q0 -d {{ HOST_IP }} {{ INFO_PORT }})" +prev_score="$( + echo "$scoreboard" \ + | grep "$my_ip" +)" +max_score="$(echo "$scoreboard" | head -n1)" +current_score="" + +echo "SOLUTION $challenge_no $submitted_solution" \ +| nc -q0 {{ HOST_IP }} {{ SLBR_PORT }} + +# give the server ample time to process the request +echo "Verifying..." +sleep 5 + +# we need to request the scoreboard twice here for the score to properly update +nc -q0 -d {{ HOST_IP }} {{ INFO_PORT }} > /dev/null +current_score="$( + nc -q0 -d {{ HOST_IP }} {{ INFO_PORT }} \ + | grep "$my_ip" +)" + +if [ "$prev_score" != "$current_score" ] +then + echo "w00t! You got the right answer!!!" + num_correct="$( + echo $current_score \ + | grep -oE '[0-9],' \ + | wc -l + )" + echo "$num_correct correct answers so far" + if [ "$num_correct" = "$max_score" ] + then + printf "\e[0;92]" + echo "Hey! thats all $max_score! that means YOU WIN!" + echo "!!!!!!!CONGRATULATIONS!!!!!!!" + printf "\e[0;91m THIS CONTAINER WILL NOW SELF DESTRUCT\e[0m\n" + fi +else + echo "|X_x| Your answer was rejected!!!." +fi diff --git a/image/clues/test.txt b/image/clues/test.txt new file mode 100644 index 0000000..763f5c8 --- /dev/null +++ b/image/clues/test.txt @@ -0,0 +1 @@ +ubj qb lbh fcryy ahzoref? diff --git a/image/insults.txt b/image/insults.txt new file mode 100644 index 0000000..53d9dbb --- /dev/null +++ b/image/insults.txt @@ -0,0 +1,12 @@ +Oops, looks like you misspelt something >:) +Where did you learn to type? +Are you on drugs? +My pet ferret can type better than you! +You type like i drive. +Do you think like you type? +stty: unknown mode: doofus +Maybe if you used more than just two fingers... +Have you considered trying to match wits with a rutabaga? +I've seen penguins that can type better than that. +Game over, man, game over. +https://ddg.gg/?q=free+online+typing+lessons diff --git a/image/package.list b/image/package.list new file mode 100644 index 0000000..40d43e7 --- /dev/null +++ b/image/package.list @@ -0,0 +1,9 @@ +netcat-openbsd +emacs-nox +curl +nano +vim +tmux +man +xxd +openssh-server diff --git a/image/rules.md b/image/rules.md new file mode 100644 index 0000000..729aa0d --- /dev/null +++ b/image/rules.md @@ -0,0 +1,33 @@ +# PLAYING THE GAME +- Each game has a series of challenges to complete, users are provided with a + bash shell and a folder of clues. +- The challenges are numbered 1-X. +- Users must submit the solutions to each challenge to the admin, the first + user to submit correct solutions to each challenge will win the game. +- If at any point the user enters an invalid shell command, they will be + eliminated from the game. +- If you think you have the solution for a challenge, submit your answer using + the `submit` command in the format `submit ` +- For example, you would run `submit 3 "the color pink"` to submit "the color + pink" as your solution for challenge #3 (quotes would not be included, and +are subject to normal bash escaping rules) +- At any time, you can run the `progress` command to see your progress in + completing the challenges + +# DA RULES +- You may use the internet +- You may write scripts +- You may not use sh, you must use bash +- You may not install more packages +- You may not attempt to obtain root access +- You may not communicate with other players +- You may not interact or tamper with the containers or environments of other + users +- You may not interact or tamper with your own container +- You may not break out of your container and/or access the host system in any + way +- You may not nullify or remove the "Suicide Linux" mechanic in any way +- You may use any package already installed on the system provided its use does + not break any of the other rules +- For the love of god don't build a bot that just brute forces random strings + to try and get a correct answer diff --git a/image/slbr.env b/image/slbr.env new file mode 100644 index 0000000..2a58000 --- /dev/null +++ b/image/slbr.env @@ -0,0 +1,13 @@ +handler_command='command_not_found_handle is a function +command_not_found_handle () +{ + echo "DEAD" | nc illegaldrugs.net 1337 & printf "\e[0;94m%s\e[0m\n" "$(shuf -n1 /insults.txt)"; + printf "\e[0;91m\e[1mYOU HAVE BEEN ELIMINATED!\e[0m\n" +}' +# we can use the output format of `type` to determine if we are in a bash shell +# or not, as well an ensuring the instant death mechanic is enabled +if [ "$(type command_not_found_handle)" != "$handler_command" ] +then + echo "no." + exit +fi diff --git a/image/suicidebash.bashrc b/image/suicidebash.bashrc new file mode 100644 index 0000000..26351af --- /dev/null +++ b/image/suicidebash.bashrc @@ -0,0 +1,24 @@ +# If not running interactively, don't do anything +[ -z "$PS1" ] && return + +# set a fancy prompt (non-color, overwrite the one in /etc/profile) +PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ ' + +# SLBR modifications + +bind -u complete + +function command_not_found_handle { + echo "DEAD" | nc {{ HOST_IP }} {{ SLBR_PORT }} & + printf "\e[0;94m%s\e[0m\n" "$(shuf -n1 /insults.txt)" + printf "\e[0;91m\e[1mYOU HAVE BEEN ELIMINATED!\e[0m\n" +} + +function log_command { + echo "$(history 1)" | nc -q0 {{ HOST_IP }} {{ LOG_PORT }} +} + +readonly -f command_not_found_handle +readonly -f log_command +readonly PROMPT_COMMAND=log_command +readonly ENV="/etc/slbr.env"; export ENV diff --git a/log.txt b/log.txt new file mode 100644 index 0000000..e69de29 diff --git a/new_image.sh b/new_image.sh new file mode 100755 index 0000000..fc0959b --- /dev/null +++ b/new_image.sh @@ -0,0 +1,59 @@ +username="$1" +pubkey_path="$2" +slbr_port="1337" +info_port="1338" +log_port="1339" +user_sshd_port="5000" +container_id="" +container_ip="" +host_ip="" + +[ -z "$1" ] && echo "please provide a username." && exit +[ -z "$2" ] && echo "please provide a pubkey file." && exit + +get_user_sshd_port() { + listening_ports="$(ss -tuln \ + | awk '{print $5}' \ + | rev \ + | cut -f1 -d ":" \ + | rev + )" + if echo "$listening_ports" | grep -q "$user_sshd_port" + then + user_sshd_port="$(( user_sshd_port + 1 ))" + get_user_sshd_port + else + echo "$user_sshd_port" + fi + +} +get_user_sshd_port + +cp "$pubkey_path" image/pubkey +host_ip="$( + ip -br -4 addr \ + | grep eth0 \ + | awk '{print $3}' \ + | cut -f 1 -d '/' +)" + +docker build \ + -t slbr:$username \ + --build-arg USERNAME=$username \ + --build-arg HOST_IP=$host_ip \ + --build-arg SLBR_PORT=$slbr_port \ + --build-arg INFO_PORT=$info_port \ + --build-arg LOG_PORT=$log_port \ + --build-arg SSHD_PORT=$user_sshd_port \ + --label "SLBR User Container" \ + ./image + +rm image/pubkey +echo "Starting container..." +container_id="$(docker run -p $user_sshd_port:$user_sshd_port -h slbr -d "slbr:$username")" +container_ip="$( + docker container inspect "$container_id" \ + | jq '.[0].NetworkSettings.Networks.bridge.IPAddress' +)" +echo "Done!" +echo "connect using: ssh $username@$host_ip -p $user_sshd_port" diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..4cc131e --- /dev/null +++ b/readme.md @@ -0,0 +1,51 @@ +# SUICIDE LINUX BATTLE ROYALE +It's back, baby + +coming soon! check #slbr on irc for updates! + +typo? you're out. + +when you play, you'll be dropped in a fresh vm. on the way to your goal, any +mistyped command will delete your vm and you'll be eliminated. + +contact ~ben or sose on tilde.chat (in the #slbr channel) to sign up. + +## How it works +- Each game has a series of challenges to complete, users are provided with a + bash shell and a folder of clues. +- The challenges are numbered 1-X. +- Users must submit the solutions to each challenge to the admin, the first + user to submit correct solutions to each challenge will win the game. +- If at any point the user enters an invalid shell command, they will be + eliminated from the game. + +## Configuration +- Configuring a new set of challenges is as simple as editing the + `solutions.txt` file with their solutions, and providing new clues in the +`./image/clues` folder. +- In the `solutions.txt` file, the line number of each solution corresponds to + the challenge it is the solution for. Solutions may not take up multiple lines. + +## Prerequisites +- docker +- pslist +- BSD style netcat (`netcat-openbsd` on debian) + +## Setup +- Make sure your ports 1337, 1338 and 1339 are not exposed to the internet, as + SLBR will use them internally +- `mkdir /home/slbr-admin` +- `useradd slbr-admin -d /home/slbr-admin` +- `usermod -a -G docker slbr-admin` +- `sudo su slbr-admin` +- `newgrp docker` +- `cd` +- `git clone https://tildegit.org/tildeverse/SLBR` +- `cd SLBR` +- You are now ready to manage an slbr game + +## Running the game +- run `./start.sh` to start the listeners for various game events +- run `./new_image.sh ` to create a new user +- All game events and user commands will be logged to log.txt +- You can `./reset.sh` to cleanup after a game has finished diff --git a/reset.sh b/reset.sh new file mode 100755 index 0000000..922c279 --- /dev/null +++ b/reset.sh @@ -0,0 +1,16 @@ +#!/bin/sh +num_challenges="$( + wc -l solutions.txt \ + | cut -f1 -d ' ' +)" +docker ps \ + -f label="SLBR User Container" \ + --format "{{.ID}}" \ +| xargs docker rm -f +docker images -a \ +| grep ^slbr \ +| awk '{print $3}' \ +| xargs docker rmi -f + +printf "$num_challenges\n" > scoreboard.txt +printf "" > log.txt diff --git a/rules.md b/rules.md new file mode 120000 index 0000000..ba11edc --- /dev/null +++ b/rules.md @@ -0,0 +1 @@ +image/rules.md \ No newline at end of file diff --git a/scoreboard.txt b/scoreboard.txt new file mode 100644 index 0000000..7ed6ff8 --- /dev/null +++ b/scoreboard.txt @@ -0,0 +1 @@ +5 diff --git a/servers/deathlistener.sh b/servers/deathlistener.sh new file mode 100755 index 0000000..1adb8af --- /dev/null +++ b/servers/deathlistener.sh @@ -0,0 +1,86 @@ +#!/bin/sh +# we will need the suicide bashrc to continuously write DEAD to the nc socket +# in case two messages are sent at the same time. + +# this will not work if your slbr admin is not in the "docker" group + +PORT="$1" +LOG="log.txt" + +[ -z "$1" ] && echo "port?" && exit + +docker_subnet_mask="$( + docker network inspect bridge \ + | jq '.[0].IPAM.Config[0].Subnet' \ + | tr -d '"' \ + | cut -f2 -d '/' +)" + +handle_packet() { + packet="$1" + message="$(echo $packet | cut -f3 -d '|')" + + address="$( + echo "$packet" \ + | cut -f2 -d '|' \ + | cut -f3 -d ' ' + )" + address="$address/$docker_subnet_mask" + if [ "$message" = "DEAD" ] + then + echo "Killing user with ip $address" >> $LOG + container_name="$( + docker network inspect bridge \ + | jq \ + '.[0].Containers + | map(select(.IPv4Address == "'"$address"'")) + | .[0].Name' \ + | tr -d '"' + )" + docker rm -f "$container_name" + echo "killed container $container_name" >> $LOG + elif [ "$(echo $message | cut -f1 -d ' ')" = "SOLUTION" ] + then + submitted_solution="$(echo $message | cut -f3- -d ' ')" + challenge_no="$(echo $message | cut -f2 -d ' ')" + correct_solution="$(sed $challenge_no'q;d' solutions.txt)" + sed_escaped_address="$(echo $address | sed 's/\./\\./g')" + + echo "$address submitted solution for challenge $challenge_no: $submitted_solution" >> $LOG + if [ "$submitted_solution" = "$correct_solution" ] + then + num_correct="" + echo "Solution was correct!" >> $LOG + + grep -qE "^$address" scoreboard.txt \ + || echo "$address" >> scoreboard.txt + + grep -qE "^$address .*$challenge_no," scoreboard.txt \ + || sed -i "s|$sed_escaped_address|$address $challenge_no,|" scoreboard.txt + + num_correct="$( + grep -m1 -E "^$address" scoreboard.txt \ + | grep -oE '[0-9],' \ + | wc -l + )" + total_solutions="$(cat solutions.txt | wc -l)" + if [ $num_correct = $total_solutions ] + then + echo "$address HAS WON THE GAME!!!" >> $LOG + sleep 5 + echo "Killing all continers..." >> $LOG + docker ps -aq --filter label="SLBR User Container" | xargs docker rm -f + exit + fi + + echo "$num_correct correct answers so far" >> $LOG + fi + fi +} + +echo "game server listening on $PORT" +while true +do + packet=$(nc -w 1 -W 1 -vlp $PORT 2>&1) + handle_packet "$(echo "$packet" | head -n 3 | tr '\n' '|')" & +done diff --git a/servers/gamelog.sh b/servers/gamelog.sh new file mode 100755 index 0000000..de49ef4 --- /dev/null +++ b/servers/gamelog.sh @@ -0,0 +1,6 @@ +#!/bin/sh +PORT="$1" +LOG="log.txt" +[ -z "$1" ] && echo "port?" && exit +echo "log server listening on $PORT" >> $LOG +nc -w1 -W1 -vlkp $PORT >> $LOG 2>&1 diff --git a/servers/scoreboard.sh b/servers/scoreboard.sh new file mode 100755 index 0000000..201369a --- /dev/null +++ b/servers/scoreboard.sh @@ -0,0 +1,10 @@ +#!/bin/sh +PORT="$1" +[ -z "$1" ] && echo "port?" && exit +echo "scoreboard server listening on $PORT" +while true +do + cat scoreboard.txt \ + | nc -w 1 -W 1 -lp "$PORT" + echo "Got scoreboard request." +done diff --git a/solutions.txt b/solutions.txt new file mode 100644 index 0000000..b2f931a --- /dev/null +++ b/solutions.txt @@ -0,0 +1,5 @@ +one +two +three +four +five diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..295230a --- /dev/null +++ b/start.sh @@ -0,0 +1,22 @@ +#!/bin/sh +LOG="log.txt" +death_pid="" +score_pid="" +log_pid="" + +cleanup() { + rkill $death_pid >/dev/null 2>&1 + rkill $score_pid >/dev/null 2>&1 + rkill $log_pid >/dev/null 2>&1 +} +trap 'cleanup' 1 2 3 9 + +servers/deathlistener.sh 1337 & death_pid="$!" +echo "death listener pid: $death_pid" >> "$LOG" +servers/scoreboard.sh 1338 & score_pid="$!" +echo "scoreboard server pid: $score_pid" >> "$LOG" +servers/gamelog.sh 1339 & log_pid="$!" +echo "log server pid: $log_pid" >> "$LOG" + +printf "\e[0;91mReading log, exit to stop game\e[0m\n" +tail -f "$LOG"