Jekyll2023-11-26T15:19:11+00:00http://jjtech.dev/feed.xmlJJTechA personal blog where I write about things that interest me, and my current projects.JJTechiMessage, explained2023-08-03T00:00:00+00:002023-08-03T00:00:00+00:00http://jjtech.dev/reverse-engineering/imessage-explained<p>This blog post is going to be a cursory overview of the internals iMessage, as I’ve discovered during my work on <a href="https://github.com/JJTech0130/pypush"><code class="language-plaintext highlighter-rouge">pypush</code></a>, an open source project that reimplements iMessage.</p>
<p>I gloss over specific technical details in the pursuit of brevity and clarity. If you would like to see how things are specifically implemented, check out the <a href="https://github.com/JJTech0130/pypush"><code class="language-plaintext highlighter-rouge">pypush</code> repository</a> as I mentioned above. It’s a pretty cool project, if I do say so myself. Make sure to check it out!</p>
<p>If you still end up with any questions, feel free to ask me in the <a href="https://discord.gg/BVvNukmfTC"><code class="language-plaintext highlighter-rouge">pypush</code> Discord</a></p>
<h3 id="the-foundational-layer">the foundational layer</h3>
<p>One of the most foundational components of iMessage is Apple Push Notification Service (APNs). You might have encountered this before, as it is the <em>same service</em> that is used by applications on the App Store to receive realtime notifications and updates, even while the app is closed.</p>
<p>However, what you probably <em>didn’t</em> know about APNs is that it is <em>bidirectional</em>. That’s right, APNs can be used to send push notifications as well as receive them. You can probably already tell where this is going, right?</p>
<p>Internally, after a device connects to APNs it will receive a “push token”. This token can be used to route notifications to that specific device.</p>
<p class="notice--info"><strong>Note:</strong> This token is technically different then the token you receive when using the <code class="language-plaintext highlighter-rouge">application:didRegisterForRemoteNotificationsWithDeviceToken:</code> API. That token is scoped for per-app use, and is requested using the bundle ID of the application. However, it is basically used for the same purpose.</p>
<p>When sending push notifications to a device, you also need to specify the <em>topic</em> a message is for. This usually looks like a Bundle ID, and for iMessage it’s <code class="language-plaintext highlighter-rouge">com.apple.madrid</code>. When a device connects to APNs, it sends a filter message instructing the server on which messages it wants delivered to it.</p>
<p class="notice--info"><strong>Note:</strong> The APNs server is also known as the APNs <em>Courier</em>. The filter message includes several lists of topics, for each of the different possible states. It may want a topic to be <code class="language-plaintext highlighter-rouge">enabled</code>, <code class="language-plaintext highlighter-rouge">opertunistic</code>, <code class="language-plaintext highlighter-rouge">paused</code>, or <code class="language-plaintext highlighter-rouge">disabled</code></p>
<p>APNs is not only used for the actual message delivery part of iMessage. Using a pseudo-HTTP layer on top of APNs, IDS (which will be explained in a moment) can send queries and receive responses over APNs as well.</p>
<p>One tricky note that I will mention is that in order to connect to APNs, you need a client certificate issued by the Albert activation server.</p>
<h3 id="the-keyserver">the keyserver</h3>
<p>The next piece of this puzzle is IDS. As far as I can figure out, this stands for IDentity Services, though I don’t think there is any official confirmation on that.</p>
<p class="notice--info"><strong>Note:</strong> You may also see it referred to as ESS. This is confusing because the APNs topic <em>FaceTime</em> uses is specifically called <code class="language-plaintext highlighter-rouge">com.apple.ess</code>. Moving on…</p>
<p>IDS is used as a keyserver for iMessage, as well as a few other services like FaceTime. Remember, iMessage is E2E encrypted, so the public keys of each participant must be exchanged securely.</p>
<p>The first step in registering for IDS is getting an authentication token. This requires giving the API your Apple ID Username and Password.</p>
<p class="notice--info"><strong>Note:</strong> As 2FA is now standard, it had to be retrofitted into the IDS API. There are 2 options for this: the legacy option, in which a 2FA code is directly appended to the password, and the “GrandSlam” option. In the GrandSlam option, “Anisette data” is used to prove you are the same device and thus do not need to enter the 2FA code again. You then receive a Password Equivalent Token (PET) which can be used as if it was the password + 2FA code.</p>
<p>After one has gotten an authentication token, it must be immediately exchanged for a longer lived authentication certificate. This certificate allows registering with IDS, but it is not yet enough to perform key lookups.</p>
<p>Perhaps the most important step of the IDS setup process is <em>registration</em>. This is where public encryption and signing keys are uploaded to the keyserver, as well as various other “client data” about what features the device supports.</p>
<p>When making an IDS registration request, a binary blob called “validation data” is required. This is essentially Apple’s verification mechanism to make sure that non-Apple devices cannot use iMessage.</p>
<p class="notice--warning"><strong>Warning:</strong> In order to generate the “validation data”, pieces of information about the device such as its serial number, model, and disk UUID are used. This means that not all validation data can be treated equivalently: just like with Hackintoshes, the account age and “score” determine if an invalid serial can be used, or if you get the “customer code” error.</p>
<p class="notice--info"><strong>Note:</strong> The binary that generates this “validation data” is highly obfuscated. <code class="language-plaintext highlighter-rouge">pypush</code> sidesteps this issue by using a custom mach-o loader and the Unicorn Engine to emulate an obfuscated binary. <code class="language-plaintext highlighter-rouge">pypush</code> also bundles device properties such as the serial number in a file called data.plist, which it feeds to the emulated binary.</p>
<p>After registering with IDS, you will receive an “identity keypair”. This keypair can then be used to perform public key lookups.</p>
<p>When performing a lookup, you provide the account(s) that you would like to look up, and receive a list of “identities”. Each of these identities corresponds to a device registered on the account, and includes important details such as its public key, push token, and session token.</p>
<p class="notice--warning"><strong>Warning:</strong> Session tokens are necessary to send messages to a device. They essentially prove that you made a recent lookup, because the session token expires. Session tokens cannot be shared, as they can only be used by the account that performed the lookup request.</p>
<h3 id="message-encryption">message encryption</h3>
<p>Now, we’ve setup the basics of iMessage. We have enough that we can look up the public keys of another user, as well as publish our own. Now we just need to put it together with APNs to send and receive messages!</p>
<p>In order to receive messages, one simply filters the APNs connection to <code class="language-plaintext highlighter-rouge">com.apple.madrid</code> and sends the active state packet.</p>
<p>Depending on which capabilities you advertised in your IDS registration, as well as the iOS version of the sending device, you may receive messages in the legacy (pre-iOS 13) <code class="language-plaintext highlighter-rouge">pair</code> encryption format, or in the new <code class="language-plaintext highlighter-rouge">pair-ec</code> format. While the <code class="language-plaintext highlighter-rouge">pair</code> format is much more documented and easier to implement, it does not provide forward secrecy using “pre-keys” (similar to Signal) as the new <code class="language-plaintext highlighter-rouge">pair-ec</code> format does.</p>
<p>You can then decrypt the message as described in several papers, and as implemented in <code class="language-plaintext highlighter-rouge">pypush</code>. Verifying the message signature is optional, but is obviously important if you intend your client to be secure.</p>
<p>Sending messages is pretty much the inverse to receiving them. Keep in mind that you can chose to individually send out messages to each recipient, or you can bundle all the different recipients and their respective encrypted payloads into a giant bundle, which APNs will split up for you.</p>
<p class="notice--info"><strong>Note:</strong> Another thing to keep in mind is that message are delivered to all participants in a conversation, <em>including the other devices on your own account</em>.</p>
<p>One more thing to keep in mind that is often overlooked when sending messages is that the AES key is not entirely random: it is tagged with an HMAC. Your message will fail to decrypt on newer devices if you use an entirely random AES key.</p>
<p>And that’s pretty much it! As I mentioned, this blog post is designed to give you a good idea of how the iMessage protocol fits together, so that you can explore the <code class="language-plaintext highlighter-rouge">pypush</code> code, not directly explain every technical detail.</p>
<h3 id="resources-and-attribution">resources and attribution</h3>
<p>Many people and prior works have helped me understand iMessage. Here is a brief list, in no way exhaustive:</p>
<ul>
<li><a href="https://kb.imfreedom.org/protocols/imessage/">IMFreedom Knowledge Base: iMessage</a></li>
<li><a href="https://github.com/mfrister/pushproxy">M. Frister: <code class="language-plaintext highlighter-rouge">pushproxy</code></a></li>
<li><a href="https://gitlab.com/nicolas17/apns-dissector">Nicolás: <code class="language-plaintext highlighter-rouge">apns-dissector</code></a></li>
<li><a href="https://blog.quarkslab.com/imessage-privacy.html">QuarkSlab: iMessage Privacy</a></li>
<li><a href="https://www.usenix.org/system/files/conference/usenixsecurity16/sec16_paper_garman.pdf">Garman et al. Chosen Ciphertext Attacks on Apple iMessage</a></li>
<li><a href="https://www.nowsecure.com/blog/2021/01/27/reverse-engineering-imessage-leveraging-the-hardware-to-protect-the-software/">NowSecure: Reverse Engineering iMessage</a></li>
<li><a href="https://blog.elcomsoft.com/2018/11/imessage-security-encryption-and-attachments/">Elcomsoft: iMessage Security and Attachments</a></li>
<li><a href="https://github.com/open-imcore">Eric Rabil’s <code class="language-plaintext highlighter-rouge">open-imcore</code></a></li>
<li><a href="https://theapplewiki.com/wiki/Apple_Push_Notification_Service">The Apple Wiki: Apple Push Notification Service</a></li>
<li><a href="https://par.nsf.gov/servlets/purl/10200009">Mihir Bellare and Igors Stepanovs: Security under Message-Derived Keys: Signcryption in iMessage</a></li>
<li><a href="https://support.apple.com/lt-lt/guide/security/sec70e68c949/web">Apple Platform Security: How iMessage sends and receives messages securely</a></li>
<li><a href="https://gist.github.com/nicolas17/559bec0d8e636f93f62cca844ee94ada">Nicolás: Apple IDS payload keys</a></li>
<li><a href="https://discord.gg/NAxRYvysuc">Various people on the Hack Different Discord</a></li>
</ul>
<p class="notice">This blog post has been reworked from <a href="/reverse-engineering/imessage-overview-original/">its original version</a>.</p>JJTechThis blog post is going to be a cursory overview of the internals iMessage, as I’ve discovered during my work on pypush, an open source project that reimplements iMessage.iMessage Overview (Original)2023-04-19T00:00:00+00:002023-04-19T00:00:00+00:00http://jjtech.dev/reverse-engineering/imessage-overview-original<p>My project right now is reverse engineering iMessage. And I don’t mean like a half usable POC that only works with Macs. I mean the real thing: a fully open demo that can run on any computer.</p>
<p>Sounds far-fetched? It really isn’t. When you get down to it, iMessage isn’t that complex. I should be able to get it working soon! <em>knocks on wood</em></p>
<p>First and foremost, here’s the <a href="https://github.com/JJTech0130/pypush">repo where I’m experimenting</a> and the <a href="https://discord.gg/hackdifferent">Hack Different Discord</a> where I’m working on this in real-time.</p>
<h3 id="prior-art">Prior Art</h3>
<p>Everyone knows what iMessage is, but not many know how it works.
A few people have done research on this before, and I’ve heavily borrowed from them.
Here’s an incomplete list:</p>
<ul>
<li><a href="https://kb.imfreedom.org/protocols/imessage/">IMFreedom Knowledge Base</a></li>
<li><a href="https://github.com/mfrister/pushproxy">M. Frister</a></li>
<li><a href="https://gitlab.com/nicolas17/apns-dissector">Nicolás</a></li>
<li><a href="https://blog.quarkslab.com/imessage-privacy.html">QuarkSlab</a></li>
<li><a href="https://www.usenix.org/system/files/conference/usenixsecurity16/sec16_paper_garman.pdf">Garman et al.</a></li>
<li><a href="https://www.nowsecure.com/blog/2021/01/27/reverse-engineering-imessage-leveraging-the-hardware-to-protect-the-software/">NowSecure</a></li>
<li><a href="https://blog.elcomsoft.com/2018/11/imessage-security-encryption-and-attachments/">Elcomsoft</a></li>
<li><a href="https://github.com/open-imcore">Eric Rabil</a></li>
<li><a href="https://theapplewiki.com/wiki/Apple_Push_Notification_Service">The Apple Wiki</a></li>
<li>There’s probably others too. If I forgot you, leave a comment.</li>
</ul>
<h2 id="construction">Construction</h2>
<p>So, the iMessage protocol. Here’s what happens when a device is first setting up:</p>
<ol>
<li>The device asks Albert for a “push certificate”. Basically, it uses a key obfuscated in the binary itself to prove to Albert that this is “legitimate Apple software”. <a href="https://gist.github.com/JJTech0130/647705a968fe0f9d1633c32a6c5a8c8d">I defeated this about 2 weeks ago</a>.</li>
<li>The device connects to Apple Push Notification Service using the aforementioned push certificate. It receives a “push token” which allows notifications to be routed to it.</li>
<li>The device uses Grand Slam Authentication to authenticate the user’s Apple ID and receives a “Password Equivalent Token (PET)”. <a href="https://github.com/JJTech0130/grandslam">I wrote something for this last year</a>.</li>
<li>The device makes a request to the iCloud Setup server to exchange the PET for an IDS “authentication token”.</li>
<li>The device uses the IDS “authentication token” to receive an “authentication certificate” for the user’s account.</li>
<li>The device sends a registration request to IDS (signed by both the “authentication certificate” and the “push certificate”) containing its new public keys and other public information. In exchange, it receives an “IDS certificate” which it can then use to perform lookup requests. Additionally, this information is public and can be looked up by other users.</li>
</ol>
<p>This is a new way of writing blog posts, where I slowly add to them over time. Hopefully it means that even if I get distracted, it’s still useful for others.</p>JJTechMy project right now is reverse engineering iMessage. And I don’t mean like a half usable POC that only works with Macs. I mean the real thing: a fully open demo that can run on any computer.Downloading Vimeo videos2022-08-06T00:00:00+00:002022-08-06T00:00:00+00:00http://jjtech.dev/reverse-engineering/vimeo-download<p>So, if you saw my previous blog post, you know about how I wanted to download chapel videos from COTW.
This is a continuation post on how I automated that. This mainly consists of using Vimeo’s API to download the videos.</p>
<p>Here’s the final code I ended up using:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># extract the URLs</span>
<span class="nv">URLS</span><span class="o">=</span><span class="si">$(</span>curl <span class="nt">-s</span> <span class="s1">'http://www.livestream.cotw365.morleyconsulting.xyz/chapels.php'</span> | pup <span class="s2">"iframe attr{src}"</span><span class="si">)</span>
<span class="c"># yes, the XMLHttpRequest part is important for some reason...</span>
<span class="nv">json</span><span class="o">=</span><span class="si">$(</span>curl <span class="nt">-s</span> <span class="s1">'https://vimeo.com/_next/jwt'</span> <span class="nt">-H</span> <span class="s1">'X-Requested-With: XMLHttpRequest'</span><span class="si">)</span>
<span class="nv">JWT</span><span class="o">=</span><span class="si">$(</span><span class="nb">echo</span> <span class="nv">$json</span> | jq <span class="s1">'.token'</span> <span class="nt">--raw-output</span><span class="si">)</span>
<span class="c"># For each video URL...</span>
<span class="k">for </span>URL <span class="k">in</span> <span class="nv">$URLS</span>
<span class="k">do</span>
<span class="c"># keep everything after the last slasg</span>
<span class="nv">trimmed</span><span class="o">=</span><span class="si">$(</span><span class="nb">echo</span> <span class="nv">$URL</span> | <span class="nb">sed</span> <span class="s1">'s:.*/::'</span><span class="si">)</span>
<span class="c"># everything before ?h= is the ID</span>
<span class="nv">ID</span><span class="o">=</span><span class="k">${</span><span class="nv">trimmed</span><span class="p">%?h=*</span><span class="k">}</span>
<span class="c"># everything after ?h= is the hash</span>
<span class="nv">temp</span><span class="o">=</span><span class="k">${</span><span class="nv">trimmed</span><span class="p">##*?h=</span><span class="k">}</span>
<span class="c"># throw away everything after &, it's garbage</span>
<span class="nv">HASH</span><span class="o">=</span><span class="k">${</span><span class="nv">temp</span><span class="p">%%&*</span><span class="k">}</span>
<span class="c"># print debug info</span>
<span class="nb">echo</span> <span class="s2">""</span>
<span class="nb">echo</span> <span class="s2">"Trying to download video: </span><span class="nv">$ID</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"With hash: </span><span class="nv">$HASH</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"and token: </span><span class="nv">$JWT</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">""</span>
<span class="c"># get info about formats</span>
<span class="nv">json</span><span class="o">=</span><span class="si">$(</span>curl <span class="nt">-s</span> <span class="s2">"https://api.vimeo.com/videos/</span><span class="nv">$ID</span><span class="s2">:</span><span class="nv">$HASH</span><span class="s2">?fields=download.height%2Cdownload.link&action=load_download_config"</span> <span class="nt">-H</span> <span class="s2">"Authorization: jwt </span><span class="k">${</span><span class="nv">JWT</span><span class="k">}</span><span class="s2">"</span><span class="si">)</span>
<span class="c"># hardcoded 1080p</span>
<span class="nv">DOWNLOAD</span><span class="o">=</span><span class="si">$(</span><span class="nb">echo</span> <span class="nv">$json</span> | jq <span class="s1">'.download[] | select(.height == 1080) | .link'</span> <span class="nt">--raw-output</span><span class="si">)</span>
wget <span class="nv">$DOWNLOAD</span>
<span class="k">done</span>
</code></pre></div></div>
<ol>
<li>
<p>I extracted the video URLs from the original page, using <code class="language-plaintext highlighter-rouge">pup</code>, simply selecting the <code class="language-plaintext highlighter-rouge">src</code> attribute of all iframes</p>
</li>
<li>
<p>I got a JWT token for Vimeo’s API</p>
</li>
<li>
<p>I trimmed out the video ID and hash from the original (embed) URL</p>
</li>
<li>
<p>I asked Vimeo’s API for download URLs using the above info</p>
</li>
<li>
<p>I parsed the response with <code class="language-plaintext highlighter-rouge">jq</code>, filtering for the 1080p video</p>
</li>
<li>
<p>I downloaded the video with <code class="language-plaintext highlighter-rouge">wget</code></p>
</li>
</ol>
<p>This is <em>much</em> faster than <code class="language-plaintext highlighter-rouge">yt-dlp</code> for some reason, usually getting like 20mb/s.</p>
<p>This is just a basic “I did it” post, as I don’t really know how to elaborate :smile:…</p>JJTechSo, if you saw my previous blog post, you know about how I wanted to download chapel videos from COTW. This is a continuation post on how I automated that. This mainly consists of using Vimeo’s API to download the videos.Adobe ADEPT2022-08-01T00:00:00+00:002022-08-01T00:00:00+00:00http://jjtech.dev/reverse-engineering/adobe-adept<p class="notice">This mega-post is intended to overview on how the ADEPT protocol works, as discovered by my own efforts and those of Grégory Soutadé and Florian Bach. It will be periodically updated to contain new information.</p>
<p>For the uninitiated, ADEPT is the protocol that Adobe’s flagship DRM product, <strong>Adobe Digital Editions (ADE)</strong>, uses to communicate with both <strong>Eden2 (Adobe Digital Edition activation service)</strong> and with <strong>Adobe Content Server (ACS)</strong> instances run by book distributors.</p>
<p>On it’s most basic level, the ADEPT protocol consists of two main parts: Activation and Fulfilment.</p>
<h2 id="activation">Activation</h2>
<p>In order to fulfill books, your device needs to be “activated”. There are several methods of going about this, but the simplest is activating anonymously.</p>
<p class="notice--warning"><strong>Warning:</strong> Because anonymous activations are, well, anonymous, they cannot be used across multiple devices: if you lose the device, your purchases are gone forever.</p>
<h3 id="signing-in">Signing In</h3>
<p>The first step of activation involves signing into your account. This process sets up a bunch of keys and other things that we will use later.</p>
<h4 id="generating-keys">Generating Keys</h4>
<p>The first thing you need to do is generate the keys you are going to use for your device. These include:</p>
<ol>
<li>
<p>The <em>Device Key</em>, which is simply 16 random bytes</p>
</li>
<li>
<p>The <em>License Key</em>, which is a 1024-bit RSA keypair</p>
</li>
<li>
<p>The <em>Authentication Key</em>, which is a 1024-bit RSA keypair</p>
</li>
</ol>
<p>Save these in a safe place, as you will need them later.</p>
<h4 id="constructing-sign-in-data">Constructing Sign-In Data</h4>
<p>Now, you need to construct a buffer containing the sign in data, which looks like this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>DEVICE_KEY + len(USERNAME) + USERNAME + len(PASSWORD) + PASSWORD
</code></pre></div></div>
<p>The device key is the same one you generated earlier, while the username and password are empty. Thus, for an anonymous account, it should actually look something like this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>DEVICE_KEY00
</code></pre></div></div>
<p>You then need to encrypt this whole buffer with the Authentication Certificate.</p>
<div class="notice">
<p>For simplicity, you can assume the Authentication Certificate will be:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>MIIEYDCCA0igAwIBAgIER2q5eTANBgkqhkiG9w0BAQUFADCBhDELMAkGA1UEBhMCVVMxIzAhBgNVBAoTGkFkb2JlIFN5c3RlbXMgSW5jb3Jwb3JhdGVkMRswGQYDVQQLExJEaWdpdGFsIFB1Ymxpc2hpbmcxMzAxBgNVBAMTKkFkb2JlIENvbnRlbnQgU2VydmVyIENlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0wODAxMDkxODQzNDNaFw0xODAxMzEwODAwMDBaMHwxKzApBgNVBAMTImh0dHA6Ly9hZGVhY3RpdmF0ZS5hZG9iZS5jb20vYWRlcHQxGzAZBgNVBAsTEkRpZ2l0YWwgUHVibGlzaGluZzEjMCEGA1UEChMaQWRvYmUgU3lzdGVtcyBJbmNvcnBvcmF0ZWQxCzAJBgNVBAYTAlVTMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZAxpzOZ7N38ZGlQjfMY/lfu4Ta4xK3FRm069VwdqGZIwrfTTRxnLE4A9i1X00BnNk/5z7C0pQX435ylIEQPxIFBKTH+ip5rfDNh/Iu6cIlB0N4I/t7Pac8cIDwbc9HxcGTvXg3BFqPjaGVbmVZmoUtSVOsphdA43sZc6j1iFfOQIDAQABo4IBYzCCAV8wEgYDVR0TAQH/BAgwBgEB/wIBATAUBgNVHSUEDTALBgkqhkiG9y8CAQUwgbIGA1UdIASBqjCBpzCBpAYJKoZIhvcvAQIDMIGWMIGTBggrBgEFBQcCAjCBhhqBg1lvdSBhcmUgbm90IHBlcm1pdHRlZCB0byB1c2UgdGhpcyBMaWNlbnNlIENlcnRpZmljYXRlIGV4Y2VwdCBhcyBwZXJtaXR0ZWQgYnkgdGhlIGxpY2Vuc2UgYWdyZWVtZW50IGFjY29tcGFueWluZyB0aGUgQWRvYmUgc29mdHdhcmUuMDEGA1UdHwQqMCgwJqAkoCKGIGh0dHA6Ly9jcmwuYWRvYmUuY29tL2Fkb2JlQ1MuY3JsMAsGA1UdDwQEAwIBBjAfBgNVHSMEGDAWgBSL7vCBYMmi2h4OUsFYDASwQ/eP6DAdBgNVHQ4EFgQU9RP19K+lzF03he+0T47hCVkPhdAwDQYJKoZIhvcNAQEFBQADggEBAJoqOj+bUa+bDYyOSljs6SVzWH2BN2ylIeZKpTQYEo7jA62tRqW/rBZcNIgCudFvEYa7vH8lHhvQak1s95g+NaNidb5tpgbS8Q7/XTyEGS/4Q2HYWHD/8ydKFROGbMhfxpdJgkgn21mb7dbsfq5AZVGS3M4PP1xrMDYm50+Sip9QIm1RJuSaKivDa/piA5p8/cv6w44YBefLzGUN674Y7WS5u656MjdyJsN/7Oup+12fHGiye5QS5mToujGd6LpU80gfhNxhrphASiEBYQ/BUhWjHkSi0j4WOiGvGpT1Xvntcj0rf6XV6lNrOddOYUL+KdC1uDIe8PUI+naKI+nWgrs=
</code></pre></div></div>
</div>
<h4 id="encrypting-private-keys">Encrypting Private Keys</h4>
<p>In order to securly send the Authentication and License keypairs to Adobe, we must encrypt their private keys.</p>
<ol>
<li>
<p>Export the private key as <strong>PKCS#8</strong></p>
</li>
<li>
<p>Encrypt the exported data with the Device Key using <code class="language-plaintext highlighter-rouge">AES-128-CBC</code></p>
</li>
<li>
<p>Base64-encode the result</p>
</li>
</ol>
<p>For the public keys, all you need to do is export them as plain PKCS#1 keys, and then Base64-encode them.</p>
<h4 id="sending-the-sign-in-request">Sending the Sign-in Request</h4>
<p>The sign in request is formatted like this, replacing the placeholders with the data we just generated:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0"?></span>
<span class="nt"><adept:signIn</span> <span class="na">xmlns:adept=</span><span class="s">"http://ns.adobe.com/adept"</span> <span class="na">method=</span><span class="s">"anonymous"</span><span class="nt">></span>
<span class="nt"><adept:signInData></span>(Encrypted Sign-in Data)<span class="nt"></adept:signInData></span>
<span class="nt"><adept:publicAuthKey></span>(Public Authentication Key)<span class="nt"></adept:publicAuthKey></span>
<span class="nt"><adept:encryptedPrivateAuthKey></span>(Encrypted Private Authentication Key)<span class="nt"></adept:encryptedPrivateAuthKey></span>
<span class="nt"><adept:publicLicenseKey></span>(Public License Key)<span class="nt"></adept:publicLicenseKey></span>
<span class="nt"><adept:encryptedPrivateLicenseKey></span>(Encrypted Private License Key)<span class="nt"></adept:encryptedPrivateLicenseKey></span>
<span class="nt"></adept:signIn></span>
</code></pre></div></div>
<p>Then, make a POST request to <code class="language-plaintext highlighter-rouge">https://adeactivate.adobe.com/adept/SignInDirect</code> with a Content-Type of <code class="language-plaintext highlighter-rouge">application/vnd.adobe.adept+xml</code>. If everything went as planned, the reponse should contain a license key, a license certificate, a user ID, a username, and a PKCS#12 archive. Save these things for later.</p>
<p class="notice--info"><strong>Note:</strong> If the (decrypted) license key returned by the server <em>does not match</em> the license key you sent, this means that there was already a license key associated with the account. Make sure to replace the key you generated by the one returned by the server. This should never happen with an anonymous account.</p>
<h3 id="activating">Activating</h3>
<p>Now for the actual activation!</p>
<h4 id="generating-information">Generating information</h4>
<p>When activating a device, Adobe collects a whole bunch of information about it. Adobe actually recognizes <em>two separate devices</em> during this phase. The first is the device communicating the Activation Request. The second is the “target device” that is being activated. For most use-cases, these will be identical.</p>
<h5 id="device-information">Device Information</h5>
<ol>
<li>
<p>A unique fingerprint. (e.g. <code class="language-plaintext highlighter-rouge">4e1243bd22c66e76c2ba9eddc1f91394e57f9f8</code>)</p>
</li>
<li>
<p>An OS name (e.g. <code class="language-plaintext highlighter-rouge">Windows Vista</code>)</p>
</li>
<li>
<p>A locale (e.g. <code class="language-plaintext highlighter-rouge">en</code>)</p>
</li>
<li>
<p>A type (e.g. <code class="language-plaintext highlighter-rouge">standalone</code>)</p>
</li>
<li>
<p>A target Hobbes version (e.g. <code class="language-plaintext highlighter-rouge">9.3.58046</code>)</p>
</li>
<li>
<p>A client software version (e.g. <code class="language-plaintext highlighter-rouge">2.0.1.78765</code>)</p>
</li>
<li>
<p>A product name (e.g. <code class="language-plaintext highlighter-rouge">Adobe Digitial Editions</code>)</p>
</li>
</ol>
<h4 id="constructing-the-request">Constructing the request</h4>
<p>For now, this is all the post contains. I’ll update it with the rest of the process at some further time.</p>JJTechThis mega-post is intended to overview on how the ADEPT protocol works, as discovered by my own efforts and those of Grégory Soutadé and Florian Bach. It will be periodically updated to contain new information.Camp-of-the-Woods Streaming2022-08-01T00:00:00+00:002022-08-01T00:00:00+00:00http://jjtech.dev/reverse-engineering/cotw-streaming<p>Camp-of-the-Woods (COTW) is a family resort up in the Adirondacks. This post is about their various streams and videos, and how one might make the best use of them.</p>
<h3 id="camp-cam">Camp Cam</h3>
<p>COTW has a nice “Camp Cam” avalible <a href="https://www.cotwny.org/cam.shtml">here</a> or <a href="https://www.camp-of-the-woods.org/camp-cam">here</a>, which provides a 24/7 livestream overlooking the lake. However, their web-viewer is annoying, as it cuts out occasionally, and has an annoying “HD Relay” logo in the corner. However, I discovered that the branding is a simple image overlay, and the stream itself is clean.</p>
<p>The m3u8 url (discovered by simply opening the Network tab in DevTools) is <code class="language-plaintext highlighter-rouge">https://b10.hdrelay.com/camera/4c5d3b77-a2f8-4437-aa4c-33462cc8d8da/relay/playlist.m3u8</code>, which can be opened in VLC!</p>
<p>It can even be opened in a web browser: <a href="https://bharadwajpro.github.io/m3u8-player/player/#https://b10.hdrelay.com/camera/4c5d3b77-a2f8-4437-aa4c-33462cc8d8da/relay/playlist.m3u8">see this online player</a>!</p>
<p>Now, say you want to re-stream this video to something like YouTube Live or Twitch. It’s pretty simple:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ffmpeg <span class="nt">-user_agent</span> <span class="s1">'not ffmpeg'</span> <span class="nt">-i</span> https://b10.hdrelay.com/camera/4c5d3b77-a2f8-4437-aa4c-33462cc8d8da/relay/playlist.m3u8 <span class="nt">-c</span> copy <span class="nt">-f</span> flv rtmp://LIVE_STREAM_INGEST_URL
</code></pre></div></div>
<p>Replace <code class="language-plaintext highlighter-rouge">LIVE_STREAM_INGEST_URL</code> with your ingest URL, and you’re done!</p>
<p class="notice--warning"><strong>Note:</strong> For some reason, HDRelay blacklists FFmpeg’s default User-Agent. This causes it to return <code class="language-plaintext highlighter-rouge">405 Method Not Allowed</code>, for some unknown reason. Anyway, all you have to do is use the <code class="language-plaintext highlighter-rouge">-user_agent</code> flag shown above to change it.</p>
<h3 id="chapel-and-seminar-videos">Chapel and Seminar videos</h3>
<p>The chapel and seminar videos can be found <a href="http://www.livestream.cotw365.morleyconsulting.xyz/livestream.php">here</a> for the current week. However, one might want to, for example, download the video for offline viewing. This is pretty simple, as it turns out that they are simply embedded Vimeo videos.</p>
<p>The videos are private, which means that you need both the video ID, and the hash. This can be discovered using DevToos (automated script to come later).
You should find a <code class="language-plaintext highlighter-rouge"><video></code> element, with a source url of something like <code class="language-plaintext highlighter-rouge">https://player.vimeo.com/video/735188929?h=98bdee7354&amp;</code>. This can be translated into a regular Vimeo URL: <code class="language-plaintext highlighter-rouge">https://vimeo.com/735188929/98bdee7354</code>, where you can easily download it.</p>
<h3 id="chapel-livestream">Chapel livestream</h3>
<p>For the livestream, you can see an iframe with a source of something like <code class="language-plaintext highlighter-rouge">https://vimeo.com/event/136341/embed/41a374a9ec</code>. This can be turned into a regular URL like so: <code class="language-plaintext highlighter-rouge">https://vimeo.com/event/136341/41a374a9ec</code>. They seem to re-use the same even for every day, but you can double-check.</p>
<p>Anyway, don’t abuse this, it’s simply so that you can get a tad bit more value out of these resources. Eventually, I plan to make a simple tool to scrape and save the videos for every day.</p>JJTechCamp-of-the-Woods (COTW) is a family resort up in the Adirondacks. This post is about their various streams and videos, and how one might make the best use of them.Sending a link to a Discord Profile2022-07-31T00:00:00+00:002022-07-31T00:00:00+00:00http://jjtech.dev/discord-profile-link<p>Sometimes, you want to include your Discord somewhere. You might want to link to it on your website, or on a social network, or even in a QR code. The problem, however, is that it’s not completely obvious how to do so.</p>
<p class="notice--warning"><strong>Warning:</strong> People will only be able to see your profile if they are currently logged into Discord. Otherwise, they will be prompted to do so.</p>
<p>It’s rather simple, really. It involves crafting a link like <code class="language-plaintext highlighter-rouge">https://discord.com/users/672897428470366208</code>, where <code class="language-plaintext highlighter-rouge">672897428470366208</code> is your user ID.</p>
<p>But wait a moment! How do you get your ID? It’s pretty simple, and involves activating a Discord feature known as “Developer Mode”.</p>
<h3 id="activating-developer-mode">Activating Developer Mode</h3>
<p class="notice--info"><strong>Note:</strong> Though it might have a scary name, all it does is show normally hidden information about various things, such as user IDs!</p>
<p>You’ll find this mysterious setting in various places.</p>
<p>On Desktop or web, you need to head into User Settings, and go to the Advanced tab, where you will find the Developer Mode toggle.</p>
<p>For mobile apps, you can see <a href="https://support.discord.com/hc/en-us/articles/206346498">Discord’s support article</a> on the topic.</p>
<h3 id="getting-your-user-id">Getting your user ID</h3>
<p>The very first thing you need to do is find a message you’ve sent. (The easiest way to do this is to just send one!) Now, right-click (or hold on mobile) <strong>on your username</strong>, and you should see a new “Copy ID” button in the context menu. Click it, and your user ID will be copied to the clipboard.</p>
<p class="notice--warning"><strong>Note:</strong> It’s very important that you right-click on your username attached to the message, not the message itself. Otherwise, you will be getting the message ID instead!</p>
<p>Your user ID should be an 18-digit long number.</p>
<h3 id="final-words">Final words</h3>
<p>Now, you have all the components necessary to link to your profile! Simply append the user ID you copied to the URL from earlier, like so: <code class="language-plaintext highlighter-rouge">https://discord.com/users/672897428470366208</code></p>
<p>This process can even be done to link to other people’s profiles: simply right click on their username instead of yours!</p>JJTechSometimes, you want to include your Discord somewhere. You might want to link to it on your website, or on a social network, or even in a QR code. The problem, however, is that it’s not completely obvious how to do so.Modding hidden Insyde BIOS options2022-04-04T00:00:00+00:002022-04-04T00:00:00+00:00http://jjtech.dev/dvmt-unlock<p class="notice">This tutorial was originally posted by me <a href="https://gist.github.com/JJTech0130/bd9564858e4cd4f7d94ea4b4657660e2">as a Gist</a>.
It is modified from a tutorial I found <a href="https://www.reddit.com/r/hackintosh/comments/hz2rtm/cfg_lockunlocking_alternative_method/">on Reddit</a>.</p>
<p class="notice--warning"><strong>Warning:</strong> BIOS modding can be dangerous. You run the risk of <em>bricking your device</em>. I am not responsible for broken devices.</p>
<p class="notice--info"><strong>Note:</strong> I tested this procedure on my HP Pavilion 15 cs3065cl, running Insyde F.16 Rev.A</p>
<h1 id="finding-variables">Finding variables</h1>
<p>The first thing we need to do is find the variable we want to change.</p>
<p>Let’s clear up some terms we’re going to use.</p>
<h3 id="terminology">Terminology</h3>
<dl>
<dt>Variable Store (varstore)</dt>
<dd>a block of memory that contains the current values of variables
Offset</dd>
<dd>the offset within the varstore where the variable is located
Possible Values</dd>
<dd>these are the possible values that a variable can be set to</dd>
</dl>
<p class="notice--info"><strong>Note:</strong> In this guide, I’m going to specifically mention finding the variable to control “DVMT pre-allocation”. However, it’s a generic process, any you should be able to follow along by substituting “DVMT pre-alloc” with whatever variable you want to change.</p>
<h3 id="extracting-information">Extracting information</h3>
<p>We can extract all the information we need from the BIOS update provided on the manufacture’s website.</p>
<h4 id="extracting-the-bin-file-from-the-updater">Extracting the <code class="language-plaintext highlighter-rouge">.bin</code> file from the updater</h4>
<p class="notice--warning">Some manufactures package BIOS updates into self-contained executables. Luckily, most of them have an option to “extract BIOS update” into a file when you run it.</p>
<p>(Rewrite in progress)</p>
<h2 id="finding-variable">Finding variable</h2>
<p>We need to find the offset, varstore, and possible values for the DVMT pre-alloc.</p>
<h3 id="extracting-bios">Extracting BIOS</h3>
<ol>
<li>Download the BIOS updater from your manufacture’s website.</li>
<li>Run the updater. Look for an option to extract the update file.</li>
<li>Extract the bin file.</li>
</ol>
<h3 id="extracting-config-section">Extracting config section</h3>
<ol>
<li>Open your bin file in <a href="https://github.com/LongSoft/UEFITool/releases">UEFITool</a>.</li>
<li>Search for the string “DVMT” in UEFITool.</li>
<li>It should highlight a section named “Setup” or similar. (Or not, mine was named something else.)</li>
<li>Right-click and extract it as a bin or sct file.</li>
</ol>
<h3 id="extracting-ifr-text">Extracting IFR text</h3>
<ol>
<li>Using <a href="https://github.com/LongSoft/Universal-IFR-Extractor/releases">IFR Extractor</a>, extract the bin or sct file to a txt file.</li>
</ol>
<h3 id="finding-variable-1">Finding variable</h3>
<ol>
<li>Open the text file in an editor and search for “DVMT”.</li>
<li>Look for the offset and the value corresponding to your desired option. Take the varstore id and go to the table in the beginning, looking up it’s name.</li>
</ol>
<hr />
<p>If you got the offset value, you can download RU.</p>
<p>You can find it here: http://ruexe.blogspot.com/</p>
<p>Don’t try to use the exe file, it’s for DOS, not Windows. What we need is the RU.efi file.</p>
<p>Now, follow these steps:</p>
<ol>
<li>
<p>Grab an USB drive, 100 mb is enough but it needs to be empty.</p>
</li>
<li>Format it as MBR, fat32. You can use Rufus on Windows, or Disk Utility on macOS.
<blockquote>
<p>Note: GPT works too but you’ll need an EFI partition on your drive for it.</p>
</blockquote>
</li>
<li>
<p>You now have an empty USB drive. Create a folder called “EFI” and in that folder, create another folder called “BOOT”. In “BOOT”, paste RU.efi and rename it to bootx64.efi. The file tree should thus look like this:</p>
<p><img src="https://user-images.githubusercontent.com/53275876/132030535-46bd4fca-f1d7-4879-8c48-bf0c6e6085a1.png" alt="Tree" /></p>
<blockquote>
<p>On GPT, this would be in the hidden EFI folder, and then pasted in the (normally) already present BOOT folder. You can also temporarily use your OpenCore/Clover boot USB since those already have the EFI partition. Just replace the bootx64.efi with RU.efi and also rename it. After we’re done, you can place the old bootx64.efi back. This method is possible but I wouldn’t recommend it.</p>
</blockquote>
</li>
<li>
<p>Make sure secure boot is disabled in your BIOS and boot to the USB drive. You’ll be greeted with a screen like this.</p>
<p><img src="https://user-images.githubusercontent.com/53275876/132030840-daff26d6-670a-4ce3-a1f4-b453788dc80c.jpg" alt="Start" /></p>
<p>Just press enter. Now, we’ll go to a screen to edit UEFI variables. Press <kbd>alt</kbd> + <kbd>=</kbd>. You should get a screen like this.</p>
<p><img src="https://user-images.githubusercontent.com/53275876/132030991-e232f108-ce64-4f5a-9785-fef6acc8bef9.jpg" alt="UEFI_Variables" /></p>
<blockquote>
<p>Note: You can navigate in the tool by using the bar at the top. Press <kbd>alt</kbd> + First letter of a word in the bar to go to there. For example, by pressing <kbd>alt</kbd> + <kbd>C</kbd>, you’ll go to the “CONFIG” tab. Press <kbd>f1</kbd> to know more about key shortcuts.</p>
</blockquote>
</li>
<li>
<p>Now, search the list till you find “CPUSetup”.</p>
<p><img src="https://user-images.githubusercontent.com/53275876/132031456-98d96d49-87b3-4435-ab5a-0f601e1fdcbd.jpg" alt="UEFI_Variables_2" /></p>
<p>Press <kbd>enter</kbd>.</p>
</li>
<li>
<p>If everything’s right, you should get a screen that looks like this.</p>
<p><img src="https://user-images.githubusercontent.com/53275876/132031696-8232353e-beb7-44bf-a3c9-da70c59d1815.jpg" alt="OFFSET_BEFORE" /></p>
<p>Now, remember the value from the dortania guide? We’ll need that here. For example, my offset value is <code class="language-plaintext highlighter-rouge">0x3C</code>. Finding that value is easy. In my example, <code class="language-plaintext highlighter-rouge">0030</code> is for the <code class="language-plaintext highlighter-rouge">3</code>, and <code class="language-plaintext highlighter-rouge">0C</code> is for the <code class="language-plaintext highlighter-rouge">C</code>. In the upper left corner you should be able to comfirm it’s the right value. For me, it’s displayed as <code class="language-plaintext highlighter-rouge">003C</code>.</p>
<blockquote>
<p>Note: if you can’t find your value, chances are it’s on a different page. You can scroll through the pages with <kbd>ctrl</kbd> + <kbd>pg up</kbd>/<kbd>pg down</kbd></p>
</blockquote>
<p>As you can see, the value set is <code class="language-plaintext highlighter-rouge">01</code>. Set that to whatever value you got from above, that corresponds with the desired DVMT value. Again, <code class="language-plaintext highlighter-rouge">0x3C</code> is for my pc specific, yours will probably be different. So press <kbd>enter</kbd>, and just type it (numlock might be enabled, be aware of that). If everything goes right, that value should be highlighted now.</p>
<p><img src="https://user-images.githubusercontent.com/53275876/132032193-4e411771-dfc6-4eca-8112-2f3561dc366a.jpg" alt="OFFSET_EDITING" /></p>
<p>Press again <kbd>enter</kbd> to finish the editing.</p>
<p><img src="https://user-images.githubusercontent.com/53275876/132032317-6fc2b414-2b95-4a46-8f9b-1d85e516ea61.jpg" alt="OFFSET_AFTER" /></p>
</li>
<li>Press <kbd>ctrl</kbd> + <kbd>W</kbd> to save. Press <kbd>alt</kbd> + <kbd>Q</kbd> to quit and turn the computer off. Done!</li>
</ol>
<p>The DVMT value should be modifed now. Not sure how to verify it, other than trying to boot macOS and seeing if it works. Be aware that resetting your BIOS will also reset the DVMT configuration, and you will need to do this over again. So, it can be handy to write that value down somewhere. Also, note that this value may change with BIOS updates.</p>
<p>If something isn’t clear, leave a comment.</p>
<p>Sources:</p>
<ul>
<li><a href="https://dortania.github.io/OpenCore-Post-Install/misc/msr-lock.html#turning-off-cfg-lock-manually">OpenCore Post-Install Guide</a></li>
<li><a href="https://www.reddit.com/r/hackintosh/comments/hz2rtm/cfg_lockunlocking_alternative_method/">CFG Lock unlocking</a></li>
</ul>JJTechThis tutorial was originally posted by me as a Gist. It is modified from a tutorial I found on Reddit.