name: inverse layout: true class: center, middle, inverse --- layout: false .left-column[ ## Hacking Homebrew ] .right-column[ Marcus Young - Blog: https://marcyoung.us - Github: https://www.github.com/myoung34 - This slideshow: https://marcyoung.us/pytn-2020/#1 ] --- # Introduction ## Senior Cloud Security Engineer at Zapier ## Maker of unnecessary things --- ![](images/rocket.png) --- ![](images/barcade.jpg) --- ![](https://marcyoung.us/images/hulksmash.gif) --- layout: false .left-column[ ## What is IoT ] .right-column[ The Internet of things (IoT) is a system of interrelated computing devices, mechanical and digital machines are provided with unique identifiers (UIDs) and the ability to transfer data over a network without requiring human-to-human or human-to-computer interaction. ] --- layout: false .left-column[ ## What is IoT ### It's Crap ] .right-column[ ![](images/candle.jpg) ] --- layout: false .left-column[ ## What is IoT ### It's Crap ### It's Other Crap ] .right-column[ ![](images/teeth.jpg) ] --- layout: false .left-column[ ## What is IoT ### It's Crap ### It's Other Crap ### Sometimes it's useful ] .right-column[ ![](images/cat.png) ] --- layout: false .left-column[ ## What is IoT ### It's Crap ### It's Other Crap ### Sometimes it's useful ### It's Crap ] .right-column[ ![](images/bidet.jpg) .footnote[.red[*] literally] ] --- layout: false .left-column[ ## What is IoT ### It's Crap ### It's Other Crap ### Sometimes it's useful ### It's Crap ### Security ] .right-column[ ![](images/iot.jpg) ] --- layout: false .left-column[ ## ELI5 Hacking ### What People Think I Do ] .right-column[ ![](images/hacking1.gif) ] --- layout: false .left-column[ ## ELI5 Hacking ### What People Think I Do ### What I Actually Do ] .right-column[ ![](images/hacking2.gif) ] --- layout: false .left-column[ ## Beer: a primer ] .right-column[ ] --- layout: false .left-column[ ## Beer: a primer ### Grass ] .right-column[ ![](images/barley.jpg) ] --- layout: false .left-column[ ## Beer: a primer ### Grass ### Hops ] .right-column[ ![](images/hops.jpg) ] --- layout: false .left-column[ ## Beer: a primer ### Grass ### Hops ### Water + Yeast ] .right-column[ ![](images/yeast.jpg) ] --- layout: false .left-column[ ## Beer: a primer ### Grass ### Hops ### Water + Yeast ### Mash It ] .right-column[ ![](images/mash.jpg) ] --- layout: false .left-column[ ## Beer: a primer ### Grass ### Hops ### Water + Yeast ### Mash It ### Boil it ] .right-column[ ![](images/boil.png) ] --- layout: false .left-column[ ## Beer: a primer ### Grass ### Hops ### Water + Yeast ### Mash It ### Boil it ### Ferment It ] .right-column[ ![](images/ferment.gif) ] --- layout: false .left-column[ ## Beer: a primer ### Grass ### Hops ### Water + Yeast ### Mash It ### Boil it ### Ferment It ### Carbonate It ] .right-column[ ![](images/keg.jpg) ] --- layout: false .left-column[ ## Lets Begin: The Bluetooth Hydrometer ### What is gravity ] .right-column[ ![](images/hydro1.jpg) ] --- layout: false .left-column[ ## Lets Begin: The Bluetooth Hydrometer ### What is gravity ### Invasive measurement ] .right-column[ ![](images/hydro2.jpg) ] --- layout: false .left-column[ ## Lets Begin: The Bluetooth Hydrometer ### What is gravity ### Invasive measurement ### Nonnvasive measurement ] .right-column[ ![](images/refract.jpg) ] --- layout: false .left-column[ ## Lets Begin: The Bluetooth Hydrometer ### What is gravity ### Invasive measurement ### Nonnvasive measurement ### Smart measurement ] .right-column[ ![](images/tilt.jpg) ] --- layout: false .left-column[ ## Why do this? ] .right-column[ ![](images/tiltpi.png) - The "[official dashboard](https://github.com/baronbrew/TILTpi)" is gross - I dont like using different "apps" for different things - Everyone has a biased flavor of this. - "We" can do better. ] --- layout: false .left-column[ ## iBeacons ] .right-column[ ![](images/ibeacon.png) ] --- layout: false .left-column[ ## iBeacons ### Helpful Info ] .right-column[ Example tilt hydrometer sensor data message from hcidump -R: ```remark > 04 3E 27 02 01 00 00 5A 09 9B 16 A3 04 1B 1A FF 4C 00 02 15 A4 95 BB 10 C5 B1 4B 44 B5 12 13 70 F0 2D 74 DE 00 44 03 F8 C5 C7 ``` ```remark 04: HCI Packet Type HCI Event 3E: LE Meta event 27: Parameter total length (39 octets) 02: LE Advertising report sub-event 01: Number of reports (1) 00: Event type connectable and scannable undirected advertising 00: Public address type 5A 09 9B 16 A3 04: address 1B: length of data field (27 octets) 1A: length of first advertising data (AD) structure (26) FF: type of first AD structure - manufacturer specific data 4C 00: manufacturer ID - Apple iBeacon 02: type (constant, defined by iBeacon spec) 15: length (constant, defined by iBeacon spec) A4 95 BB 10 C5 B1 4B 44 B5 12 13 70 F0 2D 74 DE: device UUID 00 44: major - temperature (in degrees fahrenheit) 03 F8: minor - specific gravity (x1000) C5: TX power in dBm C7: RSSI in dBm ``` .footnote[.red[*] https://kvurd.com/blog/tilt-hydrometer-ibeacon-data-format/] ] --- layout: false .left-column[ ## iBeacons ### Helpful Info ### First pass ] .right-column[ ![](images/ibeacon.png) Let's steal a live packet What do we know? We know the mac address. If We can find a packet with that, we can do whatever we want in a vacuum. https://raw.githubusercontent.com/myoung34/tilty/pytn/1/blescan.py ] --- ![](images/pkt1.png) --- layout: false .left-column[ ## iBeacons ### Helpful Info ### First pass ### We have a packet ] .right-column[ We now have a full packet with all the info we need. ``` pkt = b'\x04>*\x02\x01\x03\x01w\t\xbc\xd0W\xef\x1e\x02\x01\x04 \x1a\xffL\x00\x02\x15\xa4\x95\xbb0\xc5\xb1KD\xb5\x12 \x12p\xf0-t\xde\x00B\x03\xf7\xc5\xa7' ``` Let's use TDD so we can iterate fast and start parsing. https://raw.githubusercontent.com/myoung34/tilty/pytn/2/blescan.py https://raw.githubusercontent.com/myoung34/tilty/pytn/2/test_ble.py ] --- layout: false .left-column[ ## iBeacons ### Helpful Info ### First pass ### We have a packet ### We have a known list of UUID ] .right-column[ We happen to have a list of known UUID's from the company https://github.com/atlefren/pytilt/blob/master/pytilt.py TILT_DEVICES = { 'a495bb30c5b14b44b5121370f02d74de': 'Black', 'a495bb60c5b14b44b5121370f02d74de': 'Blue', 'a495bb20c5b14b44b5121370f02d74de': 'Green', ...snip... } Lets pull only from that whitelist. https://raw.githubusercontent.com/myoung34/tilty/pytn/3/blescan.py https://raw.githubusercontent.com/myoung34/tilty/pytn/3/test_ble.py ] --- layout: false .left-column[ ## iBeacons ### Helpful Info ### First pass ### We have a packet ### We have a known list of UUID ### All the data ] .right-column[ All thats left now is to get the temp and the gravity from major/minor https://raw.githubusercontent.com/myoung34/tilty/pytn/4/blescan.py https://raw.githubusercontent.com/myoung34/tilty/pytn/4/test_ble.py ] --- layout: false .left-column[ ## iBeacons ### Helpful Info ### First pass ### We have a packet ### We have a known list of UUID ### All the data ### Whats next ] .right-column[ ![](images/datadog.png) https://github.com/myoung34/tilty https://pypi.org/project/Tilty/ https://hub.docker.com/r/myoung34/tilty ] --- layout: false .left-column[ ## The Plaato Keg ### Scale ] .right-column[ ![](images/plaato1.gif) ] --- layout: false .left-column[ ## The Plaato Keg ### Scale ### Leak Detector ] .right-column[ ![](images/plaato2.gif) ] --- layout: false .left-column[ ## The Plaato Keg ### Scale ### Leak Detector ### "Neat App" ] .right-column[ ![](images/plaato3.gif) ] --- layout: false .left-column[ ## Why do this ] .right-column[ I want control. ] --- layout: false .left-column[ ## Why do this ] .right-column[ I want control. - The App Sucks ] --- layout: false .left-column[ ## Why do this ] .right-column[ I want control. - The App Sucks - Beta features not complete yet ] --- layout: false .left-column[ ## Why do this ] .right-column[ I want control. - The App Sucks - Beta features not complete yet - Ain't no one paying for your "cloud" ] --- layout: false .left-column[ ## Lets Start Recon --- ] .right-column[ ] --- layout: false .left-column[ ## Lets Start Recon ### Take it apart ] .right-column[ ![](images/keg1.jpg) ] --- layout: false .left-column[ ## Lets Start Recon ### Take it apart ] .right-column[ ![](images/keg2.png) ] --- layout: false .left-column[ ## Lets Intercept ### The Pineapple ] .right-column[ ] --- layout: false .left-column[ ## Lets Intercept ### The Pineapple ### PCAP Inspection ] .right-column[ ] --- layout: false .left-column[ ## Lets Intercept ### The Pineapple ### PCAP Inspection ### DNS Spoof ] .right-column[ ] --- layout: false .left-column[ ## Lets Intercept ### The Pineapple ### PCAP Inspection ### DNS Spoof ### Recon ] .right-column[ Lets recap: - What is Blynk? https://blynk.io - Why plaato.blink.cc ? - Port 80 ? - [Any libraries we can test out](https://github.com/blynkkk/lib-python)? (we have an auth code, what is it waiting on). ] --- # Lets Pretend to be a blynk Device ![](images/blynk1.png) --- # Lets Pretend to be a blynk Device ![](images/blynk2.png) --- layout: false .left-column[ ## What do we know ### Who it wants to talk to and how ] .right-column[ Based on the pcap we know it wants to talk to plaato.blynk.cc on port 80 with no SSL. It's sending some sort of auth token and waiting. ] --- layout: false .left-column[ ## What do we know ### Who it wants to talk to and how ### Hardware protocol ] .right-column[ Blynk transfers binary messages between the server and the hardware with the following structure: | Command | Message Id | Length/Status | Body | |:-------------:|:-------------:|:---------------:|:--------:| | 1 byte | 2 bytes | 2 bytes | Variable | .footnote[.red[*] https://github.com/blynkkk/blynk-server#blynk-protocol] ] --- layout: false .left-column[ ## What do we know ### Who it wants to talk to and how ### Hardware protocol ### Auth Successful Packet ] .right-column[ TESTS ARE AWESOME ```remark def test_authenticate(self, cb, mocker): mocker.patch.object(cb, 'send', return_value=None) mocker.patch.object(cb, 'receive', return_value=b'\x00\x00\x02\x00\xc8') cb._authenticate() assert cb._state == cb.AUTHENTICATED ``` .footnote[.red[*] https://github.com/blynkkk/lib-python/blob/master/test/test_blynk_connection.py#L120] ] --- layout: false .left-column[ ## Lets fake a server ] .right-column[ ```remark def parse_response(rsp_data, msg_buffer): msg_args = [] msg_head_length = 5 msg_type, msg_id, h_data = struct.unpack('!BHH', rsp_data[:msg_head_length]) if msg_type in [2, 20, 15, 17, 41]: msg_body = rsp_data[msg_head_length: msg_head_length + h_data] msg_args = [itm.decode('utf-8') for itm in msg_body.split(b'\0')] else: pass return msg_type, msg_id, h_data, msg_args socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) socket.bind(('0.0.0.0', 80)) socket.listen(1) conn, addr = socket.accept() with conn: while True: try: rsp_data = conn.recv(1024) data = parse_response(rsp_data, 1024) if data: pprint(data) if data[0] in [2, 29]: # auth? conn.sendall(b'\x00\x00\x01\x00\xc8') # -> .hex() => '00000100c8' except KeyboardInterrupt: socket.close() sys.exit() except Exception as e: socket.close() raise e ``` ] --- layout: false .left-column[ ## Lets fake a server ### Prove it ] .right-column[ ] --- ## Whats next --- layout: false .left-column[ ## Post Mortem ] .right-column[ ] --- layout: false .left-column[ ## Post Mortem ### Was that hacking? ] .right-column[ ] --- layout: false .left-column[ ## Post Mortem ### Was that hacking? ### Prevention ] .right-column[ ] --- layout: false .left-column[ ## Post Mortem ### Was that hacking? ### Prevention ### Responsible Disclosure ] .right-column[ ] --- # Questions ---