(writeup) BYU-CTF-2023
official write-up
CRYPTO
RSA1
Flag: byuctf{too_smol}
RSA2
Flag:byuctf{rsa_is_only_secure_when_p_and_q_are_unknown}
RSA3
- Chall này cho ta data như sau:
Flag của chall này là: byuctf{coprime_means_factoring_N_becomes_much_easier}
RSA4
Flag: byuctf{hastad_broadcast_attack_is_why_e_needs_to_be_very_large}
RSA5
Flag:byuctf{NEVER_USE_SAME_MODULUS_WITH_DIFFERENT_e_VALUES}
RSA5 script2
- Chall này cho ta 1 data như sau:
- Ta thấy 2 bộ này đều cùng module n, có 2 e1 và e2, c1 và c2. Nhìn phát biết ngay sẽ cần phải tấn công kiểu nào
- Ta sẽ tấn công kiểu External Module
- Ta thấy gcd(e1,e2) = 1 –> sẽ thu được
e1*a1 + e2*a2 = 1
- Khi đó,
c1^a1 * c2^a2 ≡ (m^e1)^a1 * (m^e2)^a2 ≡ m^(e1*a1 + e2*a2) ≡ m (mod n)
- Tức là
pow(c1,a1,n) * pow(c2,a2,n)%n = m
Flag: byuctf{NEVER_USE_SAME_MODULUS_WITH_DIFFERENT_e_VALUES}
Compact


Flag: byuctf{well its definitely more compact}
REV
Rev Eng
- Sau khi tệp
gettingBetter
được tải xuống, tôi thực hiện check
file.

- Biết được đây là tệp thực thi, tôi đã chạy tệp thực thi và nhập "paspharase" là một chuỗi bất kì và chương trình cho tôi biết điều đó không chính xác.

- Vì vậy tôi đã xác định được yêu cầu của thử thách là nhập đúng pass để lấy được flag. Bắt đầu quá trình debug bằng công cụ GDB:
- Tôi sẽ sử dụng lệnh sau để biết hàm
main
làm những gì:


- Tôi tìm được hàm
check passphrase
tại địa chỉ 0x00005555555551bf
(có thể đoán được đây là hàm quyết định để ta có được flag) vì vậy tôi đặt break tại địa chỉ này và bắt đầu debug.


- passphrase đúng sẽ là
She turned me into a newt

Flag:byuctf{i_G0t_3etTeR!_1975}
PWN
2038
- Ý tưởng
- Đây là bài time UNIX, đầu vào cho ta nhập vào 1 chuỗi và đổi chuỗi đó sang số thông qua hàm atoi()
- Nói chung chỉ cần thoả các điều kiện thì nó sẽ in ra flag, chú ý đề bài
2038
, nên ta sẽ dùng các trang web UNIX time converter
để convert sang số.
- Khai thác:
- Do đề bài hint cho ta năm 2038, nên ta sẽ convert năm 2038 còn ngày tháng bao nhiêu cũng được
- Kết quả

VFS-1
- Ý tưởng:
- Đọc code khá lú, đại loại sẽ tạo (option 1) và đọc (option 4), khi ta debug ta thấy, flag sẽ được đọc và ghi vào heap như này


- Ta debug đến strcpy thì thấy nó như này

- Nghĩa là nó lấy flag bỏ vào địa chỉ 0x007fffffffdab0, lưu trong stack
- Flag của chúng ở đây
- Chuỗi chúng ta nhập ở đây
- Nghĩa là nếu chúng ta có thể nối chuỗi ở
0x007fff9417e550
với flag thành 1 chuỗi không có null byte thì %s nó sẽ in ra hết và kèm flag của ta vào
- Thực thi
- Do ta chỉ được nhập 10 lần và chỉ cần 10 lần đó có thể tạo được một chuỗi dài không có null byte
- Chương trình cho ta nhập 32 byte tên và 256 byte nội dung nên ta vẫn nhập đủ 32 và 256 byte đó
- script:

Shellcode

- thông qua source code ta sẽ nhập 4 lần shellcode, và cuối cùng thực thi shellcode ngay địa chỉ mình nhập lần đầu
- idea ban đầu là chia shellcode làm 4 phần

3 phần đầu
phần 4 k cần thiết, nhập tuỳ thích (lệnh 'nop' vẫn được)
- tới đây chương trình sẽ thực thi shellcode mình bằng cách gọi rdx

- khi mình đi sâu vào lệnh bằng
si
thì thấy có mỗi 10 byte đầu của mình


- thấy là shellcode ta nhập cách nhau
- đổi hướng: gọi hàm read lên để chèn shellcode
- argument của read :

rax : 0
rdi : 0 (fd: file descriptor là stdin nên là 0)
rsi : rdx ( *buf: vị trí ghi, lấy luôn tại rdx)
rdx : rdx ( size : kích thước ghi, chắc khỏi đổi)

có sẵn rax = NULL, rdi = NULL
- vậy shellcode gọi thằng read là

5 byte là quá đủ cho 1 cuộc tình
- sau khi gọi thành công thằng read, nó sẽ nhập lại vị trí mình nhập 5 byte gọi read lên, nên mình sẽ bypass qua 5 byte đó r thêm shellcode
- nó sẽ thực thi tiếp vị trí sau syscall gọi read (byte thứ 6)

đóng server rồi nên chạy local đỡ
Flag: byuctf{1m_als0_pretty_new_t0_pwn_s0_h0p3_it_was_g00d}
frorg
- Ý tưởng
- Ta chú ý dòng này
read(0, (char *)&v4[1] + 10 * i, 0xAuLL);
, đại loại nó sẽ read vào địa chỉ v4 + 10 * i
vậy các địa chỉ là sẽ liên tụ với nhau
- Ta debug thử thì đúng như lí thuyết, 10 byte tiếp theo sẽ là
- Vậy nó sẽ là ret2libc, chỉ là cực hơn thôi
- Chú ý chỗ này
- số 2 trong này sử dụng để biểu diễn số vòng lặp, nên khi ghi đè nó thì phải trả lại cho nó giá trị đúng, ở câu lệnh này
0x401275 <main+139> cmp DWORD PTR [rbp-0x4], eax
- khai thác
- offset
0x0461616161
như nó ở trên nếu ghi đè giá trị vòng lặp nó sẽ lặp không đúng, nên ta sẽ phải có byte 0x4
để trả lại giá trị cho nó
- Trước lúc ghi đè
0x4011e5
là giá trị của pop rdi, ban đầu tính nhảy vào hàm pop rdi nhưng lằng nhằng quá nên ra lấy gadget, ta ghi đè được như này
- 4 byte null này để ghi đè cả địa chỉ này thành
0x007ffd08da7fa8| : 0x000000004011e5
- Vậy chúng ta đã hiểu cách hoạt động rồi nên chỉ wu đến đây thoi =)), các lần sau cũng tương tự như vậy thôi, có thể dùng one_gadget hoặc system chắc là đều được
Chú ý
- Chỗ này tại sao phải tách ra 2 part thì do 4 byte null nó ở trên, nên mới phải tách binsh ra 4byte và 2byte và gửi vào chứ không được
b"\0" * 4 + p64(binsh)
vì nó đã 12byte rồi

MISC
xkcd 2637
- Ta thấy server gửi cho ta 500 phép tính, nhìn thì tưởng là cộng bình thường, nhưng khum, nó là như thế này:
- 101 + 1010101, tưởng là 1010202 nhưng mà khum phải, 101 –> XI (10 là X, 1 là I), và 1010101–> XXXI, và ta đổi 2 số này ra số thập phân là 11 và 31, cộng vào ra 42, và ta lại phải chuyển về số la mã là XLII, rùi ta lại phải chuyển thành
105011
, đó mới chính là đáp án đúng
- script:
from pwn import *
io = remote('byuctf.xyz',40014)
def roman_to_int(s):
roman_numeral_map = {
'I': 1, 'IV': 4, 'V': 5, 'IX': 9, 'X': 10, 'XL': 40,
'L': 50, 'XC': 90, 'C': 100, 'CD': 400, 'D': 500,
'CM': 900, 'M': 1000
}
i, num = 0, 0
while i < len(s):
if i + 1 < len(s) and s[i:i+2] in roman_numeral_map:
num += roman_numeral_map[s[i:i+2]]
i += 2
else:
num += roman_numeral_map[s[i]]
i += 1
return num
def int_to_roman(num):
roman_numeral_map = {
1: 'I', 4: 'IV', 5: 'V', 9: 'IX', 10: 'X', 40: 'XL',
50: 'L', 90: 'XC', 100: 'C', 400: 'CD', 500: 'D',
900: 'CM', 1000: 'M'
}
result = ''
for value, numeral in sorted(roman_numeral_map.items(), reverse=True):
while num >= value:
result += numeral
num -= value
return result
def convert_numbers(string):
conversion_rules = {
"10": "X",
"1": "I",
"110": "IX",
"1": "I",
"5": "V",
"50": "L",
"100":"C",
"500":"D",
"1000":"M"
}
result = ""
i = 0
while i < len(string):
found = False
for rule in conversion_rules:
if string[i:].startswith(rule):
result += conversion_rules[rule]
i += len(rule)
found = True
break
if not found:
result += string[i]
i += 1
return result
def reverse_convert_numbers(string):
conversion_rules = {
"X": "10",
"I": "1",
"I": "1",
"IX": "110",
"V": "5",
"L": "50",
"C": "100",
"D": "500",
"M": "1000"
}
result = ""
i = 0
while i < len(string):
found = False
for rule in conversion_rules:
if string[i:].startswith(rule):
result += conversion_rules[rule]
i += len(rule)
found = True
break
if not found:
result += string[i]
i += 1
return result
io.recvuntil(b'flag!\n')
for i in range(500):
x = (io.recvuntil(b'= ',drop=True).decode())
lst = []
lst = x.split(' ')
send = ''
if lst[1] =='+':
a = convert_numbers(lst[0])
b = convert_numbers(lst[2])
num_a = roman_to_int(a)
num_b = roman_to_int(b)
x = num_a + num_b
lama = int_to_roman(x)
data = reverse_convert_numbers(lama)
a = (data.encode("ascii"))
io.sendline((a))
if lst[1] == '*':
a = convert_numbers(lst[0])
b = convert_numbers(lst[2])
num_a = roman_to_int(a)
num_b = roman_to_int(b)
x = num_a * num_b
lama = int_to_roman(x)
data = reverse_convert_numbers(lama)
a = (data.encode("ascii"))
io.sendline((a))
io.interactive()
Flag: không nhớ flag vì server đóng rùi :(((
Sanity Check
- Check discord là được à :-1:

Flag: byuctf{yes_this_is_sanity_check_flag_race_for_who_gets_1st_blood}
006 I
- Ta sử dụng hashcat để tìm lại bản rõ của hash này
- Ta sử dụng command như sau:
- tức là tấn công theo mã MD5 dựa vào danh sách các mật khẩu ở file
rockyou.txt

- Tiếp theo, để xuất hiện file
cracked.txt
, ta thêm --show
ở sau command trên

- Vào file
cracked.txt
là thấy được flag thui :v:

Flag:byuctf{brittishhottie}