Published on

PBJar CTF 2022: Writeup Compilation

Authors
Table of Contents

MEV

  • Challenge Author: bruh.#0766

  • Challenge Description: The miner of Block #12983883 on the Ethereum Blockchain partakes in the common practice of MEV. What is the exact amount of Ether that was transfered to the miner as a bribe from the transaction that was included first in this block? Info about MEV: https://ethereum.org/en/developers/docs/mev/ Flag format: flag0.00694206942

TLDR
  • Go on etherscan.io and find the block provided in the challenge description.
  • Find the first transaction on that block.
  • Copy the exact amount transferred to the MEV bot and that's the flag.

In-Depth Solution

This challenge is surprsingly the second to least solved challenge in the misc category excluding survey which I thought was kind of weird as I had less trouble with it than some of the other misc challenges. Anyways I digress, here's my solution.

In the description we are given a block that is in fact on the etherium blockchain so no ropsten this time. Then we are told a little about the practice of MEV and given a link to learn about it. However, reading is lame so let's just go right into it.

We fire up etherscan.io, put in the name of the block, and now we must find as the challenge states:

the exact amount of Ether that was transfered to the miner as a bribe from the transaction that was included first in this block

Now that last part is the most important in regards to where to look. We now know we can find it in the first transaction included in this block. First I clicked the: 145 transactions link and was taken to this page. I went to the very first transaction, but I don't see any sketchy bribing here ๐Ÿ‘€. Then I go back and click the 26 contract internal transactions link and was taken to this page. Scrollarooni down to the bottom and wow there cowboy, MEV Bot: 0x4d2...2d3 and Wrapped Ether should be exactly what we're looking for. We know this is something to do with MEV as the ether was transferred to an MEV bot and the wrapped ether must of course be the bribe payment. We can also see that both have identical ether values of 0.009672680170055 Ether.

Submit that number and it's the flag. In my opinion this challenge should have had much more solves as it really only entailed looking at a block's first transaction but whatever. Enjoy your flag!

Flag: flag{0.009672680170055}


Not_Baby_Fixed

  • Challenge Author: QuickMaffs

  • Challenge Description: Hmm.... What is this? (Note: Not_Baby has a different solution than intended)

TLDR
  • Factor aa, bb, cc and use substitution to rewrite the equation for nn to only have 2 (known) variables rather than 3.
  • Factor your new polynomial into 2 smaller factors, plug-in your variables and solve for those 2 factors.
  • Use a factoring calculator to get all the prime factors of your two smaller nn factors, easier than doing entire big nn value.
  • Standard RSA now: Calculate ฯ•\phi, then dd, then mm, and finally get the flag.

In-Depth Solution

So after looking through the files we have provided we can see that script.py reads in flag.txt and nums.txt and then generates out.txt. Out.txt prints out an nn value, ee value, and ct value so we know this is RSA. A lot of other solutions that I found for this challenge used factor calculators for a long time or factorDB now that someone uploaded the factors into it. However that's cringe and we like to do the intended solution around here ๐Ÿ˜Ž. (Note: I did talk to the challenge author and this method is the intended sol).

So first we have to figure out how to factor nn into prime factors so that we can proceed with the RSA decryption process. However, nn is supposed to be too large to factor so we must use other methods then simply factor calculators. We do know however from script.py that the way nn is generated makes this equation:

n=a3+b3โˆ’34c3n = a^3 + b^3 - 34c^3

and we know all the values of aa, bb, and cc from nums.txt.

Note: the reason this challenge had to be "fixed" is that the original Not_Baby accidentally didn't provide nums.txt so it was "supposedly" impossible, but people just ran factor calculators for 2 hours to solve it.

Anyways, knowing aa, bb, and cc, we can try to break up this equation/polynomial or whatever you want to call it to make factoring nn more feasible. So we can use any big factorization calculator like alpertron or factorDB to start factoring aa, bb, and cc (I used factorDB). Eventually, you'll find that aa and cc have a large, prime common factor and that bb and cc also have a large, prime common factor. We can then rewrite aa, bb, and cc using these two large factors we found, lets call them xx and yy.

x=321329349024937022728435772726127082487y=302518462040600437690188095770599287567a=15x2,b=7y2,c=3xyx = 321329349024937022728435772726127082487 \\ y = 302518462040600437690188095770599287567 \\ a = 15x^2, b = 7y^2, c = 3xy

Knowing that n=a3+b3โˆ’34c3n = a^3 + b^3 - 34c^3, we can know plug-in the values we have calculated and make a new two-variable equation which looks like this:

3375x6+343y6โˆ’918x3y33375x^6 + 343y^6 - 918x^3y^3

Now we have a two-variable equation/polynomial so we can try to factor it out now. You can use something like sage for this but I was lazy and found this website that factored it for me. The two resulting factors we get are:

(15x2+3xy+7y2)(225x4โˆ’45x3yโˆ’96x2y2โˆ’21xy3+49y4)(15x^2 + 3xy + 7y^2)(225x^4 - 45x^3y - 96x^2y^2 - 21xy^3 + 49y^4)

Now we have successfully split nn into two smaller factors, yay. Rather than mutliplying out those polynomials by hand we can write a script to do it for us.

x = 321329349024937022728435772726127082487
y = 302518462040600437690188095770599287567

f = 15*x**2 + 3*x*y + 7*y**2
g = 225*x**4 - 45*x**3*y - 96*x**2*y**2 - 21*x*y**3 + 49*y**4

print(f)
print(g)

Now that we have two smaller numbers that we know are the products of nn, we can begin to prime factorize them, which will be much faster and more efficient than an incredibly large nn value. Using factorDB I was able to get a long list of only prime factors that all multiply to get nn. Time to write our script and decrypt this RSA now that the hard part is over. I will post my full script in this folder but I will give a brief explanation of it here. After calculating ff and gg I create an array of all the prime factors and call it nums.

nums = [9, 5, 71, 103, 50543, 190026430624001, 2703970397964298301, 2612704207743743498414225576245857791, 8581, 9202842813283520053373814153366196725555378670569425651403981961003320229089581578132314718638828971883763395128536959296142080739168256752552585624307]

I then calculate ฯ•\phi by multiplying all of the factors - 1 by each other (note: I will explain why this won't always work).

phi = 1
for num in nums:
	phi *= (num - 1)

Then I just calculate dd value with standard RSA, so eโˆ’1modโ€‰โ€‰ฯ•e^{-1} \mod \phi. I think this can also be achieved with the pycrypto inverse function with inverse (ee, ฯ•\phi).

d = pow(e, -1, phi)

Then find the message, again with standard RSA protocol, so ourย cyphertextdmodโ€‰โ€‰n\text{our cyphertext}^d \mod n.

m = pow(ct, d, n)

Then I just run the long_to_bytes() function to convert the message from numbers to readable text and voila, we have our flag.

flag = long_to_bytes(m)
print(flag)

Quickly to explain my previous note the phi function isn't calculated simply by getting the product of every prime factor - 1 it is actually calculated wtih:

ฯ•(pe)=p(eโˆ’1)โ‹…(pโˆ’1),pย beingย theย primeย andย eย beingย theย exponentย  \phi(p^e) = p^{(e-1)} \cdot (p-1), \quad p \text{ being the prime and } e \text{ being the exponent }

Sometimes though you won't get any primes with an exponent >1> 1 so the beginning part of the ฯ•\phi function which is p(eโˆ’1)p^{(e-1)} will just end up equalling 11, so all you have to really solve for is (pโˆ’1)(p-1). In this problem there was a prime factor of 242^4, but I was too lazy to write an actually good script so when I solved the ฯ•\phi function by hand with 242^4 I found that it would end up multiplying ฯ•\phi by 88, so in my nums array I just lazily added a factor of 99 so that when ฯ•\phi was multiplied by (numโˆ’1)(num - 1) the 99 would multiply ฯ•\phi by 88. If you want a much better way of doing this I'd recommend checking this out--this persons write-up has a much better and more dynamic way to calculate ฯ•\phi that will work with multiple factors with exponents >1> 1. Also funny enough this was the only write-up I could find that solved the challenge with the intended solution so kudos to them. However, as a beginner reading that write-up I thought it could use a little more explanation to help beginners trying to learn which I tried to achieve in this write-up.

Anyways I'm done blabbering now here's the flag that is printed when I run my solve.py script.

Flag: flag{plz_n0_guess_sum_of_a_b_c_d1vides_n}

Full Script:

from Crypto.Util.number import *

with open('nums.txt','r') as f:
	s=f.read().strip().split()
	a=int(s[0])
	b=int(s[1])
	c=int(s[2])

e=65537
ct=1918452064660929090686220330495385310745803950329608928110672560978679963497394969369363585721389729566306519544561789659164639271919010791127784820214512488663422537225906608133719652453804000168907004058397487865279113133220466050285
n=3134820448585521702394003995997656455907477282436511703324204127865184340978305062848983553236851077753614495104127538077189920381627136628226756258746377111950396074035862527542407869672121642062363412247864869790585619483151943257840

x = 321329349024937022728435772726127082487
y = 302518462040600437690188095770599287567

f = 15*x**2 + 3*x*y + 7*y**2
g = 225*x**4 - 45*x**3*y - 96*x**2*y**2 - 21*x*y**3 + 49*y**4

nums = [9, 5, 71, 103, 50543, 190026430624001, 2703970397964298301, 2612704207743743498414225576245857791, 8581, 9202842813283520053373814153366196725555378670569425651403981961003320229089581578132314718638828971883763395128536959296142080739168256752552585624307]
phi = 1
for num in nums:
	phi *= (num - 1)

d = pow(e, -1, phi)

m = pow(ct, d, n)

flag = long_to_bytes(m)

print(flag)

ProgrammersHateProgramming 2

TLDR
  • Use same methodology as the original ProgrammersHateProgramming challenge, but bypass more "one-time" filters and use nesting or concatenation on "permanently" filtered out words.

In-Depth Solution

This challenge starts off with the website looking identical to the first ProgrammersHateProgramming challenge, but the source code provided is a little bit different. Pretty much as the description says, more filters. To start off I always like getting the str_replace_first filters out of the way to make writing the rest of my XSS injection easier. For now my injection just looks like this:

<? <?php ?> flag

Now that we've got all of the one-time filters out of the way we can craft our injection. For the original challenge I used the php readfile() function and was able to do it that way, but this time the word read is filtered out so we won't be able to use that function ... just kidding! We can actually get around these stinky filters quite easily and my answer was nesting. If you have something like readfile() then when 'read' is filtered out that read will turn into a blank space and the 'file()' part on the right side will collapse in and result in just file(). That caving in as I like to call it can be exploited however. If we nest a read inside another read, then when the inside read is replaced the two sides of the outer read will collapse in and result in a normal read. Phew, that was long, here's an example.

rereadad ---> re read ad ---> read

After testing it out it works perfectly and it now means we can use this with all the other functions. But first let's just get the flag. Here's how my final XSS injection turned out:

<? <?php ?> flag <?php rereadadfile("/flag.php"); ?>

And boom, our flag is printed out. I want to put out a quick note though saying that this is not the only way to solve this challenge. Php has a ton of different functions that you can use to do the same thing as I did here. Another common example I found looks something like:

<? <?php ?> flag <?php system("llss -a /"); system("ccatat /flag.php"); ?>

This was actually the most common method I saw especially in the original challenge because people had to first even figure out that the flag was being stored in the file flag.php. However a lot of methods did use the php system() function so I want to recognize MikeCAT's solution here because it used a different function of passthru() which I think is a lot less common and will be less likely filtered out in similar challenges like this. MikeCAT's solution also has a different method than nesting which concatenates strings together using the php . operator and this just goes to show how many different ways you can craft this injection.

Anyways enough blabbering here's the flag!

Flag: flag{wow_that_was_a_lot_of_filters_anyways_how_about_that_meaningful_connection_i_mentioned_earlier_:)}


Stegosaurus stenops

  • Challenge Author: ZeroDayTea

  • Challenge Description: This stenops swallowed the flag... and some unusually large rock

TLDR
  • Use some steg cracking tool like steghide or stegcracker to find hidden data in the file.
  • Figure out that the hint 'rock' is leading you to the wordlist rockyou.txt.
  • Write a script that brute forces the passphrase using words from rockyou.txt.

In-Depth Solution

So when we first open up the image we just see a simple stegosaurus, but there must be more here ๐Ÿ‘€. The challenge is definitely something steganography related so I quickly googled common steganography tools. I ended up finding this tool called steghide where you can simply run a command in the terminal and it should extract the hidden data ... right?

steghide extract -sf stegosaurus.jpg

First I ran this command, however afterwords it asks me for a passphrase. I was a bit stumped at first but when I looked back at the challenge we are given a hint. From the description we can reasonably infer the author is pointing us towards 'rock'. After doing some research on common passphrases and 'rock' I came across this very neat file called rockyou.txt. Turns out this file is an extremely large assortment of the most common passwords that have been used over time. It is extremely big though and the passphrase can only be one of them. Let's make python do the heavy lifting. Also I downloaded rockyou.txt at this link.

with open("rockyou.txt", "r") as my_file:
	for line in my_file:

I started off with these lines of code and now I was able to read in the rockyou.txt file I downloaded and loop through each line of it. Now I just had to actually run the steghide command using the lines we take from the txt file.

os.system("steghide extract -sf stegosaurus.jpg -xf info.txt -p " + "\"" + line + "\"")

After searching I found this neat module by the name of os, that I'm pretty sure is built into python so no installing is necessary, which allows you to run terminal commands from python. Eventually I was able to craft together that line shown above which will try to extract info from the image, put anything extracted into info.txt, and use the passphrase taken as we loop through rockyou.txt. The weird "\\"" on both sides of the variable line are just to make sure that if there are any spaces in one of the lines, such as 'I love you', the command will read it in as one string rather than spitting out something like:

Unknown argument "you"

After that just let the terminal get spammed a bunch of times with something along the lines of:

Unable to extract data with that passphrase

Finally, the nice thing of adding -xf info.txt into the command alows you to walk away from your computer in case the data extracted was just raw data as you could come back and it would be neatly saved in info.txt. ZeroDayTea set it up however to create a flag.txt file for you anyways, so it didn't matter that much, but I think it still makes life much more convenient.

Anyways enough blabbering here's the flag that was extracted!

Flag: flag{ungulatus_better_than_stenops}

Full Script:

import os

with open("rockyou.txt", "r") as my_file:
	for line in my_file:
		os.system("steghide extract -sf stegosaurus.jpg -xf info.txt -p " + "\"" + line + "\"")

web

  • Challenge Author: eyangch

  • Challenge Description: I downloaded this program back when the version number was still v1. It's been a long time... I heard the most recent update has the flag in it. Download: http://147.182.172.217:42100/v1

TLDR
  • Find the outer limits of the possible versions by just adding 0's.
  • Use a binary search method to keep finding numbers between your limits until you get the newest version.
  • Find the flag in the downloaded flag file.

In-Depth Solution

So to start off we go to the website on the link provided and a weird file is downloaded. It has a bunch of non-readable text on it, but some is readable. On it though we can find a link that takes us to download version 2. Once we click the link to version 2 we download another file and in there we get a link to version 3. This will repeat over and over again. Our goal as the description states is to find the newest one. I start off by playing around with the url and I realize that I can change the url to get whatever version number I put in. I then proceed to find the limits of the version by just adding zeroes.

Eventually I get the URL to download a file for me. However, the next URL with one more zero: http://147.182.172.217:42100/v1000000000 would give me the message:

version not found

After that I decided a write a script for me to perform a sort of binary search to find the number that is in between the two limits that I have set. I thought about automating this process so that I would send a web request with my script and based on the response I change my limits. However of course I was too lazy so I kept inputting the version my program spit out by hand into the url and would change the variables myself accordingly. Pretty much the general idea is that if we do get a version back and it downloads a file, that is the new min_limit and if the website responds with 'version not found' we update the max_limit.

min_limit = 100000000
max_limit = 1000000000
halfway = int(((max_limit - min_limit) / 2) + min_limit)
print(halfway)

This is the short script that I use to get my middle number. After that I just go the website and replace the version in the url with the number my program prints, check what response I get from the website and update min and max limit accordingly, and just rinse and repeat. Eventually the version number 133791021 downloaded a file called flag. Then just run strings or cat on it like this:

strings flag or cat flag

and boom, our flag is in there. There's definitely a much more optimized way to do this especially if you can automate updating the max and min limits, but this method works and got me the flag which is what matters. Anyways here's the flag, enjoy!

Flag: flag{h0w_l0ng_wher3_y0u_g0ne_f0r_3910512832}

Full Script:

min_limit = 133791015
max_limit = 133791027

halfway = int(((max_limit - min_limit) / 2) + min_limit)

print("Version can be: " + str(min_limit))
print("Version can't be: " + str(max_limit))
print("In the middle: " + str(halfway))