Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Help wanted: Using L2CAP connection to read from and write settings to AirPods #215

Open
d4rken opened this issue Jul 12, 2024 · 25 comments
Labels
enhancement Add a new feature of improve an existing feature

Comments

@d4rken
Copy link
Member

d4rken commented Jul 12, 2024

Our sister project for Windows, MagicPods, has reverse engineered reading and writing data to AirPods.

  • The good news is that this actually works and I could reproduce it on a desktop computer running linux. So support for changing settings (ANC mode, microphone etc.) and getting more reliable information would be possible

  • The bad news is that I'm unable to get the connection established on an Android device.

If anyone can figure out how to establish the connection, then I could start on adding support for this.


AirPods require a Bluetooth connection that uses the L2CAP protocol. I was unable to establish this connection on a Pixel 6 running Android 15 and Pixel 8 running Android 14.

This is what I tried so far

Use the public createInsecureL2capChannel method

06:50:53.800 bt_l2cap                             E  packages/modules/Bluetooth/system/stack/l2cap/l2c_api.cc:417 - L2CA_RegisterLECoc: Invalid BLE PSM value, PSM: 0x1001
06:50:53.800 bt_stack                             E  [ERROR:gap_conn.cc(255)] GAP_ConnOpen: Failure registering PSM 0x1001

but that seems to have limits on the PSM value that you can provide

https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Bluetooth/system/stack/l2cap/l2c_api.cc;l=416?q=%22Invalid%20BLE%20PSM%20value%22

#define L2C_IS_VALID_LE_PSM(psm) (((psm) > 0x0000) && ((psm) < 0x0100))

Then I tried the hidden method createInsecureL2capSocket

https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Bluetooth/framework/java/android/bluetooth/BluetoothDevice.java;l=2924?q=createInsecureL2capSocket&sq=&ss=android%2Fplatform%2Fsuperproject%2Fmain

Accessing hidden method Landroid/bluetooth/BluetoothDevice;->createInsecureL2capSocket(I)Landroid/bluetooth/BluetoothSocket; (max-target-o, reflection, denied)

Which needed some convincing to execute (https://github.com/LSPosed/AndroidHiddenApiBypass) but

07:44:04.151 bluetooth                            W  packages/modules/Bluetooth/system/stack/l2cap/l2c_fcr.cc:1603 - l2c_fcr_chk_chan_modes: L2CAP - Peer does not support our desired channel types
07:44:04.151 bt_btif_sock                         I  packages/modules/Bluetooth/system/btif/src/btif_sock.cc:164 - btif_sock_connection_logger: address=xx:xx:xx:xx:e4:0a, state=4, role=2, server_name=, channel=4097
07:44:04.151 bluetooth                            I  packages/modules/Bluetooth/system/btif/src/btif_sock_l2cap.cc:363 - send_app_err_code: Sending l2cap failure reason socket_id:34 reason code:1
07:44:04.151 bt_btif_sock                         I  packages/modules/Bluetooth/system/btif/src/btif_sock.cc:164 - btif_sock_connection_logger: address=xx:xx:xx:xx:e4:0a, state=5, role=2, server_name=, channel=4097
07:44:04.151 bluetooth                            I  packages/modules/Bluetooth/system/btif/src/btif_sock_l2cap.cc:234 - btsock_l2cap_free_l: Application has already closed l2cap socket socket_id:34

android.bluetooth.BluetoothSocketException: Connection failed for unknown reason 

still no dice.

A friend has tried simulating AirPods on a Desktop computer and was able to connect to that mock via createInsecureL2capSocket from an Android phone, but wasn't able to do this with actual AirPods.

@d4rken d4rken added the enhancement Add a new feature of improve an existing feature label Jul 12, 2024
@d4rken
Copy link
Member Author

d4rken commented Jul 12, 2024

See here for details from the MagicPods project:

and here is a python script that can connect to AirPods from a desktop computer:

import bluetooth

# Change to MAC address of your AirPods
address = "MAC_ADDRESS"

aap_service = "74EC2172-0BAD-4D01-8F77-997B2BE0722A"
aap_port = 0x1001

# Commands taken from https://github.com/steam3d/MagicPodsCore/blob/master/src/aap/Aap.h
cmd_handshake = b"\x00\x00\x04\x00\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00"
cmd_off = b"\x04\x00\x04\x00\x09\x00\x0d\x01\x00\x00\x00"
cmd_on = b"\x04\x00\x04\x00\x09\x00\x0d\x02\x00\x00\x00"
cmd_transparency = b"\x04\x00\x04\x00\x09\x00\x0d\x03\x00\x00\x00"

services = bluetooth.find_service(address=address)

service = [s for s in services if s["service-classes"] == [aap_service]]

if not service:
    print("Device does not have AAP service")
    exit()

sock = bluetooth.BluetoothSocket(bluetooth.L2CAP)

sock.connect((address, aap_port))

print("Connected to AirPods")
print("Sending handshake...")

sock.send(cmd_handshake)

while True:
    # Ignore the responses because I am unsure what they mean
    # res = sock.recv(1024)
    # print(f"Response: {res.hex()}")

    print("Select command:")
    print("1. Turn off")
    print("2. Turn on")
    print("3. Toggle transparency")
    print("4. Exit")

    cmd = input("Enter command: ")

    if cmd == "1":
        sock.send(cmd_off)
    elif cmd == "2":
        sock.send(cmd_on)
    elif cmd == "3":
        sock.send(cmd_transparency)
    elif cmd == "4":
        break

sock.close()

@d4rken d4rken pinned this issue Jul 12, 2024
@d4rken
Copy link
Member Author

d4rken commented Jul 12, 2024

Enabling this could enable/fix #38,#186,#187,#191,#204,#212

@fynngodau

This comment was marked as outdated.

@vulpes2
Copy link

vulpes2 commented Sep 2, 2024

Tried playing with this and I didn't get very far unfortunately. Was able to establish the connection on a desktop computer too, but didn't manage to get anything to happen on Android 14.

The names for createInsecureL2capChannel and createL2capChannel are pretty misleading, if you look at the source closely, you can see they're for BLE only, not BR/EDR. The 0x1001 PSM was reported as invalid because BLE uses SPSM instead of PSM, which are only two bytes in size. It turns out both createL2capSocket and createInsecureL2capSocket are hidden APIs, and I didn't have xposed on my phone, so I couldn't get any further. Maybe some HCI packet logs will help figuring out why the connection is failing.

@d4rken
Copy link
Member Author

d4rken commented Sep 2, 2024

Thanks for trying @vulpes2 🍻. I had some hope that Android 15 would add more APIs here, but didn't see anything in the changelogs so far 😦

@vulpes2
Copy link

vulpes2 commented Sep 2, 2024

Seems like L2CAP on BR/EDR was never supported for 3rd party use on Android, which is kind of surprising. Not sure why did they make a new API to create L2CAP sockets for LE, but still wouldn't allow it over BR/EDR to this day.

@xerootg
Copy link

xerootg commented Sep 3, 2024

Any chance someone with an understanding of L2CAP (I'm certainly not an L2CAP understander) could do a PR into AOSP?

@vulpes2
Copy link

vulpes2 commented Sep 4, 2024

The function exists, you are just not allowed to use it from a normal app. Neither Android nor iOS allow you to create raw L2CAP connections on BR/EDR, but obviously both use them heavily at the system level.

@DragonSWDev
Copy link

@vulpes2 So it might be not possible at all unless you root your device?

@vulpes2
Copy link

vulpes2 commented Sep 10, 2024

Effectively yes, LSPosed requires Magisk.

@steam3d
Copy link

steam3d commented Sep 10, 2024

I reverse engineered the beats app too. The difference between AirPods and Beats headphones (both on H1 and H2 chips) is that Beats headphones expose the RFCOMM interface, but it seems that Apple intentionally turned RFCOMM off.

@kavishdevar
Copy link
Contributor

kavishdevar commented Sep 14, 2024

I have also been working on this myself (without knowing magicpods existed :| ), but have had no luck on android (unlike linux)... i am planning to publish my findings (and a small app for linux) that i found while reverse engineering using the packetlogger on mac (set ANC mode, etc.), but i think it's useless for now considering the psm value thing..

@vulpes2
Copy link

vulpes2 commented Sep 14, 2024

I've been poking at this for a little while now, and have managed to map out most of the packet formats, opcodes and configuration keys as of version 6F8. Validating everything is not possible for me as I don't have access to the AirPods Max and Beats devices at the moment. Haven't had time to look into the recent 7A294 fw either, it has the new head gesture stuff which are not super critical for desktop use. When I have more time I'll write up some docs on the protocol, and provide a cross platform reference library to handle the connection logic.

@steam3d
Copy link

steam3d commented Sep 14, 2024

@vulpes2 I have fully implemented the work of all functions of all versions of airpods in MagicPods, it works for any firmware version. The main problem is that the functions are hardcoded, and not obtained dynamically as apple does.

Apple uses several configuration requests, but I still don't understand the logic behind it requests for them. It seems like it depends on the protocol version.

@vulpes2
Copy link

vulpes2 commented Sep 14, 2024

The capability stuff and notification type bitfield is a total nightmare to deal with, I have managed to decipher a few of them, but not enough to make a difference just yet. By configuration requests, do you mean opcode 0x9, or the initial capability response with opcode 0x2?

@kavishdevar
Copy link
Contributor

@d4rken could you provide a simple apk with xposed with 3 simple toggles for ANC modes? or maybe even publish the code? I'll try it on my phone and try experimenting..

@kavishdevar
Copy link
Contributor

kavishdevar commented Sep 15, 2024

mine doesn't work using the lsposed hiddenapibypass, the socket.connect() takes for ever and doesn't return with an error or something.

Here's my error when using createL2capSocket:

2024-09-16 01:56:33.390  4950-5018  bt_btif_sock            com.android.bluetooth                I  btsock_connect: 
2024-09-16 01:56:33.390  4950-5018  bt_btif_sock            com.android.bluetooth                I  btif_sock_connection_logger: address=xx:xx:xx:xx:05:5b, state=2, role=2, server_name=00000000-0000-0000-0000-000000000000, channel=4097
2024-09-16 01:56:33.397  4950-5018  bluetooth               com.android.bluetooth                I  btsock_l2cap_alloc_l: Allocated l2cap socket structure socket_id:105
2024-09-16 01:56:33.406  4950-5050  bt_l2cap                com.android.bluetooth                I  L2CA_Register: L2CAP Registered service classic PSM: 0x1001
2024-09-16 01:56:33.407  4950-5050  bluetooth               com.android.bluetooth                W  l2c_fcr_chk_chan_modes: L2CAP - Peer does not support our desired channel types

Also, interestingly, this says public
image

@kavishdevar
Copy link
Contributor

adolfintel/OpenPods#58 (comment)

TL;DR Got it working by commenting a few lines in the bluetooth stack's code for managing l2c connections (the ones which were blocking when sending data to the airpods, and getting no response). I made a small app, using the hidden method to create br/edr l2c socket. i can change listening mode, get battery data, and in-ear detection works, for now.

@steam3d
Copy link

steam3d commented Sep 25, 2024

I've started working on AAP protocol documentation in python, decided to abandon c++ in favor of simplicity.
https://github.com/steam3d/MagicPodsCore

@kavishdevar
Copy link
Contributor

I've created a issue on issuetracker for this! https://issuetracker.google.com/issues/371713238. please upvote :)

@aikooo7
Copy link

aikooo7 commented Oct 16, 2024

Just want to say that this feature is a banger and could be a huge deal breaker for a lot of people to start using airpods in android, maybe even buying just to use on android

@kavishdevar
Copy link
Contributor

@aikooo7 you can try a beta app if you have a rooted phone right now!

@aikooo7
Copy link

aikooo7 commented Oct 16, 2024

@aikooo7 you can try a beta app if you have a rooted phone right now!

I do have root, where can I get the app?

Note: I will use it if it is open-source.

@kavishdevar
Copy link
Contributor

Are you comfortable with working with a shell environment?

If you are then, you will have to create a shell script in post-data-fs for overlaying the library file (I'll upload it on my repo later today) in /apex (where the bluetooth services live), and then another root module, or a script (ksu or magisk) to overlay the same library in /system/lib64 (because apex files have signature verification). Then you should be able to use my app (same repo)..

(The app works best for pro 2, and is very much a work in progress!!)

@kavishdevar
Copy link
Contributor

@aikooo7, instructions and files on my first release

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Add a new feature of improve an existing feature
Projects
None yet
Development

No branches or pull requests

8 participants