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;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> p > img {
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.footnotes {
|
.footnotes {
|
||||||
font-size: .9em;
|
font-size: .9em;
|
||||||
line-height: 1.2;
|
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