Obfuscation Techniques: Magic Numbers

Obfuscation Techniques: Magic Numbers Featured Image

Magic numbers are numerical constants that have no clear meaning in the code and therefore make code harder to read. Anything that makes code harder to read is something we can use to obfuscate our code!

Table of Contents

What Are Magic Numbers?

I was first introduced to the concept of magic numbers when I started teaching. According to the curriculum I was assigned, magic numbers were defined as any number without an obvious meaning or purpose.

Of course, it’s hard for a static analysis tool to infer if a number has meaning to its reader, so most tools define magic numbers as any number other than 0, 1, or 2. That way, it’s up to the user to give that number a meaningful name.

The way this usually plays out in code is that there is some known constant that we use in a formula or as a loop counter. For example, in the Sample Programs repo, we have a solution to the Baklava problem in PythonOpens in a new tab. as follows:

for i in range(0, 10, 1):
    print((" " * (10 - i)) + ("*" * (i * 2 + 1)))

for i in range(10, -1, -1):
    print((" " * (10 - i)) + ("*" * (i * 2 + 1)))

Immediately, without knowing what the Baklava problem is, this code appears to be littered with magic numbers. For instance, the number “10” appears in several places, as do “2”, “1”, “0”, and “-1”.

By the law of magic numbers, we should really be giving these constants names, so the code is much easier to read. Here’s what that might look like:

NUM_ROWS = 10

for i in range(0, NUM_ROWS, 1):
    print((" " * (NUM_ROWS - i)) + ("*" * (i * 2 + 1)))

for i in range(NUM_ROWS, -1, -1):
    print((" " * (NUM_ROWS - i)) + ("*" * (i * 2 + 1)))

With one additional variable, it becomes a bit more clear what the code is trying to accomplish. Though, I think it could benefit from a few more variables:

NUM_ROWS = 10

for i in range(0, NUM_ROWS, 1):
    num_spaces = NUM_ROWS - i
    num_stars = i * 2 + 1
    print((" " * num_spaces) + ("*" * num_stars))

for i in range(NUM_ROWS, -1, -1):
    num_spaces = NUM_ROWS - i
    num_stars = i * 2 + 1
    print((" " * num_spaces) + ("*" * num_stars))

Or even better, a function:

NUM_ROWS = 10

def print_row(i):
    num_spaces = NUM_ROWS - i
    num_stars = i * 2 + 1
    print((" " * num_spaces) + ("*" * num_stars))

for i in range(0, NUM_ROWS, 1):
    print_row(i)

for i in range(NUM_ROWS, -1, -1):
    print_row(i)

Regardless, the point being that we can improve the readability of our code by removing magic numbers. Therefore, if we want to make our code harder to read, we can introduce magic numbers! And let me tell you, there are a lot of fun ways to do this.

Making Magic Numbers the Norm

Anywhere in your code that you’re using a numerical constant, it’s time to abandon its name. We already looked at an example of the opposite, so let’s start with a new example. For instance, imagine we had a program which computed some roman numeral math. In this example, we probably have constants for each of the roman numerals:

I = 1
V = 5
X = 10
L = 50
C = 100
D = 500
M = 1000

And then, we could use these constants to compute a list of roman numerals. To do that, we can loop over each value in the list and add or subtract that current value from the total as expected:

number = [I, X, X]
i = 0
total = 0
while i < len(number):
  ...
  i++

Now, it’s clear we’re dealing with roman numerals, so we can probably compute and verify the result in our heads. Let’s instead remove the constants all together:

number = [1, 10, 10]
i = 0
total = 0
while i < len(number):
  ...
  i++

Now, it’s completely unclear what this code is even doing. Therefore, mission accomplished!

When The Numbers Aren’t Magic Enough

While magic numbers only slightly make code harder to read, we can do so much worse. For instance, magic numbers are usually in a base we’re used to reading (i.e., base 10). What’s stopping us from using a different base to represent our magic numbers?

number = [0b1, 0b1010, 0b1010]
i = 0
total = 0
while i < len(number):
  ...
  i++

And because these examples are Python, we can get the binary representations of each number easily: just call the bin() function on the number. But, why stop there? We can use any of the common bases, such as hexadecimal or octal. Hell, mix and match them to completely lose your readers. I’d definitely recommend doing this for duplicate numbers, so they appear different at a glance:

number = [0b1, 0b1010, 0xa]
i = 0
total = 0
while i < len(number):
  ...
  i++

Fortunately, languages like Python and Java can handle different bases interchangeably, so this won’t affect your code at all. However, this will really hurt your reader, so why not go all out? Swap all the numbers with different bases:

number = [0b1, 0b1010, 0xa]
i = 0o0
total = 0x0
while i < len(number):
  ...
  i++

Now that’s some fun looking code! Imagine combining this with the visually similar characters trick. I can already see a function call that looks like this: 0OO000O00OOOO0(0x00101).

Good Luck; Have Fun!

One of the things I’m really enjoying about this series is breaking all of the best practices we’re taught as developers. It’s even more fun as someone who teaches best practices to lean into the memes a bit. Overall, I’m really pleased with where this series is at, and I hope to keep expanding it for some time. Surely, y’all are enjoying it as well!

With that said, let’s call it a day. As usual, if you liked this one, there’s a lot more where that came from:

In addition, here are some Python resources you might like (#ad):

Finally, you can take your support a step further by heading over to my ways to grow the site. Otherwise, take care!

Obfuscation Techniques (6 Articles)—Series Navigation

In our field, everyone likes to talk about best practices, but it’s sometimes difficult to make the case for what is and isn’t a best practice. On the other hand, I think it’s very easy to come up with ways to make code worse, but who would want to read about ways to make their code bad? I’ll tell you who! People who want to obfuscate their code, or at least that’s what I tell myself. Regardless, that’s my cover story for putting together this new series, and I figure it’ll be a lot of fun.

Jeremy Grifski

Jeremy grew up in a small town where he enjoyed playing soccer and video games, practicing taekwondo, and trading Pokémon cards. Once out of the nest, he pursued a Bachelors in Computer Engineering with a minor in Game Design. After college, he spent about two years writing software for a major engineering company. Then, he earned a master's in Computer Science and Engineering. Today, he pursues a PhD in Engineering Education in order to ultimately land a teaching gig. In his spare time, Jeremy enjoys spending time with his wife, playing Overwatch and Phantasy Star Online 2, practicing trombone, watching Penguins hockey, and traveling the world.

Recent Posts