Wargames My 2022
Writeup for the challenges I solved in Wargames My 2022
This is my third time participating in Wargames My. Thanks to my teammates @DavidTan0527 and @1001mei for carrying the team (iMonkee Ultra Max
) ๐
Misc
Secure Dream 1.0
Let me know your dreams. Could your dreams bypass my expectation?
Observation
For this challenge, we are given a server endpoint that will take in user input and run the eval
function.
The payload, however, must not contain any lowercase or uppercase letter or string literal
The goal of this challenge is to read the flag from the server
Solution
At first, I was searching for a method to code python without alphabets, apparently, this is possible in python2 with backtick ``. But since the server was running on python3, this method will not work.
I got stuck for a while until the organizers released this hint
Is print(1) == ๐๐๐๐๐(1) ? Maybe? Really?
Observe that the first print
is a regular string, and the second ๐๐๐๐๐
was italic.
I thought that this might mean that python accepts Unicode character as the function name, so I can just provide italic characters to bypass the filter
I opened a python terminal to try it out
>>> ๐๐๐๐๐("hello")
hello
It works!
Great! Now that I can use every lowercase character, I just need to find a way to bypass the string literal restriction.
It is not difficult to do this by using the bytes()
function
First I convert the code
print(open('flag.txt').read())
to bytes
[112, 114, 105, 110, 116, 40, 111, 112, 101, 110, 40, 34, 102, 108, 97, 103, 46, 116, 120, 116, 34, 41, 46, 114, 101, 97, 100, 40, 41, 41]
Then I convert it to bytes with the bytes()
function and wrap it around again with eval()
to execute the code
Final payload
๐๐๐๐(๐๐๐๐๐([112, 114, 105, 110, 116, 40, 111, 112, 101, 110, 40, 34, 102, 108, 97, 103, 46, 116, 120, 116, 34, 41, 46, 114, 101, 97, 100, 40, 41, 41]).๐
๐๐๐๐
๐())
Secure Dream 2.0
Can you really bypass another dreams?
Observation
Secure Dream v2.0 is just the same as v1.0 but with +
character blacklisted
Solution
My solution for v1.0 did not use the +
character so I solved this question with the same payload
Final payload
๐๐๐๐(๐๐๐๐๐([112, 114, 105, 110, 116, 40, 111, 112, 101, 110, 40, 34, 102, 108, 97, 103, 46, 116, 120, 116, 34, 41, 46, 114, 101, 97, 100, 40, 41, 41]).๐
๐๐๐๐
๐())
OSINT
Where Am I
Find the place. (Caught in a landside, No escape from reality)
Solution
The organizer has hinted that all the flags are in wgmy{}
format and we are not required to add the flag format ourselves.
So I started to think where can one provide a flag with the image as a hint?
And I immediately thought of social media and google reviews.
But since the question was Where Am I
so google review might be the more probable option.
I searched for all Texas Chicken outlets in Malaysia to find the outlet that looked like the picture.
Anddd I found it! The flag was captioned below the image
Who Am I
Find me. (Is this the real life? Is this just fantasy?)
Solution
This seems like a registration page, but the registration on the main website had already closed.
Again, all the flags are in wgmy{}
format and we are not required to add the flag format ourselves.
The best place to put the flag will be on social media.
So I looked through the facebook page of wgmy and found the original image!
Although there are no comments related to the flag on the post, the image contains weird symbols on the side.
I looked at it closely and guess that it was wingdings. (and it really was XD)
With some manual work of decoding it, I then got the flag.
Web
Christmas Wishlist
Submit your wishlist at this website!
Observation
This is a flask app that provides an endpoint to upload a file.
On every upload, the command /bin/file -b <filepath>
will be run.
The server will then read the uploaded file and pass the content to the jinja template.
Solution
I am really bad at web challenges and have no idea where to start. I thought of command injection but that is prevented by shell=False
I was stuck here for a few hours until I found a writeup online that looks very similar to this question. Then only I know about template injection.
The gist of the solution is we provide {{something...}}
and the jinja template will run the command in the bracket. I just copied the payload from the writeup and got the solution
Final payload (filename doesnโt matter)
File content
{{ request.__class__._load_form_data.__globals__.__builtins__.open("/flag").read() }}
Christmas Wishlist 2
Someone exploited the previous website, I have upgraded you can submit your wishlist at my website again!
Observation
It looks pretty much the same as v1.0 just that the file will not be read if the ASCII text
is in the file output command
Also, only the file command output will be added when ASCII text
is not in the output
Solution
The writeup I found in v1.0 already accounted for this scenario, basically, you can put the template in the shebang of the file so that the file command will output it.
To make sure that it does not contains the word ASCII text
, just add a Unicode character like an emoji or characters from another language in the file body.
Final payload (filename doesnโt matter)
File content
#!/usr/bin/{{ request.__class__._load_form_data.__globals__.__builtins__.open("/flag").read() }}
ๆๅจ
Steganography
Color
Please message us on discord if you are colorblind (Because Iโm easy come, easy go, Little high, little low,)
Solution
We get a QR code with multiple colours. I guessed that each colour will represent one part of the flag
So I used stegonline to filter out the 3 different colours and scan each of the QR separately.
And the QR codes indeed represented one part of the flag! Combine them and solve the question
Puzzle
Is the original always better? Maybe, should we check? (Iโm just a poor boy, I need no sympathy,)
Observation
Look at the top part of the image carefully and we can see pieces of QR code.
The organizer also provided the original image as the hint.
Solution
I load two images into python and observe the difference between each pixel.
I first create a new image.
For each of the pixels, if both are the same then set the pixel to white.
If the maximum of (Puzzle pixel - Original pixel)
for each of the colors (r,g,b) is negative, then I set the pixel to full black.
Else I set it to blue
from PIL import Image
ori = Image.open('ori.jpg', 'r')
width, height = ori.size
oripxl = list(ori.getdata())
puz = Image.open('puzzle.jpg', 'r')
oripuz = list(puz.getdata())
im = Image.new(mode="RGB", size=(width, height), )
for i in range(width * height):
x = i % width
y = i // width
if (oripxl[i] != oripuz[i]):
a = ((oripuz[i][0] - oripxl[i][0]), (oripuz[i][1] - oripxl[i][1]), (oripuz[i][2] - oripxl[i][2]))
if (max(a) < 0):
im.putpixel((x, y), (0,0,0))
else:
im.putpixel((x, y), (0,0,256))
else:
im.putpixel((x, y), (256,256,256))
im.save("out.jpg")
This is what I get in the end
Rearranging the pieces with photoshop (manual labour),
Tada! I got flag by scanning the QR
Boot2root
Sanity Check (TryHackMe)
Please use the link below to learn how to use TryHackMe platform. Submit the root flag located in /root/root.txt
Solution
Trivial, just cat the file for the flag
D00raemon (User)
User flag located in /home//user.txt
Solution
First I did a port scan of the provided IP, 2 ports are open port 22 (ssh) and port 80 (http)
Port 80 was nothing but just a default page for apache.
I then did a directory brute force on the IP and found that /wordpress
was online.
Continue my directory brute force on /wordpress
, I found /wordpress/wp-content/uploads/
Moving further in, /wordpress/wp-content/uploads/2022/12/notes.txt
was a random string.
PvWu&q563b3cwctZjL
I guess that it was the ssh password for the user and I was right!
ssh to the user account and cat the file for the flag.
D00raemon (Root)
Root flag located in /root/root.txt
Solution
Now that I got ssh access to the user account, I want to escalate our privilege to read /root/root.txt
First, letโs check what command this user can run with sudo -l
This is interesting, I can use the command
/usr/bin/csvtool trim t * --help
as any user without a password
csvtool is a powerful command line tool that can read/write to a file
I can add any character I want to the command because of the *
wildcard, just that the prefix and suffix must be the same.
Here, the organizers have hinted a lot that we need to bypass the --help
flag
So to do this, I just add -o
flag just before --help
flag. Then --help
will then be regarded as the output file.
Final command
sudo -u root /usr/bin/csvtool trim t /root/root.txt -o --help
cat -- --help
Cryptography
HMAC master
Observation
For this HMAC question, we have 3 parts to solve.
- Provide 2 different messages with the same signature
- Given a signature of a chosen message prepended with a secret, provide a different message and the signature when is prepended with the same secret
- Given a signature of a chosen message appended with a secret, provide a different message and the signature when is appended with the same secret
Solution
- MD5 is known as a weak hash because collisions have been found for the hash algorithm. To solve this question, go to this website and take the 2 messages
- MD5 is vulnerable to length extension attack because it is an MerkleโDamgรฅrd hash function. To solve this question, use hash_extender to construct the second message and the signature
- This is more interesting compared to the previous 2.
Again, since MD5 is an MerkleโDamgรฅrd hash function, the hash is basically done block by block. (similar to AES-CBC)
Each block is of a size of 512 bits. If the message is less than the length, it will then be padded to a certain length before hashing.
So to solve this question, we can first take the 2 messages we got from the first question, and add padding to both of them. (same as how would md5 algorithm do it)
The padding looks something like this
08000....004000000000000
Now, these 2 messages will be 512 bits, which fits in as the first block.
From part 1, we know the hash in the first block of both messages is the same. Since the second block has the same message (the appended key), the final hash will be equal
Final input for part 3
m1 = d131dd02c5e6eec4693d9a0698aff95c2fcab58712467eab4004583eb8fb7f8955ad340609f4b30283e488832571415a085125e8f7cdc99fd91dbdf280373c5bd8823e3156348f5bae6dacd436c919c6dd53e2b487da03fd02396306d248cda0e99f33420f577ee8ce54b67080a80d1ec69821bcb6a8839396f9652b6ff72a7080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000
m2 = d131dd02c5e6eec4693d9a0698aff95c2fcab50712467eab4004583eb8fb7f8955ad340609f4b30283e4888325f1415a085125e8f7cdc99fd91dbd7280373c5bd8823e3156348f5bae6dacd436c919c6dd53e23487da03fd02396306d248cda0e99f33420f577ee8ce54b67080280d1ec69821bcb6a8839396f965ab6ff72a7080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000
E-Signature
I made a E-Signature service! Try it now by connect with nc 54.255.181.88 3000
Observation
The goal of this challenge is to obtain the signature of gimmetheflag
Here we are given a signature oracle that will sign any message user provides as long as it is not the target message.
Solution
Let \(m\) be the target message
Goal : Find \(m^d \equiv mod(n)\)
To solve this question,
- Send \(2 \cdot m \mod(n)\) to the oracle to obtain \((2m)^d \mod(n)\)
- Send \(2^{-1} \mod(n)\) to the oracle to obtain \(2^{-d} \mod(n)\)
- Multiply both answers
Send the answer to the server to get the flag
Solution script
from pwn import *
from Crypto.Util.number import bytes_to_long, long_to_bytes
msg = b"gimmetheflag"
r = remote("54.255.181.88", 3000)
r.sendline("3")
r.recvuntil("n=")
n = int(r.recvline().decode())
r.recvuntil("e=")
e = int(r.recvline().decode())
r.sendline("1")
r.sendline(long_to_bytes(bytes_to_long(msg) * 2).hex())
r.recvuntil("Signature: ")
m2 = int(r.recvline(), 16)
r.sendline("1")
r.sendline(hex(pow(2, -1, n)))
r.recvuntil("Signature: ")
s2 = int(r.recvline(), 16)
r.sendline("2")
r.sendline(long_to_bytes((m2 * s2) % n).hex())
r.interactive()
Corrupted
My private key was corrupted, luckily still got half is not corrupted Can you help me to recover the private key? I got an important file on my server Login my server using godam@178.128.106.114 port 2222
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQC0hAS5rQUKGw6oaJ4+PUDJrq537SAtuINquhoZu17GeLJhMPxR
vVfYoy7SVqetqgE0ZCpxyz+DOh7fX0eLVJByoMDB4ljV4ipjP4tN+pCMOt1ZTi2x
mgzV1fnlU7cYF+s9C1SazDPAdzdVRQGxMGsKX5Y9nWqLe37Uju6x2bOOHwIDAQAB
????????????????????????????????????????????????????????????????
????????????????????????????????????????????????????????????????
????????????????????????????????????????????????????????????????
???????????????????????????????Xe3iK5RisoeJtgdOXHp0+6oC+zbbyzpS4
P6z0852lAkEA5zyeIqW0dBjZW/fRP3+ZhZ6BojWU40DCQygcZXk2vcGB????????
????????????????????????????????????????????????????????????????
????????????????????????????????????????????????????????????????
????????????????????????????????????????????????????????????????
????????????????????????????????????????????????????????????????
????????????????????????????????????????????
-----END RSA PRIVATE KEY-----
Observation
We are given a partial (PKCS#1) private RSA key in ASN.1 PEM.
Our goal is to obtain the \(p\) and \(q\) to connect the server
Solution
Previously I have been using the ASN.1 convertor as a black box, but for this question, I will have to deep dive into the structure of ASN.1.
In short, it uses the Typeโlengthโvalue encoding scheme.
The first part of the data will be the type, followed by length and value.
You can learn more about it here
With this in mind, we can then look at the PKCS#1 structure
RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER, -- n
publicExponentINTEGER, -- e
privateExponent INTEGER, -- d
prime1INTEGER, -- p
prime2INTEGER, -- q
exponent1 INTEGER, -- d mod (p-1)
exponent2 INTEGER, -- d mod (q-1)
coefficient INTEGER, -- (inverse of q) mod p
otherPrimeInfos OtherPrimeInfos OPTIONAL
}
The first 2 integer should be \(n\) and \(e\)
By converting the first known part of the key from base64 to hex,
Here we filter for the byte 0x02
because that stands for the type of integer.
Observe the part with 02 81 81 01
This is the starting of the modulus \(n\). The length of \(n\) is 0x81 bytes which is 1032 bits. This means that the \(n\) we have is most likely 1024 bit (the first hex byte of n is 01)
There is also a part that shows 02 03 01 00 01
This correspond to our exponent \(e\), which is 0x100001
So, this first part of the key has leaked the public modulus and exponent to us.
Now letโs take a look at the second part.
Note: Before we convert from base64 to hex, we must make sure that the
starting character position \(\times\ 6 \mod 8 = 0\)
This is because each base64 character corresponds to 6 bits, the formulation is to make sure that the hex is correctly aligned
So we start from e3iK5R...
instead of Xe3iK5R...
Observe part 02 41 00
.
We donโt know whether the value before this was a private exponent or another prime, but we can be certain that the part after this will be one of the primes. This is because the byte length is 0x41, which is 512
bit :)
So now we know the higher bits of a prime p
, we know the modulus n
and the exponent e
.
We can then use the classical coppersmith method to recover the prime!
Sage Script to find p
from Crypto.Util.number import bytes_to_long
n = [0xb4,0x84,0x04,0xb9,0xad,0x05,0x0a,0x1b,0x0e,0xa8,0x68,0x9e,0x3e,0x3d,0x40,0xc9,0xae,0xae,0x77,0xed,0x20,0x2d,0xb8,0x83,0x6a,0xba,0x1a,0x19,0xbb,0x5e,0xc6,0x78,0xb2,0x61,0x30,0xfc,0x51,0xbd,0x57,0xd8,0xa3,0x2e,0xd2,0x56,0xa7,0xad,0xaa,0x01,0x34,0x64,0x2a,0x71,0xcb,0x3f,0x83,0x3a,0x1e,0xdf,0x5f,0x47,0x8b,0x54,0x90,0x72,0xa0,0xc0,0xc1,0xe2,0x58,0xd5,0xe2,0x2a,0x63,0x3f,0x8b,0x4d,0xfa,0x90,0x8c,0x3a,0xdd,0x59,0x4e,0x2d,0xb1,0x9a,0x0c,0xd5,0xd5,0xf9,0xe5,0x53,0xb7,0x18,0x17,0xeb,0x3d,0x0b,0x54,0x9a,0xcc,0x33,0xc0,0x77,0x37,0x55,0x45,0x01,0xb1,0x30,0x6b,0x0a,0x5f,0x96,0x3d,0x9d,0x6a,0x8b,0x7b,0x7e,0xd4,0x8e,0xee,0xb1,0xd9,0xb3,0x8e,0x1f]
n = bytes_to_long(bytes(n))
ph = [0xe7,0x3c,0x9e,0x22,0xa5,0xb4,0x74,0x18,0xd9,0x5b,0xf7,0xd1,0x3f,0x7f,0x99,0x85,0x9e,0x81,0xa2,0x35,0x94,0xe3,0x40,0xc2,0x43,0x28,0x1c,0x65,0x79,0x36,0xbd,0xc1,0x81]
ph = bytes_to_long(bytes(ph))
F.<x> = Zmod(n)[]
f = ph * (2^248) + x
p = ph*(2^248) + f.small_roots(X = 2^248, epsilon = 0.015, beta=0.49)[0]
assert n % int(p) == 0
q = n // int(p)
print(p, q)
The part to generate a private key and ssh to the target is easy after finding the primes.
Connect to the server and cat the flag!