<?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://mijailovic.net/feed.xml" rel="self" type="application/atom+xml" /><link href="https://mijailovic.net/" rel="alternate" type="text/html" /><updated>2025-09-07T09:34:03+00:00</updated><id>https://mijailovic.net/feed.xml</id><title type="html">Nemanja Mijailovic’s Blog</title><subtitle>My personal blog</subtitle><entry><title type="html">How to (actually) become an expert in .NET</title><link href="https://mijailovic.net/2025/09/07/dotnet/" rel="alternate" type="text/html" title="How to (actually) become an expert in .NET" /><published>2025-09-07T09:30:00+00:00</published><updated>2025-09-07T09:30:00+00:00</updated><id>https://mijailovic.net/2025/09/07/dotnet</id><content type="html" xml:base="https://mijailovic.net/2025/09/07/dotnet/"><![CDATA[<p>The influencers want you to think that you must learn a gazillion different technologies to become a
successful .NET developer. These days, the internet is full of .NET “roadmaps” that include everything
but the kitchen sink: Azure, AWS, Redis, Docker, Postgres, GraphQL, gRPC, Dapper, ELK stack, CQRS, you
name it.</p>

<p>There are several problems with this trend. One is that it generates fear and anxiety that it’s impossible
to become a good developer, because the list of things you “must” know is growing by the day. It might also
lead you to focus on the wrong things and waste the time you could spend <em>actually</em> improving your .NET skills.
The other problem is that these claims are objectively wrong. I’ve been using .NET for over 15 years, 6 of
which I’ve spent working for Microsoft, where I’m currently a principal engineer. If I were to be judged by
what influencers call minimal .NET knowledge, I wouldn’t even qualify as a junior developer.</p>

<p>I’m not being funny or anything, but shouldn’t you learn Redis only if you, like, work with Redis? You don’t have
to learn GraphQL to be a good .NET developer, but you should absolutely learn how memory management works. Kafka and
RabbitMQ are optional—knowing how async/await works is essential. Don’t get me wrong: these are all cool technologies
and it’s great to be familiar with them. But most technologies can be easily learned when there is a real need. What
truly matters is mastering the knowledge of .NET itself, and that’s what this post is all about.</p>

<h2 id="how-to-read-this-guide">How to read this guide</h2>

<p>I’m going to share with you the list of resources that will help you to become a great .NET developer. Even
though I tried to summarize only the essentials, it’s still a lot of information. Mastering every area I’m
gonna talk about will take you months or even years, so don’t get discouraged early on. It’s impossible to
learn everything immediately—what matters is expanding your expertise slowly, topic by topic.</p>

<p>You can learn the topics I presented in any order. For example, if your job requires you to write
high-performance code, you could start your learning journey by focusing on the performance and tools
sections. Or if you want to broaden your C# knowledge first, you could start with <em>C# learning resources</em>.
If you are not sure, <em>Exploring .NET internals</em> is the best place to get started.</p>

<p>Before we start: if you are an absolute .NET beginner, this post is probably not for you. If you want to learn C#,
I recommend watching the <a href="https://www.youtube.com/playlist?list=PLdo4fOcmZ0oULFjxrOagaERVAMbmG20Xe">C# for Beginners</a>
YouTube video course first. Now let’s get started.</p>

<h2 id="books">Books</h2>

<p>If you had asked me ten years ago what’s the best way to learn .NET, I would have probably told you to read CLR via C#.
Although it’s still one of my favorite technical books ever, .NET has changed so much since the book came out that
I can no longer recommend it as the most efficient way to master .NET. In fact, I think that the quality of technical
books has been decreasing over the years, so there are fewer and fewer books that I would consider must-reads.</p>

<p>Having said that, if you like reading books, there are still some excellent ones out there! If I had to recommend only
one .NET book, it would be
<a href="https://www.ebooks.com/en-cz/book/210046474/framework-design-guidelines/krzysztof-cwalina/">Framework Design Guidelines</a>.
Written by .NET architects, it’s a collection of conventions and best practices for writing idiomatic .NET code. What
elevates this book from the rest is that it’s full of comments and annotations from .NET legends such as Jeffrey Richter,
Joe Duffy, Rico Mariani, and Vance Morrison, in which they explain not only the best practices, but also the reasoning
behind them.</p>

<p>Another book I loved is <a href="https://www.writinghighperf.net/">Writing High-Performance .NET Code</a>. When it came out in 2018, it
was the most comprehensive guide to .NET performance. The only thing that’s holding me back from giving it my seal of approval
now is that I haven’t re-read it since, so I’m not sure if it still holds up today (my guess is that it’s still relevant).</p>

<h2 id="exploring-net-internals">Exploring .NET internals</h2>

<p>I think the key to becoming a .NET expert is learning how things work under the hood. Gaining a deep understanding of
async/await, string interpolation, spans, and garbage collection will give you superpowers and help you stand out from the
crowd. The .NET team regularly publishes deep dives on their blog (well, it’s just Stephen Toub in most cases), and they are
great, without exception. Here are the ones I consider the most important:</p>

<ul>
  <li><a href="https://devblogs.microsoft.com/dotnet/how-async-await-really-works/">How Async/Await Really Works in C#</a></li>
  <li><a href="https://devblogs.microsoft.com/dotnet/string-interpolation-in-c-10-and-net-6/">String Interpolation in C# 10 and .NET 6</a></li>
  <li><a href="https://devblogs.microsoft.com/dotnet/configureawait-faq/">ConfigureAwait FAQ</a></li>
  <li><a href="https://devblogs.microsoft.com/dotnet/an-introduction-to-system-threading-channels/">An Introduction to System.Threading.Channels</a></li>
  <li><a href="https://devblogs.microsoft.com/dotnet/understanding-the-whys-whats-and-whens-of-valuetask/">Understanding the Whys, Whats, and Whens of ValueTask</a></li>
  <li><a href="https://learn.microsoft.com/en-us/archive/msdn-magazine/2018/january/csharp-all-about-span-exploring-a-new-net-mainstay">All About Span: Exploring a New .NET Mainstay</a></li>
</ul>

<p>If you prefer watching videos, <a href="https://www.youtube.com/playlist?list=PLdo4fOcmZ0oX8eqDkSw4hH9cSehrGgdr1">Deep .NET</a>
is hands down the best collection of .NET deep dives on the internet. These videos cover the same topics as the blog
posts I mentioned and even more, so they are definitely worth your time.</p>

<h2 id="c-learning-resources">C# learning resources</h2>

<p>Assuming you already know the basics of C#, progressing further is fairly straightforward. Whether you want to
learn about the new C# features or just fill the gaps in your knowledge, you should check out the following pages:</p>

<ul>
  <li><a href="https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-14">What’s new in C# 14</a></li>
  <li><a href="https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-10/overview">What’s new in .NET 10</a></li>
</ul>

<p>These two articles describe the new features in the latest versions of C# and .NET, but I also recommend reading about
all previous versions. Even if you are an experienced .NET developer, I think you will still learn something new.</p>

<h2 id="staying-up-to-date">Staying up to date</h2>

<p>First of all, you don’t need to know about every new runtime or language feature. You’ll be perfectly fine even
if you don’t start using <code class="language-plaintext highlighter-rouge">readonly ref struct</code>, record types, or pattern matching right away. But I would still
advise you to periodically check what’s been going on in the .NET ecosystem. It’s not necessary to do this on a
weekly or even a monthly basis—doing this once a year should be more than enough.</p>

<p>Your main source of information should be the <a href="https://devblogs.microsoft.com/dotnet/">.NET blog</a> (other blogs
often cherry-pick random pieces of information from official .NET posts). Not everything on the .NET blog is
required reading, though. For me, the most illuminating posts are the ones that showcase the improvements in
the latest .NET versions. My personal highlights are the following series:</p>

<ul>
  <li><a href="https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-9/">Performance Improvements in .NET 9</a></li>
  <li><a href="https://devblogs.microsoft.com/dotnet/dotnet-9-networking-improvements/">.NET 9 Networking Improvements</a></li>
  <li><a href="https://devblogs.microsoft.com/dotnet/system-text-json-in-dotnet-9/">What’s new in System.Text.Json in .NET 9</a></li>
  <li><a href="https://devblogs.microsoft.com/dotnet/file-io-improvements-in-dotnet-6/">File IO improvements in .NET 6</a></li>
</ul>

<p>These are just the latest posts in the series—if you like them, I highly recommend reading the earlier posts
in the series as well.</p>

<h2 id="mastering-the-key-libraries">Mastering the key libraries</h2>

<p>To be an effective .NET developer, it’s more important to be proficient with the standard library than
to be familiar with any specific external library. It’s definitely worth learning the modern APIs for
<a href="https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/overview">JSON serialization and deserialization</a>.
Learning how to use the HttpClient correctly is also essential—it seems obvious, but it’s surprisingly
tricky to get right. Here are my recommended articles about writing reliable networking code and avoiding
common pitfalls:</p>

<ul>
  <li><a href="https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines">Guidelines for using HttpClient</a></li>
  <li><a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factory">IHttpClientFactory with .NET</a></li>
  <li><a href="https://learn.microsoft.com/en-us/dotnet/core/resilience/http-resilience">Build resilient HTTP apps: Key development patterns</a></li>
</ul>

<p>Outside the standard library, Newtonsoft.Json is still so widely used that it’s very useful to at least
know how to use it optimally <a href="https://www.newtonsoft.com/json/help/html/performance.htm">for performance</a>.</p>

<h2 id="writing-high-performance-code">Writing high-performance code</h2>

<p>Modern .NET is incredibly fast. In fact, I would say that high performance is one of its defining characteristics,
so it pays off to understand how to take full advantage of the platform’s high-performance features.</p>

<p>Stephen Toub’s annual blog posts on
<a href="https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-9/">performance improvements in .NET</a>
are among the most valuable resources for learning about .NET performance. Although they are published as blog
posts, they really are books in disguise—the latest installment is more than 300 pages long. Unless you have
unlimited free time, reading them end-to-end is next to impossible. My recommended way of consuming these posts
is to scan through the topics and carefully read sections that pique your curiosity. What does that mean? If you
are a compiler optimization geek, you might want to read sections about the latest JIT and PGO improvements. But
if you are looking for practical knowledge you can apply immediately in your backend service, you could read
sections about JSON and networking improvements.</p>

<p>Writing the fastest possible .NET code requires understanding how garbage collection works. The official documentation on
<a href="https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/">garbage collection</a> is great: it’s easy to read and
very thorough. It will teach you how generations work, what is the large object heap, what are the differences between
workstation and server garbage collection, and much more. But the definitive resource for mastering memory in .NET is the
<a href="https://github.com/Maoni0/mem-doc/blob/master/doc/.NETMemoryPerformanceAnalysis.md">.NET Memory Performance Analysis</a>
document written by Maoni Stephens, the .NET GC architect. It’s the ultimate guide on how to approach memory performance
analysis, and one of the best ways to deepen your understanding of how memory works in .NET.</p>

<p>Finally, it’s important to learn how to measure performance correctly. <a href="https://benchmarkdotnet.org/">BenchmarkDotNet</a>
is the undisputed king of .NET benchmarking and one of the few libraries everyone should be using. However, knowing how
to use BenchmarkDotNet is just half of the story. Writing good benchmarks is not trivial and you can easily end up
measuring the wrong thing. That’s where the .NET team’s
<a href="https://github.com/dotnet/performance/blob/main/docs/microbenchmark-design-guidelines.md">Microbenchmark Design Guidelines</a>
come into play. Think of this document as the missing BenchmarkDotNet manual—it goes beyond the syntax and teaches you how
to design your benchmarks the right way. The <a href="https://github.com/dotnet/performance">.NET Performance</a> GitHub repository
also contains all benchmarks for the .NET standard library. If you ever need inspiration when designing benchmarks, it’s
the best place to start.</p>

<h2 id="useful-tools">Useful tools</h2>

<p>I think everyone should have a decompiler in their toolbox. It’s useful not only for reverse engineering, but also
if you want to learn how seemingly simple statements like string interpolation work under the hood. The choice of
decompiler is up to you—I personally prefer <a href="https://github.com/icsharpcode/ILSpy">ILSpy</a>.</p>

<p>One more indispensable tool is the <a href="https://source.dot.net/">.NET Source Browser</a>. I often use it to inspect how
.NET classes are implemented and to find examples of good code design patterns. Even though the .NET source code
is available in the <a href="https://github.com/dotnet/runtime">.NET Runtime</a> GitHub repository, the source browser makes
navigation much easier.</p>

<p>.NET Framework’s tooling was pretty bare-bones. In contrast, .NET Core comes with some fantastic
<a href="https://learn.microsoft.com/en-us/dotnet/core/diagnostics/tools-overview">diagnostic tools</a>. You most likely won’t
use all of them in your day-to-day work, but in the right circumstances, they can be incredibly handy. At the very
least, you should be aware of which tools exist. That way, if you ever need to collect a memory dump, you’ll know
you can use <a href="https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-dump">dotnet-dump</a>. Or if you need to
debug runaway threads, <a href="https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-stack">dotnet-stack</a> can
capture the stacks of all threads in a .NET process.</p>

<h2 id="conclusion">Conclusion</h2>

<p>I hope this post was not too overwhelming! But my main message is simple: focus on having rock-solid fundamentals
and you’ll be golden. Hopefully, you also discovered some new and interesting learning resources. Let me know if I
missed anything you consider essential!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[The influencers want you to think that you must learn a gazillion different technologies to become a successful .NET developer. These days, the internet is full of .NET “roadmaps” that include everything but the kitchen sink: Azure, AWS, Redis, Docker, Postgres, GraphQL, gRPC, Dapper, ELK stack, CQRS, you name it.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://mijailovic.net/assets/img/2025-09-07-dotnet.png" /><media:content medium="image" url="https://mijailovic.net/assets/img/2025-09-07-dotnet.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">How scammers trick fans on Bandcamp—and how to protect yourself</title><link href="https://mijailovic.net/2025/06/01/bandcamp-scams/" rel="alternate" type="text/html" title="How scammers trick fans on Bandcamp—and how to protect yourself" /><published>2025-06-01T15:50:00+00:00</published><updated>2025-06-01T15:50:00+00:00</updated><id>https://mijailovic.net/2025/06/01/bandcamp-scams</id><content type="html" xml:base="https://mijailovic.net/2025/06/01/bandcamp-scams/"><![CDATA[<p>It’s Friday, and <em>Invincible Shield</em>, the long-awaited Judas Priest album, is finally out. To your
surprise, it’s available on Bandcamp! You buy the album immediately, ecstatic that your favorite band
has finally started releasing their music on your favorite music platform. You wake up the next day,
only to find the album has vanished from your collection—you’ve fallen victim to a scam.</p>

<p>Fake artist pages often appear on Bandcamp, especially on major album release days. Anytime you see a new
page for a big name (such as Iron Maiden, Taylor Swift, John Coltrane, Pantera, James Brown, or Apocalyptica),
there’s a 99.99% chance the page is a scam. Bandcamp does an excellent job of disabling these pages quickly.
Some slip through the cracks and survive for several days or even weeks, but most are removed within a day.
But even just a few hours is enough for buyers to lose money they will never get back.</p>

<p>Even though I’m an experienced user (I have purchased over 2,000 albums on Bandcamp over the past decade),
I too have been deceived by convincing fake pages. That got me wondering: how much money is lost to scams on
Bandcamp each month? In today’s post, I’ll show you how I calculated that number and share some tips to help
you avoid getting scammed.</p>

<h2 id="collecting-the-data">Collecting the data</h2>

<p>If you are a Bandcamp user, you are probably familiar with their music discovery feed. The feed can show
best-selling albums, new arrivals, or just random releases. While the list of new arrivals isn’t especially
useful for discovering quality music, it was a key piece in my quest to figure out how much money fans
lose to scams.</p>

<p><img src="/assets/img/2025-06-01-discover.png" alt="" /></p>

<p>All new releases show up in the feed. When Bandcamp detects a copyright violation or any other shenanigans,
they disable the offending album, and it disappears from the feed. However, if you happen to remember the
album’s ID, you can still use the Bandcamp API to retrieve the price and the number of fans who had bought
it before it was disabled. That gave me an idea to periodically take a snapshot of all new releases, so I
could track which ones were removed.</p>

<p>And that’s exactly what I had been doing in April 2024. Every day, I took a snapshot of the new releases
feed. After one month of collecting data, I had the IDs of all albums released in April. With these IDs,
I could check whether the albums had been disabled—and if they were, calculate how much money fans had
spent on them. After a few hours of writing code to summarize the raw data, I had the numbers.</p>

<h2 id="the-statistics">The statistics</h2>

<p>Before diving into the topic of money, let me first show you some volume statistics:</p>

<ul>
  <li><strong>81,391</strong> albums were released in April 2024.</li>
  <li><strong>6,475</strong> of those were eventually disabled.</li>
  <li><strong>5,158</strong> disabled albums were never purchased by anyone.</li>
  <li><strong>1,317</strong> disabled albums were purchased at least once.</li>
  <li><strong>1,287</strong> different Bandcamp users were scammed.</li>
</ul>

<p>The sheer number of releases significantly exceeded my expectations. But the number of disabled albums surprised
me even more—I didn’t expect it to be in the thousands. Thanks to Bandcamp’s quick reaction time, most fake albums
were never purchased. But 1,317 were, and in the next section, we’ll focus on those. Let’s see how much money was
lost on them.</p>

<h2 id="lets-talk-money">Let’s talk money</h2>

<p>April 2024 was a month of high-profile releases. Believe it or not, both Beyoncé and Taylor Swift
released new albums! It was an ideal situation for scammers, but they didn’t turn it into a profit.
Fake Beyoncé’s <em>Cowboy Carter</em> took in only £34, and fake Taylor’s <em>The Tortured Poets Department</em>
just €14. Fake Dua Lipa was more successful: fake <em>Radical Optimism</em> tricked unsuspecting fans into
spending $227.76.</p>

<p>The three most successful fake albums were also new releases:</p>

<ul>
  <li>Pearl Jam - <em>Dark Matter</em> (<strong>$1,212</strong>)</li>
  <li>Vampire Weekend - <em>Only God Was Above Us</em> (<strong>$1,210.99</strong>)</li>
  <li>Bladee - <em>Cold Visions</em> (<strong>$552</strong>)</li>
</ul>

<p>Sadly, two of these crossed the $1,000 threshold, showing that a well-timed scam can be a lucrative
business. Many of these scam campaigns were cleverly designed to include entire discographies—not just the
new album—so the amount of stolen money per artist was even higher:</p>

<ul>
  <li>Vampire Weekend (<strong>$2,123.16</strong>)</li>
  <li>Pearl Jam (<strong>$1,408.44</strong>)</li>
  <li>DJ SickMix (<strong>$1,140</strong>)</li>
</ul>

<p>But enough about individual statistics—let’s get to the numbers you’ve all been waiting for! The total
amount of money lost to scams in April 2024 was <strong>$18,773.55</strong>, <strong>£5,364.49</strong>, and <strong>€1,670.73</strong>. Altogether,
that adds up to approximately <strong>$27,280</strong> or <strong>€25,420</strong>. That’s quite a lot of money!</p>

<p>The good news is that you can easily protect yourself from a scam by following just a few simple practices, which
I’ll describe in the next section.</p>

<h2 id="recommendations">Recommendations</h2>

<p>Scammers on Bandcamp are clever—they often create pages that are visually indistinguishable from
the real thing. Here’s a screenshot of one of many fake pages impersonating Norwegian musician Ihsahn:</p>

<p><img src="/assets/img/2025-06-01-ihsahn.png" alt="" /></p>

<p>Everything is there: high-resolution album cover, social media links, and even tour dates. It might seem
impossible to differentiate the fake page from the real one, but a few simple tips can go a long way.</p>

<h4 id="avoid-sketchy-subdomains">Avoid sketchy subdomains</h4>

<p>First, let’s explain what a subdomain is. It’s super simple: if a band’s page URL on Bandcamp is
<a href="https://immolation.bandcamp.com">immolation.bandcamp.com</a>, their subdomain is <em>immolation</em>. On
Bandcamp, each label and artist has their own subdomain.</p>

<p>You should start paying attention to subdomains, and avoid them if they look sketchy. This is
easier said than done, though. Real artists often form their subdomain by adding a suffix to their
name—a city, state, genre, or just random-sounding abbreviation:</p>

<ul>
  <li>Tower (from New York) is <em>towernyc</em></li>
  <li>Ripped to Shreds (a death metal band) is <em>rippedtoshredsdeathmetal</em></li>
  <li>Cryptopsy is <em>cryptopsyofficial</em></li>
  <li>Deceased is <em>the-true-deceased</em></li>
  <li>Black Curse is <em>blackcurse-svr</em> (<em>svr</em> stands for Sepulchral Voice Records)</li>
</ul>

<p>This common practice gives scammers the opportunity to use an infinite number of plausible-sounding
subdomains for their fake pages.</p>

<p>Despite this, you can still spot most of the fake subdomains fairly easily. For example, Taylor Swift would
never release an album under the <em>quelquunquiexiste</em> subdomain, and Beyoncé would never use the <em>d-jam</em> subdomain.
Album names as subdomains may look more convincing, but they are almost never used by real artists.
This means <em>radicaloptimism</em> is not Dua Lipa’s real page.</p>

<p>Sometimes, scammers use an authentic-looking subdomain, such as <em>pearljam</em> or <em>vampireweekend</em>. In those cases,
you’ll need the techniques from the next two sections.</p>

<h4 id="be-patient">Be patient</h4>

<p>If it looks like a major artist has just released an album for the first time ever on Bandcamp, chances are
it’s a fake. The best thing you can do is wait at least a few days—or better yet, a few weeks. If the
page hasn’t been taken down during that time, you can be much more confident that the album is legit.</p>

<h4 id="contact-the-label">Contact the label</h4>

<p>When your favorite label shows up on Bandcamp with tons of releases, there’s still no guarantee that the
page really belongs to them. Check out their Facebook or Instagram—if they don’t mention their new Bandcamp
page, that should be a red flag. In some cases, their socials might even warn you about the ongoing scam,
like this label <a href="https://www.facebook.com/quasipop/posts/1261359247613754">did</a>. If you want to be 100% sure,
find the label’s email and ask whether the new Bandcamp account really belongs to them. The response you are
hoping for looks something like this:</p>

<blockquote>
  <p>Yepp, this is really us, all legit!</p>
</blockquote>

<h2 id="conclusion">Conclusion</h2>

<p>To end on a positive note: I still think Bandcamp is by far the best music platform on the internet. The number
of fake albums is far outweighed by the fact that, in the past year alone, fans have paid real artists over $16
million per month on average. And now that you’ve learned how to better protect yourself, hopefully scammers
won’t affect you at all!</p>

<p>Listen to great music, support independent artists, and stay tuned for my upcoming post. In the crowning
jewel of my investigative blogging, you’ll read the wild story of the scammiest scammer of them all.</p>

<p><small><i>Huge thanks to my wife, Milica Miljkov, for editing this post and reminding me that no matter
how good ChatGPT gets, she’s still better. (Yes, I tried to replace her. No, it didn’t work.)</i></small></p>]]></content><author><name></name></author><summary type="html"><![CDATA[It’s Friday, and Invincible Shield, the long-awaited Judas Priest album, is finally out. To your surprise, it’s available on Bandcamp! You buy the album immediately, ecstatic that your favorite band has finally started releasing their music on your favorite music platform. You wake up the next day, only to find the album has vanished from your collection—you’ve fallen victim to a scam.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://mijailovic.net/assets/img/2025-06-01-discover.png" /><media:content medium="image" url="https://mijailovic.net/assets/img/2025-06-01-discover.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">High-performance string formatting in .NET</title><link href="https://mijailovic.net/2025/05/14/high-performance-strings/" rel="alternate" type="text/html" title="High-performance string formatting in .NET" /><published>2025-05-14T12:00:00+00:00</published><updated>2025-05-14T12:00:00+00:00</updated><id>https://mijailovic.net/2025/05/14/high-performance-strings</id><content type="html" xml:base="https://mijailovic.net/2025/05/14/high-performance-strings/"><![CDATA[<p>One of the topics I covered in my previous post on
<a href="/2025/04/10/memory-optimizations/">memory optimizations</a> was string formatting.
As I was writing that post, I uncovered an embarrassing gap in my knowledge!</p>

<p>In .NET Framework, the default string formatting method was <code class="language-plaintext highlighter-rouge">string.Format</code>, which requires the runtime to
call <code class="language-plaintext highlighter-rouge">ToString</code> on all its arguments. For example, <code class="language-plaintext highlighter-rouge">string.Format("{0} = {1}", Key, Value)</code> would internally call
both <code class="language-plaintext highlighter-rouge">Key.ToString()</code> and <code class="language-plaintext highlighter-rouge">Value.ToString()</code> to produce the final result. These two extra allocations
introduce a performance penalty, which is unfortunate because we don’t need those temporary
strings—we only need the final formatted result.</p>

<p>Having worked with the .NET Framework for quite a long time, I had assumed that interpolated strings like
<code class="language-plaintext highlighter-rouge">$"{Key} = {Value}"</code> worked the same way. I was excited to learn that I was wrong! .NET Core has come a long
way in eliminating unnecessary allocations and boxing. In today’s blog post, we’ll explore high-performance
<code class="language-plaintext highlighter-rouge">ToString</code> alternatives that help you avoid temporary string allocations, and in some cases, avoid allocations
altogether!</p>

<h2 id="how-string-interpolation-works">How string interpolation works</h2>

<p>Before we dive into high-performance topics, let’s refresh our memory on how string interpolation works behind
the scenes. Imagine you are working with the following classes:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="n">record</span> <span class="nf">Point3D</span><span class="p">(</span><span class="kt">double</span> <span class="n">X</span><span class="p">,</span> <span class="kt">double</span> <span class="n">Y</span><span class="p">,</span> <span class="kt">double</span> <span class="n">Z</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">override</span> <span class="nf">ToString</span><span class="p">()</span> <span class="p">=&gt;</span> <span class="s">$"(</span><span class="p">{</span><span class="n">X</span><span class="p">}</span><span class="s">, </span><span class="p">{</span><span class="n">Y</span><span class="p">}</span><span class="s">, </span><span class="p">{</span><span class="n">Z</span><span class="p">}</span><span class="s">)"</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">public</span> <span class="n">record</span> <span class="nf">Line3D</span><span class="p">(</span><span class="n">Point3D</span> <span class="n">Start</span><span class="p">,</span> <span class="n">Point3D</span> <span class="n">End</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">override</span> <span class="nf">ToString</span><span class="p">()</span> <span class="p">=&gt;</span> <span class="s">$"[</span><span class="p">{</span><span class="n">Start</span><span class="p">}</span><span class="s">, </span><span class="p">{</span><span class="n">End</span><span class="p">}</span><span class="s">]"</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>If you inspect the generated code for the <code class="language-plaintext highlighter-rouge">Line3D</code> class with a .NET decompiler such as
<a href="https://github.com/icsharpcode/ILSpy">ILSpy</a>, you will see that all the heavy lifting in its
<code class="language-plaintext highlighter-rouge">ToString</code> implementation is done at compile time. The compiler transforms the interpolation
expression into a sequence of <code class="language-plaintext highlighter-rouge">AppendLiteral</code> and <code class="language-plaintext highlighter-rouge">AppendFormatted</code> calls:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">handler</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">DefaultInterpolatedStringHandler</span><span class="p">(</span><span class="m">4</span><span class="p">,</span> <span class="m">2</span><span class="p">);</span>

<span class="n">handler</span><span class="p">.</span><span class="nf">AppendLiteral</span><span class="p">(</span><span class="s">"["</span><span class="p">);</span>
<span class="n">handler</span><span class="p">.</span><span class="nf">AppendFormatted</span><span class="p">(</span><span class="n">Start</span><span class="p">);</span>
<span class="n">handler</span><span class="p">.</span><span class="nf">AppendLiteral</span><span class="p">(</span><span class="s">", "</span><span class="p">);</span>
<span class="n">handler</span><span class="p">.</span><span class="nf">AppendFormatted</span><span class="p">(</span><span class="n">End</span><span class="p">);</span>
<span class="n">handler</span><span class="p">.</span><span class="nf">AppendLiteral</span><span class="p">(</span><span class="s">"]"</span><span class="p">);</span>

<span class="k">return</span> <span class="n">handler</span><span class="p">.</span><span class="nf">ToStringAndClear</span><span class="p">();</span>
</code></pre></div></div>

<p>The code is self-explanatory. Literal parts of the interpolation expression are added to the interpolated string
handler using <code class="language-plaintext highlighter-rouge">AppendLiteral</code> calls, while placeholder values are added using <code class="language-plaintext highlighter-rouge">AppendFormatted</code> calls. The handler
internally uses a temporary <code class="language-plaintext highlighter-rouge">char</code> buffer from the <code class="language-plaintext highlighter-rouge">ArrayPool&lt;char&gt;.Shared</code> and allocates memory only when the
final string is created.</p>

<p>The formatting of placeholder values happens in the <code class="language-plaintext highlighter-rouge">AppendFormatted</code> method. If the object you want to format
overrides only <code class="language-plaintext highlighter-rouge">ToString</code>, then the runtime must call <code class="language-plaintext highlighter-rouge">ToString</code>. This will create a temporary string, wasting
both CPU and memory (remember, we only need the final interpolated result). In the case of <code class="language-plaintext highlighter-rouge">Line3D</code>, two unnecessary
strings would be allocated (one for each point). But if a type implements the <code class="language-plaintext highlighter-rouge">ISpanFormattable</code> interface,
something magical will happen.</p>

<h2 id="ispanformattable">ISpanFormattable</h2>

<p>Without much fanfare, .NET 6 introduced an important interface called
<a href="https://learn.microsoft.com/en-us/dotnet/api/system.ispanformattable">ISpanFormattable</a>:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">interface</span> <span class="nc">ISpanFormattable</span> <span class="p">:</span> <span class="n">IFormattable</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">bool</span> <span class="nf">TryFormat</span><span class="p">(</span>
        <span class="n">Span</span><span class="p">&lt;</span><span class="kt">char</span><span class="p">&gt;</span> <span class="n">destination</span><span class="p">,</span>
        <span class="k">out</span> <span class="kt">int</span> <span class="n">charsWritten</span><span class="p">,</span>
        <span class="n">ReadOnlySpan</span><span class="p">&lt;</span><span class="kt">char</span><span class="p">&gt;</span> <span class="n">format</span><span class="p">,</span>
        <span class="n">IFormatProvider</span><span class="p">?</span> <span class="n">provider</span>
    <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The interface serves a simple purpose: when a placeholder in an interpolated string needs to be filled,
<code class="language-plaintext highlighter-rouge">DefaultInterpolatedStringHandler</code> checks whether the type implements <code class="language-plaintext highlighter-rouge">ISpanFormattable</code>. If it does, the
<code class="language-plaintext highlighter-rouge">TryFormat</code> method is used to write the content directly into the final buffer, bypassing <code class="language-plaintext highlighter-rouge">ToString</code> entirely.</p>

<p>Types such as <code class="language-plaintext highlighter-rouge">int</code> and <code class="language-plaintext highlighter-rouge">DateTime</code> have implemented this interface from day one, so when you write something like
<code class="language-plaintext highlighter-rouge">$"Today is {DateTime.Now}"</code>, a single allocation occurs—the one for the resulting string.</p>

<p>Of course, the use of this interface is not limited to primitive types. You can implement <code class="language-plaintext highlighter-rouge">ISpanFormattable</code> in any
type, and your <code class="language-plaintext highlighter-rouge">TryFormat</code> implementation will be called instead of <code class="language-plaintext highlighter-rouge">ToString</code> during string interpolation. Although
the signature of <code class="language-plaintext highlighter-rouge">TryFormat</code> might look intimidating, implementing it is usually quite straightforward.</p>

<h2 id="implementing-ispanformattable">Implementing ISpanFormattable</h2>

<p>The official docs don’t explain how to implement <code class="language-plaintext highlighter-rouge">ISpanFormattable</code>, and almost all online blog posts showcase
overly complicated implementations. Let me try to fix that.</p>

<p>First, let’s talk about how <em>not</em> to implement the interface. Since you have access to the destination buffer,
you could manually write every component of your type’s string representation into it. You would also need to
perform bounds checks, but all of this would make your code very fragile and error-prone.</p>

<p>Fortunately, there is a much simpler way to implement <code class="language-plaintext highlighter-rouge">TryFormat</code>: by calling the <code class="language-plaintext highlighter-rouge">MemoryExtensions.TryWrite</code>
extension method and passing it the exact same string format you would use in a <code class="language-plaintext highlighter-rouge">ToString</code> implementation.
The easiest way to explain how this method works is simply to show how it could be used in <code class="language-plaintext highlighter-rouge">Point3D.TryFormat</code>:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="kt">bool</span> <span class="nf">TryFormat</span><span class="p">(</span>
    <span class="n">Span</span><span class="p">&lt;</span><span class="kt">char</span><span class="p">&gt;</span> <span class="n">destination</span><span class="p">,</span>
    <span class="k">out</span> <span class="kt">int</span> <span class="n">charsWritten</span><span class="p">,</span>
    <span class="n">ReadOnlySpan</span><span class="p">&lt;</span><span class="kt">char</span><span class="p">&gt;</span> <span class="n">format</span><span class="p">,</span>
    <span class="n">IFormatProvider</span> <span class="n">provider</span><span class="p">)</span> <span class="p">=&gt;</span>
        <span class="n">destination</span><span class="p">.</span><span class="nf">TryWrite</span><span class="p">(</span><span class="n">provider</span><span class="p">,</span> <span class="s">$"(</span><span class="p">{</span><span class="n">X</span><span class="p">}</span><span class="s">, </span><span class="p">{</span><span class="n">Y</span><span class="p">}</span><span class="s">, </span><span class="p">{</span><span class="n">Z</span><span class="p">}</span><span class="s">)"</span><span class="p">,</span> <span class="k">out</span> <span class="n">charsWritten</span><span class="p">);</span>
</code></pre></div></div>

<p>That’s literally all! You might be thinking that the <code class="language-plaintext highlighter-rouge">$"({X}, {Y}, {Z})"</code> expression generates a temporary
string, but that’s not the case. If you look closely, you’ll notice that the type of that function argument
is not <code class="language-plaintext highlighter-rouge">string</code>, but <code class="language-plaintext highlighter-rouge">MemoryExtensions.TryWriteInterpolatedStringHandler</code>. This means the compiler is doing
the same heavy lifting as before—transforming the string template into a sequence of <code class="language-plaintext highlighter-rouge">AppendLiteral</code> and
<code class="language-plaintext highlighter-rouge">AppendFormatted</code> calls that write directly into <code class="language-plaintext highlighter-rouge">destination</code>. I won’t go into detail, but if you are
interested in how the compiler does this, you can find the explanation
<a href="https://devblogs.microsoft.com/dotnet/string-interpolation-in-c-10-and-net-6/">here</a>.</p>

<p>Now let’s measure the performance improvements using <a href="https://benchmarkdotnet.org/">BenchmarkDotNet</a>. Here
are the results of a benchmark comparing two versions of <code class="language-plaintext highlighter-rouge">$"{line}"</code> interpolation—one where <code class="language-plaintext highlighter-rouge">Line3D</code>
implements only <code class="language-plaintext highlighter-rouge">ToString</code>, and one where it also implements <code class="language-plaintext highlighter-rouge">TryFormat</code>:</p>

<table>
  <thead>
    <tr>
      <th>Method</th>
      <th style="text-align: right">Mean</th>
      <th style="text-align: right">Error</th>
      <th style="text-align: right">StdDev</th>
      <th style="text-align: right">Ratio</th>
      <th style="text-align: right">Allocated</th>
      <th style="text-align: right">Alloc Ratio</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>ToString</td>
      <td style="text-align: right">620.4 ns</td>
      <td style="text-align: right">8.36 ns</td>
      <td style="text-align: right">7.82 ns</td>
      <td style="text-align: right">1.00</td>
      <td style="text-align: right">336 B</td>
      <td style="text-align: right">1.00</td>
    </tr>
    <tr>
      <td>TryFormat</td>
      <td style="text-align: right">433.2 ns</td>
      <td style="text-align: right">4.00 ns</td>
      <td style="text-align: right">3.74 ns</td>
      <td style="text-align: right">0.70</td>
      <td style="text-align: right">104 B</td>
      <td style="text-align: right">0.31</td>
    </tr>
  </tbody>
</table>

<p><code class="language-plaintext highlighter-rouge">ISpanFormattable</code> is a clear winner in both execution speed (30% improvement in the <code class="language-plaintext highlighter-rouge">Ratio</code> column) and
memory usage (69% improvement in the <code class="language-plaintext highlighter-rouge">Alloc Ratio</code> column). And the greatest thing about this optimization
is that it basically didn’t cost us anything—we used the same string format we previously used in <code class="language-plaintext highlighter-rouge">ToString</code>!</p>

<p>If you are worried about duplicating the string format in the <code class="language-plaintext highlighter-rouge">ToString</code> and <code class="language-plaintext highlighter-rouge">TryFormat</code> methods, there is
an elegant solution to that as well. You can implement <code class="language-plaintext highlighter-rouge">ToString</code> like this and rely on the fact that the string
interpolation will internally call <code class="language-plaintext highlighter-rouge">TryFormat</code>, producing the same result:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">override</span> <span class="kt">string</span> <span class="nf">ToString</span><span class="p">()</span> <span class="p">=&gt;</span> <span class="s">$"</span><span class="p">{</span><span class="k">this</span><span class="p">}</span><span class="s">"</span><span class="p">;</span>
</code></pre></div></div>

<p>Isn’t that neat? You gotta love modern .NET! But the story of high-performance string formatting doesn’t end here.</p>

<h2 id="utf-8-string-literals">UTF-8 string literals</h2>

<p>In .NET, strings are stored in memory using UTF-16 encoding. But what happens when you need to send a string over
the network or save it to a file? The standard encoding for these purposes is UTF-8, so in most cases you need to
transcode your strings to UTF-8 first. Wouldn’t it be nice if you could encode strings directly as UTF-8 bytes,
avoiding the unnecessary conversion?</p>

<p>Ever since C# 11, you have been able to create
<a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/utf8-string-literals">UTF-8 string literals</a>
as <code class="language-plaintext highlighter-rouge">ReadOnlySpan&lt;byte&gt;</code> objects by adding the <code class="language-plaintext highlighter-rouge">u8</code> suffix to string constants:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ReadOnlySpan</span><span class="p">&lt;</span><span class="kt">byte</span><span class="p">&gt;</span> <span class="n">hello</span> <span class="p">=</span> <span class="s">"Hi there, hello!"</span><span class="n">u8</span><span class="p">;</span>
</code></pre></div></div>

<p>This syntax is great for <em>constant</em> strings, but it doesn’t allow us to use string interpolation, which significantly
limits its usefulness. But don’t worry—you wouldn’t be reading this post if the .NET team didn’t have a solution for
that too. Say hello to one of the latest additions to .NET:
<a href="https://learn.microsoft.com/en-us/dotnet/api/system.iutf8spanformattable">IUtf8SpanFormattable</a>.</p>

<h2 id="iutf8spanformattable">IUtf8SpanFormattable</h2>

<p>Let me show you the interface first, and you can try to guess what it’s used for:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">interface</span> <span class="nc">IUtf8SpanFormattable</span>
<span class="p">{</span>
    <span class="kt">bool</span> <span class="nf">TryFormat</span><span class="p">(</span>
        <span class="n">Span</span><span class="p">&lt;</span><span class="kt">byte</span><span class="p">&gt;</span> <span class="n">destination</span><span class="p">,</span>
        <span class="k">out</span> <span class="kt">int</span> <span class="n">bytesWritten</span><span class="p">,</span>
        <span class="n">ReadOnlySpan</span><span class="p">&lt;</span><span class="kt">char</span><span class="p">&gt;</span> <span class="n">format</span><span class="p">,</span>
        <span class="n">IFormatProvider</span><span class="p">?</span> <span class="n">provider</span>
    <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>If your first thought was that it looks very similar to
<code class="language-plaintext highlighter-rouge">ISpanFormattable</code>, you were absolutely right—these two interfaces are almost identical.
The key difference is that <code class="language-plaintext highlighter-rouge">IUtf8SpanFormattable</code> is used to format strings into UTF-8
byte buffers, so the destination must be a <code class="language-plaintext highlighter-rouge">Span&lt;byte&gt;</code> instead of a <code class="language-plaintext highlighter-rouge">Span&lt;char&gt;</code>.</p>

<p>Similar to <code class="language-plaintext highlighter-rouge">ISpanFormattable</code>, this new interface can be easily implemented using regular
string interpolation syntax. Instead of calling <code class="language-plaintext highlighter-rouge">MemoryExtensions.TryWrite</code>, you call
<a href="https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/">Utf8.TryWrite</a>:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="kt">bool</span> <span class="nf">TryFormat</span><span class="p">(</span>
    <span class="n">Span</span><span class="p">&lt;</span><span class="kt">byte</span><span class="p">&gt;</span> <span class="n">destination</span><span class="p">,</span>
    <span class="k">out</span> <span class="kt">int</span> <span class="n">bytesWritten</span><span class="p">,</span>
    <span class="n">ReadOnlySpan</span><span class="p">&lt;</span><span class="kt">char</span><span class="p">&gt;</span> <span class="n">format</span><span class="p">,</span>
    <span class="n">IFormatProvider</span> <span class="n">provider</span><span class="p">)</span> <span class="p">=&gt;</span>
        <span class="n">Utf8</span><span class="p">.</span><span class="nf">TryWrite</span><span class="p">(</span><span class="n">destination</span><span class="p">,</span> <span class="n">provider</span><span class="p">,</span> <span class="s">$"(</span><span class="p">{</span><span class="n">X</span><span class="p">}</span><span class="s">, </span><span class="p">{</span><span class="n">Y</span><span class="p">}</span><span class="s">, </span><span class="p">{</span><span class="n">Z</span><span class="p">}</span><span class="s">)"</span><span class="p">,</span> <span class="k">out</span> <span class="n">bytesWritten</span><span class="p">);</span>
</code></pre></div></div>

<p>Same compiler magic, same ease of use. This time, the type of the interpolated
string handler is <code class="language-plaintext highlighter-rouge">Utf8.TryWriteInterpolatedStringHandler</code>. Its <code class="language-plaintext highlighter-rouge">AppendFormatted</code> method internally
calls <code class="language-plaintext highlighter-rouge">IUtf8SpanFormattable.TryFormat</code>, while the <code class="language-plaintext highlighter-rouge">AppendLiteral</code> method encodes its parameters to
UTF-8 at JIT time, which means you pay the runtime cost of transcoding UTF-16 to UTF-8 only once.</p>

<p>The only remaining problem is that even though you have <code class="language-plaintext highlighter-rouge">IUtf8SpanFormattable</code> as the
low-level building block, there is no string interpolation syntax that can use it internally to produce
the final formatted string. To be more specific, you can’t say something like <code class="language-plaintext highlighter-rouge">$"Hello, {name}!"u8</code> and get
a byte array or a span as the result. There’s a pretty easy workaround, though—simply call the <code class="language-plaintext highlighter-rouge">Utf8.TryWrite</code>
method one more time to write the interpolated string into a rented array:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">static</span> <span class="k">readonly</span> <span class="n">Point3D</span> <span class="n">_point</span> <span class="p">=</span> <span class="k">new</span><span class="p">(</span><span class="m">1.2</span><span class="p">,</span> <span class="m">2.5</span><span class="p">,</span> <span class="m">1.8</span><span class="p">);</span>

<span class="p">[</span><span class="n">Benchmark</span><span class="p">]</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">Utf8TryWrite</span><span class="p">()</span>
<span class="p">{</span>
    <span class="kt">var</span> <span class="n">buffer</span> <span class="p">=</span> <span class="n">ArrayPool</span><span class="p">&lt;</span><span class="kt">byte</span><span class="p">&gt;.</span><span class="n">Shared</span><span class="p">.</span><span class="nf">Rent</span><span class="p">(</span><span class="m">128</span><span class="p">);</span>
    <span class="n">Utf8</span><span class="p">.</span><span class="nf">TryWrite</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="s">$"</span><span class="p">{</span><span class="n">_point</span><span class="p">}</span><span class="s">"</span><span class="p">,</span> <span class="k">out</span> <span class="n">_</span><span class="p">);</span>

    <span class="n">ArrayPool</span><span class="p">&lt;</span><span class="kt">byte</span><span class="p">&gt;.</span><span class="n">Shared</span><span class="p">.</span><span class="nf">Return</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>If you benchmark this code, you will notice something fascinating.</p>

<table>
  <thead>
    <tr>
      <th>Method</th>
      <th style="text-align: right">Mean</th>
      <th style="text-align: right">Error</th>
      <th style="text-align: right">StdDev</th>
      <th style="text-align: right">Allocated</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Utf8TryWrite</td>
      <td style="text-align: right">127.5 ns</td>
      <td style="text-align: right">1.28 ns</td>
      <td style="text-align: right">1.19 ns</td>
      <td style="text-align: right">-</td>
    </tr>
  </tbody>
</table>

<p>No allocations at all! And what’s even more important, we didn’t pay in implementation complexity—we used
the same string interpolation syntax we already know and love. Now let’s see if we can find some real-world use
cases for UTF-8 string interpolation.</p>

<h2 id="a-real-world-example">A real-world example</h2>

<p>To send a string over the network using <code class="language-plaintext highlighter-rouge">HttpClient</code>, you typically create an instance of <code class="language-plaintext highlighter-rouge">StringContent</code>.
The first thing the <code class="language-plaintext highlighter-rouge">StringContent</code> constructor does is call <code class="language-plaintext highlighter-rouge">Encoding.UTF8.GetBytes(content)</code>, which
allocates a new byte array. If you know the maximum possible size of your payload, you can avoid this
unnecessary allocation by renting a byte array, formatting the string as UTF-8 directly into it, and
then sending the data using <code class="language-plaintext highlighter-rouge">ByteArrayContent</code>:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">buffer</span> <span class="p">=</span> <span class="n">ArrayPool</span><span class="p">&lt;</span><span class="kt">byte</span><span class="p">&gt;.</span><span class="n">Shared</span><span class="p">.</span><span class="nf">Rent</span><span class="p">(</span><span class="n">MaxSize</span><span class="p">);</span>
<span class="n">Utf8</span><span class="p">.</span><span class="nf">TryWrite</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="s">$"</span><span class="p">{</span><span class="n">data</span><span class="p">}</span><span class="s">"</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">bytesWritten</span><span class="p">);</span>

<span class="k">using</span> <span class="nn">var</span> <span class="n">client</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">HttpClient</span><span class="p">();</span>

<span class="kt">var</span> <span class="n">content</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ByteArrayContent</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="n">bytesWritten</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">request</span> <span class="p">=</span> <span class="k">new</span> <span class="n">HttpRequestMessage</span> <span class="p">{</span> <span class="n">Content</span> <span class="p">=</span> <span class="n">content</span> <span class="p">};</span>

<span class="k">await</span> <span class="n">client</span><span class="p">.</span><span class="nf">SendAsync</span><span class="p">(</span><span class="n">request</span><span class="p">);</span>
<span class="n">ArrayPool</span><span class="p">&lt;</span><span class="kt">byte</span><span class="p">&gt;.</span><span class="n">Shared</span><span class="p">.</span><span class="nf">Return</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span>
</code></pre></div></div>

<p>Let’s measure the performance difference between creating <code class="language-plaintext highlighter-rouge">StringContent</code> and <code class="language-plaintext highlighter-rouge">ByteArrayContent</code>:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">static</span> <span class="k">readonly</span> <span class="n">Line3D</span> <span class="n">_line</span> <span class="p">=</span> <span class="k">new</span><span class="p">(</span>
    <span class="k">new</span><span class="p">(</span><span class="m">1.23</span><span class="p">,</span> <span class="m">2.81</span><span class="p">,</span> <span class="m">3.56</span><span class="p">),</span>
    <span class="k">new</span><span class="p">(</span><span class="m">0.85</span><span class="p">,</span> <span class="m">1.44</span><span class="p">,</span> <span class="m">4.32</span><span class="p">)</span>
<span class="p">);</span>

<span class="p">[</span><span class="nf">Benchmark</span><span class="p">(</span><span class="n">Baseline</span> <span class="p">=</span> <span class="k">true</span><span class="p">)]</span>
<span class="k">public</span> <span class="n">StringContent</span> <span class="nf">StringContent</span><span class="p">()</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="k">new</span> <span class="nf">StringContent</span><span class="p">(</span><span class="s">$"</span><span class="p">{</span><span class="n">_line</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span>

<span class="p">[</span><span class="n">Benchmark</span><span class="p">]</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">ByteArrayContent</span><span class="p">()</span>
<span class="p">{</span>
    <span class="kt">var</span> <span class="n">buffer</span> <span class="p">=</span> <span class="n">ArrayPool</span><span class="p">&lt;</span><span class="kt">byte</span><span class="p">&gt;.</span><span class="n">Shared</span><span class="p">.</span><span class="nf">Rent</span><span class="p">(</span><span class="m">1024</span><span class="p">);</span>
    <span class="n">Utf8</span><span class="p">.</span><span class="nf">TryWrite</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="s">$"</span><span class="p">{</span><span class="n">_line</span><span class="p">}</span><span class="s">"</span><span class="p">,</span> <span class="k">out</span> <span class="kt">int</span> <span class="n">bytesWritten</span><span class="p">);</span>

    <span class="n">_</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ByteArrayContent</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="n">bytesWritten</span><span class="p">);</span>
    <span class="n">ArrayPool</span><span class="p">&lt;</span><span class="kt">byte</span><span class="p">&gt;.</span><span class="n">Shared</span><span class="p">.</span><span class="nf">Return</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>Method</th>
      <th style="text-align: right">Mean</th>
      <th style="text-align: right">Error</th>
      <th style="text-align: right">StdDev</th>
      <th style="text-align: right">Ratio</th>
      <th style="text-align: right">Allocated</th>
      <th style="text-align: right">Alloc Ratio</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>StringContent</td>
      <td style="text-align: right">536.5 ns</td>
      <td style="text-align: right">5.49 ns</td>
      <td style="text-align: right">5.13 ns</td>
      <td style="text-align: right">1.00</td>
      <td style="text-align: right">384 B</td>
      <td style="text-align: right">1.00</td>
    </tr>
    <tr>
      <td>ByteArrayContent</td>
      <td style="text-align: right">428.1 ns</td>
      <td style="text-align: right">2.47 ns</td>
      <td style="text-align: right">2.19 ns</td>
      <td style="text-align: right">0.80</td>
      <td style="text-align: right">64 B</td>
      <td style="text-align: right">0.17</td>
    </tr>
  </tbody>
</table>

<p>The difference is huge—UTF-8 string formatting allocates 83% less memory! And we didn’t even
measure the best-case scenario, since our example used very short strings. The longer the
strings, the greater the improvements.</p>

<h2 id="the-final-implementation">The final implementation</h2>

<p>Here is the full implementation of <code class="language-plaintext highlighter-rouge">Point3D</code> for reference.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="n">record</span> <span class="nf">Point3D</span><span class="p">(</span><span class="kt">double</span> <span class="n">X</span><span class="p">,</span> <span class="kt">double</span> <span class="n">Y</span><span class="p">,</span> <span class="kt">double</span> <span class="n">Z</span><span class="p">)</span>
    <span class="p">:</span> <span class="n">ISpanFormattable</span><span class="p">,</span> <span class="n">IUtf8SpanFormattable</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">override</span> <span class="kt">string</span> <span class="nf">ToString</span><span class="p">()</span> <span class="p">=&gt;</span> <span class="s">$"</span><span class="p">{</span><span class="k">this</span><span class="p">}</span><span class="s">"</span><span class="p">;</span>

    <span class="k">public</span> <span class="kt">string</span> <span class="nf">ToString</span><span class="p">(</span><span class="kt">string</span> <span class="n">format</span><span class="p">,</span> <span class="n">IFormatProvider</span> <span class="n">provider</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nf">ToString</span><span class="p">();</span>

    <span class="k">public</span> <span class="kt">bool</span> <span class="nf">TryFormat</span><span class="p">(</span>
        <span class="n">Span</span><span class="p">&lt;</span><span class="kt">char</span><span class="p">&gt;</span> <span class="n">destination</span><span class="p">,</span>
        <span class="k">out</span> <span class="kt">int</span> <span class="n">charsWritten</span><span class="p">,</span>
        <span class="n">ReadOnlySpan</span><span class="p">&lt;</span><span class="kt">char</span><span class="p">&gt;</span> <span class="n">format</span><span class="p">,</span>
        <span class="n">IFormatProvider</span> <span class="n">provider</span><span class="p">)</span> <span class="p">=&gt;</span>
            <span class="n">destination</span><span class="p">.</span><span class="nf">TryWrite</span><span class="p">(</span><span class="n">provider</span><span class="p">,</span> <span class="s">$"(</span><span class="p">{</span><span class="n">X</span><span class="p">}</span><span class="s">, </span><span class="p">{</span><span class="n">Y</span><span class="p">}</span><span class="s">, </span><span class="p">{</span><span class="n">Z</span><span class="p">}</span><span class="s">)"</span><span class="p">,</span> <span class="k">out</span> <span class="n">charsWritten</span><span class="p">);</span>

    <span class="k">public</span> <span class="kt">bool</span> <span class="nf">TryFormat</span><span class="p">(</span>
        <span class="n">Span</span><span class="p">&lt;</span><span class="kt">byte</span><span class="p">&gt;</span> <span class="n">destination</span><span class="p">,</span>
        <span class="k">out</span> <span class="kt">int</span> <span class="n">bytesWritten</span><span class="p">,</span>
        <span class="n">ReadOnlySpan</span><span class="p">&lt;</span><span class="kt">char</span><span class="p">&gt;</span> <span class="n">format</span><span class="p">,</span>
        <span class="n">IFormatProvider</span> <span class="n">provider</span><span class="p">)</span> <span class="p">=&gt;</span>
            <span class="n">Utf8</span><span class="p">.</span><span class="nf">TryWrite</span><span class="p">(</span><span class="n">destination</span><span class="p">,</span> <span class="n">provider</span><span class="p">,</span> <span class="s">$"(</span><span class="p">{</span><span class="n">X</span><span class="p">}</span><span class="s">, </span><span class="p">{</span><span class="n">Y</span><span class="p">}</span><span class="s">, </span><span class="p">{</span><span class="n">Z</span><span class="p">}</span><span class="s">)"</span><span class="p">,</span> <span class="k">out</span> <span class="n">bytesWritten</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="conclusion">Conclusion</h2>

<p>Modern .NET is really amazing. Whenever I think I don’t need any further improvements in the
language or runtime, the .NET team surprises me with some new and useful high-performance
features. The fact that you can format strings with zero allocations using familiar syntax
is still mind-blowing to me! I can’t wait to see how .NET 10 will delight us all.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[One of the topics I covered in my previous post on memory optimizations was string formatting. As I was writing that post, I uncovered an embarrassing gap in my knowledge!]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://mijailovic.net/assets/img/2025-05-14-high-performance-strings-preview.png" /><media:content medium="image" url="https://mijailovic.net/assets/img/2025-05-14-high-performance-strings-preview.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Optimizing memory usage with modern .NET features</title><link href="https://mijailovic.net/2025/04/10/memory-optimizations/" rel="alternate" type="text/html" title="Optimizing memory usage with modern .NET features" /><published>2025-04-10T08:00:00+00:00</published><updated>2025-04-10T08:00:00+00:00</updated><id>https://mijailovic.net/2025/04/10/memory-optimizations</id><content type="html" xml:base="https://mijailovic.net/2025/04/10/memory-optimizations/"><![CDATA[<p>After <a href="https://devblogs.microsoft.com/dotnet/modernizing-push-notification-api-for-teams/">my service migrated</a>
from .NET Framework to .NET 8 (and later to .NET 9), it felt like a whole new world had opened to me. All
the modern .NET features that I had only been reading about on the <a href="https://devblogs.microsoft.com/dotnet/">.NET Blog</a>
were finally available to me. Armed with Microsoft’s continuous, fleet-wide performance profiler, I embarked on a journey
to find the places where my service was allocating the most memory and fix the unnecessary allocations by using the newly
available .NET APIs. In this blog post, I’ll show you some common code patterns that can be found in .NET Framework code,
along with their modern, high-performance alternatives.</p>

<p>It’s worth noting that before you start making improvements to your code,
you should make sure you are addressing a real performance problem.
Low-level optimizations can be incredibly addictive, and you could end up spending a lot of time on them without seeing
visible results. Every example I’m about to show you comes from a real-world memory allocation issue discovered using a
profiler, so you should conduct your own performance analysis as well (<a href="https://github.com/microsoft/perfview">PerfView</a>
is a fantastic tool for that). Now let’s dive in!</p>

<h2 id="string-formatting">String formatting</h2>

<p>If your codebase has been around long enough, it likely contains many different ways of formatting strings:
concatenation using the plus operator, <code class="language-plaintext highlighter-rouge">StringBuilder</code> usages, and calls to <code class="language-plaintext highlighter-rouge">string.Join</code>, <code class="language-plaintext highlighter-rouge">string.Concat</code>,
and <code class="language-plaintext highlighter-rouge">string.Format</code> methods. Even though most of these are
<a href="https://learn.microsoft.com/en-us/dotnet/csharp/how-to/concatenate-multiple-strings">usually fine</a>, you can still
end up allocating much more memory than is really needed.</p>

<p>In almost all cases, <a href="https://learn.microsoft.com/en-us/dotnet/csharp/tutorials/string-interpolation">string interpolation</a> should
be your preferred method for formatting strings. It’s superior to other approaches in both speed and memory usage (if you want to
learn why, check out this <a href="https://devblogs.microsoft.com/dotnet/string-interpolation-in-c-10-and-net-6/">great post</a> written
by Stephen Toub). Not only that, but it also looks much nicer than building strings manually—especially when working with
interpolated multi-line strings. Thanks to the recently added
<a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/raw-string">raw string literals</a>,
it’s never been easier to create complex templated strings like this one:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">string</span> <span class="n">s</span> <span class="p">=</span> <span class="s">$"""
</span>    <span class="n">Fancy</span> <span class="n">report</span><span class="p">,</span> <span class="p">({</span><span class="n">DateTime</span><span class="p">.</span><span class="n">Now</span><span class="p">})</span>
    <span class="n">Line</span> <span class="m">1</span><span class="p">:</span> <span class="p">{</span><span class="n">Math</span><span class="p">.</span><span class="nf">Pow</span><span class="p">(</span><span class="m">2</span><span class="p">,</span> <span class="m">16</span><span class="p">)}.</span>
    <span class="n">Line</span> <span class="m">2</span><span class="p">:</span> <span class="p">{</span><span class="k">new</span> <span class="kt">string</span><span class="p">(</span><span class="sc">'X'</span><span class="p">,</span> <span class="m">5</span><span class="p">)}.</span>
    <span class="n">Line</span> <span class="m">3</span><span class="p">:</span> <span class="p">{</span><span class="n">RandomNumberGenerator</span><span class="p">.</span><span class="nf">GetHexString</span><span class="p">(</span><span class="m">10</span><span class="p">)}.</span>
    <span class="s">""";
</span></code></pre></div></div>

<h2 id="collection-capacity">Collection capacity</h2>

<p>Collections like <code class="language-plaintext highlighter-rouge">List&lt;T&gt;</code> and <code class="language-plaintext highlighter-rouge">Dictionary&lt;TKey, TValue&gt;</code> don’t grow
magically—their implementations use a fixed-size array behind the scenes.
When that fixed-size array runs out of space for additional elements,
a new, larger array is allocated and existing elements are copied to it.
Most of the time, the compiler and the runtime handle this optimally, but
there is one surprising case where you need to assist them.</p>

<p>If you use a good ol’ collection initializer, you might assume the compiler will statically determine the
initial collection capacity. It makes perfect sense, but it’s also wrong. Take a look at this benchmark:
in the first case, we initialize the dictionary without specifying the capacity; in the second, we specify
the exact capacity we need:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">Benchmark</span><span class="p">]</span>
<span class="k">public</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">&gt;</span> <span class="nf">DefaultCapacity</span><span class="p">()</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="k">new</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">&gt;</span>
    <span class="p">{</span>
        <span class="p">[</span><span class="s">"1"</span><span class="p">]</span> <span class="p">=</span> <span class="s">"1"</span><span class="p">,</span>
        <span class="p">[</span><span class="s">"2"</span><span class="p">]</span> <span class="p">=</span> <span class="s">"2"</span><span class="p">,</span>
        <span class="p">[</span><span class="s">"3"</span><span class="p">]</span> <span class="p">=</span> <span class="s">"3"</span><span class="p">,</span>
        <span class="p">[</span><span class="s">"4"</span><span class="p">]</span> <span class="p">=</span> <span class="s">"4"</span><span class="p">,</span>
        <span class="p">[</span><span class="s">"5"</span><span class="p">]</span> <span class="p">=</span> <span class="s">"5"</span><span class="p">,</span>
        <span class="p">[</span><span class="s">"6"</span><span class="p">]</span> <span class="p">=</span> <span class="s">"6"</span><span class="p">,</span>
        <span class="p">[</span><span class="s">"7"</span><span class="p">]</span> <span class="p">=</span> <span class="s">"7"</span><span class="p">,</span>
        <span class="p">[</span><span class="s">"8"</span><span class="p">]</span> <span class="p">=</span> <span class="s">"8"</span><span class="p">,</span>
    <span class="p">};</span>
<span class="p">}</span>

<span class="p">[</span><span class="n">Benchmark</span><span class="p">]</span>
<span class="k">public</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">&gt;</span> <span class="nf">ExactCapacity</span><span class="p">()</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="k">new</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">&gt;(</span><span class="m">8</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="p">[</span><span class="s">"1"</span><span class="p">]</span> <span class="p">=</span> <span class="s">"1"</span><span class="p">,</span>
        <span class="p">[</span><span class="s">"2"</span><span class="p">]</span> <span class="p">=</span> <span class="s">"2"</span><span class="p">,</span>
        <span class="p">[</span><span class="s">"3"</span><span class="p">]</span> <span class="p">=</span> <span class="s">"3"</span><span class="p">,</span>
        <span class="p">[</span><span class="s">"4"</span><span class="p">]</span> <span class="p">=</span> <span class="s">"4"</span><span class="p">,</span>
        <span class="p">[</span><span class="s">"5"</span><span class="p">]</span> <span class="p">=</span> <span class="s">"5"</span><span class="p">,</span>
        <span class="p">[</span><span class="s">"6"</span><span class="p">]</span> <span class="p">=</span> <span class="s">"6"</span><span class="p">,</span>
        <span class="p">[</span><span class="s">"7"</span><span class="p">]</span> <span class="p">=</span> <span class="s">"7"</span><span class="p">,</span>
        <span class="p">[</span><span class="s">"8"</span><span class="p">]</span> <span class="p">=</span> <span class="s">"8"</span><span class="p">,</span>
    <span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>Method</th>
      <th style="text-align: right">Mean</th>
      <th style="text-align: right">Error</th>
      <th style="text-align: right">StdDev</th>
      <th style="text-align: right">Gen0</th>
      <th style="text-align: right">Allocated</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>DefaultCapacity</td>
      <td style="text-align: right">113.00 ns</td>
      <td style="text-align: right">0.376 ns</td>
      <td style="text-align: right">0.333 ns</td>
      <td style="text-align: right">0.1185</td>
      <td style="text-align: right">992 B</td>
    </tr>
    <tr>
      <td>ExactCapacity</td>
      <td style="text-align: right">65.57 ns</td>
      <td style="text-align: right">0.623 ns</td>
      <td style="text-align: right">0.521 ns</td>
      <td style="text-align: right">0.0526</td>
      <td style="text-align: right">440 B</td>
    </tr>
  </tbody>
</table>

<p>Why is there such a huge difference in both CPU and memory usage? When you use a collection initializer,
the default constructor gets called, initializing the collection with zero capacity. Elements are then
added to the collection one by one, triggering the internal resizing algorithm as needed. Here’s a simple
program to demonstrate how this works:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">d</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="kt">int</span><span class="p">&gt;();</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">$"Capacity: </span><span class="p">{</span><span class="n">d</span><span class="p">.</span><span class="n">Capacity</span><span class="p">,</span><span class="m">2</span><span class="p">}</span><span class="s">, Count: </span><span class="p">{</span><span class="n">d</span><span class="p">.</span><span class="n">Count</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>

<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="m">8</span><span class="p">;</span> <span class="p">++</span><span class="n">i</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">d</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">i</span><span class="p">);</span>
    <span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">$"Capacity: </span><span class="p">{</span><span class="n">d</span><span class="p">.</span><span class="n">Capacity</span><span class="p">,</span><span class="m">2</span><span class="p">}</span><span class="s">, Count: </span><span class="p">{</span><span class="n">d</span><span class="p">.</span><span class="n">Count</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This program prints the following results:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Capacity:  0, Count: 0
Capacity:  3, Count: 1
Capacity:  3, Count: 2
Capacity:  3, Count: 3
Capacity:  7, Count: 4
Capacity:  7, Count: 5
Capacity:  7, Count: 6
Capacity:  7, Count: 7
Capacity: 17, Count: 8
</code></pre></div></div>

<p>You can see that we unnecessarily allocated arrays of size 3 and 7, and that the final array is way
too large for a collection of 8 elements. As you already know from previous benchmark results, you
can avoid all this throwaway work by specifying the collection size in advance.</p>

<p>Is manually counting the number of elements in collection initializers really the best we can do?
For dictionaries, yes. For lists, there is a better way.
<a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/collection-expressions">Collection expressions</a>
are not only a cosmetic feature, but also the most performant way of initializing collections. Unlike
collection initializers, collection expressions set the exact capacity and are also much faster:</p>

<table>
  <thead>
    <tr>
      <th>Method</th>
      <th style="text-align: right">Mean</th>
      <th style="text-align: right">Error</th>
      <th style="text-align: right">StdDev</th>
      <th style="text-align: right">Gen0</th>
      <th style="text-align: right">Allocated</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>InitializerDefaultCapacity</td>
      <td style="text-align: right">57.46 ns</td>
      <td style="text-align: right">0.244 ns</td>
      <td style="text-align: right">0.228 ns</td>
      <td style="text-align: right">0.0440</td>
      <td style="text-align: right">368 B</td>
    </tr>
    <tr>
      <td>InitializerExactCapacity</td>
      <td style="text-align: right">26.59 ns</td>
      <td style="text-align: right">0.109 ns</td>
      <td style="text-align: right">0.102 ns</td>
      <td style="text-align: right">0.0162</td>
      <td style="text-align: right">136 B</td>
    </tr>
    <tr>
      <td>CollectionExpression</td>
      <td style="text-align: right">10.69 ns</td>
      <td style="text-align: right">0.012 ns</td>
      <td style="text-align: right">0.012 ns</td>
      <td style="text-align: right">0.0163</td>
      <td style="text-align: right">136 B</td>
    </tr>
  </tbody>
</table>

<p>Of course, this doesn’t mean that you should immediately update your entire codebase to use collection
expressions for all lists and set the initial capacity for all dictionaries.
What it means is that
if you use a lot of fixed-size collections in your hot code path and see a significant portion
of your allocations coming from <code class="language-plaintext highlighter-rouge">Resize</code> calls, there is an easy fix for that problem.</p>

<h2 id="stack-allocated-memory">Stack-allocated memory</h2>

<p>There is one trick you won’t be able to use often, but if the right conditions apply, it can lead to a useful performance
optimization. <a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/stackalloc">Stackalloc</a> allows
you to allocate a small block of memory on the stack and bypass garbage collection completely (the entire stack frame is
discarded after you exit the function). One example of its usage is computing cryptographic hashes. Their output size
is small and well-known, which means you can calculate them like this:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Span</span><span class="p">&lt;</span><span class="kt">byte</span><span class="p">&gt;</span> <span class="n">hash</span> <span class="p">=</span> <span class="k">stackalloc</span> <span class="kt">byte</span><span class="p">[</span><span class="n">SHA256</span><span class="p">.</span><span class="n">HashSizeInBytes</span><span class="p">];</span>
<span class="n">SHA256</span><span class="p">.</span><span class="nf">HashData</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">hash</span><span class="p">);</span>
</code></pre></div></div>

<p>Of course, this isn’t the only use case—you can use <code class="language-plaintext highlighter-rouge">stackalloc</code> any time you need a small, temporary
buffer. But what does small even mean in this context? On Windows, it means less than 1MB, which is
the default stack size (though I’m not sure how up to date this information is). .NET itself uses a
<a href="https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs">512-byte</a>
limit internally:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">internal</span> <span class="k">const</span> <span class="kt">int</span> <span class="n">StackallocIntBufferSizeLimit</span> <span class="p">=</span> <span class="m">128</span><span class="p">;</span>
<span class="k">internal</span> <span class="k">const</span> <span class="kt">int</span> <span class="n">StackallocCharBufferSizeLimit</span> <span class="p">=</span> <span class="m">256</span><span class="p">;</span>
</code></pre></div></div>

<p>The examples from the
<a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/stackalloc">documentation</a>
set the limit somewhat higher, at 1,024 bytes, so both 512 bytes and 1,024 bytes should be perfectly safe.</p>

<h2 id="case-insensitive-hashing">Case-insensitive hashing</h2>

<p>Nowadays you probably know that you should avoid using <code class="language-plaintext highlighter-rouge">ToLower</code> or <code class="language-plaintext highlighter-rouge">ToUpper</code>
for case-insensitive string comparison, because these methods perform unnecessary
allocations. A better approach is to use the <code class="language-plaintext highlighter-rouge">StringComparison</code> overload, and an
<a href="https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1862">analyzer</a>
will remind you to do this. While this is all fairly obvious, what about case-insensitive hashing?
Even though the analyzer and the corresponding docs don’t mention it, hash code calculation can
also be case-insensitive! If you are doing this:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">hashCode</span> <span class="p">=</span> <span class="n">s</span><span class="p">.</span><span class="nf">ToUpper</span><span class="p">().</span><span class="nf">GetHashCode</span><span class="p">();</span>
</code></pre></div></div>

<p>You can do this instead:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">hashCode</span> <span class="p">=</span> <span class="n">s</span><span class="p">.</span><span class="nf">GetHashCode</span><span class="p">(</span><span class="n">StringComparison</span><span class="p">.</span><span class="n">OrdinalIgnoreCase</span><span class="p">);</span>
</code></pre></div></div>

<p>What about calculating the combined hash code of multiple objects? While you can’t directly use
<code class="language-plaintext highlighter-rouge">HashCode.Combine</code> in this specific scenario, you can still create an instance of the <code class="language-plaintext highlighter-rouge">HashCode</code>
struct and add a string to it using the <code class="language-plaintext highlighter-rouge">StringComparer</code> overload. Pretty neat!</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">HashCode</span> <span class="n">hashCode</span> <span class="p">=</span> <span class="k">new</span><span class="p">();</span>
<span class="n">hashCode</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">StringComparer</span><span class="p">.</span><span class="n">OrdinalIgnoreCase</span><span class="p">);</span>
</code></pre></div></div>

<h2 id="hex-conversion">Hex conversion</h2>

<p>For almost two decades, there was no good way to convert byte arrays to hexadecimal strings in .NET
(and no, <a href="https://learn.microsoft.com/en-us/dotnet/api/system.runtime.remoting.metadata.w3cxsd2001.soaphexbinary">System.Runtime.Remoting.Metadata.W3cXsd2001.SoapHexBinary</a> doesn’t count).
Apart from rolling your own implementation (there are a million different ones all over the internet),
you had two available options: one bad and one terrible.</p>

<p>The bad one was <code class="language-plaintext highlighter-rouge">BitConverter.ToString</code>. For some reason, it was designed to generate a string
in which all hexadecimal pairs were separated by hyphens, so everyone had to use it like this:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">upper</span> <span class="p">=</span> <span class="n">BitConverter</span><span class="p">.</span><span class="nf">ToString</span><span class="p">(</span><span class="n">bytes</span><span class="p">).</span><span class="nf">Replace</span><span class="p">(</span><span class="s">"-"</span><span class="p">,</span> <span class="s">""</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">lower</span> <span class="p">=</span> <span class="n">BitConverter</span><span class="p">.</span><span class="nf">ToString</span><span class="p">(</span><span class="n">bytes</span><span class="p">).</span><span class="nf">Replace</span><span class="p">(</span><span class="s">"-"</span><span class="p">,</span> <span class="s">""</span><span class="p">).</span><span class="nf">ToLower</span><span class="p">();</span>
</code></pre></div></div>

<p>If that’s the bad option, what’s the terrible one? Warning: graphic content ahead.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">string</span><span class="p">.</span><span class="nf">Join</span><span class="p">(</span><span class="s">""</span><span class="p">,</span> <span class="n">bytes</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">b</span> <span class="p">=&gt;</span> <span class="n">b</span><span class="p">.</span><span class="nf">ToString</span><span class="p">(</span><span class="s">"x2"</span><span class="p">)));</span>
</code></pre></div></div>

<p>Recent versions of .NET finally offer proper methods for hex conversion: <code class="language-plaintext highlighter-rouge">Convert.ToHex</code>
and <code class="language-plaintext highlighter-rouge">Convert.ToHexStringLower</code>. There is even an
<a href="https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1872">analyzer</a>
that will warn you if you are using the <code class="language-plaintext highlighter-rouge">BitConverter</code> pattern I described earlier, making it easy
to switch to the modern approach.</p>

<h2 id="httpcontent-json-deserialization">HttpContent JSON deserialization</h2>

<p>You can easily shoot yourself in the foot when deserializing HTTP responses in
JSON format. For example, let’s say you decide
to use <code class="language-plaintext highlighter-rouge">Json.NET</code>’s <code class="language-plaintext highlighter-rouge">JsonConvert.DeserializeObject</code>, the most well-known
method for parsing JSON data. Since that method only works with strings, you
need to read the HTTP response as a string, too. But if the HTTP response is
sufficiently large, your string will end up on the large object heap (yuck).</p>

<p>One way to avoid this problem is to use a complicated combination of
<code class="language-plaintext highlighter-rouge">StreamReader</code>, <code class="language-plaintext highlighter-rouge">JsonTextReader</code>, and <code class="language-plaintext highlighter-rouge">JsonSerializer</code> (documented
<a href="https://www.newtonsoft.com/json/help/html/performance.htm#MemoryUsage">here</a>),
but a better solution is to simply use <code class="language-plaintext highlighter-rouge">System.Text.Json</code>. It’s faster,
allocates less memory, and is easy to use. You can send the HTTP request,
receive the response, and deserialize the JSON content in one line of code.
That line also happens to be optimal in terms of performance!</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">var</span> <span class="n">client</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">HttpClient</span><span class="p">();</span>
<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="n">client</span><span class="p">.</span><span class="n">GetFromJsonAsync</span><span class="p">&lt;</span><span class="n">Data</span><span class="p">&gt;(</span><span class="n">url</span><span class="p">);</span>
</code></pre></div></div>

<p>And if you throw in some compile-time
<a href="https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/source-generation">source code generation</a>,
you get reflection-free deserialization code, ready for
<a href="https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/">Native AOT</a> scenarios:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">JsonSerializable</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">Data</span><span class="p">))]</span>
<span class="k">public</span> <span class="k">partial</span> <span class="k">class</span> <span class="nc">DataContext</span> <span class="p">:</span> <span class="n">JsonSerializerContext</span> <span class="p">{</span> <span class="p">}</span>

<span class="k">using</span> <span class="nn">var</span> <span class="n">client</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">HttpClient</span><span class="p">();</span>
<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="n">client</span><span class="p">.</span><span class="nf">GetFromJsonAsync</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">DataContext</span><span class="p">.</span><span class="n">Default</span><span class="p">.</span><span class="n">Data</span><span class="p">);</span>
</code></pre></div></div>

<p>Here are some performance numbers showing the difference between <code class="language-plaintext highlighter-rouge">Json.NET</code>, <code class="language-plaintext highlighter-rouge">System.Text.Json</code>,
and source-generated <code class="language-plaintext highlighter-rouge">System.Text.Json</code>. The benchmark is measuring the time to make an HTTP
request to a TCP socket listening on <code class="language-plaintext highlighter-rouge">localhost</code> and then parse the response.</p>

<table>
  <thead>
    <tr>
      <th>Method</th>
      <th style="text-align: right">Mean</th>
      <th style="text-align: right">Error</th>
      <th style="text-align: right">StdDev</th>
      <th style="text-align: right">Gen0</th>
      <th style="text-align: right">Allocated</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>NewtonsoftJson</td>
      <td style="text-align: right">78.85 us</td>
      <td style="text-align: right">0.673 us</td>
      <td style="text-align: right">0.629 us</td>
      <td style="text-align: right">0.4883</td>
      <td style="text-align: right">12.02 KB</td>
    </tr>
    <tr>
      <td>SystemTextJson</td>
      <td style="text-align: right">69.06 us</td>
      <td style="text-align: right">1.348 us</td>
      <td style="text-align: right">1.324 us</td>
      <td style="text-align: right">0.2441</td>
      <td style="text-align: right">6.26 KB</td>
    </tr>
    <tr>
      <td>SourceGeneration</td>
      <td style="text-align: right">67.65 us</td>
      <td style="text-align: right">1.085 us</td>
      <td style="text-align: right">1.015 us</td>
      <td style="text-align: right">0.2441</td>
      <td style="text-align: right">6.26 KB</td>
    </tr>
  </tbody>
</table>

<p><code class="language-plaintext highlighter-rouge">System.Text.Json</code> is a winner, but the difference is not as dramatic as you might expect.
<code class="language-plaintext highlighter-rouge">Json.NET</code> is still a fine option—it’s a well-optimized, mature library. If you are happy
with it, feel free to keep using it, just make sure you are using it correctly.</p>

<h2 id="memory-allocations-in-unexpected-places">Memory allocations in unexpected places</h2>

<p>There are certain places where memory allocations definitely shouldn’t be happening.
Here’s an example from real-world code (the name of the class has been changed to protect
its real identity):</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">MemoryEater</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">Value</span> <span class="p">=&gt;</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Concat</span><span class="p">(</span><span class="n">Base</span><span class="p">,</span> <span class="s">"."</span><span class="p">,</span> <span class="n">Extension</span><span class="p">);</span>
    <span class="k">public</span> <span class="kt">bool</span> <span class="nf">Equals</span><span class="p">(</span><span class="n">MemoryEater</span> <span class="n">other</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Equals</span><span class="p">(</span><span class="n">Value</span><span class="p">,</span> <span class="n">other</span><span class="p">.</span><span class="n">Value</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>You might notice a couple of issues here. The first one is that the property getter is allocating memory.
While I’m not aware of any official guidelines on avoiding allocations in getters, the callers usually
expect properties to behave as fields in disguise. That means it’s common to see a property used in the
following way:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(!</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrEmpty</span><span class="p">(</span><span class="n">instance</span><span class="p">.</span><span class="n">Value</span><span class="p">))</span>
<span class="p">{</span>
    <span class="nf">DoSomething</span><span class="p">(</span><span class="n">instance</span><span class="p">.</span><span class="n">Value</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>In our case, this will waste both CPU and memory, and if you don’t know how the property getter is
implemented, you might not even realize there’s potentially a hidden performance issue.</p>

<p>The second issue might be even worse: memory is being allocated in the <code class="language-plaintext highlighter-rouge">Equals</code> method. The number
of people who would be happy to get an <code class="language-plaintext highlighter-rouge">OutOfMemoryException</code> while comparing two objects
for equality is, you guessed it—exactly zero.</p>

<p>Fixing both problems is straightforward. Avoid complex properties by either pre-calculating
their values or converting them to methods (to signal to the callers that they are doing
some non-trivial amount of work). And definitely don’t allocate memory in <code class="language-plaintext highlighter-rouge">Equals</code>. In the
case of the <code class="language-plaintext highlighter-rouge">MemoryEater</code> class, you can easily avoid the allocations by comparing the
individual components separately:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="kt">bool</span> <span class="nf">Equals</span><span class="p">(</span><span class="n">MemoryEater</span> <span class="n">other</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="n">Base</span> <span class="p">==</span> <span class="n">other</span><span class="p">.</span><span class="n">Base</span> <span class="p">&amp;&amp;</span> <span class="n">Extension</span> <span class="p">==</span> <span class="n">other</span><span class="p">.</span><span class="n">Extension</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="closing-thoughts">Closing thoughts</h2>

<p>It’s been a blast being a .NET developer ever since .NET Core was released, especially
when you are into writing high-performance code. The .NET team’s dedication to performance
is impressive, and .NET is getting faster and more fun to use with every new release. While
we are waiting for .NET 10, you can’t go wrong with reading Stephen Toub’s previous annual
<a href="https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-9/">Performance Improvements in .NET</a>
posts. Whether you want to learn more about performance optimization techniques, or just discover new
and incredibly fast .NET APIs, these posts are a gold mine of information, and I can’t recommend them enough.</p>

<p><small><i>Endless thanks to my wife and editor Milica Miljkov, who somehow
always has the unwavering enthusiasm to sit with me for hours and edit
paragraphs destined to be read by maybe five people.</i></small></p>]]></content><author><name></name></author><summary type="html"><![CDATA[After my service migrated from .NET Framework to .NET 8 (and later to .NET 9), it felt like a whole new world had opened to me. All the modern .NET features that I had only been reading about on the .NET Blog were finally available to me. Armed with Microsoft’s continuous, fleet-wide performance profiler, I embarked on a journey to find the places where my service was allocating the most memory and fix the unnecessary allocations by using the newly available .NET APIs. In this blog post, I’ll show you some common code patterns that can be found in .NET Framework code, along with their modern, high-performance alternatives.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://mijailovic.net/assets/img/2025-04-10-memory-optimizations-preview.png" /><media:content medium="image" url="https://mijailovic.net/assets/img/2025-04-10-memory-optimizations-preview.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Reverse engineering Bandcamp authentication protocol</title><link href="https://mijailovic.net/2024/04/04/bandcamp-auth/" rel="alternate" type="text/html" title="Reverse engineering Bandcamp authentication protocol" /><published>2024-04-04T12:15:00+00:00</published><updated>2024-04-04T12:15:00+00:00</updated><id>https://mijailovic.net/2024/04/04/bandcamp-auth</id><content type="html" xml:base="https://mijailovic.net/2024/04/04/bandcamp-auth/"><![CDATA[<p>Did you know that the albums you purchase on Bandcamp can disappear from your collection
without notice? This can happen for various reasons. For example, a seller might
decide on a whim to remove the album from the platform. Bandcamp apparently
allows this in their <a href="https://bandcamp.com/terms_of_use">terms of use</a>:</p>

<blockquote>
  <p>Content you purchase in a Transaction cannot be guaranteed to
be available to you perpetually.</p>
</blockquote>

<blockquote>
  <p>Users bear all risk from the denial of access to any Content
purchased through the Service.</p>
</blockquote>

<p>The only way to make sure your albums stay in your possession is to download them
immediately after purchase. Heck, even Bandcamp officially recommends this:</p>

<blockquote>
  <p>[…] we encourage you to promptly download any Content you purchase through the Site […]</p>
</blockquote>

<p>However, even if the album has been removed, and you hadn’t dowloaded it, not all is lost.
In the Bandcamp mobile app, you can continue to listen to all your albums (but without the
option to download them), even after they’ve been removed from the platform. This obviously
means that Bandcamp doesn’t delete the actual content from their servers. And if the app can
still access the lost albums, so can everyone who is patient enough to reverse engineer the app.
Surprisingly, no one has done this by now. Could it be that it’s impossible? Let’s dive in and
see what’s going on inside the Bandcamp app!</p>

<h2 id="inspecting-the-network-traffic">Inspecting the network traffic</h2>

<p>As always, my first step was to inspect the network traffic between the Bandcamp
app and their backend servers. My favorite tool for this purpose has always been
<a href="https://portswigger.net/burp/communitydownload">Burp Suite Community Edition</a>.
After setting up the proxy and opening the collection page in the app, I quickly
noticed the following HTTP request in proxy logs:</p>

<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">GET</span> <span class="nn">/api/collectionsync/1/collection?page_size=200</span> <span class="k">HTTP</span><span class="o">/</span><span class="m">2</span>
<span class="na">Host</span><span class="p">:</span> <span class="s">bandcamp.com</span>
<span class="na">Authorization</span><span class="p">:</span> <span class="s">Bearer MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3</span>
</code></pre></div></div>

<p>This API endpoint returns the information about all your albums (things like album
name, band info, release date, purchase date, etc.). Not only that, but it also lists
all the album tracks, together with something that looks like high-quality audio URLs:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"token"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1:1700775127:355751800:a"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"tralbum_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">355751800</span><span class="p">,</span><span class="w">
  </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Pinnacle Of Bedlam"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"tracks"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"track_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">3770803404</span><span class="p">,</span><span class="w">
      </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Cycles Of Suffering"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"audio_url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://t4.bcbits.com/stream/b32687/mp3-128/3770803404"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"hq_audio_url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://t4.bcbits.com/stream/fc3538/mp3-v0/3770803404"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"track_number"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"track_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">2590214273</span><span class="p">,</span><span class="w">
      </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Purgatorical Punishment"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"audio_url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://t4.bcbits.com/stream/2b6cad/mp3-128/2590214273"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"hq_audio_url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://t4.bcbits.com/stream/a37aa9/mp3-v0/2590214273"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"track_number"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Unfortunately, <code class="language-plaintext highlighter-rouge">hq_audio_url</code> is a bit of a misnomer. High quality in this context
refers to MP3 V0, which is a lossy format (unlike regular Bandcamp downloads on the
web page, where you can choose from various selection of lossless formats). The good
news is that it’s very unlikely you can even hear the difference between lossless
formats and high bitrate lossy formats. In any case, it’s better to have your music
than not to have it at all, so I’ll happily take MP3 V0 over nothing any day. Anyway,
I tried one of the download links from the JSON response and it worked:</p>

<p><img src="/assets/img/2024-04-04-player.png" alt="" /></p>

<p>First obstacle had been conquered. It was an important milestone for me, because
at this moment I knew that even if I couldn’t figure out how to get the authentication
token programmatically, I would still be able to manually download the missing albums
from my collection: I could just save all HTTP responses and extract the audio URLs by
hand. It wouldn’t be the most exciting job ever, but it would do the trick. Ah, who am
I kidding? Of course I would not be happy with such half-assed solution. I obviously
had to automate this process, which meant I needed to figure out how to get the
authentication token.</p>

<h2 id="authentication-protocol">Authentication protocol</h2>

<p>Logins are typically very simple: you send a POST request with your username and
password, and you get an authentication token in return. Bandcamp’s login protocol
is much more convoluted. Here is the high level description of the login flow:</p>

<ol>
  <li>App sends the login request to <code class="language-plaintext highlighter-rouge">/oauth_login</code> endpoint.</li>
  <li>Server returns 418 status code and a hex-encoded, random-looking <code class="language-plaintext highlighter-rouge">X-Bandcamp-Dm</code> header.</li>
  <li>App resends the login request with its own <code class="language-plaintext highlighter-rouge">X-Bandcamp-Dm</code> value.</li>
  <li>Server returns 451 status code and introduces a new <code class="language-plaintext highlighter-rouge">X-Bandcamp-Pow</code> header.</li>
  <li>App sends the final login request with its own <code class="language-plaintext highlighter-rouge">X-Bandcamp-Pow</code> value.</li>
  <li>Server returns 200 status code and an authentication token.</li>
</ol>

<p><img src="/assets/img/2024-04-04-flowchart.png" alt="" /></p>

<p>Based on this entire exchange, it appeared that <code class="language-plaintext highlighter-rouge">X-Bandcamp-Dm</code> and <code class="language-plaintext highlighter-rouge">X-Bandcamp-Pow</code>
response headers served as some sort of challenge. Correct outgoing header values were
necessary for successful authentication, and were in some way dependent on the incoming values.</p>

<p>Figuring out how the correct header values are generated just by looking at network traffic
was clearly impossible; the answer to this question could only be found in the client
application code. On the off chance that someone else had already figured out
the algorithm, I did a quick Google and GitHub search for <code class="language-plaintext highlighter-rouge">X-Bandcamp-Dm</code>. I got a
couple of hits, but all of them were just documenting the struggles of other people:</p>

<blockquote>
  <p>ok how they handle the new DM is, basically put, a pain in the ass,
we’ll see if I get around figuring out wtf is happening
(<a href="https://github.com/the-eater/camp-collective/issues/2">MITM Android Bandcamp app</a>)</p>
</blockquote>

<blockquote>
  <p>I’d love to support that if someone wants to reverse-engineer
the X-Bandcamp-DM and X-Bandcamp-PoW headers.
(<a href="https://pypi.org/project/Mopidy-Bandcamp/1.0.1/">Mopidy-Bandcamp</a>)</p>
</blockquote>

<blockquote>
  <p>Unfortunately I cannot recreate x-bandcamp-dm value in headers.
(<a href="https://github.com/milicamilivojevic/bandcamp">Python Bandcamp scraper</a>)</p>
</blockquote>

<p>Apparently, <code class="language-plaintext highlighter-rouge">X-Bandcamp-Dm</code> and <code class="language-plaintext highlighter-rouge">X-Bandcamp-Pow</code> headers were the secret ingredient
that made it difficult to reverse engineer the login API. It was time to decompile the mobile
app and find the answer to how these secret values are generated.</p>

<h2 id="decompiling-the-android-app">Decompiling the Android app</h2>

<p>Since reversing managed code is much easier than reversing native code, I chose
to decompile the Android mobile app. I’ve always used
<a href="https://github.com/skylot/jadx">JADX</a> for this purpose and it has always served me well
(its <a href="https://github.com/skylot/jadx/wiki/jadx-gui-features-overview">GUI features</a>
would turn out to be particularly useful). After downloading the Bandcamp application
package from <a href="https://www.apkmirror.com/apk/bandcamp-inc/bandcamp/">APKMirror</a> and
opening it with JADX, I found out that the app was obfuscated:</p>

<p><img src="/assets/img/2024-04-04-jadx.png" alt="" /></p>

<p>I had never reverse engineered obfuscated code before. To determine how difficult the
process would be, I searched for all occurrences of string <code class="language-plaintext highlighter-rouge">X-Bandcamp-Dm</code>. Number of
results: zero. So, not only was the code obfuscated, but the string values were obfuscated
as well. That meant the job of figuring out how the mysterious header values were calculated
was not going to be easy. In fact, I wasn’t sure if it was going to be possible at all,
since I didn’t have any clue where to start. I had doubts whether I even wanted to
embark on this journey, but ultimately, I decided to do it, even if it takes me half a
year (luckily, I only needed three weeks).</p>

<h2 id="obfuscation-techniques">Obfuscation techniques</h2>

<p>The app was using many different obfuscation techniques, and since I was a complete
newbie in this area, I had to learn from scratch how to defeat each one. I’m going
to show you some of the techniques I’ve seen, along with the tips on how to fight
them. Reverse engineering veterans among you probably know all of them already, but
if you are a beginner, I hope that you’ll learn something new and see that obfuscation
is not as intimidating as it might appear.</p>

<h3 id="renaming">Renaming</h3>

<p>Renaming is an obfuscating method where identifiers (variable, class, field, and
method names) are renamed to random gibberish. This is probably the most well-known
type of obfuscation, so it’s not surprising that it’s frequently used in Bandcamp
mobile app. For example, a typical method call might look something like this:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">dVar</span><span class="o">.</span><span class="na">F</span><span class="o">(</span><span class="n">f17618q</span><span class="o">,</span> <span class="n">b</span><span class="o">(</span><span class="n">dVar</span><span class="o">.</span><span class="na">i</span><span class="o">()));</span>
</code></pre></div></div>

<p>When I first looked at this code, I had no idea what methods <code class="language-plaintext highlighter-rouge">F</code>, <code class="language-plaintext highlighter-rouge">b</code> and <code class="language-plaintext highlighter-rouge">i</code> were doing.
Luckily, JADX is almost a full-blown IDE, so it contains features such as “Find usage”
and “Go to declaration”. These two made the analysis much easier, because I was able to
traverse the call chains until I reached some method with a normal, unobfuscated
name, such as this one:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">setHeaders</span><span class="o">(</span><span class="n">s7</span><span class="o">.</span><span class="na">d</span> <span class="n">dVar</span><span class="o">)</span> <span class="o">{</span>
  <span class="k">this</span><span class="o">.</span><span class="na">f4578a</span><span class="o">.</span><span class="na">d</span><span class="o">(</span><span class="n">dVar</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p>When you repeat this process for all unknown method names, you will eventually discover
that <code class="language-plaintext highlighter-rouge">F</code> sets the request header value in the HTTP client, <code class="language-plaintext highlighter-rouge">b</code> calculates the value of
the header, and <code class="language-plaintext highlighter-rouge">i</code> composes the body of the outgoing HTTP request. The obfuscated code
then becomes something that you can easily reason about:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">request</span><span class="o">.</span><span class="na">setHeaders</span><span class="o">(</span><span class="s">"X-Bandcamp-Dm"</span><span class="o">,</span> <span class="n">calculateHash</span><span class="o">(</span><span class="n">request</span><span class="o">.</span><span class="na">getParams</span><span class="o">()));</span>
</code></pre></div></div>

<p>After spending a lot of time with the obfuscated code, you become so familiar
with it that you start noticing things that were impossible to see before. For
example, after a while it became obvious to me that these two methods were aliases
for HMAC SHA-256 and HMAC SHA-512 cryptographic hash functions, respectively:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">e</span><span class="o">(</span><span class="nc">String</span> <span class="n">str</span><span class="o">,</span> <span class="nc">String</span> <span class="n">str2</span><span class="o">,</span> <span class="kt">int</span> <span class="n">i10</span><span class="o">,</span> <span class="kt">float</span> <span class="n">f10</span><span class="o">)</span> <span class="o">{</span>
  <span class="k">return</span> <span class="n">com</span><span class="o">.</span><span class="na">bandcamp</span><span class="o">.</span><span class="na">shared</span><span class="o">.</span><span class="na">platform</span><span class="o">.</span><span class="na">a</span><span class="o">.</span><span class="na">d</span><span class="o">().</span><span class="na">h</span><span class="o">(</span><span class="n">str</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="n">str2</span><span class="o">,</span> <span class="n">i10</span><span class="o">,</span> <span class="n">f10</span><span class="o">);</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">d</span><span class="o">(</span><span class="nc">String</span> <span class="n">str</span><span class="o">,</span> <span class="nc">String</span> <span class="n">str2</span><span class="o">,</span> <span class="kt">int</span> <span class="n">i10</span><span class="o">)</span> <span class="o">{</span>
  <span class="k">return</span> <span class="n">com</span><span class="o">.</span><span class="na">bandcamp</span><span class="o">.</span><span class="na">shared</span><span class="o">.</span><span class="na">platform</span><span class="o">.</span><span class="na">a</span><span class="o">.</span><span class="na">d</span><span class="o">().</span><span class="na">B</span><span class="o">(</span><span class="n">str</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="n">str2</span><span class="o">,</span> <span class="n">i10</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Once you discover the real purpose of a method or a variable, you can also rename it
in JADX. I didn’t use this feature, though. After finally understanding the meaning
of the code, I didn’t feel the need to rename anything, because I had already formed a
mental map, and the obfuscated code began to look just like regular code to me. This
would probably be more challenging for larger apps or if I had to deobfuscate multiple
features, not just header calculation.</p>

<h3 id="string-obfuscation">String obfuscation</h3>

<p>Even when all method and variable names are random nonsense, you still expect to at
least be able to search for string constants. Since the app sends and receives the
header <code class="language-plaintext highlighter-rouge">X-Bandcamp-Dm</code>, that string has to be somewhere in the code, right? But as
I mentioned earlier, that header name was nowhere to be found. How do you even proceed
from here? I started looking for string fragments. How about the string <code class="language-plaintext highlighter-rouge">"X"</code>, the
first character of the header name? There were dozens of occurrences of this value
across the codebase, and most of them led nowhere, but there was also this one:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="n">f17618q</span> <span class="o">=</span> <span class="s">"X"</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="n">f17620s</span> <span class="o">=</span> <span class="s">"pmac"</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="n">f17621t</span> <span class="o">=</span> <span class="s">"D"</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="n">f17622u</span> <span class="o">=</span> <span class="s">"M"</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="n">f17619r</span> <span class="o">=</span> <span class="s">"nab"</span><span class="o">;</span>

<span class="kd">static</span> <span class="o">{</span>
  <span class="n">f17620s</span> <span class="o">+=</span> <span class="s">"d"</span><span class="o">;</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="kt">void</span> <span class="nf">d</span><span class="o">(</span><span class="n">s7</span><span class="o">.</span><span class="na">d</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="n">dVar</span><span class="o">)</span> <span class="o">{</span>
  <span class="kt">char</span><span class="o">[]</span> <span class="n">charArray</span> <span class="o">=</span> <span class="n">f17619r</span><span class="o">.</span><span class="na">toCharArray</span><span class="o">();</span>
  <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i10</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i10</span> <span class="o">&lt;</span> <span class="n">charArray</span><span class="o">.</span><span class="na">length</span> <span class="o">/</span> <span class="mi">2</span><span class="o">;</span> <span class="n">i10</span><span class="o">++)</span> <span class="o">{</span>
    <span class="kt">char</span> <span class="n">c10</span> <span class="o">=</span> <span class="n">charArray</span><span class="o">[</span><span class="n">i10</span><span class="o">];</span>
    <span class="n">charArray</span><span class="o">[</span><span class="n">i10</span><span class="o">]</span> <span class="o">=</span> <span class="n">charArray</span><span class="o">[(</span><span class="n">charArray</span><span class="o">.</span><span class="na">length</span> <span class="o">-</span> <span class="n">i10</span><span class="o">)</span> <span class="o">-</span> <span class="mi">1</span><span class="o">];</span>
    <span class="n">charArray</span><span class="o">[(</span><span class="n">charArray</span><span class="o">.</span><span class="na">length</span> <span class="o">-</span> <span class="n">i10</span><span class="o">)</span> <span class="o">-</span> <span class="mi">1</span><span class="o">]</span> <span class="o">=</span> <span class="n">c10</span><span class="o">;</span>
  <span class="o">}</span>
  <span class="nc">String</span> <span class="n">str</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">(</span><span class="n">charArray</span><span class="o">);</span>
  <span class="kt">char</span><span class="o">[]</span> <span class="n">charArray2</span> <span class="o">=</span> <span class="n">f17620s</span><span class="o">.</span><span class="na">toCharArray</span><span class="o">();</span>
  <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i11</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i11</span> <span class="o">&lt;</span> <span class="n">charArray2</span><span class="o">.</span><span class="na">length</span> <span class="o">/</span> <span class="mi">2</span><span class="o">;</span> <span class="n">i11</span><span class="o">++)</span> <span class="o">{</span>
    <span class="kt">char</span> <span class="n">c11</span> <span class="o">=</span> <span class="n">charArray2</span><span class="o">[</span><span class="n">i11</span><span class="o">];</span>
    <span class="n">charArray2</span><span class="o">[</span><span class="n">i11</span><span class="o">]</span> <span class="o">=</span> <span class="n">charArray2</span><span class="o">[(</span><span class="n">charArray2</span><span class="o">.</span><span class="na">length</span> <span class="o">-</span> <span class="n">i11</span><span class="o">)</span> <span class="o">-</span> <span class="mi">1</span><span class="o">];</span>
    <span class="n">charArray2</span><span class="o">[(</span><span class="n">charArray2</span><span class="o">.</span><span class="na">length</span> <span class="o">-</span> <span class="n">i11</span><span class="o">)</span> <span class="o">-</span> <span class="mi">1</span><span class="o">]</span> <span class="o">=</span> <span class="n">c11</span><span class="o">;</span>
  <span class="o">}</span>
  <span class="nc">String</span> <span class="n">str2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">(</span><span class="n">charArray2</span><span class="o">);</span>
  <span class="n">dVar</span><span class="o">.</span><span class="na">F</span><span class="o">(</span><span class="n">f17618q</span> <span class="o">+</span> <span class="s">"-"</span> <span class="o">+</span> <span class="n">str</span> <span class="o">+</span> <span class="n">str2</span> <span class="o">+</span> <span class="s">"-"</span> <span class="o">+</span> <span class="n">f17621t</span> <span class="o">+</span> <span class="n">f17622u</span><span class="o">,</span> <span class="n">b</span><span class="o">(</span><span class="n">dVar</span><span class="o">.</span><span class="na">i</span><span class="o">()));</span>
<span class="o">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">X</code>, <code class="language-plaintext highlighter-rouge">pmac</code>, <code class="language-plaintext highlighter-rouge">D</code>, <code class="language-plaintext highlighter-rouge">M</code>, <code class="language-plaintext highlighter-rouge">nab</code>, <code class="language-plaintext highlighter-rouge">d</code>—what a weird bunch. Hm, but doesn’t it look an awful
lot like something we are looking for? If your first thought was “this seems to
be a permutation of the <code class="language-plaintext highlighter-rouge">X-Bandcamp-Dm</code> header name”, you were 100% right! The
sole purpose of this class is to hide the well-known string by constructing it
using string concatenation and reversing. All these shenanigans can be replaced
with a single line of code:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="kt">void</span> <span class="nf">d</span><span class="o">(</span><span class="n">s7</span><span class="o">.</span><span class="na">d</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="n">dVar</span><span class="o">)</span> <span class="o">{</span>
  <span class="n">dVar</span><span class="o">.</span><span class="na">F</span><span class="o">(</span><span class="s">"X-Bandcamp-Dm"</span><span class="o">,</span> <span class="n">b</span><span class="o">(</span><span class="n">dVar</span><span class="o">.</span><span class="na">i</span><span class="o">()));</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Searching for string fragments has served me well multiple times, so I hereby
officially declare it to be a very useful method for finding obfuscated string
values.</p>

<h3 id="reflection">Reflection</h3>

<p>This is where analyzing the obfuscated code becomes much more difficult. I’ve
mentioned earlier that even if a method has been renamed, you can still learn
something about it by following its call chain. But in some cases, you don’t
have this luxury: if a method is invoked using reflection, you can’t track
its usage directly anymore. Take a look at this simple example:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">obj</span><span class="o">.</span><span class="na">getClass</span><span class="o">()</span>
  <span class="o">.</span><span class="na">getMethod</span><span class="o">(</span><span class="s">"v0"</span><span class="o">.</span><span class="na">replace</span><span class="o">(</span><span class="s">"0"</span><span class="o">,</span> <span class="s">"alue"</span><span class="o">),</span> <span class="k">new</span> <span class="nc">Class</span><span class="o">[</span><span class="mi">0</span><span class="o">]);</span>
  <span class="o">.</span><span class="na">invoke</span><span class="o">(</span><span class="n">obj</span><span class="o">,</span> <span class="k">new</span> <span class="nc">Object</span><span class="o">[</span><span class="mi">0</span><span class="o">]);</span>
</code></pre></div></div>

<p>If you searched for all usages of method <code class="language-plaintext highlighter-rouge">value()</code> defined in the 
<code class="language-plaintext highlighter-rouge">CacheListenerEvent</code> class, you wouldn’t have found anything. But if you
knew that it might have been called via reflection, you could have searched
for <code class="language-plaintext highlighter-rouge">value</code>, <code class="language-plaintext highlighter-rouge">val</code>, or <code class="language-plaintext highlighter-rouge">lue</code>, and you would have found this call eventually.
It’s not always that easy, though. In some cases, even string search wouldn’t
have helped you:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Class</span><span class="o">.</span><span class="na">forName</span><span class="o">(</span><span class="n">sb2</span><span class="o">.</span><span class="na">toString</span><span class="o">())</span>
  <span class="o">.</span><span class="na">getMethod</span><span class="o">(</span><span class="n">x7</span><span class="o">.</span><span class="na">d</span><span class="o">.</span><span class="na">c</span><span class="o">(</span><span class="s">"lmrgdw"</span><span class="o">,</span> <span class="mi">2</span><span class="o">),</span> <span class="nc">Object</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
  <span class="o">.</span><span class="na">invoke</span><span class="o">(</span><span class="n">cls</span><span class="o">,</span> <span class="s">"2"</span> <span class="o">+</span> <span class="n">obj</span><span class="o">.</span><span class="na">toString</span><span class="o">().</span><span class="na">replaceAll</span><span class="o">(</span><span class="s">"3"</span><span class="o">,</span> <span class="s">"5"</span><span class="o">));</span>
</code></pre></div></div>

<p>Which method is being called here? You can’t easily discover that using only
static analysis—you must directly invoke <code class="language-plaintext highlighter-rouge">x7.d.c("lmrgdw", 2)</code> to determine
the result of the call.</p>

<p>In the end, there is no guaranteed way to defeat this obfuscation technique. You
just need to be patient, and in the worst case, be ready to search for all usages
of reflection to find that single call you need.</p>

<h2 id="x-bandcamp-dm">X-Bandcamp-Dm</h2>

<p>I showed you all these obfuscation techniques because all of them were used
in some form in the <code class="language-plaintext highlighter-rouge">X-Bandcamp-Dm</code> calculation. As I was learning more about
deobfuscation, I was also slowly piecing together the <code class="language-plaintext highlighter-rouge">X-Bandcamp-Dm</code> algorithm.
One day, I would learn how the header name was being constructed and where it was
used. The next day, I would learn that the final header value is an output of the
HMAC function and what its inputs are. After that, I would reverse engineer the
weird, home-made key derivation function used to generate the keys for the HMAC
calculation. Ultimately, I realized that <code class="language-plaintext highlighter-rouge">X-Bandcamp-Dm</code> is an HMAC SHA-256
hash of the incoming <code class="language-plaintext highlighter-rouge">X-Bandcamp-Dm</code> value, outgoing HTTP request body, and one
more value that I couldn’t yet identify. That unidentified value was being
initialized in the following method:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Override</span> <span class="c1">// java.util.Observer</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">update</span><span class="o">(</span><span class="nc">Observable</span> <span class="n">observable</span><span class="o">,</span> <span class="nc">Object</span> <span class="n">obj</span><span class="o">)</span> <span class="o">{</span>
  <span class="k">if</span> <span class="o">((</span><span class="n">obj</span> <span class="k">instanceof</span> <span class="nc">String</span><span class="o">)</span> <span class="o">&amp;&amp;</span> <span class="n">f17625n</span> <span class="o">+</span> <span class="mi">48</span> <span class="o">==</span> <span class="o">((</span><span class="nc">String</span><span class="o">)</span> <span class="n">obj</span><span class="o">).</span><span class="na">charAt</span><span class="o">(</span><span class="mi">0</span><span class="o">))</span> <span class="o">{</span>
    <span class="n">f17624m</span> <span class="o">=</span> <span class="n">obj</span><span class="o">.</span><span class="na">toString</span><span class="o">().</span><span class="na">substring</span><span class="o">(</span><span class="mi">1</span><span class="o">).</span><span class="na">getBytes</span><span class="o">(</span><span class="s">"utf-8"</span><span class="o">);</span>
  <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Of course, there was a catch—I couldn’t find any direct callers of this method.
It could mean only one thing: the call sites were obfuscated to use reflection.
I tried brute-forcing my way out of this problem by inspecting every observer chain
in the code, but there were hundreds of them. Most of them were obfuscated, so this
approach wasn’t going to work in a reasonable timeframe.</p>

<p>I was stuck on this for almost one entire week. After many unsuccessful attempts to find
the caller of this method, it finally dawned on me. The condition before the assignment
was checking if the string <code class="language-plaintext highlighter-rouge">obj</code> starts with the character <code class="language-plaintext highlighter-rouge">"3"</code> (the value of the
field <code class="language-plaintext highlighter-rouge">f17625n</code> was always 3, and 48 is the numeric value of ASCII character 0). This
meant that <code class="language-plaintext highlighter-rouge">obj</code> didn’t start with 3 randomly, but on purpose. Otherwise, the code
couldn’t possibly work. And what’s the way to ensure that something starts with 3?
Well, prepend <code class="language-plaintext highlighter-rouge">"3"</code> to it, of course! I searched for <code class="language-plaintext highlighter-rouge">"3" +</code> and found this:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Override</span> <span class="c1">// java.util.Observer</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">update</span><span class="o">(</span><span class="nc">Observable</span> <span class="n">observable</span><span class="o">,</span> <span class="nc">Object</span> <span class="n">obj</span><span class="o">)</span> <span class="o">{</span>
  <span class="o">((</span><span class="nc">Class</span><span class="o">)</span> <span class="o">((</span><span class="nc">Object</span><span class="o">[])</span> <span class="n">obj</span><span class="o">)[</span><span class="mi">2</span><span class="o">]).</span><span class="na">getMethod</span><span class="o">(</span>
    <span class="n">a</span><span class="o">.</span><span class="na">this</span><span class="o">.</span><span class="na">f18941o</span><span class="o">.</span><span class="na">substring</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">5</span><span class="o">)</span> <span class="o">+</span> <span class="n">a</span><span class="o">.</span><span class="na">this</span><span class="o">.</span><span class="na">f18942p</span><span class="o">.</span><span class="na">substring</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">1</span><span class="o">),</span>
    <span class="nc">Object</span><span class="o">.</span><span class="na">class</span>
  <span class="o">).</span><span class="na">invoke</span><span class="o">(</span>
    <span class="o">(</span><span class="nc">Class</span><span class="o">)</span> <span class="o">((</span><span class="nc">Object</span><span class="o">[])</span> <span class="n">obj</span><span class="o">)[</span><span class="mi">2</span><span class="o">],</span>
    <span class="s">"3"</span> <span class="o">+</span> <span class="n">x7</span><span class="o">.</span><span class="na">h</span><span class="o">.</span><span class="na">d</span><span class="o">(</span>
      <span class="o">(</span><span class="nc">String</span><span class="o">)</span> <span class="o">((</span><span class="nc">Object</span><span class="o">[])</span> <span class="n">obj</span><span class="o">)[</span><span class="mi">0</span><span class="o">],</span>
      <span class="o">(</span><span class="nc">String</span><span class="o">)</span> <span class="o">((</span><span class="nc">Object</span><span class="o">[])</span> <span class="n">obj</span><span class="o">)[</span><span class="mi">1</span><span class="o">],</span>
      <span class="mi">0</span>
    <span class="o">)</span>
  <span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p>This was the most heavily obfuscated piece of code I had encountered.
It was similar to a final boss fight, because it was using all obfuscation methods
that had been bothering me previously (renaming, string obfuscation, reflection).
However, by this point, all of this had become standard procedure for me. I quickly
discovered that the reflection call was invoking the method <code class="language-plaintext highlighter-rouge">notify</code>, which was then
notifying the observer I was interested in. This was the final piece of the puzzle!
I deobfuscated the remaining parameters and updated my API client code. The moment
I ran it and finally received the HTTP status code 451 instead of 418 from Bandcamp
servers will forever remain as one of the happiest moments in my hacking history.</p>

<p>Looking back, it’s so funny that the <code class="language-plaintext highlighter-rouge">X-Bandcamp-Dm</code> calculation algorithm is so simple
and clean and so easy to describe, yet it took me weeks to recreate it from thousands of
lines of obfuscated code.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">input</span> <span class="p">=</span> <span class="n">response</span><span class="p">.</span><span class="n">Headers</span><span class="p">[</span><span class="s">"X-Bandcamp-Dm"</span><span class="p">];</span>

<span class="kt">var</span> <span class="n">key1</span> <span class="p">=</span> <span class="nf">FunkyKdf1</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">staticKey1</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">key2</span> <span class="p">=</span> <span class="nf">FunkyKdf2</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">staticKey2</span><span class="p">);</span>

<span class="kt">var</span> <span class="n">output</span> <span class="p">=</span> <span class="nf">HmacSha256</span><span class="p">(</span><span class="n">key2</span> <span class="p">+</span> <span class="n">request</span><span class="p">.</span><span class="n">Body</span><span class="p">,</span> <span class="n">key1</span><span class="p">);</span>

<span class="n">request</span><span class="p">.</span><span class="n">Headers</span><span class="p">[</span><span class="s">"X-Bandcamp-Dm"</span><span class="p">]</span> <span class="p">=</span> <span class="n">output</span><span class="p">;</span>
</code></pre></div></div>

<h2 id="x-bandcamp-pow">X-Bandcamp-Pow</h2>

<p>With <code class="language-plaintext highlighter-rouge">X-Bandcamp-Dm</code> out of the way, it was time to figure out the meaning of the
<code class="language-plaintext highlighter-rouge">X-Bandcamp-Pow</code> header. Compared to the time I had spent on <code class="language-plaintext highlighter-rouge">X-Bandcamp-Dm</code>, reversing
the calculation of <code class="language-plaintext highlighter-rouge">X-Bandcamp-Pow</code> was a breeze. It turned out to be a proof-of-work
scheme that closely resembles <a href="http://www.hashcash.org/">Hashcash</a>, the scheme that
inspired Bitcoin’s own proof-of-work implementation. Bandcamp’s version concatenates
the request body with the incoming <code class="language-plaintext highlighter-rouge">X-Bandcamp-Pow</code> value and an increasing counter.
Next, it repeatedly calculates the SHA-1 hash of the new string until the output has the
desired number of leading zero bits. The final counter value is then encoded using Base36
and appended to the original <code class="language-plaintext highlighter-rouge">X-Bandcamp-Pow</code> value.</p>

<p>For example, if <code class="language-plaintext highlighter-rouge">X-Bandcamp-Pow</code> is <code class="language-plaintext highlighter-rouge">1:10:f6e592b662b3</code>, it means we need to find a
hash with 10 leading zero bits. If we find it in 760 iterations, then the outgoing
<code class="language-plaintext highlighter-rouge">X-Bandcamp-Pow</code> value will be <code class="language-plaintext highlighter-rouge">1:10:f6e592b662b3:l4</code> (760 is l4 in Base36).</p>

<p>It seems to me that the only reason for the introduction of this header was that
everyone wanted to be a part of the blockchain craze at that time (<code class="language-plaintext highlighter-rouge">X-Bandcamp-Pow</code>
was first introduced in December 2019, a year and a half after <code class="language-plaintext highlighter-rouge">X-Bandcamp-Dm</code>).
I don’t see any other explanation, because <code class="language-plaintext highlighter-rouge">X-Bandcamp-Pow</code> doesn’t offer any
additional advantages over <code class="language-plaintext highlighter-rouge">X-Bandcamp-Dm</code> (which can’t be brute-forced anyway).</p>

<p>But I digress. The moment of truth had arrived. I implemented proof-of-work
calculation in my API client, ran it, and got the following output:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HTTP/2 418 I'm a teapot
HTTP/2 451 Unavailable For Legal Reasons
HTTP/2 200 OK
</code></pre></div></div>

<p>My first login request was successful, and the authentication token was finally
mine! After this, implementing the rest of the API for downloading the albums
from the collection was trivial.</p>

<h2 id="bandcamp-downloader">Bandcamp downloader</h2>

<p>The command line tool I wrote is available
<a href="https://github.com/Metalnem/bandcamp-downloader">here</a>.
It has an absolutely minimal set of features: you can list all your purchased
albums and you can download a specific album from your collection in MP3 V0
format. Here is one usage example:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># List all albums in your Bandcamp collection</span>
<span class="nv">$ </span>dotnet run <span class="nt">--username</span> <span class="nv">$USERNAME</span> <span class="nt">--password</span> <span class="nv">$PASSWORD</span>
870109722 Bolt Thrower — Realm of Chaos
910230745 Cannibal Corpse — Evisceration Plague
157725502 Cryptopsy — None So Vile
388372040 Incantation — Onward to Golgotha
212824804 Archspire — Relentless Mutation

<span class="c"># Download the album with the specified ID</span>
<span class="nv">$ </span>dotnet run <span class="nt">--username</span> <span class="nv">$USERNAME</span> <span class="nt">--password</span> <span class="nv">$PASSWORD</span> <span class="nt">--album</span> 870109722
</code></pre></div></div>

<p>I don’t plan to extend it with more features, since my main goal in this quest was
to enable Bandcamp users to download the albums they can’t download in any other way.
Also, there are already many feature-rich Bandcamp downloaders around, and it would
make more sense to extend them with proper authentication than to reimplement all their
features from scratch in my repo. If you are a maintainer of one such downloader, feel
free to reuse the authentication code that I have implemented.</p>

<p>Enjoy downloading your lost albums and listening to them once again!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Did you know that the albums you purchase on Bandcamp can disappear from your collection without notice? This can happen for various reasons. For example, a seller might decide on a whim to remove the album from the platform. Bandcamp apparently allows this in their terms of use: Content you purchase in a Transaction cannot be guaranteed to be available to you perpetually. Users bear all risk from the denial of access to any Content purchased through the Service. The only way to make sure your albums stay in your possession is to download them immediately after purchase. Heck, even Bandcamp officially recommends this: […] we encourage you to promptly download any Content you purchase through the Site […] However, even if the album has been removed, and you hadn’t dowloaded it, not all is lost. In the Bandcamp mobile app, you can continue to listen to all your albums (but without the option to download them), even after they’ve been removed from the platform. This obviously means that Bandcamp doesn’t delete the actual content from their servers. And if the app can still access the lost albums, so can everyone who is patient enough to reverse engineer the app. Surprisingly, no one has done this by now. Could it be that it’s impossible? Let’s dive in and see what’s going on inside the Bandcamp app! Inspecting the network traffic As always, my first step was to inspect the network traffic between the Bandcamp app and their backend servers. My favorite tool for this purpose has always been Burp Suite Community Edition. After setting up the proxy and opening the collection page in the app, I quickly noticed the following HTTP request in proxy logs: GET /api/collectionsync/1/collection?page_size=200 HTTP/2 Host: bandcamp.com Authorization: Bearer MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3 This API endpoint returns the information about all your albums (things like album name, band info, release date, purchase date, etc.). Not only that, but it also lists all the album tracks, together with something that looks like high-quality audio URLs: { "token": "1:1700775127:355751800:a", "tralbum_id": 355751800, "title": "Pinnacle Of Bedlam", "tracks": [ { "track_id": 3770803404, "title": "Cycles Of Suffering", "audio_url": "https://t4.bcbits.com/stream/b32687/mp3-128/3770803404", "hq_audio_url": "https://t4.bcbits.com/stream/fc3538/mp3-v0/3770803404", "track_number": 1 }, { "track_id": 2590214273, "title": "Purgatorical Punishment", "audio_url": "https://t4.bcbits.com/stream/2b6cad/mp3-128/2590214273", "hq_audio_url": "https://t4.bcbits.com/stream/a37aa9/mp3-v0/2590214273", "track_number": 2 } ] } Unfortunately, hq_audio_url is a bit of a misnomer. High quality in this context refers to MP3 V0, which is a lossy format (unlike regular Bandcamp downloads on the web page, where you can choose from various selection of lossless formats). The good news is that it’s very unlikely you can even hear the difference between lossless formats and high bitrate lossy formats. In any case, it’s better to have your music than not to have it at all, so I’ll happily take MP3 V0 over nothing any day. Anyway, I tried one of the download links from the JSON response and it worked: First obstacle had been conquered. It was an important milestone for me, because at this moment I knew that even if I couldn’t figure out how to get the authentication token programmatically, I would still be able to manually download the missing albums from my collection: I could just save all HTTP responses and extract the audio URLs by hand. It wouldn’t be the most exciting job ever, but it would do the trick. Ah, who am I kidding? Of course I would not be happy with such half-assed solution. I obviously had to automate this process, which meant I needed to figure out how to get the authentication token. Authentication protocol Logins are typically very simple: you send a POST request with your username and password, and you get an authentication token in return. Bandcamp’s login protocol is much more convoluted. Here is the high level description of the login flow: App sends the login request to /oauth_login endpoint. Server returns 418 status code and a hex-encoded, random-looking X-Bandcamp-Dm header. App resends the login request with its own X-Bandcamp-Dm value. Server returns 451 status code and introduces a new X-Bandcamp-Pow header. App sends the final login request with its own X-Bandcamp-Pow value. Server returns 200 status code and an authentication token. Based on this entire exchange, it appeared that X-Bandcamp-Dm and X-Bandcamp-Pow response headers served as some sort of challenge. Correct outgoing header values were necessary for successful authentication, and were in some way dependent on the incoming values. Figuring out how the correct header values are generated just by looking at network traffic was clearly impossible; the answer to this question could only be found in the client application code. On the off chance that someone else had already figured out the algorithm, I did a quick Google and GitHub search for X-Bandcamp-Dm. I got a couple of hits, but all of them were just documenting the struggles of other people: ok how they handle the new DM is, basically put, a pain in the ass, we’ll see if I get around figuring out wtf is happening (MITM Android Bandcamp app) I’d love to support that if someone wants to reverse-engineer the X-Bandcamp-DM and X-Bandcamp-PoW headers. (Mopidy-Bandcamp) Unfortunately I cannot recreate x-bandcamp-dm value in headers. (Python Bandcamp scraper) Apparently, X-Bandcamp-Dm and X-Bandcamp-Pow headers were the secret ingredient that made it difficult to reverse engineer the login API. It was time to decompile the mobile app and find the answer to how these secret values are generated. Decompiling the Android app Since reversing managed code is much easier than reversing native code, I chose to decompile the Android mobile app. I’ve always used JADX for this purpose and it has always served me well (its GUI features would turn out to be particularly useful). After downloading the Bandcamp application package from APKMirror and opening it with JADX, I found out that the app was obfuscated: I had never reverse engineered obfuscated code before. To determine how difficult the process would be, I searched for all occurrences of string X-Bandcamp-Dm. Number of results: zero. So, not only was the code obfuscated, but the string values were obfuscated as well. That meant the job of figuring out how the mysterious header values were calculated was not going to be easy. In fact, I wasn’t sure if it was going to be possible at all, since I didn’t have any clue where to start. I had doubts whether I even wanted to embark on this journey, but ultimately, I decided to do it, even if it takes me half a year (luckily, I only needed three weeks). Obfuscation techniques The app was using many different obfuscation techniques, and since I was a complete newbie in this area, I had to learn from scratch how to defeat each one. I’m going to show you some of the techniques I’ve seen, along with the tips on how to fight them. Reverse engineering veterans among you probably know all of them already, but if you are a beginner, I hope that you’ll learn something new and see that obfuscation is not as intimidating as it might appear. Renaming Renaming is an obfuscating method where identifiers (variable, class, field, and method names) are renamed to random gibberish. This is probably the most well-known type of obfuscation, so it’s not surprising that it’s frequently used in Bandcamp mobile app. For example, a typical method call might look something like this: dVar.F(f17618q, b(dVar.i())); When I first looked at this code, I had no idea what methods F, b and i were doing. Luckily, JADX is almost a full-blown IDE, so it contains features such as “Find usage” and “Go to declaration”. These two made the analysis much easier, because I was able to traverse the call chains until I reached some method with a normal, unobfuscated name, such as this one: public void setHeaders(s7.d dVar) { this.f4578a.d(dVar); } When you repeat this process for all unknown method names, you will eventually discover that F sets the request header value in the HTTP client, b calculates the value of the header, and i composes the body of the outgoing HTTP request. The obfuscated code then becomes something that you can easily reason about: request.setHeaders("X-Bandcamp-Dm", calculateHash(request.getParams())); After spending a lot of time with the obfuscated code, you become so familiar with it that you start noticing things that were impossible to see before. For example, after a while it became obvious to me that these two methods were aliases for HMAC SHA-256 and HMAC SHA-512 cryptographic hash functions, respectively: public static String e(String str, String str2, int i10, float f10) { return com.bandcamp.shared.platform.a.d().h(str, null, str2, i10, f10); } public static String d(String str, String str2, int i10) { return com.bandcamp.shared.platform.a.d().B(str, null, str2, i10); } Once you discover the real purpose of a method or a variable, you can also rename it in JADX. I didn’t use this feature, though. After finally understanding the meaning of the code, I didn’t feel the need to rename anything, because I had already formed a mental map, and the obfuscated code began to look just like regular code to me. This would probably be more challenging for larger apps or if I had to deobfuscate multiple features, not just header calculation. String obfuscation Even when all method and variable names are random nonsense, you still expect to at least be able to search for string constants. Since the app sends and receives the header X-Bandcamp-Dm, that string has to be somewhere in the code, right? But as I mentioned earlier, that header name was nowhere to be found. How do you even proceed from here? I started looking for string fragments. How about the string "X", the first character of the header name? There were dozens of occurrences of this value across the codebase, and most of them led nowhere, but there was also this one: public static String f17618q = "X"; public static String f17620s = "pmac"; public static String f17621t = "D"; public static String f17622u = "M"; public static String f17619r = "nab"; static { f17620s += "d"; } public &lt;T&gt; void d(s7.d&lt;T&gt; dVar) { char[] charArray = f17619r.toCharArray(); for (int i10 = 0; i10 &lt; charArray.length / 2; i10++) { char c10 = charArray[i10]; charArray[i10] = charArray[(charArray.length - i10) - 1]; charArray[(charArray.length - i10) - 1] = c10; } String str = new String(charArray); char[] charArray2 = f17620s.toCharArray(); for (int i11 = 0; i11 &lt; charArray2.length / 2; i11++) { char c11 = charArray2[i11]; charArray2[i11] = charArray2[(charArray2.length - i11) - 1]; charArray2[(charArray2.length - i11) - 1] = c11; } String str2 = new String(charArray2); dVar.F(f17618q + "-" + str + str2 + "-" + f17621t + f17622u, b(dVar.i())); } X, pmac, D, M, nab, d—what a weird bunch. Hm, but doesn’t it look an awful lot like something we are looking for? If your first thought was “this seems to be a permutation of the X-Bandcamp-Dm header name”, you were 100% right! The sole purpose of this class is to hide the well-known string by constructing it using string concatenation and reversing. All these shenanigans can be replaced with a single line of code: public &lt;T&gt; void d(s7.d&lt;T&gt; dVar) { dVar.F("X-Bandcamp-Dm", b(dVar.i())); } Searching for string fragments has served me well multiple times, so I hereby officially declare it to be a very useful method for finding obfuscated string values. Reflection This is where analyzing the obfuscated code becomes much more difficult. I’ve mentioned earlier that even if a method has been renamed, you can still learn something about it by following its call chain. But in some cases, you don’t have this luxury: if a method is invoked using reflection, you can’t track its usage directly anymore. Take a look at this simple example: obj.getClass() .getMethod("v0".replace("0", "alue"), new Class[0]); .invoke(obj, new Object[0]); If you searched for all usages of method value() defined in the CacheListenerEvent class, you wouldn’t have found anything. But if you knew that it might have been called via reflection, you could have searched for value, val, or lue, and you would have found this call eventually. It’s not always that easy, though. In some cases, even string search wouldn’t have helped you: Class.forName(sb2.toString()) .getMethod(x7.d.c("lmrgdw", 2), Object.class) .invoke(cls, "2" + obj.toString().replaceAll("3", "5")); Which method is being called here? You can’t easily discover that using only static analysis—you must directly invoke x7.d.c("lmrgdw", 2) to determine the result of the call. In the end, there is no guaranteed way to defeat this obfuscation technique. You just need to be patient, and in the worst case, be ready to search for all usages of reflection to find that single call you need. X-Bandcamp-Dm I showed you all these obfuscation techniques because all of them were used in some form in the X-Bandcamp-Dm calculation. As I was learning more about deobfuscation, I was also slowly piecing together the X-Bandcamp-Dm algorithm. One day, I would learn how the header name was being constructed and where it was used. The next day, I would learn that the final header value is an output of the HMAC function and what its inputs are. After that, I would reverse engineer the weird, home-made key derivation function used to generate the keys for the HMAC calculation. Ultimately, I realized that X-Bandcamp-Dm is an HMAC SHA-256 hash of the incoming X-Bandcamp-Dm value, outgoing HTTP request body, and one more value that I couldn’t yet identify. That unidentified value was being initialized in the following method: @Override // java.util.Observer public void update(Observable observable, Object obj) { if ((obj instanceof String) &amp;&amp; f17625n + 48 == ((String) obj).charAt(0)) { f17624m = obj.toString().substring(1).getBytes("utf-8"); } } Of course, there was a catch—I couldn’t find any direct callers of this method. It could mean only one thing: the call sites were obfuscated to use reflection. I tried brute-forcing my way out of this problem by inspecting every observer chain in the code, but there were hundreds of them. Most of them were obfuscated, so this approach wasn’t going to work in a reasonable timeframe. I was stuck on this for almost one entire week. After many unsuccessful attempts to find the caller of this method, it finally dawned on me. The condition before the assignment was checking if the string obj starts with the character "3" (the value of the field f17625n was always 3, and 48 is the numeric value of ASCII character 0). This meant that obj didn’t start with 3 randomly, but on purpose. Otherwise, the code couldn’t possibly work. And what’s the way to ensure that something starts with 3? Well, prepend "3" to it, of course! I searched for "3" + and found this: @Override // java.util.Observer public void update(Observable observable, Object obj) { ((Class) ((Object[]) obj)[2]).getMethod( a.this.f18941o.substring(0, 5) + a.this.f18942p.substring(0, 1), Object.class ).invoke( (Class) ((Object[]) obj)[2], "3" + x7.h.d( (String) ((Object[]) obj)[0], (String) ((Object[]) obj)[1], 0 ) ); } This was the most heavily obfuscated piece of code I had encountered. It was similar to a final boss fight, because it was using all obfuscation methods that had been bothering me previously (renaming, string obfuscation, reflection). However, by this point, all of this had become standard procedure for me. I quickly discovered that the reflection call was invoking the method notify, which was then notifying the observer I was interested in. This was the final piece of the puzzle! I deobfuscated the remaining parameters and updated my API client code. The moment I ran it and finally received the HTTP status code 451 instead of 418 from Bandcamp servers will forever remain as one of the happiest moments in my hacking history. Looking back, it’s so funny that the X-Bandcamp-Dm calculation algorithm is so simple and clean and so easy to describe, yet it took me weeks to recreate it from thousands of lines of obfuscated code. var input = response.Headers["X-Bandcamp-Dm"]; var key1 = FunkyKdf1(input, staticKey1); var key2 = FunkyKdf2(input, staticKey2); var output = HmacSha256(key2 + request.Body, key1); request.Headers["X-Bandcamp-Dm"] = output; X-Bandcamp-Pow With X-Bandcamp-Dm out of the way, it was time to figure out the meaning of the X-Bandcamp-Pow header. Compared to the time I had spent on X-Bandcamp-Dm, reversing the calculation of X-Bandcamp-Pow was a breeze. It turned out to be a proof-of-work scheme that closely resembles Hashcash, the scheme that inspired Bitcoin’s own proof-of-work implementation. Bandcamp’s version concatenates the request body with the incoming X-Bandcamp-Pow value and an increasing counter. Next, it repeatedly calculates the SHA-1 hash of the new string until the output has the desired number of leading zero bits. The final counter value is then encoded using Base36 and appended to the original X-Bandcamp-Pow value. For example, if X-Bandcamp-Pow is 1:10:f6e592b662b3, it means we need to find a hash with 10 leading zero bits. If we find it in 760 iterations, then the outgoing X-Bandcamp-Pow value will be 1:10:f6e592b662b3:l4 (760 is l4 in Base36). It seems to me that the only reason for the introduction of this header was that everyone wanted to be a part of the blockchain craze at that time (X-Bandcamp-Pow was first introduced in December 2019, a year and a half after X-Bandcamp-Dm). I don’t see any other explanation, because X-Bandcamp-Pow doesn’t offer any additional advantages over X-Bandcamp-Dm (which can’t be brute-forced anyway). But I digress. The moment of truth had arrived. I implemented proof-of-work calculation in my API client, ran it, and got the following output: HTTP/2 418 I'm a teapot HTTP/2 451 Unavailable For Legal Reasons HTTP/2 200 OK My first login request was successful, and the authentication token was finally mine! After this, implementing the rest of the API for downloading the albums from the collection was trivial. Bandcamp downloader The command line tool I wrote is available here. It has an absolutely minimal set of features: you can list all your purchased albums and you can download a specific album from your collection in MP3 V0 format. Here is one usage example: # List all albums in your Bandcamp collection $ dotnet run --username $USERNAME --password $PASSWORD 870109722 Bolt Thrower — Realm of Chaos 910230745 Cannibal Corpse — Evisceration Plague 157725502 Cryptopsy — None So Vile 388372040 Incantation — Onward to Golgotha 212824804 Archspire — Relentless Mutation # Download the album with the specified ID $ dotnet run --username $USERNAME --password $PASSWORD --album 870109722 I don’t plan to extend it with more features, since my main goal in this quest was to enable Bandcamp users to download the albums they can’t download in any other way. Also, there are already many feature-rich Bandcamp downloaders around, and it would make more sense to extend them with proper authentication than to reimplement all their features from scratch in my repo. If you are a maintainer of one such downloader, feel free to reuse the authentication code that I have implemented. Enjoy downloading your lost albums and listening to them once again!]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://mijailovic.net/assets/img/2024-04-04-flowchart-preview.png" /><media:content medium="image" url="https://mijailovic.net/assets/img/2024-04-04-flowchart-preview.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">My bug finding chronicles (and how to earn money through vulnerability research)</title><link href="https://mijailovic.net/2023/08/16/bug-finding-chronicles/" rel="alternate" type="text/html" title="My bug finding chronicles (and how to earn money through vulnerability research)" /><published>2023-08-16T18:00:00+00:00</published><updated>2023-08-16T18:00:00+00:00</updated><id>https://mijailovic.net/2023/08/16/bug-finding-chronicles</id><content type="html" xml:base="https://mijailovic.net/2023/08/16/bug-finding-chronicles/"><![CDATA[<p>At least one person has asked me about my thought process when searching for denial-of-service
vulnerabilities: how do I choose the target libraries, what specifically I look for in them, etc.
“At least one person” meaning “exactly one person”, and that one person happens to be my wife, but
I decided to write a whole post on this topic anyway! In this half-memoir,
half-tutorial post, I’ll show you the most interesting bugs I’ve found so far and the methods I
used to find them. I wouldn’t say I have any advanced bug finding skills, but I do know a few useful
techniques, so I hope you will learn something new today.</p>

<h2 id="finding-a-panic-in-the-go-standard-library">Finding a panic in the Go standard library</h2>

<p>If memory serves me right, my bug finding adventures started in July 2017. Fuzzing
was a big thing back then, and I got attracted to it after reading the famous blog post
<a href="https://lcamtuf.blogspot.com/2014/11/pulling-jpegs-out-of-thin-air.html">Pulling JPEGs out of thin air</a>.
I was learning the Go programming language at the time and after I found this
<a href="https://dgryski.medium.com/go-fuzz-github-com-arolek-ase-3c74d5a3150c">great article</a>
on <a href="https://github.com/dvyukov/go-fuzz">go-fuzz</a>, I finally decided to give fuzzing a try.</p>

<p>The easiest way to get started is by fuzzing some library you are already familiar with. You
can’t fuzz just any library, though: in general, the library you want to fuzz should ideally
parse or deserialize the input parameters in some way. Or even simpler: if you can’t pass a
random byte array (or something convertible to byte array, such as string) to the library,
you can’t easily fuzz it.</p>

<p>With these criteria in mind, I then selected two candidates: UniDoc and goftp. There was nothing
special about them other than the fact I was using them at that time. You can read the detailed
description of these fuzzing adventures in my post
<a href="/2017/07/29/go-fuzz/">Going down the rabbit hole with go-fuzz</a>.</p>

<p>Fuzzing goftp turned out to be the most important milestone in my fuzzing career. I got lucky
and discovered a panic (that’s just a fancy word Go people use to call a process crash) not
in goftp itself, but in the Go standard library! Here is a seemingly innocent line of code
that used to be able to kill your process:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">time</span><span class="o">.</span><span class="n">Parse</span><span class="p">(</span><span class="s">"_2 Jan 06 15:04 MST"</span><span class="p">,</span> <span class="s">"4 --- 00 00:00 GMT"</span><span class="p">)</span>
</code></pre></div></div>

<p>I filed <a href="https://github.com/golang/go/issues/21113">the bug</a>, and it was marked as a release
blocker. If seems funny after all these years, but that made me really proud for some reason.
In retrospect, had I reported this bug to Google security team, I could have easily earned a
bug bounty reward and a <a href="https://en.wikipedia.org/wiki/Common_Vulnerabilities_and_Exposures">CVE</a>.
But a more important thing came out of this: I was now hooked on fuzzing!</p>

<h2 id="finding-a-panic-in-the-roughtime-library">Finding a panic in the Roughtime library</h2>

<p>A few years back, I was really into cryptography (proper cryptography,
not the blockchain-scam-cryptocurrency type of crypto). I was solving
<a href="https://cryptopals.com/">The Cryptopals Crypto Challenges</a>, implementing
<a href="http://www.noiseprotocol.org/">Noise Protocol Framework</a> and
<a href="https://www.imperialviolet.org/2017/05/14/aesgcmsiv.html">AES-GCM-SIV</a>, that kind of
stuff. Somewhere around October 2017, I discovered
<a href="https://roughtime.googlesource.com/roughtime">Roughtime</a>, a secure time synchronization
protocol. Why was I interested in it? I have no idea, but it probably just looked cool
(everything related to cryptography seemed fascinating back then). I wanted to implement
this in C#, so I started reading the code, and somehow I immediately discovered a panic
caused by out-of-bounds access (I wish I could recall my thought process from back then).
I was extremely proud of this, because the Roughtime protocol implementation was
written by none other than Adam Langley, one of the biggest cryptography experts in the world.
Again, it seems silly from today’s perspective, but at that time, it meant the world to me. This
time, I decided not only to report the bug, but to also become a Roughtime contributor, so I
submitted <a href="https://roughtime-review.googlesource.com/c/roughtime/+/2180">the fix</a>,
and Adam Langley himself approved it. I was still not experienced at reporting bugs, so this
one didn’t result in a CVE, either. I got this PR approval instead, and it was more than enough for me:</p>

<p><img src="/assets/img/2023-08-16-roughtime.png" alt="" /></p>

<h2 id="fuzzing-net-libraries-with-sharpfuzz">Fuzzing .NET libraries with SharpFuzz</h2>

<p>In 2018, I wrote SharpFuzz, and it turned out to be one of the projects I’m most proud of.
Five years later, it’s still the only coverage-guided fuzzing tool for .NET. You can read
about the SharpFuzz and the motivation behind it in my previous posts. In this section, I’ll
focus more on how to choose which libraries to fuzz and how to write the fuzzing code for them.</p>

<p>General fuzzing advice still applies: simply fuzz a library you are already familiar with.
Fuzzing works best with deserialization libraries, because parsing code is often highly
complex and susceptible to bugs. Another important reason for fuzzing deserialization
libraries is that you really can’t afford to have bugs in them. They are likely the first
components you call to process user-provided inputs (for example, by parsing an HTTP request
with JSON body), which means deserialization bugs can potentially have a catastrophic impact
on your service:
<a href="/2023/02/20/stack-overflow-exception/">How StackOverflowException can bring down an expensive compute cluster</a>.</p>

<p>With this in mind, you can choose from dozens of candidate formats: JSON, XML, Protocol Buffers,
MessagePack, HTML, YAML, Markdown, CSV, GraphQL, etc. Another interesting category of fuzzing targets
contains the libraries that work with file formats: images, fonts, ZIP archives, PDF documents, etc. If you
run out of targets, you can also find a few more candidates by looking at NuGet tags such as
“serialization” or “parsing” (sorted by popularity if you want to find bugs with the biggest
impact), or just Googling for “.NET serialization libraries”.</p>

<p>After choosing the <del>victim</del> library, you need to write the fuzzing function. That’s often
very easy, since most parsers/deserializers load their inputs from a string or a stream (and
SharpFuzz supports both). Fuzzing Newtonsoft.Json?</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Fuzzer</span><span class="p">.</span><span class="n">OutOfProcess</span><span class="p">.</span><span class="nf">Run</span><span class="p">(</span><span class="n">s</span> <span class="p">=&gt;</span>
<span class="p">{</span>
  <span class="n">JsonConvert</span><span class="p">.</span><span class="nf">DeserializeObject</span><span class="p">(</span><span class="n">s</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Fuzzing protobuf-net?</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Fuzzer</span><span class="p">.</span><span class="n">OutOfProcess</span><span class="p">.</span><span class="nf">Run</span><span class="p">(</span><span class="n">stream</span> <span class="p">=&gt;</span>
<span class="p">{</span>
  <span class="n">Serializer</span><span class="p">.</span><span class="n">Deserialize</span><span class="p">&lt;</span><span class="n">Person</span><span class="p">&gt;(</span><span class="n">stream</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Almost all popular libraries have some basic usage examples in their documentation,
and typically you can use them directly in your fuzzing function (it worked for me
in 99% of the cases).</p>

<p>If you are still not motivated enough, here is one more attempt to convince you to try
fuzzing: if the library has not been fuzzed before, you are very likely to find some
bug in it, even if it’s just an undocumented exception.</p>

<h2 id="finding-bugs-in-the-net-core-standard-library">Finding bugs in the .NET Core standard library</h2>

<p>.NET Core standard library is extremely well written and covered with tons of tests. To
successfully fuzz it, I needed to write more creative fuzzers and use some advanced tricks.
For example, a bug in the library doesn’t necessarily mean you’ll get an unhandled exception
or a process crash: a function may seemingly complete without errors, but it could still
produce an incorrect result. Fuzzing can help you even in this scenario. Consider this
fuzzing function:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Fuzzer</span><span class="p">.</span><span class="n">LibFuzzer</span><span class="p">.</span><span class="nf">Run</span><span class="p">(</span><span class="n">span</span> <span class="p">=&gt;</span>
<span class="p">{</span>
  <span class="kt">string</span> <span class="n">s1</span> <span class="p">=</span> <span class="n">Encoding</span><span class="p">.</span><span class="n">UTF8</span><span class="p">.</span><span class="nf">GetString</span><span class="p">(</span><span class="n">span</span><span class="p">);</span>

  <span class="k">if</span> <span class="p">(!</span><span class="kt">double</span><span class="p">.</span><span class="nf">TryParse</span><span class="p">(</span><span class="n">s1</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">d1</span><span class="p">)</span> <span class="p">||</span> <span class="kt">double</span><span class="p">.</span><span class="nf">IsNaN</span><span class="p">(</span><span class="n">d1</span><span class="p">))</span>
  <span class="p">{</span>
    <span class="k">return</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="kt">var</span> <span class="n">s2</span> <span class="p">=</span> <span class="n">d1</span><span class="p">.</span><span class="nf">ToString</span><span class="p">(</span><span class="s">"G17"</span><span class="p">);</span>
  <span class="kt">var</span> <span class="n">d2</span> <span class="p">=</span> <span class="kt">double</span><span class="p">.</span><span class="nf">Parse</span><span class="p">(</span><span class="n">s2</span><span class="p">);</span>

  <span class="k">if</span> <span class="p">(</span><span class="n">d1</span> <span class="p">!=</span> <span class="n">d2</span><span class="p">)</span>
  <span class="p">{</span>
    <span class="k">throw</span> <span class="k">new</span> <span class="nf">Exception</span><span class="p">();</span>
  <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<p>G17 format specifier is used to roundtrip floating-point numbers. That means if you
serialize a value using this format, it guarantees that deserialization will return
the original value. And how can fuzzing find bugs in the round-tripping code? Simply
compare the original number with the deserialized one and throw an exception if they
are different. Since fuzzer is instrumenting both serialization and deserialization
code paths, it will likely find a mismatch if there is one. Say hello to this .NET Core bug:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">s</span> <span class="p">=</span> <span class="s">"23723333333333333433333337"</span><span class="p">;</span>

<span class="kt">double</span> <span class="n">d1</span> <span class="p">=</span> <span class="n">Double</span><span class="p">.</span><span class="nf">Parse</span><span class="p">(</span><span class="n">s</span><span class="p">);</span>
<span class="kt">double</span> <span class="n">d2</span> <span class="p">=</span> <span class="n">Double</span><span class="p">.</span><span class="nf">Parse</span><span class="p">(</span><span class="n">d1</span><span class="p">.</span><span class="nf">ToString</span><span class="p">(</span><span class="s">"G17"</span><span class="p">));</span>

<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="n">d1</span> <span class="p">==</span> <span class="n">d2</span><span class="p">);</span>
</code></pre></div></div>

<p>By design, this code snippet is supposed to print <code class="language-plaintext highlighter-rouge">true</code>. SharpFuzz discovered that
on .NET Core 2.2, the result was actually <code class="language-plaintext highlighter-rouge">false</code>.</p>

<p>This is just a single example of a more general fuzzing technique. For example, you
can compare for equivalence two completely different libraries implementing the same
functionality: call both implementations, compare the results, throw an exception if
they are different, and fuzzer will try to find the inputs that can trigger this
scenario.</p>

<h2 id="finding-cves">Finding CVEs</h2>

<p>After using SharpFuzz to find process crashes and hangs in multiple libraries, I realized
that it would be way more fun to figure out how to use these bugs to kill a remote service
(without sending malicious requests to a real service, of course). I picked <code class="language-plaintext highlighter-rouge">IPAddress.TryParse</code>
and <code class="language-plaintext highlighter-rouge">Uri.TryCreate</code> methods as my first potential targets. My reasoning was that if you are
running a web service, it seems very likely that you will be parsing IP addresses or URLs in
some way (either directly or indirectly by using the ASP.NET Core framework). Whether it was
just sheer luck or brilliant selection of targets, I quickly found bugs in both of these functions.
Fun fact: at this time, SharpFuzz still lacked the support fuzzing .NET Core standard library
assemblies, so I just copy-pasted the source code of <code class="language-plaintext highlighter-rouge">IPAddress</code> and <code class="language-plaintext highlighter-rouge">Uri</code> classes and created
my own assembly, because I was too eager to find interesting bugs.</p>

<p>What I discovered was that parsing some malformed IP addresses could terminate a process with
<code class="language-plaintext highlighter-rouge">AccessViolationException</code> (that’s one of the few exceptions that can’t be caught). Finding the bug
was only the first step towards a potential attack—the next step was to figure out how
to crash the remote service that was calling this function. Conceptually, this is very simple.
In practice, it’s quite time-consuming. You need to look for all usages of the function (in
this case, in ASP.NET Core source code), and determine which ones can receive user-controlled
inputs (for example, headers or body of the incoming HTTP request). One of my favorite tools
for this purpose is <a href="https://source.dot.net/">.NET Source Browser</a>. It allows you not only to
see the source code of all .NET classes, but to see all of their usages as well. After examining
hundreds of call chains ending in <code class="language-plaintext highlighter-rouge">IPAddress.TryParse</code>, I found this one (it later turned out to
be a winner):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ForwardedHeadersMiddleware.Invoke
  -&gt; ForwardedHeadersMiddleware.ApplyForwarders
    -&gt; IPEndPoint.TryParse
      -&gt; IPAddress.TryParse
</code></pre></div></div>

<p>I didn’t immediately understand the purpose of this middleware. After reading
the documentation, I learned that it’s used when you host your service behind
a reverse proxy. The forwarded headers middleware reads the headers such as
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For">X-Forwarded-For</a>
and sets the associated fields in <code class="language-plaintext highlighter-rouge">HttpContext</code>. You could use it like this:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">app</span><span class="p">.</span><span class="nf">UseForwardedHeaders</span><span class="p">(</span><span class="k">new</span> <span class="n">ForwardedHeadersOptions</span>
<span class="p">{</span>
  <span class="n">ForwardedHeaders</span> <span class="p">=</span> <span class="n">ForwardedHeaders</span><span class="p">.</span><span class="n">All</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Knowing this, crashing a remote service that is using this middleware becomes super
easy. All you need to do is open the TCP connection to the server and send the HTTP
request with the malicious <code class="language-plaintext highlighter-rouge">X-Forwarded-For</code> value:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">client</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">TcpClient</span><span class="p">(</span><span class="n">host</span><span class="p">,</span> <span class="m">80</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">stream</span> <span class="p">=</span> <span class="n">client</span><span class="p">.</span><span class="nf">GetStream</span><span class="p">();</span>

<span class="kt">var</span> <span class="n">request</span> <span class="p">=</span> <span class="s">$"GET / HTTP/1.1\r\nHost: </span><span class="p">{</span><span class="n">host</span><span class="p">}</span><span class="s">\r\nX-Forwarded-For: </span><span class="p">{</span><span class="n">ip</span><span class="p">}</span><span class="s">\r\n\r\n"</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">bytes</span> <span class="p">=</span> <span class="n">Encoding</span><span class="p">.</span><span class="n">UTF8</span><span class="p">.</span><span class="nf">GetBytes</span><span class="p">(</span><span class="n">request</span><span class="p">);</span>

<span class="n">stream</span><span class="p">.</span><span class="nf">Write</span><span class="p">(</span><span class="n">bytes</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="n">bytes</span><span class="p">.</span><span class="n">Length</span><span class="p">);</span>
</code></pre></div></div>

<p>If you are wondering why I didn’t use the regular <code class="language-plaintext highlighter-rouge">HttpClient</code>, it’s because it
restricts the values you can put in headers (these evil IP addresses had some
unusual characters in them). I also wanted to emphasize that you don’t need to
be limited by the programming language or the framework you are currently using.
Sometimes, the only thing you really need is the ability to send some bytes over
the network.</p>

<p>Of course, if you decide to start looking for vulnerabilities, please don’t test
them against production servers: confirm your findings locally, then report them
to service/library owners. If you find a denial-of-service bug in .NET Core, you
can even receive a bug bounty reward going
<a href="https://www.microsoft.com/en-us/msrc/bounty-dot-net-core">up to $5,000</a>!</p>

<h2 id="stackoverflowexception">StackOverflowException</h2>

<p>Using SharpFuzz, I also discovered many <code class="language-plaintext highlighter-rouge">StackOverflowException</code> bugs (quick reminder that
<code class="language-plaintext highlighter-rouge">StackOverflowException</code> terminates the process, so it’s a very effective denial-of-service
attack vector). After some time, I realized that I didn’t really need SharpFuzz for this
purpose—I could easily discover nested recursion bugs manually. You can find the results
of that effort in one of my previous posts:
<a href="/2023/02/20/stack-overflow-exception/">How safe are .NET serialization libraries against StackOverflowException</a>.</p>

<p>Now, I want to show you my process for finding such bugs. My stack overflow
research originally started with JSON format. With JSON, it’s easy to generate deeply
nested data: you can concatenate thousands of square/curly brackets in a loop and you
are done. With binary formats, it’s slightly more complicated (at least that’s what I
thought at first). My initial approach was to serialize nested data structures of
different depths and then check the difference between the outputs. This allowed me to
generate files that exceeded the recursion limits by simply copy-pasting the diff multiple
times. That approach worked reliably until I tried it on FlatBuffers. FlatBuffers is a
weird format where there are lot of internal pointers and offsets, so everything breaks
if you modify anything by hand. I didn’t really want to learn the format internals, so I
had to figure out a better way to generate deeply nested data.</p>

<p>When creating a thread in C#, you can specify its stack size. If you create a thread with
a large stack (hundreds of megabytes), you can use that thread to serialize highly recursive
data without triggering <code class="language-plaintext highlighter-rouge">StackOverflowException</code>. Once you have the serialized output, you
can try to crash the deserializer with it. It’s easy, generic, and doesn’t require any
knowledge about the format internals. Here’s the code:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">static</span> <span class="n">T</span> <span class="n">GenerateMaliciousData</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">Func</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">generator</span><span class="p">)</span>
<span class="p">{</span>
  <span class="n">T</span> <span class="n">data</span> <span class="p">=</span> <span class="k">default</span><span class="p">;</span>
  <span class="kt">var</span> <span class="n">thread</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Thread</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="n">data</span> <span class="p">=</span> <span class="nf">generator</span><span class="p">(),</span> <span class="m">100000000</span><span class="p">);</span> <span class="c1">// 100 MB stack</span>

  <span class="n">thread</span><span class="p">.</span><span class="nf">Start</span><span class="p">();</span>
  <span class="n">thread</span><span class="p">.</span><span class="nf">Join</span><span class="p">();</span>

  <span class="k">return</span> <span class="n">data</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>What’s funny is that I had known this fact about C# threads for 15 years, but it hadn’t
occurred to me to use it for this purpose until I hit the roadblock with FlatBuffers.</p>

<h2 id="conclusion">Conclusion</h2>

<p>I hope that you enjoyed my collection of tips and tricks for finding denial-of-service
vulnerabilities. They are not the most glamorous type of security bugs, but I love finding them,
so that’s more than enough for me. You don’t really need to do what everyone else is doing anyway,
so find what you enjoy and do it, whether it’s cryptography, reverse-engineering, finding remote
code execution vulnerabilities, or gardening. Have fun and report vulnerabilities responsibly!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[At least one person has asked me about my thought process when searching for denial-of-service vulnerabilities: how do I choose the target libraries, what specifically I look for in them, etc. “At least one person” meaning “exactly one person”, and that one person happens to be my wife, but I decided to write a whole post on this topic anyway! In this half-memoir, half-tutorial post, I’ll show you the most interesting bugs I’ve found so far and the methods I used to find them. I wouldn’t say I have any advanced bug finding skills, but I do know a few useful techniques, so I hope you will learn something new today. Finding a panic in the Go standard library If memory serves me right, my bug finding adventures started in July 2017. Fuzzing was a big thing back then, and I got attracted to it after reading the famous blog post Pulling JPEGs out of thin air. I was learning the Go programming language at the time and after I found this great article on go-fuzz, I finally decided to give fuzzing a try. The easiest way to get started is by fuzzing some library you are already familiar with. You can’t fuzz just any library, though: in general, the library you want to fuzz should ideally parse or deserialize the input parameters in some way. Or even simpler: if you can’t pass a random byte array (or something convertible to byte array, such as string) to the library, you can’t easily fuzz it. With these criteria in mind, I then selected two candidates: UniDoc and goftp. There was nothing special about them other than the fact I was using them at that time. You can read the detailed description of these fuzzing adventures in my post Going down the rabbit hole with go-fuzz. Fuzzing goftp turned out to be the most important milestone in my fuzzing career. I got lucky and discovered a panic (that’s just a fancy word Go people use to call a process crash) not in goftp itself, but in the Go standard library! Here is a seemingly innocent line of code that used to be able to kill your process: time.Parse("_2 Jan 06 15:04 MST", "4 --- 00 00:00 GMT") I filed the bug, and it was marked as a release blocker. If seems funny after all these years, but that made me really proud for some reason. In retrospect, had I reported this bug to Google security team, I could have easily earned a bug bounty reward and a CVE. But a more important thing came out of this: I was now hooked on fuzzing! Finding a panic in the Roughtime library A few years back, I was really into cryptography (proper cryptography, not the blockchain-scam-cryptocurrency type of crypto). I was solving The Cryptopals Crypto Challenges, implementing Noise Protocol Framework and AES-GCM-SIV, that kind of stuff. Somewhere around October 2017, I discovered Roughtime, a secure time synchronization protocol. Why was I interested in it? I have no idea, but it probably just looked cool (everything related to cryptography seemed fascinating back then). I wanted to implement this in C#, so I started reading the code, and somehow I immediately discovered a panic caused by out-of-bounds access (I wish I could recall my thought process from back then). I was extremely proud of this, because the Roughtime protocol implementation was written by none other than Adam Langley, one of the biggest cryptography experts in the world. Again, it seems silly from today’s perspective, but at that time, it meant the world to me. This time, I decided not only to report the bug, but to also become a Roughtime contributor, so I submitted the fix, and Adam Langley himself approved it. I was still not experienced at reporting bugs, so this one didn’t result in a CVE, either. I got this PR approval instead, and it was more than enough for me: Fuzzing .NET libraries with SharpFuzz In 2018, I wrote SharpFuzz, and it turned out to be one of the projects I’m most proud of. Five years later, it’s still the only coverage-guided fuzzing tool for .NET. You can read about the SharpFuzz and the motivation behind it in my previous posts. In this section, I’ll focus more on how to choose which libraries to fuzz and how to write the fuzzing code for them. General fuzzing advice still applies: simply fuzz a library you are already familiar with. Fuzzing works best with deserialization libraries, because parsing code is often highly complex and susceptible to bugs. Another important reason for fuzzing deserialization libraries is that you really can’t afford to have bugs in them. They are likely the first components you call to process user-provided inputs (for example, by parsing an HTTP request with JSON body), which means deserialization bugs can potentially have a catastrophic impact on your service: How StackOverflowException can bring down an expensive compute cluster. With this in mind, you can choose from dozens of candidate formats: JSON, XML, Protocol Buffers, MessagePack, HTML, YAML, Markdown, CSV, GraphQL, etc. Another interesting category of fuzzing targets contains the libraries that work with file formats: images, fonts, ZIP archives, PDF documents, etc. If you run out of targets, you can also find a few more candidates by looking at NuGet tags such as “serialization” or “parsing” (sorted by popularity if you want to find bugs with the biggest impact), or just Googling for “.NET serialization libraries”. After choosing the victim library, you need to write the fuzzing function. That’s often very easy, since most parsers/deserializers load their inputs from a string or a stream (and SharpFuzz supports both). Fuzzing Newtonsoft.Json? Fuzzer.OutOfProcess.Run(s =&gt; { JsonConvert.DeserializeObject(s); }); Fuzzing protobuf-net? Fuzzer.OutOfProcess.Run(stream =&gt; { Serializer.Deserialize&lt;Person&gt;(stream); }); Almost all popular libraries have some basic usage examples in their documentation, and typically you can use them directly in your fuzzing function (it worked for me in 99% of the cases). If you are still not motivated enough, here is one more attempt to convince you to try fuzzing: if the library has not been fuzzed before, you are very likely to find some bug in it, even if it’s just an undocumented exception. Finding bugs in the .NET Core standard library .NET Core standard library is extremely well written and covered with tons of tests. To successfully fuzz it, I needed to write more creative fuzzers and use some advanced tricks. For example, a bug in the library doesn’t necessarily mean you’ll get an unhandled exception or a process crash: a function may seemingly complete without errors, but it could still produce an incorrect result. Fuzzing can help you even in this scenario. Consider this fuzzing function: Fuzzer.LibFuzzer.Run(span =&gt; { string s1 = Encoding.UTF8.GetString(span); if (!double.TryParse(s1, out var d1) || double.IsNaN(d1)) { return; } var s2 = d1.ToString("G17"); var d2 = double.Parse(s2); if (d1 != d2) { throw new Exception(); } }); G17 format specifier is used to roundtrip floating-point numbers. That means if you serialize a value using this format, it guarantees that deserialization will return the original value. And how can fuzzing find bugs in the round-tripping code? Simply compare the original number with the deserialized one and throw an exception if they are different. Since fuzzer is instrumenting both serialization and deserialization code paths, it will likely find a mismatch if there is one. Say hello to this .NET Core bug: var s = "23723333333333333433333337"; double d1 = Double.Parse(s); double d2 = Double.Parse(d1.ToString("G17")); Console.WriteLine(d1 == d2); By design, this code snippet is supposed to print true. SharpFuzz discovered that on .NET Core 2.2, the result was actually false. This is just a single example of a more general fuzzing technique. For example, you can compare for equivalence two completely different libraries implementing the same functionality: call both implementations, compare the results, throw an exception if they are different, and fuzzer will try to find the inputs that can trigger this scenario. Finding CVEs After using SharpFuzz to find process crashes and hangs in multiple libraries, I realized that it would be way more fun to figure out how to use these bugs to kill a remote service (without sending malicious requests to a real service, of course). I picked IPAddress.TryParse and Uri.TryCreate methods as my first potential targets. My reasoning was that if you are running a web service, it seems very likely that you will be parsing IP addresses or URLs in some way (either directly or indirectly by using the ASP.NET Core framework). Whether it was just sheer luck or brilliant selection of targets, I quickly found bugs in both of these functions. Fun fact: at this time, SharpFuzz still lacked the support fuzzing .NET Core standard library assemblies, so I just copy-pasted the source code of IPAddress and Uri classes and created my own assembly, because I was too eager to find interesting bugs. What I discovered was that parsing some malformed IP addresses could terminate a process with AccessViolationException (that’s one of the few exceptions that can’t be caught). Finding the bug was only the first step towards a potential attack—the next step was to figure out how to crash the remote service that was calling this function. Conceptually, this is very simple. In practice, it’s quite time-consuming. You need to look for all usages of the function (in this case, in ASP.NET Core source code), and determine which ones can receive user-controlled inputs (for example, headers or body of the incoming HTTP request). One of my favorite tools for this purpose is .NET Source Browser. It allows you not only to see the source code of all .NET classes, but to see all of their usages as well. After examining hundreds of call chains ending in IPAddress.TryParse, I found this one (it later turned out to be a winner): ForwardedHeadersMiddleware.Invoke -&gt; ForwardedHeadersMiddleware.ApplyForwarders -&gt; IPEndPoint.TryParse -&gt; IPAddress.TryParse I didn’t immediately understand the purpose of this middleware. After reading the documentation, I learned that it’s used when you host your service behind a reverse proxy. The forwarded headers middleware reads the headers such as X-Forwarded-For and sets the associated fields in HttpContext. You could use it like this: app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.All }); Knowing this, crashing a remote service that is using this middleware becomes super easy. All you need to do is open the TCP connection to the server and send the HTTP request with the malicious X-Forwarded-For value: var client = new TcpClient(host, 80); var stream = client.GetStream(); var request = $"GET / HTTP/1.1\r\nHost: {host}\r\nX-Forwarded-For: {ip}\r\n\r\n"; var bytes = Encoding.UTF8.GetBytes(request); stream.Write(bytes, 0, bytes.Length); If you are wondering why I didn’t use the regular HttpClient, it’s because it restricts the values you can put in headers (these evil IP addresses had some unusual characters in them). I also wanted to emphasize that you don’t need to be limited by the programming language or the framework you are currently using. Sometimes, the only thing you really need is the ability to send some bytes over the network. Of course, if you decide to start looking for vulnerabilities, please don’t test them against production servers: confirm your findings locally, then report them to service/library owners. If you find a denial-of-service bug in .NET Core, you can even receive a bug bounty reward going up to $5,000! StackOverflowException Using SharpFuzz, I also discovered many StackOverflowException bugs (quick reminder that StackOverflowException terminates the process, so it’s a very effective denial-of-service attack vector). After some time, I realized that I didn’t really need SharpFuzz for this purpose—I could easily discover nested recursion bugs manually. You can find the results of that effort in one of my previous posts: How safe are .NET serialization libraries against StackOverflowException. Now, I want to show you my process for finding such bugs. My stack overflow research originally started with JSON format. With JSON, it’s easy to generate deeply nested data: you can concatenate thousands of square/curly brackets in a loop and you are done. With binary formats, it’s slightly more complicated (at least that’s what I thought at first). My initial approach was to serialize nested data structures of different depths and then check the difference between the outputs. This allowed me to generate files that exceeded the recursion limits by simply copy-pasting the diff multiple times. That approach worked reliably until I tried it on FlatBuffers. FlatBuffers is a weird format where there are lot of internal pointers and offsets, so everything breaks if you modify anything by hand. I didn’t really want to learn the format internals, so I had to figure out a better way to generate deeply nested data. When creating a thread in C#, you can specify its stack size. If you create a thread with a large stack (hundreds of megabytes), you can use that thread to serialize highly recursive data without triggering StackOverflowException. Once you have the serialized output, you can try to crash the deserializer with it. It’s easy, generic, and doesn’t require any knowledge about the format internals. Here’s the code: private static T GenerateMaliciousData&lt;T&gt;(Func&lt;T&gt; generator) { T data = default; var thread = new Thread(() =&gt; data = generator(), 100000000); // 100 MB stack thread.Start(); thread.Join(); return data; } What’s funny is that I had known this fact about C# threads for 15 years, but it hadn’t occurred to me to use it for this purpose until I hit the roadblock with FlatBuffers. Conclusion I hope that you enjoyed my collection of tips and tricks for finding denial-of-service vulnerabilities. They are not the most glamorous type of security bugs, but I love finding them, so that’s more than enough for me. You don’t really need to do what everyone else is doing anyway, so find what you enjoy and do it, whether it’s cryptography, reverse-engineering, finding remote code execution vulnerabilities, or gardening. Have fun and report vulnerabilities responsibly!]]></summary></entry><entry><title type="html">Five years of fuzzing .NET with SharpFuzz</title><link href="https://mijailovic.net/2023/07/23/sharpfuzz-anniversary/" rel="alternate" type="text/html" title="Five years of fuzzing .NET with SharpFuzz" /><published>2023-07-23T17:00:00+00:00</published><updated>2023-07-23T17:00:00+00:00</updated><id>https://mijailovic.net/2023/07/23/sharpfuzz-anniversary</id><content type="html" xml:base="https://mijailovic.net/2023/07/23/sharpfuzz-anniversary/"><![CDATA[<p>It’s been almost five years since I created
<a href="https://github.com/Metalnem/sharpfuzz">SharpFuzz</a>, the only .NET
coverage-guided fuzzer. I already have a blog post on how it
works, what it can do for you, and what bugs it found, so
check it out if this is the first time you hear about SharpFuzz:</p>

<p><a href="/2019/01/03/sharpfuzz/">SharpFuzz: Bringing the power of afl-fuzz to .NET platform</a></p>

<p>A lot of interesting things have happened since then. SharpFuzz now
works with libFuzzer, Windows, and .NET Framework. And it can finally
fuzz the .NET Core base-class library! The whole fuzzing process has
been dramatically simplified, too.</p>

<p>Not many people are aware of all these developments, so I decided
to write this anniversary blog post and showcase everything SharpFuzz
is currently capable of.</p>

<h2 id="trophies">Trophies</h2>

<p>The list of bugs found by SharpFuzz has been growing steadily and it now
contains more than 80 entries. I’m pretty confident that some of the bugs
in the .NET Core standard library would have been impossible to discover
using any other testing method:</p>

<ul>
  <li><a href="https://github.com/dotnet/runtime/issues/28652">BigInteger.TryParse out-of-bounds access</a></li>
  <li><a href="https://github.com/dotnet/runtime/issues/28872">Double.Parse throws AccessViolationException on .NET Core 3.0</a></li>
  <li><a href="https://github.com/dotnet/runtime/issues/28703">G17 format specifier doesn’t always round-trip double values</a></li>
</ul>

<p>As you can see, SharpFuzz is capable of finding not only crashes, but
also correctness bugs—the more creative you are in writing your fuzzing
functions, the higher your chances are for finding an interesting bug.</p>

<p>SharpFuzz can also find serious security vulnerabilities.
I now have two CVEs in my trophy collection:</p>

<ul>
  <li><a href="https://msrc.microsoft.com/update-guide/en-us/vulnerability/CVE-2019-0980">CVE-2019-0980: .NET Framework and .NET Core Denial of Service Vulnerability</a></li>
  <li><a href="https://msrc.microsoft.com/update-guide/en-us/vulnerability/CVE-2019-0981">CVE-2019-0981: .NET Framework and .NET Core Denial of Service Vulnerability</a></li>
</ul>

<p>If you were ever wondering if fuzzing managed languages makes sense,
I think you’ve got your answer right here.</p>

<h2 id="easier-fuzzing">Easier fuzzing</h2>

<p>Initial SharpFuzz usage instructions were
<a href="https://github.com/Metalnem/sharpfuzz/blob/master/docs/legacy-usage-instructions.md">unnecessarily complicated</a>
and full of error-prone, manual steps. I decided to completely rewrite these
guidelines and also write a PowerShell script that correctly configures
all parameters behind the scenes, making the whole fuzzing process way simpler.
You no longer have to choose which assembly to instrument, which memory limit
to use, or how to configure the execution timeout. You only need to:</p>

<ol>
  <li>Create the fuzzing project and write your fuzzing function.</li>
  <li>Create one or more test cases.</li>
  <li>Run the <a href="https://github.com/Metalnem/sharpfuzz/raw/master/scripts/fuzz.ps1">fuzz.ps1</a> script like this:</li>
</ol>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pwsh scripts/fuzz.ps1 YourFuzzingProject.csproj <span class="nt">-i</span> Testcases
</code></pre></div></div>

<p>That’s all! For more details, check out the brand-new
<a href="https://github.com/Metalnem/sharpfuzz/blob/master/README.md#usage">usage</a> section
in the <a href="https://github.com/Metalnem/sharpfuzz/blob/master/README.md">README</a> file.
The new fuzzing script has made fuzzing much easier for me, and I hope everyone
else will benefit from it, too.</p>

<h2 id="libfuzzer">libFuzzer</h2>

<p>In addition to AFL, SharpFuzz now supports <a href="https://llvm.org/docs/LibFuzzer.html">libFuzzer</a>
as a fuzzing engine. If you are interested in technical implementation details, check out the
<a href="https://github.com/Metalnem/libfuzzer-dotnet">libfuzzer-dotnet</a> repository. If you just want
to start using libFuzzer, usage instructions are available
<a href="https://github.com/Metalnem/sharpfuzz/blob/master/docs/libFuzzer.md">here</a>. AFL and libFuzzer
have similar capabilities, so on its own, using libFuzzer instead of AFL doesn’t really
bring you any benefits (it might be slightly easier to use, though, because you only have to
download a single binary and you are ready to go). However, libFuzzer support in SharpFuzz has
unlocked some exciting new possibilities: native Windows support and fuzzing .NET framework
libraries.</p>

<h2 id="windows">Windows</h2>

<p>After I finished writing the libFuzzer driver, I had completely forgotten about it.
I joined Microsoft later that year, and a few months into my Microsoft career, I stumbled
upon an internal fuzzing channel, where I discovered that there was a team in Microsoft
that was working on porting my SharpFuzz libFuzzer driver to Windows! They not only
wrote the port, but they were also happy to open source it. Huge thanks to Joe
Ranweiler and the MORSE team for what they have done—I’m deeply thankful for their
contribution, and I can’t emphasize enough how important it was for the SharpFuzz
users. Native Windows support has made fuzzing much more accessible: despite .NET
Core’s cross-platform success, most .NET users are probably still on Windows.</p>

<h2 id="net-framework">.NET Framework</h2>

<p>Everyone is moving to .NET Core these days. But the migration can be slow, and
there are many libraries that might never be ported to .NET Core. With
libFuzzer and Windows support, SharpFuzz can now fuzz .NET Framework libraries,
too! I didn’t have to do anything special to enable this feature, though:
SharpFuzz has been targeting .NET Standard since the beginning. The only thing
needed to support .NET Framework was the fuzzing engine that could run on Windows,
and as you know from the previous section, libFuzzer satisfies that requirement.</p>

<h2 id="fuzzing-net-core-standard-library">Fuzzing .NET Core standard library</h2>

<p>My initial attempts to fuzz the .NET Core standard library (aka base-class library or BCL) failed,
because I thought that modifying mixed-mode assemblies was impossible (and all official .NET assemblies
are built as mixed-mode assemblies). But I really wanted to find bugs in the .NET standard library, so
I ultimately figured out how to fuzz it by doing some godawful hacks like downloading the IL-only,
nightly packages from a special NuGet feed used by the .NET team, and building the .NET runtime repo
in managed-only mode (my eyes are now bleeding from trying to read the
<a href="https://github.com/Metalnem/sharpfuzz/blob/master/docs/fuzzing-dotnet-core.md">usage instructions</a>
I wrote about this procedure back in the day). Unfortunately, this solution was short-lived,
because the .NET team stopped publishing the IL-only packages. They also changed the way
.NET runtime was built, and I couldn’t figure out how to build it in managed-only mode
again. This marked the end of my .NET Core BCL fuzzing attempts.</p>

<p>…until a few weeks ago. I was sitting in a coffee shop with a renewed
enthusiasm for building the .NET runtime when it suddenly dawned on me: why do
I even think that instrumenting mixed-mode assemblies is impossible? What if I
<a href="https://sive.rs/unlearning">unlearned</a> that belief and started from scratch?
A few minutes later, I realized that the impossible was not only
<a href="https://github.com/0xd4d/dnlib/issues/305">possible</a> (at least on Windows),
but it took me only a few moments to implement. I still don’t really know how
mixed-mode assemblies work, but I do know that I can easily strip the native code
from them, and nothing will break. It’s funny how sometimes you need to wait for
years for things to fall into place.</p>

<p>If you want to get started with fuzzing the .NET BCL, you can find plenty of
examples in my <a href="https://github.com/Metalnem/dotnet-fuzzers">dotnet-fuzzers</a>
repo (contributions are welcome). Everything you need to get started is there:
fuzzing projects, dictionaries, and PowerShell commands. For example, if you want to fuzz
the <a href="https://learn.microsoft.com/en-us/dotnet/api/system.uri.trycreate">Uri.TryCreate</a>
method, you simply need to clone the repo and run the following script:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">.</span><span class="n">\fuzz.ps1</span><span class="w"> </span><span class="se">`
</span><span class="w">  </span><span class="nt">-project</span><span class="w"> </span><span class="o">.</span><span class="nx">\src\UriFuzzer\UriFuzzer.csproj</span><span class="w"> </span><span class="se">`
</span><span class="w">  </span><span class="nt">-corpus</span><span class="w"> </span><span class="o">.</span><span class="nx">\src\UriFuzzer\Testcases\</span><span class="w"> </span><span class="se">`
</span><span class="w">  </span><span class="nt">-targetDlls</span><span class="w"> </span><span class="nx">System.Private.Uri.dll</span><span class="w">
</span></code></pre></div></div>

<p>Unfortunately for us bug finders, the .NET team has been doing a really great job in
recent years, so discovering new bugs in .NET Core has become more difficult. Every time
I thought “there must be a bug here”, I was disappointed because the code turned out to be
<a href="https://github.com/dotnet/designs/blob/main/accepted/2020/asnreader/asnreader.md">remarkably well designed</a>.
Well done, .NET team, I hope you are happy for basically ruining my life.</p>

<h2 id="community-support">Community support</h2>

<p>It makes me really happy to see so many awesome people participating in the SharpFuzz
development. I’m profoundly grateful to <a href="https://github.com/ranweiler">Joe Ranweiler</a> and the
<a href="https://news.microsoft.com/source/features/innovation/morse-microsoft-offensive-research-security-engineering/">MORSE</a>
team, <a href="https://github.com/gfoidl">Günther Foidl</a>, <a href="https://github.com/ufo95">Ulrich Fourier</a>,
<a href="https://github.com/Porges">George Pollard</a>, and <a href="https://github.com/p4fg">p4fg</a> for their contributions.
Some people have written <a href="http://writeasync.net/?p=5714">blog posts</a> about SharpFuzz,
and it was even a topic of a <a href="https://eprints.ost.ch/id/eprint/934/1/HS%202020%202021-SA-EP-PREMANANTHAN-SUNDRALINGAM-Moro-Visual%20Computing%20%20AR%20%20%20App.pdf">research project</a>
(it’s in German, though). Thank you all for being such a great community!</p>

<h2 id="conclusion">Conclusion</h2>

<p>After taking a break from fuzzing for several years, I’m again actively
working on SharpFuzz. There is still plenty of important and fun work in
.NET fuzzing (for example, ASP.NET Core fuzzing and
<a href="https://github.com/google/fuzzing/blob/master/docs/structure-aware-fuzzing.md">structure-aware fuzzing</a>),
so expect more posts in the future. In the meantime, enjoy fuzzing!</p>

<p><small><i>Big thanks to my loyal sidekick Milica Miljkov for her continuous support in my blogging efforts.</i></small></p>]]></content><author><name></name></author><summary type="html"><![CDATA[It’s been almost five years since I created SharpFuzz, the only .NET coverage-guided fuzzer. I already have a blog post on how it works, what it can do for you, and what bugs it found, so check it out if this is the first time you hear about SharpFuzz: SharpFuzz: Bringing the power of afl-fuzz to .NET platform A lot of interesting things have happened since then. SharpFuzz now works with libFuzzer, Windows, and .NET Framework. And it can finally fuzz the .NET Core base-class library! The whole fuzzing process has been dramatically simplified, too. Not many people are aware of all these developments, so I decided to write this anniversary blog post and showcase everything SharpFuzz is currently capable of. Trophies The list of bugs found by SharpFuzz has been growing steadily and it now contains more than 80 entries. I’m pretty confident that some of the bugs in the .NET Core standard library would have been impossible to discover using any other testing method: BigInteger.TryParse out-of-bounds access Double.Parse throws AccessViolationException on .NET Core 3.0 G17 format specifier doesn’t always round-trip double values As you can see, SharpFuzz is capable of finding not only crashes, but also correctness bugs—the more creative you are in writing your fuzzing functions, the higher your chances are for finding an interesting bug. SharpFuzz can also find serious security vulnerabilities. I now have two CVEs in my trophy collection: CVE-2019-0980: .NET Framework and .NET Core Denial of Service Vulnerability CVE-2019-0981: .NET Framework and .NET Core Denial of Service Vulnerability If you were ever wondering if fuzzing managed languages makes sense, I think you’ve got your answer right here. Easier fuzzing Initial SharpFuzz usage instructions were unnecessarily complicated and full of error-prone, manual steps. I decided to completely rewrite these guidelines and also write a PowerShell script that correctly configures all parameters behind the scenes, making the whole fuzzing process way simpler. You no longer have to choose which assembly to instrument, which memory limit to use, or how to configure the execution timeout. You only need to: Create the fuzzing project and write your fuzzing function. Create one or more test cases. Run the fuzz.ps1 script like this: pwsh scripts/fuzz.ps1 YourFuzzingProject.csproj -i Testcases That’s all! For more details, check out the brand-new usage section in the README file. The new fuzzing script has made fuzzing much easier for me, and I hope everyone else will benefit from it, too. libFuzzer In addition to AFL, SharpFuzz now supports libFuzzer as a fuzzing engine. If you are interested in technical implementation details, check out the libfuzzer-dotnet repository. If you just want to start using libFuzzer, usage instructions are available here. AFL and libFuzzer have similar capabilities, so on its own, using libFuzzer instead of AFL doesn’t really bring you any benefits (it might be slightly easier to use, though, because you only have to download a single binary and you are ready to go). However, libFuzzer support in SharpFuzz has unlocked some exciting new possibilities: native Windows support and fuzzing .NET framework libraries. Windows After I finished writing the libFuzzer driver, I had completely forgotten about it. I joined Microsoft later that year, and a few months into my Microsoft career, I stumbled upon an internal fuzzing channel, where I discovered that there was a team in Microsoft that was working on porting my SharpFuzz libFuzzer driver to Windows! They not only wrote the port, but they were also happy to open source it. Huge thanks to Joe Ranweiler and the MORSE team for what they have done—I’m deeply thankful for their contribution, and I can’t emphasize enough how important it was for the SharpFuzz users. Native Windows support has made fuzzing much more accessible: despite .NET Core’s cross-platform success, most .NET users are probably still on Windows. .NET Framework Everyone is moving to .NET Core these days. But the migration can be slow, and there are many libraries that might never be ported to .NET Core. With libFuzzer and Windows support, SharpFuzz can now fuzz .NET Framework libraries, too! I didn’t have to do anything special to enable this feature, though: SharpFuzz has been targeting .NET Standard since the beginning. The only thing needed to support .NET Framework was the fuzzing engine that could run on Windows, and as you know from the previous section, libFuzzer satisfies that requirement. Fuzzing .NET Core standard library My initial attempts to fuzz the .NET Core standard library (aka base-class library or BCL) failed, because I thought that modifying mixed-mode assemblies was impossible (and all official .NET assemblies are built as mixed-mode assemblies). But I really wanted to find bugs in the .NET standard library, so I ultimately figured out how to fuzz it by doing some godawful hacks like downloading the IL-only, nightly packages from a special NuGet feed used by the .NET team, and building the .NET runtime repo in managed-only mode (my eyes are now bleeding from trying to read the usage instructions I wrote about this procedure back in the day). Unfortunately, this solution was short-lived, because the .NET team stopped publishing the IL-only packages. They also changed the way .NET runtime was built, and I couldn’t figure out how to build it in managed-only mode again. This marked the end of my .NET Core BCL fuzzing attempts. …until a few weeks ago. I was sitting in a coffee shop with a renewed enthusiasm for building the .NET runtime when it suddenly dawned on me: why do I even think that instrumenting mixed-mode assemblies is impossible? What if I unlearned that belief and started from scratch? A few minutes later, I realized that the impossible was not only possible (at least on Windows), but it took me only a few moments to implement. I still don’t really know how mixed-mode assemblies work, but I do know that I can easily strip the native code from them, and nothing will break. It’s funny how sometimes you need to wait for years for things to fall into place. If you want to get started with fuzzing the .NET BCL, you can find plenty of examples in my dotnet-fuzzers repo (contributions are welcome). Everything you need to get started is there: fuzzing projects, dictionaries, and PowerShell commands. For example, if you want to fuzz the Uri.TryCreate method, you simply need to clone the repo and run the following script: .\fuzz.ps1 ` -project .\src\UriFuzzer\UriFuzzer.csproj ` -corpus .\src\UriFuzzer\Testcases\ ` -targetDlls System.Private.Uri.dll Unfortunately for us bug finders, the .NET team has been doing a really great job in recent years, so discovering new bugs in .NET Core has become more difficult. Every time I thought “there must be a bug here”, I was disappointed because the code turned out to be remarkably well designed. Well done, .NET team, I hope you are happy for basically ruining my life. Community support It makes me really happy to see so many awesome people participating in the SharpFuzz development. I’m profoundly grateful to Joe Ranweiler and the MORSE team, Günther Foidl, Ulrich Fourier, George Pollard, and p4fg for their contributions. Some people have written blog posts about SharpFuzz, and it was even a topic of a research project (it’s in German, though). Thank you all for being such a great community! Conclusion After taking a break from fuzzing for several years, I’m again actively working on SharpFuzz. There is still plenty of important and fun work in .NET fuzzing (for example, ASP.NET Core fuzzing and structure-aware fuzzing), so expect more posts in the future. In the meantime, enjoy fuzzing! Big thanks to my loyal sidekick Milica Miljkov for her continuous support in my blogging efforts.]]></summary></entry><entry><title type="html">How StackOverflowException can bring down an expensive compute cluster</title><link href="https://mijailovic.net/2023/04/01/denial-of-service/" rel="alternate" type="text/html" title="How StackOverflowException can bring down an expensive compute cluster" /><published>2023-04-01T08:30:00+00:00</published><updated>2023-04-01T08:30:00+00:00</updated><id>https://mijailovic.net/2023/04/01/denial-of-service</id><content type="html" xml:base="https://mijailovic.net/2023/04/01/denial-of-service/"><![CDATA[<p>In my <a href="/2023/02/20/stack-overflow-exception/">previous post</a> I talked about the
dangers of <code class="language-plaintext highlighter-rouge">StackOverflowException</code>. I also promised to show you how denial of service looks like in real world.
Today, I’m delivering on that promise! Let’s start by answering the fundamental question: how does a process
crash lead to denial of service in the first place?</p>

<h2 id="theoretical-explanation">Theoretical explanation</h2>

<p>Suppose you are running an HTTP web service and an attacker sends you a payload that will crash your server
process. What happens next?</p>

<p>First of all, crashing the server process will also terminate all in-flight HTTP requests, which obviously
means the clients will be disconnected and won’t get any response. More importantly, you can’t instantaneously
replace the old, dead process—it’s very likely that the new process will need at least a few seconds to initialize.
During this period your service is as good as dead, because there is nothing handling the incoming HTTP requests. What
happens when the process is finally ready? Some lucky requests might complete successfully (although with high latency,
because they were waiting for the server to restart), but the next malicious request is going to kill the process again,
and the whole dance repeats.</p>

<p>You might say “Meh, I have a massive fleet of virtual machines, no one can hurt me. If a single instance goes down, my
load balancer will just take it out of rotation—healthy instances will continue to handle the traffic.” Well, the load
balancer will indeed send traffic only to healthy instances, but this also means they’ll be up for grabs: attacker can
easily bring all your machines down one by one and keep them in the infinite process restart loop.</p>

<p>But all this is just a theory. Is it really that easy to bring a big cluster of virtual machines down to its knees?
Let’s find out!</p>

<h2 id="the-setup">The setup</h2>

<p>We need three basic components to perform a proper denial of service attack:</p>

<ol>
  <li>Vulnerable service running on multiple machines</li>
  <li>Load test to simulate the real users</li>
  <li>Attacker sending the malicious requests</li>
</ol>

<p>Let’s describe each of these in more detail.</p>

<h4 id="1-vulnerable-service"><em>1. Vulnerable service</em></h4>

<p>We want to match the real-world conditions, which means that our service should run on multiple instances
behind the load balancer (it’s technically possible to perform the attack against a single virtual machine,
but that wouldn’t be particularly fun or impressive—the goal of this post is to kill a large-scale service).
My compute technology of choice was Azure Functions, mainly because I already knew how to use Azure Functions (also,
I would rather blow my brains out than try to set up a Kubernetes cluster). Instead of using the default
<a href="https://learn.microsoft.com/en-us/azure/azure-functions/consumption-plan">Consumption plan</a>, which adds or
removes instances based on the number of incoming requests, I decided to use the
<a href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-premium-plan">Premium plan</a>. I though the
Premium plan would be a better fit because:</p>

<ul>
  <li>It eliminates cold starts with always ready instances, making the simulation more predictable.</li>
  <li>If offers more instance types (the more powerful the machines are, the more embarrassing their
downfall would be). My choice was, of course, the most expensive one: <code class="language-plaintext highlighter-rouge">Elastic Premium EP3</code>, with 840 total
<a href="https://learn.microsoft.com/en-us/azure/virtual-machines/acu">ACU</a> (whatever the hell that means).</li>
</ul>

<p>For the cluster size, ten instances seemed good enough—reasonably big to show the impact on a real
production cluster, but not too big to surprise me with a massive Azure bill. Estimated cost: 5,200 EUR/month.
Of course, I’m not crazy, and I didn’t keep this whole setup for the whole month (and at 7 EUR/hour, I had to
run my experiments pretty efficiently).</p>

<h4 id="2-load-test"><em>2. Load test</em></h4>

<p>Before looking at how denial of service attack degrades the user experience, we need to determine what’s
the expected service performance. We don’t have real users, but we do have load tests, and my load testing
tool of choice was <a href="https://github.com/giltene/wrk2">wrk2</a>. It’s easy to use (I dare you to try using
<a href="https://gatling.io/open-source/">Gatling</a>), produces constant throughput load, and accurately measures
latency percentiles. If you want to learn more about why I chose <code class="language-plaintext highlighter-rouge">wrk2</code>, you can watch one of my favorite
talks ever: <a href="https://www.youtube.com/watch?v=lJ8ydIuPFeU">How NOT to Measure Latency</a> (if you haven’t
watched it yet, please do it right now, I’ll wait—it’s way more important than this blog post anyway).</p>

<p>I wanted to use a dedicated virtual machine for load testing, so I selected
<a href="https://learn.microsoft.com/en-us/azure/virtual-machines/dv5-dsv5-series">Standard_D16s_v5</a>
(16 VCPUS, 64 GiB memory), the largest instance type available without requesting a quota increase.</p>

<h4 id="3-attacker"><em>3. Attacker</em></h4>

<p>We have the service and we have the users—the final piece of the puzzle is to ruin their experience. This
was the easiest part of the whole setup: I just had to repeatedly crash the service with malicious requests.
I didn’t want to send too many requests (just to prove that you don’t need crazy computational power or
network bandwidth), so I decided to send one request every millisecond. The code itself is nothing fancy, but if you are
interested, it’s available <a href="https://github.com/Metalnem/denial-of-service/blob/main/Client/Program.cs">here</a>.</p>

<h2 id="load-testing-the-healthy-function">Load testing the healthy function</h2>

<p>Now that all the components are ready, it’s time to establish our baseline: how much traffic can the healthy
cluster handle? I didn’t know what’s the best way to choose the values of <code class="language-plaintext highlighter-rouge">wrk2</code> parameters, so I just played
with all of them until I settled on 16 threads and 256 open HTTP connections. I wanted each instance to be able
to process at least 1,000 requests per second (pretty arbitrary and not something to brag about, but good enough
for the purposes of this blog post), so I configured <code class="language-plaintext highlighter-rouge">wrk2</code> with the constant throughput of 10,000 requests per
second. Here are the results:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wrk -t16 -c256 -d600 -R10000 -L -v $url

 Latency Distribution (HdrHistogram - Recorded Latency)
 50.000%   13.56ms
 75.000%   15.33ms
 90.000%   18.75ms
 99.000%   41.22ms
 99.900%  123.46ms
 99.990%  270.85ms
 99.999%  358.40ms
100.000%  523.52ms

  5997762 requests in 10.00m, 817.95MB read
Requests/sec:   9996.22
Transfer/sec:      1.36MB
</code></pre></div></div>

<p><img src="/assets/img/2023-04-01-baseline.png" alt="" /></p>

<p>P99 was 41ms, and even the slowest request took only half a second, so I declared victory: the cluster was successfully
handling 10,000 requests per second, officially joining the <a href="https://www.youtube.com/watch?v=b2F-DItXtZs">web scale</a>
club. Nowadays, it’s also popular to artificially inflate the traffic numbers by showing them as a number of monthly
requests, so I’ll do that, too: my service could handle 25.92 billion HMR (hypothetical monthly requests)!</p>

<h2 id="denial-of-service">Denial of service</h2>

<p>Now the real fun begins: let’s add the attacker to the story. Users are still calling the service at the same rate
(10,000 requests per second), but the attacker is now joining them with 1,000 malicious requests every second (at a
constant rate of one request every millisecond). How will that affect our cluster and the overall user experience?</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wrk -t16 -c256 -d600 -R10000 -L -v $url

  6947 requests in 30.10s, 1.00MB read
  Socket errors: connect 0, read 0, write 0, timeout 3197
  Non-2xx or 3xx responses: 4248
Requests/sec:    230.83
Transfer/sec:     34.04KB
</code></pre></div></div>

<p>Oops. Healthy cluster was capable of handling 10,000 requests per second. Under denial of service attack, it could
barely handle 230 requests per second. If you are a visual person, I have a game just for you—let’s play <em>Spot the
denial of service attack™</em>:</p>

<p><img src="/assets/img/2023-04-01-denial-of-service.png" alt="" /></p>

<p>You still might say “230 requests per second is still better than nothing.” Is it really? Let’s check the latency
distribution:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  Latency Distribution (HdrHistogram - Recorded Latency)
 50.000%   16.32s 
 75.000%   20.58s 
 90.000%   21.87s 
 99.000%   27.33s 
 99.900%   27.80s 
 99.990%   28.05s 
 99.999%   28.67s 
100.000%   28.67s 

  Detailed Percentile spectrum:
       Value   Percentile   TotalCount 1/(1-Percentile)
    8863.743     0.000000            1         1.00
   13172.735     0.100000          677         1.11
   14704.639     0.200000         1359         1.25
   15253.503     0.300000         2031         1.43
   15671.295     0.400000         2707         1.67
   16318.463     0.500000         3385         2.00
</code></pre></div></div>

<p>Ouch. For 90% of <em>successful</em> requests, latency exceeded 13 seconds. Not only that, but the <em>fastest</em> request
took almost 9 seconds! Our 5,200 EUR/month cluster, previously capable of handling at least 10,000 requests
per second, has now been rendered completely useless.</p>

<h2 id="conclusion">Conclusion</h2>

<p>If you are deserializing user-controlled data using a library vulnerable to <code class="language-plaintext highlighter-rouge">StackOverflowException</code>,
nothing can stop malicious users from bringing down your expensive compute cluster. To make matters worse,
my experiments show only the best-case scenario: my service didn’t have any initialization code, so every
process restart was relatively fast. In real world, you are most likely initializing database connections,
loading configuration files, and doing many other things that take time. All of this would make denial of
service worse and slow down the recovery even further.</p>

<p>I hope you enjoyed my most expensive post ever (my Azure bill for the last month was 66 EUR)! I still
haven’t finished exploring the topic of stack overflow, so stay tuned for more posts in this series.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[In my previous post I talked about the dangers of StackOverflowException. I also promised to show you how denial of service looks like in real world. Today, I’m delivering on that promise! Let’s start by answering the fundamental question: how does a process crash lead to denial of service in the first place? Theoretical explanation Suppose you are running an HTTP web service and an attacker sends you a payload that will crash your server process. What happens next? First of all, crashing the server process will also terminate all in-flight HTTP requests, which obviously means the clients will be disconnected and won’t get any response. More importantly, you can’t instantaneously replace the old, dead process—it’s very likely that the new process will need at least a few seconds to initialize. During this period your service is as good as dead, because there is nothing handling the incoming HTTP requests. What happens when the process is finally ready? Some lucky requests might complete successfully (although with high latency, because they were waiting for the server to restart), but the next malicious request is going to kill the process again, and the whole dance repeats. You might say “Meh, I have a massive fleet of virtual machines, no one can hurt me. If a single instance goes down, my load balancer will just take it out of rotation—healthy instances will continue to handle the traffic.” Well, the load balancer will indeed send traffic only to healthy instances, but this also means they’ll be up for grabs: attacker can easily bring all your machines down one by one and keep them in the infinite process restart loop. But all this is just a theory. Is it really that easy to bring a big cluster of virtual machines down to its knees? Let’s find out! The setup We need three basic components to perform a proper denial of service attack: Vulnerable service running on multiple machines Load test to simulate the real users Attacker sending the malicious requests Let’s describe each of these in more detail. 1. Vulnerable service We want to match the real-world conditions, which means that our service should run on multiple instances behind the load balancer (it’s technically possible to perform the attack against a single virtual machine, but that wouldn’t be particularly fun or impressive—the goal of this post is to kill a large-scale service). My compute technology of choice was Azure Functions, mainly because I already knew how to use Azure Functions (also, I would rather blow my brains out than try to set up a Kubernetes cluster). Instead of using the default Consumption plan, which adds or removes instances based on the number of incoming requests, I decided to use the Premium plan. I though the Premium plan would be a better fit because: It eliminates cold starts with always ready instances, making the simulation more predictable. If offers more instance types (the more powerful the machines are, the more embarrassing their downfall would be). My choice was, of course, the most expensive one: Elastic Premium EP3, with 840 total ACU (whatever the hell that means). For the cluster size, ten instances seemed good enough—reasonably big to show the impact on a real production cluster, but not too big to surprise me with a massive Azure bill. Estimated cost: 5,200 EUR/month. Of course, I’m not crazy, and I didn’t keep this whole setup for the whole month (and at 7 EUR/hour, I had to run my experiments pretty efficiently). 2. Load test Before looking at how denial of service attack degrades the user experience, we need to determine what’s the expected service performance. We don’t have real users, but we do have load tests, and my load testing tool of choice was wrk2. It’s easy to use (I dare you to try using Gatling), produces constant throughput load, and accurately measures latency percentiles. If you want to learn more about why I chose wrk2, you can watch one of my favorite talks ever: How NOT to Measure Latency (if you haven’t watched it yet, please do it right now, I’ll wait—it’s way more important than this blog post anyway). I wanted to use a dedicated virtual machine for load testing, so I selected Standard_D16s_v5 (16 VCPUS, 64 GiB memory), the largest instance type available without requesting a quota increase. 3. Attacker We have the service and we have the users—the final piece of the puzzle is to ruin their experience. This was the easiest part of the whole setup: I just had to repeatedly crash the service with malicious requests. I didn’t want to send too many requests (just to prove that you don’t need crazy computational power or network bandwidth), so I decided to send one request every millisecond. The code itself is nothing fancy, but if you are interested, it’s available here. Load testing the healthy function Now that all the components are ready, it’s time to establish our baseline: how much traffic can the healthy cluster handle? I didn’t know what’s the best way to choose the values of wrk2 parameters, so I just played with all of them until I settled on 16 threads and 256 open HTTP connections. I wanted each instance to be able to process at least 1,000 requests per second (pretty arbitrary and not something to brag about, but good enough for the purposes of this blog post), so I configured wrk2 with the constant throughput of 10,000 requests per second. Here are the results: wrk -t16 -c256 -d600 -R10000 -L -v $url Latency Distribution (HdrHistogram - Recorded Latency) 50.000% 13.56ms 75.000% 15.33ms 90.000% 18.75ms 99.000% 41.22ms 99.900% 123.46ms 99.990% 270.85ms 99.999% 358.40ms 100.000% 523.52ms 5997762 requests in 10.00m, 817.95MB read Requests/sec: 9996.22 Transfer/sec: 1.36MB P99 was 41ms, and even the slowest request took only half a second, so I declared victory: the cluster was successfully handling 10,000 requests per second, officially joining the web scale club. Nowadays, it’s also popular to artificially inflate the traffic numbers by showing them as a number of monthly requests, so I’ll do that, too: my service could handle 25.92 billion HMR (hypothetical monthly requests)! Denial of service Now the real fun begins: let’s add the attacker to the story. Users are still calling the service at the same rate (10,000 requests per second), but the attacker is now joining them with 1,000 malicious requests every second (at a constant rate of one request every millisecond). How will that affect our cluster and the overall user experience? wrk -t16 -c256 -d600 -R10000 -L -v $url 6947 requests in 30.10s, 1.00MB read Socket errors: connect 0, read 0, write 0, timeout 3197 Non-2xx or 3xx responses: 4248 Requests/sec: 230.83 Transfer/sec: 34.04KB Oops. Healthy cluster was capable of handling 10,000 requests per second. Under denial of service attack, it could barely handle 230 requests per second. If you are a visual person, I have a game just for you—let’s play Spot the denial of service attack™: You still might say “230 requests per second is still better than nothing.” Is it really? Let’s check the latency distribution: Latency Distribution (HdrHistogram - Recorded Latency) 50.000% 16.32s 75.000% 20.58s 90.000% 21.87s 99.000% 27.33s 99.900% 27.80s 99.990% 28.05s 99.999% 28.67s 100.000% 28.67s Detailed Percentile spectrum: Value Percentile TotalCount 1/(1-Percentile) 8863.743 0.000000 1 1.00 13172.735 0.100000 677 1.11 14704.639 0.200000 1359 1.25 15253.503 0.300000 2031 1.43 15671.295 0.400000 2707 1.67 16318.463 0.500000 3385 2.00 Ouch. For 90% of successful requests, latency exceeded 13 seconds. Not only that, but the fastest request took almost 9 seconds! Our 5,200 EUR/month cluster, previously capable of handling at least 10,000 requests per second, has now been rendered completely useless. Conclusion If you are deserializing user-controlled data using a library vulnerable to StackOverflowException, nothing can stop malicious users from bringing down your expensive compute cluster. To make matters worse, my experiments show only the best-case scenario: my service didn’t have any initialization code, so every process restart was relatively fast. In real world, you are most likely initializing database connections, loading configuration files, and doing many other things that take time. All of this would make denial of service worse and slow down the recovery even further. I hope you enjoyed my most expensive post ever (my Azure bill for the last month was 66 EUR)! I still haven’t finished exploring the topic of stack overflow, so stay tuned for more posts in this series.]]></summary></entry><entry><title type="html">How safe are .NET serialization libraries against StackOverflowException</title><link href="https://mijailovic.net/2023/02/20/stack-overflow-exception/" rel="alternate" type="text/html" title="How safe are .NET serialization libraries against StackOverflowException" /><published>2023-02-20T18:20:00+00:00</published><updated>2023-02-20T18:20:00+00:00</updated><id>https://mijailovic.net/2023/02/20/stack-overflow-exception</id><content type="html" xml:base="https://mijailovic.net/2023/02/20/stack-overflow-exception/"><![CDATA[<p>I have always been fascinated by .NET’s
<a href="https://learn.microsoft.com/en-us/dotnet/api/system.stackoverflowexception?view=net-7.0">StackOverflowException</a>.
It’s interesting because it’s fundamentally different from most other exceptions—you can’t catch it with
a <code class="language-plaintext highlighter-rouge">try/catch</code> block. When you overflow the stack, it’s game over—the runtime will terminate your process.
This behavior is especially devastating for web services that are deserializing user-controlled data. If
your data structure allows recursion, malicious users can easily craft a highly nested payload and use it
to DDoS your website.</p>

<p>Serialization libraries have the power to protect you against this type of DDoS attack by limiting the
recursion depth during deserialization. That’s exactly what libraries designed with security in mind have
always been doing. But they are outliers—most libraries were initially vulnerable to <code class="language-plaintext highlighter-rouge">StackOverflowException</code>
(and some of them still are). In this post, I’ll review the most widely used .NET serialization libraries
and show you how they fare against this mighty enemy. You’ll learn which library versions are safe to use,
which serializers require special usage patterns, and which libraries you should simply avoid.</p>

<p><img src="/assets/img/2023-02-20-stack-overflow.png" alt="" /></p>

<h2 id="safe-by-design">Safe by design</h2>

<p><strong><a href="https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/how-to?pivots=dotnet-7-0">System.Text.Json</a></strong> and <strong><a href="https://www.nuget.org/packages/Google.Protobuf/">Google.Protobuf</a></strong> are the absolute winners. They have
never been vulnerable to <code class="language-plaintext highlighter-rouge">StackOverflowException</code>, because they have always been enforcing the recursion limit by default.
This limit is configurable, though, so nothing can prevent you from intentionally increasing it. But nothing can prevent
you from trying to live with grizzly bears, either—it’s just a question of your lifestyle choices.</p>

<h2 id="previously-vulnerable-now-safe-by-default">Previously vulnerable, now safe by default</h2>

<p><strong><a href="https://www.newtonsoft.com/json">Newtonsoft.Json</a></strong> is by far the most popular .NET library, with over 2.8B total downloads on NuGet.
Despite its enormous popularity, it was only last year that its insecure defaults were fixed
(see <a href="https://github.com/advisories/GHSA-5crp-9r3c-p9vr">GHSA-5crp-9r3c-p9vr</a> for more details). The vulnerability was
not as bad as it could have been, because you always had the option to control the recursion depth by setting the
<code class="language-plaintext highlighter-rouge">MaxDepth</code> property in <code class="language-plaintext highlighter-rouge">JsonSerializerSettings</code> (though I doubt many people were doing that). ASP.NET Core users were
not even at risk—
<a href="https://learn.microsoft.com/en-us/aspnet/core/web-api/advanced/formatting?view=aspnetcore-7.0#add-newtonsoftjson-based-json-format-support-2">Newtonsoft.Json formatter</a>
has always been safe by default. Long story short, you were vulnerable only if you were doing something like this:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">T</span> <span class="k">value</span> <span class="p">=</span> <span class="n">JsonConvert</span><span class="p">.</span><span class="nf">DeserializeObject</span><span class="p">(</span><span class="n">s</span><span class="p">);</span>
</code></pre></div></div>

<p>If you’ve been keeping your libraries up to date, even this is no longer an issue.</p>

<p><strong><a href="https://github.com/jamescourtney/FlatSharp">FlatSharp</a></strong> (<a href="https://google.github.io/flatbuffers/index.html">FlatBuffers</a> implementation)
and <strong><a href="https://github.com/protobuf-net/protobuf-net">protobuf-net</a></strong> (<a href="https://protobuf.dev/">Protocol Buffers</a> implementation) were also
unsafe by default. Unlike <code class="language-plaintext highlighter-rouge">Newtonsoft.Json</code>, these two libraries were 100% vulnerable: there was no option
for users to explicitly set the recursion limit. Yours truly discovered these issues and reported them to both
library authors. <a href="https://github.com/jamescourtney">James Courtney</a> and <a href="https://github.com/mgravell">Marc Gravell</a>
quickly responded to my reports and immediately published the fixes that made these two libraries safe by default.
Huge thanks to James and Marc for keeping the .NET ecosystem safe!</p>

<h2 id="unsafe-by-default-but-can-be-configured-for-safe-use">Unsafe by default, but can be configured for safe use</h2>

<p><strong><a href="https://learn.microsoft.com/en-us/dotnet/api/system.xml.serialization.xmlserializer?view=net-7.0">System.Xml.XmlSerializer</a></strong> from the .NET standard library can be used safely, but
figuring out how to do that resembles finding a needle in a haystack. If you are learning how to use the
library by following the official documentation, you will almost certainly write something like this:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">serializer</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">XmlSerializer</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">T</span><span class="p">));</span>
<span class="n">T</span> <span class="k">value</span> <span class="p">=</span> <span class="p">(</span><span class="n">T</span><span class="p">)</span><span class="n">serializer</span><span class="p">.</span><span class="nf">Deserialize</span><span class="p">(</span><span class="n">stream</span><span class="p">);</span>
</code></pre></div></div>

<p>Congratulations, you are now vulnerable to <code class="language-plaintext highlighter-rouge">StackOverflowException</code>! Let’s say you decide to up your game by using
some fancy code quality rules.
<a href="https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca5369">CA5369: Use XmlReader for Deserialize</a>
comes to rescue with the instructions how to securely deserialize XML:</p>

<blockquote>
  <p>Deserializing untrusted XML input with XmlSerializer.Deserialize instantiated without an XmlReader object
can potentially lead to denial of service, information disclosure, and server-side request forgery attacks.</p>
</blockquote>

<p>Denial of service is exactly the thing you want to avoid, so you decide to follow this guideline and wrap
your input stream in an <code class="language-plaintext highlighter-rouge">XmlReader</code>:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">var</span> <span class="n">reader</span> <span class="p">=</span> <span class="n">XmlReader</span><span class="p">.</span><span class="nf">Create</span><span class="p">(</span><span class="n">stream</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">serializer</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">XmlSerializer</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">T</span><span class="p">));</span>
<span class="n">T</span> <span class="k">value</span> <span class="p">=</span> <span class="p">(</span><span class="n">T</span><span class="p">)</span><span class="n">serializer</span><span class="p">.</span><span class="nf">Deserialize</span><span class="p">(</span><span class="n">reader</span><span class="p">);</span>
</code></pre></div></div>

<p>Sadly, this does not protect you against <code class="language-plaintext highlighter-rouge">StackOverflowException</code> at all. At this point, you (justifiably) think
it might be wise to revisit your career choices and start raising chickens on a farm. Before you commit to that,
you do one final internet search and magically stumble upon the article
<a href="https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/security-considerations-for-data#using-xml-safely">Security Considerations for Data</a>. It takes 35 minutes to read, doesn’t have any useful code
samples, and it’s not even about <code class="language-plaintext highlighter-rouge">XmlSerializer</code>. But you are crazy and you read it anyway. By doing so you
become a member of an elite group of people who know how to limit the recursion depth when deserializing XML:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">quotas</span> <span class="p">=</span> <span class="k">new</span> <span class="n">XmlDictionaryReaderQuotas</span> <span class="p">{</span> <span class="n">MaxDepth</span> <span class="p">=</span> <span class="m">32</span> <span class="p">};</span>
<span class="k">using</span> <span class="nn">var</span> <span class="n">reader</span> <span class="p">=</span> <span class="n">XmlDictionaryReader</span><span class="p">.</span><span class="nf">CreateTextReader</span><span class="p">(</span><span class="n">stream</span><span class="p">,</span> <span class="n">quotas</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">serializer</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">XmlSerializer</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">T</span><span class="p">));</span>
<span class="n">T</span> <span class="k">value</span> <span class="p">=</span> <span class="p">(</span><span class="n">T</span><span class="p">)</span><span class="n">serializer</span><span class="p">.</span><span class="nf">Deserialize</span><span class="p">(</span><span class="n">reader</span><span class="p">);</span>
</code></pre></div></div>

<p>If you are somehow doing this, congratulations—you are not vulnerable to <code class="language-plaintext highlighter-rouge">StackOverflowException</code>! Not only are
you safe, but I will also buy you a beer (or a drink of your choice) if we ever meet.</p>

<p>Of course, ASP.NET Core got everything right one more time, because this is exactly how the
<code class="language-plaintext highlighter-rouge">XmlSerializerInputFormatter</code> works behind the scenes.</p>

<p>So what’s the final verdict for <code class="language-plaintext highlighter-rouge">XmlSerializer</code>? If you are using ASP.NET Core to automatically deserialize HTTP
requests in XML format, you are safe. Otherwise, you are most likely unsafe and should consider switching
to <code class="language-plaintext highlighter-rouge">XmlDictionaryReader.CreateTextReader</code>.</p>

<p><strong><a href="https://github.com/neuecc/MessagePack-CSharp">MessagePack for C#</a></strong> is the most popular .NET library for working with the
<a href="https://msgpack.org/index.html">MessagePack</a> binary serialization format. It used to be vulnerable to denial of
service, but then it got fixed and the vulnerability details were published in
<a href="https://github.com/neuecc/MessagePack-CSharp/security/advisories/GHSA-7q36-4xx7-xcxf">GHSA-7q36-4xx7-xcxf</a>
(fun fact: I reported the vulnerability to Microsoft Security Response Center while it was still unknown, but
I got the response that they are aware of the issue, so no CVE for me this time). Unfortunately, even the fixed
version is not safe by default—you have to turn on the secure mode explicitly:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">options</span> <span class="p">=</span> <span class="n">MessagePackSerializerOptions</span><span class="p">.</span><span class="n">Standard</span>
  <span class="p">.</span><span class="nf">WithSecurity</span><span class="p">(</span><span class="n">MessagePackSecurity</span><span class="p">.</span><span class="n">UntrustedData</span><span class="p">);</span>

<span class="n">T</span> <span class="k">value</span> <span class="p">=</span> <span class="n">MessagePackSerializer</span><span class="p">.</span><span class="n">Deserialize</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">data</span><span class="p">,</span> <span class="n">options</span><span class="p">);</span>
</code></pre></div></div>

<p>The decision of library authors to introduce secure mode and then keep it disabled puzzles me, because all
other libraries have chosen the safe-by-default approach to fix this type of vulnerability. It’s not that
easy to figure out that this configuration option even exists: it’s in the
<a href="https://github.com/neuecc/MessagePack-CSharp#security">security</a> section, buried in the middle of a massive
README file. But if you care about security, it’s good knowing that at least you have the option of being safe.</p>

<h2 id="completely-unsafe">Completely unsafe</h2>

<p>Ah, <strong><a href="https://microsoft.github.io/bond/manual/bond_cs.html">Bond</a></strong>. The only serialization library where the recursion limit doesn’t even exist as an option.
That wouldn’t be too terrible on its own, because many other libraries had faced the same issue, but ultimately
fixed it. The real problem here is that when I reported this to Microsoft Security Response Center, they just
didn’t care. Verdict: avoid.</p>

<p><strong>Update (Mar 22, 2023):</strong> I might have been too harsh when I said that MSRC didn’t care
about my report. Here is the full story: I reported the issue as denial of service in
<a href="https://learn.microsoft.com/en-us/dotnet/orleans/overview">Microsoft Orleans</a> using Bond
deserializer. The report was evaluated in the context of Orleans, and since Orleans
is not intended to be publicly accessible (as described in the
<a href="https://learn.microsoft.com/en-us/dotnet/orleans/resources/frequently-asked-questions#can-i-connect-to-orleans-silos-from-the-public-internet">official documentation</a>),
MSRC determined that Orleans users are not exposed to denial of service by design.</p>

<h2 id="summary">Summary</h2>

<p>If you don’t like reading and you just want me to tell you how to be safe, here’s a pretty table for you.</p>

<table>
  <thead>
    <tr>
      <th>Library</th>
      <th>Format</th>
      <th style="text-align: right">Safe version</th>
      <th style="text-align: right">Max depth</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><a href="https://microsoft.github.io/bond/manual/bond_cs.html">Bond</a></td>
      <td>Bond</td>
      <td style="text-align: right"><span style="color:red">none</span></td>
      <td style="text-align: right">no limit</td>
    </tr>
    <tr>
      <td><a href="https://github.com/jamescourtney/FlatSharp">FlatSharp</a></td>
      <td>FlatBuffers</td>
      <td style="text-align: right">6.3.0</td>
      <td style="text-align: right">1000</td>
    </tr>
    <tr>
      <td><a href="https://www.nuget.org/packages/Google.Protobuf/">Google.Protobuf</a></td>
      <td>Protocol Buffers</td>
      <td style="text-align: right"><span style="color:blue">all</span></td>
      <td style="text-align: right">100</td>
    </tr>
    <tr>
      <td><a href="https://github.com/neuecc/MessagePack-CSharp">MessagePack for C#</a></td>
      <td>MessagePack</td>
      <td style="text-align: right">2.1.90 <sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup></td>
      <td style="text-align: right">500</td>
    </tr>
    <tr>
      <td><a href="https://www.newtonsoft.com/json">Newtonsoft.Json</a></td>
      <td>JSON</td>
      <td style="text-align: right">13.0.2</td>
      <td style="text-align: right">64</td>
    </tr>
    <tr>
      <td><a href="https://github.com/protobuf-net/protobuf-net">protobuf-net</a></td>
      <td>Protocol Buffers</td>
      <td style="text-align: right">3.10</td>
      <td style="text-align: right">512</td>
    </tr>
    <tr>
      <td><a href="https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/how-to?pivots=dotnet-7-0">System.Text.Json</a></td>
      <td>JSON</td>
      <td style="text-align: right"><span style="color:blue">all</span></td>
      <td style="text-align: right">64</td>
    </tr>
    <tr>
      <td><a href="https://learn.microsoft.com/en-us/dotnet/api/system.xml.serialization.xmlserializer?view=net-7.0">System.Xml.XmlSerializer</a></td>
      <td>XML</td>
      <td style="text-align: right"><span style="color:blue">all</span> <sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup></td>
      <td style="text-align: right">32</td>
    </tr>
  </tbody>
</table>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Only if you configure <code class="language-plaintext highlighter-rouge">MessagePackSerializerOptions.Security</code> property. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>Only if you use <code class="language-plaintext highlighter-rouge">XmlDictionaryReader.CreateTextReader</code>. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>

<h2 id="conclusion">Conclusion</h2>

<p>If you keep your libraries up to date or use ASP.NET Core formatters, you are most likely safe from
<code class="language-plaintext highlighter-rouge">StackOverflowException</code>. Otherwise, you should probably try adopting the guidelines from this post.
But how bad would it be to be vulnerable anyway? Stay tuned for the next post, where the real fun
begins: I’ll show you how denial of service attack looks like in practice.</p>

<p><small><i>Huge thanks to Milica Miljkov for editing this post, and also for making
it more about users and less about me.</i></small></p>]]></content><author><name></name></author><summary type="html"><![CDATA[I have always been fascinated by .NET’s StackOverflowException. It’s interesting because it’s fundamentally different from most other exceptions—you can’t catch it with a try/catch block. When you overflow the stack, it’s game over—the runtime will terminate your process. This behavior is especially devastating for web services that are deserializing user-controlled data. If your data structure allows recursion, malicious users can easily craft a highly nested payload and use it to DDoS your website. Serialization libraries have the power to protect you against this type of DDoS attack by limiting the recursion depth during deserialization. That’s exactly what libraries designed with security in mind have always been doing. But they are outliers—most libraries were initially vulnerable to StackOverflowException (and some of them still are). In this post, I’ll review the most widely used .NET serialization libraries and show you how they fare against this mighty enemy. You’ll learn which library versions are safe to use, which serializers require special usage patterns, and which libraries you should simply avoid. Safe by design System.Text.Json and Google.Protobuf are the absolute winners. They have never been vulnerable to StackOverflowException, because they have always been enforcing the recursion limit by default. This limit is configurable, though, so nothing can prevent you from intentionally increasing it. But nothing can prevent you from trying to live with grizzly bears, either—it’s just a question of your lifestyle choices. Previously vulnerable, now safe by default Newtonsoft.Json is by far the most popular .NET library, with over 2.8B total downloads on NuGet. Despite its enormous popularity, it was only last year that its insecure defaults were fixed (see GHSA-5crp-9r3c-p9vr for more details). The vulnerability was not as bad as it could have been, because you always had the option to control the recursion depth by setting the MaxDepth property in JsonSerializerSettings (though I doubt many people were doing that). ASP.NET Core users were not even at risk— Newtonsoft.Json formatter has always been safe by default. Long story short, you were vulnerable only if you were doing something like this: T value = JsonConvert.DeserializeObject(s); If you’ve been keeping your libraries up to date, even this is no longer an issue. FlatSharp (FlatBuffers implementation) and protobuf-net (Protocol Buffers implementation) were also unsafe by default. Unlike Newtonsoft.Json, these two libraries were 100% vulnerable: there was no option for users to explicitly set the recursion limit. Yours truly discovered these issues and reported them to both library authors. James Courtney and Marc Gravell quickly responded to my reports and immediately published the fixes that made these two libraries safe by default. Huge thanks to James and Marc for keeping the .NET ecosystem safe! Unsafe by default, but can be configured for safe use System.Xml.XmlSerializer from the .NET standard library can be used safely, but figuring out how to do that resembles finding a needle in a haystack. If you are learning how to use the library by following the official documentation, you will almost certainly write something like this: var serializer = new XmlSerializer(typeof(T)); T value = (T)serializer.Deserialize(stream); Congratulations, you are now vulnerable to StackOverflowException! Let’s say you decide to up your game by using some fancy code quality rules. CA5369: Use XmlReader for Deserialize comes to rescue with the instructions how to securely deserialize XML: Deserializing untrusted XML input with XmlSerializer.Deserialize instantiated without an XmlReader object can potentially lead to denial of service, information disclosure, and server-side request forgery attacks. Denial of service is exactly the thing you want to avoid, so you decide to follow this guideline and wrap your input stream in an XmlReader: using var reader = XmlReader.Create(stream); var serializer = new XmlSerializer(typeof(T)); T value = (T)serializer.Deserialize(reader); Sadly, this does not protect you against StackOverflowException at all. At this point, you (justifiably) think it might be wise to revisit your career choices and start raising chickens on a farm. Before you commit to that, you do one final internet search and magically stumble upon the article Security Considerations for Data. It takes 35 minutes to read, doesn’t have any useful code samples, and it’s not even about XmlSerializer. But you are crazy and you read it anyway. By doing so you become a member of an elite group of people who know how to limit the recursion depth when deserializing XML: var quotas = new XmlDictionaryReaderQuotas { MaxDepth = 32 }; using var reader = XmlDictionaryReader.CreateTextReader(stream, quotas); var serializer = new XmlSerializer(typeof(T)); T value = (T)serializer.Deserialize(reader); If you are somehow doing this, congratulations—you are not vulnerable to StackOverflowException! Not only are you safe, but I will also buy you a beer (or a drink of your choice) if we ever meet. Of course, ASP.NET Core got everything right one more time, because this is exactly how the XmlSerializerInputFormatter works behind the scenes. So what’s the final verdict for XmlSerializer? If you are using ASP.NET Core to automatically deserialize HTTP requests in XML format, you are safe. Otherwise, you are most likely unsafe and should consider switching to XmlDictionaryReader.CreateTextReader. MessagePack for C# is the most popular .NET library for working with the MessagePack binary serialization format. It used to be vulnerable to denial of service, but then it got fixed and the vulnerability details were published in GHSA-7q36-4xx7-xcxf (fun fact: I reported the vulnerability to Microsoft Security Response Center while it was still unknown, but I got the response that they are aware of the issue, so no CVE for me this time). Unfortunately, even the fixed version is not safe by default—you have to turn on the secure mode explicitly: var options = MessagePackSerializerOptions.Standard .WithSecurity(MessagePackSecurity.UntrustedData); T value = MessagePackSerializer.Deserialize&lt;T&gt;(data, options); The decision of library authors to introduce secure mode and then keep it disabled puzzles me, because all other libraries have chosen the safe-by-default approach to fix this type of vulnerability. It’s not that easy to figure out that this configuration option even exists: it’s in the security section, buried in the middle of a massive README file. But if you care about security, it’s good knowing that at least you have the option of being safe. Completely unsafe Ah, Bond. The only serialization library where the recursion limit doesn’t even exist as an option. That wouldn’t be too terrible on its own, because many other libraries had faced the same issue, but ultimately fixed it. The real problem here is that when I reported this to Microsoft Security Response Center, they just didn’t care. Verdict: avoid. Update (Mar 22, 2023): I might have been too harsh when I said that MSRC didn’t care about my report. Here is the full story: I reported the issue as denial of service in Microsoft Orleans using Bond deserializer. The report was evaluated in the context of Orleans, and since Orleans is not intended to be publicly accessible (as described in the official documentation), MSRC determined that Orleans users are not exposed to denial of service by design. Summary If you don’t like reading and you just want me to tell you how to be safe, here’s a pretty table for you. Library Format Safe version Max depth Bond Bond none no limit FlatSharp FlatBuffers 6.3.0 1000 Google.Protobuf Protocol Buffers all 100 MessagePack for C# MessagePack 2.1.90 1 500 Newtonsoft.Json JSON 13.0.2 64 protobuf-net Protocol Buffers 3.10 512 System.Text.Json JSON all 64 System.Xml.XmlSerializer XML all 2 32 Only if you configure MessagePackSerializerOptions.Security property. &#8617; Only if you use XmlDictionaryReader.CreateTextReader. &#8617; Conclusion If you keep your libraries up to date or use ASP.NET Core formatters, you are most likely safe from StackOverflowException. Otherwise, you should probably try adopting the guidelines from this post. But how bad would it be to be vulnerable anyway? Stay tuned for the next post, where the real fun begins: I’ll show you how denial of service attack looks like in practice. Huge thanks to Milica Miljkov for editing this post, and also for making it more about users and less about me.]]></summary></entry><entry><title type="html">Advanced lesson in procrastination</title><link href="https://mijailovic.net/2023/01/23/procrastination/" rel="alternate" type="text/html" title="Advanced lesson in procrastination" /><published>2023-01-23T14:45:00+00:00</published><updated>2023-01-23T14:45:00+00:00</updated><id>https://mijailovic.net/2023/01/23/procrastination</id><content type="html" xml:base="https://mijailovic.net/2023/01/23/procrastination/"><![CDATA[<p>This story started while I was preparing my blog post
about <a href="/2023/01/15/piracy/">piracy</a>. The idea was to make it clear
that I am all for supporting artists with my money, while arguing that in some cases using piracy
might be justified.</p>

<p>I hadn’t even completed two sentences of the post before my brain decided to sabotage me with a
seemingly innocent suggestion: “Hey, you should totally calculate the value of your Bandcamp
collection, I bet it would make your position stronger!” Ok, mister brain, challenge accepted,
or whatever. I have 1,303 albums, average album price is $10, so my collection is worth roughly
$13,030. Can we move on now? “Oh, but that’s just the <em>estimate</em>—why don’t you calculate the
<em>exact</em> value?”</p>

<p>The challenge was too fun to resist, and that’s how we ended up here. In this post, I will teach
you how to properly avoid doing the important work by turning a 10-second job into a weeklong odyssey.</p>

<h2 id="purchases-page">Purchases page</h2>

<p><img src="/assets/img/2023-01-23-purchases.png" alt="" /></p>

<p>Bandcamp conveniently lists all your purchases on a single page (well, in my case only after scrolling
down 130 times). You can save that page to a file and then parse the HTML content to extract the prices.
That’s exactly what I planned to do, but my brain interfered again. “Come on, are you really going to
scrape the HTML? You call yourself a master hacker, you should be ashamed of yourself!” I couldn’t argue
against this impeccable logic—I was indeed ashamed and beaten into submission once more.</p>

<h2 id="using-the-bandcamp-api">Using the Bandcamp API</h2>

<p>I fired up the developer tools and immediately found the order history API. It was super easy to
use, and I was only a few lines of code away from finding out the exact value of my collection. But before
doing that, I brilliantly decided that now was the best time to catch up with all fancy new .NET features
that I’ve been ignoring for years, such as
<a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/async-streams">Async Streams</a>
and high-performance JSON processing using the
<a href="https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/how-to?pivots=dotnet-7-0">System.Text.Json</a>
library. After putting it all to (questionable) use, I ran out of excuses and finally ran the code:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>   64.45 AUD
  271.35 CAD
   31.20 CHF
   50.00 DKK
 2582.63 EUR
  475.30 GBP
16056.00 JPY
  350.65 NOK
    5.00 NZD
  641.60 PLN
  599.13 SEK
   12.10 SGD
 9004.38 USD
</code></pre></div></div>

<p>God damn it, what was I supposed to do with 13 different currencies? I was now in an even worse position
than at the beginning: even though these numbers were correct, it was impossible to put all of them in a
sentence. Can you see where this is all going? Yes, it was currency conversion time!</p>

<h2 id="currency-conversion">Currency conversion</h2>

<p>The first idea that came to my mind was to use the current exchange rates, but that would have been
ridiculous. Today’s rates are irrelevant for purchases made many years ago—I needed <em>historical</em>
exchange rates instead. Searching for free currency exchange rate APIs reminded me how much I hate
the internet today. All I could find were generic, sponsored, contentless articles with names like
“25 best free currency APIs in 2022”, where meaningless statements such as “bank-grade 256-bit SSL
encryption” easily earn you the top spot in the list. In this endless sea of adverts for websites
falsely claiming to be free, I somehow managed to stumble upon
<a href="https://exchangerate.host/#/">Exchangerate.host</a>, a hidden little gem that offers a free currency
conversion API, but for real. It was so refreshing to find a great, free product made with love in
the age where almost everything is just a hustle to make some money. Kudos to you, my unknown neighbor
from Slovakia! Anyway, I quickly figured out how to use this API, but as it happens in every good
procrastination story, this was not the end.</p>

<h2 id="european-central-bank">European Central Bank</h2>

<p>Home page of the <a href="https://exchangerate.host/#/">Exchangerate.host</a> says that “currency data
delivered are sourced from financial data providers and banks, including the European Central Bank”.
Wait, if they are getting the data from the European Central Bank, what’s preventing me
from doing the same? Nothing, other than the fact that it would be completely unnecessary,
because I already had a free API I knew how to use. Logical thinking didn’t stop me, though,
so I searched for “European Central Bank exchange rates” and found this:</p>

<p><a href="https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/index.en.html">Euro foreign exchange reference rates</a></p>

<p>Latest reference rates, historical time series—everything was there, in PDF, CSV, and XML! I don’t
remember why, but I chose to go with the XML format. Trying to figure out how to correctly parse the
unnecessarily complicated XML schema made me feel dumb for a while, but I ultimately succeeded
and correctly concluded that XML namespaces are dumb, not me (but Jesus, Nemanja, what the hell
were you thinking: why didn’t you just parse the simple CSV file instead of wasting your time with
XML?). Anyway, the final ingredient was in my hands!</p>

<h2 id="snake_case">snake_case</h2>

<p>It’s not enough to just come close to the finish line. You still need to cross it, and that’s
sometimes surprisingly difficult. In every side quest you can find an even more useless side
quest hidden inside. Here is what caused my final detour (note that at this time I already had
all the info I needed to finish the quest I had embarked on):</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">JsonPropertyName</span><span class="p">(</span><span class="s">"item_title"</span><span class="p">)]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">ItemTitle</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>

<span class="p">[</span><span class="nf">JsonPropertyName</span><span class="p">(</span><span class="s">"unit_price"</span><span class="p">)]</span>
<span class="k">public</span> <span class="kt">decimal</span> <span class="n">UnitPrice</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>

<span class="p">[</span><span class="nf">JsonPropertyName</span><span class="p">(</span><span class="s">"payment_date"</span><span class="p">)]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">PaymentDate</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</code></pre></div></div>

<p>These are the properties of the class I used for deserializing Bandcamp API responses. You can see
that I had to manually annotate them with names of underlying JSON fields. I was annoyed with that,
especially because I knew it was possible to automatically convert the names during deserialization,
at least for <code class="language-plaintext highlighter-rouge">PascalCase</code> and <code class="language-plaintext highlighter-rouge">camelCase</code>. But I didn’t know the name of this letter case with
underscores, so I used my crazy Google skills to search for—wait for it—”case with underscores”. I
learned that this case is named <code class="language-plaintext highlighter-rouge">snake_case</code>, but I also discovered that <code class="language-plaintext highlighter-rouge">SCREAMING_SNAKE_CASE</code> and
<code class="language-plaintext highlighter-rouge">kebab-case</code> are a thing, too. All of this had led me nowhere, though, because I soon discovered
that snake case is not supported in .NET 7. It was finally the time to let go, which I did, but not
before I read the whole GitHub thread about the plans to
<a href="https://github.com/dotnet/runtime/issues/782">add snake_case support for System.Text.Json</a>.</p>

<h2 id="results">Results</h2>

<p>After seven days of pointless work, my mission was finally accomplished—I found out the exact value
of my Bandcamp collection! Remember how my initial guess was $13,030? Brace yourself before your hear
the exact value: it’s $13,260. That’s… pretty much the same number. Was it worth it the effort?
Absolutely not, but it was at least fun.</p>

<p>Leaving <a href="https://github.com/Metalnem/bandcamp-calculator">Bandcamp calculator</a> behind me,
I had no other option but to focus and write the
<a href="/2023/01/15/piracy/">piracy post</a>.</p>

<p><small><i>Huge thanks to Milica Miljkov for editing this post.
Special thanks to Steven Pressfield for teaching me about the
<a href="https://www.amazon.com/War-Art-Steven-Pressfield-ebook/dp/B007A4SDCG/">Resistance</a>.</i></small></p>]]></content><author><name></name></author><summary type="html"><![CDATA[This story started while I was preparing my blog post about piracy. The idea was to make it clear that I am all for supporting artists with my money, while arguing that in some cases using piracy might be justified. I hadn’t even completed two sentences of the post before my brain decided to sabotage me with a seemingly innocent suggestion: “Hey, you should totally calculate the value of your Bandcamp collection, I bet it would make your position stronger!” Ok, mister brain, challenge accepted, or whatever. I have 1,303 albums, average album price is $10, so my collection is worth roughly $13,030. Can we move on now? “Oh, but that’s just the estimate—why don’t you calculate the exact value?” The challenge was too fun to resist, and that’s how we ended up here. In this post, I will teach you how to properly avoid doing the important work by turning a 10-second job into a weeklong odyssey. Purchases page Bandcamp conveniently lists all your purchases on a single page (well, in my case only after scrolling down 130 times). You can save that page to a file and then parse the HTML content to extract the prices. That’s exactly what I planned to do, but my brain interfered again. “Come on, are you really going to scrape the HTML? You call yourself a master hacker, you should be ashamed of yourself!” I couldn’t argue against this impeccable logic—I was indeed ashamed and beaten into submission once more. Using the Bandcamp API I fired up the developer tools and immediately found the order history API. It was super easy to use, and I was only a few lines of code away from finding out the exact value of my collection. But before doing that, I brilliantly decided that now was the best time to catch up with all fancy new .NET features that I’ve been ignoring for years, such as Async Streams and high-performance JSON processing using the System.Text.Json library. After putting it all to (questionable) use, I ran out of excuses and finally ran the code: 64.45 AUD 271.35 CAD 31.20 CHF 50.00 DKK 2582.63 EUR 475.30 GBP 16056.00 JPY 350.65 NOK 5.00 NZD 641.60 PLN 599.13 SEK 12.10 SGD 9004.38 USD God damn it, what was I supposed to do with 13 different currencies? I was now in an even worse position than at the beginning: even though these numbers were correct, it was impossible to put all of them in a sentence. Can you see where this is all going? Yes, it was currency conversion time! Currency conversion The first idea that came to my mind was to use the current exchange rates, but that would have been ridiculous. Today’s rates are irrelevant for purchases made many years ago—I needed historical exchange rates instead. Searching for free currency exchange rate APIs reminded me how much I hate the internet today. All I could find were generic, sponsored, contentless articles with names like “25 best free currency APIs in 2022”, where meaningless statements such as “bank-grade 256-bit SSL encryption” easily earn you the top spot in the list. In this endless sea of adverts for websites falsely claiming to be free, I somehow managed to stumble upon Exchangerate.host, a hidden little gem that offers a free currency conversion API, but for real. It was so refreshing to find a great, free product made with love in the age where almost everything is just a hustle to make some money. Kudos to you, my unknown neighbor from Slovakia! Anyway, I quickly figured out how to use this API, but as it happens in every good procrastination story, this was not the end. European Central Bank Home page of the Exchangerate.host says that “currency data delivered are sourced from financial data providers and banks, including the European Central Bank”. Wait, if they are getting the data from the European Central Bank, what’s preventing me from doing the same? Nothing, other than the fact that it would be completely unnecessary, because I already had a free API I knew how to use. Logical thinking didn’t stop me, though, so I searched for “European Central Bank exchange rates” and found this: Euro foreign exchange reference rates Latest reference rates, historical time series—everything was there, in PDF, CSV, and XML! I don’t remember why, but I chose to go with the XML format. Trying to figure out how to correctly parse the unnecessarily complicated XML schema made me feel dumb for a while, but I ultimately succeeded and correctly concluded that XML namespaces are dumb, not me (but Jesus, Nemanja, what the hell were you thinking: why didn’t you just parse the simple CSV file instead of wasting your time with XML?). Anyway, the final ingredient was in my hands! snake_case It’s not enough to just come close to the finish line. You still need to cross it, and that’s sometimes surprisingly difficult. In every side quest you can find an even more useless side quest hidden inside. Here is what caused my final detour (note that at this time I already had all the info I needed to finish the quest I had embarked on): [JsonPropertyName("item_title")] public string ItemTitle { get; set; } [JsonPropertyName("unit_price")] public decimal UnitPrice { get; set; } [JsonPropertyName("payment_date")] public string PaymentDate { get; set; } These are the properties of the class I used for deserializing Bandcamp API responses. You can see that I had to manually annotate them with names of underlying JSON fields. I was annoyed with that, especially because I knew it was possible to automatically convert the names during deserialization, at least for PascalCase and camelCase. But I didn’t know the name of this letter case with underscores, so I used my crazy Google skills to search for—wait for it—”case with underscores”. I learned that this case is named snake_case, but I also discovered that SCREAMING_SNAKE_CASE and kebab-case are a thing, too. All of this had led me nowhere, though, because I soon discovered that snake case is not supported in .NET 7. It was finally the time to let go, which I did, but not before I read the whole GitHub thread about the plans to add snake_case support for System.Text.Json. Results After seven days of pointless work, my mission was finally accomplished—I found out the exact value of my Bandcamp collection! Remember how my initial guess was $13,030? Brace yourself before your hear the exact value: it’s $13,260. That’s… pretty much the same number. Was it worth it the effort? Absolutely not, but it was at least fun. Leaving Bandcamp calculator behind me, I had no other option but to focus and write the piracy post. Huge thanks to Milica Miljkov for editing this post. Special thanks to Steven Pressfield for teaching me about the Resistance.]]></summary></entry></feed>