<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://mitchtalmadge.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://mitchtalmadge.com/" rel="alternate" type="text/html" /><updated>2025-04-14T03:45:17+00:00</updated><id>https://mitchtalmadge.com/feed.xml</id><title type="html">Mitch Talmadge</title><subtitle>Personal Blog &amp; Portfolio</subtitle><entry><title type="html">lutron caséta pico teardown</title><link href="https://mitchtalmadge.com/2025/04/08/lutron-caseta-pico-teardown.html" rel="alternate" type="text/html" title="lutron caséta pico teardown" /><published>2025-04-08T00:00:00+00:00</published><updated>2025-04-08T00:00:00+00:00</updated><id>https://mitchtalmadge.com/2025/04/08/lutron-caseta-pico-teardown</id><content type="html" xml:base="https://mitchtalmadge.com/2025/04/08/lutron-caseta-pico-teardown.html"><![CDATA[<p><img src="/assets/images/2025-04-08-lutron-caseta-pico-teardown/1744142366476.png" alt="the remote" /></p>

<p>I’ve been messing with Lutron Caséta smart switches around my home recently. I wanted to see what was inside one of my Pico remotes (model number <code class="language-plaintext highlighter-rouge">PJ2-P2B</code>), so I took it apart.</p>

<p><img src="/assets/images/2025-04-08-lutron-caseta-pico-teardown/1744140902994.png" alt="label on back of remote" /></p>

<p><img src="/assets/images/2025-04-08-lutron-caseta-pico-teardown/1744142389762.png" alt="internals of the remote" /></p>

<p>Inside is a CR2032 coin cell which supposedly lasts up to 10 years, and people on the web agree.</p>

<p><img src="/assets/images/2025-04-08-lutron-caseta-pico-teardown/1744142448173.png" alt="back of the motherboard" /></p>

<p>All the components live on the back of the motherboard, except two metal dome switches for the on/off buttons.</p>

<p><img src="/assets/images/2025-04-08-lutron-caseta-pico-teardown/1744142478903.png" alt="front of the motherboard" /></p>

<p><img src="/assets/images/2025-04-08-lutron-caseta-pico-teardown/1744142603605.png" alt="zoomed in back of the motherboard" /></p>

<p>There’s also a loop antenna surrounding the entire bottom half of the backside of the board, though it is difficult to make it out.</p>

<p><img src="/assets/images/2025-04-08-lutron-caseta-pico-teardown/1744142680325.png" alt="loop antenna" /></p>

<p>The main chip is an Si4010-C2 from Silicon Labs. This is a low-power sub-GHz RF transmitter. <a href="/assets/docs/Si4010.pdf">Datasheet.</a></p>

<p><img src="/assets/images/2025-04-08-lutron-caseta-pico-teardown/1744143167825.png" alt="Si4010-C2 up close" /></p>

<p><img src="/assets/images/2025-04-08-lutron-caseta-pico-teardown/1744142876843.png" alt="Si4010-C2 functional block diagram" /></p>

<p><img src="/assets/images/2025-04-08-lutron-caseta-pico-teardown/1744143036572.png" alt="Si4010-C2 pinout" /></p>

<p>From the diagrams, it is clear to me that this chip is very purpose-built for an application like a simple remote control. It’s a simple enough circuit that you could easily build your own with a protoboard and a few components. The chip can be purchased <a href="https://www.digikey.com/en/products/detail/silicon-labs/SI4010-C2-GS/2441251">on DigiKey</a> in small quantities for about 4-5 bucks each.</p>

<p><img src="/assets/images/2025-04-08-lutron-caseta-pico-teardown/1744143398451.png" alt="digikey part listing" /></p>

<p>Of course if you wanted to make it work with the Lutron system, you’d have to reverse engineer their protocol. I wouldn’t be surprised if that info is already on the web.</p>

<p>Anyway, that’s a look inside the remote. I’m putting it back together now 🙂 It’s going on the wall of my stairwell. If you’re interested in seeing more Lutron teardowns, <a href="https://www.allaboutcircuits.com/news/teardown-tuesday-lutron-caseta-wireless-remote/">I found this article on All About Circuits</a> by Mark Hughes from 2016 that is pretty neat. There’s a clearer picture of the loop antenna on the Pico, too.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">run node-red on macos as a service</title><link href="https://mitchtalmadge.com/2025/03/22/node-red-macos-service.html" rel="alternate" type="text/html" title="run node-red on macos as a service" /><published>2025-03-22T00:00:00+00:00</published><updated>2025-03-22T00:00:00+00:00</updated><id>https://mitchtalmadge.com/2025/03/22/node-red-macos-service</id><content type="html" xml:base="https://mitchtalmadge.com/2025/03/22/node-red-macos-service.html"><![CDATA[<p>As part of <a href="/2025/03/21/iphone-focus-automation-via-homeassistant.html">automating the sleep focus of my iPhone</a>, I needed a way to run <a href="https://nodered.org/">Node-Red</a> on my Mac as a service so that it would run in the background and start up when I logged into my MacBook. I also wanted to make sure that it ran under my user account instead of root to improve security.</p>

<p><em>Note: These instructions are to set up a service that starts on login, and shuts down on log-out. I only have one user on my MacBook, and never log out, so keep that in mind if you’ve got multiple users.</em></p>

<h1 id="node-red-installation">Node-Red Installation</h1>

<p>I installed Node-Red on my MacBook using <a href="https://nodered.org/docs/getting-started/local">these instructions</a>, which I will summarize here for posterity:</p>

<p><em>Note: I originally tried installing Node-Red and setting up a service with <code class="language-plaintext highlighter-rouge">brew</code>, but it never would start for me. It kept crashing and I don’t know why. Either way, the official install instructions recommend using <code class="language-plaintext highlighter-rouge">npm</code>, so I gave up on <code class="language-plaintext highlighter-rouge">brew</code> and went with that.</em></p>

<ol>
  <li>Install via NPM: <code class="language-plaintext highlighter-rouge">sudo npm install -g --unsafe-perm node-red</code></li>
  <li>Test it out; restart your shell and run <code class="language-plaintext highlighter-rouge">node-red</code>.</li>
  <li>Go to http://127.0.0.1:1880/ and it should be running.</li>
</ol>

<h1 id="service-config">Service Config</h1>

<p>Here is how you can set up the service:</p>

<ol>
  <li>Create the file <code class="language-plaintext highlighter-rouge">~/Library/LaunchAgents/org.nodered.plist</code>, which is a <a href="https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html">launchd</a> configuration file. Here’s what mine looks like (remember to replace <code class="language-plaintext highlighter-rouge">mitchtalmadge</code> with your username):</li>
</ol>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="cp">&lt;!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&gt;</span>
<span class="nt">&lt;plist</span> <span class="na">version=</span><span class="s">"1.0"</span><span class="nt">&gt;</span>
<span class="nt">&lt;dict&gt;</span>
    <span class="nt">&lt;key&gt;</span>Label<span class="nt">&lt;/key&gt;</span>
    <span class="nt">&lt;string&gt;</span>org.nodered<span class="nt">&lt;/string&gt;</span>
    <span class="nt">&lt;key&gt;</span>ProgramArguments<span class="nt">&lt;/key&gt;</span>
    <span class="nt">&lt;array&gt;</span>
        <span class="nt">&lt;string&gt;</span>/opt/homebrew/bin/node-red<span class="nt">&lt;/string&gt;</span>
        <span class="nt">&lt;string&gt;</span>--userDir<span class="nt">&lt;/string&gt;</span>
        <span class="nt">&lt;string&gt;</span>/Users/mitchtalmadge/.node-red<span class="nt">&lt;/string&gt;</span>
    <span class="nt">&lt;/array&gt;</span>
    <span class="nt">&lt;key&gt;</span>EnvironmentVariables<span class="nt">&lt;/key&gt;</span>
    <span class="nt">&lt;dict&gt;</span>
        <span class="nt">&lt;key&gt;</span>PATH<span class="nt">&lt;/key&gt;</span>
        <span class="nt">&lt;string&gt;</span>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin<span class="nt">&lt;/string&gt;</span>
    <span class="nt">&lt;/dict&gt;</span>
    <span class="nt">&lt;key&gt;</span>RunAtLoad<span class="nt">&lt;/key&gt;</span>
    <span class="nt">&lt;true/&gt;</span>
    <span class="nt">&lt;key&gt;</span>KeepAlive<span class="nt">&lt;/key&gt;</span>
    <span class="nt">&lt;true/&gt;</span>
    <span class="nt">&lt;key&gt;</span>WorkingDirectory<span class="nt">&lt;/key&gt;</span>
    <span class="nt">&lt;string&gt;</span>/Users/mitchtalmadge/.node-red<span class="nt">&lt;/string&gt;</span>
    <span class="nt">&lt;key&gt;</span>StandardOutPath<span class="nt">&lt;/key&gt;</span>
    <span class="nt">&lt;string&gt;</span>/Users/mitchtalmadge/.node-red/node-red.log<span class="nt">&lt;/string&gt;</span>
    <span class="nt">&lt;key&gt;</span>StandardErrorPath<span class="nt">&lt;/key&gt;</span>
    <span class="nt">&lt;string&gt;</span>/Users/mitchtalmadge/.node-red/node-red-error.log<span class="nt">&lt;/string&gt;</span>
<span class="nt">&lt;/dict&gt;</span>
<span class="nt">&lt;/plist&gt;</span>
</code></pre></div></div>

<ol>
  <li>Activate the service with <code class="language-plaintext highlighter-rouge">launchctl load ~/Library/LaunchAgents/org.nodered.plist</code></li>
  <li>Start the service with  <code class="language-plaintext highlighter-rouge">launchctl start org.nodered</code>.</li>
  <li>Go to http://localhost:1880/ and make sure it’s running.</li>
  <li>Reboot and log back in to make sure it starts up automatically.</li>
</ol>

<h1 id="dont-go-to-sleep">Don’t Go to Sleep!</h1>

<p>You may want to configure your sleep settings to prevent your Mac from sleeping while plugged in, so that Node-Red does not go down. This is in System Preferences -&gt; Battery -&gt; Options:</p>

<p><img src="/assets/images/2025-03-22-node-red-macos-service/1742706740765.png" alt="Showing that the &quot;Prevent automatic sleeping on power adapter&quot; option is turned on" /></p>

<hr />

<p>Node-Red should now run in the background on your Mac and start up when you log in. It will also run as your own user, not root, which I think is much safer. If you need to stop it, you can do so with <code class="language-plaintext highlighter-rouge">launchctl stop org.nodered</code>.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[As part of automating the sleep focus of my iPhone, I needed a way to run Node-Red on my Mac as a service so that it would run in the background and start up when I logged into my MacBook. I also wanted to make sure that it ran under my user account instead of root to improve security.]]></summary></entry><entry><title type="html">iphone focus automation via home assistant</title><link href="https://mitchtalmadge.com/2025/03/21/iphone-focus-automation-via-homeassistant.html" rel="alternate" type="text/html" title="iphone focus automation via home assistant" /><published>2025-03-21T00:00:00+00:00</published><updated>2025-03-21T00:00:00+00:00</updated><id>https://mitchtalmadge.com/2025/03/21/iphone-focus-automation-via-homeassistant</id><content type="html" xml:base="https://mitchtalmadge.com/2025/03/21/iphone-focus-automation-via-homeassistant.html"><![CDATA[<p>I had a simple goal: <strong>Make my iPhone go into Sleep focus via some automation in Home Assistant.</strong></p>

<p><strong>TL;DR:</strong> <a href="#attempt-5-successful-macbook-node-red-and-icloud-focus-sync">Using Node-Red and an always-on MacBook, I automated my focus state</a>.</p>

<p>My use case was an <a href="https://arrehome.com/pages/arre-smart-button">Arre Smart Button</a> on my bedside table that I clicked to activate my “good night” routine: turn off the lights, set the thermostat, and (ideally) set my phone to Sleep focus so that notifications wouldn’t make sounds.</p>

<p>That last bit proved to be a lot harder than I expected. The only way from my iPhone to turn on the Sleep focus without relying on time-based automation was to use the built-in <strong>Shortcuts</strong> app. What I needed was an automation trigger that could be invoked from another server. Here were all of the automation triggers that Apple had implemented:</p>

<p><img src="/assets/images/2025-03-21-iphone-focus-automation-via-homeassistant/1742702118649.jpeg" alt="iPhone Shortcuts automation options" width="300" />
<img src="/assets/images/2025-03-21-iphone-focus-automation-via-homeassistant/1742702118650.jpeg" alt="iPhone Shortcuts automation options" width="300" />
<img src="/assets/images/2025-03-21-iphone-focus-automation-via-homeassistant/1742702118651.jpeg" alt="iPhone Shortcuts automation options" width="300" />
<img src="/assets/images/2025-03-21-iphone-focus-automation-via-homeassistant/1742702118652.jpeg" alt="iPhone Shortcuts automation options" width="300" /></p>

<p>Right away, the only triggers that I could see being useful here were <strong>Email</strong> and <strong>Message</strong>. If I could have had Home Assistant either email or text my phone, that should have been enough to trigger my shortcut and turn on my Sleep focus…right?</p>

<h1 id="attempt-1-email">Attempt 1: Email</h1>

<p>My main concern with this idea was that emails on my iPhone had <strong>never</strong> arrived quickly. If an app sent me a 6-digit code to sign in, for example, I always had to go into my Mail app and force a refresh before I got the email. Otherwise, I’d be sitting there for 5-15 minutes before I got the email.</p>

<p>The primary reason for slow mail was that only iCloud email accounts supported “Push” notifications in the Mail app; all others had to “Fetch” the mail every 15+ minutes. This was why my Gmail messages took forever to come in. From the little research I’d done, it sounded like Google did not support Push notifications on third-party apps, and instead only allowed it on their own Gmail app. Lame…</p>

<p>So, to make this work, I needed to send an email to my iCloud address. That should get it to my iPhone the fastest. To test this idea, I set up an automation to listen for emails on my iCloud address, and turn on Sleep focus when one arrived. Then, I sent an email to my iCloud address from my Gmail address.</p>

<p>Sadly, the emails did not always come in quickly. Many times, I let my iPhone sit still and it took over 5 minutes before the email showed up (battery saver was disabled, btw). It showed up immediately on my MacBook, though. There were a few rare cases where the iPhone did show the email within about 10 seconds, but I’d really like something faster than that.</p>

<p>Even still, if the emails did come in immediately, it wouldn’t matter. The automation just straight up didn’t work, at least on my phone. It just didn’t run at all. I double and triple-checked that everything was correctly configured, but it would never even try to run the automation. <a href="https://www.reddit.com/r/shortcuts/comments/1d8g1n7/icloud_mail_doesnt_trigger_automation/">I found a thread on Reddit</a> where people were complaining of this happening for iCloud accounts. I had to assume it was just an iOS 18 bug.</p>

<p>Moving on to my next idea… Messages?</p>

<h1 id="attempt-2-sms-messaging-via-email">Attempt 2: SMS Messaging via Email</h1>

<p>So I couldn’t detect an email. Maybe I could detect an SMS? To send SMS messages to my phone, I would likely need to pay per message with a service like Twilio. It’s not a ton of money ($0.0079 per message – about 50 cents per month for two messages per day), but it’s more the principle of it that hurt me. We live in an era where there are better, faster, and completely free options for push-notifying a cell phone than with SMS messages. Yet, here we are …</p>

<p>First, though, I wanted to try a trick that I’d messed with in the past. It’s a little bit hacky, though. Did you know you could email a phone number? For example, with a T-Mobile number, you could send an email to e.g. <code class="language-plaintext highlighter-rouge">1234567890@tmomail.net</code> and it would arrive as a text message to <code class="language-plaintext highlighter-rouge">(123) 456-7890</code>. Verizon and some other carriers have killed this antiquated feature, but mine still supported it so I thought I’d at least try it.</p>

<p>This kinda worked. The first few messages came through. It was a little slow, around 5 seconds, but it did work. However, it quickly stopped working. The email server started to immediately send back messages saying that the email was blocked. It seemed like they had some very aggressive spam filtering and were able to detect automated messages like “Goodnight!”. Maybe I could find a way around this, but frankly, I was in search of a more reliable solution, and this felt gross. Plus, it was probably against their TOS to automate such a thing, and I’d like to keep my phone number alive.</p>

<h1 id="attempt-3-apple-home-automation">Attempt 3: Apple Home Automation</h1>

<p>While playing with the previous two ideas, I remembered that I could create automations in Apple Home. You could do things like turning on a light when a sensor detected too much CO2 in the air, or whatever. I had my Home Assistant and Apple Home services connected, so I could expose devices in Home Assistant into my Apple Home. Maybe I could use this to accomplish my goal?</p>

<p>I was able to create a virtual “switch” in Home Assistant that represented whether I was sleeping or not. I put this into Apple Home, and began creating an automation on it.</p>

<p><img src="/assets/images/2025-03-21-iphone-focus-automation-via-homeassistant/1742709169936.png" alt="A preview of my automation, which has no actions" /></p>

<p>…but, it was pretty useless. You couldn’t set a focus. You couldn’t run another shortcut. You couldn’t really do <strong>anything</strong> that involved changing the iPhone. I can only guess that this automation would run on a remote server somewhere, perhaps even on my Apple TV (which tended to serve as a HomeKit hub), and therefore would have no direct access to any of the iPhone features. Lame!</p>

<h1 id="attempt-4-sms-messaging-via-twilio">Attempt 4: SMS Messaging via Twilio</h1>

<p>Ok, fine. Maybe I’d pay the 50 cents per month to solve this minor annoyance.</p>

<p>I rented a “Local” phone number on Twilio (another $1.15 per month already) and installed the <a href="https://www.home-assistant.io/integrations/twilio/">Twilio integration</a> on Home Assistant. Then I added a <code class="language-plaintext highlighter-rouge">notify</code> section to my <code class="language-plaintext highlighter-rouge">configuration.yml</code> as such (<a href="https://www.home-assistant.io/integrations/twilio_sms">docs for this are here</a>)…</p>

<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Example configuration.yaml entry</span>
<span class="na">notify</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">send_sms</span>
    <span class="na">platform</span><span class="pi">:</span> <span class="s">twilio_sms</span>
    <span class="na">from_number</span><span class="pi">:</span> <span class="s2">"</span><span class="s">+12223334444"</span> <span class="c1"># Twilio number goes here</span>
</code></pre></div></div>

<p>I rebooted Home Assistant and sent my SMS with the following action:</p>

<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">action</span><span class="pi">:</span> <span class="s">notify.send_sms</span>
<span class="na">data</span><span class="pi">:</span>
  <span class="na">message</span><span class="pi">:</span> <span class="s">Good Night!</span>
  <span class="na">target</span><span class="pi">:</span> <span class="m">19998887777</span> <span class="c1"># Personal number goes here</span>
</code></pre></div></div>

<p>and!!! nothing happened. I had to check the Twilio error logs and saw this:</p>

<blockquote>
  <p>Messages sent to US numbers will not be delivered if they are sent from numbers that are not associated with an approved A2P 10DLC Campaign</p>
</blockquote>

<p>??? wat? This turned out to be <a href="https://www.youtube.com/watch?v=KWvbRToRnGg">some new regulation</a> I never had to do in the past, so it caught me off-guard. After some research, I learned that it was a way to cut down on spam. I had to register with “The Campaign Registry (TCR)” as a sole proprietor, then tell them what I planned to do with the number, who I was messaging, etc… And I had to pay $2/month to maintain my registration. So this project was now coming out to about $0.50 for messages, $2 for the registration, and $1.15 for the phone number, for a total of $3.65/month. All so that I could have my iPhone go into sleep mode?? ☠️</p>

<p>If I instead got a “Toll Free” number for $2.15/month, there was a simpler registration process with no additional monthly fee, so it was more like $2.65/month for this project instead. So I tried that. But they wanted a legal entity name, which I did not have. I was just a person try’na get my phone to change focus!! I ain’t no company…</p>

<p>I did end up submitting a registration request to the TCR, but honestly, I gave up at this point. It should not be this hard or costly to just change my iPhone’s focus mode.</p>

<h1 id="attempt-5-successful-macbook-node-red-and-icloud-focus-sync">Attempt 5 (Successful!): MacBook, Node-Red, and iCloud Focus Sync</h1>

<p>While researching this problem online, <a href="https://community.home-assistant.io/t/set-ios-focus-mode/571518/2?u=mitchtalmadge">I came across someone</a> who used Node-Red on a MacBook to set their MacBook focus mode, which would then sync via iCloud as it normally would and set the focus on the iPhone, Apple Watch, etc.</p>

<p>That’s what I ended up doing, and after some trial and error, I made it work.</p>

<p>First, I installed Node-Red on my MacBook using <a href="https://nodered.org/docs/getting-started/local">these instructions</a>, which I will summarize here for posterity:</p>

<ol>
  <li>Install via NPM: <code class="language-plaintext highlighter-rouge">sudo npm install -g --unsafe-perm node-red</code></li>
  <li>Test it out; restart your shell and run <code class="language-plaintext highlighter-rouge">node-red</code>.</li>
  <li>Go to http://127.0.0.1:1880/ and it should be running.</li>
</ol>

<p>Then I set up Home Assistant to expose my sleep state to MQTT. I had a boolean helper which I turned “on” when my focus should be “Sleep”, and “off” when it should be whatever else. With MQTT set up already for other purposes, I just had to add this section to my config:</p>

<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">mqtt_statestream</span><span class="pi">:</span>
  <span class="na">base_topic</span><span class="pi">:</span> <span class="s">homeassistant</span>
  <span class="na">include</span><span class="pi">:</span>
    <span class="na">entities</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">input_boolean.mitch_asleep</span>
</code></pre></div></div>

<p>Then I could view the state at <code class="language-plaintext highlighter-rouge">homeassistant/input_boolean/mitch_asleep/state</code>, which would be either <code class="language-plaintext highlighter-rouge">on</code> or <code class="language-plaintext highlighter-rouge">off</code>.</p>

<p>Here’s what my Node-Red flow ended up looking like:</p>

<p><img src="/assets/images/2025-03-21-iphone-focus-automation-via-homeassistant/1742702052223.png" alt="Node-Red flow with an mqtt input that goes into a switch, then into one of two exec configs" /></p>

<p>Basically, when the input value changed on MQTT, it would exec either <code class="language-plaintext highlighter-rouge">shortcuts run sleep:on</code> or <code class="language-plaintext highlighter-rouge">shortcuts run sleep:off</code>. However, there was a gotcha that really stumped me for a while. The Node-Red exec action was getting stuck for no apparent reason. It turned out that despite my shortcut not calling for any input, the <code class="language-plaintext highlighter-rouge">shortcuts</code> command was expecting something from stdin. I ended up changing the command to <code class="language-plaintext highlighter-rouge">: | shortcuts run sleep:on</code>, which effectively sent nothing to the command’s stdin, and it worked!</p>

<p><img src="/assets/images/2025-03-21-iphone-focus-automation-via-homeassistant/1742702468529.png" alt="Node-Red exec command config" /></p>

<p>For the shortcuts, I used the macOS Shortcuts app to create a new shortcut that set my focus to “Sleep”, like so:</p>

<p><img src="/assets/images/2025-03-21-iphone-focus-automation-via-homeassistant/1742702524847.png" alt="sleep:on shortcut example" /></p>

<p>For the <code class="language-plaintext highlighter-rouge">sleep:off</code> shortcut, I added a check to make sure the focus was currently on, because I think Node-Red would run this shortcut at random times during the day and it would wipe whatever focus my phone was currently in (my guess is that the wifi renegotiating would act like an MQTT state change).</p>

<p><img src="/assets/images/2025-03-21-iphone-focus-automation-via-homeassistant/1744602143777.png" alt="sleep:off shortcut example" /></p>

<p>And it all worked! When I turned my sleep state on in Home Assistant, my MacBook ran the shortcut and my iPhone went into Sleep focus. It wasn’t instant, but it was pretty quick (around 5 seconds).</p>

<h2 id="keeping-node-red-running">Keeping Node-Red Running</h2>

<p>The only problem now was that to run Node-Red, I had to keep a terminal open. Also, when my MacBook went to sleep, it stopped running Node-Red. <a href="/2025/03/22/node-red-macos-service.html">Here is how I fixed it.</a></p>

<p>so…yeah! It wasn’t a very simple solution to my problem, but at least it worked, didn’t really cost anything extra, and it gave me the option to create other useful automations in the future now that I had Node-Red running.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[I had a simple goal: Make my iPhone go into Sleep focus via some automation in Home Assistant.]]></summary></entry><entry><title type="html">hello jekyll</title><link href="https://mitchtalmadge.com/2025/03/20/hello-jekyll.html" rel="alternate" type="text/html" title="hello jekyll" /><published>2025-03-20T00:00:00+00:00</published><updated>2025-03-20T00:00:00+00:00</updated><id>https://mitchtalmadge.com/2025/03/20/hello-jekyll</id><content type="html" xml:base="https://mitchtalmadge.com/2025/03/20/hello-jekyll.html"><![CDATA[<p>This is my first time using <a href="https://jekyllrb.com/">Jekyll</a>.</p>

<p>I once <a href="https://github.com/MitchTalmadge/Old-Portfolio">created a web portfolio in Angular</a>, but it became neglected due to its high maintenance costs and my lack of free time with my full-time job and other life obligations. I would also write blog posts on <a href="https://medium.com/mitchtalmadge">Medium</a>, but I’ve always wanted to have my content on my own site, and Medium quit supporting custom domains a while ago.</p>

<p>So… today, I am combining <a href="https://docs.github.com/en/pages/setting-up-a-github-pages-site-with-jekyll/about-github-pages-and-jekyll">GitHub Pages and Jekyll</a>, an apparently very popular combination:</p>

<p><img src="/assets/images/2025-03-20-hello-jekyll/1742703122616.png" alt="Search results on GitHub.com for &quot;jekyll&quot; returning 80 thousand results" /></p>

<p>…and it’s already obvious why it’s so loved. In the very little time I’ve spent playing with this, I have already decided that I won’t be going back to Angular or React for my web portfolio/blog. This is just SO much easier to manage. I think my favorite part is that I can write just about everything in markdown, and don’t need to worry about the HTML/CSS/JS; in other words, I can just focus on the content.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[This is my first time using Jekyll.]]></summary></entry><entry><title type="html">downloading videos from canvas</title><link href="https://mitchtalmadge.com/2021/02/21/downloading-videos-from-canvas.html" rel="alternate" type="text/html" title="downloading videos from canvas" /><published>2021-02-21T22:10:32+00:00</published><updated>2021-02-21T22:11:03+00:00</updated><id>https://mitchtalmadge.com/2021/02/21/downloading-videos-from-canvas</id><content type="html" xml:base="https://mitchtalmadge.com/2021/02/21/downloading-videos-from-canvas.html"><![CDATA[<p>If you are like me, then you are a student who prefers watching video lectures on your TV than on the embedded video player within Canvas. Unfortunately, there is no Canvas app for my Android TV. However, there is the PLEX media client, where I can watch any video files from my computer’s hard drive. The problem is that the Kaltura-based videos that most professors use on Canvas have no download button. This is likely to stop people from downloading and distributing lectures that they paid for, but honestly I think it does more harm than good.</p>

<p>In this article, I will show you how to use a tool called <strong>youtube-dl</strong> to download these videos to your hard drive as <code class="language-plaintext highlighter-rouge">mp4</code> files, which you can then stream to your TV or play on your phone or whatever else you’d like. Despite the fact that “youtube” is in the name, this tool is meant for all kinds of video websites, and we can make it work for Canvas.</p>

<p><em>Disclaimer: This process may be in breach of Canvas and/or Kaltura’s TOS, so do this at your own risk. I assume no responsibility for you following these instructions. You should not distribute any videos you may download with this method.</em></p>
<h2 id="1-install-youtube-dl">1: Install <strong>youtube-dl</strong></h2>

<p>First things first, go to <a href="https://github.com/ytdl-org/youtube-dl/releases">https://github.com/ytdl-org/youtube-dl/releases</a> to get <strong>youtube-dl</strong> . On Windows, download the <code class="language-plaintext highlighter-rouge">youtube-dl.exe</code> file and place it somewhere easy to find. On Mac, get the <code class="language-plaintext highlighter-rouge">youtube-dl</code> file and do the same. <strong>This is a command-line program. Don’t double-click it and expect something to show up.</strong></p>

<p>Next, open a command prompt or terminal and navigate to the place where you put the file you just downloaded. If you don’t know how to use the terminal, please either watch <a href="https://www.youtube.com/watch?v=MBBWVgE0ewk">this Windows Tutorial</a> or <a href="https://www.youtube.com/watch?v=aKRYQsKR46I">this Mac Tutorial</a> .</p>

<p>Once you are in the directory, type <code class="language-plaintext highlighter-rouge">youtube-dl --version</code> and hit enter. Make sure it prints something out like <code class="language-plaintext highlighter-rouge">2021.02.10</code> (this value will depend on your actual version of course) . If you see this, then <strong>youtube-dl</strong> is ready to go.</p>

<p><img src="/assets/images/2021-02-21-how-to-download-videos-from-canvas/1*hlIiM2hTfoHffQd_XyN__g.png" alt="Example" /></p>

<p>Example</p>
<h2 id="2-find-the-video-url">2: Find the video URL</h2>

<p><strong>youtube-dl</strong> expects the URL of a video to download. Unfortunately you cannot just grab the URL of the page with the video <em>on</em> it and be done. It’s a bit more involved than that.</p>

<p>First, find open the video you want to download from Canvas. I’m using Google Chrome for this, and I suggest you do too or the steps may differ. However, any browser should work if you know what you are doing.</p>

<p>The Kaltura videos we want to download will usually be in the “Media Gallery.” Open just one of them, and <strong>do not hit play.</strong> It will look something like this:</p>

<p><img src="/assets/images/2021-02-21-how-to-download-videos-from-canvas/1*m6ilbEEcXQ52F1rP24CabQ.png" alt="" /></p>

<p>Next, press F12 to open the browser developer tools. Open the “Network” tab and type “m3u8” into the filter box:</p>

<p><img src="/assets/images/2021-02-21-how-to-download-videos-from-canvas/1*_G4lW3Ph1UuoMZTcE682sw.png" alt="" /></p>

<p>Now you can hit play. Pause the video after a few seconds and come back to this window. You will see a few entries; you want the last of the ones which say “index.m3u8”: (This should be the highest quality version. )</p>

<p><img src="/assets/images/2021-02-21-how-to-download-videos-from-canvas/1*NNpbF3WPjK7ZWakeNnL7XQ.png" alt="" /></p>

<p>Right click on it and copy the link address:</p>

<p><img src="/assets/images/2021-02-21-how-to-download-videos-from-canvas/1*Lyizkve6mkf3IGEkCQGzSA.png" alt="" /></p>

<h2 id="3-initiate-the-download">3: Initiate the Download</h2>

<p>Now, open the terminal back up and enter the following command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>youtube-dl <span class="s2">"PASTE URL HERE"</span> <span class="nt">-o</span> DESIRED_NAME.mp4
</code></pre></div></div>

<p>Paste the URL inside the quotation marks as shown, and choose the name of the output file. Here’s an example with many parts of the URL stripped out:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>youtube-dl <span class="s2">"https://...kaltura.com/.../index.m3u8..."</span> <span class="nt">-o</span> Lecture1.mp4
</code></pre></div></div>

<p>Now hit enter. The file will begin downloading and will be stored in the same place that <code class="language-plaintext highlighter-rouge">youtube-dl</code> resides. Once it is done, you should have an mp4 file ready to play!</p>

<p>Now just repeat steps 2 and 3 for as many videos as you’d like to download. It’s tedious but in my opinion it’s worth the effort. Hope this helps you! The next step might be to save these to your phone’s internal memory to watch them offline on-the-go, or put them on a PLEX server so you can watch them from your smart TV. It’s all up to you from here.</p>]]></content><author><name>Mitch Talmadge</name></author><category term="canvas" /><category term="videos" /><category term="download" /><category term="university" /><category term="kaltura" /><summary type="html"><![CDATA[Learn to save Canvas / Kaltura videos as mp4 files so that you can play them on your TV or other devices.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://mitchtalmadge.com/assets/2021-02-21-how-to-download-videos-from-canvas/1*m6ilbEEcXQ52F1rP24CabQ.png" /><media:content medium="image" url="https://mitchtalmadge.com/assets/2021-02-21-how-to-download-videos-from-canvas/1*m6ilbEEcXQ52F1rP24CabQ.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">nsa codebreaker 2020 solutions</title><link href="https://mitchtalmadge.com/2021/02/02/nsa-codebreaker-2020-solutions.html" rel="alternate" type="text/html" title="nsa codebreaker 2020 solutions" /><published>2021-02-02T02:57:22+00:00</published><updated>2021-02-02T02:57:22+00:00</updated><id>https://mitchtalmadge.com/2021/02/02/nsa-codebreaker-2020-solutions</id><content type="html" xml:base="https://mitchtalmadge.com/2021/02/02/nsa-codebreaker-2020-solutions.html"><![CDATA[<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/0*09tAanAlUKxktlBU.JPG" alt="" /></p>

<p>In this post I go through how I solved challenges 1 through 5 of the 2020 NSA Codebreaker CTF (Capture the Flag) . This is a cybersecurity challenge involving reverse engineering, cryptography, and many other technical skills.</p>

<p>In many cases, my solutions will be different from your own. Where possible, the task files are subtly changed for each user so that the resulting answers are slightly different. The premise is the same, though. :)</p>
<h2 id="intro">Intro</h2>

<p>This CTF takes place around a <strong>fictitious</strong> (read: fake) story about a kidnapping of a journalist. Here is the intro prompt from the challenge, if you are curious:</p>

<blockquote>
  <p>Two days ago, a renowned American journalist went missing while on assignment abroad. Although the city where the journalist was last seen has very few surveillance cameras on its streets, local authorities were able to provide us with some surveillance footage taken near the journalist’s hotel during the days leading up to the disappearance. From the footage, we see that the journalist was kidnapped from the front of their hotel and taken away in an unmarked van. Unfortunately, we can also observe the kidnappers destroying the journalist’s mobile phone making it impossible to track their route via GPS. Locals have also reported drone activity in the area leading up to the event. A criminal organization, well known for using drones in their kidnap-for-ransom schemes, has claimed responsibility for the incident.</p>

  <p>Your mission is to:</p>
  <ol>
    <li>Locate the missing journalist and hostage takers’ current position.</li>
    <li>Facilitate the recovery of the journalist and take actions to prevent another incident from happening.</li>
  </ol>

  <p>Each task in this year’s challenge will require a range of skills. We need you to call upon all of your technical expertise, your intuition, and your common sense to help us locate and rescue the journalist!</p>

  <p>Good luck. We hope you enjoy the challenge!</p>
</blockquote>

<p>Sounds fun! Let’s get started.</p>
<h2 id="task-1-whats-on-the-drive">Task 1: What’s On the Drive?</h2>
<h3 id="10-points--computer-forensics-command-line-encryption-tools">10 Points | Computer Forensics, Command Line, Encryption Tools</h3>

<blockquote>
  <p>In accordance with USSID18, a collection on an American citizen is permitted in cases where the person is reasonably believed to be held captive by a group engaged in international terrorism. As a result, we have obtained a copy of the home directory from the journalist’s laptop and are hoping it will contain information that will help us to locate and rescue the hostage. Your first task is to analyze the data and files available in the journalist’s home directory.</p>
</blockquote>

<h4 id="goals">Goals:</h4>
<ol>
  <li>Find the journalist’s username.</li>
  <li>Find the encrypted file in the journalist’s home directory.</li>
</ol>

<h4 id="goal-1-find-the-username">Goal 1: Find the username</h4>

<p>We are provided a file called <code class="language-plaintext highlighter-rouge">home.zip</code> which is an “archive of data from the journalist’s computer.” This is clearly a Linux-based home directory upon extraction. Here is the file structure, which I printed with the <code class="language-plaintext highlighter-rouge">tree</code> command:</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*wOaZaOYLexLTsMiq5SRAxA.png" alt="" /></p>

<p>In Linux, each standard user gets their own directory inside <code class="language-plaintext highlighter-rouge">home</code> , which is named by their username. Thus, the username of this journalist is <code class="language-plaintext highlighter-rouge">NapoleonDovetail374</code> .</p>

<h4 id="goal-2-find-the-encrypted-file">Goal 2:** **Find the encrypted file</h4>

<p>There’s a few ways you could go about this, but I just went the common-sense route. Which of the files in the above picture would you most likely want to encrypt? Probably the file named <code class="language-plaintext highlighter-rouge">pwfile</code> , which presumably contains a bunch of passwords. To verify that this is the encrypted file and determine how it is encrypted, we can use the <code class="language-plaintext highlighter-rouge">file</code> command.</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*wsxp6ItI7monMULx2B9hZw.png" alt="" /></p>

<p>Yep, that’s the one!</p>
<h2 id="task-2-social-engineering">Task 2: Social Engineering</h2>
<h3 id="40-points--computer-forensics-metadata-analysis-encryption-tools">40 Points | Computer Forensics, Metadata Analysis, Encryption Tools</h3>

<blockquote>
  <p>The name of the encrypted file you found implies that it might contain credentials for the journalist’s various applications and accounts. Your next task is to find information on the journalist’s computer that will allow you to decrypt this file.</p>
</blockquote>

<h4 id="goal-find-the-password-to-decrypt-pwfile">Goal: Find the password to decrypt <code class="language-plaintext highlighter-rouge">pwfile</code></h4>

<p>Ok, this is a very clear goal. We just need to figure out how to decrypt this file. My very first intuition was to look at the <code class="language-plaintext highlighter-rouge">pwHints.txt</code> file. Who wouldn’t want some password hints? Let’s open it up:</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*WMNMbbaRuOoUUixfKxTg3Q.png" alt="" /></p>

<p>There’s lots of different hints in here, but the important one is the <code class="language-plaintext highlighter-rouge">keychain</code> hint. The terms “keychain” or “keyring” are pretty synonymous with a password storage these days, so it makes sense that this would go to our encrypted <code class="language-plaintext highlighter-rouge">pwfile</code> . We need to find the name of this person’s pet, and the pet’s birthday.</p>

<p>Let’s start in the <code class="language-plaintext highlighter-rouge">Pictures/Pets</code> directory. I found an image there called <code class="language-plaintext highlighter-rouge">shenanigans.jpg</code> which looks pretty promising:</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*3TFEw92iZWK1rDOeszmIug.png" alt="animals wearing party hats and having a birthday party" /></p>

<p>just wow</p>

<p>Let’s assume that this was taken with a camcorder even though it’s clearly photoshopped (it’s a fictitious story, let’s cut them some slack) . In that case, assuming this picture was taken on this journalist’s pet’s birthday, we can hopefully get the capture date from the EXIF data of the image. Let’s run <code class="language-plaintext highlighter-rouge">exif</code> on the file:</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*yEEr1JQ3F1t8ITzSgU7tmA.png" alt="" /></p>

<p>Yep, there it is! This picture was taken on December 28th, 2019 (12/28/19) . We don’t know the birth year of this pet (which pet owners do, honestly?), so it’s probably a safe bet that the password only involves the month and day of the birthday; 12/28.</p>

<p>Now we need to find the pet’s name. Nothing in the file names or pictures revealed anything here, so I started reading their blog entries found in <code class="language-plaintext highlighter-rouge">Documents/Blog-Articles</code> . Check out the one called <code class="language-plaintext highlighter-rouge">blogIntro.txt</code> :</p>

<blockquote>
  <p>Journalist by day. New Blogger by night. Traveler when able. Pet lover…always.</p>

  <p>Napoleon here. Welcome to the ‘Diary of an Exotic Furry Voyager’. Outside of work, my two favorite things are traveling the world and getting to come home to <strong>my favorite furry little friend, and the best friend on the planet, Pearl.</strong> This blog is going to account for some of my travels and more importantly, the animals I meet on the way. Hope you enjoy!</p>
</blockquote>

<p>Bingo! This journalist’s pet name is “Pearl.”</p>

<p>The password hint was <code class="language-plaintext highlighter-rouge">pet name + pet bday</code> , so let’s try <code class="language-plaintext highlighter-rouge">Pearl1228</code> :</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*KTqgr-zYU_2ILj7ZwnTeWQ.png" alt="" /></p>

<p>Success! Opening <code class="language-plaintext highlighter-rouge">pwfile.dec</code> with <code class="language-plaintext highlighter-rouge">vim</code> reveals that this is an SQLite database. Task 2 complete.</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*3IY02HmtbDxG_c9rlPAk2A.png" alt="" /></p>

<h2 id="task-3-social-engineering">Task 3: Social Engineering</h2>
<h3 id="150-points--computer-forensics-metadata-analysis-encryption-tools">150 Points | Computer Forensics, Metadata Analysis, Encryption Tools</h3>

<blockquote>
  <p>Good news — the decrypted key file includes the journalist’s password for the Stepinator app. A Stepinator is a wearable fitness device that tracks the number of steps a user walks. Tell us the associated username and password for that account. We might be able to use data from that account to track the journalist’s location!</p>
</blockquote>

<h4 id="goal-find-the-username-and-password-for-the-stepinator-account">Goal: Find the username and password for the Stepinator account</h4>

<p>Well this sounds like it’ll be easy now that the <code class="language-plaintext highlighter-rouge">pwfile</code> is decrypted. We know that it’s an SQLite database, so let’s use the <code class="language-plaintext highlighter-rouge">sqlite</code> command to do some queries:</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*TSGCzJc7pGUAT_QZJ1WfLg.png" alt="" /></p>

<p>We can see that there are two tables in this database. Let’s jump into the <code class="language-plaintext highlighter-rouge">passwords</code> table and see what we have:</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*0iuRHAEHbcF2LK5--4DZ-A.png" alt="pro tip: use **.explain on** to add column headers to outputs" /></p>

<p>pro tip: use <strong>.explain on</strong> to add column headers to outputs</p>

<p>There’s lots of usernames and passwords in here, but it’s not clear which one is the Stepinator account. There is a <code class="language-plaintext highlighter-rouge">service</code> ID for each account, so let’s check the <code class="language-plaintext highlighter-rouge">services</code> table:</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*avlX5CrebwuvvB214AYimA.png" alt="" /></p>

<p>Awesome! Stepinator has <code class="language-plaintext highlighter-rouge">service</code> ID 8. Thus, our username and password appear to be <code class="language-plaintext highlighter-rouge">Pearl_Dovetail_1228</code> and <code class="language-plaintext highlighter-rouge">&lt;~:gn0OB6%QpDGXnQEbmHb0JbC?0J</code>~&gt;<code class="language-plaintext highlighter-rouge"> … but hang on, notice how all of the passwords look a bit _odd?_ Based on the </code>pwHints.txt<code class="language-plaintext highlighter-rouge"> file, we expect it to look something like </code>color + petName + anniversary + fdate<code class="language-plaintext highlighter-rouge"> . I had never seen this format before, so I googled </code>&lt;~ ~&gt;` and found this helpful piece of information:</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*tb5I7gPzYcxdBZVSc_kjxA.png" alt="" /></p>

<p>So they’re just ascii85-encoded. Cool! I used <a href="https://cryptii.com/pipes/ascii85-encoding">an online tool</a> to decode the password, and it turns out to be <code class="language-plaintext highlighter-rouge">PalegreenPearl11030503</code> . That’s much better — and it was the correct answer!</p>
<h2 id="task-4-follow-that-car">Task 4: Follow That Car!</h2>
<h3 id="500-points--graph-algorithms-computer-science">500 Points | Graph Algorithms, Computer Science</h3>

<blockquote>
  <p>By using the credentials in the decrypted file, we were able to download the journalist’s accelerometer data from their Stepinator device from the time of the kidnapping. Local officials have provided us with a city map and traffic light schedule. Using these along with the journalist’s accelerometer data, find the closest intersection to where the kidnappers took their hostage.</p>
</blockquote>

<h4 id="goal-find-the-intersection-where-the-kidnappers-took-the-journalist">Goal: Find the intersection where the kidnappers took the journalist</h4>

<p>For this task we are provided 3 files:</p>
<ul>
  <li>Relevant information for solving the problem ( <code class="language-plaintext highlighter-rouge">README.txt</code> )</li>
  <li>Acceleration data ( <code class="language-plaintext highlighter-rouge">stepinator.json</code> )</li>
  <li>City map and traffic light schedule ( <code class="language-plaintext highlighter-rouge">maps.zip</code> )</li>
</ul>

<p>Here is the <code class="language-plaintext highlighter-rouge">README.txt</code> contents, if you are interested. Scroll past it for a TL;DR.</p>

<blockquote>
  <p>README — Additional Instructions</p>

  <p>The jounalist was wearing a fitness tracker. Although this device does not capture GPS data, it does capture acceleration data. Perhaps we can use this data to locate and save our victim!</p>

  <p>Security cameras show where the the journalist was kidnapped and it has been marked on the included city map. The kidnappers then headed directly East. After that, we lost track of them.</p>

  <p>We have obtained the journalist’s accelerometer data from their fitness tracker app thanks to your success in decrypting their password database. A list of lateral acceleration values (in m/s²) for each second starting at the time of the kidnapping can be found in the <code class="language-plaintext highlighter-rouge">stepinator.json</code> file.</p>

  <p>Unfortunately, after about two and a half minutes, the tracker stopped collecting data.</p>

  <p>We have provided a map of the city streets showing the kidnapping location. Each city block is about 100m long. This map is available as a shapefile as well as a .png image file.</p>

  <p>Finally, we have obtained the traffic light information for the city. Each intersection has a light, and each light has a cycle of 30 seconds green, 30 seconds red. If a light is green for vehicles traveling through the intersection vertically, it will be red for those traveling horizontally (and vice versa) . The victim was kidnapped just as the lights changed color. Maps showing the traffic lights at certain time intervals are attached.</p>

  <p>We suspect that the kidnappers obeyed all traffic laws and stayed close to the speed limits. Right turns on red lights are not allowed. The speed limit in the city is 13 m/s. The kidnappers likely slow down when they take a right or left turn. We also believe that the kidnappers would not loop back when driving away — you may assume that once an intersection was traversed, that intersection was not revisited.</p>

  <p>Finally, since the journalist was kidnapped in the middle of the night, it is safe to assume that there were no other cars on the road.</p>

  <p>Determine the closest intersection to where the journalist was located when their fitness tracker stopped recording data.</p>
</blockquote>

<p><strong>TL;DR:</strong></p>
<ul>
  <li>We have the acceleration data for the moving vehicle because of the Stepinator.</li>
  <li>We know where the kidnapping took place.</li>
  <li>We have maps of all intersections and the schedule at which the lights change.</li>
  <li>We know that each city block is about 100m long.</li>
  <li>We know the vehicle started moving east right as the lights changed.</li>
  <li>We know that the speed limit is 13 m/s and that the kidnappers obeyed traffic laws.</li>
  <li>The kidnappers slow down to make a right or left turn, but do not turn on red.</li>
</ul>

<p>Here are the maps:</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*l_ZBlQxX2V1N1q9Y7C0sJQ.png" alt="" /></p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*85ZGlN1V0hCFCO9jPSTpUw.png" alt="" /></p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*rqRlfHfi89FkXXLRRoU9Zg.png" alt="" /></p>

<p>And here is the acceleration data:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 0, 0, 0, 0, 0, 0, 0, 0, -0.833, -4.5, -4.5, -3.667, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 0, 0, 0, 0, 0, 0, 0, 0, -0.833, -4.5, -4.5, -3.667, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 0, -0.846, -4, -4, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 0, -2.345, -4.5, -4.5, -2.309, 0, 0, 0, 0, 0, 0, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, -3.308, -4, 1.5, 1.5, 1.5, 1.5, 1.5, 0, 0, -2.426, -4, 1.5, 1.5, 1.5, 1.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, -0.266, -2.761, -4.5, -4.5, -1.239, 0]
</code></pre></div></div>

<p>How are we going to put this whole puzzle together? My first idea was to plot this acceleration data in Excel. Using some formulas I was able to derive the acceleration, velocity, and distance travelled at each second:</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*HDBR6r0QguoV9UL3seNXiA.png" alt="" /></p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*OcCUI3e5PpznUhqfKYJErA.png" alt="" /></p>

<p>There’s some pretty valuable information in these graphs.</p>

<p>First of all, with the distance graph we can be certain that a total of 11 blocks were travelled, since the data ends at 1100m from the start.</p>

<p>Second, the velocity graph shows the behavior of the car at every second. Look at the first “hump:”</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*_Py9sRj3X1Vgj3e8bFyMRQ.png" alt="" /></p>

<p>Here we can see that the car speeds up, moves at a constant rate of about 13m/s, and then slows down to a stop. This is indicative of driving, then stopping for a red light.</p>

<p>But how do we know that they went straight, and did not turn? The answer is that we know that the car will slow down for a turn. In this case, the car goes a constant speed. A turn would look more like this:</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*Ual_IiSFESAos3SDZs3Dyg.png" alt="" /></p>

<p>Here, the car speeds up from a stop to 13m/s, then slows down to 5m/s, before quickly speeding back up to 13m/s, and then coming to a stop.</p>

<p>They sped up, slowed down to turn, then sped back up before running into a red light.</p>

<p>Now all that is left to do is open OneNote on my Surface, and start drawing a path that aligns with the data. This took a while, but I ended up with the following path, which turned out to be correct!</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*qa9sXf-mejUKsBqFE18uXw.png" alt="" /></p>

<p>You might be wondering how I decided that they made a right or left turn. Luckily, at each turn, the opposite turn would have run me into a wall or caused other problems which made that turn impossible; so the choice was obvious at each step. However, even if you had only narrowed down the choices to three or so, you do get multiple chances to answer the question, so it’s no big deal.</p>
<h2 id="task-5-where-has-the-drone-been">Task 5: Where Has the Drone Been?</h2>
<h3 id="1300-points--reverse-engineering-cryptography">1300 Points | Reverse Engineering, Cryptography</h3>

<blockquote>
  <p>A rescue team was deployed to the criminal safehouse identified by your efforts. The team encountered resistance but was able to seize the location without causalities. Unfortunately, all of the kidnappers escaped and the hostage was not found. The team did find two important pieces of technology left behind: the journalist’s Stepinator device, and a damaged surveillance drone. An analyst retrieved some encrypted logs as well as part of the drone’s GPS software. Your goal for this task is to identify the location of the criminal organization’s base of operations.</p>
</blockquote>

<p>This one was <strong>TOUGH.</strong> Something that bothers me when most people give their CTF solutions is that you only see the successful bits. You never hear about them struggling to find the answer. You never hear about how they accidentally stay up all night working on the problem, throwing off their sleep schedule, and missing classes as a result, which only added to their depression and anxiety. You never hear about how the problem obsessed them and caused them to neglect their college assignments. You never hear about the stress and the imposter syndrome, many times wondering “am I good enough to do this?” I am here to tell you that I experienced all of this and more. Problems like these can be a huge undertaking, and it was NOT easy to solve this one. However, I learned so much from it, and I’d do it again in a heartbeat. Be strong; power through; it’s worth it.</p>

<p>Now — onto the problem. We are given two downloads:</p>
<ul>
  <li>Interesting looking binary ( <code class="language-plaintext highlighter-rouge">gpslogger</code> )</li>
  <li>Recovered log files ( <code class="language-plaintext highlighter-rouge">logs.tgz</code> )</li>
</ul>

<p>This includes four encrypted log files from the drone, and the software that produced them:</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*VecDG2a4l2FpybjlVMB-gw.png" alt="" /></p>

<p>Let’s run <code class="language-plaintext highlighter-rouge">file</code> on one of the log files and see what it knows:</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*xW2JhYz-TlafdQoHcoBeXw.png" alt="" /></p>

<p>Oh.. “data” ..that’s not useful. This is definitely a type of encryption that does not include metadata. Probably something simple like AES. Let’s also run <code class="language-plaintext highlighter-rouge">file</code> on the binary:</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*3idkxhJGl1i6Oulb-XW_tw.png" alt="" /></p>

<p>This is an aarch64 ELF executable. In other words, it was compiled for a 64 bit ARM processor, which I wouldn’t be surprised to find inside a drone. This is a similar processor to what powers the Raspberry Pi 4, or your smart phone — low on battery consumption with decent performance.</p>

<p>I tried many ideas to crack this encryption, and I will go through some of them here. First, I like to run <code class="language-plaintext highlighter-rouge">strings</code> to see what hardcoded information exists in the binary. If we are really lucky, the encryption key would be hardcoded. Here’s some of the things I found.</p>
<ul>
  <li>These function names reveal that we are looking for a key and IV which are generated inside the code. <code class="language-plaintext highlighter-rouge">setup_cipher</code> also looks useful.</li>
</ul>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*-bfvErwYDVF0NiK7cgIzng.png" alt="" /></p>

<ul>
  <li>These function names suggest that we will be working with AES CBC, ASM, or GCM encryption.. I wonder which.</li>
</ul>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*r6lG6LHGNxonv9Mpi_n9oA.png" alt="" /></p>

<ul>
  <li>Ooh! Look at this one. The IV depends on some longitude coordinate, and the key depends on some latitude coordinate.</li>
</ul>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*2r-6QsbD0rHTWONQXLqEGA.png" alt="" /></p>

<p>That last one really got me thinking… where have I seen latitude and longitude coordinates before? Oh yeah, the map from Task 4!</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*rMkgf0elsvwArdCl443kTw.png" alt="" /></p>

<p>I bet that will be useful later.</p>

<p>Okay, at this point I have some information but definitely not enough. I know that those coordinates play into the key and IV somehow, but I don’t know the exact details.</p>

<p>Let’s see if we can run the binary. Since it’s ARM64 based, I will need a device with this kind of processor. Remember when I mentioned phones? I have a spare OnePlus 6 Android phone laying around, so I installed <a href="https://play.google.com/store/apps/details?id=com.termux">Termux</a> as well as <a href="https://play.google.com/store/apps/details?id=studio.com.techriz.andronix">Andronix</a> , two apps which allow me to install and run Kali linux on my phone, even without root. I set up an SSH server and then remoted in from my desktop. After uploading the binary, I tried to run it:</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*4v-7rXEFlvuEYTMt-jwCsQ.png" alt="" /></p>

<p>Hmm.. missing libraries and symbols. Let’s see:</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*SOByBlOV567CqEZAIzlFTA.png" alt="" /></p>

<p>Ok, we’re missing <code class="language-plaintext highlighter-rouge">libgps.so</code> and <code class="language-plaintext highlighter-rouge">libc.musl-aarch64.so.1</code> . After installing these missing libraries, I still could not run the binary because of the <code class="language-plaintext highlighter-rouge">GPSNMEA: symbol not found</code> error. I think this binary won’t work unless I have an actual hardware GPS module hooked up, and I do not have one, and don’t know how to use the one my phone has built-in. I gave up on running the binary at this point.</p>

<p>Let’s finally disassemble the binary and look inside, something I was sort-of hoping to avoid. I used the command <code class="language-plaintext highlighter-rouge">objdump -D gpslogger &gt; gpslogger.asm</code> . I opened the file and jumped to <code class="language-plaintext highlighter-rouge">main.main</code> which I knew about from <code class="language-plaintext highlighter-rouge">strings</code> :</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*Ba1FYVf1AzW8VvSc4vSxPQ.png" alt="" /></p>

<p>Hey, do you see that? <code class="language-plaintext highlighter-rouge">&lt;go.string.*+0x6fe0&gt;</code> … This binary was written in golang! This is actually going to end up being obnoxious because golang does a lot of interesting things which make the assembly look fairly different from traditional c-lang type binaries, like c and c++ . Here’s some of the things I learned while I read through the assembly and scoured Google:</p>
<ul>
  <li>golang passes all parameters into functions through the stack. They do not use registers for this, unlike c/c++ . ( <a href="https://github.com/golang/go/issues/40724">This will change in golang 1.16 or 1.17</a> )</li>
  <li>golang functions can return multiple values.</li>
  <li>golang functions return their values on the stack, not through registers. They do this by placing the return values after all the parameters. So if you pass parameters into a function at <code class="language-plaintext highlighter-rouge">SP + 0x08</code> and <code class="language-plaintext highlighter-rouge">SP + 0x10</code> , then expect return values starting at <code class="language-plaintext highlighter-rouge">SP + 0x18</code> .</li>
  <li>Strings are returned as two words; a pointer and a length.</li>
</ul>

<p><em>I could go on forever, but instead <a href="https://dr-knz.net/go-calling-convention-x86-64.html">here’s a great page</a> explaining more if you want to learn.</em></p>

<p>I decided to go take a look at the <code class="language-plaintext highlighter-rouge">main.generate_key</code> and <code class="language-plaintext highlighter-rouge">main.generate_iv</code> functions.</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*tQ0c7gcujHuEYkIuBuwQRw.png" alt="" /></p>

<p>There’s a lot going on here. I can see something which I bet will be valuable, though: <code class="language-plaintext highlighter-rouge">strings.Repeat</code> . Let me narrow things down for you:</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*zY9U_qjHrkinDTJlI27NuQ.png" alt="" /></p>

<p>Here we store to <code class="language-plaintext highlighter-rouge">SP + 8</code> , <code class="language-plaintext highlighter-rouge">SP + 16</code> , and <code class="language-plaintext highlighter-rouge">SP + 24</code> . Then the function is called and we load from <code class="language-plaintext highlighter-rouge">SP + 32</code> and <code class="language-plaintext highlighter-rouge">SP + 40</code> . Remember what I was talking about with return values coming after the parameters? 32 comes directly after 24. So it looks like we pass in 3 parameters and get 2 out. Let’s look at the method signature for <code class="language-plaintext highlighter-rouge">strings.Repeat</code> :</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*ML5Bd9oqKJRcS8xHM8zQ4A.png" alt="" /></p>

<p><a href="https://golang.org/pkg/strings/#Repeat">https://golang.org/pkg/strings/#Repeat</a></p>

<p>So it takes in a string, and a count of how many times to repeat it. It returns a string. Remember also what I said about strings taking up two slots: a pointer and a length? So <code class="language-plaintext highlighter-rouge">SP + 8</code> is the input string pointer, <code class="language-plaintext highlighter-rouge">SP + 16</code> is the input string length, and <code class="language-plaintext highlighter-rouge">SP + 24</code> is the count.</p>

<p>We can see in the screenshot that <code class="language-plaintext highlighter-rouge">orr x0, xzr, #0x4</code> will cause <code class="language-plaintext highlighter-rouge">x0</code> to contain <code class="language-plaintext highlighter-rouge">0x04</code> , or simply <code class="language-plaintext highlighter-rouge">4</code> . <code class="language-plaintext highlighter-rouge">x0</code> is then stored in the count parameter position. So this string will repeat 4 times.</p>

<p>The return value is the repeated string; the pointer and the length. These are immediately returned from our <code class="language-plaintext highlighter-rouge">main.generate_key</code> function.</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*5ebEP5LcNQYk7XHTg33Rug.png" alt="" /></p>

<p><code class="language-plaintext highlighter-rouge">main.generate_iv</code> works very similar, except that we repeat 3 times, and then we load <code class="language-plaintext highlighter-rouge">runtime.rodata+0x3c7a0</code> , which turns out to be a single <code class="language-plaintext highlighter-rouge">0</code> character. This is appended onto the end of our repeated string via the <code class="language-plaintext highlighter-rouge">concatstring2</code> function call. I am guessing this is just a single character of padding, since IVs need to be the exact length of one plaintext block.</p>

<p>Where are <code class="language-plaintext highlighter-rouge">main.generate_key</code> and <code class="language-plaintext highlighter-rouge">main.generate_iv</code> called from? The answer is <code class="language-plaintext highlighter-rouge">main.setup_cipher</code> .</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*pN98SgGZNHrrIklzXb2_gQ.png" alt="" /></p>

<p>Here I have jumped right into the middle of it, but you can see that it is calling both functions in succession.</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*iZRIzIrFPU1BKHYdM5veBA.png" alt="" /></p>

<p>Still in <code class="language-plaintext highlighter-rouge">main.setup_cipher</code> , there are two very important function calls. One to <code class="language-plaintext highlighter-rouge">crypto/aes.NewCipher</code> and one to <code class="language-plaintext highlighter-rouge">crypto/cipher.NewCBCEncrypter</code> . This tells us that we are using AES CBC encryption! Now that we know which cipher and mode are being used, we can also infer some things:</p>
<ul>
  <li>AES-128 requires blocks to be 16 bytes in length, AES-192 requires 24, and AES-256 requires 32. Our key has to match this length, which means that the string we repeat 4 times must be of length 4, 6, or 8 bytes. We don’t know yet whether 128, 192, or 256 is being used.</li>
  <li>CBC requires that the IV be the same length as one block. This means our IV has to be either 16, 24, or 32 bytes in length. To figure out which one we are using, let’s consider the equation <code class="language-plaintext highlighter-rouge">3x + 1 = y</code> . Here, <code class="language-plaintext highlighter-rouge">x</code> represents the length of our input string that is multiplied by 3. <code class="language-plaintext highlighter-rouge">1</code> is for our single added <code class="language-plaintext highlighter-rouge">0</code> char padding. <code class="language-plaintext highlighter-rouge">y</code> is either 16, 24, or 32. Also, <code class="language-plaintext highlighter-rouge">x</code> must be an integer. The only way to solve this equation is with <code class="language-plaintext highlighter-rouge">x = 5, y = 16</code> . Therefore, we know that our block size is 16 bytes, so we are using AES-128 with a 16 byte key and a 16 byte IV.</li>
  <li>To summarize, our key input must be 4 bytes long, and our IV input must be 5 bytes long.</li>
</ul>

<p>This is really good information. All that’s left to do is figure out where these inputs come from. I already have a guess; remember the strings from the binary which mention that the key is based on latitude, and IV on longitude? Let’s keep that in mind.</p>

<p>Something I discovered while looking through <code class="language-plaintext highlighter-rouge">setup_cipher</code> is that it will branch to a panic if certain conditions fail. The panic message is quite valuable:</p>

<blockquote>
  <p>!!! EXPECTED TO FIND NMEA $GNGGA HEADER !!!</p>
</blockquote>

<p>Wow, what a message. So much excitement in that error. It reveals something super useful to us, though! This <code class="language-plaintext highlighter-rouge">setup_cipher</code> function takes in a <code class="language-plaintext highlighter-rouge">$GNGGA</code> NMEA header. This is raw information that GPS modules receive from satellites. Here’s an example of one of these headers:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$GNGGA,054157.013,2307.1261,N,12016.4308,E,1,6,1.93,34.9,M,17.8,M,,*76
</code></pre></div></div>

<p>And here’s the formatting:</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*6YJXvAn6U5UtyJcC7hdXSQ.png" alt="" /></p>

<p>Truncated a bit. Source: <a href="https://gpsd.gitlab.io/gpsd/NMEA.html#_gga_global_positioning_system_fix_data">https://gpsd.gitlab.io/gpsd/NMEA.html#_gga_global_positioning_system_fix_data</a></p>

<p>Look! Latitude and longitude. Look what else! 4 latitude characters (key) and 5 longitude characters (IV) . If you dig into the <code class="language-plaintext highlighter-rouge">main.setup_cipher</code> code a bit, you will find that it splits on <code class="language-plaintext highlighter-rouge">,</code> and <code class="language-plaintext highlighter-rouge">.</code> characters in a couple places. This allows it to extract the first 4 characters of the latitude and the first 5 characters of the longitude (stripping off the decimals) .</p>

<p>Keeping with the theme, I wrote a golang program using the same libraries in order to decrypt the log files. I plugged in <code class="language-plaintext highlighter-rouge">0513</code> as the latitude and <code class="language-plaintext highlighter-rouge">02459</code> as the longitude inputs, which come from the map image from many paragraphs ago. I then tried decrypting each log file. The newest two according to the dates in the file names were successfully decrypted!</p>

<p><img src="/assets/images/2021-02-01-nsa-codebreaker-2020-solutions/1*R48b21LQozkOxJnjLCDk_w.png" alt="" /></p>

<p>The very first block was messed up ( <code class="language-plaintext highlighter-rouge">$EHFKH</code> is not a valid NMEA header) and I guess that’s because my IV was incorrect somehow. I never took the time to figure out the real IV because it didn’t matter; all the rest of the data was correct and had the info I needed.</p>

<p>Let’s remember our goal: find the coordinates of the kidnappers’ headquarters. If I had to guess, I’d say that these two newer files are from when the drones were flying around looking for our journalist before the kidnapping. The two older files must be from flights taken at the headquarters.</p>

<p>I suppose that I could try graphing the NMEA files and see if that leads me to their coordinates, but I had an easier idea: bruteforce! It’s only a 4 character key and I don’t need the IV, so I just guessed all values from <code class="language-plaintext highlighter-rouge">0000</code> to <code class="language-plaintext highlighter-rouge">9999</code> and checked if the output looked right. Here’s what I wrote:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span>

<span class="k">import</span> <span class="p">(</span>
	<span class="s">"crypto/aes"</span>
	<span class="s">"crypto/cipher"</span>
	<span class="s">"fmt"</span>
	<span class="s">"io/ioutil"</span>
	<span class="s">"strings"</span>
<span class="p">)</span>

<span class="k">const</span> <span class="n">nmeaLat</span> <span class="o">=</span> <span class="s">"0521"</span>
<span class="k">const</span> <span class="n">nmeaLong</span> <span class="o">=</span> <span class="s">"02459"</span>
<span class="k">const</span> <span class="n">logName</span> <span class="o">=</span> <span class="s">"logs/20200628_153027.log"</span>

<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
	<span class="n">decryptKnown</span><span class="p">()</span>
	<span class="c">//bruteforce()</span>
<span class="p">}</span>

<span class="k">func</span> <span class="n">decryptKnown</span><span class="p">()</span> <span class="p">{</span>
	<span class="n">ciphertext</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">ioutil</span><span class="o">.</span><span class="n">ReadFile</span><span class="p">(</span><span class="n">logName</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="nb">panic</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
	<span class="p">}</span>
	<span class="n">plaintext</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">([]</span><span class="kt">byte</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">ciphertext</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">ciphertext</span><span class="p">))</span>

	<span class="n">key</span> <span class="o">:=</span> <span class="n">strings</span><span class="o">.</span><span class="n">Repeat</span><span class="p">(</span><span class="n">nmeaLat</span><span class="p">,</span> <span class="m">4</span><span class="p">)</span>
	<span class="n">IV</span> <span class="o">:=</span> <span class="n">strings</span><span class="o">.</span><span class="n">Repeat</span><span class="p">(</span><span class="n">nmeaLong</span><span class="p">,</span> <span class="m">3</span><span class="p">)</span> <span class="o">+</span> <span class="s">"0"</span>
	<span class="n">block</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">aes</span><span class="o">.</span><span class="n">NewCipher</span><span class="p">([]</span><span class="kt">byte</span><span class="p">(</span><span class="n">key</span><span class="p">))</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="nb">panic</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
	<span class="p">}</span>

	<span class="n">cbc</span> <span class="o">:=</span> <span class="n">cipher</span><span class="o">.</span><span class="n">NewCBCDecrypter</span><span class="p">(</span><span class="n">block</span><span class="p">,</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">(</span><span class="n">IV</span><span class="p">))</span>
	<span class="n">cbc</span><span class="o">.</span><span class="n">CryptBlocks</span><span class="p">(</span><span class="n">plaintext</span><span class="p">,</span> <span class="n">ciphertext</span><span class="p">)</span>

	<span class="n">err</span> <span class="o">=</span> <span class="n">ioutil</span><span class="o">.</span><span class="n">WriteFile</span><span class="p">(</span><span class="n">logName</span><span class="o">+</span><span class="s">".dec"</span><span class="p">,</span> <span class="n">plaintext</span><span class="p">,</span> <span class="m">0644</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="nb">panic</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="k">func</span> <span class="n">bruteforce</span><span class="p">()</span> <span class="p">{</span>
	<span class="n">ciphertext</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">ioutil</span><span class="o">.</span><span class="n">ReadFile</span><span class="p">(</span><span class="n">logName</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="nb">panic</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
	<span class="p">}</span>
	<span class="n">plaintext</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">([]</span><span class="kt">byte</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">ciphertext</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">ciphertext</span><span class="p">))</span>

	<span class="k">for</span> <span class="n">i</span> <span class="o">:=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="m">9999</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span> <span class="p">{</span>
		<span class="n">keyInput</span> <span class="o">:=</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"%04d"</span><span class="p">,</span> <span class="n">i</span><span class="p">)</span>
		<span class="n">key</span> <span class="o">:=</span> <span class="n">strings</span><span class="o">.</span><span class="n">Repeat</span><span class="p">(</span><span class="n">keyInput</span><span class="p">,</span> <span class="m">4</span><span class="p">)</span>
		<span class="n">IV</span> <span class="o">:=</span> <span class="n">strings</span><span class="o">.</span><span class="n">Repeat</span><span class="p">(</span><span class="s">"00000"</span><span class="p">,</span> <span class="m">3</span><span class="p">)</span> <span class="o">+</span> <span class="s">"0"</span>
		<span class="n">block</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">aes</span><span class="o">.</span><span class="n">NewCipher</span><span class="p">([]</span><span class="kt">byte</span><span class="p">(</span><span class="n">key</span><span class="p">))</span>
		<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
			<span class="nb">panic</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
		<span class="p">}</span>

		<span class="n">cbc</span> <span class="o">:=</span> <span class="n">cipher</span><span class="o">.</span><span class="n">NewCBCDecrypter</span><span class="p">(</span><span class="n">block</span><span class="p">,</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">(</span><span class="n">IV</span><span class="p">))</span>
		<span class="n">cbc</span><span class="o">.</span><span class="n">CryptBlocks</span><span class="p">(</span><span class="n">plaintext</span><span class="p">,</span> <span class="n">ciphertext</span><span class="p">)</span>

		<span class="k">if</span> <span class="kt">string</span><span class="p">(</span><span class="n">plaintext</span><span class="p">[</span><span class="m">28</span><span class="o">:</span><span class="m">31</span><span class="p">])</span> <span class="o">==</span> <span class="s">",N,"</span> <span class="p">{</span>
			<span class="n">fmt</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"Found key: %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">keyInput</span><span class="p">)</span>
			<span class="k">return</span>
		<span class="p">}</span>
	<span class="p">}</span>

<span class="p">}</span>
</code></pre></div></div>

<p>In less than a second, I had the correct key to the older log files: 0521. Not far off from our original 0513! They must be nearby.</p>

<p>I opened the decrypted log file and obtained these decimal coordinates:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0521.073024 N 02418.706995 W
</code></pre></div></div>

<p>So now we had our answer; the CTF asks for the answer in the format <code class="language-plaintext highlighter-rouge">##°##'N ##°##'W</code>, so my answer was <code class="language-plaintext highlighter-rouge">05°21'N 24°18'W</code>. Auē, that was a lot of work.</p>

<hr />

<h2 id="tasks-6-9">Tasks 6-9</h2>

<p>I ran out of time for these, but there’s lots of solutions on Google. I hope you enjoyed reading my writeup! If you have any questions, feel free to ask. I’m always happy to help.</p>]]></content><author><name>Mitch Talmadge</name></author><category term="programming" /><category term="hacking" /><category term="ctf" /><category term="capture-the-flag" /><category term="cryptography" /><summary type="html"><![CDATA[I explain my methods to solving the 2020 NSA Codebreaker CTF]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://mitchtalmadge.com/assets/2021-02-01-nsa-codebreaker-2020-solutions/0*09tAanAlUKxktlBU.JPG" /><media:content medium="image" url="https://mitchtalmadge.com/assets/2021-02-01-nsa-codebreaker-2020-solutions/0*09tAanAlUKxktlBU.JPG" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">bulk-export script for adobe illustrator</title><link href="https://mitchtalmadge.com/2019/11/27/bulk-export-script-for-adobe-illustrator.html" rel="alternate" type="text/html" title="bulk-export script for adobe illustrator" /><published>2019-11-27T09:04:49+00:00</published><updated>2019-11-27T09:04:49+00:00</updated><id>https://mitchtalmadge.com/2019/11/27/bulk-export-script-for-adobe-illustrator</id><content type="html" xml:base="https://mitchtalmadge.com/2019/11/27/bulk-export-script-for-adobe-illustrator.html"><![CDATA[<p><em>Note: This was never tested on a Mac; it was created with Windows. your mileage may vary.</em></p>

<p>I create a lot of logos in Illustrator. The most painful part is always at the end, when I have to export my work to the many formats that life demands. In the past, I would export each size I needed one at a time, or try to make use of the frustrating “Export for Screens” feature. Enough was enough.</p>

<blockquote>
  <p>The great thing about being an engineer is that if something isn’t exactly how you want it… you just make it exactly how you want it.</p>
</blockquote>

<p>So I did. Here’s a script that takes a logo like this:</p>

<p><img src="/assets/images/2019-11-27-bulk-export-script-for-adobe-illustrator/1*vBGx3AaHp4qJJN2tP0gXgw.png" alt="" /></p>

<p>… and exports it into something like this:</p>

<p><img src="/assets/images/2019-11-27-bulk-export-script-for-adobe-illustrator/1*4LLTUjaBw4yMA8cFWtjEuQ.png" alt="" /></p>

<p>More specifically, the resulting file structure looks like this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Drawing.ai
JPG
  Drawing-16px.jpg
  Drawing-32px.jpg
  Drawing-50px.jpg
  Drawing-64px.jpg
  Drawing-100px.jpg
  Drawing-150px.jpg
  Drawing-256px.jpg
  Drawing-300px.jpg
  Drawing-512px.jpg
  Drawing-1024px.jpg
PNG
  Drawing-16px.png
  Drawing-32px.png
  Drawing-50px.png
  Drawing-64px.png
  Drawing-100px.png
  Drawing-150px.png
  Drawing-256px.png
  Drawing-300px.png
  Drawing-512px.png
  Drawing-1024px.png
SVG
  Drawing.svg
</code></pre></div></div>

<p>Nice and organized! I picked the dimensions I use most, but you can of course edit the script and pick the ones you need.</p>

<p>Convinced? Here’s the script and instructions:</p>
<h2 id="the-script">The Script</h2>
<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">#</span><span class="nx">target</span> <span class="nx">Illustrator</span>

<span class="cm">/**
 * This script will export an Illustrator file into multiple sizes of multiple file types.
 * @author Mitch Talmadge ( https://MitchTalmadge.com )
 */</span>

<span class="k">if</span> <span class="p">(</span><span class="nx">app</span><span class="p">.</span><span class="nx">documents</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">main</span><span class="p">();</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
  <span class="nx">Window</span><span class="p">.</span><span class="nx">alert</span><span class="p">(</span><span class="dl">"</span><span class="s2">Cancelled export.</span><span class="dl">"</span><span class="p">);</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">var</span> <span class="nx">sizes</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1024</span><span class="p">,</span> <span class="mi">512</span><span class="p">,</span> <span class="mi">300</span><span class="p">,</span> <span class="mi">256</span><span class="p">,</span> <span class="mi">150</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">64</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">32</span><span class="p">,</span> <span class="mi">16</span><span class="p">];</span>
  <span class="kd">var</span> <span class="nb">document</span> <span class="o">=</span> <span class="nx">app</span><span class="p">.</span><span class="nx">activeDocument</span><span class="p">;</span>
  <span class="kd">var</span> <span class="nx">afile</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">fullName</span><span class="p">;</span>
  <span class="kd">var</span> <span class="nx">filename</span> <span class="o">=</span> <span class="nx">afile</span><span class="p">.</span><span class="nx">name</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">.</span><span class="dl">'</span><span class="p">)[</span><span class="mi">0</span><span class="p">];</span>

  <span class="kd">var</span> <span class="nx">svgFolder</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Folder</span><span class="p">(</span><span class="nx">afile</span><span class="p">.</span><span class="nx">parent</span><span class="p">.</span><span class="nx">fsName</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">/SVG</span><span class="dl">"</span><span class="p">);</span>
  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">svgFolder</span><span class="p">.</span><span class="nx">exists</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">svgFolder</span><span class="p">.</span><span class="nx">create</span><span class="p">();</span>
  <span class="p">}</span>

  <span class="kd">var</span> <span class="nx">pngFolder</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Folder</span><span class="p">(</span><span class="nx">afile</span><span class="p">.</span><span class="nx">parent</span><span class="p">.</span><span class="nx">fsName</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">/PNG</span><span class="dl">"</span><span class="p">);</span>
  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">pngFolder</span><span class="p">.</span><span class="nx">exists</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">pngFolder</span><span class="p">.</span><span class="nx">create</span><span class="p">();</span>
  <span class="p">}</span>

  <span class="kd">var</span> <span class="nx">jpgFolder</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Folder</span><span class="p">(</span><span class="nx">afile</span><span class="p">.</span><span class="nx">parent</span><span class="p">.</span><span class="nx">fsName</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">/JPG</span><span class="dl">"</span><span class="p">);</span>
  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">jpgFolder</span><span class="p">.</span><span class="nx">exists</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">jpgFolder</span><span class="p">.</span><span class="nx">create</span><span class="p">();</span>
  <span class="p">}</span>

  <span class="nx">Window</span><span class="p">.</span><span class="nx">alert</span><span class="p">(</span><span class="dl">"</span><span class="s2">Press OK to begin exporting.</span><span class="dl">"</span><span class="p">);</span>

  <span class="kd">var</span> <span class="nx">size</span><span class="p">,</span> <span class="nx">file</span><span class="p">;</span>

  <span class="k">if</span> <span class="p">(</span><span class="nx">svgFolder</span> <span class="o">!=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nx">options</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ExportOptionsSVG</span><span class="p">();</span>
    <span class="nx">options</span><span class="p">.</span><span class="nx">cssProperties</span> <span class="o">=</span> <span class="nx">SVGCSSPropertyLocation</span><span class="p">.</span><span class="nx">PRESENTATIONATTRIBUTES</span><span class="p">;</span>
    <span class="nx">options</span><span class="p">.</span><span class="nx">documentEncoding</span> <span class="o">=</span> <span class="nx">SVGDocumentEncoding</span><span class="p">.</span><span class="nx">UTF8</span><span class="p">;</span>
    <span class="nx">options</span><span class="p">.</span><span class="nx">fontType</span> <span class="o">=</span> <span class="nx">SVGFontType</span><span class="p">.</span><span class="nx">OUTLINEFONT</span><span class="p">;</span>
    <span class="nx">options</span><span class="p">.</span><span class="nx">fontSubsetting</span> <span class="o">=</span> <span class="nx">SVGFontSubsetting</span><span class="p">.</span><span class="nx">None</span><span class="p">;</span>
    <span class="nx">options</span><span class="p">.</span><span class="nx">preserveEditability</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
    <span class="nx">options</span><span class="p">.</span><span class="nx">embedRasterImages</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>

    <span class="nx">file</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">File</span><span class="p">(</span><span class="nx">svgFolder</span><span class="p">.</span><span class="nx">fsName</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">filename</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">.svg</span><span class="dl">"</span><span class="p">);</span>

    <span class="nb">document</span><span class="p">.</span><span class="nx">exportFile</span><span class="p">(</span><span class="nx">file</span><span class="p">,</span> <span class="nx">ExportType</span><span class="p">.</span><span class="nx">SVG</span><span class="p">,</span> <span class="nx">options</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="k">if</span> <span class="p">(</span><span class="nx">pngFolder</span> <span class="o">!=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nx">options</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ExportOptionsPNG24</span><span class="p">();</span>
    <span class="nx">options</span><span class="p">.</span><span class="nx">antiAliasing</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
    <span class="nx">options</span><span class="p">.</span><span class="nx">transparency</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
    <span class="nx">options</span><span class="p">.</span><span class="nx">artBoardClipping</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>

    <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">sizes</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">size</span> <span class="o">=</span> <span class="nx">sizes</span><span class="p">[</span><span class="nx">i</span><span class="p">];</span>

      <span class="nx">file</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">File</span><span class="p">(</span><span class="nx">pngFolder</span><span class="p">.</span><span class="nx">fsName</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">filename</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">-</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">size</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">px.png</span><span class="dl">"</span><span class="p">);</span>

      <span class="kd">var</span> <span class="nx">scale</span> <span class="o">=</span> <span class="nx">size</span> <span class="o">/</span> <span class="nb">document</span><span class="p">.</span><span class="nx">height</span><span class="p">;</span>

      <span class="k">if</span> <span class="p">(</span><span class="nx">scale</span> <span class="o">&lt;=</span> <span class="mf">7.76</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">options</span><span class="p">.</span><span class="nx">verticalScale</span> <span class="o">=</span> <span class="mi">100</span> <span class="o">*</span> <span class="nx">scale</span><span class="p">;</span>
        <span class="nx">options</span><span class="p">.</span><span class="nx">horizontalScale</span> <span class="o">=</span> <span class="mi">100</span> <span class="o">*</span> <span class="nx">scale</span><span class="p">;</span>

        <span class="nb">document</span><span class="p">.</span><span class="nx">exportFile</span><span class="p">(</span><span class="nx">file</span><span class="p">,</span> <span class="nx">ExportType</span><span class="p">.</span><span class="nx">PNG24</span><span class="p">,</span> <span class="nx">options</span><span class="p">);</span>
      <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="nx">Window</span><span class="p">.</span><span class="nx">alert</span><span class="p">(</span><span class="dl">"</span><span class="s2">Cannot scale to required size. Artboard too small.</span><span class="dl">"</span><span class="p">);</span>
        <span class="nx">reopenDocument</span><span class="p">(</span><span class="nb">document</span><span class="p">,</span> <span class="nx">afile</span><span class="p">);</span>
        <span class="k">return</span><span class="p">;</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="k">if</span> <span class="p">(</span><span class="nx">jpgFolder</span> <span class="o">!=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nx">options</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ExportOptionsJPEG</span><span class="p">();</span>
    <span class="nx">options</span><span class="p">.</span><span class="nx">antiAliasing</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
    <span class="nx">options</span><span class="p">.</span><span class="nx">qualitySetting</span> <span class="o">=</span> <span class="mi">100</span><span class="p">;</span>
    <span class="nx">options</span><span class="p">.</span><span class="nx">optimization</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
    <span class="nx">options</span><span class="p">.</span><span class="nx">artBoardClipping</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>

    <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">sizes</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">size</span> <span class="o">=</span> <span class="nx">sizes</span><span class="p">[</span><span class="nx">i</span><span class="p">];</span>

      <span class="nx">file</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">File</span><span class="p">(</span><span class="nx">jpgFolder</span><span class="p">.</span><span class="nx">fsName</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">filename</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">-</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">size</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">px.jpg</span><span class="dl">"</span><span class="p">);</span>

      <span class="kd">var</span> <span class="nx">scale</span> <span class="o">=</span> <span class="nx">size</span> <span class="o">/</span> <span class="nb">document</span><span class="p">.</span><span class="nx">height</span><span class="p">;</span>

      <span class="k">if</span> <span class="p">(</span><span class="nx">scale</span> <span class="o">&lt;=</span> <span class="mf">7.76</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">options</span><span class="p">.</span><span class="nx">verticalScale</span> <span class="o">=</span> <span class="mi">100</span> <span class="o">*</span> <span class="nx">scale</span><span class="p">;</span>
        <span class="nx">options</span><span class="p">.</span><span class="nx">horizontalScale</span> <span class="o">=</span> <span class="mi">100</span> <span class="o">*</span> <span class="nx">scale</span><span class="p">;</span>

        <span class="nb">document</span><span class="p">.</span><span class="nx">exportFile</span><span class="p">(</span><span class="nx">file</span><span class="p">,</span> <span class="nx">ExportType</span><span class="p">.</span><span class="nx">JPEG</span><span class="p">,</span> <span class="nx">options</span><span class="p">);</span>
      <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="nx">Window</span><span class="p">.</span><span class="nx">alert</span><span class="p">(</span><span class="dl">"</span><span class="s2">Cannot scale to required size. Artboard too small.</span><span class="dl">"</span><span class="p">);</span>
        <span class="nx">reopenDocument</span><span class="p">(</span><span class="nb">document</span><span class="p">,</span> <span class="nx">afile</span><span class="p">);</span>
        <span class="k">return</span><span class="p">;</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="nx">Window</span><span class="p">.</span><span class="nx">alert</span><span class="p">(</span><span class="dl">"</span><span class="s2">Images have been exported!</span><span class="dl">"</span><span class="p">);</span>

  <span class="nx">reopenDocument</span><span class="p">(</span><span class="nb">document</span><span class="p">,</span> <span class="nx">afile</span><span class="p">);</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">reopenDocument</span><span class="p">(</span><span class="nb">document</span><span class="p">,</span> <span class="nx">afile</span><span class="p">)</span> <span class="p">{</span>
  <span class="nb">document</span><span class="p">.</span><span class="nx">close</span><span class="p">(</span><span class="nx">SaveOptions</span><span class="p">.</span><span class="nx">DONOTSAVECHANGES</span><span class="p">);</span>
  <span class="nx">app</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="nx">afile</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Save it as a jsx file, such as <code class="language-plaintext highlighter-rouge">Illustrator_Drawing_Exporter.jsx</code> .</p>
<h2 id="usage--installation">Usage / Installation</h2>

<p>There are three ways to use the script.</p>
<ol>
  <li>With an Illustrator drawing already open and in-view, right click on the jsx file and open it with Illustrator. You will be prompted to begin exporting.</li>
  <li>Inside Illustrator, with the drawing in-view, go to <code class="language-plaintext highlighter-rouge">File -&gt; Scripts -&gt; Other Script…</code> and open the jsx file from here.</li>
  <li>To add the script to the <code class="language-plaintext highlighter-rouge">File -&gt; Scripts</code> menu permanently, open the Illustrator install directory (you’ll have to find it yourself, it varies), and put the script inside <code class="language-plaintext highlighter-rouge">Presets/en_US/Scripts</code> . Now restart Illustrator and you can use the script at any time from the <code class="language-plaintext highlighter-rouge">File -&gt; Scripts</code> menu.</li>
</ol>

<p>Hope this is helpful, let me know if you have questions!</p>]]></content><author><name>Mitch Talmadge</name></author><category term="illustrator" /><category term="adobe" /><category term="export" /><category term="programming" /><category term="design" /><summary type="html"><![CDATA[A little script to automagically export your drawing to PNG, JPG, and SVG in many sizes.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://mitchtalmadge.com/assets/2019-11-27-bulk-export-script-for-adobe-illustrator/1*4LLTUjaBw4yMA8cFWtjEuQ.png" /><media:content medium="image" url="https://mitchtalmadge.com/assets/2019-11-27-bulk-export-script-for-adobe-illustrator/1*4LLTUjaBw4yMA8cFWtjEuQ.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">hacking into a sharp mx copier</title><link href="https://mitchtalmadge.com/2018/06/26/hacking-into-a-sharp-mx-copier.html" rel="alternate" type="text/html" title="hacking into a sharp mx copier" /><published>2018-06-26T19:52:03+00:00</published><updated>2018-06-26T20:04:09+00:00</updated><id>https://mitchtalmadge.com/2018/06/26/hacking-into-a-sharp-mx-copier</id><content type="html" xml:base="https://mitchtalmadge.com/2018/06/26/hacking-into-a-sharp-mx-copier.html"><![CDATA[<p><img src="/assets/images/2018-06-26-hacking-into-a-sharp-mx-copier/1*Rme10nfDkl3YWvy4eqR7uQ.jpeg" alt="" /></p>

<p>In my spare time, I do web and IT work for a local school. In the main office, there is a SHARP MX-5141 copier which has used the default admin password for as long as it has been installed. Recently, someone changed the password and didn’t tell anyone. We needed into the admin settings, but every password we tried did not work. To make matters worse, the copier company would have taken three days to come out and perform a factory reset.</p>

<p>I took this opportunity to try and hack my way into the printer. Hardware like this are notorious for having security vulnerabilities, so I figured I might have some hope. Some searching across Google did not yield me any answers, and I was on my own.</p>
<h3 id="communicating-with-the-printer">Communicating with the Printer</h3>

<p><img src="/assets/images/2018-06-26-hacking-into-a-sharp-mx-copier/1*HyDsQPobWGWWCdmDQSmeNA.jpeg" alt="" /></p>

<p>The first step was to find a way to talk to the printer via my computer, since the built-in display was far too limited. Almost every copier of this size has a web interface that can be accessed on port 80. Through Windows’ printer scanner, I was able to find the printer on the network. However, I kept getting connection refused errors when trying to connect.</p>

<p>I used <strong>nmap</strong> to scan the most common ports of the printer, but again, nothing was getting through. I chalked it up to the school’s firewall, and decided to use alternative methods.</p>

<p>The printer is hardwired into the school’s network via an ethernet cable. I plugged this cable into my own laptop and set up an ad-hoc network. Then I was able to access all open ports on the printer, including the web interface! Success :)</p>
<h3 id="the-web-interface">The Web Interface</h3>

<p>This copier has two built-in admin accounts: “admin” and “sysadmin”. The default passwords for these accounts are the same as their usernames. The sysadmin account can only be accessed from the web interface, which meant that the password was unchanged. I was able to login as sysadmin, however I was not allowed to change the admin password from this account. Technically, for our needs, I could have stopped here. However, I am not satisfied with “good enough.” If I couldn’t change the password, maybe I could guess it.</p>

<p>As the sysadmin, I was able to open and close ports at will. I found the telnet port to be disabled, and I opened it up.</p>
<h3 id="telnet-dictionary-attack">Telnet Dictionary Attack</h3>

<p><img src="/assets/images/2018-06-26-hacking-into-a-sharp-mx-copier/1*xtTPiigU5lHuYCjoKoxq-A.png" alt="" /></p>

<p>Now that I had access to telnet, I had a way to bruteforce the admin password without rate limiting. I used <strong>patator</strong> on an Ubuntu virtual machine to try the 10,000 most common passwords. Unfortunately, this did not yield any results. None of the passwords worked. I was tired of waiting around, so I came up with a new idea.</p>
<h3 id="return-to-the-web-interface">Return to The Web Interface</h3>

<p>For some reason, there seems to be a trend of not validating user input in embedded software, especially among Asian companies like SHARP. With this in mind, I decided to try changing the password via an HTTP POST request. I first opened the Chrome debugger and enabled network logging. Then, I changed my own password as sysadmin. Copying the request format, I changed the ID of the user from the sysadmin to the admin.</p>

<p>Lo and behold, it worked!! The admin password was changed by an account which did not have the privileges to do so, because the backend web framework did not include user form validation. A rookie mistake.</p>

<p>I was unable to repeat the attack when signed in as a standard user, but I have a feeling most people forget to change the sysadmin password anyway.</p>]]></content><author><name>Mitch Talmadge</name></author><category term="security" /><category term="hacking" /><category term="printers" /><category term="networking" /><summary type="html"><![CDATA[I change the admin password on a SHARP MX copier through an account that did not have permission to do so.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://mitchtalmadge.com/assets/2018-06-26-hacking-into-a-sharp-mx-copier/1*Rme10nfDkl3YWvy4eqR7uQ.jpeg" /><media:content medium="image" url="https://mitchtalmadge.com/assets/2018-06-26-hacking-into-a-sharp-mx-copier/1*Rme10nfDkl3YWvy4eqR7uQ.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">reverse engineering a rigged coupon wheel-of-fortune</title><link href="https://mitchtalmadge.com/2018/05/30/reverse-engineering-a-rigged-coupon-wheel-of-fortune.html" rel="alternate" type="text/html" title="reverse engineering a rigged coupon wheel-of-fortune" /><published>2018-05-30T02:45:13+00:00</published><updated>2020-04-01T02:45:33+00:00</updated><id>https://mitchtalmadge.com/2018/05/30/reverse%20engineering-a-rigged-coupon-wheel-of-fortune</id><content type="html" xml:base="https://mitchtalmadge.com/2018/05/30/reverse-engineering-a-rigged-coupon-wheel-of-fortune.html"><![CDATA[<p><img src="/assets/images/2018-05-29-how-companies-like-hush-blankets-are-taking-advantage-of-your-emotions-for-profit/0*05Unn77opyN0qkWl.png" alt="" /></p>

<p>While doing productive work on the internet like <a href="https://reddit.com/r/CatSlaps">watching cats slap each other</a> , I happened upon a company called <a href="https://hushblankets.com/"><em>Hush Blankets</em></a> that creates weighted blankets to help with sleep and anxiety. What an awesome idea! I spent a couple minutes browsing through the website when — all of a sudden — a giant wheel of fortune came popping out of the left side of the screen.</p>

<blockquote>
  <p>“You unlocked a <strong>special bonus,” it proclaimed!</strong></p>
</blockquote>

<p>Gasp! The wheel was covered in coupons promising $10, $15, $20, $30, OFF— or even a FREE blanket! Considering that these blankets go for nearly $200 each, a free blanket would be pretty darn cool. All I had to do was enter my email and press the button! My mom always told me I was special. 😋 <em>(I’m not sure who this “Ed” guy is though. )</em></p>

<p>“Okay,” I thought, “I have nothing to lose.” So, naturally, I entered a fake email address and hit enter. The wheel instantly came to life! It spun slower and slower until it began to get <strong>STUCK <em>RIgHt ON tHe EdGE</em></strong> of “Almost” and “$10 OFF”!!…</p>

<p>gasp!!! <strong>I WON!</strong></p>

<p><img src="/assets/images/2018-05-29-how-companies-like-hush-blankets-are-taking-advantage-of-your-emotions-for-profit/1*FWyh1ZaslsdekoazknsKGg.png" alt="" /></p>

<p><strong>Or did I?</strong></p>

<p>As both a software engineer and an entrepreneur, I know two things very well:</p>
<ol>
  <li>Randomness (so called “luck” 🙄) and computers don’t mix.</li>
  <li>No company would ever give away a $200 product for free just because some lucky bastard clicked a button. Not if they can help it.</li>
</ol>

<p>Needless to say, I was skeptical. I was also intrigued. I wanted to know how the wheel <em>really</em> worked. Was the prize predetermined? Could you <em>really super truly</em> win a FREE blanket?! I started digging.</p>

<p>Betting on my intuition that a blanket company wouldn’t code up a custom wheel of fortune just for coupons, I hopped right into the Chrome Debugger’s “Sources” section. This allows me to see all of the scripts currently loaded on the page. Glancing through the URLs, I quickly noticed one with the word “wheelio.” Hmm… wheelio… “wheel”… yeah, that seems like a good place to start.</p>

<p><img src="/assets/images/2018-05-29-how-companies-like-hush-blankets-are-taking-advantage-of-your-emotions-for-profit/1*F7Ee_kjhnfW14XIFHpTNtg.png" alt="wheeeeelio" /></p>

<p>wheeeeelio</p>

<p>Without even looking at the source code, I punched “Wheelio” into Google, and… oh. That was easy.</p>

<p><img src="/assets/images/2018-05-29-how-companies-like-hush-blankets-are-taking-advantage-of-your-emotions-for-profit/1*5e1l1Ys3KO_KremXCwTZUg.png" alt="" /></p>

<p><strong>“They spin, you win.”</strong> That tagline doesn’t sit well with me. Feels kinda disturbing. Let’s dig deeper. Here’s an explanation on the website of why you should be using Wheelio:</p>

<blockquote>
  <p>Purchase behavior psychology is a strong thing…</p>
</blockquote>

<blockquote>
  <p>People don’t really like easy free stuff. Not when it comes to coupons and usage of coupons. They need to feel like they “won” and the coupon was hard earned. When they feel like they have the upper hand, the usage of coupons is 10X higher than as of an ordinary coupon.</p>
</blockquote>

<p><strong>TL;DR:</strong> You click the button, it makes you feel like you’ve won, so you’re more likely to use the coupon.</p>

<p><em>Why does this feel emotionally manipulative to me?</em> Back to that in a second. Let’s keep going, because I’m really eager to get my hands on a free blanket.</p>

<p>I was curious if the prize was determined in my web browser or on a remote server somewhere. Could I trick my browser into giving me any prize on the wheel? I figured that the best way to find out would be to monitor the network traffic. I refreshed the page and the data came flowing in:</p>

<p><img src="/assets/images/2018-05-29-how-companies-like-hush-blankets-are-taking-advantage-of-your-emotions-for-profit/1*bk5y0ntuO-YtWltyBe4uRw.png" alt="Ooh, pretty colors" /></p>

<p>Ooh, pretty colors</p>

<p>I filtered down the results to only those that included “wheelio”:</p>

<p><img src="/assets/images/2018-05-29-how-companies-like-hush-blankets-are-taking-advantage-of-your-emotions-for-profit/1*57ff-FAfsgPeY92hSTABpg.png" alt="" /></p>

<p>Oh! A websocket? This means that the server and browser are having an ongoing conversation. Let’s listen in. 😈</p>

<p><img src="/assets/images/2018-05-29-how-companies-like-hush-blankets-are-taking-advantage-of-your-emotions-for-profit/1*IyxmabMu8SqotciVSoZlVQ.png" alt="" /></p>

<p>Here’s what the server and browser are saying to each other. Most of these frames (messages) look pretty useless to me, but one of them is being cut off. Judging by the length, it probably contains some  <em>juicy details.</em> A little copying there, some formatting here, and…</p>

<p><img src="/assets/images/2018-05-29-how-companies-like-hush-blankets-are-taking-advantage-of-your-emotions-for-profit/1*Rzrz_mxbgqGQ9-eiLhFLIQ.png" alt="" /></p>

<p>Check it out! This must be the information that is used to populate the wheel with all the labels, like “$10 OFF Coupon”. Not only can you see the labels, but you can see the <strong>coupon code</strong> and the <strong>“gravity”</strong> (i.e. probability) for each coupon.</p>

<p>Now about that free blanket… The gravity for the free blanket coupon is 0! <em>That means you can never land on it!</em> In fact, this is true of the $30 and $20 coupons too!</p>

<p>I even tried checking out with their respective codes: <code class="language-plaintext highlighter-rouge">000-000-000</code> , <code class="language-plaintext highlighter-rouge">30OFF</code> , and <code class="language-plaintext highlighter-rouge">WELCOMEBACK</code> , but only the $20 coupon worked.</p>

<p>What really irks me is that they outright say “You have a chance to win up to $30 OFF.” This is a blatant lie. Not only this, but there is a progress bar on the popup which says “12 coupons left. Hurry up!”, but this number is hard-coded into the popup! It says 12 for everyone, forever! Another lie. 😡</p>

<p>So, back to the whole emotional manipulation thing. I fail to understand how a CEO could sleep at night knowing that they are purposely toying with their customers’ emotions by using these kinds of tactics. Not to mention that the target market for Hush Blankets may already be struggling with their emotions to begin with. A wheel of fortune is supposed to have near-equal probability for all landing spaces. Wheelio has intentionally designed this app to be rigged, and they boast about it!</p>

<p>Hush Blanket isn’t the only company doing this, either. Take a stroll through the <a href="https://apps.shopify.com/wheelio-first-interactive-exit-intent-pop-up#reviews-heading">over 350 reviews on the Shopify app page</a> and you’ll find many many more companies using this tactic. Companies like <a href="https://www.heycasey.co.za"><em>Hey Casey!</em></a> which lists a 20% off coupon with 0 gravity, or <a href="https://wavyprints.com"><em>Wavy Prints</em></a> which has a free print coupon with 0 gravity. Interestingly, whether by accident or not, <em>Wavy Prints</em> did not actually disable their free print coupon, so I took the liberty of ordering their largest print of <strong>Kanye Crossing the Alps:</strong></p>

<p><img src="/assets/images/2018-05-29-how-companies-like-hush-blankets-are-taking-advantage-of-your-emotions-for-profit/1*jyMelakbUqTKVFZ3umDkGA.png" alt="" /></p>

<p>Two days later, Wavy Prints took their website offline, presumably because they thought they were hacked.</p>

<p>Karma is a bitch.</p>
<h2 id="update-mar-31-2020-privy-enters-the-arena">Update Mar. 31, 2020: Privy Enters the Arena</h2>

<p>Let’s shame another company! Today one of my readers came across a clothing company called <a href="http://pearlfeet.com">Pearlfeet</a> which uses a very similar coupon wheel. This one has options like “ <strong>100% Off</strong> ,” “ <strong>Free shipping</strong> ,” and more! However, what I quickly found out is that this wheel is rigged just like the rest. This wheel is not hosted by Wheelio like the other sites, instead it uses a new service called <a href="https://www.privy.com/spin-to-win-examples">Privy</a> , which also works on Shopify sites.</p>

<p>In this case, you enter your email to get spammed by their junk newsletter, and then the wheel always lands on the “ <strong>10% Off</strong> ” coupon. Check it out:</p>

<p><img src="/assets/images/2018-05-29-how-companies-like-hush-blankets-are-taking-advantage-of-your-emotions-for-profit/1*fASla9qnBuiAyuvVqwHjxw.gif" alt="" /></p>

<p>Once it’s done spinning, a “ <strong>Thanks!</strong> ” dialog box pops up with the code “ <strong>PF10</strong> ”:</p>

<p><img src="/assets/images/2018-05-29-how-companies-like-hush-blankets-are-taking-advantage-of-your-emotions-for-profit/1*HCn7DrnFGX1AJbNpAaPZbg.png" alt="" /></p>

<p>I did some quick digging to see what was going on here. First of all, while I cannot view the coupon codes of the wheel since the developers of Privy were smarter than Wheelio and decided to keep that information server-sided rather than download it all into the client’s browser, I did notice that the returned prize had a win ratio of <code class="language-plaintext highlighter-rouge">1</code> (signifying a 100% probability that it will land on the 10% off coupon… typical) and even more interesting, the coupon code is “ <strong>EXAMPLE</strong> ,” <em>not</em> “ <strong>PF10</strong> ” like the picture shows.</p>

<p><img src="/assets/images/2018-05-29-how-companies-like-hush-blankets-are-taking-advantage-of-your-emotions-for-profit/1*Jp1A0-8ZA9TuV5SKZbpKlg.png" alt="" /></p>

<p>So what does this mean? It probably means that none of the other coupons exist, and in fact I would bet that every slice of the wheel is filled in with “ <strong>EXAMPLE</strong> .” It also means that “ <strong>PF10</strong> ” is hard-coded somewhere. I looked back at the metadata for the “campaign,” which is downloaded into the browser before you even get a chance to see the wheel, and this is what I saw:</p>

<p><img src="/assets/images/2018-05-29-how-companies-like-hush-blankets-are-taking-advantage-of-your-emotions-for-profit/1*t-xEdvKsp0pHiZP8TqTXSA.png" alt="" /></p>

<p>What you’re seeing here is that the “Thanks!” dialog box from before has the code “ <strong>PF10</strong> ” hard-coded into it!</p>

<p>This is why the wheel has no actual coupon codes; because the code you get does not depend on the wheel at all. The wheel is just a fancy way to get you to enter your email and forever be bombarded with digital envelopes full of garbage. Once that email is submitted and you’ve been entertained by the wheel, the 10% off coupon dialog box is displayed so that you can feel like you’ve won and need to use the coupon.</p>

<p>Very manipulative.</p>]]></content><author><name>Mitch Talmadge</name></author><category term="marketing" /><category term="hacking" /><category term="psychology" /><category term="emotions" /><category term="networking" /><summary type="html"><![CDATA[I dig into the network traffic behind a Shopify app called "Wheelio" that tricks users into believing they won a coupon by luck.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://mitchtalmadge.com/assets/2018-05-29-how-companies-like-hush-blankets-are-taking-advantage-of-your-emotions-for-profit/1*bk5y0ntuO-YtWltyBe4uRw.png" /><media:content medium="image" url="https://mitchtalmadge.com/assets/2018-05-29-how-companies-like-hush-blankets-are-taking-advantage-of-your-emotions-for-profit/1*bk5y0ntuO-YtWltyBe4uRw.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">using datatables.net with webpack</title><link href="https://mitchtalmadge.com/2016/12/31/using-datatables-net-with-webpack.html" rel="alternate" type="text/html" title="using datatables.net with webpack" /><published>2016-12-31T02:02:31+00:00</published><updated>2018-05-26T03:04:58+00:00</updated><id>https://mitchtalmadge.com/2016/12/31/using-datatables-net-with-webpack</id><content type="html" xml:base="https://mitchtalmadge.com/2016/12/31/using-datatables-net-with-webpack.html"><![CDATA[<p>Recently I found a need for installing DataTables.net in my Angular 2 application, which is packaged using Webpack. Unfortunately, I faced many errors while trying to install the library using NPM and importing DataTables.net into my <code class="language-plaintext highlighter-rouge">vendor.ts</code> file.</p>

<p>As it turned out, DataTables.net can be used with AMD and CommonJS importers, but Webpack allows loading of either one. AMD is the default loader in DataTables.net, so it was being loaded using the AMD method, when I was trying to load with CommonJS. The solution is to use the <code class="language-plaintext highlighter-rouge">imports-loader</code> plugin for Webpack to disable the AMD loading support for DataTables.net.</p>

<p>Add this to your <code class="language-plaintext highlighter-rouge">webpack.config.js</code> :</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">module</span><span class="p">:</span> <span class="p">{</span>
    <span class="nl">loaders</span><span class="p">:</span> <span class="p">[</span>
        <span class="p">...</span>
        <span class="p">{</span>
            <span class="nl">test</span><span class="p">:</span> <span class="sr">/datatables.net.*/</span><span class="p">,</span>
            <span class="nx">loader</span><span class="p">:</span> <span class="dl">'</span><span class="s1">imports?define=&gt;false</span><span class="dl">'</span>
        <span class="p">}</span>
        <span class="p">...</span>
<span class="p">}</span>
</code></pre></div></div>

<p><a href="https://github.com/webpack/imports-loader#disable-amd">On the imports-loader github page</a>, it says:</p>

<blockquote>
  <p>There are many modules that check for a <code class="language-plaintext highlighter-rouge">define</code> function before using CommonJS. Since webpack is capable of both, they default to AMD in this case, which can be a problem if the implementation is quirky.</p>

  <p>Then you can easily disable the AMD path by writing</p>

  <p><code class="language-plaintext highlighter-rouge">imports-loader?define=&gt;false</code></p>
</blockquote>

<p>Finally, to import the packages into your <code class="language-plaintext highlighter-rouge">vendor.ts</code> :</p>
<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">datatables.net</span><span class="dl">"</span><span class="p">)(</span><span class="nb">window</span><span class="p">,</span> <span class="nx">$</span><span class="p">);</span>
<span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">datatables.net-bs</span><span class="dl">"</span><span class="p">)(</span><span class="nb">window</span><span class="p">,</span> <span class="nx">$</span><span class="p">);</span>
<span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">datatables.net-responsive</span><span class="dl">"</span><span class="p">)(</span><span class="nb">window</span><span class="p">,</span> <span class="nx">$</span><span class="p">);</span>
<span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">datatables.net-responsive-bs</span><span class="dl">"</span><span class="p">)(</span><span class="nb">window</span><span class="p">,</span> <span class="nx">$</span><span class="p">);</span>
<span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">datatables.net-select</span><span class="dl">"</span><span class="p">)(</span><span class="nb">window</span><span class="p">,</span> <span class="nx">$</span><span class="p">);</span>
</code></pre></div></div>

<p>Now DataTables.net loads up perfectly.</p>]]></content><author><name>Mitch Talmadge</name></author><category term="programming" /><summary type="html"><![CDATA[Getting DataTables.net to work with Webpack can be a pain! I figured it out so you don't have to.]]></summary></entry></feed>