~sqwishy/froghat.ca

a3982f6785eadb239e29b2f82c4cee17455b2607 — brandon a month ago 364e025
A boring a complex tale of bending my camera's will to my weird desires
A cazander.ca/2024/realfakeframe.io/DSCF8999.jpg => cazander.ca/2024/realfakeframe.io/DSCF8999.jpg +0 -0
A cazander.ca/2024/realfakeframe.io/DSCF9093.jpg => cazander.ca/2024/realfakeframe.io/DSCF9093.jpg +0 -0
A cazander.ca/2024/realfakeframe.io/DSCF9110.jpg => cazander.ca/2024/realfakeframe.io/DSCF9110.jpg +0 -0
A cazander.ca/2024/realfakeframe.io/DSCF9507.jpg => cazander.ca/2024/realfakeframe.io/DSCF9507.jpg +0 -0
A cazander.ca/2024/realfakeframe.io/DSCF9602.jpg => cazander.ca/2024/realfakeframe.io/DSCF9602.jpg +0 -0
A cazander.ca/2024/realfakeframe.io/DSCF9608.jpg => cazander.ca/2024/realfakeframe.io/DSCF9608.jpg +0 -0
A cazander.ca/2024/realfakeframe.io/DSCF9733.JPG => cazander.ca/2024/realfakeframe.io/DSCF9733.JPG +0 -0
A cazander.ca/2024/realfakeframe.io/realfakeframeio.rst => cazander.ca/2024/realfakeframe.io/realfakeframeio.rst +186 -0
@@ 0,0 1,186 @@
🪳ITM: Roach in the middle
##########################

:date: Sat 7 September 2024
:summary: Replacing frame.io integration on my camera with something I actually
  want to use


.. figure:: DSCF9733.JPG

A year ago, I bought my first Fujifilm camera: an X-T1. This eleven year-old
camera was such a delight to use that it quickly relegated my full-frame Nikon
mirrorless to a glorified webcam, despite being in an entirely different weight
class.

Part of why I love the Fuji cameras so much is that it speeds up my image
workflow. On the Nikon, I always had to set aside time to edit my photos to get
them to look the way I wanted. On the Fuji, I can pretty much dial in the look
I want on the camera using their film simulations and then just transfer them
to my PC and upload them. This was still a bit of a pain, so images would pile
up for a bit on the SD card until I dumped them all and uploaded them to Google
Photos, my image-sharing service of choice.

I used the X-T1 exclusively for over a year—and I loved so much about it—but
decided on an upgrade, for two reasons:

#. The autofocus on the X-T1 is pretty dated, especially in lower light
   conditions (like our house). Since I mostly photograph a toddler that moves near
   the speed of light, I needed something a bit quicker.
#. For some reason, this camera does some skin smoothing effects in the JPEG
   processing pipeline at higher ISOs (low light). This isn't something you
   can disable, so I would have to go and process the RAW photos for low-light
   situations.

The stars aligned when Fuji released the X100VI in February, and I put in a
pre-order a mere hour after the initial release. Due to the camera's popularity,
it still took over six months to arrive, but it's here now and I've been
extremely happy with it. It's a definite step up from the X-T1 in terms of
autofocus, I can disable the skin smoothing, and it still has all the great
color science and custom film simulations that I loved about the X-T1.

----

.. figure:: DSCF9608.jpg

With the X100VI in hand, I had further simplified my image processing workflow
(to no processing at all). I still had to transfer the photos off my SD card
using my PC and then upload them to Google Photos. It might sound ridiculous,
but I'm not the only user of the camera, and it's not infrequent that Alex wants
to take a photo and share it that day, not after I get around to uploading.

While waiting the six months for my camera to arrive, I was exploring the camera
features and manual and the `frame.io`_ integration caught my eye. This feature
allows the camera to automatically upload the photos of your choosing (or all of
them) to your frame.io account over wifi. This worked pretty well in practice;
and I used this functionality for about a month, or until my trial period was
up.

I wasn't a huge fan of the frame.io interface: it meant our library was
bifurcated between services, and I generally try to avoid adobe products, so I
was a bit sad to see that the camera didn't support any alternative services for
this service. Having the camera auto-upload immediately after taking photos was
such a perfect finish to my workflow optimization.

----

.. figure:: DSCF9093.jpg

Taking my data back
-------------------

The initial idea was pretty simple: frame.io offered a free account option that
lets you upload up to 2GB of photos and videos, and they also offered an API. I
used their `webhook`_ support to have their service call my server whenever an
asset was uploaded, at which point I would download the original files, delete
from their service, and upload to google photos.

This service took about a day to implement and worked surprisingly well. If you
watched the frame.io site while the camera was uploading, you would see an asset
appear for under a second before being removed and uploaded to google photos.

I called this small service 🪳 roach, and I was pretty happy with it. Images
would take a while to show up on the frame.io site, as it does a bunch of
processing on the photos as they arrive, but it was still so much quicker than
my old workflow.

There were two unresolved complaints in my mind at this point:

#. My photos, including photos of my family, were still going to a third-party
   site, and I am not entirely confident that they are good wardens of my data. You
   can make the same argument against google photos, but at least that's the devil
   I know.

#. I wasn't able to upload movies taken on the camera due to the 2GB project
   limit, as a single movie is frequently larger than this limit.

.. figure:: DSCF8999.jpg

RITM: 🪳 in the middle
----------------------

While monitoring some uploads on my camera, I came across an intriguing menu
item under the camera's frame.io menus titled **ROOT CERTIFICATE**. This
immediately caught my attention as I love a good RITM attack.

.. figure:: ritm.jpg
   :alt: A picture of the camera allowing me to MITM it and frame.io

   I'm not entirely sure why the camera lets you choose a root certificate to
   use with this service, but I'm very happy it does. Thanks, Fuji engineers!

I hurriedly set up `mitmproxy`_, loaded the CA certificate that it generated
onto the camera, and hijacked the DNS for api.frame.io on my local network. I
was able to capture the traffic between the camera and the frame.io servers,
but as soon as the camera attempted to upload a photo, it would disconnect. It
turned out that frame.io uses S3 presigned URLs for its storage backend, and
the camera will exclusively verify the certificates of these requests with the
loaded root certificate, so it was just a matter of me doing the same MITM on
the s3 url before I had the entire flow captured.

It turned out that doing the MITM was entirely unnecessary as frame.io helpfully
`documents`_ the entire C2C integration on their site, which is what the camera
was using.

.. aside::
.. [#]
   This might mean that if you're on my local network you could figure out
   how to upload random things to my google photos by pretending to be a Fujifilm
   X100VI.

Using this documentation, I rewrote roach to instead be a local service that
listens on my network and emulates the frame.io API. As it's local only, I got
to skip some complications around handling device authorization or pesky things
like basic authentication tokens, and I found that I could just immediately
authorize the camera when it connects [#]_.

My initial thought was to use minio as my local s3 backend as I could return
the camera the multipart presigned URLs for uploads and let it combine things,
but this proved to be problematic as the camera clearly expects some particular
details from the AWS S3 URLs, so it was failing to upload. In the end, I just
handle the uploads from the same web service, using the same URL structure as
AWS S3 but ignoring most of the query parameters aside from the asset ID and
part number.

Performance doesn't really matter here as the camera uploads a single ~25MiB
part at a time, limited by the networking stack of the camera, so any time spent
processing and stitching together the photos is inconsequential.

----

.. figure:: DSCF9507.jpg

In the end, this real fake frame.io service I wrote is my favorite solution. I
plan on expanding support for this to have plugins based on the file types to
further improve my workflow, for example:

- save RAW files to local storage instead of uploading them to google photos
- transfer the movies to my computer for editing
- transcode movies before upload to google photos which doesn't like it when I use flog2

The one downside to this approach is that I can't hijack DNS wherever I go,
so the wifi uploading breaks once I leave my network. I can fall back to
my cloud-based roach service in these cases, but I need to remove the root
certificate from the camera before doing this.

An alternative (that I haven't attempted yet) is to use my phone as a hotspot,
where I can use tailscale to use my home services to hijack the DNS.

----

.. figure:: DSCF9602.jpg

This entire project was one of my favorite ones of recent memory: not only do
I get to solve a real technical problem I have with some software but I got to
do some hacky stuff along the way. If you're interested in exploring either of
these options for your X100VI (or any camera that uses frame.io C2C integration,
probably), feel free to reach out to me with any questions or if you want a copy
of the services I wrote for this.

.. figure:: DSCF9110.jpg

.. _webhook: https://developer.frame.io/docs/automations-webhooks/webhooks-overview)
.. _mitmproxy: https://github.com/mitmproxy/mitmproxy
.. _documents: https://developer.frame.io/docs/device-integrations/concepts-and-fundamentals
.. _frame.io: https://frame.io/


A cazander.ca/2024/realfakeframe.io/ritm.jpg => cazander.ca/2024/realfakeframe.io/ritm.jpg +0 -0