<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Yap Han Chiang</title>
    <description>The latest articles on DEV Community by Yap Han Chiang (@hanchiang).</description>
    <link>https://dev.to/hanchiang</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F561799%2Fd96b95ec-a17f-43fd-a37a-db7c10a9846a.jpg</url>
      <title>DEV Community: Yap Han Chiang</title>
      <link>https://dev.to/hanchiang</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hanchiang"/>
    <language>en</language>
    <item>
      <title>Reverse engineering CryptoPanic REST API</title>
      <dc:creator>Yap Han Chiang</dc:creator>
      <pubDate>Sun, 28 May 2023 13:36:47 +0000</pubDate>
      <link>https://dev.to/hanchiang/reverse-engineering-cryptopanic-rest-api-4o2a</link>
      <guid>https://dev.to/hanchiang/reverse-engineering-cryptopanic-rest-api-4o2a</guid>
      <description>&lt;p&gt;&lt;a href="https://cryptopanic.com/"&gt;CryptoPanic&lt;/a&gt; is a news aggregator website for trending news based on social sentiment. It's a good website for keeping up to date on the latest news, as well as using it as a trading signal.&lt;/p&gt;

&lt;p&gt;Its compact layout reminds me of hacker news. I was looking through the network requests and got intrigued after finding out that the main data is not in plaintext:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dbBZlVlr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1684933900016/21180fe6-5d97-48ae-abbf-a18237c27228.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dbBZlVlr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1684933900016/21180fe6-5d97-48ae-abbf-a18237c27228.png" alt="Posts request encrypted data" width="800" height="148"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The server sends some sort of encoded data, and the client decodes it. After digging around the js bundle, I manage to recover the plaintext data that is displayed on the website.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: This post is for educational purposes only.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The reverse engineering process
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Open the js bundle
&lt;/h3&gt;

&lt;p&gt;The first thing is to find out which part of the javascript code is responsible for decoding this response. Begin by searching for and opening the js bundle &lt;code&gt;cryptopanic.min.xxxxx.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DLL6HRGj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1684934522582/8edfd255-6ccc-4827-a3e8-2c755efdf099.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DLL6HRGj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1684934522582/8edfd255-6ccc-4827-a3e8-2c755efdf099.png" alt="Chrome consoel js bundle" width="800" height="156"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Locate the part of the code that decodes the data
&lt;/h3&gt;

&lt;p&gt;Searching for "decrypt" immediately zooms in on this function called &lt;code&gt;dc&lt;/code&gt; that does the core work of decoding the data.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dk()&lt;/code&gt; returns the encryption key, together with parameter &lt;code&gt;t&lt;/code&gt; which is used as the Initialization Vector(IV), are passed through the AES algorithm with zero padding.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;wordArrayToByteArray&lt;/code&gt; converts the decrypted response into a byte array, and &lt;code&gt;pako.default.inflate&lt;/code&gt; decompresses the data, and converts them into javascript's utf-16.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iRulimtu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1684934721630/84f85625-e91b-4c3a-832b-69c8c26a8f5e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iRulimtu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1684934721630/84f85625-e91b-4c3a-832b-69c8c26a8f5e.png" alt="Decrypt and inflate" width="697" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After this step, the result is a JSON string of the API response. The JSON version looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--X-XMedYw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1685276053358/a39775b8-ba8a-41c6-a7e4-5cab0f6c7cd5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--X-XMedYw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1685276053358/a39775b8-ba8a-41c6-a7e4-5cab0f6c7cd5.png" alt="Posts raw data" width="800" height="667"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Transform the raw response into a usable array of objects
&lt;/h3&gt;

&lt;p&gt;The last step is to transform the raw JSON into an array of objects so that it is easy to work with.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--o4dv3iB4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1685275897099/2e8180ed-7701-4f68-8782-03f1b0ccd3fe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--o4dv3iB4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1685275897099/2e8180ed-7701-4f68-8782-03f1b0ccd3fe.png" alt="Posts normalized data" width="694" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Revisiting the arguments passed to &lt;code&gt;dc&lt;/code&gt; earlier, the first argument &lt;code&gt;t&lt;/code&gt;(IV) is the first 16 characters of the module and some string or the CSRF token.&lt;/p&gt;

&lt;p&gt;For posts API, &lt;code&gt;t&lt;/code&gt; is &lt;code&gt;news&lt;/code&gt; and &lt;code&gt;n&lt;/code&gt; is empty.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YQjJksED--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1685276708104/c94a9a2e-9b89-4dc0-b619-6e7da41331ce.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YQjJksED--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1685276708104/c94a9a2e-9b89-4dc0-b619-6e7da41331ce.png" alt="Decoding posts caller" width="498" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For dashboard API, &lt;code&gt;t&lt;/code&gt; is also &lt;code&gt;news&lt;/code&gt; while &lt;code&gt;n&lt;/code&gt; is &lt;code&gt;rnlistrnlistrnlistrnlist&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RFaqs4F8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1685276913945/ebedbcc5-304a-4737-8005-530b55508413.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RFaqs4F8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1685276913945/ebedbcc5-304a-4737-8005-530b55508413.png" alt="Decoding dashboard caller" width="498" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The final output
&lt;/h3&gt;

&lt;p&gt;This is the final output of the posts and dashboard response after decrypting, decompressing and normalizing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kInnOwuV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1685277081971/a75e011f-9512-4391-b940-ea341b13242b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kInnOwuV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1685277081971/a75e011f-9512-4391-b940-ea341b13242b.png" alt="Posts normalized" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cZjIRPCY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1685277094868/a54111f0-113a-4f0e-bbf7-14f87b21e85f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cZjIRPCY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1685277094868/a54111f0-113a-4f0e-bbf7-14f87b21e85f.png" alt="Dashboard normalized" width="763" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lw95FE2F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1685277172266/68b886e0-959c-4b9b-9743-350803a4847c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lw95FE2F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1685277172266/68b886e0-959c-4b9b-9743-350803a4847c.png" alt="Cryptopanic home page highlighted" width="800" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it out yourself
&lt;/h2&gt;

&lt;p&gt;Here is a &lt;a href="https://gist.github.com/hanchiang/ae411d08cb238ccefa0aef4b2c2899e0"&gt;gist&lt;/a&gt; that you can use to try decoding the response yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to use&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create an &lt;code&gt;.env&lt;/code&gt; file with &lt;code&gt;CRYPTOPANIC_ENCRYPTION_KEY&lt;/code&gt; and &lt;code&gt;CRYPTOPANIC_CSRF_TOKEN&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Install the dependencies: &lt;code&gt;npm install dotenv pako crypto-js&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run &lt;code&gt;node decrypt.js [post|dashboard]&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


</description>
      <category>reverseengineering</category>
      <category>webdev</category>
      <category>cryptopanic</category>
      <category>api</category>
    </item>
  </channel>
</rss>
